Skip to content
This repository was archived by the owner on Jun 3, 2026. It is now read-only.

Commit 29c9efc

Browse files
Include code annotations in raw search
1 parent 2b78f4e commit 29c9efc

5 files changed

Lines changed: 132 additions & 12 deletions

File tree

src/api/schemas.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ class SearchRequest(BaseModel):
158158
..., min_length=1, max_length=256, pattern=r"^[\w.\-@]+$",
159159
)
160160
domains: List[str] = Field(
161-
default=["profile", "temporal", "summary", "snippet"],
161+
default=["profile", "temporal", "summary", "snippet", "code"],
162162
description="Which memory domains to search",
163163
)
164164
top_k: int = Field(default=10, ge=1, le=100)
@@ -170,7 +170,7 @@ class SearchRequest(BaseModel):
170170
@field_validator("domains")
171171
@classmethod
172172
def validate_domains(cls, v: List[str]) -> List[str]:
173-
allowed = {"profile", "temporal", "summary", "snippet"}
173+
allowed = {"profile", "temporal", "summary", "snippet", "code"}
174174
for d in v:
175175
if d not in allowed:
176176
raise ValueError(f"Invalid domain '{d}'. Allowed: {allowed}")

src/pipelines/retrieval.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,8 @@ async def search_raw(
317317
tasks.append(self._search_summary(query, user_id, top_k))
318318
if "snippet" in domain_set:
319319
tasks.append(self._search_snippet(query, user_id, top_k))
320+
if "code" in domain_set:
321+
tasks.append(self._search_code(query, user_id, top_k))
320322

321323
if not tasks:
322324
return []
@@ -561,6 +563,53 @@ async def _search_summary(
561563
logger.info(" → Summary [%s]: %d results", query, len(records))
562564
return records
563565

566+
# -- Code: Pinecone semantic search --------------------------------
567+
568+
async def _search_code(
569+
self,
570+
query: str,
571+
user_id: str,
572+
top_k: int = 10,
573+
) -> List[SourceRecord]:
574+
"""Semantic search over stored code annotations."""
575+
576+
results = await self.vector_store.search_by_text(
577+
query_text=query,
578+
top_k=top_k,
579+
filters={
580+
"user_id": user_id,
581+
"domain": "code",
582+
},
583+
)
584+
585+
records = []
586+
for r in results:
587+
metadata = dict(r.metadata)
588+
detail_parts = []
589+
for label, key in (
590+
("repo", "repo"),
591+
("file", "target_file"),
592+
("symbol", "target_symbol"),
593+
("type", "annotation_type"),
594+
("severity", "severity"),
595+
):
596+
value = metadata.get(key)
597+
if value:
598+
detail_parts.append(f"{label}={value}")
599+
600+
prefix = f"[{'; '.join(detail_parts)}] " if detail_parts else ""
601+
records.append(
602+
SourceRecord(
603+
domain="code",
604+
content=f"{prefix}{r.content}",
605+
score=r.score,
606+
metadata={"id": r.id, **metadata},
607+
)
608+
)
609+
610+
logger.info(" -> Code [%s]: %d results", query, len(records))
611+
return records
612+
564613
# -- Snippet: Pinecone semantic search ─────────────────────────────
565614

566615
def _get_snippet_store(self, user_id: str) -> BaseVectorStore:

src/schemas/retrieval.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def source_count(self) -> int:
2626
class SourceRecord:
2727
"""A single piece of evidence fetched from a data store."""
2828

29-
domain: str # "profile", "temporal", "summary", "snippet"
29+
domain: str # "profile", "temporal", "summary", "snippet", "code"
3030
content: str # the actual text
3131
score: float = 0.0 # similarity score (if applicable)
3232
metadata: Dict[str, Any] = field(default_factory=dict)

tests/api/test_memory_search_routes.py

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,38 @@ class FakeSearchPipeline:
1818

1919
def __init__(self) -> None:
2020
self.answer_calls = 0
21+
self.search_calls: list[dict[str, object]] = []
2122
self.latencies: dict[str, list[float]] = {}
2223

23-
async def search_raw(self, query: str, user_id: str, domains: list[str], top_k: int):
24+
async def search_raw(
25+
self, query: str, user_id: str, domains: list[str], top_k: int
26+
):
2427
assert query == "latency"
2528
assert user_id == "Static Key User"
26-
assert domains == ["profile", "summary"]
2729
assert top_k == 3
28-
return [
29-
SourceRecord(domain="summary", content="Low-latency summary", score=0.9),
30-
SourceRecord(domain="profile", content="work / company = XMem", score=0.7),
31-
]
30+
self.search_calls.append(
31+
{"query": query, "user_id": user_id, "domains": domains, "top_k": top_k}
32+
)
33+
34+
fixtures = {
35+
"summary": SourceRecord(
36+
domain="summary",
37+
content="Low-latency summary",
38+
score=0.9,
39+
),
40+
"profile": SourceRecord(
41+
domain="profile",
42+
content="work / company = XMem",
43+
score=0.7,
44+
),
45+
"code": SourceRecord(
46+
domain="code",
47+
content="[file=src/retry.py; symbol=RetryLoop] Timeout retry note",
48+
score=0.8,
49+
metadata={"target_file": "src/retry.py"},
50+
),
51+
}
52+
return [fixtures[domain] for domain in domains if domain in fixtures]
3253

3354
async def answer_from_sources(self, query: str, sources: list[SourceRecord]) -> str:
3455
self.answer_calls += 1
@@ -39,7 +60,12 @@ def record_latency(self, mode: str, elapsed_ms: float) -> None:
3960

4061
def get_latency_snapshot(self):
4162
return {
42-
mode: {"count": len(samples), "p50": samples[-1], "p95": samples[-1], "p99": samples[-1]}
63+
mode: {
64+
"count": len(samples),
65+
"p50": samples[-1],
66+
"p95": samples[-1],
67+
"p99": samples[-1],
68+
}
4369
for mode, samples in self.latencies.items()
4470
}
4571

@@ -78,6 +104,7 @@ def test_memory_search_route_returns_raw_hits_without_answer(memory_search_app):
78104
assert payload["data"]["total"] == 2
79105
assert payload["data"]["answer"] == ""
80106
assert payload["data"]["latency"]["raw"]["count"] == 1
107+
assert pipeline.search_calls[0]["domains"] == ["profile", "summary"]
81108
assert pipeline.answer_calls == 0
82109

83110

@@ -102,3 +129,25 @@ def test_root_search_alias_can_synthesize_answer(memory_search_app):
102129
assert payload["data"]["model"] == "fake-retrieval"
103130
assert payload["data"]["latency"]["answer"]["count"] == 1
104131
assert pipeline.answer_calls == 1
132+
133+
134+
def test_memory_search_route_accepts_code_domain(memory_search_app):
135+
app, pipeline = memory_search_app
136+
response = TestClient(app).post(
137+
"/v1/memory/search",
138+
headers={"Authorization": "Bearer test-static-key"},
139+
json={
140+
"query": "latency",
141+
"user_id": "ignored-by-auth",
142+
"domains": ["code"],
143+
"top_k": 3,
144+
},
145+
)
146+
147+
payload = response.json()
148+
149+
assert response.status_code == 200
150+
assert payload["data"]["total"] == 1
151+
assert payload["data"]["results"][0]["domain"] == "code"
152+
assert payload["data"]["results"][0]["metadata"]["target_file"] == "src/retry.py"
153+
assert pipeline.search_calls[0]["domains"] == ["code"]

tests/integration/test_retrieval_pipeline.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,20 @@ async def test_raw_search_returns_ranked_hits_without_tool_selection(
121121
{"user_id": "alice", "domain": "summary"},
122122
score=0.9,
123123
)
124+
vector_store.seed(
125+
"code-1",
126+
"RetryLoop can spin when the first retrieval attempt times out.",
127+
{
128+
"user_id": "alice",
129+
"domain": "code",
130+
"annotation_type": "bug_report",
131+
"target_symbol": "RetryLoop",
132+
"target_file": "src/retry.py",
133+
"repo": "xmem",
134+
"severity": "high",
135+
},
136+
score=0.95,
137+
)
124138
neo4j_client.seed_event(
125139
user_id="alice",
126140
date="05-11",
@@ -137,15 +151,23 @@ async def test_raw_search_returns_ranked_hits_without_tool_selection(
137151
results = await pipeline.search_raw(
138152
"latency",
139153
"alice",
140-
["profile", "temporal", "summary"],
154+
["profile", "temporal", "summary", "code"],
141155
top_k=5,
142156
)
143157

144158
assert [record.score for record in results] == sorted(
145159
[record.score for record in results],
146160
reverse=True,
147161
)
148-
assert {record.domain for record in results} == {"profile", "temporal", "summary"}
162+
assert {record.domain for record in results} == {
163+
"profile",
164+
"temporal",
165+
"summary",
166+
"code",
167+
}
168+
code_hit = next(record for record in results if record.domain == "code")
169+
assert "file=src/retry.py" in code_hit.content
170+
assert code_hit.metadata["target_symbol"] == "RetryLoop"
149171
assert not pipeline.model_with_tools.calls
150172

151173

0 commit comments

Comments
 (0)