Skip to content

Commit 778b7cd

Browse files
committed
refactor(storage): DRY DocumentRepository
1 parent ee8a81c commit 778b7cd

3 files changed

Lines changed: 37 additions & 58 deletions

File tree

src/everspring_mcp/storage/repository.py

Lines changed: 23 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,27 @@ def __init__(self, db: aiosqlite.Connection) -> None:
180180
"""
181181
self.db = db
182182

183+
def _to_row_tuple(self, doc: DocumentRecord) -> tuple[Any, ...]:
184+
"""Convert a DocumentRecord to a tuple for sqlite insertion."""
185+
return (
186+
doc.id,
187+
doc.url,
188+
doc.title,
189+
doc.module,
190+
doc.submodule,
191+
doc.major_version,
192+
doc.minor_version,
193+
doc.patch_version,
194+
doc.content_hash,
195+
doc.file_path,
196+
doc.s3_key,
197+
doc.size_bytes,
198+
doc.scraped_at.isoformat(),
199+
doc.synced_at.isoformat() if doc.synced_at else None,
200+
doc.schema_version,
201+
int(doc.is_indexed),
202+
)
203+
183204
async def insert(self, doc: DocumentRecord) -> None:
184205
"""Insert a new document.
185206
@@ -197,24 +218,7 @@ async def insert(self, doc: DocumentRecord) -> None:
197218
schema_version, is_indexed
198219
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
199220
""",
200-
(
201-
doc.id,
202-
doc.url,
203-
doc.title,
204-
doc.module,
205-
doc.submodule,
206-
doc.major_version,
207-
doc.minor_version,
208-
doc.patch_version,
209-
doc.content_hash,
210-
doc.file_path,
211-
doc.s3_key,
212-
doc.size_bytes,
213-
doc.scraped_at.isoformat(),
214-
doc.synced_at.isoformat() if doc.synced_at else None,
215-
doc.schema_version,
216-
int(doc.is_indexed),
217-
),
221+
self._to_row_tuple(doc),
218222
)
219223
await self.db.commit()
220224
logger.debug("Inserted document: %s", doc.id)
@@ -246,24 +250,7 @@ async def upsert(self, doc: DocumentRecord) -> None:
246250
ELSE documents.is_indexed
247251
END
248252
""",
249-
(
250-
doc.id,
251-
doc.url,
252-
doc.title,
253-
doc.module,
254-
doc.submodule,
255-
doc.major_version,
256-
doc.minor_version,
257-
doc.patch_version,
258-
doc.content_hash,
259-
doc.file_path,
260-
doc.s3_key,
261-
doc.size_bytes,
262-
doc.scraped_at.isoformat(),
263-
doc.synced_at.isoformat() if doc.synced_at else None,
264-
doc.schema_version,
265-
int(doc.is_indexed),
266-
),
253+
self._to_row_tuple(doc),
267254
)
268255
await self.db.commit()
269256
logger.debug("Upserted document: %s", doc.id)

src/everspring_mcp/vector/embeddings.py

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,8 @@ async def embed(self, texts: list[str], batch_size: int) -> list[dict[str, Any]]
131131
]
132132

133133

134-
class BGESlimStrategy(EmbeddingStrategy):
135-
"""Concrete strategy for the BGE-Base (Slim) model."""
136-
137-
@property
138-
def tier_name(self) -> str:
139-
return SLIM_TIER
134+
class DenseEmbeddingStrategy(EmbeddingStrategy):
135+
"""Base strategy for models that only return dense embeddings."""
140136

141137
async def embed(self, texts: list[str], batch_size: int) -> list[dict[str, Any]]:
142138
model = await asyncio.to_thread(self._ensure_loaded)
@@ -153,27 +149,21 @@ async def embed(self, texts: list[str], batch_size: int) -> list[dict[str, Any]]
153149
]
154150

155151

156-
class BGEXSlimStrategy(EmbeddingStrategy):
152+
class BGESlimStrategy(DenseEmbeddingStrategy):
153+
"""Concrete strategy for the BGE-Base (Slim) model."""
154+
155+
@property
156+
def tier_name(self) -> str:
157+
return SLIM_TIER
158+
159+
160+
class BGEXSlimStrategy(DenseEmbeddingStrategy):
157161
"""Concrete strategy for the BGE-Small (X-Slim) model."""
158162

159163
@property
160164
def tier_name(self) -> str:
161165
return XSLIM_TIER
162166

163-
async def embed(self, texts: list[str], batch_size: int) -> list[dict[str, Any]]:
164-
model = await asyncio.to_thread(self._ensure_loaded)
165-
embeddings = await asyncio.to_thread(
166-
model.encode,
167-
texts,
168-
batch_size=batch_size,
169-
convert_to_numpy=True,
170-
show_progress_bar=False,
171-
normalize_embeddings=True,
172-
)
173-
return [
174-
{"dense": emb.tolist(), "sparse": None} for emb in np.asarray(embeddings)
175-
]
176-
177167

178168
class StrategyFactory:
179169
"""Factory for creating embedding strategies based on tier."""
@@ -240,6 +230,7 @@ async def prefetch_model(self) -> None:
240230

241231
__all__ = [
242232
"EmbeddingStrategy",
233+
"DenseEmbeddingStrategy",
243234
"BGEM3Strategy",
244235
"BGESlimStrategy",
245236
"BGEXSlimStrategy",

tests/test_vector_indexer.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,16 +190,17 @@ async def mark_indexed(self, doc_ids: list[str]) -> int:
190190
content="c1",
191191
metadata={"module": "spring-boot"},
192192
embedding=[0.1, 0.2],
193+
sparse_weights=None,
193194
),
194195
types.SimpleNamespace(
195196
chunk_id="doc1-1",
196197
document_id="doc1",
197198
content="c2",
198199
metadata={"module": "spring-boot"},
199200
embedding=[0.3, 0.4],
201+
sparse_weights=None,
200202
),
201203
]
202-
203204
flushed_chunks, flushed_docs = await VectorIndexer._flush_vector_payloads(
204205
indexer,
205206
payloads=payloads,

0 commit comments

Comments
 (0)