Adapt search to lancedb
This commit is contained in:
parent
c69daecf53
commit
0592a7d122
10 changed files with 132 additions and 121 deletions
|
|
@ -56,7 +56,7 @@ dev = [
|
||||||
"mkdocs>=1.6.1",
|
"mkdocs>=1.6.1",
|
||||||
"mkdocs-material>=9.6.14",
|
"mkdocs-material>=9.6.14",
|
||||||
"pre-commit>=4.2.0",
|
"pre-commit>=4.2.0",
|
||||||
"pyright>=1.1.403",
|
"pyright>=1.1.404",
|
||||||
"pytest>=8.4.0",
|
"pytest>=8.4.0",
|
||||||
"pytest-asyncio>=1.0.0",
|
"pytest-asyncio>=1.0.0",
|
||||||
"pytest-cov>=6.2.1",
|
"pytest-cov>=6.2.1",
|
||||||
|
|
|
||||||
|
|
@ -53,9 +53,9 @@ class HaikuRAGApp:
|
||||||
await self.client.delete_document(doc_id)
|
await self.client.delete_document(doc_id)
|
||||||
self.console.print(f"[b]Document {doc_id} deleted successfully.[/b]")
|
self.console.print(f"[b]Document {doc_id} deleted successfully.[/b]")
|
||||||
|
|
||||||
async def search(self, query: str, limit: int = 5, k: int = 60):
|
async def search(self, query: str, limit: int = 5):
|
||||||
async with HaikuRAG(db_path=self.db_path) as self.client:
|
async with HaikuRAG(db_path=self.db_path) as self.client:
|
||||||
results = await self.client.search(query, limit=limit, k=k)
|
results = await self.client.search(query, limit=limit)
|
||||||
if not results:
|
if not results:
|
||||||
self.console.print("[red]No results found.[/red]")
|
self.console.print("[red]No results found.[/red]")
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -135,11 +135,6 @@ def search(
|
||||||
"-l",
|
"-l",
|
||||||
help="Maximum number of results to return",
|
help="Maximum number of results to return",
|
||||||
),
|
),
|
||||||
k: int = typer.Option(
|
|
||||||
60,
|
|
||||||
"--k",
|
|
||||||
help="Reciprocal Rank Fusion k parameter",
|
|
||||||
),
|
|
||||||
db: Path = typer.Option(
|
db: Path = typer.Option(
|
||||||
Config.DEFAULT_DATA_DIR / "haiku.rag.lancedb",
|
Config.DEFAULT_DATA_DIR / "haiku.rag.lancedb",
|
||||||
"--db",
|
"--db",
|
||||||
|
|
@ -147,7 +142,7 @@ def search(
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
app = HaikuRAGApp(db_path=db)
|
app = HaikuRAGApp(db_path=db)
|
||||||
asyncio.run(app.search(query=query, limit=limit, k=k))
|
asyncio.run(app.search(query=query, limit=limit))
|
||||||
|
|
||||||
|
|
||||||
@cli.command("ask", help="Ask a question using the QA agent")
|
@cli.command("ask", help="Ask a question using the QA agent")
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,11 @@ from haiku.rag.config import Config
|
||||||
from haiku.rag.reader import FileReader
|
from haiku.rag.reader import FileReader
|
||||||
from haiku.rag.reranking import get_reranker
|
from haiku.rag.reranking import get_reranker
|
||||||
from haiku.rag.store.engine import Store
|
from haiku.rag.store.engine import Store
|
||||||
from haiku.rag.store.factory import create_repositories
|
|
||||||
from haiku.rag.store.models.chunk import Chunk
|
from haiku.rag.store.models.chunk import Chunk
|
||||||
from haiku.rag.store.models.document import Document
|
from haiku.rag.store.models.document import Document
|
||||||
|
from haiku.rag.store.repositories.chunk import ChunkRepository
|
||||||
|
from haiku.rag.store.repositories.document import DocumentRepository
|
||||||
|
from haiku.rag.store.repositories.settings import SettingsRepository
|
||||||
from haiku.rag.utils import text_to_docling_document
|
from haiku.rag.utils import text_to_docling_document
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -34,9 +36,8 @@ class HaikuRAG:
|
||||||
if not db_path.parent.exists():
|
if not db_path.parent.exists():
|
||||||
Path.mkdir(db_path.parent, parents=True)
|
Path.mkdir(db_path.parent, parents=True)
|
||||||
self.store = Store(db_path, skip_validation=skip_validation)
|
self.store = Store(db_path, skip_validation=skip_validation)
|
||||||
repos = create_repositories(self.store)
|
self.document_repository = DocumentRepository(self.store)
|
||||||
self.document_repository = repos["document"]
|
self.chunk_repository = ChunkRepository(self.store)
|
||||||
self.chunk_repository = repos["chunk"]
|
|
||||||
|
|
||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
"""Async context manager entry."""
|
"""Async context manager entry."""
|
||||||
|
|
@ -316,14 +317,14 @@ class HaikuRAG:
|
||||||
return await self.document_repository.list_all(limit=limit, offset=offset)
|
return await self.document_repository.list_all(limit=limit, offset=offset)
|
||||||
|
|
||||||
async def search(
|
async def search(
|
||||||
self, query: str, limit: int = 5, k: int = 60
|
self, query: str, limit: int = 5, search_type: str = "hybrid"
|
||||||
) -> list[tuple[Chunk, float]]:
|
) -> list[tuple[Chunk, float]]:
|
||||||
"""Search for relevant chunks using hybrid search (vector similarity + full-text search) with reranking.
|
"""Search for relevant chunks using the specified search method with optional reranking.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
query: The search query string.
|
query: The search query string.
|
||||||
limit: Maximum number of results to return.
|
limit: Maximum number of results to return.
|
||||||
k: Parameter for Reciprocal Rank Fusion (default: 60).
|
search_type: Type of search - "vector", "fts", or "hybrid" (default).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List of (chunk, score) tuples ordered by relevance.
|
List of (chunk, score) tuples ordered by relevance.
|
||||||
|
|
@ -332,12 +333,29 @@ class HaikuRAG:
|
||||||
reranker = get_reranker()
|
reranker = get_reranker()
|
||||||
|
|
||||||
if reranker is None:
|
if reranker is None:
|
||||||
return await self.chunk_repository.search_chunks_hybrid(query, limit, k)
|
# No reranking - return direct search results
|
||||||
|
if search_type == "vector":
|
||||||
|
return await self.chunk_repository.search_chunks(query, limit)
|
||||||
|
elif search_type == "fts":
|
||||||
|
return await self.chunk_repository.search_chunks_fts(query, limit)
|
||||||
|
else: # hybrid (default)
|
||||||
|
return await self.chunk_repository.search_chunks_hybrid(query, limit)
|
||||||
|
|
||||||
# Get more initial results (3X) for reranking
|
# Get more initial results (3X) for reranking
|
||||||
search_results = await self.chunk_repository.search_chunks_hybrid(
|
search_limit = limit * 3
|
||||||
query, limit * 3, k
|
if search_type == "vector":
|
||||||
)
|
search_results = await self.chunk_repository.search_chunks(
|
||||||
|
query, search_limit
|
||||||
|
)
|
||||||
|
elif search_type == "fts":
|
||||||
|
search_results = await self.chunk_repository.search_chunks_fts(
|
||||||
|
query, search_limit
|
||||||
|
)
|
||||||
|
else: # hybrid (default)
|
||||||
|
search_results = await self.chunk_repository.search_chunks_hybrid(
|
||||||
|
query, search_limit
|
||||||
|
)
|
||||||
|
|
||||||
# Apply reranking
|
# Apply reranking
|
||||||
chunks = [chunk for chunk, _ in search_results]
|
chunks = [chunk for chunk, _ in search_results]
|
||||||
reranked_results = await reranker.rerank(query, chunks, top_n=limit)
|
reranked_results = await reranker.rerank(query, chunks, top_n=limit)
|
||||||
|
|
@ -507,8 +525,7 @@ class HaikuRAG:
|
||||||
self.store.recreate_embeddings_table()
|
self.store.recreate_embeddings_table()
|
||||||
|
|
||||||
# Update settings to current config
|
# Update settings to current config
|
||||||
repos = create_repositories(self.store)
|
settings_repo = SettingsRepository(self.store)
|
||||||
settings_repo = repos["settings"]
|
|
||||||
settings_repo.save_current_settings()
|
settings_repo.save_current_settings()
|
||||||
|
|
||||||
documents = await self.list_documents()
|
documents = await self.list_documents()
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from haiku.rag.store.engine import Store
|
|
||||||
from haiku.rag.store.repositories.chunk import ChunkRepository
|
|
||||||
from haiku.rag.store.repositories.document import DocumentRepository
|
|
||||||
from haiku.rag.store.repositories.settings import SettingsRepository
|
|
||||||
|
|
||||||
|
|
||||||
def create_store(
|
|
||||||
db_path: Path,
|
|
||||||
skip_validation: bool = False,
|
|
||||||
) -> Store:
|
|
||||||
"""Create a Store instance."""
|
|
||||||
return Store(db_path, skip_validation)
|
|
||||||
|
|
||||||
|
|
||||||
def create_repositories(store: Store):
|
|
||||||
"""Create repository instances for the store."""
|
|
||||||
return {
|
|
||||||
"chunk": ChunkRepository(store),
|
|
||||||
"document": DocumentRepository(store),
|
|
||||||
"settings": SettingsRepository(store),
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import json
|
import json
|
||||||
import re
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from docling_core.types.doc.document import DoclingDocument
|
from docling_core.types.doc.document import DoclingDocument
|
||||||
|
from lancedb.rerankers import RRFReranker
|
||||||
|
|
||||||
from haiku.rag.chunker import chunker
|
from haiku.rag.chunker import chunker
|
||||||
from haiku.rag.embeddings import get_embedder
|
from haiku.rag.embeddings import get_embedder
|
||||||
|
|
@ -17,6 +17,13 @@ class ChunkRepository:
|
||||||
self.store = store
|
self.store = store
|
||||||
self.embedder = get_embedder()
|
self.embedder = get_embedder()
|
||||||
|
|
||||||
|
def _ensure_fts_index(self) -> None:
|
||||||
|
"""Ensure FTS index exists on the content column."""
|
||||||
|
try:
|
||||||
|
self.store.chunks_table.create_fts_index("content", replace=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
async def create(self, entity: Chunk) -> Chunk:
|
async def create(self, entity: Chunk) -> Chunk:
|
||||||
"""Create a chunk in the database."""
|
"""Create a chunk in the database."""
|
||||||
assert entity.document_id, "Chunk must have a document_id to be created"
|
assert entity.document_id, "Chunk must have a document_id to be created"
|
||||||
|
|
@ -175,69 +182,63 @@ class ChunkRepository:
|
||||||
# Generate embedding for the query
|
# Generate embedding for the query
|
||||||
query_embedding = await self.embedder.embed(query)
|
query_embedding = await self.embedder.embed(query)
|
||||||
|
|
||||||
# Perform vector search
|
# Perform vector search with proper query type
|
||||||
results = (
|
results = (
|
||||||
self.store.chunks_table.search(query_embedding)
|
self.store.chunks_table.search(query_embedding, query_type="vector")
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.to_pydantic(self.store.ChunkRecord)
|
.to_pydantic(self.store.ChunkRecord)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get document info for each chunk
|
return await self._process_search_results(results)
|
||||||
chunks_with_scores = []
|
|
||||||
for chunk_record in results:
|
|
||||||
# Get document info
|
|
||||||
doc_results = list(
|
|
||||||
self.store.documents_table.search()
|
|
||||||
.where(f"id = '{chunk_record.document_id}'")
|
|
||||||
.limit(1)
|
|
||||||
.to_pydantic(DocumentRecord)
|
|
||||||
)
|
|
||||||
|
|
||||||
doc_uri = doc_results[0].uri if doc_results else None
|
|
||||||
doc_meta = doc_results[0].metadata if doc_results else "{}"
|
|
||||||
|
|
||||||
chunk = Chunk(
|
|
||||||
id=chunk_record.id,
|
|
||||||
document_id=chunk_record.document_id,
|
|
||||||
content=chunk_record.content,
|
|
||||||
metadata=json.loads(chunk_record.metadata)
|
|
||||||
if chunk_record.metadata
|
|
||||||
else {},
|
|
||||||
document_uri=doc_uri,
|
|
||||||
document_meta=json.loads(doc_meta) if doc_meta else {},
|
|
||||||
)
|
|
||||||
|
|
||||||
# LanceDB returns similarity score (higher is better)
|
|
||||||
score = getattr(
|
|
||||||
chunk_record, "_distance", 0.8
|
|
||||||
) # Default score if not available
|
|
||||||
chunks_with_scores.append(
|
|
||||||
(chunk, 1.0 - score)
|
|
||||||
) # Convert distance to similarity
|
|
||||||
|
|
||||||
return chunks_with_scores
|
|
||||||
|
|
||||||
async def search_chunks_fts(
|
async def search_chunks_fts(
|
||||||
self, query: str, limit: int = 5
|
self, query: str, limit: int = 5
|
||||||
) -> list[tuple[Chunk, float]]:
|
) -> list[tuple[Chunk, float]]:
|
||||||
"""Search for chunks using full-text search."""
|
"""Search for chunks using full-text search."""
|
||||||
# Extract keywords for search
|
if not query.strip():
|
||||||
words = re.findall(r"\b\w+\b", query.lower())
|
|
||||||
|
|
||||||
if not words:
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# Search by content similarity (approximate FTS using vector search)
|
# Ensure FTS index exists
|
||||||
# This is a fallback since LanceDB doesn't have built-in FTS
|
self._ensure_fts_index()
|
||||||
return await self.search_chunks(query, limit)
|
|
||||||
|
# Use LanceDB's native full-text search
|
||||||
|
try:
|
||||||
|
results = (
|
||||||
|
self.store.chunks_table.search(query, query_type="fts")
|
||||||
|
.limit(limit)
|
||||||
|
.to_pydantic(self.store.ChunkRecord)
|
||||||
|
)
|
||||||
|
return await self._process_search_results(results)
|
||||||
|
except Exception:
|
||||||
|
# Fallback to vector search if FTS is not available or fails
|
||||||
|
return await self.search_chunks(query, limit)
|
||||||
|
|
||||||
async def search_chunks_hybrid(
|
async def search_chunks_hybrid(
|
||||||
self, query: str, limit: int = 5, k: int = 60
|
self, query: str, limit: int = 5
|
||||||
) -> list[tuple[Chunk, float]]:
|
) -> list[tuple[Chunk, float]]:
|
||||||
"""Hybrid search - for now, just use vector search in LanceDB."""
|
"""Hybrid search combining vector and full-text search with native LanceDB RRFReranker."""
|
||||||
# For LanceDB, we'll use vector search as the primary method
|
if not query.strip():
|
||||||
# In the future, this could be enhanced with additional ranking strategies
|
return []
|
||||||
return await self.search_chunks(query, limit)
|
|
||||||
|
# Ensure FTS index exists for hybrid search
|
||||||
|
self._ensure_fts_index()
|
||||||
|
|
||||||
|
# Generate embedding for the query since LanceDB doesn't have embedding function configured
|
||||||
|
query_embedding = await self.embedder.embed(query)
|
||||||
|
|
||||||
|
# Create RRF reranker (k parameter is handled internally)
|
||||||
|
reranker = RRFReranker()
|
||||||
|
|
||||||
|
# Perform native hybrid search with RRF reranking
|
||||||
|
results = (
|
||||||
|
self.store.chunks_table.search(query_type="hybrid")
|
||||||
|
.vector(query_embedding)
|
||||||
|
.text(query)
|
||||||
|
.rerank(reranker)
|
||||||
|
.limit(limit)
|
||||||
|
.to_pydantic(self.store.ChunkRecord)
|
||||||
|
)
|
||||||
|
return await self._process_search_results(results)
|
||||||
|
|
||||||
async def get_by_document_id(self, document_id: str) -> list[Chunk]:
|
async def get_by_document_id(self, document_id: str) -> list[Chunk]:
|
||||||
"""Get all chunks for a specific document."""
|
"""Get all chunks for a specific document."""
|
||||||
|
|
@ -294,3 +295,41 @@ class ChunkRepository:
|
||||||
adjacent_chunks.append(c)
|
adjacent_chunks.append(c)
|
||||||
|
|
||||||
return adjacent_chunks
|
return adjacent_chunks
|
||||||
|
|
||||||
|
async def _process_search_results(self, results) -> list[tuple[Chunk, float]]:
|
||||||
|
"""Process search results into chunks with document info and scores."""
|
||||||
|
chunks_with_scores = []
|
||||||
|
|
||||||
|
for chunk_record in results:
|
||||||
|
# Get document info
|
||||||
|
doc_results = list(
|
||||||
|
self.store.documents_table.search()
|
||||||
|
.where(f"id = '{chunk_record.document_id}'")
|
||||||
|
.limit(1)
|
||||||
|
.to_pydantic(DocumentRecord)
|
||||||
|
)
|
||||||
|
|
||||||
|
doc_uri = doc_results[0].uri if doc_results else None
|
||||||
|
doc_meta = doc_results[0].metadata if doc_results else "{}"
|
||||||
|
|
||||||
|
chunk = Chunk(
|
||||||
|
id=chunk_record.id,
|
||||||
|
document_id=chunk_record.document_id,
|
||||||
|
content=chunk_record.content,
|
||||||
|
metadata=json.loads(chunk_record.metadata)
|
||||||
|
if chunk_record.metadata
|
||||||
|
else {},
|
||||||
|
document_uri=doc_uri,
|
||||||
|
document_meta=json.loads(doc_meta) if doc_meta else {},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get distance score - LanceDB returns _distance (lower is better)
|
||||||
|
distance = getattr(chunk_record, "_distance", 1.0)
|
||||||
|
|
||||||
|
# Convert distance to similarity score (higher is better)
|
||||||
|
# Using exponential decay to convert distance to similarity
|
||||||
|
score = max(0.0, 1.0 / (1.0 + distance))
|
||||||
|
|
||||||
|
chunks_with_scores.append((chunk, score))
|
||||||
|
|
||||||
|
return chunks_with_scores
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ async def test_search(app: HaikuRAGApp, monkeypatch):
|
||||||
with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
|
with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
|
||||||
await app.search("query")
|
await app.search("query")
|
||||||
|
|
||||||
mock_client.search.assert_called_once_with("query", limit=5, k=60)
|
mock_client.search.assert_called_once_with("query", limit=5)
|
||||||
assert mock_rich_print_search.call_count == len(mock_results)
|
assert mock_rich_print_search.call_count == len(mock_results)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -167,7 +167,7 @@ async def test_search_no_results(app: HaikuRAGApp, monkeypatch):
|
||||||
with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
|
with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
|
||||||
await app.search("query")
|
await app.search("query")
|
||||||
|
|
||||||
mock_client.search.assert_called_once_with("query", limit=5, k=60)
|
mock_client.search.assert_called_once_with("query", limit=5)
|
||||||
mock_print.assert_called_once_with("[red]No results found.[/red]")
|
mock_print.assert_called_once_with("[red]No results found.[/red]")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ def test_search():
|
||||||
result = runner.invoke(cli, ["search", "query"])
|
result = runner.invoke(cli, ["search", "query"])
|
||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
mock_app_instance.search.assert_called_once_with(query="query", limit=5, k=60)
|
mock_app_instance.search.assert_called_once_with(query="query", limit=5)
|
||||||
|
|
||||||
|
|
||||||
def test_serve():
|
def test_serve():
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,9 @@ async def test_search_qa_corpus(qa_corpus: Dataset, temp_db_path):
|
||||||
store = Store(temp_db_path)
|
store = Store(temp_db_path)
|
||||||
doc_repo = DocumentRepository(store)
|
doc_repo = DocumentRepository(store)
|
||||||
chunk_repo = ChunkRepository(store)
|
chunk_repo = ChunkRepository(store)
|
||||||
|
|
||||||
|
# Load first 20 documents with embeddings (reduced for faster testing)
|
||||||
num_documents = 20
|
num_documents = 20
|
||||||
# Load first 10 documents with embeddings (reduced for faster testing)
|
|
||||||
documents = []
|
documents = []
|
||||||
for i in range(num_documents):
|
for i in range(num_documents):
|
||||||
doc_data = qa_corpus[i]
|
doc_data = qa_corpus[i]
|
||||||
|
|
@ -41,7 +42,7 @@ async def test_search_qa_corpus(qa_corpus: Dataset, temp_db_path):
|
||||||
)
|
)
|
||||||
documents.append((created_document, doc_data))
|
documents.append((created_document, doc_data))
|
||||||
|
|
||||||
for i in range(5): # Test with first few documents
|
for i in range(3): # Test with first few documents
|
||||||
target_document, doc_data = documents[i]
|
target_document, doc_data = documents[i]
|
||||||
question = doc_data["question"]
|
question = doc_data["question"]
|
||||||
|
|
||||||
|
|
@ -55,10 +56,6 @@ async def test_search_qa_corpus(qa_corpus: Dataset, temp_db_path):
|
||||||
target_document_ids = {chunk.document_id for chunk, _ in fts_results}
|
target_document_ids = {chunk.document_id for chunk, _ in fts_results}
|
||||||
assert target_document.id in target_document_ids
|
assert target_document.id in target_document_ids
|
||||||
|
|
||||||
for i in range(num_documents): # Test with first few documents
|
|
||||||
target_document, doc_data = documents[i]
|
|
||||||
question = doc_data["question"]
|
|
||||||
|
|
||||||
# Test hybrid search
|
# Test hybrid search
|
||||||
hybrid_results = await chunk_repo.search_chunks_hybrid(question, limit=5)
|
hybrid_results = await chunk_repo.search_chunks_hybrid(question, limit=5)
|
||||||
target_document_ids = {chunk.document_id for chunk, _ in hybrid_results}
|
target_document_ids = {chunk.document_id for chunk, _ in hybrid_results}
|
||||||
|
|
|
||||||
22
uv.lock
22
uv.lock
|
|
@ -1033,7 +1033,6 @@ dependencies = [
|
||||||
{ name = "pydantic-ai" },
|
{ name = "pydantic-ai" },
|
||||||
{ name = "python-dotenv" },
|
{ name = "python-dotenv" },
|
||||||
{ name = "rich" },
|
{ name = "rich" },
|
||||||
{ name = "sqlite-vec" },
|
|
||||||
{ name = "tiktoken" },
|
{ name = "tiktoken" },
|
||||||
{ name = "typer" },
|
{ name = "typer" },
|
||||||
{ name = "watchfiles" },
|
{ name = "watchfiles" },
|
||||||
|
|
@ -1072,7 +1071,6 @@ requires-dist = [
|
||||||
{ name = "pydantic-ai", specifier = ">=0.7.2" },
|
{ name = "pydantic-ai", specifier = ">=0.7.2" },
|
||||||
{ name = "python-dotenv", specifier = ">=1.1.0" },
|
{ name = "python-dotenv", specifier = ">=1.1.0" },
|
||||||
{ name = "rich", specifier = ">=14.0.0" },
|
{ name = "rich", specifier = ">=14.0.0" },
|
||||||
{ name = "sqlite-vec", specifier = ">=0.1.6" },
|
|
||||||
{ name = "tiktoken", specifier = ">=0.9.0" },
|
{ name = "tiktoken", specifier = ">=0.9.0" },
|
||||||
{ name = "typer", specifier = ">=0.16.0" },
|
{ name = "typer", specifier = ">=0.16.0" },
|
||||||
{ name = "voyageai", marker = "extra == 'voyageai'", specifier = ">=0.3.2" },
|
{ name = "voyageai", marker = "extra == 'voyageai'", specifier = ">=0.3.2" },
|
||||||
|
|
@ -1086,7 +1084,7 @@ dev = [
|
||||||
{ name = "mkdocs", specifier = ">=1.6.1" },
|
{ name = "mkdocs", specifier = ">=1.6.1" },
|
||||||
{ name = "mkdocs-material", specifier = ">=9.6.14" },
|
{ name = "mkdocs-material", specifier = ">=9.6.14" },
|
||||||
{ name = "pre-commit", specifier = ">=4.2.0" },
|
{ name = "pre-commit", specifier = ">=4.2.0" },
|
||||||
{ name = "pyright", specifier = ">=1.1.403" },
|
{ name = "pyright", specifier = ">=1.1.404" },
|
||||||
{ name = "pytest", specifier = ">=8.4.0" },
|
{ name = "pytest", specifier = ">=8.4.0" },
|
||||||
{ name = "pytest-asyncio", specifier = ">=1.0.0" },
|
{ name = "pytest-asyncio", specifier = ">=1.0.0" },
|
||||||
{ name = "pytest-cov", specifier = ">=6.2.1" },
|
{ name = "pytest-cov", specifier = ">=6.2.1" },
|
||||||
|
|
@ -2775,15 +2773,15 @@ wheels = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyright"
|
name = "pyright"
|
||||||
version = "1.1.403"
|
version = "1.1.404"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "nodeenv" },
|
{ name = "nodeenv" },
|
||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/fe/f6/35f885264ff08c960b23d1542038d8da86971c5d8c955cfab195a4f672d7/pyright-1.1.403.tar.gz", hash = "sha256:3ab69b9f41c67fb5bbb4d7a36243256f0d549ed3608678d381d5f51863921104", size = 3913526, upload-time = "2025-07-09T07:15:52.882Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/e2/6e/026be64c43af681d5632722acd100b06d3d39f383ec382ff50a71a6d5bce/pyright-1.1.404.tar.gz", hash = "sha256:455e881a558ca6be9ecca0b30ce08aa78343ecc031d37a198ffa9a7a1abeb63e", size = 4065679, upload-time = "2025-08-20T18:46:14.029Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/49/b6/b04e5c2f41a5ccad74a1a4759da41adb20b4bc9d59a5e08d29ba60084d07/pyright-1.1.403-py3-none-any.whl", hash = "sha256:c0eeca5aa76cbef3fcc271259bbd785753c7ad7bcac99a9162b4c4c7daed23b3", size = 5684504, upload-time = "2025-07-09T07:15:50.958Z" },
|
{ url = "https://files.pythonhosted.org/packages/84/30/89aa7f7d7a875bbb9a577d4b1dc5a3e404e3d2ae2657354808e905e358e0/pyright-1.1.404-py3-none-any.whl", hash = "sha256:c7b7ff1fdb7219c643079e4c3e7d4125f0dafcc19d253b47e898d130ea426419", size = 5902951, upload-time = "2025-08-20T18:46:12.096Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3501,18 +3499,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" },
|
{ url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sqlite-vec"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/88/ed/aabc328f29ee6814033d008ec43e44f2c595447d9cccd5f2aabe60df2933/sqlite_vec-0.1.6-py3-none-macosx_10_6_x86_64.whl", hash = "sha256:77491bcaa6d496f2acb5cc0d0ff0b8964434f141523c121e313f9a7d8088dee3", size = 164075, upload-time = "2024-11-20T16:40:29.847Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a7/57/05604e509a129b22e303758bfa062c19afb020557d5e19b008c64016704e/sqlite_vec-0.1.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fdca35f7ee3243668a055255d4dee4dea7eed5a06da8cad409f89facf4595361", size = 165242, upload-time = "2024-11-20T16:40:31.206Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f2/48/dbb2cc4e5bad88c89c7bb296e2d0a8df58aab9edc75853728c361eefc24f/sqlite_vec-0.1.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b0519d9cd96164cd2e08e8eed225197f9cd2f0be82cb04567692a0a4be02da3", size = 103704, upload-time = "2024-11-20T16:40:33.729Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/80/76/97f33b1a2446f6ae55e59b33869bed4eafaf59b7f4c662c8d9491b6a714a/sqlite_vec-0.1.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux1_x86_64.whl", hash = "sha256:823b0493add80d7fe82ab0fe25df7c0703f4752941aee1c7b2b02cec9656cb24", size = 151556, upload-time = "2024-11-20T16:40:35.387Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6a/98/e8bc58b178266eae2fcf4c9c7a8303a8d41164d781b32d71097924a6bebe/sqlite_vec-0.1.6-py3-none-win_amd64.whl", hash = "sha256:c65bcfd90fa2f41f9000052bcb8bb75d38240b2dae49225389eca6c3136d3f0c", size = 281540, upload-time = "2024-11-20T16:40:37.296Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sse-starlette"
|
name = "sse-starlette"
|
||||||
version = "2.3.6"
|
version = "2.3.6"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue