From a51b922be3c54c74b60badb58ee7ba3003087dd6 Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Thu, 9 Apr 2026 11:08:57 +0300 Subject: [PATCH] fix skill analyze tool ignores state.document_filter --- CHANGELOG.md | 1 + docs/skills/rlm.md | 4 +- haiku_rag_slim/haiku/rag/skills/_tools.py | 10 +++-- haiku_rag_slim/haiku/rag/skills/rlm.py | 1 + tests/skills/test_rlm.py | 47 +++++++++++++++++++++++ 5 files changed, 58 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3324ebbd..731e6adc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Fixed - **Skill `list_documents` ignores `document_filter`**: `list_documents` tool now respects `state.document_filter`, consistent with `search`, `ask`, and `research` +- **Skill `analyze` ignores `document_filter`**: `analyze` tool now uses `state.document_filter` (combined with any explicit `filter` parameter). Added `document_filter` field to `RLMState` ## [0.38.0] - 2026-04-07 diff --git a/docs/skills/rlm.md b/docs/skills/rlm.md index fb0fa8de..7df3d162 100644 --- a/docs/skills/rlm.md +++ b/docs/skills/rlm.md @@ -33,6 +33,7 @@ The skill manages an `RLMState` under the `"rlm"` namespace: ```python class RLMState(BaseModel): + document_filter: str | None = None analyses: list[AnalysisEntry] = [] class AnalysisEntry(BaseModel): @@ -41,7 +42,8 @@ class AnalysisEntry(BaseModel): program: str | None = None ``` -Each `analyze` call appends an `AnalysisEntry` with the question, answer, and executed program. +- **document_filter** — SQL WHERE clause applied to `analyze` calls (combined with any explicit `filter` parameter). Set this to scope analysis to specific documents. +- **analyses** — Each `analyze` call appends an `AnalysisEntry` with the question, answer, and executed program. ## Usage with RAG Skill diff --git a/haiku_rag_slim/haiku/rag/skills/_tools.py b/haiku_rag_slim/haiku/rag/skills/_tools.py index abefcc5e..6ba2ed51 100644 --- a/haiku_rag_slim/haiku/rag/skills/_tools.py +++ b/haiku_rag_slim/haiku/rag/skills/_tools.py @@ -8,6 +8,7 @@ from haiku.rag.agents.research.models import Citation from haiku.rag.config.models import AppConfig from haiku.rag.store.models.chunk import SearchResult from haiku.rag.tools.document import DocumentInfo +from haiku.rag.tools.filters import combine_filters from haiku.rag.tools.qa import QAHistoryEntry from haiku.skills.state import SkillRunDeps @@ -476,11 +477,12 @@ def create_skill_tools( document: Optional document ID or title to pre-load for analysis. filter: Optional SQL WHERE clause to filter documents. """ - output, answer, program = await skill_analyze( - db_path, config, question, document=document, filter=filter - ) - state = _get_state(ctx, state_type) + state_filter = state.document_filter if state else None + effective_filter = combine_filters(state_filter, filter) + output, answer, program = await skill_analyze( + db_path, config, question, document=document, filter=effective_filter + ) if state: state.analyses.append( AnalysisEntry( diff --git a/haiku_rag_slim/haiku/rag/skills/rlm.py b/haiku_rag_slim/haiku/rag/skills/rlm.py index 503d9d4a..c913db78 100644 --- a/haiku_rag_slim/haiku/rag/skills/rlm.py +++ b/haiku_rag_slim/haiku/rag/skills/rlm.py @@ -11,6 +11,7 @@ from haiku.skills.parser import parse_skill_md class RLMState(BaseModel): + document_filter: str | None = None analyses: list[AnalysisEntry] = [] diff --git a/tests/skills/test_rlm.py b/tests/skills/test_rlm.py index 0b803014..82cf41af 100644 --- a/tests/skills/test_rlm.py +++ b/tests/skills/test_rlm.py @@ -155,6 +155,53 @@ class TestAnalyzeTool: assert state.analyses[0].answer == "42" assert state.analyses[0].program == "print(42)" + async def test_analyze_applies_document_filter_from_state( + self, rag_db, monkeypatch + ): + from haiku.rag.skills.rlm import RLMState, create_skill + + captured_kwargs = {} + + async def mock_rlm(self, question, **kwargs): + captured_kwargs.update(kwargs) + return RLMResult(answer="42", program="print(42)") + + monkeypatch.setattr(HaikuRAG, "rlm", mock_rlm) + + skill = create_skill(db_path=rag_db) + analyze = _get_tool(skill, "analyze") + state = RLMState(document_filter="title = 'AI Overview'") + ctx = _make_ctx(state) + await analyze(ctx, question="How many documents?") + assert captured_kwargs.get("filter") == "title = 'AI Overview'" + + async def test_analyze_combines_state_filter_with_explicit_filter( + self, rag_db, monkeypatch + ): + from haiku.rag.skills.rlm import RLMState, create_skill + + captured_kwargs = {} + + async def mock_rlm(self, question, **kwargs): + captured_kwargs.update(kwargs) + return RLMResult(answer="Result", program="code()") + + monkeypatch.setattr(HaikuRAG, "rlm", mock_rlm) + + skill = create_skill(db_path=rag_db) + analyze = _get_tool(skill, "analyze") + state = RLMState(document_filter="title = 'AI Overview'") + ctx = _make_ctx(state) + await analyze( + ctx, + question="Count pages", + filter="uri LIKE '%test%'", + ) + result_filter = captured_kwargs["filter"] + assert isinstance(result_filter, str) + assert "title = 'AI Overview'" in result_filter + assert "uri LIKE '%test%'" in result_filter + async def test_analyze_with_document_and_filter(self, rag_db, monkeypatch): from haiku.rag.skills.rlm import create_skill