From f4dd2dafc3316816cb20ab6aa674f47e45d184f5 Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Fri, 27 Mar 2026 11:31:10 +0200 Subject: [PATCH] Use haiku.skills extras for passing utility functions --- .../skill_generator/templates/__init__.py.j2 | 15 ++----- .../templates/pyproject.toml.j2 | 2 +- haiku_rag_slim/haiku/rag/skills/_tools.py | 45 +++++++++++++++++++ haiku_rag_slim/haiku/rag/skills/rag.py | 4 +- haiku_rag_slim/pyproject.toml | 2 +- pyproject.toml | 1 + tests/skills/test_rag.py | 9 ++++ tests/test_skill_generator.py | 12 ++--- uv.lock | 26 +++++------ 9 files changed, 83 insertions(+), 33 deletions(-) diff --git a/haiku_rag_slim/haiku/rag/skill_generator/templates/__init__.py.j2 b/haiku_rag_slim/haiku/rag/skill_generator/templates/__init__.py.j2 index 27c130af..40953416 100644 --- a/haiku_rag_slim/haiku/rag/skill_generator/templates/__init__.py.j2 +++ b/haiku_rag_slim/haiku/rag/skill_generator/templates/__init__.py.j2 @@ -67,7 +67,7 @@ def create_skill( db_path: Path | None = None, config: AppConfig | None = None, ) -> Skill: - from haiku.rag.skills._tools import create_skill_tools + from haiku.rag.skills._tools import create_skill_extras, create_skill_tools metadata, instructions = parse_skill_md(Path(__file__).parent / "SKILL.md") @@ -78,21 +78,12 @@ def create_skill( db_path = _DB_PATH tools = create_skill_tools(db_path, config, SkillState, _TOOL_NAMES) + extras = create_skill_extras(db_path, config) return Skill( metadata=metadata, instructions=instructions, tools=list(tools.values()), + extras=extras, state_type=SkillState, state_namespace="{{ name }}", ) - - -async def visualize_chunk(chunk_id: str) -> list: - from haiku.rag.client import HaikuRAG - - config = _get_config() - async with HaikuRAG(_DB_PATH, config=config, read_only=True) as rag: - chunk = await rag.get_chunk_by_id(chunk_id) - if chunk is None: - return [] - return await rag.visualize_chunk(chunk) diff --git a/haiku_rag_slim/haiku/rag/skill_generator/templates/pyproject.toml.j2 b/haiku_rag_slim/haiku/rag/skill_generator/templates/pyproject.toml.j2 index 62d52c3d..6e3af747 100644 --- a/haiku_rag_slim/haiku/rag/skill_generator/templates/pyproject.toml.j2 +++ b/haiku_rag_slim/haiku/rag/skill_generator/templates/pyproject.toml.j2 @@ -10,7 +10,7 @@ readme = "README.md" requires-python = ">=3.12" dependencies = [ "haiku.rag-slim >= {{ rag_version }}", - "haiku-skills >= 0.11.0", + "haiku-skills >= 0.12.0", ] [project.entry-points."haiku.skills"] diff --git a/haiku_rag_slim/haiku/rag/skills/_tools.py b/haiku_rag_slim/haiku/rag/skills/_tools.py index 02b7494d..79b1d3e8 100644 --- a/haiku_rag_slim/haiku/rag/skills/_tools.py +++ b/haiku_rag_slim/haiku/rag/skills/_tools.py @@ -243,6 +243,51 @@ def _get_state(ctx: RunContext[SkillRunDeps], state_type: type[BaseModel]) -> An return None +def create_skill_extras( + db_path: Path, + config: AppConfig, +) -> dict[str, Any]: + """Create non-tool utility functions bound to a specific database. + + Returns a dict of callables that can be attached to a Skill's extras. + """ + + async def visualize_chunk(chunk_id: str) -> list: + from haiku.rag.client import HaikuRAG + + async with HaikuRAG(db_path, config=config, read_only=True) as rag: + chunk = await rag.get_chunk_by_id(chunk_id) + if chunk is None: + return [] + return await rag.visualize_chunk(chunk) + + async def list_documents( + limit: int | None = None, + offset: int | None = None, + filter: str | None = None, + ) -> list[dict[str, Any]]: + from haiku.rag.client import HaikuRAG + + async with HaikuRAG(db_path, config=config, read_only=True) as rag: + documents = await rag.list_documents(limit, offset, filter=filter) + return [ + { + "id": doc.id, + "title": doc.title, + "uri": doc.uri, + "metadata": doc.metadata, + "created_at": str(doc.created_at), + "updated_at": str(doc.updated_at), + } + for doc in documents + ] + + return { + "visualize_chunk": visualize_chunk, + "list_documents": list_documents, + } + + def create_skill_tools( db_path: Path, config: AppConfig, diff --git a/haiku_rag_slim/haiku/rag/skills/rag.py b/haiku_rag_slim/haiku/rag/skills/rag.py index 10270e2a..db3e82c6 100644 --- a/haiku_rag_slim/haiku/rag/skills/rag.py +++ b/haiku_rag_slim/haiku/rag/skills/rag.py @@ -73,7 +73,7 @@ def create_skill( config: haiku.rag AppConfig instance. If None, uses get_config(). """ from haiku.rag.config import get_config - from haiku.rag.skills._tools import create_skill_tools + from haiku.rag.skills._tools import create_skill_extras, create_skill_tools if config is None: config = get_config() @@ -86,6 +86,7 @@ def create_skill( db_path = config.storage.data_dir / "haiku.rag.lancedb" tools = create_skill_tools(db_path, config, RAGState, _RAG_TOOLS) + extras = create_skill_extras(db_path, config) return Skill( metadata=skill_metadata(), @@ -93,6 +94,7 @@ def create_skill( path=_skill_path, instructions=instructions(), tools=list(tools.values()), + extras=extras, state_type=STATE_TYPE, state_namespace=STATE_NAMESPACE, ) diff --git a/haiku_rag_slim/pyproject.toml b/haiku_rag_slim/pyproject.toml index 7c87d35d..8ea410cc 100644 --- a/haiku_rag_slim/pyproject.toml +++ b/haiku_rag_slim/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ dependencies = [ "cachetools>=7.0.2", "docling-core>=2.70.2", - "haiku.skills>=0.11.0", + "haiku.skills>=0.12.0", "httpx>=0.28.1", "jinja2>=3.1.0", "jsonpatch>=1.33", diff --git a/pyproject.toml b/pyproject.toml index acf0d379..a07282a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,6 +64,7 @@ members = ["haiku_rag_slim", "evaluations"] "haiku.rag-slim" = { workspace = true } "haiku.rag-evals" = { workspace = true } + [dependency-groups] dev = [ "haiku.rag-evals", diff --git a/tests/skills/test_rag.py b/tests/skills/test_rag.py index ce2b1b32..a66a4096 100644 --- a/tests/skills/test_rag.py +++ b/tests/skills/test_rag.py @@ -81,6 +81,15 @@ class TestRAGSkillCreation: assert skill._state_type is RAGState assert skill._state_namespace == "rag" + def test_create_skill_has_extras(self, temp_db_path): + from haiku.rag.skills.rag import create_skill + + skill = create_skill(db_path=temp_db_path) + assert "visualize_chunk" in skill.extras + assert "list_documents" in skill.extras + assert callable(skill.extras["visualize_chunk"]) + assert callable(skill.extras["list_documents"]) + def test_create_skill_from_env(self, monkeypatch, temp_db_path): monkeypatch.setenv("HAIKU_RAG_DB", str(temp_db_path)) from haiku.rag.skills.rag import create_skill diff --git a/tests/test_skill_generator.py b/tests/test_skill_generator.py index 9025f84e..7730f86e 100644 --- a/tests/test_skill_generator.py +++ b/tests/test_skill_generator.py @@ -244,7 +244,10 @@ class TestRenderTemplates: ) init = tmp_path / "recipes-skill" / "recipes_skill" / "__init__.py" content = init.read_text() - assert "from haiku.rag.skills._tools import create_skill_tools" in content + assert ( + "from haiku.rag.skills._tools import create_skill_extras, create_skill_tools" + in content + ) def test_readme(self, tmp_path): render_templates( @@ -258,7 +261,7 @@ class TestRenderTemplates: assert "recipes" in content assert "haiku-rag" in content - def test_visualize_chunk_function_present(self, tmp_path): + def test_extras_in_create_skill(self, tmp_path): render_templates( output_dir=tmp_path, name="docs", @@ -267,9 +270,8 @@ class TestRenderTemplates: ) init = tmp_path / "docs-skill" / "docs_skill" / "__init__.py" content = init.read_text() - assert "async def visualize_chunk(chunk_id: str)" in content - assert "skill_visualize_chunk" not in content - assert "HaikuRAG" in content + assert "create_skill_extras" in content + assert "extras=extras" in content def test_create_skill_accepts_optional_params(self, tmp_path): render_templates( diff --git a/uv.lock b/uv.lock index 2d7ea5bc..4752881a 100644 --- a/uv.lock +++ b/uv.lock @@ -1571,7 +1571,7 @@ requires-dist = [ { name = "cohere", marker = "extra == 'cohere'", specifier = ">=5.20.7" }, { name = "docling", marker = "extra == 'docling'", specifier = ">=2.81.0" }, { name = "docling-core", specifier = ">=2.70.2" }, - { name = "haiku-skills", specifier = ">=0.11.0" }, + { name = "haiku-skills", specifier = ">=0.12.0" }, { name = "httpx", specifier = ">=0.28.1" }, { name = "jinja2", specifier = ">=3.1.0" }, { name = "jsonpatch", specifier = ">=1.33" }, @@ -1604,7 +1604,7 @@ provides-extras = ["docling", "voyageai", "mxbai", "cohere", "zeroentropy", "jin [[package]] name = "haiku-skills" -version = "0.11.0" +version = "0.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ag-ui-protocol" }, @@ -1614,9 +1614,9 @@ dependencies = [ { name = "pyyaml" }, { name = "skills-ref" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/a7/4f8483ee3c8789cf5a2882c82545c93787cff343b78bb5a3da25f43f2bbf/haiku_skills-0.11.0.tar.gz", hash = "sha256:bb9117a3c1636ddafa4a4dda7abaed79da83c409c4428136393de0365ab4fa52", size = 166785, upload-time = "2026-03-26T11:44:38.651Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/e1/0380b8201d153a7ef77b48534a7237d6f08a33ed9624a4b841a306b02712/haiku_skills-0.12.0.tar.gz", hash = "sha256:ca30bec1aa36ed364e33fe3d6301aa35b4aa14f869a6bef0634368ca8cb509f0", size = 167737, upload-time = "2026-03-27T09:23:24.511Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/4a/3578dbbf251ec94b894163d4859c1dcdc271d5972fec2376c1a73978d45f/haiku_skills-0.11.0-py3-none-any.whl", hash = "sha256:19c76532c6228c7abb60f06cb666a023d67b9809dd2ef8597aa0347c471a4695", size = 29664, upload-time = "2026-03-26T11:44:37.479Z" }, + { url = "https://files.pythonhosted.org/packages/86/a8/0a8af6e46fb6e16aad72e686177f2d6f2ee453bb60d7188a1ee894ae53ff/haiku_skills-0.12.0-py3-none-any.whl", hash = "sha256:8a2546f65507bd986381d7890d7c663952346e03cfba33d458c3586d4d26cbe4", size = 30718, upload-time = "2026-03-27T09:23:23.327Z" }, ] [[package]] @@ -3620,7 +3620,7 @@ email = [ [[package]] name = "pydantic-ai-slim" -version = "1.70.0" +version = "1.73.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "genai-prices" }, @@ -3631,9 +3631,9 @@ dependencies = [ { name = "pydantic-graph" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/97/d57ee44976c349658ea7c645c5c2e1a26830e4b60fdeeee2669d4aaef6eb/pydantic_ai_slim-1.70.0.tar.gz", hash = "sha256:3df0c0e92f72c35e546d24795bce1f4d38f81da2d10addd2e9f255b2d2c83c91", size = 445474, upload-time = "2026-03-18T04:24:34.393Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/1b/a5e18c7c721a3cfce5b17f86cb99e4142fcb70f38ea6d2b8963c2df445e1/pydantic_ai_slim-1.73.0.tar.gz", hash = "sha256:758d5bedb4b4f484c433672639bfc87af216a38453b1539ae10928a9ca62ff62", size = 497208, upload-time = "2026-03-27T03:49:49.459Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/8c/8545d28d0b3a9957aa21393cfdab8280bb854362360b296cd486ed1713ec/pydantic_ai_slim-1.70.0-py3-none-any.whl", hash = "sha256:162907092a562b3160d9ef0418d317ec941c5c0e6dd6e0aa0dbb53b5a5cd3450", size = 576244, upload-time = "2026-03-18T04:24:27.301Z" }, + { url = "https://files.pythonhosted.org/packages/04/3b/6aa1874cd0ccbc83c17c8eb308834bf004c8d4344c27cd8048851d4b284d/pydantic_ai_slim-1.73.0-py3-none-any.whl", hash = "sha256:f7176ce6c78539e1070d7e22549186862c2f6e6ea8b05b3aaad8a1942ba1ff4f", size = 638701, upload-time = "2026-03-27T03:49:42.804Z" }, ] [package.optional-dependencies] @@ -3753,7 +3753,7 @@ wheels = [ [[package]] name = "pydantic-evals" -version = "1.70.0" +version = "1.73.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -3763,14 +3763,14 @@ dependencies = [ { name = "pyyaml" }, { name = "rich" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/01/46/21ab46e81cba78892c92ab71d21b61b23682e5e5fc645aa3647822abc3a5/pydantic_evals-1.70.0.tar.gz", hash = "sha256:ac42099233557344b41f6c43429294e61202490eb0ee9ebf6422dd4c7ea6d941", size = 56737, upload-time = "2026-03-18T04:24:35.643Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/45/ce1f9b97c4838f940c98693bc1d6298f0e1396355998942b095ce17157fe/pydantic_evals-1.73.0.tar.gz", hash = "sha256:c1f38ad9c4f566bee6958c92f205b8200957b4baf3dd5239e2a4a06edd28e3dc", size = 56137, upload-time = "2026-03-27T03:49:50.861Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/9a/6d5b74b602820621bb225e47d47f514d72e5ac5119e5dd740cd493e8ffa7/pydantic_evals-1.70.0-py3-none-any.whl", hash = "sha256:2f0c3c045c8c07b3d13876b8b0a64063ef14eb9ce27331694c8c1275f9c234b1", size = 67604, upload-time = "2026-03-18T04:24:29.134Z" }, + { url = "https://files.pythonhosted.org/packages/01/4e/aefc34a68adc165ddec22c0632cb3076579c46751ac11acdf8cec6462891/pydantic_evals-1.73.0-py3-none-any.whl", hash = "sha256:0609210d4825cc8339b5cb649be38321450b46d6e87d72c1ffde73598741fd5a", size = 67143, upload-time = "2026-03-27T03:49:44.298Z" }, ] [[package]] name = "pydantic-graph" -version = "1.70.0" +version = "1.73.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, @@ -3778,9 +3778,9 @@ dependencies = [ { name = "pydantic" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/07/27/f7a71ca2a3705e7c24fd777959cf5515646cc5f23b5b16c886a2ed373340/pydantic_graph-1.70.0.tar.gz", hash = "sha256:3f76d9137369ef8748b0e8a6df1a08262118af20a32bc139d23e5c0509c6b711", size = 58578, upload-time = "2026-03-18T04:24:37.007Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/22/d479ea32e3c712c6711e41157fb975d81582e5171510e4c662f21a85e9fe/pydantic_graph-1.73.0.tar.gz", hash = "sha256:f0d3e4984af1d902cdda1ccd3fcd86949d45d3ed21559e781f7cf9eace2ed914", size = 58717, upload-time = "2026-03-27T03:49:51.967Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/fd/19c42b60c37dfdbbf5b76c7b218e8309b43dac501f7aaf2025527ca05023/pydantic_graph-1.70.0-py3-none-any.whl", hash = "sha256:6083c1503a2587990ee1b8a15915106e3ddabc8f3f11fbc4a108a7d7496af4a5", size = 72351, upload-time = "2026-03-18T04:24:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/08/b3/4cc0b1c543b8a0c1f9add7bdeb2e8cd583961a795664a1a74d1fc8200416/pydantic_graph-1.73.0-py3-none-any.whl", hash = "sha256:aaab8b1580885f5108401db0a7da58d6c7643e467eb626b8a1364b1030327de0", size = 72504, upload-time = "2026-03-27T03:49:45.668Z" }, ] [[package]]