Remove documents from state, fix tests

This commit is contained in:
Yiorgis Gozadinos 2026-04-20 09:48:18 +03:00
parent d52f453c44
commit 68a9f191d2
No known key found for this signature in database
7 changed files with 24 additions and 80 deletions

View file

@ -9,20 +9,12 @@ export interface Citation {
content: string; content: string;
} }
export interface DocumentInfo {
id: string;
title: string;
uri: string;
created: string;
}
// Matches RAGState from the backend skill // Matches RAGState from the backend skill
export interface RAGState { export interface RAGState {
citation_index: Record<string, Citation>; citation_index: Record<string, Citation>;
citations: string[][]; citations: string[][];
document_filter: string | null; document_filter: string | null;
searches: Record<string, unknown[]>; searches: Record<string, unknown[]>;
documents: DocumentInfo[];
} }
export interface StoredMessage { export interface StoredMessage {
@ -50,7 +42,6 @@ export function normalizeRAGState(state?: Partial<RAGState>): RAGState {
citations: state?.citations ?? [], citations: state?.citations ?? [],
document_filter: state?.document_filter ?? null, document_filter: state?.document_filter ?? null,
searches: state?.searches ?? {}, searches: state?.searches ?? {},
documents: state?.documents ?? [],
}; };
} }

View file

@ -8,9 +8,6 @@ from haiku.skills.parser import parse_skill_md
{% if "cite" in tool_names %} {% if "cite" in tool_names %}
from haiku.rag.agents.research.models import Citation from haiku.rag.agents.research.models import Citation
{% endif %} {% endif %}
{% if "search" in tool_names or "list_documents" in tool_names or "get_document" in tool_names %}
from haiku.rag.tools.document import DocumentInfo
{% endif %}
{% if "search" in tool_names %} {% if "search" in tool_names %}
from haiku.rag.store.models.chunk import SearchResult from haiku.rag.store.models.chunk import SearchResult
{% endif %} {% endif %}
@ -38,9 +35,6 @@ class SkillState(BaseModel):
{% if "search" in tool_names %} {% if "search" in tool_names %}
searches: dict[str, list[SearchResult]] = Field(default_factory=dict) searches: dict[str, list[SearchResult]] = Field(default_factory=dict)
{% endif %} {% endif %}
{% if "search" in tool_names or "list_documents" in tool_names or "get_document" in tool_names %}
documents: list[DocumentInfo] = Field(default_factory=list)
{% endif %}
{% if "execute_code" in tool_names %} {% if "execute_code" in tool_names %}
executions: list[CodeExecutionEntry] = Field(default_factory=list) executions: list[CodeExecutionEntry] = Field(default_factory=list)
{% endif %} {% endif %}

View file

