Make search limit configurable

This commit is contained in:
Yiorgis Gozadinos 2025-12-09 11:46:43 +02:00
parent a56e1ba67c
commit 4da50873bd
No known key found for this signature in database
13 changed files with 30 additions and 18 deletions

View file

@ -99,6 +99,7 @@ research:
max_concurrency: 1
search:
limit: 5 # Default number of results to return
vector_index_metric: cosine # cosine, l2, or dot
vector_refine_factor: 30

View file

@ -57,16 +57,18 @@ haiku.rag intelligently handles database creation based on operation type:
This prevents the common mistake where a search query accidentally creates an empty database. To initialize your database, simply add your first document using `haiku-rag add` or `haiku-rag add-src`.
## Vector Indexing
## Search Settings
Configure vector indexing behavior for efficient similarity search:
Configure search behavior:
```yaml
search:
limit: 5 # Default number of results to return
vector_index_metric: cosine # cosine, l2, or dot
vector_refine_factor: 30 # Re-ranking factor for accuracy
```
- **limit**: Default number of search results to return when no limit is specified. Used by CLI, MCP server, and API. Default: 5
- **vector_index_metric**: Distance metric for vector similarity:
- `cosine`: Cosine similarity (default, best for most embeddings)
- `l2`: Euclidean distance

View file

@ -265,7 +265,9 @@ class HaikuRAGApp:
f"[yellow]Document with id {doc_id} not found.[/yellow]"
)
async def search(self, query: str, limit: int = 5, filter: str | None = None):
async def search(
self, query: str, limit: int | None = None, filter: str | None = None
):
async with HaikuRAG(db_path=self.db_path, config=self.config) as self.client:
results = await self.client.search(query, limit=limit, filter=filter)
if not results:

View file

@ -239,11 +239,11 @@ def search(
query: str = typer.Argument(
help="The search query to use",
),
limit: int = typer.Option(
5,
limit: int | None = typer.Option(
None,
"--limit",
"-l",
help="Maximum number of results to return",
help="Maximum number of results to return (default: config search.default_limit)",
),
filter: str | None = typer.Option(
None,

View file

@ -834,7 +834,7 @@ class HaikuRAG:
async def search(
self,
query: str,
limit: int = 5,
limit: int | None = None,
search_type: str = "hybrid",
filter: str | None = None,
) -> list[SearchResult]:
@ -842,13 +842,16 @@ class HaikuRAG:
Args:
query: The search query string.
limit: Maximum number of results to return.
limit: Maximum number of results to return. Defaults to config.search.default_limit.
search_type: Type of search - "vector", "fts", or "hybrid" (default).
filter: Optional SQL WHERE clause to filter documents before searching chunks.
Returns:
List of SearchResult objects ordered by relevance.
"""
if limit is None:
limit = self._config.search.limit
reranker = get_reranker(config=self._config)
if reranker is None:

View file

@ -123,6 +123,7 @@ class ProcessingConfig(BaseModel):
class SearchConfig(BaseModel):
limit: int = 5
vector_index_metric: Literal["cosine", "l2", "dot"] = "cosine"
vector_refine_factor: int = 30

View file

@ -101,7 +101,7 @@ def create_plan_node[AgentDepsT: GraphAgentDeps](
@plan_agent.tool
async def gather_context(
ctx2: RunContext[AgentDepsT], query: str, limit: int = 6
ctx2: RunContext[AgentDepsT], query: str, limit: int | None = None
) -> str:
results = await ctx2.deps.client.search(query, limit=limit)
results = await ctx2.deps.client.expand_context(results)
@ -227,7 +227,7 @@ async def _do_search[AgentDepsT: GraphAgentDeps](
@agent.tool
async def search_and_answer(
ctx2: RunContext[AgentDepsT], query: str, limit: int = 5
ctx2: RunContext[AgentDepsT], query: str, limit: int | None = None
) -> str:
"""Search the knowledge base for relevant documents.

View file

@ -79,11 +79,13 @@ def create_mcp_server(db_path: Path, config: AppConfig = Config) -> FastMCP:
return None
@mcp.tool()
async def search_documents(query: str, limit: int = 5) -> list[SearchResult]:
async def search_documents(
query: str, limit: int | None = None
) -> list[SearchResult]:
"""Search the RAG system for documents using hybrid search (vector similarity + full-text search)."""
try:
async with HaikuRAG(db_path, config=config) as rag:
return await rag.search(query, limit)
return await rag.search(query, limit=limit)
except Exception:
return []

View file

@ -39,7 +39,7 @@ class QuestionAnswerAgent:
async def search_documents(
ctx: RunContext[Dependencies],
query: str,
limit: int = 5,
limit: int | None = None,
) -> str:
"""Search the knowledge base for relevant documents.

View file

@ -159,7 +159,7 @@ async def test_search(app: HaikuRAGApp, monkeypatch):
with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
await app.search("query")
mock_client.search.assert_called_once_with("query", limit=5, filter=None)
mock_client.search.assert_called_once_with("query", limit=None, filter=None)
assert mock_rich_print_search.call_count == len(mock_results)
@ -176,7 +176,7 @@ async def test_search_no_results(app: HaikuRAGApp, monkeypatch):
with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
await app.search("query")
mock_client.search.assert_called_once_with("query", limit=5, filter=None)
mock_client.search.assert_called_once_with("query", limit=None, filter=None)
mock_print.assert_called_once_with("[yellow]No results found.[/yellow]")

View file

@ -171,7 +171,7 @@ def test_search():
assert result.exit_code == 0
mock_app_instance.search.assert_called_once_with(
query="query", limit=5, filter=None
query="query", limit=None, filter=None
)

View file

@ -148,7 +148,7 @@ async def test_mcp_search_documents():
assert result[0].content == "Result 1"
assert result[0].score == 0.9
assert result[1].document_id == "doc2"
mock_rag.search.assert_called_once_with("test query", 5)
mock_rag.search.assert_called_once_with("test query", limit=5)
@pytest.mark.asyncio

View file

@ -225,8 +225,9 @@ async def test_search_graceful_degradation(temp_db_path):
custom_chunks = [
Chunk(content="Custom chunk without docling metadata", metadata={}),
]
docling_doc = await client.convert("Document with custom chunks")
await client.import_document(
content="Document with custom chunks",
docling_document=docling_doc,
chunks=custom_chunks,
uri="https://example.com/custom.html",
)