Merge pull request #91 from ggozad/feat/auto-vacuming
Auto-vacuuming versions older than VACUUM_RETENTION_SECONDS (default=60sec)
This commit is contained in:
commit
d87cafdea4
15 changed files with 199 additions and 112 deletions
BIN
docs/.DS_Store
vendored
BIN
docs/.DS_Store
vendored
Binary file not shown.
|
|
@ -173,6 +173,8 @@ Reduce disk usage by optimizing and pruning old table versions across all tables
|
|||
haiku-rag vacuum
|
||||
```
|
||||
|
||||
**Automatic Cleanup:** Vacuum runs automatically in the background after document operations. By default, it removes versions older than 60 seconds (`VACUUM_RETENTION_SECONDS`), preserving recent versions for concurrent connections. Manual vacuum can be useful for cleanup after bulk operations or to free disk space immediately.
|
||||
|
||||
### Rebuild Database
|
||||
|
||||
Rebuild the database by deleting all chunks & embeddings and re-indexing all documents. This is useful
|
||||
|
|
|
|||
|
|
@ -232,6 +232,12 @@ CHUNK_SIZE=256
|
|||
# When expanded chunks overlap or are adjacent, they are automatically merged
|
||||
# into single chunks with continuous content to eliminate duplication
|
||||
CONTEXT_CHUNK_RADIUS=0
|
||||
|
||||
# Vacuum retention threshold (seconds) for automatic cleanup
|
||||
# When documents are added/updated, old table versions older than this are removed
|
||||
# Default: 60 seconds (safe for concurrent connections)
|
||||
# Set to 0 for aggressive cleanup (removes all old versions immediately)
|
||||
VACUUM_RETENTION_SECONDS=60
|
||||
```
|
||||
|
||||
#### Markdown Preprocessor
|
||||
|
|
|
|||
BIN
docs/img/.DS_Store
vendored
BIN
docs/img/.DS_Store
vendored
Binary file not shown.
|
|
@ -23,10 +23,10 @@ classifiers = [
|
|||
]
|
||||
|
||||
dependencies = [
|
||||
"docling>=2.52.0",
|
||||
"docling>=2.55.1",
|
||||
"fastmcp>=2.12.3",
|
||||
"httpx>=0.28.1",
|
||||
"lancedb>=0.25.0",
|
||||
"lancedb>=0.25.1",
|
||||
"pydantic>=2.11.9",
|
||||
"pydantic-ai>=1.0.8",
|
||||
"pydantic-graph>=1.0.8",
|
||||
|
|
@ -62,7 +62,7 @@ dev = [
|
|||
"mkdocs-material>=9.6.14",
|
||||
"pydantic-evals>=1.0.8",
|
||||
"pre-commit>=4.2.0",
|
||||
"pyright>=1.1.405",
|
||||
"pyright>=1.1.406",
|
||||
"pytest>=8.4.2",
|
||||
"pytest-asyncio>=1.2.0",
|
||||
"pytest-cov>=7.0.0",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,6 @@ async def populate_db(spec: DatasetSpec) -> None:
|
|||
metadata=payload.metadata,
|
||||
)
|
||||
progress.advance(task)
|
||||
rag.store.vacuum()
|
||||
|
||||
|
||||
def _is_relevant_match(retrieved_uri: str | None, sample: RetrievalSample) -> bool:
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ class HaikuRAG:
|
|||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb): # noqa: ARG002
|
||||
"""Async context manager exit."""
|
||||
# Wait for any pending vacuum to complete before closing
|
||||
async with self.store._vacuum_lock:
|
||||
pass
|
||||
self.close()
|
||||
return False
|
||||
|
||||
|
|
@ -617,13 +620,13 @@ class HaikuRAG:
|
|||
|
||||
# Final maintenance: centralized vacuum to curb disk usage
|
||||
try:
|
||||
self.store.vacuum()
|
||||
await self.store.vacuum()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def vacuum(self) -> None:
|
||||
"""Optimize and clean up old versions across all tables."""
|
||||
self.store.vacuum()
|
||||
await self.store.vacuum()
|
||||
|
||||
def close(self):
|
||||
"""Close the underlying store connection."""
|
||||
|
|
|
|||
|
|
@ -57,6 +57,11 @@ class AppConfig(BaseModel):
|
|||
# and error out when the database does not already exist.
|
||||
DISABLE_DB_AUTOCREATE: bool = False
|
||||
|
||||
# Vacuum retention threshold in seconds. Only versions older than this
|
||||
# threshold will be removed during vacuum operations. Default is 60 seconds
|
||||
# to allow concurrent connections to safely use recent versions.
|
||||
VACUUM_RETENTION_SECONDS: int = 60
|
||||
|
||||
@field_validator("MONITOR_DIRECTORIES", mode="before")
|
||||
@classmethod
|
||||
def parse_monitor_directories(cls, v):
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class SQLiteToLanceDBMigrator:
|
|||
self.lancedb_path = lancedb_path
|
||||
self.console = Console()
|
||||
|
||||
def migrate(self) -> bool:
|
||||
async def migrate(self) -> bool:
|
||||
"""Perform the migration."""
|
||||
try:
|
||||
self.console.print(
|
||||
|
|
@ -94,7 +94,7 @@ class SQLiteToLanceDBMigrator:
|
|||
# Optimize and cleanup using centralized vacuum
|
||||
self.console.print("[cyan]Optimizing LanceDB...[/cyan]")
|
||||
try:
|
||||
lance_store.vacuum()
|
||||
await lance_store.vacuum()
|
||||
self.console.print("[green]✅ Optimization completed[/green]")
|
||||
except Exception as e:
|
||||
self.console.print(
|
||||
|
|
@ -313,4 +313,4 @@ async def migrate_sqlite_to_lancedb(
|
|||
lancedb_path = sqlite_path.parent / (sqlite_path.stem + ".lancedb")
|
||||
|
||||
migrator = SQLiteToLanceDBMigrator(sqlite_path, lancedb_path)
|
||||
return migrator.migrate()
|
||||
return await migrator.migrate()
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
|
@ -51,6 +52,7 @@ class Store:
|
|||
def __init__(self, db_path: Path, skip_validation: bool = False):
|
||||
self.db_path: Path = db_path
|
||||
self.embedder = get_embedder()
|
||||
self._vacuum_lock = asyncio.Lock()
|
||||
|
||||
# Create the ChunkRecord model with the correct vector dimension
|
||||
self.ChunkRecord = create_chunk_model(self.embedder._vector_dim)
|
||||
|
|
@ -78,14 +80,40 @@ class Store:
|
|||
if not skip_validation:
|
||||
self._validate_configuration()
|
||||
|
||||
def vacuum(self) -> None:
|
||||
"""Optimize and clean up old versions across all tables to reduce disk usage."""
|
||||
async def vacuum(self, retention_seconds: int | None = None) -> None:
|
||||
"""Optimize and clean up old versions across all tables to reduce disk usage.
|
||||
|
||||
Args:
|
||||
retention_seconds: Retention threshold in seconds. Only versions older
|
||||
than this will be removed. If None, uses Config.VACUUM_RETENTION_SECONDS.
|
||||
|
||||
Note:
|
||||
If vacuum is already running, this method returns immediately without blocking.
|
||||
Use asyncio.create_task(store.vacuum()) for non-blocking background execution.
|
||||
"""
|
||||
if self._has_cloud_config() and str(Config.LANCEDB_URI).startswith("db://"):
|
||||
return
|
||||
|
||||
# Perform maintenance per table using optimize() with cleanup_older_than 0
|
||||
for table in [self.documents_table, self.chunks_table, self.settings_table]:
|
||||
table.optimize(cleanup_older_than=timedelta(0))
|
||||
# Skip if already running (non-blocking)
|
||||
if self._vacuum_lock.locked():
|
||||
return
|
||||
|
||||
async with self._vacuum_lock:
|
||||
try:
|
||||
# Evaluate config at runtime to allow dynamic changes
|
||||
if retention_seconds is None:
|
||||
retention_seconds = Config.VACUUM_RETENTION_SECONDS
|
||||
# Perform maintenance per table using optimize() with configurable retention
|
||||
retention = timedelta(seconds=retention_seconds)
|
||||
for table in [
|
||||
self.documents_table,
|
||||
self.chunks_table,
|
||||
self.settings_table,
|
||||
]:
|
||||
table.optimize(cleanup_older_than=retention)
|
||||
except (RuntimeError, OSError) as e:
|
||||
# Handle resource errors gracefully
|
||||
logger.debug(f"Vacuum skipped due to resource constraints: {e}")
|
||||
|
||||
def _connect_to_lancedb(self, db_path: Path):
|
||||
"""Establish connection to LanceDB (local, cloud, or object storage)."""
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import asyncio
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
|
|
@ -23,7 +22,6 @@ class ChunkRepository:
|
|||
def __init__(self, store: Store) -> None:
|
||||
self.store = store
|
||||
self.embedder = get_embedder()
|
||||
self._optimize_lock = asyncio.Lock()
|
||||
|
||||
def _ensure_fts_index(self) -> None:
|
||||
"""Ensure FTS index exists on the content column."""
|
||||
|
|
@ -35,21 +33,6 @@ class ChunkRepository:
|
|||
# Log the error but don't fail - FTS might already exist
|
||||
logger.debug(f"FTS index creation skipped: {e}")
|
||||
|
||||
async def _optimize(self) -> None:
|
||||
"""Optimize the chunks table to refresh indexes."""
|
||||
# Skip optimization for LanceDB Cloud as it handles this automatically
|
||||
if Config.LANCEDB_URI and Config.LANCEDB_URI.startswith("db://"):
|
||||
return
|
||||
|
||||
async with self._optimize_lock:
|
||||
try:
|
||||
self.store.chunks_table.optimize()
|
||||
except (RuntimeError, OSError) as e:
|
||||
# Handle "too many open files" and other resource errors gracefully
|
||||
logger.debug(
|
||||
f"Table optimization skipped due to resource constraints: {e}"
|
||||
)
|
||||
|
||||
async def create(self, entity: Chunk) -> Chunk:
|
||||
"""Create a chunk in the database."""
|
||||
assert entity.document_id, "Chunk must have a document_id to be created"
|
||||
|
|
@ -77,11 +60,6 @@ class ChunkRepository:
|
|||
self.store.chunks_table.add([chunk_record])
|
||||
|
||||
entity.id = chunk_id
|
||||
|
||||
# Try to optimize if not currently locked (non-blocking)
|
||||
if not self._optimize_lock.locked():
|
||||
asyncio.create_task(self._optimize())
|
||||
|
||||
return entity
|
||||
|
||||
async def get_by_id(self, entity_id: str) -> Chunk | None:
|
||||
|
|
@ -125,10 +103,6 @@ class ChunkRepository:
|
|||
"vector": embedding,
|
||||
},
|
||||
)
|
||||
# Try to optimize if not currently locked (non-blocking)
|
||||
if not self._optimize_lock.locked():
|
||||
asyncio.create_task(self._optimize())
|
||||
|
||||
return entity
|
||||
|
||||
async def delete(self, entity_id: str) -> bool:
|
||||
|
|
@ -227,8 +201,6 @@ class ChunkRepository:
|
|||
if chunk_records:
|
||||
self.store.chunks_table.add(chunk_records)
|
||||
|
||||
# Force optimization once at the end for bulk operations
|
||||
await self._optimize()
|
||||
return created_chunks
|
||||
|
||||
async def delete_all(self) -> None:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import asyncio
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
|
|
@ -200,6 +201,9 @@ class DocumentRepository:
|
|||
chunk.order = order
|
||||
await self.chunk_repository.create(chunk)
|
||||
|
||||
# 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
|
||||
|
|
@ -230,6 +234,9 @@ class DocumentRepository:
|
|||
updated_doc.id, docling_document
|
||||
)
|
||||
|
||||
# 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
|
||||
|
|
|
|||
|
|
@ -4,43 +4,24 @@ import pytest
|
|||
|
||||
from haiku.rag.config import Config
|
||||
from haiku.rag.store.engine import Store
|
||||
from haiku.rag.store.models.chunk import Chunk
|
||||
from haiku.rag.store.models.document import Document
|
||||
from haiku.rag.store.repositories.chunk import ChunkRepository
|
||||
from haiku.rag.store.repositories.document import DocumentRepository
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_lancedb_cloud_skips_optimization(temp_db_path):
|
||||
"""Test that optimization is skipped when using LanceDB Cloud (db:// URI)."""
|
||||
"""Test that vacuum is skipped when using LanceDB Cloud (db:// URI)."""
|
||||
# Create a store
|
||||
store = Store(temp_db_path)
|
||||
chunk_repo = ChunkRepository(store)
|
||||
doc_repo = DocumentRepository(store)
|
||||
|
||||
# Create a document
|
||||
document = Document(content="Test document content", metadata={})
|
||||
created_document = await doc_repo.create(document)
|
||||
document_id = created_document.id
|
||||
|
||||
# Mock LANCEDB_URI to simulate LanceDB Cloud usage
|
||||
with patch.object(Config, "LANCEDB_URI", "db://test-database"):
|
||||
# Mock all cloud config to simulate LanceDB Cloud usage
|
||||
with (
|
||||
patch.object(Config, "LANCEDB_URI", "db://test-database"),
|
||||
patch.object(Config, "LANCEDB_API_KEY", "test-api-key"),
|
||||
patch.object(Config, "LANCEDB_REGION", "us-east-1"),
|
||||
):
|
||||
# Mock the optimize method to track if it's called
|
||||
with patch.object(store.chunks_table, "optimize") as mock_optimize:
|
||||
# Create a chunk - this should trigger optimization logic
|
||||
chunk = Chunk(
|
||||
document_id=document_id,
|
||||
content="Test chunk content",
|
||||
metadata={"test": "value"},
|
||||
)
|
||||
|
||||
created_chunk = await chunk_repo.create(chunk)
|
||||
assert created_chunk.id is not None
|
||||
|
||||
# Wait a moment to ensure any async optimization would complete
|
||||
import asyncio
|
||||
|
||||
await asyncio.sleep(0.1)
|
||||
# Call vacuum - this should skip optimization for LanceDB Cloud
|
||||
await store.vacuum()
|
||||
|
||||
# The optimize method should NOT have been called for LanceDB Cloud
|
||||
mock_optimize.assert_not_called()
|
||||
|
|
@ -50,35 +31,16 @@ async def test_lancedb_cloud_skips_optimization(temp_db_path):
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_local_storage_calls_optimization(temp_db_path):
|
||||
"""Test that optimization is called for local storage."""
|
||||
"""Test that vacuum calls optimization for local storage."""
|
||||
# Create a store
|
||||
store = Store(temp_db_path)
|
||||
chunk_repo = ChunkRepository(store)
|
||||
doc_repo = DocumentRepository(store)
|
||||
|
||||
# Create a document
|
||||
document = Document(content="Test document content", metadata={})
|
||||
created_document = await doc_repo.create(document)
|
||||
document_id = created_document.id
|
||||
|
||||
# Ensure LANCEDB_URI is empty (local storage)
|
||||
with patch.object(Config, "LANCEDB_URI", ""):
|
||||
# Mock the optimize method to track if it's called
|
||||
with patch.object(store.chunks_table, "optimize") as mock_optimize:
|
||||
# Create a chunk - this should trigger optimization logic
|
||||
chunk = Chunk(
|
||||
document_id=document_id,
|
||||
content="Test chunk content",
|
||||
metadata={"test": "value"},
|
||||
)
|
||||
|
||||
created_chunk = await chunk_repo.create(chunk)
|
||||
assert created_chunk.id is not None
|
||||
|
||||
# Wait a moment to ensure async optimization completes
|
||||
import asyncio
|
||||
|
||||
await asyncio.sleep(0.1)
|
||||
# Call vacuum - this should optimize all tables for local storage
|
||||
await store.vacuum()
|
||||
|
||||
# The optimize method SHOULD have been called for local storage
|
||||
mock_optimize.assert_called()
|
||||
|
|
|
|||
|
|
@ -122,3 +122,106 @@ def test_existing_database_runs_upgrades(monkeypatch, temp_db_path):
|
|||
Store(temp_db_path)
|
||||
|
||||
assert called["value"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_vacuum_with_retention_threshold(temp_db_path):
|
||||
store = Store(temp_db_path)
|
||||
repo = DocumentRepository(store)
|
||||
|
||||
# Stub embeddings to avoid network
|
||||
dim = repo.chunk_repository.embedder._vector_dim
|
||||
|
||||
async def fake_embed(x): # type: ignore[no-redef]
|
||||
if isinstance(x, list):
|
||||
return [[0.0] * dim for _ in x]
|
||||
return [0.0] * dim
|
||||
|
||||
repo.chunk_repository.embedder.embed = fake_embed # type: ignore[assignment]
|
||||
|
||||
# Create first document
|
||||
doc1 = Document(content="First document")
|
||||
dl_doc1 = text_to_docling_document("First document", name="doc1.md")
|
||||
await repo._create_with_docling(doc1, dl_doc1)
|
||||
|
||||
# Create second document
|
||||
doc2 = Document(content="Second document")
|
||||
dl_doc2 = text_to_docling_document("Second document", name="doc2.md")
|
||||
await repo._create_with_docling(doc2, dl_doc2)
|
||||
|
||||
# Get initial version counts (should have multiple versions from creates)
|
||||
initial_doc_versions = len(list(store.documents_table.list_versions()))
|
||||
initial_chunk_versions = len(list(store.chunks_table.list_versions()))
|
||||
|
||||
assert initial_doc_versions > 1, "Should have multiple document table versions"
|
||||
assert initial_chunk_versions > 1, "Should have multiple chunk table versions"
|
||||
|
||||
# Vacuum with default threshold (60 seconds) - should keep recent versions
|
||||
# Note: vacuum may create new versions even when not cleaning up old ones
|
||||
await store.vacuum()
|
||||
|
||||
after_default_doc_versions = len(list(store.documents_table.list_versions()))
|
||||
after_default_chunk_versions = len(list(store.chunks_table.list_versions()))
|
||||
|
||||
# After vacuum with retention, version count should stay the same or increase
|
||||
# (optimize may create new versions) but not decrease
|
||||
assert after_default_doc_versions >= initial_doc_versions, (
|
||||
"Default vacuum should not remove recent versions"
|
||||
)
|
||||
assert after_default_chunk_versions >= initial_chunk_versions, (
|
||||
"Default vacuum should not remove recent versions"
|
||||
)
|
||||
|
||||
# Vacuum with 0 threshold - should significantly reduce versions
|
||||
await store.vacuum(retention_seconds=0)
|
||||
|
||||
after_zero_doc_versions = len(list(store.documents_table.list_versions()))
|
||||
after_zero_chunk_versions = len(list(store.chunks_table.list_versions()))
|
||||
|
||||
# After aggressive vacuum, should have minimal versions (1-2)
|
||||
# Note: optimize operation may create a version after cleanup
|
||||
assert after_zero_doc_versions <= 2, (
|
||||
f"Should have minimal document versions after vacuum(0), got {after_zero_doc_versions}"
|
||||
)
|
||||
assert after_zero_chunk_versions <= 2, (
|
||||
f"Should have minimal chunk versions after vacuum(0), got {after_zero_chunk_versions}"
|
||||
)
|
||||
|
||||
# And it should be significantly fewer than before
|
||||
assert after_zero_doc_versions < initial_doc_versions, (
|
||||
"Should have fewer versions after vacuum(0)"
|
||||
)
|
||||
assert after_zero_chunk_versions < initial_chunk_versions, (
|
||||
"Should have fewer versions after vacuum(0)"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_vacuum_completes_before_context_exit(temp_db_path, monkeypatch):
|
||||
"""Test that background vacuum completes when context manager exits."""
|
||||
from haiku.rag.client import HaikuRAG
|
||||
from haiku.rag.config import Config
|
||||
from haiku.rag.utils import text_to_docling_document
|
||||
|
||||
# Set aggressive vacuum retention for this test
|
||||
monkeypatch.setattr(Config, "VACUUM_RETENTION_SECONDS", 0)
|
||||
|
||||
async with HaikuRAG(db_path=temp_db_path) as client:
|
||||
# Create multiple documents - each creation triggers automatic vacuum with retention=0
|
||||
# This aggressively cleans up old versions between operations
|
||||
for i in range(3):
|
||||
doc = Document(content=f"Test document {i}")
|
||||
dl_doc = text_to_docling_document(f"Test document {i}", name=f"test{i}.md")
|
||||
await client.document_repository._create_with_docling(doc, dl_doc)
|
||||
|
||||
# After context exit, automatic vacuum should have kept versions minimal
|
||||
store = Store(temp_db_path)
|
||||
final_versions = len(list(store.documents_table.list_versions()))
|
||||
|
||||
# With retention_seconds=0, vacuum aggressively cleans up between operations
|
||||
# Should have very few versions remaining (1-2)
|
||||
assert final_versions <= 2, (
|
||||
f"Aggressive vacuum should keep minimal versions, got {final_versions}"
|
||||
)
|
||||
assert final_versions >= 1, "Should have at least one version remaining"
|
||||
store.close()
|
||||
|
|
|
|||
40
uv.lock
40
uv.lock
|
|
@ -645,7 +645,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "docling"
|
||||
version = "2.52.0"
|
||||
version = "2.55.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "accelerate" },
|
||||
|
|
@ -676,14 +676,14 @@ dependencies = [
|
|||
{ name = "tqdm" },
|
||||
{ name = "typer" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/40/e6b25533f99eb48dae6f21b226ee7ebe6c9796d42ecf4ecfce525414f5fa/docling-2.52.0.tar.gz", hash = "sha256:e6c6b4db5ed583e899528f76a9de71679c75f25a0585afcc7fc3ccff6c791d41", size = 196658, upload-time = "2025-09-11T16:12:40.951Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/81/8c/baa24f0d64a36a87c66eef91dcf169ac346776739c4fb8065e59c31b1291/docling-2.55.1.tar.gz", hash = "sha256:e60a5612b2b993efd8a0b5464aff1b9868e3cab5c2e239c863709e6b780f3c57", size = 212483, upload-time = "2025-10-03T10:27:46.907Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/28/6a/ff3e65868f409438f2957ad4601852db51d7f6e5eb866825fa306177edc7/docling-2.52.0-py3-none-any.whl", hash = "sha256:85927fde42cd6b597c314c7a73241a7d28c817f8666b3c00c045c2905a769d8b", size = 224855, upload-time = "2025-09-11T16:12:39.319Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/a3/2a2801cb909981b57326da2a9736cd11514d0393dc37771e200615b8b44f/docling-2.55.1-py3-none-any.whl", hash = "sha256:895aba282c6cca9ca1f6b9ff57c2002e4f581f722c608aa671d68382d4d61e07", size = 239394, upload-time = "2025-10-03T10:27:45.157Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "docling-core"
|
||||
version = "2.48.1"
|
||||
version = "2.48.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jsonref" },
|
||||
|
|
@ -697,9 +697,9 @@ dependencies = [
|
|||
{ name = "typer" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f9/0c/dce7f80e99e56570d143885fc40536107e8a39ef4de2888959e055b39607/docling_core-2.48.1.tar.gz", hash = "sha256:48cb77575dfd020a51413957e96b165e45f6d1027c641710fddb389dcb9b189c", size = 161311, upload-time = "2025-09-11T12:33:22.46Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/38/d8/f0c8034f87d6151eb955e56975b9f2374a54d57af2b56b1682d7c8ff5c71/docling_core-2.48.4.tar.gz", hash = "sha256:d87ce3021cdae3d073ce7572a2396b69be3cde82ebf9a74d4bad1e1cdfdfd524", size = 161377, upload-time = "2025-10-01T09:10:08.614Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/90/fe/1b96120c9d94c97016716ccf46ad2708a2e76157e52dfcca4101db70fc21/docling_core-2.48.1-py3-none-any.whl", hash = "sha256:a3985999ac2067e15e589ef0f11ccde264deacaea403c0f94049242f10a6189a", size = 164330, upload-time = "2025-09-11T12:33:20.935Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/2a/06e5f9d3083f830de8bef86f91acda994965f88d8b945ce3b257ea83e780/docling_core-2.48.4-py3-none-any.whl", hash = "sha256:367675c1165d0934ae498fa57ca2d27ef0468aad74dc44a5ab061f5d87882ea1", size = 164374, upload-time = "2025-10-01T09:10:06.034Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
|
|
@ -1153,10 +1153,10 @@ dev = [
|
|||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "docling", specifier = ">=2.52.0" },
|
||||
{ name = "docling", specifier = ">=2.55.1" },
|
||||
{ name = "fastmcp", specifier = ">=2.12.3" },
|
||||
{ name = "httpx", specifier = ">=0.28.1" },
|
||||
{ name = "lancedb", specifier = ">=0.25.0" },
|
||||
{ name = "lancedb", specifier = ">=0.25.1" },
|
||||
{ name = "mxbai-rerank", marker = "extra == 'mxbai'", specifier = ">=0.1.6" },
|
||||
{ name = "pydantic", specifier = ">=2.11.9" },
|
||||
{ name = "pydantic-ai", specifier = ">=1.0.8" },
|
||||
|
|
@ -1178,7 +1178,7 @@ dev = [
|
|||
{ name = "mkdocs-material", specifier = ">=9.6.14" },
|
||||
{ name = "pre-commit", specifier = ">=4.2.0" },
|
||||
{ name = "pydantic-evals", specifier = ">=1.0.8" },
|
||||
{ name = "pyright", specifier = ">=1.1.405" },
|
||||
{ name = "pyright", specifier = ">=1.1.406" },
|
||||
{ name = "pytest", specifier = ">=8.4.2" },
|
||||
{ name = "pytest-asyncio", specifier = ">=1.2.0" },
|
||||
{ name = "pytest-cov", specifier = ">=7.0.0" },
|
||||
|
|
@ -1516,7 +1516,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "lancedb"
|
||||
version = "0.25.0"
|
||||
version = "0.25.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "deprecation" },
|
||||
|
|
@ -1529,13 +1529,13 @@ dependencies = [
|
|||
{ name = "tqdm" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/e7/10953deea89b06ae5bc568169d5ae888ff6df314decb92b9b3e453f53f0b/lancedb-0.25.0-cp39-abi3-macosx_10_15_x86_64.whl", hash = "sha256:ae2e80b7b3be3fa4d92fc8d500f47549dd1f8d28ca5092f1c898b92d0cfd4393", size = 34171227, upload-time = "2025-09-04T11:05:31.327Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/7f/2874a3709f1b8c487e707e171c9004a9240af3af0fd7a247b9187bb6e0f7/lancedb-0.25.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:a9d67ea9edffa596c6f190151fdd535da8e355a4fd1979c1dc19d540a5665916", size = 31552856, upload-time = "2025-09-04T09:46:50.788Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/e9/faab70ad918576ed3bb7cb936474137ac265ac3026d3e16e30cd4d3daac2/lancedb-0.25.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8fe20079ed86b1ab75c65dcfc920a9646c835e9c40ef825cadd148c11b0001e", size = 32487962, upload-time = "2025-09-04T08:51:35.358Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/40/5471bc8115f287040b5afdf9d7a20c4685ec16cddb4a7da79e7c1f63914e/lancedb-0.25.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b37bc402d85c83e454d9f2e79480b31acc5904bb159a4fc715032c7560494157", size = 35726794, upload-time = "2025-09-04T08:57:30.554Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/5e/aa3d9d2c7a834a9aa539b2b1c731ab860f7e32e2c87b9086ad233ecb13cd/lancedb-0.25.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f9bbc20bd1e64be359ca11c90428c00b0062d26b0291bddf32ab5471a3525c76", size = 32492508, upload-time = "2025-09-04T08:53:54.661Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/37/75f4e3ed7fa00a2cd5d321e8bf13441cdb61a83fbbcd0fa0f1a7241affe1/lancedb-0.25.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1306be9c08e208a5bcb5188275f47f962c2eda96369fad5949a3ddaf592afc6d", size = 35776383, upload-time = "2025-09-04T08:57:18.737Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/af/eb217ea1daab5c28ce4c764d2f672f4e3a5bcd3d4faf7921a8ee28c6cb5b/lancedb-0.25.0-cp39-abi3-win_amd64.whl", hash = "sha256:f66283e5d63c99c2bfbd4eaa134d9a5c5b0145eb26a972648214f8ba87777e24", size = 37826272, upload-time = "2025-09-04T09:15:23.729Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/2b/ed9870288506d8ca61cddf7b1dbb03c68f95b8797feb49467b33ef185477/lancedb-0.25.1-cp39-abi3-macosx_10_15_x86_64.whl", hash = "sha256:ec0a1cab435a5307054b84ffb798a4d828253f23698848788bfe31930e343c6c", size = 34985432, upload-time = "2025-09-23T23:15:56.558Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/75/320f9142918b646b4b6d0277676c2466d2e0ce2a22aca320d0113b3ef035/lancedb-0.25.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:69e1f8343f6a4ff6985ea13f5c5cdf6d07435d04f8279c4fc6e623a34ceadda0", size = 31993179, upload-time = "2025-09-23T22:20:23.039Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/44/d223cb64c9feb78dfa3857690d743e961f76e065935c8c4304cb64659882/lancedb-0.25.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9432134155474e73907fc5e1f8a4310433b9234a0c5f964c21b4c39aca50dde6", size = 32872519, upload-time = "2025-09-23T22:29:03.5Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/a6/e6d88d8076fa8c40b7b6f96a37f21c75ce3518ccbf64a351d26ae983461a/lancedb-0.25.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955c6e1aa4e249be7456ea7f7c42ba119be5a5c2c51f4d78efeb6c4f3cc2dbdf", size = 36325984, upload-time = "2025-09-23T22:31:46.118Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/84/14d4f0c3a98a324fcb401161e25fb1699c69ba1cd2928983fb283bd8b04f/lancedb-0.25.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d584bdfb96372c03a209bb8f010eb7358135e4adddb903ae1385450af39e1187", size = 32883704, upload-time = "2025-09-23T22:27:41.393Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/10/3e8ae8bf9880b2fed10122cef5e535bd67f0df0a874cc3122220d47ca255/lancedb-0.25.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c495da53d3dfa105364f202710d0bb2f031fe54a077b9c2ac9d098d02bd20bb2", size = 36369514, upload-time = "2025-09-23T22:30:53.605Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/fb/dce4757f257cb4e11e13b71ce502dc5d1caf51f1e5cccfdae85bf23960a0/lancedb-0.25.1-cp39-abi3-win_amd64.whl", hash = "sha256:2c6effc10c8263ea84261f49d5ff1957c18814ed7e3eaa5094d71b1aa0573871", size = 38390878, upload-time = "2025-09-23T22:55:24.687Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3212,15 +3212,15 @@ sdist = { url = "https://files.pythonhosted.org/packages/30/23/2f0a3efc4d6a32f3b
|
|||
|
||||
[[package]]
|
||||
name = "pyright"
|
||||
version = "1.1.405"
|
||||
version = "1.1.406"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nodeenv" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fb/6c/ba4bbee22e76af700ea593a1d8701e3225080956753bee9750dcc25e2649/pyright-1.1.405.tar.gz", hash = "sha256:5c2a30e1037af27eb463a1cc0b9f6d65fec48478ccf092c1ac28385a15c55763", size = 4068319, upload-time = "2025-09-04T03:37:06.776Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f7/16/6b4fbdd1fef59a0292cbb99f790b44983e390321eccbc5921b4d161da5d1/pyright-1.1.406.tar.gz", hash = "sha256:c4872bc58c9643dac09e8a2e74d472c62036910b3bd37a32813989ef7576ea2c", size = 4113151, upload-time = "2025-10-02T01:04:45.488Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/1a/524f832e1ff1962a22a1accc775ca7b143ba2e9f5924bb6749dce566784a/pyright-1.1.405-py3-none-any.whl", hash = "sha256:a2cb13700b5508ce8e5d4546034cb7ea4aedb60215c6c33f56cec7f53996035a", size = 5905038, upload-time = "2025-09-04T03:37:04.913Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/a2/e309afbb459f50507103793aaef85ca4348b66814c86bc73908bdeb66d12/pyright-1.1.406-py3-none-any.whl", hash = "sha256:1d81fb43c2407bf566e97e57abb01c811973fdb21b2df8df59f870f688bdca71", size = 5980982, upload-time = "2025-10-02T01:04:43.137Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
Loading…
Reference in a new issue