From 895b5f0532473d880a25cb1c37118a21a2f41fa8 Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Wed, 19 Aug 2026 13:06:39 +0300 Subject: [PATCH] Refresh source-backed documents in place on a FULL rebuild FULL rebuild deleted a document before re-ingesting it from its URI, and the handler around that logged and continued. A 404, a timeout or any conversion error therefore removed the document permanently. Deleting after a successful create is not an alternative: create_document_from_source resolves the same URI to the existing document and updates it in place, so a trailing delete would remove the freshly rebuilt row. Refresh in place instead. create_document_from_source takes an internal force flag that skips the revision and MD5 short-circuits, so an unchanged source is still re-converted, re-chunked and re-embedded into the existing document, and the document id survives a rebuild. A failed refresh now falls through to the stored-content path rather than skipping the document: FULL recreates the chunks table before the loop, so skipping left the document present but unsearchable until the next rebuild. The pending-batch flush moves out of the try. A failed flush is a lost write and should abort the rebuild, not be logged and skipped. --- CHANGELOG.md | 1 + haiku_rag_slim/haiku/rag/client/documents.py | 14 +++- haiku_rag_slim/haiku/rag/client/rebuild.py | 47 +++++++---- ..._failure_falls_back_to_stored_content.yaml | 82 +++++++++++++++++++ ..._source_failure_is_logged_and_skipped.yaml | 42 ---------- tests/test_rebuild.py | 66 +++++++++------ 6 files changed, 163 insertions(+), 89 deletions(-) create mode 100644 tests/cassettes/test_rebuild/test_rebuild_full_source_failure_falls_back_to_stored_content.yaml delete mode 100644 tests/cassettes/test_rebuild/test_rebuild_full_source_failure_is_logged_and_skipped.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 1daa4601..0731b93b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ ### Fixed +- A FULL rebuild no longer deletes a source-backed document before re-ingesting it: it refreshes the document in place, so the document id is preserved and a failed fetch or conversion falls back to rebuilding from stored content instead of losing the document. - `DocumentRepository.delete_all` recreated `document_items` from `DocumentItemRecord` instead of `get_document_items_arrow_schema()`, returning `picture_data` as `binary` rather than `large_binary`. - `server.json` runtime arguments are `mcp --stdio`, was `serve --mcp`. diff --git a/haiku_rag_slim/haiku/rag/client/documents.py b/haiku_rag_slim/haiku/rag/client/documents.py index 7257fcbd..d3256a69 100644 --- a/haiku_rag_slim/haiku/rag/client/documents.py +++ b/haiku_rag_slim/haiku/rag/client/documents.py @@ -677,6 +677,7 @@ async def create_document_from_source( sources: "list[Source] | None" = None, source_id: str | None = None, metadata_provider: "MetadataProvider | None" = None, + force: bool = False, ) -> Document | list[Document]: """Create or update document(s) from a file path, directory, or URL. @@ -685,6 +686,10 @@ async def create_document_from_source( - If MD5 changed, updates the document - If no document exists, creates a new one + ``force`` skips both freshness checks so an unchanged source is re-converted, + re-chunked and re-embedded into the existing document. Internal: rebuild uses + it to refresh a document in place instead of deleting and recreating it. + If ``uri`` is provided, it overrides the URI auto-derived from the source (which is normally ``file://`` for local files or the URL for remote sources). Not supported for directory sources, which produce one document @@ -730,6 +735,7 @@ async def create_document_from_source( sources=sources, source_id=source_id, metadata_provider=metadata_provider, + force=force, ) assert isinstance(doc, Document) documents.append(doc) @@ -784,7 +790,7 @@ async def create_document_from_source( stored_revision = ( (existing_doc.metadata or {}).get("source_revision") if existing_doc else None ) - if existing_doc and stored_revision: + if existing_doc and stored_revision and not force: current_revision = await fetcher.head(source_str) if current_revision == stored_revision: return await _refresh_doc_metadata( @@ -808,7 +814,11 @@ async def create_document_from_source( # MD5 short-circuit: the bytes are unchanged even if the revision wasn't. # Refresh the source-derived metadata (revision may have rolled) but skip # convert/embed/store entirely. - if existing_doc and existing_doc.metadata.get("md5") == result.content_hash: + if ( + existing_doc + and not force + and existing_doc.metadata.get("md5") == result.content_hash + ): source_meta: dict = { "content_type": result.content_type, "md5": result.content_hash, diff --git a/haiku_rag_slim/haiku/rag/client/rebuild.py b/haiku_rag_slim/haiku/rag/client/rebuild.py index 9374a2f4..9193123e 100644 --- a/haiku_rag_slim/haiku/rag/client/rebuild.py +++ b/haiku_rag_slim/haiku/rag/client/rebuild.py @@ -8,7 +8,10 @@ from typing import TYPE_CHECKING from docling_core.types.doc.document import DescriptionMetaField, PictureMeta from lancedb.pydantic import LanceModel -from haiku.rag.client.documents import check_source_accessible +from haiku.rag.client.documents import ( + check_source_accessible, + create_document_from_source, +) from haiku.rag.converters import get_converter from haiku.rag.store.compression import compress_docling_split from haiku.rag.store.engine import ChunkRecordBase @@ -807,28 +810,38 @@ async def _rebuild_full( # Try to rebuild from source if available — uses the light listing # directly, no need to load the stored content/blobs first. if light_doc.uri and check_source_accessible(light_doc.uri): - try: - # Flush pending batch before source rebuild (creates new doc) - if pending_docs: - await _flush_rebuild_batch(client, pending_docs, pending_chunks) - pending_chunks = [] - pending_docs = [] + # The refresh writes through the client, not the batch buffer, so + # anything pending has to land first. + if pending_docs: + await _flush_rebuild_batch(client, pending_docs, pending_chunks) + pending_chunks = [] + pending_docs = [] - await client.delete_document(light_doc.id) - new_doc = await client.create_document_from_source( - source=light_doc.uri, metadata=light_doc.metadata or {} + try: + # force=True: the source bytes are usually unchanged, and the + # point of a FULL rebuild is to re-convert them anyway. Updates + # in place, so a failure here cannot cost the document. + refreshed = await create_document_from_source( + client, + source=light_doc.uri, + metadata=light_doc.metadata or {}, + force=True, ) - assert isinstance(new_doc, Document) - assert new_doc.id is not None - yield new_doc.id + assert isinstance(refreshed, Document) + assert refreshed.id is not None + yield refreshed.id continue except Exception as e: - logger.error( - "Error recreating document from source %s: %s", + logger.warning( + "Rebuilding %s from source failed (%s), " + "falling back to stored content", light_doc.uri, e, ) - continue + elif light_doc.uri: + logger.warning( + "Source missing for %s, re-embedding from content", light_doc.uri + ) # Fallback: rebuild from stored content. Now we need the full # record (content + docling_pages for the round-trip write). @@ -838,8 +851,6 @@ async def _rebuild_full( if doc is None: continue assert doc.id is not None - if doc.uri: - logger.warning("Source missing for %s, re-embedding from content", doc.uri) docling_document = await converter.convert_text(doc.content, format="md") chunks = await client.chunk(docling_document) diff --git a/tests/cassettes/test_rebuild/test_rebuild_full_source_failure_falls_back_to_stored_content.yaml b/tests/cassettes/test_rebuild/test_rebuild_full_source_failure_falls_back_to_stored_content.yaml new file mode 100644 index 00000000..ff7f37ab --- /dev/null +++ b/tests/cassettes/test_rebuild/test_rebuild_full_source_failure_falls_back_to_stored_content.yaml @@ -0,0 +1,82 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '111' + content-type: + - application/json + host: + - localhost:11434 + method: POST + parsed_body: + encoding_format: base64 + input: + - Content that will vanish by rebuild time. + model: qwen3-embedding:4b + uri: http://localhost:11434/v1/embeddings + response: + headers: + content-type: + - application/json + transfer-encoding: + - chunked + parsed_body: + data: + - embedding: 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 + index: 0 + object: embedding + model: qwen3-embedding:4b + object: list + usage: + prompt_tokens: 9 + total_tokens: 9 + status: + code: 200 + message: OK +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '111' + content-type: + - application/json + host: + - localhost:11434 + method: POST + parsed_body: + encoding_format: base64 + input: + - Content that will vanish by rebuild time. + model: qwen3-embedding:4b + uri: http://localhost:11434/v1/embeddings + response: + headers: + content-type: + - application/json + transfer-encoding: + - chunked + parsed_body: + data: + - embedding: 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 + index: 0 + object: embedding + model: qwen3-embedding:4b + object: list + usage: + prompt_tokens: 9 + total_tokens: 9 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_rebuild/test_rebuild_full_source_failure_is_logged_and_skipped.yaml b/tests/cassettes/test_rebuild/test_rebuild_full_source_failure_is_logged_and_skipped.yaml deleted file mode 100644 index 6ff93083..00000000 --- a/tests/cassettes/test_rebuild/test_rebuild_full_source_failure_is_logged_and_skipped.yaml +++ /dev/null @@ -1,42 +0,0 @@ -interactions: -- request: - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate, zstd - connection: - - keep-alive - content-length: - - '111' - content-type: - - application/json - host: - - localhost:11434 - method: POST - parsed_body: - encoding_format: base64 - input: - - Content that will vanish by rebuild time. - model: qwen3-embedding:4b - uri: http://localhost:11434/v1/embeddings - response: - headers: - content-type: - - application/json - transfer-encoding: - - chunked - parsed_body: - data: - - embedding: 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 - index: 0 - object: embedding - model: qwen3-embedding:4b - object: list - usage: - prompt_tokens: 9 - total_tokens: 9 - status: - code: 200 - message: OK -version: 1 diff --git a/tests/test_rebuild.py b/tests/test_rebuild.py index bcc41f4b..01c29a55 100644 --- a/tests/test_rebuild.py +++ b/tests/test_rebuild.py @@ -523,7 +523,10 @@ async def test_rebuild_full_with_accessible_source(temp_db_path): """FULL rebuild re-ingests from source when the URI is accessible. Covers the main path in _rebuild_full (source-accessible branch): the - document is deleted and re-created from its URI, producing a new ID. + document is refreshed in place, keeping its ID. The source bytes are + unchanged since ingestion, so this also pins that the refresh bypasses the + revision and MD5 short-circuits instead of returning the document + untouched. """ async with HaikuRAG(temp_db_path, create=True) as client: with tempfile.TemporaryDirectory() as temp_dir: @@ -540,15 +543,17 @@ async def test_rebuild_full_with_accessible_source(temp_db_path): async for doc_id in client.rebuild_database(mode=RebuildMode.FULL) ] - # Original doc was deleted and a new one created; the old ID - # must not appear, and exactly one new ID must have been yielded. - assert original_id not in processed_ids - assert len(processed_ids) == 1 + assert processed_ids == [original_id] - new_doc = await client.get_document_by_id(processed_ids[0]) - assert new_doc is not None - assert new_doc.uri == source_path.as_uri() - assert "Fresh content" in new_doc.content + refreshed = await client.get_document_by_id(original_id) + assert refreshed is not None + assert refreshed.uri == source_path.as_uri() + assert "Fresh content" in refreshed.content + + # The chunks table is recreated at the top of FULL, so the refresh + # must have written new chunks for the document to stay searchable. + chunks = await client.chunk_repository.get_by_document_id(original_id) + assert chunks async def test_rebuild_title_only_reads_structural_title(temp_db_path): @@ -622,14 +627,14 @@ async def test_rebuild_title_only_handles_llm_failure(temp_db_path, monkeypatch) @pytest.mark.vcr() -async def test_rebuild_full_source_failure_is_logged_and_skipped( +async def test_rebuild_full_source_failure_falls_back_to_stored_content( temp_db_path, monkeypatch ): - """FULL rebuild logs-and-continues when re-ingesting from source fails. + """A failed source refresh must never cost the document. - Covers _rebuild_full's `except Exception` branch: when - create_document_from_source raises, the doc is skipped (no yield) and - the error is logged. Regression guard against silent failures. + Covers _rebuild_full's `except Exception` branch: when the refresh raises, + the document keeps its stored row and is rebuilt from stored content, so it + is still readable and still searchable afterwards. """ import logging @@ -644,22 +649,31 @@ async def test_rebuild_full_source_failure_is_logged_and_skipped( assert not isinstance(original, list) assert original.id is not None - # Force the source rebuild branch to raise. - async def failing_create(*args, **kwargs): + # Force the source refresh to raise. + async def failing_refresh(*args, **kwargs): raise RuntimeError("simulated ingestion failure") - monkeypatch.setattr(client, "create_document_from_source", failing_create) + monkeypatch.setattr( + rebuild_module, "create_document_from_source", failing_refresh + ) - with capture_logs(rebuild_module.logger, logging.ERROR) as records: + with capture_logs(rebuild_module.logger, logging.WARNING) as records: processed_ids = [ doc_id async for doc_id in client.rebuild_database(mode=RebuildMode.FULL) ] - assert processed_ids == [] + assert processed_ids == [original.id] + + survivor = await client.get_document_by_id(original.id) + assert survivor is not None + assert "Content that will vanish" in survivor.content + + chunks = await client.chunk_repository.get_by_document_id(original.id) + assert chunks + assert any( - "Error recreating document from source" in rec.getMessage() - for rec in records + "falling back to stored content" in rec.getMessage() for rec in records ) @@ -1412,12 +1426,10 @@ async def test_rebuild_full_flushes_pending_before_source_rebuild(temp_db_path): async for doc_id in client.rebuild_database(mode=RebuildMode.FULL) ] - # The content-path document keeps its id and must survive the - # flush that precedes the source re-ingest; the source document - # is replaced by a freshly ingested one with a new id. - assert content_doc.id in processed - assert source_doc.id not in processed - assert len(processed) == 2 + # Both documents keep their ids: the content-path one must survive + # the flush that precedes the source refresh, and the source one is + # refreshed in place. + assert sorted(processed) == sorted([content_doc.id, source_doc.id]) assert await client.store.documents_table.count_rows() == 2