Rename document_meta identity column document_id to id

This commit is contained in:
Yiorgis Gozadinos 2026-07-08 16:11:36 +03:00
parent d1d06729dc
commit a1cf405cae
No known key found for this signature in database
20 changed files with 345 additions and 116 deletions

View file

@ -9,6 +9,7 @@
### Fixed
- Concurrent ingestion of the same URI no longer creates duplicate documents; the URI is re-checked under the write lock and a colliding create becomes an update.
- Document filters can reference `id` (`list_documents`/`count_documents`/`search`/`analyze` `filter=`); the `document_meta` identity column is renamed `document_id``id` with a migration.
## [0.63.2] - 2026-07-03

View file

@ -8,7 +8,7 @@ dependencies = [
"uvicorn[standard]>=0.40.0",
"pydantic-ai-slim[ag-ui,anthropic,openai]>=1.81.0",
"python-dotenv>=1.2.1",
"haiku.rag-slim>=0.63.2",
"haiku.rag-slim>=0.64.0",
"logfire[pydantic-ai]>=3.17.0",
]

View file

@ -2,7 +2,7 @@
name = "haiku.rag-evals"
description = "Benchmarking and evaluation scripts for haiku.rag"
version = "0.63.2"
version = "0.64.0"
authors = [{ name = "Yiorgis Gozadinos", email = "ggozadinos@gmail.com" }]
license = { text = "MIT" }
requires-python = ">=3.12"

View file

