Refactor create_document(), create_document_from_source() to use primitives
This commit is contained in:
parent
e299aed788
commit
0ab43d62cd
1 changed files with 137 additions and 22 deletions
|
|
@ -186,6 +186,99 @@ class HaikuRAG:
|
||||||
chunker = get_chunker(self._config)
|
chunker = get_chunker(self._config)
|
||||||
return await chunker.chunk(docling_document)
|
return await chunker.chunk(docling_document)
|
||||||
|
|
||||||
|
async def _store_document_with_chunks(
|
||||||
|
self,
|
||||||
|
document: Document,
|
||||||
|
chunks: list[Chunk],
|
||||||
|
) -> Document:
|
||||||
|
"""Store a document with its pre-embedded chunks.
|
||||||
|
|
||||||
|
Handles versioning/rollback on failure.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
document: The document to store (will be created).
|
||||||
|
chunks: Pre-embedded chunks to store with the document.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The created Document instance with ID set.
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
# Snapshot table versions for versioned rollback (if supported)
|
||||||
|
versions = self.store.current_table_versions()
|
||||||
|
|
||||||
|
# Create the document
|
||||||
|
created_doc = await self.document_repository.create(document)
|
||||||
|
|
||||||
|
try:
|
||||||
|
assert created_doc.id is not None, (
|
||||||
|
"Document ID should not be None after creation"
|
||||||
|
)
|
||||||
|
# Set document_id and order for all chunks
|
||||||
|
for order, chunk in enumerate(chunks):
|
||||||
|
chunk.document_id = created_doc.id
|
||||||
|
chunk.order = order
|
||||||
|
|
||||||
|
# Batch create all chunks in a single operation
|
||||||
|
await self.chunk_repository.create(chunks)
|
||||||
|
|
||||||
|
# Vacuum old versions in background (non-blocking)
|
||||||
|
asyncio.create_task(self.store.vacuum())
|
||||||
|
|
||||||
|
return created_doc
|
||||||
|
except Exception:
|
||||||
|
# Roll back to the captured versions and re-raise
|
||||||
|
self.store.restore_table_versions(versions)
|
||||||
|
raise
|
||||||
|
|
||||||
|
async def _update_document_and_rechunk(
|
||||||
|
self,
|
||||||
|
document: Document,
|
||||||
|
chunks: list[Chunk],
|
||||||
|
) -> Document:
|
||||||
|
"""Update a document and replace its chunks with pre-embedded chunks.
|
||||||
|
|
||||||
|
Handles versioning/rollback on failure.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
document: The document to update (must have ID set).
|
||||||
|
chunks: Pre-embedded chunks to replace existing chunks.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The updated Document instance.
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
assert document.id is not None, "Document ID is required for update"
|
||||||
|
|
||||||
|
# Snapshot table versions for versioned rollback
|
||||||
|
versions = self.store.current_table_versions()
|
||||||
|
|
||||||
|
# Delete existing chunks before writing new ones
|
||||||
|
await self.chunk_repository.delete_by_document_id(document.id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Update the document
|
||||||
|
updated_doc = await self.document_repository.update(document)
|
||||||
|
|
||||||
|
# Set document_id and order for all chunks
|
||||||
|
assert updated_doc.id is not None
|
||||||
|
for order, chunk in enumerate(chunks):
|
||||||
|
chunk.document_id = updated_doc.id
|
||||||
|
chunk.order = order
|
||||||
|
|
||||||
|
# Batch create all chunks in a single operation
|
||||||
|
await self.chunk_repository.create(chunks)
|
||||||
|
|
||||||
|
# Vacuum old versions in background (non-blocking)
|
||||||
|
asyncio.create_task(self.store.vacuum())
|
||||||
|
|
||||||
|
return updated_doc
|
||||||
|
except Exception:
|
||||||
|
# Roll back to the captured versions and re-raise
|
||||||
|
self.store.restore_table_versions(versions)
|
||||||
|
raise
|
||||||
|
|
||||||
async def _create_document_with_docling(
|
async def _create_document_with_docling(
|
||||||
self,
|
self,
|
||||||
docling_document,
|
docling_document,
|
||||||
|
|
@ -228,9 +321,14 @@ class HaikuRAG:
|
||||||
Returns:
|
Returns:
|
||||||
The created Document instance.
|
The created Document instance.
|
||||||
"""
|
"""
|
||||||
converter = get_converter(self._config)
|
from haiku.rag.embeddings import embed_chunks
|
||||||
docling_document = await converter.convert_text(content)
|
|
||||||
|
|
||||||
|
# Convert → Chunk → Embed using primitives
|
||||||
|
docling_document = await self.convert(content)
|
||||||
|
chunks = await self.chunk(docling_document)
|
||||||
|
embedded_chunks = await embed_chunks(chunks, self._config)
|
||||||
|
|
||||||
|
# Create document model
|
||||||
document = Document(
|
document = Document(
|
||||||
content=content,
|
content=content,
|
||||||
uri=uri,
|
uri=uri,
|
||||||
|
|
@ -240,9 +338,8 @@ class HaikuRAG:
|
||||||
docling_version=docling_document.version,
|
docling_version=docling_document.version,
|
||||||
)
|
)
|
||||||
|
|
||||||
return await self.document_repository._create_and_chunk(
|
# Store document and chunks
|
||||||
document, docling_document, None
|
return await self._store_document_with_chunks(document, embedded_chunks)
|
||||||
)
|
|
||||||
|
|
||||||
async def import_document(
|
async def import_document(
|
||||||
self,
|
self,
|
||||||
|
|
@ -392,6 +489,8 @@ class HaikuRAG:
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If the file cannot be parsed or doesn't exist
|
ValueError: If the file cannot be parsed or doesn't exist
|
||||||
"""
|
"""
|
||||||
|
from haiku.rag.embeddings import embed_chunks
|
||||||
|
|
||||||
metadata = metadata or {}
|
metadata = metadata or {}
|
||||||
|
|
||||||
converter = get_converter(self._config)
|
converter = get_converter(self._config)
|
||||||
|
|
@ -430,29 +529,33 @@ class HaikuRAG:
|
||||||
return await self.document_repository.update(existing_doc)
|
return await self.document_repository.update(existing_doc)
|
||||||
return existing_doc
|
return existing_doc
|
||||||
|
|
||||||
# Parse file only when content changed or new document
|
# Convert → Chunk → Embed using primitives
|
||||||
converter = get_converter(self._config)
|
docling_document = await self.convert(source_path)
|
||||||
docling_document = await converter.convert_file(source_path)
|
chunks = await self.chunk(docling_document)
|
||||||
|
embedded_chunks = await embed_chunks(chunks, self._config)
|
||||||
|
|
||||||
if existing_doc:
|
if existing_doc:
|
||||||
# Update existing document
|
# Update existing document and rechunk
|
||||||
existing_doc.content = docling_document.export_to_markdown()
|
existing_doc.content = docling_document.export_to_markdown()
|
||||||
existing_doc.metadata = metadata
|
existing_doc.metadata = metadata
|
||||||
existing_doc.docling_document_json = docling_document.model_dump_json()
|
existing_doc.docling_document_json = docling_document.model_dump_json()
|
||||||
existing_doc.docling_version = docling_document.version
|
existing_doc.docling_version = docling_document.version
|
||||||
if title is not None:
|
if title is not None:
|
||||||
existing_doc.title = title
|
existing_doc.title = title
|
||||||
return await self.document_repository._update_and_rechunk(
|
return await self._update_document_and_rechunk(
|
||||||
existing_doc, docling_document
|
existing_doc, embedded_chunks
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Create new document using DoclingDocument
|
# Create new document
|
||||||
return await self._create_document_with_docling(
|
document = Document(
|
||||||
docling_document=docling_document,
|
content=docling_document.export_to_markdown(),
|
||||||
uri=uri,
|
uri=uri,
|
||||||
title=title,
|
title=title,
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
|
docling_document_json=docling_document.model_dump_json(),
|
||||||
|
docling_version=docling_document.version,
|
||||||
)
|
)
|
||||||
|
return await self._store_document_with_chunks(document, embedded_chunks)
|
||||||
|
|
||||||
async def _create_or_update_document_from_url(
|
async def _create_or_update_document_from_url(
|
||||||
self, url: str, title: str | None = None, metadata: dict | None = None
|
self, url: str, title: str | None = None, metadata: dict | None = None
|
||||||
|
|
@ -475,6 +578,8 @@ class HaikuRAG:
|
||||||
ValueError: If the content cannot be parsed
|
ValueError: If the content cannot be parsed
|
||||||
httpx.RequestError: If URL request fails
|
httpx.RequestError: If URL request fails
|
||||||
"""
|
"""
|
||||||
|
from haiku.rag.embeddings import embed_chunks
|
||||||
|
|
||||||
metadata = metadata or {}
|
metadata = metadata or {}
|
||||||
|
|
||||||
converter = get_converter(self._config)
|
converter = get_converter(self._config)
|
||||||
|
|
@ -519,35 +624,45 @@ class HaikuRAG:
|
||||||
|
|
||||||
# Create a temporary file with the appropriate extension
|
# Create a temporary file with the appropriate extension
|
||||||
with tempfile.NamedTemporaryFile(
|
with tempfile.NamedTemporaryFile(
|
||||||
mode="wb", suffix=file_extension
|
mode="wb", suffix=file_extension, delete=False
|
||||||
) as temp_file:
|
) as temp_file:
|
||||||
temp_file.write(response.content)
|
temp_file.write(response.content)
|
||||||
temp_file.flush() # Ensure content is written to disk
|
temp_file.flush()
|
||||||
temp_path = Path(temp_file.name)
|
temp_path = Path(temp_file.name)
|
||||||
|
|
||||||
# Parse the content using converter
|
try:
|
||||||
docling_document = await converter.convert_file(temp_path)
|
# Convert → Chunk → Embed using primitives
|
||||||
|
docling_document = await self.convert(temp_path)
|
||||||
|
chunks = await self.chunk(docling_document)
|
||||||
|
embedded_chunks = await embed_chunks(chunks, self._config)
|
||||||
|
finally:
|
||||||
|
temp_path.unlink(missing_ok=True)
|
||||||
|
|
||||||
# Merge metadata with contentType and md5
|
# Merge metadata with contentType and md5
|
||||||
metadata.update({"contentType": content_type, "md5": md5_hash})
|
metadata.update({"contentType": content_type, "md5": md5_hash})
|
||||||
|
|
||||||
if existing_doc:
|
if existing_doc:
|
||||||
|
# Update existing document and rechunk
|
||||||
existing_doc.content = docling_document.export_to_markdown()
|
existing_doc.content = docling_document.export_to_markdown()
|
||||||
existing_doc.metadata = metadata
|
existing_doc.metadata = metadata
|
||||||
existing_doc.docling_document_json = docling_document.model_dump_json()
|
existing_doc.docling_document_json = docling_document.model_dump_json()
|
||||||
existing_doc.docling_version = docling_document.version
|
existing_doc.docling_version = docling_document.version
|
||||||
if title is not None:
|
if title is not None:
|
||||||
existing_doc.title = title
|
existing_doc.title = title
|
||||||
return await self.document_repository._update_and_rechunk(
|
return await self._update_document_and_rechunk(
|
||||||
existing_doc, docling_document
|
existing_doc, embedded_chunks
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return await self._create_document_with_docling(
|
# Create new document
|
||||||
docling_document=docling_document,
|
document = Document(
|
||||||
|
content=docling_document.export_to_markdown(),
|
||||||
uri=url,
|
uri=url,
|
||||||
title=title,
|
title=title,
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
|
docling_document_json=docling_document.model_dump_json(),
|
||||||
|
docling_version=docling_document.version,
|
||||||
)
|
)
|
||||||
|
return await self._store_document_with_chunks(document, embedded_chunks)
|
||||||
|
|
||||||
def _get_extension_from_content_type_or_url(
|
def _get_extension_from_content_type_or_url(
|
||||||
self, url: str, content_type: str
|
self, url: str, content_type: str
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue