Optimized rebuild --embed-only to use batch updates via LanceDB merge_insert instead of individual chunk updates
This commit is contained in:
parent
3e18a5f2ff
commit
e548a51ff7
3 changed files with 67 additions and 9 deletions
|
|
@ -1,6 +1,10 @@
|
|||
# Changelog
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- **Rebuild Performance**: Optimized `rebuild --embed-only` to use batch updates via LanceDB's `merge_insert` instead of individual chunk updates, and skip chunks with unchanged embeddings
|
||||
|
||||
## [0.19.4] - 2025-11-28
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -748,21 +748,39 @@ class HaikuRAG:
|
|||
"""Re-embed all chunks without changing chunk boundaries."""
|
||||
for doc in documents:
|
||||
assert doc.id is not None
|
||||
chunks = await self.chunk_repository.get_by_document_id(doc.id)
|
||||
if not chunks:
|
||||
|
||||
# Get raw chunk records directly from LanceDB
|
||||
chunk_records = list(
|
||||
self.store.chunks_table.search()
|
||||
.where(f"document_id = '{doc.id}'")
|
||||
.to_pydantic(self.store.ChunkRecord)
|
||||
)
|
||||
if not chunk_records:
|
||||
continue
|
||||
|
||||
# Batch embed all chunk contents
|
||||
contents = [chunk.content for chunk in chunks]
|
||||
contents = [rec.content for rec in chunk_records]
|
||||
embeddings = await self.chunk_repository.embedder.embed(contents)
|
||||
|
||||
# Update each chunk with new embedding
|
||||
for chunk, embedding in zip(chunks, embeddings):
|
||||
assert chunk.id is not None
|
||||
self.store.chunks_table.update(
|
||||
where=f"id = '{chunk.id}'",
|
||||
values={"vector": embedding},
|
||||
# Build updated records only for chunks with changed embeddings
|
||||
updated_records = [
|
||||
self.store.ChunkRecord(
|
||||
id=rec.id,
|
||||
document_id=rec.document_id,
|
||||
content=rec.content,
|
||||
metadata=rec.metadata,
|
||||
order=rec.order,
|
||||
vector=embedding,
|
||||
)
|
||||
for rec, embedding in zip(chunk_records, embeddings)
|
||||
if rec.vector != embedding
|
||||
]
|
||||
|
||||
# Batch update chunks with changed embeddings
|
||||
if updated_records:
|
||||
self.store.chunks_table.merge_insert(
|
||||
"id"
|
||||
).when_matched_update_all().execute(updated_records)
|
||||
|
||||
yield doc.id
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,42 @@ async def test_rebuild_embed_only(qa_corpus: Dataset, temp_db_path):
|
|||
assert chunk.content == chunk_contents_before[chunk.id]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rebuild_embed_only_skips_unchanged(qa_corpus: Dataset, temp_db_path):
|
||||
"""Test embed-only rebuild skips chunks with unchanged embeddings."""
|
||||
async with HaikuRAG(temp_db_path) as client:
|
||||
doc = await client.create_document(content=qa_corpus["document_extracted"][0])
|
||||
assert doc.id is not None
|
||||
|
||||
# Get embeddings before rebuild
|
||||
records_before = list(
|
||||
client.store.chunks_table.search()
|
||||
.where(f"document_id = '{doc.id}'")
|
||||
.to_pydantic(client.store.ChunkRecord)
|
||||
)
|
||||
embeddings_before = {rec.id: rec.vector for rec in records_before}
|
||||
|
||||
# Run embed-only rebuild with same embedder - embeddings should be identical
|
||||
processed_ids = [
|
||||
doc_id
|
||||
async for doc_id in client.rebuild_database(mode=RebuildMode.EMBED_ONLY)
|
||||
]
|
||||
assert doc.id in processed_ids
|
||||
|
||||
# Get embeddings after rebuild
|
||||
records_after = list(
|
||||
client.store.chunks_table.search()
|
||||
.where(f"document_id = '{doc.id}'")
|
||||
.to_pydantic(client.store.ChunkRecord)
|
||||
)
|
||||
embeddings_after = {rec.id: rec.vector for rec in records_after}
|
||||
|
||||
# Embeddings should be identical (same content, same embedder)
|
||||
assert embeddings_before.keys() == embeddings_after.keys()
|
||||
for chunk_id in embeddings_before:
|
||||
assert embeddings_before[chunk_id] == embeddings_after[chunk_id]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rebuild_rechunk(qa_corpus: Dataset, temp_db_path):
|
||||
"""Test rechunk rebuild: re-chunks from content without accessing source files."""
|
||||
|
|
|
|||
Loading…
Reference in a new issue