@ -520,7 +520,7 @@ async def _flush_rebuild_batch(
)
meta_records.append(
DocumentMetaRecord(
document_id=doc.id,
id=doc.id,
uri=doc.uri,
title=doc.title,
metadata=json.dumps(doc.metadata),
@ -535,7 +535,7 @@ async def _flush_rebuild_batch(
.execute(doc_records)
)
await (
client.store.document_meta_table.merge_insert("document_id")
client.store.document_meta_table.merge_insert("id")
.when_matched_update_all()
.when_not_matched_insert_all()
.execute(meta_records)

View file

@ -454,18 +454,16 @@ async def run_db_checks(
doc_ids = set(await _column_values(store.documents_table, "id"))
meta_rows = (
await store.document_meta_table.query()
.select(["document_id", "metadata", "uri", "title"])
.select(["id", "metadata", "uri", "title"])
.to_list()
)
meta_doc_ids = {row["document_id"] for row in meta_rows}
meta_doc_ids = {row["id"] for row in meta_rows}
content_type_by_doc = {
row["document_id"]: json.loads(row.get("metadata") or "{}").get(
"content_type", ""
)
row["id"]: json.loads(row.get("metadata") or "{}").get("content_type", "")
for row in meta_rows
}
uri_by_doc = {row["document_id"]: row.get("uri") for row in meta_rows}
title_by_doc = {row["document_id"]: row.get("title") for row in meta_rows}
uri_by_doc = {row["id"]: row.get("uri") for row in meta_rows}
title_by_doc = {row["id"]: row.get("title") for row in meta_rows}
notify("Reading chunks")
chunk_rows = (

View file

@ -87,7 +87,7 @@ class DocumentMetaRecord(LanceModel):
write-once content/blobs in `documents`. Updating these (metadata, title,
source_revision) must not rewrite the multi-MB docling row."""
document_id: str
id: str
uri: str | None = None
title: str | None = None
metadata: str = Field(default="{}")
@ -610,7 +610,7 @@ class Store:
"document_meta", schema=DocumentMetaRecord
)
await self.document_meta_table.create_index(
"document_id", config=BTree(), replace=True
"id", config=BTree(), replace=True
)
await self.document_meta_table.create_index(
"uri", config=BTree(), replace=True

View file

@ -272,13 +272,13 @@ class ChunkRepository:
# whenever the top-N window lacked `limit` matching chunks.
docs_df = await (
self.store.document_meta_table.query()
.select(["document_id"])
.select(["id"])
.where(filter)
.to_pandas()
)
if docs_df.empty:
return []
id_list = ", ".join(f"'{d}'" for d in docs_df["document_id"])
id_list = ", ".join(f"'{d}'" for d in docs_df["id"])
chunk_filter = f"document_id IN ({id_list})"
if query_vector is not None:
@ -346,8 +346,8 @@ class ChunkRepository:
# Get document info from the mutable attributes table
doc_rows = await (
self.store.document_meta_table.query()
.select(["document_id", "uri", "title", "metadata"])
.where(f"document_id = '{document_id}'")
.select(["id", "uri", "title", "metadata"])
.where(f"id = '{document_id}'")
.limit(1)
.to_list()
)
@ -503,14 +503,14 @@ class ChunkRepository:
documents_map: dict[str, dict] = {}
if document_ids:
id_list = "', '".join(document_ids)
where_clause = f"document_id IN ('{id_list}')"
where_clause = f"id IN ('{id_list}')"
doc_rows = await (
self.store.document_meta_table.query()
.select(["document_id", "uri", "title", "metadata"])
.select(["id", "uri", "title", "metadata"])
.where(where_clause)
.to_list()
)
documents_map = {str(row["document_id"]): row for row in doc_rows}
documents_map = {str(row["id"]): row for row in doc_rows}
# Build final results with document info
chunks_with_scores = []

View file

@ -94,7 +94,7 @@ class DocumentRepository:
updated_at: str,
) -> DocumentMetaRecord:
return DocumentMetaRecord(
document_id=doc_id,
id=doc_id,
uri=entity.uri,
title=entity.title,
metadata=json.dumps(entity.metadata),
@ -105,9 +105,7 @@ class DocumentRepository:
async def _meta_by_id(self, doc_id: str) -> DocumentMetaRecord | None:
safe_id = escape_sql_string(doc_id)
results = await query_to_pydantic(
self.store.document_meta_table.query()
.where(f"document_id = '{safe_id}'")
.limit(1),
self.store.document_meta_table.query().where(f"id = '{safe_id}'").limit(1),
DocumentMetaRecord,
)
return results[0] if results else None
@ -146,9 +144,7 @@ class DocumentRepository:
)
except Exception:
safe_id = escape_sql_string(doc_id)
await self.store.document_meta_table.delete(
f"document_id = '{safe_id}'"
)
await self.store.document_meta_table.delete(f"id = '{safe_id}'")
raise
entity.id = doc_id
entity.created_at = datetime.fromisoformat(now)
@ -178,7 +174,7 @@ class DocumentRepository:
await self.store.documents_table.add(doc_records)
except Exception:
ids = ", ".join(f"'{escape_sql_string(d)}'" for d in doc_ids)
await self.store.document_meta_table.delete(f"document_id IN ({ids})")
await self.store.document_meta_table.delete(f"id IN ({ids})")
raise
return documents
@ -271,7 +267,7 @@ class DocumentRepository:
# from create()/migration; inserting on no-match would manufacture a
# ghost row (visible to list_all/count) for an id with no documents row.
await (
self.store.document_meta_table.merge_insert("document_id")
self.store.document_meta_table.merge_insert("id")
.when_matched_update_all()
.execute([record])
)
@ -310,7 +306,7 @@ class DocumentRepository:
# Delete the document row, its mutable attributes
safe_id = escape_sql_string(entity_id)
await self.store.documents_table.delete(f"id = '{safe_id}'")
await self.store.document_meta_table.delete(f"document_id = '{safe_id}'")
await self.store.document_meta_table.delete(f"id = '{safe_id}'")
return True
async def list_all(
@ -348,13 +344,13 @@ class DocumentRepository:
if not include_content:
return [
self._merge_to_document(DocumentRecord(id=m.document_id, content=""), m)
self._merge_to_document(DocumentRecord(id=m.id, content=""), m)
for m in meta_records
]
documents: list[Document] = []
for meta in meta_records:
safe_id = escape_sql_string(meta.document_id)
safe_id = escape_sql_string(meta.id)
doc_results = await query_to_pydantic(
self.store.documents_table.query().where(f"id = '{safe_id}'").limit(1),
DocumentRecord,
@ -362,7 +358,7 @@ class DocumentRepository:
doc_record = (
doc_results[0]
if doc_results
else DocumentRecord(id=meta.document_id, content="")
else DocumentRecord(id=meta.id, content="")
)
documents.append(self._merge_to_document(doc_record, meta))
return documents
@ -385,7 +381,7 @@ class DocumentRepository:
return None
meta = meta_results[0]
safe_id = escape_sql_string(meta.document_id)
safe_id = escape_sql_string(meta.id)
doc_results = await query_to_pydantic(
self.store.documents_table.query().where(f"id = '{safe_id}'").limit(1),
DocumentRecord,
@ -433,7 +429,7 @@ class DocumentRepository:
"document_meta", schema=DocumentMetaRecord
)
await self.store.document_meta_table.create_index(
"document_id", config=BTree(), replace=True
"id", config=BTree(), replace=True
)
await self.store.document_meta_table.create_index(
"uri", config=BTree(), replace=True

View file

@ -96,6 +96,9 @@ from haiku.rag.store.upgrades.v0_50_0 import (
from haiku.rag.store.upgrades.v0_58_0 import (
upgrade_split_document_meta as upgrade_0_58_0_split_document_meta,
)
from haiku.rag.store.upgrades.v0_64_0 import (
upgrade_rename_document_meta_id as upgrade_0_64_0_rename_document_meta_id,
)
upgrades.append(upgrade_0_20_0_docling)
upgrades.append(upgrade_0_23_1_contextualize)
@ -106,3 +109,4 @@ upgrades.append(upgrade_0_45_0_extract_picture_bytes)
upgrades.append(upgrade_0_48_0_heading_hierarchy)
upgrades.append(upgrade_0_50_0_canonical_metadata_keys)
upgrades.append(upgrade_0_58_0_split_document_meta)
upgrades.append(upgrade_0_64_0_rename_document_meta_id)

View file

@ -31,10 +31,8 @@ async def _apply_split_document_meta(store: Store) -> None:
# Resume support: skip documents whose meta row already exists.
existing_meta = {
row["document_id"]
for row in await store.document_meta_table.query()
.select(["document_id"])
.to_list()
row["id"]
for row in await store.document_meta_table.query().select(["id"]).to_list()
}
rows = await store.documents_table.query().select(["id", *present]).to_list()
@ -45,7 +43,7 @@ async def _apply_split_document_meta(store: Store) -> None:
meta = row.get("metadata")
records.append(
DocumentMetaRecord(
document_id=row["id"],
id=row["id"],
uri=row.get("uri"),
title=row.get("title"),
metadata=meta if isinstance(meta, str) and meta else "{}",

View file

@ -0,0 +1,35 @@
import logging
from lancedb.index import BTree
from haiku.rag.store.engine import Store
from haiku.rag.store.upgrades import Upgrade
logger = logging.getLogger(__name__)
async def _apply_rename_document_meta_id(store: Store) -> None:
"""Rename the `document_meta` identity column `document_id` → `id`.
In `document_meta` the column is the document's own identity (1:1 with the
row), so `id` matches the `documents` table and the public `Document.id`.
Callers filter/list/count against `document_meta`, so with the old name a
filter on `id` raised "No field named id". Idempotent: skips if the column
is already `id`.
"""
schema = await store.document_meta_table.schema()
if "id" in schema.names:
logger.info("document_meta.id already present; nothing to rename")
return
await store.document_meta_table.alter_columns(
{"path": "document_id", "rename": "id"} # ty: ignore[invalid-argument-type]
)
await store.document_meta_table.create_index("id", config=BTree(), replace=True)
upgrade_rename_document_meta_id = Upgrade(
version="0.64.0",
apply=_apply_rename_document_meta_id,
description="Rename the document_meta identity column document_id to id",
)

View file

@ -2,7 +2,7 @@
name = "haiku.rag-slim"
description = "Opinionated agentic RAG powered by LanceDB, Pydantic AI, and Docling - Minimal dependencies"
version = "0.63.2"
version = "0.64.0"
authors = [{ name = "Yiorgis Gozadinos", email = "ggozadinos@gmail.com" }]
license = { text = "MIT" }
readme = { file = "README.md", content-type = "text/markdown" }

View file

@ -2,7 +2,7 @@
name = "haiku.rag"
description = "Opinionated agentic RAG powered by LanceDB, Pydantic AI, and Docling"
version = "0.63.2"
version = "0.64.0"
authors = [{ name = "Yiorgis Gozadinos", email = "ggozadinos@gmail.com" }]
license = { text = "MIT" }
readme = { file = "README.md", content-type = "text/markdown" }
@ -30,7 +30,7 @@ classifiers = [
]
dependencies = [
"haiku.rag-slim[docling,voyageai,mxbai,cohere,zeroentropy,tui,cross-encoder]==0.63.2",
"haiku.rag-slim[docling,voyageai,mxbai,cohere,zeroentropy,tui,cross-encoder]==0.64.0",
]
[project.scripts]
@ -38,9 +38,9 @@ haiku-rag = "haiku.rag.cli:cli"
[project.optional-dependencies]
tui = ["textual>=8.2.4"]
s3 = ["haiku.rag-slim[s3]==0.63.2"]
cross-encoder = ["haiku.rag-slim[cross-encoder]==0.63.2"]
ingester = ["haiku.rag-slim[ingester]==0.63.2"]
s3 = ["haiku.rag-slim[s3]==0.64.0"]
cross-encoder = ["haiku.rag-slim[cross-encoder]==0.64.0"]
ingester = ["haiku.rag-slim[ingester]==0.64.0"]
[build-system]
requires = ["hatchling"]

File diff suppressed because one or more lines are too long

View file

@ -156,6 +156,6 @@ async def test_get_by_uri_with_orphan_meta_returns_none(temp_db_path):
async with Store(temp_db_path, create=True, skip_migration_check=True) as store:
repo = DocumentRepository(store)
await store.document_meta_table.add(
[DocumentMetaRecord(document_id="ghost", uri="u-ghost", metadata="{}")]
[DocumentMetaRecord(id="ghost", uri="u-ghost", metadata="{}")]
)
assert await repo.get_by_uri("u-ghost") is None

View file

@ -52,7 +52,7 @@ class TestV0_58_0Migration:
meta_rows = await store.document_meta_table.query().to_list()
assert len(meta_rows) == 1
row = meta_rows[0]
assert row["document_id"] == "doc-1"
assert row["id"] == "doc-1"
assert row["uri"] == "s3://b/one"
assert row["title"] == "One"
assert json.loads(row["metadata"]) == {"source_revision": "r1", "md5": "a"}
@ -95,7 +95,7 @@ class TestV0_58_0Migration:
meta_rows = await store.document_meta_table.query().to_list()
assert len(meta_rows) == 1
assert meta_rows[0]["document_id"] == "doc-1"
assert meta_rows[0]["id"] == "doc-1"
@pytest.mark.asyncio
@ -115,14 +115,14 @@ class TestV0_58_0MigrationEdgeCases:
)
# Pretend a prior run already moved doc "a".
await store.document_meta_table.add(
[DocumentMetaRecord(document_id="a", uri="u-a", metadata="{}")]
[DocumentMetaRecord(id="a", uri="u-a", metadata="{}")]
)
await store.set_haiku_version("0.57.0")
async with Store(temp_db_path, skip_migration_check=True) as store:
await store.migrate()
rows = await store.document_meta_table.query().to_list()
by_id = {r["document_id"]: r for r in rows}
by_id = {r["id"]: r for r in rows}
assert set(by_id) == {"a", "b"} # exactly one row each, no duplicates
assert len(rows) == 2

View file

@ -0,0 +1,51 @@
import pytest
from lancedb.pydantic import LanceModel
from haiku.rag.store.engine import Store
from haiku.rag.store.upgrades.v0_64_0 import _apply_rename_document_meta_id
class LegacyMetaRecord(LanceModel):
"""The pre-0.63.2 `document_meta` record (identity column `document_id`)."""
document_id: str
uri: str | None = None
title: str | None = None
metadata: str = "{}"
created_at: str = ""
updated_at: str = ""
async def _seed_legacy_meta(store: Store, doc_id: str) -> None:
await store.db.drop_table("document_meta")
store.document_meta_table = await store.db.create_table(
"document_meta", schema=LegacyMetaRecord
)
await store.document_meta_table.add(
[LegacyMetaRecord(document_id=doc_id, uri="u", metadata="{}")]
)
@pytest.mark.asyncio
async def test_renames_document_id_to_id_and_keeps_rows(temp_db_path):
async with Store(temp_db_path, create=True, skip_migration_check=True) as store:
await _seed_legacy_meta(store, "doc-1")
await _apply_rename_document_meta_id(store)
names = {f.name for f in await store.document_meta_table.schema()}
assert "id" in names and "document_id" not in names
rows = await store.document_meta_table.query().to_list()
assert [r["id"] for r in rows] == ["doc-1"]
@pytest.mark.asyncio
async def test_idempotent_when_already_renamed(temp_db_path):
async with Store(temp_db_path, create=True, skip_migration_check=True) as store:
await _seed_legacy_meta(store, "doc-1")
await _apply_rename_document_meta_id(store)
# Second run must be a no-op, not an error.
await _apply_rename_document_meta_id(store)
names = {f.name for f in await store.document_meta_table.schema()}
assert "id" in names and "document_id" not in names

View file

@ -114,7 +114,7 @@ async def _build_db(
]
)
await docs_tbl.add([DocumentRecord(id="d1", content="hello")])
await meta_tbl.add([DocumentMetaRecord(document_id="d1", uri="test://d1")])
await meta_tbl.add([DocumentMetaRecord(id="d1", uri="test://d1")])
await items_tbl.add(
[
DocumentItemRecord(
@ -240,7 +240,7 @@ async def _add_doc(db, doc_id, *, items, metadata=None, chunks=None):
await meta_tbl.add(
[
DocumentMetaRecord(
document_id=doc_id,
id=doc_id,
uri=f"test://{doc_id}",
metadata=json.dumps(metadata or {}),
)
@ -1143,9 +1143,7 @@ async def _build_dup_db(path, docs: dict[str, list[int]], *, vector_dim: int = 8
)
for doc_id, idxs in docs.items():
await docs_tbl.add([DocumentRecord(id=doc_id, content="x")])
await meta_tbl.add(
[DocumentMetaRecord(document_id=doc_id, uri=f"test://{doc_id}")]
)
await meta_tbl.add([DocumentMetaRecord(id=doc_id, uri=f"test://{doc_id}")])
await items_tbl.add(
[
DocumentItemRecord(

View file

@ -37,6 +37,32 @@ async def test_search_with_uri_filter(temp_db_path):
assert result.document_uri == "https://other.com/java.html"
@pytest.mark.vcr()
async def test_filter_by_document_id(temp_db_path):
"""A document's identity is `id` everywhere user-facing, so filtering by
`id` must resolve against the document_meta table (list/count/search)."""
async with HaikuRAG(db_path=temp_db_path, create=True) as client:
doc = await client.create_document(
content="Filterable content about pelicans",
uri="https://example.com/pelican.html",
title="Pelican Guide",
)
await client.create_document(
content="Other content about penguins",
uri="https://example.com/penguin.html",
title="Penguin Guide",
)
listed = await client.list_documents(filter=f"id = '{doc.id}'")
assert [d.id for d in listed] == [doc.id]
assert await client.count_documents(filter=f"id = '{doc.id}'") == 1
results = await client.search("content", limit=5, filter=f"id = '{doc.id}'")
assert len(results) > 0
assert all(r.document_id == doc.id for r in results)
@pytest.mark.vcr()
async def test_search_with_title_filter(temp_db_path):
"""Test filtering by document title."""

120
uv.lock
View file

@ -751,7 +751,7 @@ name = "cuda-bindings"
version = "13.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cuda-pathfinder", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "cuda-pathfinder" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/52/c8/b2589d68acf7e3d63e2be330b84bc25712e97ed799affbca7edd7eae25d6/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e865447abfb83d6a98ad5130ed3c70b1fc295ae3eeee39fd07b4ddb0671b6788", size = 5722404, upload-time = "2026-03-11T00:12:44.041Z" },
@ -782,37 +782,37 @@ wheels = [
[package.optional-dependencies]
cublas = [
{ name = "nvidia-cublas", marker = "sys_platform == 'linux'" },
{ name = "nvidia-cublas" },
]
cudart = [
{ name = "nvidia-cuda-runtime", marker = "sys_platform == 'linux'" },
{ name = "nvidia-cuda-runtime" },
]
cufft = [
{ name = "nvidia-cufft", marker = "sys_platform == 'linux'" },
{ name = "nvidia-cufft" },
]
cufile = [
{ name = "nvidia-cufile", marker = "sys_platform == 'linux'" },
{ name = "nvidia-cufile" },
]
cupti = [
{ name = "nvidia-cuda-cupti", marker = "sys_platform == 'linux'" },
{ name = "nvidia-cuda-cupti" },
]
curand = [
{ name = "nvidia-curand", marker = "sys_platform == 'linux'" },
{ name = "nvidia-curand" },
]
cusolver = [
{ name = "nvidia-cusolver", marker = "sys_platform == 'linux'" },
{ name = "nvidia-cusolver" },
]
cusparse = [
{ name = "nvidia-cusparse", marker = "sys_platform == 'linux'" },
{ name = "nvidia-cusparse" },
]
nvjitlink = [
{ name = "nvidia-nvjitlink", marker = "sys_platform == 'linux'" },
{ name = "nvidia-nvjitlink" },
]
nvrtc = [
{ name = "nvidia-cuda-nvrtc", marker = "sys_platform == 'linux'" },
{ name = "nvidia-cuda-nvrtc" },
]
nvtx = [
{ name = "nvidia-nvtx", marker = "sys_platform == 'linux'" },
{ name = "nvidia-nvtx" },
]
[[package]]
@ -1275,7 +1275,7 @@ name = "ffmpeg-python"
version = "0.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "future", marker = "python_full_version < '3.14'" },
{ name = "future" },
]
sdist = { url = "https://files.pythonhosted.org/packages/dd/5e/d5f9105d59c1325759d838af4e973695081fbbc97182baf73afc78dec266/ffmpeg-python-0.2.0.tar.gz", hash = "sha256:65225db34627c578ef0e11c8b1eb528bb35e024752f6f10b78c011f6f64c4127", size = 21543, upload-time = "2019-07-06T00:19:08.989Z" }
wheels = [
@ -1580,7 +1580,7 @@ wheels = [
[[package]]
name = "haiku-rag"
version = "0.63.2"
version = "0.64.0"
source = { editable = "." }
dependencies = [
{ name = "haiku-rag-slim", extra = ["cohere", "cross-encoder", "docling", "mxbai", "tui", "voyageai", "zeroentropy"] },
@ -1647,7 +1647,7 @@ dev = [
[[package]]
name = "haiku-rag-evals"
version = "0.63.2"
version = "0.64.0"
source = { editable = "evaluations" }
dependencies = [
{ name = "datasets" },
@ -1670,7 +1670,7 @@ requires-dist = [
[[package]]
name = "haiku-rag-slim"
version = "0.63.2"
version = "0.64.0"
source = { editable = "haiku_rag_slim" }
dependencies = [
{ name = "docling-core" },
@ -2292,15 +2292,15 @@ name = "langchain-core"
version = "1.4.8"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jsonpatch", marker = "python_full_version < '3.14'" },
{ name = "langchain-protocol", marker = "python_full_version < '3.14'" },
{ name = "langsmith", marker = "python_full_version < '3.14'" },
{ name = "packaging", marker = "python_full_version < '3.14'" },
{ name = "pydantic", marker = "python_full_version < '3.14'" },
{ name = "pyyaml", marker = "python_full_version < '3.14'" },
{ name = "tenacity", marker = "python_full_version < '3.14'" },
{ name = "typing-extensions", marker = "python_full_version < '3.14'" },
{ name = "uuid-utils", marker = "python_full_version < '3.14'" },
{ name = "jsonpatch" },
{ name = "langchain-protocol" },
{ name = "langsmith" },
{ name = "packaging" },
{ name = "pydantic" },
{ name = "pyyaml" },
{ name = "tenacity" },
{ name = "typing-extensions" },
{ name = "uuid-utils" },
]
sdist = { url = "https://files.pythonhosted.org/packages/12/e3/bea6d0080acf183332f24dcd74c208aee5857cf8f783c3fb0bd86027d8fb/langchain_core-1.4.8.tar.gz", hash = "sha256:5bf1f8411077c904182ad8f975943d36adcbf579c4e017b3a118b719229ebf9a", size = 957974, upload-time = "2026-06-18T19:39:23.636Z" }
wheels = [
@ -2312,7 +2312,7 @@ name = "langchain-protocol"
version = "0.0.18"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.14'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d2/59/b5959aea96faa9146e2e49a7a22882b3528c62efafe9a6a95beab30c2305/langchain_protocol-0.0.18.tar.gz", hash = "sha256:ec3e11782f1ed0c9db38e5a9ed01b0e7a0d3fba406faa8aef6594b73c56a63e6", size = 6150, upload-time = "2026-06-18T17:08:26.959Z" }
wheels = [
@ -2324,7 +2324,7 @@ name = "langchain-text-splitters"
version = "1.1.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "langchain-core", marker = "python_full_version < '3.14'" },
{ name = "langchain-core" },
]
sdist = { url = "https://files.pythonhosted.org/packages/26/9f/6c545900fefb7b00ddfa3f16b80d61338a0ec68c31c5451eeeab99082760/langchain_text_splitters-1.1.2.tar.gz", hash = "sha256:782a723db0a4746ac91e251c7c1d57fd23636e4f38ed733074e28d7a86f41627", size = 293580, upload-time = "2026-04-16T14:20:39.162Z" }
wheels = [
@ -2336,20 +2336,20 @@ name = "langsmith"
version = "0.9.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio", marker = "python_full_version < '3.14'" },
{ name = "distro", marker = "python_full_version < '3.14'" },
{ name = "httpx", marker = "python_full_version < '3.14'" },
{ name = "orjson", marker = "python_full_version < '3.14' and platform_python_implementation != 'PyPy'" },
{ name = "packaging", marker = "python_full_version < '3.14'" },
{ name = "pydantic", marker = "python_full_version < '3.14'" },
{ name = "requests", marker = "python_full_version < '3.14'" },
{ name = "requests-toolbelt", marker = "python_full_version < '3.14'" },
{ name = "sniffio", marker = "python_full_version < '3.14'" },
{ name = "typing-extensions", marker = "python_full_version < '3.14'" },
{ name = "uuid-utils", marker = "python_full_version < '3.14'" },
{ name = "websockets", marker = "python_full_version < '3.14'" },
{ name = "xxhash", marker = "python_full_version < '3.14'" },
{ name = "zstandard", marker = "python_full_version < '3.14'" },
{ name = "anyio" },
{ name = "distro" },
{ name = "httpx" },
{ name = "orjson", marker = "platform_python_implementation != 'PyPy'" },
{ name = "packaging" },
{ name = "pydantic" },
{ name = "requests" },
{ name = "requests-toolbelt" },
{ name = "sniffio" },
{ name = "typing-extensions" },
{ name = "uuid-utils" },
{ name = "websockets" },
{ name = "xxhash" },
{ name = "zstandard" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5b/26/b72987d947278f63ec1e85f01ce85ca7ab2621c7efc0845d4a3a8e5d5dfb/langsmith-0.9.1.tar.gz", hash = "sha256:e5eb905224d156bcece4985285c55b51fffcb06c9353b2c4adb42e1c48b0d05d", size = 4557557, upload-time = "2026-06-23T17:04:23.233Z" }
wheels = [
@ -2951,7 +2951,7 @@ name = "nvidia-cudnn-cu13"
version = "9.19.0.56"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-cublas", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "nvidia-cublas" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/f1/84/26025437c1e6b61a707442184fa0c03d083b661adf3a3eecfd6d21677740/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:6ed29ffaee1176c612daf442e4dd6cfeb6a0caa43ddcbeb59da94953030b1be4", size = 433781201, upload-time = "2026-02-03T20:40:53.805Z" },
@ -2963,7 +2963,7 @@ name = "nvidia-cufft"
version = "12.0.0.61"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "nvidia-nvjitlink" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" },
@ -2993,9 +2993,9 @@ name = "nvidia-cusolver"
version = "12.0.4.66"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-cublas", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "nvidia-cusparse", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "nvidia-cublas" },
{ name = "nvidia-cusparse" },
{ name = "nvidia-nvjitlink" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" },
@ -3007,7 +3007,7 @@ name = "nvidia-cusparse"
version = "12.6.3.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "nvidia-nvjitlink" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" },
@ -4503,7 +4503,7 @@ name = "requests-toolbelt"
version = "1.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "requests", marker = "python_full_version < '3.14'" },
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" }
wheels = [
@ -4809,8 +4809,8 @@ name = "secretstorage"
version = "3.5.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cryptography", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "jeepney", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "cryptography" },
{ name = "jeepney" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" }
wheels = [
@ -5679,16 +5679,16 @@ name = "voyageai"
version = "0.3.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp", marker = "python_full_version < '3.14'" },
{ name = "aiolimiter", marker = "python_full_version < '3.14'" },
{ name = "ffmpeg-python", marker = "python_full_version < '3.14'" },
{ name = "langchain-text-splitters", marker = "python_full_version < '3.14'" },
{ name = "numpy", marker = "python_full_version < '3.14'" },
{ name = "pillow", marker = "python_full_version < '3.14'" },
{ name = "pydantic", marker = "python_full_version < '3.14'" },
{ name = "requests", marker = "python_full_version < '3.14'" },
{ name = "tenacity", marker = "python_full_version < '3.14'" },
{ name = "tokenizers", marker = "python_full_version < '3.14'" },
{ name = "aiohttp" },
{ name = "aiolimiter" },
{ name = "ffmpeg-python" },
{ name = "langchain-text-splitters" },
{ name = "numpy" },
{ name = "pillow" },
{ name = "pydantic" },
{ name = "requests" },
{ name = "tenacity" },
{ name = "tokenizers" },
]
sdist = { url = "https://files.pythonhosted.org/packages/94/16/1b46b3cd401e1717a68197c1fe336d7bb4e0a1833f8105e1738f5b1add05/voyageai-0.3.7.tar.gz", hash = "sha256:826cd97f97223f42b5babc5c459c9c80f3a8215ce5c0e007b0b276550f790d24", size = 26485, upload-time = "2025-12-16T18:43:05.26Z" }
wheels = [