@ -7,7 +7,6 @@ from pydantic_ai import RunContext
from haiku.rag.agents.research.models import Citation from haiku.rag.agents.research.models import Citation
from haiku.rag.config.models import AppConfig from haiku.rag.config.models import AppConfig
from haiku.rag.store.models.chunk import SearchResult from haiku.rag.store.models.chunk import SearchResult
from haiku.rag.tools.document import DocumentInfo
from haiku.skills.state import SkillRunDeps from haiku.skills.state import SkillRunDeps
@ -86,21 +85,6 @@ async def skill_get_document(
} }
def update_documents_state(
documents_state: list[DocumentInfo],
doc_dicts: list[dict[str, Any]],
) -> None:
for doc_dict in doc_dicts:
doc_info = DocumentInfo(
id=str(doc_dict["id"]),
title=doc_dict["title"] or "Untitled",
uri=doc_dict.get("uri") or "",
created=doc_dict.get("created_at", ""),
)
if not any(d.id == doc_info.id for d in documents_state):
documents_state.append(doc_info)
def _get_state(ctx: RunContext[SkillRunDeps], state_type: type[BaseModel]) -> Any: def _get_state(ctx: RunContext[SkillRunDeps], state_type: type[BaseModel]) -> Any:
if ctx.deps and ctx.deps.state and isinstance(ctx.deps.state, state_type): if ctx.deps and ctx.deps.state and isinstance(ctx.deps.state, state_type):
return ctx.deps.state return ctx.deps.state
@ -237,8 +221,6 @@ def create_skill_tools(
config, config,
filter=state.document_filter if state else None, filter=state.document_filter if state else None,
) )
if state:
update_documents_state(state.documents, result)
return result return result
tools["list_documents"] = list_documents tools["list_documents"] = list_documents
@ -253,12 +235,7 @@ def create_skill_tools(
Args: Args:
query: Document ID, title, or URI to look up. query: Document ID, title, or URI to look up.
""" """
result = await skill_get_document(db_path, config, query) return await skill_get_document(db_path, config, query)
if result is not None:
state = _get_state(ctx, state_type)
if state:
update_documents_state(state.documents, [result])
return result
tools["get_document"] = get_document tools["get_document"] = get_document

View file

@ -8,7 +8,6 @@ from haiku.rag.agents.research.models import Citation
from haiku.rag.config.models import AppConfig from haiku.rag.config.models import AppConfig
from haiku.rag.skills._tools import CodeExecutionEntry from haiku.rag.skills._tools import CodeExecutionEntry
from haiku.rag.store.models.chunk import SearchResult from haiku.rag.store.models.chunk import SearchResult
from haiku.rag.tools.document import DocumentInfo
from haiku.skills.models import Skill, SkillMetadata, SkillSource, StateMetadata from haiku.skills.models import Skill, SkillMetadata, SkillSource, StateMetadata
from haiku.skills.parser import parse_skill_md from haiku.skills.parser import parse_skill_md
@ -19,7 +18,6 @@ class AnalysisState(BaseModel):
citation_index: dict[str, Citation] = Field(default_factory=dict) citation_index: dict[str, Citation] = Field(default_factory=dict)
citations: list[list[str]] = Field(default_factory=list) citations: list[list[str]] = Field(default_factory=list)
searches: dict[str, list[SearchResult]] = Field(default_factory=dict) searches: dict[str, list[SearchResult]] = Field(default_factory=dict)
documents: list[DocumentInfo] = Field(default_factory=list)
STATE_TYPE = AnalysisState STATE_TYPE = AnalysisState

View file

@ -7,7 +7,6 @@ from pydantic import BaseModel, Field
from haiku.rag.agents.research.models import Citation from haiku.rag.agents.research.models import Citation
from haiku.rag.config.models import AppConfig from haiku.rag.config.models import AppConfig
from haiku.rag.store.models.chunk import SearchResult from haiku.rag.store.models.chunk import SearchResult
from haiku.rag.tools.document import DocumentInfo
from haiku.skills.models import Skill, SkillMetadata, SkillSource, StateMetadata from haiku.skills.models import Skill, SkillMetadata, SkillSource, StateMetadata
from haiku.skills.parser import parse_skill_md from haiku.skills.parser import parse_skill_md
@ -34,7 +33,6 @@ class RAGState(BaseModel):
citations: list[list[str]] = Field(default_factory=list) citations: list[list[str]] = Field(default_factory=list)
document_filter: str | None = None document_filter: str | None = None
searches: dict[str, list[SearchResult]] = Field(default_factory=dict) searches: dict[str, list[SearchResult]] = Field(default_factory=dict)
documents: list[DocumentInfo] = Field(default_factory=list)
STATE_TYPE = RAGState STATE_TYPE = RAGState

View file

@ -8,7 +8,6 @@ from haiku.rag.skills.rag import (
state_metadata, state_metadata,
) )
from haiku.rag.store.models.chunk import SearchResult from haiku.rag.store.models.chunk import SearchResult
from haiku.rag.tools.document import DocumentInfo
from haiku.skills.models import SkillMetadata, StateMetadata from haiku.skills.models import SkillMetadata, StateMetadata
from .conftest import _get_tool, _make_ctx from .conftest import _get_tool, _make_ctx
@ -213,17 +212,6 @@ class TestListDocumentsTool:
assert isinstance(results, list) assert isinstance(results, list)
assert len(results) == 2 assert len(results) == 2
async def test_list_documents_updates_state(self, rag_db):
from haiku.rag.skills.rag import RAGState, create_skill
skill = create_skill(db_path=rag_db)
list_docs = _get_tool(skill, "list_documents")
state = RAGState()
ctx = _make_ctx(state)
await list_docs(ctx)
assert len(state.documents) == 2
assert isinstance(state.documents[0], DocumentInfo)
async def test_list_documents_applies_document_filter_from_state(self, rag_db): async def test_list_documents_applies_document_filter_from_state(self, rag_db):
from haiku.rag.skills.rag import RAGState, create_skill from haiku.rag.skills.rag import RAGState, create_skill

