diff --git a/CHANGELOG.md b/CHANGELOG.md index 04335c3b..e595d314 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ # Changelog ## [Unreleased] +### Added + +- **Search Filter for Graphs**: Research and Deep QA graphs now support `search_filter` parameter to restrict searches to specific documents + - Set `state.search_filter` to a SQL WHERE clause (e.g., `"id IN ('doc1', 'doc2')"`) before running the graph + - Enables document-scoped research workflows + - CLI: `haiku-rag research "question" --filter "uri LIKE '%paper%'"` + - CLI: `haiku-rag ask "question" --filter "title = 'My Doc'"` + - Python: `client.ask(question, filter="...")` and `agent.answer(question, filter="...")` +- **AG-UI Research Example**: Added bidirectional state demonstration with document filter + - New `/api/documents` endpoint to list available documents + - Frontend document selector component with search and multi-select + - Demonstrates client-to-server state flow via AG-UI protocol + ## [0.20.0] - 2025-12-10 ### Added diff --git a/docs/cli.md b/docs/cli.md index a1207ef4..3e0edf0f 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -149,9 +149,9 @@ Show verbose output with deep QA: haiku-rag ask "What are the main features and architecture of haiku.rag?" --deep --verbose ``` -Filter to specific documents with deep QA: +Filter to specific documents: ```bash -haiku-rag ask "What are the main findings?" --deep --filter "uri LIKE '%paper%'" +haiku-rag ask "What are the main findings?" --filter "uri LIKE '%paper%'" ``` The QA agent searches your documents for relevant information and provides a comprehensive answer. When available, citations use the document title; otherwise they fall back to the URI. @@ -161,7 +161,7 @@ Flags: - `--cite`: Include citations showing which documents were used - `--deep`: Decompose the question into sub-questions answered in parallel before synthesizing a final answer - `--verbose`: Show planning, searching, evaluation, and synthesis steps (only with `--deep`) -- `--filter`: Restrict searches to documents matching the filter (only with `--deep`) +- `--filter`: Restrict searches to documents matching the filter (see [Filtering Search Results](python.md#filtering-search-results)) ## Research diff --git a/haiku_rag_slim/haiku/rag/app.py b/haiku_rag_slim/haiku/rag/app.py index c92206f4..e6c95755 100644 --- a/haiku_rag_slim/haiku/rag/app.py +++ b/haiku_rag_slim/haiku/rag/app.py @@ -363,7 +363,7 @@ class HaikuRAGApp: if cite: citations = result.citations else: - answer, citations = await self.client.ask(question) + answer, citations = await self.client.ask(question, filter=filter) self.console.print(f"[bold blue]Question:[/bold blue] {question}") self.console.print() diff --git a/haiku_rag_slim/haiku/rag/cli.py b/haiku_rag_slim/haiku/rag/cli.py index 84216409..42f8fbd9 100644 --- a/haiku_rag_slim/haiku/rag/cli.py +++ b/haiku_rag_slim/haiku/rag/cli.py @@ -309,7 +309,7 @@ def ask( None, "--filter", "-f", - help="SQL WHERE clause to filter documents (only with --deep)", + help="SQL WHERE clause to filter documents (e.g., \"uri LIKE '%arxiv%'\")", ), ): app = create_app(db) diff --git a/haiku_rag_slim/haiku/rag/client.py b/haiku_rag_slim/haiku/rag/client.py index afdc87af..53ceff30 100644 --- a/haiku_rag_slim/haiku/rag/client.py +++ b/haiku_rag_slim/haiku/rag/client.py @@ -1229,13 +1229,17 @@ class HaikuRAG: return final_results + passthrough async def ask( - self, question: str, system_prompt: str | None = None + self, + question: str, + system_prompt: str | None = None, + filter: str | None = None, ) -> "tuple[str, list[Citation]]": """Ask a question using the configured QA agent. Args: question: The question to ask. system_prompt: Optional custom system prompt for the QA agent. + filter: SQL WHERE clause to filter documents. Returns: Tuple of (answer text, list of resolved citations). @@ -1243,7 +1247,7 @@ class HaikuRAG: from haiku.rag.qa import get_qa_agent qa_agent = get_qa_agent(self, config=self._config, system_prompt=system_prompt) - return await qa_agent.answer(question) + return await qa_agent.answer(question, filter=filter) async def visualize_chunk(self, chunk: Chunk) -> list: """Render page images with bounding box highlights for a chunk. diff --git a/haiku_rag_slim/haiku/rag/qa/agent.py b/haiku_rag_slim/haiku/rag/qa/agent.py index 5c37ffeb..03b6c0d5 100644 --- a/haiku_rag_slim/haiku/rag/qa/agent.py +++ b/haiku_rag_slim/haiku/rag/qa/agent.py @@ -15,6 +15,7 @@ class Dependencies(BaseModel): model_config = {"arbitrary_types_allowed": True} client: HaikuRAG search_results: list[SearchResult] = [] + search_filter: str | None = None class QuestionAnswerAgent: @@ -46,7 +47,9 @@ class QuestionAnswerAgent: Returns results with chunk IDs and relevance scores. Reference results by their chunk_id in cited_chunks. """ - results = await ctx.deps.client.search(query, limit=limit) + results = await ctx.deps.client.search( + query, limit=limit, filter=ctx.deps.search_filter + ) results = await ctx.deps.client.expand_context(results) # Store results for citation resolution ctx.deps.search_results = results @@ -54,13 +57,19 @@ class QuestionAnswerAgent: parts = [r.format_for_agent() for r in results] return "\n\n".join(parts) if parts else "No results found." - async def answer(self, question: str) -> tuple[str, list[Citation]]: + async def answer( + self, question: str, filter: str | None = None + ) -> tuple[str, list[Citation]]: """Answer a question using the RAG system. + Args: + question: The question to answer + filter: SQL WHERE clause to filter documents + Returns: Tuple of (answer text, list of resolved citations) """ - deps = Dependencies(client=self._client) + deps = Dependencies(client=self._client, search_filter=filter) result = await self._agent.run(question, deps=deps) citations = resolve_citations(result.output.cited_chunks, deps.search_results) return result.output.answer, citations