Add a context manager to the client

This commit is contained in:
Yiorgis Gozadinos 2025-06-17 12:48:26 +02:00
parent 6321cfb7a8
commit 1e0b6b18aa
No known key found for this signature in database
3 changed files with 102 additions and 64 deletions

132
README.md
View file

@ -34,48 +34,50 @@ source .venv/bin/activate
from pathlib import Path
from haiku.rag.client import HaikuRAG
# Initialize client with database path
client = HaikuRAG("path/to/database.db")
# Or use in-memory database for testing
# Use as async context manager (recommended)
async with HaikuRAG("path/to/database.db") as client:
# Create document from text
doc = await client.create_document(
content="Your document content here",
uri="doc://example",
metadata={"source": "manual", "topic": "example"}
)
# Create document from file (auto-parses content)
doc = await client.create_document_from_source("path/to/document.pdf")
# Create document from URL
doc = await client.create_document_from_source("https://example.com/article.html")
# Retrieve documents
doc = await client.get_document_by_id(1)
doc = await client.get_document_by_uri("file:///path/to/document.pdf")
# List all documents with pagination
docs = await client.list_documents(limit=10, offset=0)
# Update document content
doc.content = "Updated content"
await client.update_document(doc)
# Delete document
await client.delete_document(doc.id)
# Search documents using hybrid search (vector + full-text)
results = await client.search("machine learning algorithms", limit=5)
for chunk, score in results:
print(f"Score: {score:.3f}")
print(f"Content: {chunk.content}")
print(f"Document ID: {chunk.document_id}")
print("---")
# Or use without the context manager.
client = HaikuRAG(":memory:")
# Create document from text
doc = await client.create_document(
content="Your document content here",
uri="doc://example",
metadata={"source": "manual", "topic": "example"}
)
# Create document from file (auto-parses content)
doc = await client.create_document_from_source("path/to/document.pdf")
# Create document from URL
doc = await client.create_document_from_source("https://example.com/article.html")
# Retrieve documents
doc = await client.get_document_by_id(1)
doc = await client.get_document_by_uri("file:///path/to/document.pdf")
# List all documents with pagination
docs = await client.list_documents(limit=10, offset=0)
# Update document content
doc.content = "Updated content"
await client.update_document(doc)
# Delete document
await client.delete_document(doc.id)
# Search documents using hybrid search (vector + full-text)
results = await client.search("machine learning algorithms", limit=5)
for chunk, score in results:
print(f"Score: {score:.3f}")
print(f"Content: {chunk.content}")
print(f"Document ID: {chunk.document_id}")
print("---")
# Clean up
client.close()
try:
# ... operations ...
finally:
client.close()
```
## Search Functionality
@ -87,21 +89,22 @@ client.close()
4. **Chunked Results**: Returns relevant document chunks with scores
```python
# Basic search
results = await client.search("your query here")
async with HaikuRAG("database.db") as client:
# Basic search
results = await client.search("your query here")
# Search with custom parameters
results = await client.search(
query="machine learning",
limit=10, # Maximum results to return
k=60 # RRF parameter for reciprocal rank fusion
)
# Search with custom parameters
results = await client.search(
query="machine learning",
limit=10, # Maximum results to return
k=60 # RRF parameter for reciprocal rank fusion
)
# Process results
for chunk, relevance_score in results:
print(f"Relevance: {relevance_score:.3f}")
print(f"Content: {chunk.content}")
print(f"From document: {chunk.document_id}")
# Process results
for chunk, relevance_score in results:
print(f"Relevance: {relevance_score:.3f}")
print(f"Content: {chunk.content}")
print(f"From document: {chunk.document_id}")
```
## Smart Document Updates
@ -109,18 +112,19 @@ for chunk, relevance_score in results:
The system automatically tracks file changes using MD5 hashes:
```python
# First call - creates new document
doc1 = await client.create_document_from_source("document.txt")
async with HaikuRAG("database.db") as client:
# First call - creates new document
doc1 = await client.create_document_from_source("document.txt")
# Second call - no changes, returns existing document (no processing)
doc2 = await client.create_document_from_source("document.txt")
assert doc1.id == doc2.id
# Second call - no changes, returns existing document (no processing)
doc2 = await client.create_document_from_source("document.txt")
assert doc1.id == doc2.id
# After file modification - automatically updates existing document
# File content changed...
doc3 = await client.create_document_from_source("document.txt")
assert doc1.id == doc3.id # Same document
assert doc3.content != doc1.content # Updated content
# After file modification - automatically updates existing document
# File content changed...
doc3 = await client.create_document_from_source("document.txt")
assert doc1.id == doc3.id # Same document
assert doc3.content != doc1.content # Updated content
```
## Supported File Formats

View file

@ -24,6 +24,15 @@ class HaikuRAG:
self.document_repository = DocumentRepository(self.store)
self.chunk_repository = ChunkRepository(self.store)
async def __aenter__(self):
"""Async context manager entry."""
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
"""Async context manager exit."""
self.close()
return False
async def create_document(
self, content: str, uri: str | None = None, metadata: dict | None = None
) -> Document:

View file

@ -472,3 +472,28 @@ async def test_client_search():
assert len(limited_results) <= 1
client.close()
@pytest.mark.asyncio
async def test_client_async_context_manager():
"""Test HaikuRAG as async context manager."""
# Test that context manager works and auto-closes
async with HaikuRAG(":memory:") as client:
# Create a document to ensure the client works
doc = await client.create_document(
content="Test content for context manager",
uri="test://context",
metadata={"test": "context_manager"},
)
assert doc.id is not None
assert doc.content == "Test content for context manager"
# Test search works within context
results = await client.search("Test content", limit=1)
assert len(results) > 0
# Context manager should have automatically closed the connection
# We can't easily test that the connection is closed without accessing internals,
# but the test passing means the context manager methods work correctly