Fix validate_metadata layering and type annotations in _tools.py

This commit is contained in:
Yiorgis Gozadinos 2026-03-24 17:09:14 +02:00
parent 7309d13317
commit 7d50edea86
No known key found for this signature in database
4 changed files with 19 additions and 22 deletions

View file

@ -3,10 +3,7 @@
### Added
- **`create-skill` CLI command**: Generate standalone skill packages with embedded LanceDB databases. Supports tool selection, custom preamble/description, and optional config embedding. Generated packages register as `haiku.skills` entry points.
- **`haiku.rag.skill_generator`**: Programmatic API for skill generation (`generate_skill()`, `render_templates()`)
- **`haiku.rag.skills._tools`**: Reusable tool implementations and `create_skill_tools()` factory shared by built-in and generated skills
- **Jinja2 dependency**: Added for skill template rendering
- **`create-skill` CLI command**: Generate standalone skill packages with embedded LanceDB databases. Generated packages register as `haiku.skills` entry points.
## [0.35.0] - 2026-03-24

View file

@ -36,13 +36,12 @@ def _get_env() -> Environment:
def validate_metadata(name: str, description: str) -> None:
if not name.isidentifier():
raise ValueError(f"{name!r} is not a valid Python identifier")
from haiku.skills import SkillMetadata
SkillMetadata(name=name, description=description)
if not name.isidentifier():
raise ValueError(f"{name!r} is not a valid Python identifier")
if not name.islower():
raise ValueError(f"{name!r} must be lowercase")
def validate_tools(tools: list[str]) -> None:

View file

@ -5,6 +5,7 @@ from pydantic import BaseModel
from pydantic_ai import RunContext
from haiku.rag.agents.research.models import Citation
from haiku.rag.config.models import AppConfig
from haiku.rag.tools.document import DocumentInfo
from haiku.rag.tools.qa import QAHistoryEntry
from haiku.skills.state import SkillRunDeps
@ -25,7 +26,7 @@ class AnalysisEntry(BaseModel):
async def find_relevant_prior_qa(
qa_history: list[QAHistoryEntry],
query: str,
config: Any,
config: AppConfig,
) -> list[QAHistoryEntry]:
from haiku.rag.embeddings import get_embedder
from haiku.rag.tools.qa import PRIOR_ANSWER_RELEVANCE_THRESHOLD
@ -61,7 +62,7 @@ async def find_relevant_prior_qa(
async def skill_search(
db_path: Path,
config: Any,
config: AppConfig,
query: str,
limit: int | None = None,
document_filter: str | None = None,
@ -85,7 +86,7 @@ async def skill_search(
async def skill_list_documents(
db_path: Path,
config: Any,
config: AppConfig,
limit: int | None = None,
offset: int | None = None,
) -> list[dict[str, Any]]:
@ -108,7 +109,7 @@ async def skill_list_documents(
async def skill_get_document(
db_path: Path,
config: Any,
config: AppConfig,
query: str,
) -> dict[str, Any] | None:
from haiku.rag.client import HaikuRAG
@ -130,7 +131,7 @@ async def skill_get_document(
async def skill_ask(
db_path: Path,
config: Any,
config: AppConfig,
question: str,
qa_history: list[QAHistoryEntry] | None = None,
document_filter: str | None = None,
@ -166,7 +167,7 @@ async def skill_ask(
async def skill_research(
db_path: Path,
config: Any,
config: AppConfig,
question: str,
document_filter: str | None = None,
) -> tuple[str, str, str]:
@ -203,7 +204,7 @@ async def skill_research(
async def skill_analyze(
db_path: Path,
config: Any,
config: AppConfig,
question: str,
document: str | None = None,
filter: str | None = None,
@ -235,7 +236,7 @@ def update_documents_state(
documents_state.append(doc_info)
def _get_state(ctx: RunContext[SkillRunDeps], state_type: type) -> 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):
return ctx.deps.state
return None
@ -243,8 +244,8 @@ def _get_state(ctx: RunContext[SkillRunDeps], state_type: type) -> Any:
def create_skill_tools(
db_path: Path,
config: Any,
state_type: type,
config: AppConfig,
state_type: type[BaseModel],
tool_names: list[str],
) -> dict[str, Any]:
"""Create tool closures for a skill.

View file

@ -43,7 +43,7 @@ class TestValidateMetadata:
validate_metadata("Recipes", "A skill.")
def test_rejects_empty_name(self):
with pytest.raises(ValueError, match="name"):
with pytest.raises(ValueError, match="identifier"):
validate_metadata("", "A skill.")
def test_rejects_not_identifier(self):
@ -51,11 +51,11 @@ class TestValidateMetadata:
validate_metadata("123abc", "A skill.")
def test_rejects_spaces_in_name(self):
with pytest.raises(ValueError, match="name"):
with pytest.raises(ValueError, match="identifier"):
validate_metadata("my recipes", "A skill.")
def test_rejects_special_chars(self):
with pytest.raises(ValueError, match="name"):
with pytest.raises(ValueError, match="identifier"):
validate_metadata("my@recipes", "A skill.")
def test_rejects_empty_description(self):
@ -313,7 +313,7 @@ class TestGenerateSkill:
def test_rejects_invalid_name(self, tmp_path):
db_path = _make_fake_lancedb(tmp_path / "test.lancedb")
with pytest.raises(ValueError, match="name"):
with pytest.raises(ValueError, match="identifier"):
generate_skill(
db_path=db_path,
output_dir=tmp_path,