Merge pull request #331 from tseaver/fix-329-330-reconfig-and-extras

fix: include 'db_path'/'config' in 'extras'
This commit is contained in:
Yiorgis Gozadinos 2026-03-28 10:03:43 +02:00 committed by GitHub
commit 55a429ec28
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 66 additions and 28 deletions

View file

@ -249,7 +249,13 @@ def create_skill_extras(
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Create non-tool utility functions bound to a specific database. """Create non-tool utility functions bound to a specific database.
Returns a dict of callables that can be attached to a Skill's extras. Returns a dict of values that can be attached to a Skill's extras:
Keys:
- 'db_path': path to the LanceDB used to configure the skill
- 'config': config passed to (or derived for) the skill
- 'list_documents': returns info for documents in the database
- 'visualize_chunk': returns visualizations for chunks in the database
""" """
async def visualize_chunk(chunk_id: str) -> list: async def visualize_chunk(chunk_id: str) -> list:
@ -283,6 +289,8 @@ def create_skill_extras(
] ]
return { return {
"db_path": db_path,
"config": config,
"visualize_chunk": visualize_chunk, "visualize_chunk": visualize_chunk,
"list_documents": list_documents, "list_documents": list_documents,
} }

View file

@ -54,7 +54,7 @@ def create_skill(
config: haiku.rag AppConfig instance. If None, uses get_config(). config: haiku.rag AppConfig instance. If None, uses get_config().
""" """
from haiku.rag.config import 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: if config is None:
config = get_config() config = get_config()
@ -67,6 +67,7 @@ def create_skill(
db_path = config.storage.data_dir / "haiku.rag.lancedb" db_path = config.storage.data_dir / "haiku.rag.lancedb"
tools = create_skill_tools(db_path, config, RLMState, ["analyze"]) tools = create_skill_tools(db_path, config, RLMState, ["analyze"])
extras = create_skill_extras(db_path, config)
return Skill( return Skill(
metadata=skill_metadata(), metadata=skill_metadata(),
@ -74,6 +75,7 @@ def create_skill(
path=_skill_path, path=_skill_path,
instructions=instructions(), instructions=instructions(),
tools=list(tools.values()), tools=list(tools.values()),
extras=extras,
state_type=STATE_TYPE, state_type=STATE_TYPE,
state_namespace=STATE_NAMESPACE, state_namespace=STATE_NAMESPACE,
) )

View file

@ -5,6 +5,7 @@ import pytest
from pydantic_ai import RunContext from pydantic_ai import RunContext
from haiku.rag.client import HaikuRAG from haiku.rag.client import HaikuRAG
from haiku.rag.config.models import AppConfig
from haiku.rag.embeddings import EmbedderWrapper from haiku.rag.embeddings import EmbedderWrapper
from haiku.skills.state import SkillRunDeps from haiku.skills.state import SkillRunDeps
@ -45,6 +46,11 @@ def mock_embedder(monkeypatch):
monkeypatch.setattr(EmbedderWrapper, "embed_documents", fake_embed_documents) monkeypatch.setattr(EmbedderWrapper, "embed_documents", fake_embed_documents)
@pytest.fixture
def test_app_config():
return AppConfig(environment="skills-test")
@pytest.fixture @pytest.fixture
async def rag_db(temp_db_path): async def rag_db(temp_db_path):
"""Create a test database with sample documents.""" """Create a test database with sample documents."""

View file

@ -42,10 +42,10 @@ class TestRAGModuleAPI:
assert isinstance(result, str) assert isinstance(result, str)
assert len(result) > 0 assert len(result) > 0
def test_constants_match_create_skill(self, temp_db_path): def test_constants_match_create_skill(self, test_app_config, temp_db_path):
from haiku.rag.skills.rag import create_skill from haiku.rag.skills.rag import create_skill
skill = create_skill(db_path=temp_db_path) skill = create_skill(config=test_app_config, db_path=temp_db_path)
assert skill.state_type is STATE_TYPE assert skill.state_type is STATE_TYPE
assert skill.state_namespace == STATE_NAMESPACE assert skill.state_namespace == STATE_NAMESPACE
assert skill.metadata == skill_metadata() assert skill.metadata == skill_metadata()
@ -53,18 +53,18 @@ class TestRAGModuleAPI:
class TestRAGSkillCreation: class TestRAGSkillCreation:
def test_create_skill_returns_valid_skill(self, temp_db_path): def test_create_skill_returns_valid_skill(self, test_app_config, temp_db_path):
from haiku.rag.skills.rag import create_skill from haiku.rag.skills.rag import create_skill
skill = create_skill(db_path=temp_db_path) skill = create_skill(config=test_app_config, db_path=temp_db_path)
assert skill.metadata.name == "rag" assert skill.metadata.name == "rag"
assert skill.metadata.description assert skill.metadata.description
assert skill.instructions assert skill.instructions
def test_create_skill_has_expected_tools(self, temp_db_path): def test_create_skill_has_expected_tools(self, test_app_config, temp_db_path):
from haiku.rag.skills.rag import create_skill from haiku.rag.skills.rag import create_skill
skill = create_skill(db_path=temp_db_path) skill = create_skill(config=test_app_config, db_path=temp_db_path)
tool_names = {getattr(t, "__name__") for t in skill.tools if callable(t)} tool_names = {getattr(t, "__name__") for t in skill.tools if callable(t)}
assert tool_names == { assert tool_names == {
"search", "search",
@ -74,17 +74,19 @@ class TestRAGSkillCreation:
"research", "research",
} }
def test_create_skill_has_state(self, temp_db_path): def test_create_skill_has_state(self, test_app_config, temp_db_path):
from haiku.rag.skills.rag import RAGState, create_skill from haiku.rag.skills.rag import RAGState, create_skill
skill = create_skill(db_path=temp_db_path) skill = create_skill(config=test_app_config, db_path=temp_db_path)
assert skill._state_type is RAGState assert skill._state_type is RAGState
assert skill._state_namespace == "rag" assert skill._state_namespace == "rag"
def test_create_skill_has_extras(self, temp_db_path): def test_create_skill_has_extras(self, test_app_config, temp_db_path):
from haiku.rag.skills.rag import create_skill from haiku.rag.skills.rag import create_skill
skill = create_skill(db_path=temp_db_path) skill = create_skill(config=test_app_config, db_path=temp_db_path)
assert skill.extras["config"] is test_app_config
assert skill.extras["db_path"] is temp_db_path
assert "visualize_chunk" in skill.extras assert "visualize_chunk" in skill.extras
assert "list_documents" in skill.extras assert "list_documents" in skill.extras
assert callable(skill.extras["visualize_chunk"]) assert callable(skill.extras["visualize_chunk"])
@ -99,33 +101,42 @@ class TestRAGSkillCreation:
class TestSkillExtras: class TestSkillExtras:
async def test_list_documents_returns_all(self, rag_db): async def test_list_documents_returns_all(self, test_app_config, rag_db):
from haiku.rag.skills.rag import create_skill from haiku.rag.skills.rag import create_skill
skill = create_skill(db_path=rag_db) skill = create_skill(config=test_app_config, db_path=rag_db)
list_docs = skill.extras["list_documents"] list_docs = skill.extras["list_documents"]
results = await list_docs() results = await list_docs()
assert len(results) == 2 assert len(results) == 2
assert all(k in results[0] for k in ("id", "title", "uri", "metadata")) assert all(k in results[0] for k in ("id", "title", "uri", "metadata"))
async def test_list_documents_with_filter(self, rag_db): async def test_list_documents_with_filter(self, test_app_config, rag_db):
from haiku.rag.skills.rag import create_skill from haiku.rag.skills.rag import create_skill
skill = create_skill(db_path=rag_db) skill = create_skill(config=test_app_config, db_path=rag_db)
list_docs = skill.extras["list_documents"] list_docs = skill.extras["list_documents"]
results = await list_docs(filter="title = 'AI Overview'") results = await list_docs(filter="title = 'AI Overview'")
assert len(results) == 1 assert len(results) == 1
assert results[0]["title"] == "AI Overview" assert results[0]["title"] == "AI Overview"
async def test_visualize_chunk_unknown_returns_empty(self, rag_db): async def test_visualize_chunk_unknown_returns_empty(
self,
test_app_config,
rag_db,
):
from haiku.rag.skills.rag import create_skill from haiku.rag.skills.rag import create_skill
skill = create_skill(db_path=rag_db) skill = create_skill(config=test_app_config, db_path=rag_db)
visualize = skill.extras["visualize_chunk"] visualize = skill.extras["visualize_chunk"]
result = await visualize("nonexistent-chunk-id") result = await visualize("nonexistent-chunk-id")
assert result == [] assert result == []
async def test_visualize_chunk_returns_images(self, rag_db, monkeypatch): async def test_visualize_chunk_returns_images(
self,
test_app_config,
rag_db,
monkeypatch,
):
from haiku.rag.client import HaikuRAG from haiku.rag.client import HaikuRAG
from haiku.rag.skills.rag import create_skill from haiku.rag.skills.rag import create_skill
@ -133,7 +144,7 @@ class TestSkillExtras:
HaikuRAG, "visualize_chunk", AsyncMock(return_value=["img1"]) HaikuRAG, "visualize_chunk", AsyncMock(return_value=["img1"])
) )
skill = create_skill(db_path=rag_db) skill = create_skill(config=test_app_config, db_path=rag_db)
visualize = skill.extras["visualize_chunk"] visualize = skill.extras["visualize_chunk"]
# Get a real chunk_id from the db # Get a real chunk_id from the db

View file

@ -39,10 +39,10 @@ class TestRLMModuleAPI:
assert isinstance(result, str) assert isinstance(result, str)
assert len(result) > 0 assert len(result) > 0
def test_constants_match_create_skill(self, temp_db_path): def test_constants_match_create_skill(self, test_app_config, temp_db_path):
from haiku.rag.skills.rlm import create_skill from haiku.rag.skills.rlm import create_skill
skill = create_skill(db_path=temp_db_path) skill = create_skill(config=test_app_config, db_path=temp_db_path)
assert skill.state_type is STATE_TYPE assert skill.state_type is STATE_TYPE
assert skill.state_namespace == STATE_NAMESPACE assert skill.state_namespace == STATE_NAMESPACE
assert skill.metadata == skill_metadata() assert skill.metadata == skill_metadata()
@ -50,28 +50,39 @@ class TestRLMModuleAPI:
class TestRLMSkillCreation: class TestRLMSkillCreation:
def test_create_skill_returns_valid_skill(self, temp_db_path): def test_create_skill_returns_valid_skill(self, test_app_config, temp_db_path):
from haiku.rag.skills.rlm import create_skill from haiku.rag.skills.rlm import create_skill
skill = create_skill(db_path=temp_db_path) skill = create_skill(config=test_app_config, db_path=temp_db_path)
assert skill.metadata.name == "rag-rlm" assert skill.metadata.name == "rag-rlm"
assert skill.metadata.description assert skill.metadata.description
assert skill.instructions assert skill.instructions
def test_create_skill_has_expected_tools(self, temp_db_path): def test_create_skill_has_expected_tools(self, test_app_config, temp_db_path):
from haiku.rag.skills.rlm import create_skill from haiku.rag.skills.rlm import create_skill
skill = create_skill(db_path=temp_db_path) skill = create_skill(config=test_app_config, db_path=temp_db_path)
tool_names = {getattr(t, "__name__") for t in skill.tools if callable(t)} tool_names = {getattr(t, "__name__") for t in skill.tools if callable(t)}
assert tool_names == {"analyze"} assert tool_names == {"analyze"}
def test_create_skill_has_state(self, temp_db_path): def test_create_skill_has_state(self, test_app_config, temp_db_path):
from haiku.rag.skills.rlm import RLMState, create_skill from haiku.rag.skills.rlm import RLMState, create_skill
skill = create_skill(db_path=temp_db_path) skill = create_skill(config=test_app_config, db_path=temp_db_path)
assert skill._state_type is RLMState assert skill._state_type is RLMState
assert skill._state_namespace == "rlm" assert skill._state_namespace == "rlm"
def test_create_skill_has_extras(self, test_app_config, temp_db_path):
from haiku.rag.skills.rlm import create_skill
skill = create_skill(config=test_app_config, db_path=temp_db_path)
assert skill.extras["config"] is test_app_config
assert skill.extras["db_path"] is 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): def test_create_skill_from_env(self, monkeypatch, temp_db_path):
monkeypatch.setenv("HAIKU_RAG_DB", str(temp_db_path)) monkeypatch.setenv("HAIKU_RAG_DB", str(temp_db_path))
from haiku.rag.skills.rlm import create_skill from haiku.rag.skills.rlm import create_skill