View file

@ -23,9 +23,8 @@ class TestAvailableTools:
"list_documents", "list_documents",
"get_document", "get_document",
"search", "search",
"ask", "execute_code",
"research", "cite",
"analyze",
} }
@ -43,7 +42,7 @@ class TestValidateTools:
validate_tools(["search"]) validate_tools(["search"])
def test_valid_multiple_tools(self): def test_valid_multiple_tools(self):
validate_tools(["list_documents", "get_document", "search", "ask"]) validate_tools(["list_documents", "get_document", "search", "cite"])
def test_valid_all_tools(self): def test_valid_all_tools(self):
validate_tools(list(AVAILABLE_TOOLS)) validate_tools(list(AVAILABLE_TOOLS))
@ -97,7 +96,7 @@ class TestRenderTemplates:
output_dir=tmp_path, output_dir=tmp_path,
name="recipes", name="recipes",
description="A recipe skill.", description="A recipe skill.",
tool_names=["list_documents", "get_document", "search", "ask"], tool_names=["list_documents", "get_document", "search", "cite"],
) )
assert result == tmp_path / "recipes-skill" assert result == tmp_path / "recipes-skill"
assert result.is_dir() assert result.is_dir()
@ -112,7 +111,7 @@ class TestRenderTemplates:
output_dir=tmp_path, output_dir=tmp_path,
name="my-recipes", name="my-recipes",
description="A recipe skill.", description="A recipe skill.",
tool_names=["search", "ask"], tool_names=["search", "cite"],
) )
assert result == tmp_path / "my-recipes-skill" assert result == tmp_path / "my-recipes-skill"
pkg = result / "my_recipes_skill" pkg = result / "my_recipes_skill"
@ -129,11 +128,11 @@ class TestRenderTemplates:
output_dir=tmp_path, output_dir=tmp_path,
name="docs", name="docs",
description="A docs skill.", description="A docs skill.",
tool_names=["search", "ask"], tool_names=["search", "cite"],
) )
init = tmp_path / "docs-skill" / "docs_skill" / "__init__.py" init = tmp_path / "docs-skill" / "docs_skill" / "__init__.py"
content = init.read_text() content = init.read_text()
assert '["search", "ask"]' in content assert '["search", "cite"]' in content
def test_create_skill_tools_called(self, tmp_path): def test_create_skill_tools_called(self, tmp_path):
render_templates( render_templates(
@ -151,12 +150,12 @@ class TestRenderTemplates:
output_dir=tmp_path, output_dir=tmp_path,
name="recipes", name="recipes",
description="A recipe skill.", description="A recipe skill.",
tool_names=["search", "ask"], tool_names=["search", "cite"],
) )
init = tmp_path / "recipes-skill" / "recipes_skill" / "__init__.py" init = tmp_path / "recipes-skill" / "recipes_skill" / "__init__.py"
content = init.read_text() content = init.read_text()
assert '"search"' in content assert '"search"' in content
assert '"ask"' in content assert '"cite"' in content
def test_pyproject_toml(self, tmp_path): def test_pyproject_toml(self, tmp_path):
render_templates( render_templates(
@ -184,24 +183,23 @@ class TestRenderTemplates:
) )
skill_md = tmp_path / "docs-skill" / "docs_skill" / "SKILL.md" skill_md = tmp_path / "docs-skill" / "docs_skill" / "SKILL.md"
content = skill_md.read_text() content = skill_md.read_text()
assert "**search**" in content assert "### search" in content
assert "**ask**" not in content assert "### cite" not in content
assert "**list_documents**" not in content assert "### list_documents" not in content
assert "**research**" not in content assert "### execute_code" not in content
assert "**analyze**" not in content
def test_skill_md_includes_all_selected(self, tmp_path): def test_skill_md_includes_all_selected(self, tmp_path):
render_templates( render_templates(
output_dir=tmp_path, output_dir=tmp_path,
name="docs", name="docs",
description="A docs skill.", description="A docs skill.",
tool_names=["search", "ask", "analyze"], tool_names=["search", "execute_code", "cite"],
) )
skill_md = tmp_path / "docs-skill" / "docs_skill" / "SKILL.md" skill_md = tmp_path / "docs-skill" / "docs_skill" / "SKILL.md"
content = skill_md.read_text() content = skill_md.read_text()
assert "**search**" in content assert "search" in content
assert "**ask**" in content assert "execute_code" in content
assert "**analyze**" in content assert "cite" in content
def test_custom_preamble(self, tmp_path): def test_custom_preamble(self, tmp_path):
render_templates( render_templates(
@ -226,23 +224,23 @@ class TestRenderTemplates:
content = init.read_text() content = init.read_text()
assert 'state_namespace="recipes"' in content assert 'state_namespace="recipes"' in content
def test_analyze_state_fields(self, tmp_path): def test_execute_code_state_fields(self, tmp_path):
render_templates( render_templates(
output_dir=tmp_path, output_dir=tmp_path,
name="docs", name="docs",
description="A docs skill.", description="A docs skill.",
tool_names=["search", "analyze"], tool_names=["search", "execute_code"],
) )
init = tmp_path / "docs-skill" / "docs_skill" / "__init__.py" init = tmp_path / "docs-skill" / "docs_skill" / "__init__.py"
content = init.read_text() content = init.read_text()
assert "analyses" in content assert "executions" in content
def test_imports_from_shared_tools(self, tmp_path): def test_imports_from_shared_tools(self, tmp_path):
render_templates( render_templates(
output_dir=tmp_path, output_dir=tmp_path,
name="recipes", name="recipes",
description="A recipe skill.", description="A recipe skill.",
tool_names=["search", "ask", "analyze"], tool_names=["search", "execute_code", "cite"],
) )
init = tmp_path / "recipes-skill" / "recipes_skill" / "__init__.py" init = tmp_path / "recipes-skill" / "recipes_skill" / "__init__.py"
content = init.read_text() content = init.read_text()
@ -329,7 +327,7 @@ class TestGenerateSkill:
output_dir=tmp_path, output_dir=tmp_path,
name="recipes", name="recipes",
description="A recipe skill.", description="A recipe skill.",
tool_names=["search", "ask"], tool_names=["search", "cite"],
) )
assert result == tmp_path / "recipes-skill" assert result == tmp_path / "recipes-skill"
assets = result / "recipes_skill" / "assets" assets = result / "recipes_skill" / "assets"
@ -479,7 +477,7 @@ class TestGenerateSkillRemote:
output_dir=tmp_path, output_dir=tmp_path,
name="recipes", name="recipes",
description="A recipe skill.", description="A recipe skill.",
tool_names=["search", "ask"], tool_names=["search", "cite"],
config_path=config_file, config_path=config_file,
) )
assets = result / "recipes_skill" / "assets" assets = result / "recipes_skill" / "assets"