Skip to content

Commit 4cfaf0b

Browse files
Merge pull request #97 from GoodbyePlanet/fix/bm25-sparse-text-metadata
fix: include annotations, http route, package, and name in BM25 text
2 parents 65eea62 + 9675786 commit 4cfaf0b

4 files changed

Lines changed: 56 additions & 5 deletions

File tree

docs/ingestion.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,12 @@ The whole embedding text (preamble + signature + docstring + source) is budgeted
9999
100100
#### Sparse: `_build_bm25_text`
101101

102-
Simpler — only the functional code text, no metadata preamble:
102+
No preamble sentence, but folds in the same high-signal identifiers as the dense
103+
preamble so keyword search on an annotation, route, package, or symbol name still
104+
gets sparse matches:
103105

104106
```
105-
signature + docstring + source
107+
name + package + annotations (@-prefixed) + HTTP method/route + signature + docstring + source
106108
```
107109

108110
This text is then pre-processed by `split_code_identifiers` (see [sparse-vectors.md](sparse-vectors.md)) before BM25 encoding.
@@ -189,7 +191,7 @@ All `CodeSymbol` fields are stored verbatim, plus:
189191

190192
**No embedding retry** — a transient API error on either embedding call causes the file to be silently skipped, leaving its existing index stale indefinitely. There is no exponential backoff or retry queue. Reindexing requires either a force reindex or waiting for the file's content to change.
191193

192-
**BM25 text excludes metadata**`_build_bm25_text` produces only `signature + docstring + source`. Metadata present in the dense text (service name, language, symbol type) is absent. A BM25 query for "Python method" will not match unless the word "Python" or "method" appears in the source code itself.
194+
**BM25 text still omits some dense-only metadata**`_build_bm25_text` folds in name, package, annotations, and HTTP method/route, but the dense preamble's service name, language, and symbol-type phrasing (e.g. "Java method") are still dense-only. A BM25 query for "Python method" will not match unless the word "Python" or "method" appears elsewhere in the folded-in fields or the source code itself.
193195

194196
**delete-before-upsert gap** — The pipeline deletes all entries for a file before upserting the new ones. If the process is interrupted between delete and upsert, the file has no index entries. The next incremental run will redownload and reindex the file correctly — but until then, queries miss the file entirely.
195197

docs/sparse-vectors.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,6 @@ SparseVectorParams(index=SparseIndexParams(on_disk=False))
104104

105105
**In-memory sparse index**`on_disk=False` is not configurable. On very large codebases, the sparse index memory footprint may become a concern. Qdrant supports `on_disk=True` for sparse vectors, but switching requires dropping and recreating the collection.
106106

107-
**BM25 text excludes metadata** — the text passed to BM25 (`_build_bm25_text`) contains only `signature + docstring + source`. The rich metadata preamble used for dense embeddings (service name, language, symbol type, HTTP routes) is absent. A keyword search for "POST /orders" or "Java method" will not match via the sparse path unless those strings appear literally in the source code.
107+
**BM25 text still omits some dense-only metadata** — the text passed to BM25 (`_build_bm25_text`) folds in name, package, annotations, and HTTP method/route alongside `signature + docstring + source`, but the dense preamble's service name, language, and symbol-type phrasing (e.g. "Java method") remain dense-only. A keyword search for "POST /orders" now matches via the sparse path; a search for "Java method" still will not, unless those words appear literally in the source code.
108108

109109
**Expanded form affects IDF statistics**`split_code_identifiers` appends the expanded form, making each document approximately twice as long as the raw source. BM25's document length normalization (the `b` parameter in the BM25 formula) is computed over this expanded length, which may reduce scores for long symbols relative to what they would be with raw text.

server/indexer/pipeline.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,17 @@ def _build_embedding_text(
111111

112112

113113
def _build_bm25_text(symbol: CodeSymbol) -> str:
114-
parts = []
114+
extras = symbol.extras or {}
115+
parts = [symbol.name]
116+
if symbol.package:
117+
parts.append(symbol.package)
118+
if symbol.annotations:
119+
parts.extend(
120+
f"@{a}" if not a.startswith("@") else a for a in symbol.annotations
121+
)
122+
if http_method := extras.get("http_method"):
123+
route = extras.get("http_route") or ""
124+
parts.append(f"{http_method} {route}".strip())
115125
if symbol.signature:
116126
parts.append(symbol.signature)
117127
if symbol.docstring:

tests/test_pipeline.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,45 @@ def test_bm25_text_contains_signature_and_source() -> None:
135135
assert "void placeOrder(PlaceOrderRequest req) {}" in text
136136

137137

138+
def test_bm25_text_includes_name_package_annotations_and_http_route() -> None:
139+
sym = CodeSymbol(
140+
name="placeOrder",
141+
symbol_type="method",
142+
language="java",
143+
source="void placeOrder(PlaceOrderRequest req) {}",
144+
file_path="svc/OrderController.java",
145+
start_line=10,
146+
end_line=12,
147+
package="com.example.orders",
148+
annotations=["RestController", "PostMapping"],
149+
signature="void placeOrder(PlaceOrderRequest req)",
150+
docstring="Places an order.",
151+
extras={"http_method": "POST", "http_route": "/orders"},
152+
)
153+
text = _build_bm25_text(sym)
154+
assert "placeOrder" in text
155+
assert "com.example.orders" in text
156+
assert "@RestController" in text
157+
assert "@PostMapping" in text
158+
assert "POST /orders" in text
159+
160+
161+
def test_bm25_text_annotation_already_prefixed_not_double_prefixed() -> None:
162+
sym = CodeSymbol(
163+
name="placeOrder",
164+
symbol_type="method",
165+
language="java",
166+
source="void placeOrder() {}",
167+
file_path="svc/Order.java",
168+
start_line=1,
169+
end_line=1,
170+
annotations=["@Deprecated"],
171+
)
172+
text = _build_bm25_text(sym)
173+
assert "@@Deprecated" not in text
174+
assert "@Deprecated" in text
175+
176+
138177
def test_bm25_text_excludes_preamble() -> None:
139178
sym = CodeSymbol(
140179
name="placeOrder",

0 commit comments

Comments
 (0)