Expose skill state metadata as module-level API
This commit is contained in:
parent
8a859fbe3c
commit
3f02abbc70
7 changed files with 165 additions and 23 deletions
|
|
@ -1,6 +1,14 @@
|
|||
# Changelog
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- **Module-level skill introspection API**: `STATE_TYPE`, `STATE_NAMESPACE`, `skill_metadata()`, `instructions()`, and `state_metadata()` on `haiku.rag.skills.rag` and `haiku.rag.skills.rlm` — allows introspecting skill configuration without calling `create_skill()`
|
||||
|
||||
### Changed
|
||||
|
||||
- **`haiku.skills` dependency**: Bumped to `>=0.7.0` for `StateMetadata` dataclass
|
||||
|
||||
## [0.32.3] - 2026-03-03
|
||||
|
||||
### Changed
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
from functools import cache
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
|
@ -9,7 +10,7 @@ from haiku.rag.agents.research.models import Citation
|
|||
from haiku.rag.store.models.chunk import SearchResult
|
||||
from haiku.rag.tools.document import DocumentInfo
|
||||
from haiku.rag.tools.qa import QAHistoryEntry
|
||||
from haiku.skills.models import Skill, SkillSource
|
||||
from haiku.skills.models import Skill, SkillMetadata, SkillSource, StateMetadata
|
||||
from haiku.skills.parser import parse_skill_md
|
||||
from haiku.skills.state import SkillRunDeps
|
||||
|
||||
|
|
@ -37,6 +38,32 @@ class RAGState(BaseModel):
|
|||
reports: list[ResearchEntry] = Field(default_factory=list)
|
||||
|
||||
|
||||
STATE_TYPE = RAGState
|
||||
STATE_NAMESPACE = "rag"
|
||||
|
||||
_skill_path = Path(__file__).parent / "rag"
|
||||
|
||||
|
||||
@cache
|
||||
def skill_metadata() -> SkillMetadata:
|
||||
metadata, _ = parse_skill_md(_skill_path / "SKILL.md")
|
||||
return metadata
|
||||
|
||||
|
||||
@cache
|
||||
def instructions() -> str | None:
|
||||
_, instr = parse_skill_md(_skill_path / "SKILL.md")
|
||||
return instr
|
||||
|
||||
|
||||
def state_metadata() -> StateMetadata:
|
||||
return StateMetadata(
|
||||
namespace=STATE_NAMESPACE,
|
||||
type=STATE_TYPE,
|
||||
schema=STATE_TYPE.model_json_schema(),
|
||||
)
|
||||
|
||||
|
||||
def create_skill(
|
||||
db_path: Path | None = None,
|
||||
config: Any = None,
|
||||
|
|
@ -62,9 +89,6 @@ def create_skill(
|
|||
else:
|
||||
db_path = config.storage.data_dir / "haiku.rag.lancedb"
|
||||
|
||||
path = Path(__file__).parent / "rag"
|
||||
metadata, instructions = parse_skill_md(path / "SKILL.md")
|
||||
|
||||
async def _find_relevant_prior_qa(
|
||||
state: RAGState, query: str
|
||||
) -> list[QAHistoryEntry]:
|
||||
|
|
@ -323,10 +347,10 @@ def create_skill(
|
|||
return "\n".join(parts)
|
||||
|
||||
return Skill(
|
||||
metadata=metadata,
|
||||
metadata=skill_metadata(),
|
||||
source=SkillSource.ENTRYPOINT,
|
||||
path=path,
|
||||
instructions=instructions,
|
||||
path=_skill_path,
|
||||
instructions=instructions(),
|
||||
tools=[
|
||||
search,
|
||||
list_documents,
|
||||
|
|
@ -334,6 +358,6 @@ def create_skill(
|
|||
ask,
|
||||
research,
|
||||
],
|
||||
state_type=RAGState,
|
||||
state_namespace="rag",
|
||||
state_type=STATE_TYPE,
|
||||
state_namespace=STATE_NAMESPACE,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import os
|
||||
from functools import cache
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic_ai import RunContext
|
||||
|
||||
from haiku.skills.models import Skill, SkillSource
|
||||
from haiku.skills.models import Skill, SkillMetadata, SkillSource, StateMetadata
|
||||
from haiku.skills.parser import parse_skill_md
|
||||
from haiku.skills.state import SkillRunDeps
|
||||
|
||||
|
|
@ -20,6 +21,32 @@ class RLMState(BaseModel):
|
|||
analyses: list[AnalysisEntry] = []
|
||||
|
||||
|
||||
STATE_TYPE = RLMState
|
||||
STATE_NAMESPACE = "rlm"
|
||||
|
||||
_skill_path = Path(__file__).parent / "rag-rlm"
|
||||
|
||||
|
||||
@cache
|
||||
def skill_metadata() -> SkillMetadata:
|
||||
metadata, _ = parse_skill_md(_skill_path / "SKILL.md")
|
||||
return metadata
|
||||
|
||||
|
||||
@cache
|
||||
def instructions() -> str | None:
|
||||
_, instr = parse_skill_md(_skill_path / "SKILL.md")
|
||||
return instr
|
||||
|
||||
|
||||
def state_metadata() -> StateMetadata:
|
||||
return StateMetadata(
|
||||
namespace=STATE_NAMESPACE,
|
||||
type=STATE_TYPE,
|
||||
schema=STATE_TYPE.model_json_schema(),
|
||||
)
|
||||
|
||||
|
||||
def create_skill(
|
||||
db_path: Path | None = None,
|
||||
config: Any = None,
|
||||
|
|
@ -45,9 +72,6 @@ def create_skill(
|
|||
else:
|
||||
db_path = config.storage.data_dir / "haiku.rag.lancedb"
|
||||
|
||||
path = Path(__file__).parent / "rag-rlm"
|
||||
metadata, instructions = parse_skill_md(path / "SKILL.md")
|
||||
|
||||
async def analyze(
|
||||
ctx: RunContext[SkillRunDeps],
|
||||
question: str,
|
||||
|
|
@ -85,11 +109,11 @@ def create_skill(
|
|||
return output
|
||||
|
||||
return Skill(
|
||||
metadata=metadata,
|
||||
metadata=skill_metadata(),
|
||||
source=SkillSource.ENTRYPOINT,
|
||||
path=path,
|
||||
instructions=instructions,
|
||||
path=_skill_path,
|
||||
instructions=instructions(),
|
||||
tools=[analyze],
|
||||
state_type=RLMState,
|
||||
state_namespace="rlm",
|
||||
state_type=STATE_TYPE,
|
||||
state_namespace=STATE_NAMESPACE,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ classifiers = [
|
|||
dependencies = [
|
||||
"cachetools>=5.5.0",
|
||||
"docling-core==2.65.1",
|
||||
"haiku.skills>=0.6.0",
|
||||
"haiku.skills>=0.7.0",
|
||||
"httpx>=0.28.1",
|
||||
"jsonpatch>=1.33",
|
||||
"lancedb==0.29.2",
|
||||
|
|
|
|||
|
|
@ -2,13 +2,56 @@ from unittest.mock import AsyncMock
|
|||
|
||||
from haiku.rag.agents.research.models import Citation, ResearchReport
|
||||
from haiku.rag.client import HaikuRAG
|
||||
from haiku.rag.skills.rag import (
|
||||
STATE_NAMESPACE,
|
||||
STATE_TYPE,
|
||||
RAGState,
|
||||
instructions,
|
||||
skill_metadata,
|
||||
state_metadata,
|
||||
)
|
||||
from haiku.rag.store.models.chunk import SearchResult
|
||||
from haiku.rag.tools.document import DocumentInfo
|
||||
from haiku.rag.tools.qa import QAHistoryEntry
|
||||
from haiku.skills.models import SkillMetadata, StateMetadata
|
||||
|
||||
from .conftest import _get_tool, _make_ctx
|
||||
|
||||
|
||||
class TestRAGModuleAPI:
|
||||
def test_state_type_is_rag_state(self):
|
||||
assert STATE_TYPE is RAGState
|
||||
|
||||
def test_state_namespace(self):
|
||||
assert STATE_NAMESPACE == "rag"
|
||||
|
||||
def test_state_metadata_returns_state_metadata(self):
|
||||
result = state_metadata()
|
||||
assert isinstance(result, StateMetadata)
|
||||
assert result.namespace == "rag"
|
||||
assert result.type is RAGState
|
||||
assert result.schema == RAGState.model_json_schema()
|
||||
|
||||
def test_skill_metadata_returns_skill_metadata(self):
|
||||
result = skill_metadata()
|
||||
assert isinstance(result, SkillMetadata)
|
||||
assert result.name == "rag"
|
||||
|
||||
def test_instructions_returns_string(self):
|
||||
result = instructions()
|
||||
assert isinstance(result, str)
|
||||
assert len(result) > 0
|
||||
|
||||
def test_constants_match_create_skill(self, temp_db_path):
|
||||
from haiku.rag.skills.rag import create_skill
|
||||
|
||||
skill = create_skill(db_path=temp_db_path)
|
||||
assert skill.state_type is STATE_TYPE
|
||||
assert skill.state_namespace == STATE_NAMESPACE
|
||||
assert skill.metadata == skill_metadata()
|
||||
assert skill.instructions == instructions()
|
||||
|
||||
|
||||
class TestRAGSkillCreation:
|
||||
def test_create_skill_returns_valid_skill(self, temp_db_path):
|
||||
from haiku.rag.skills.rag import create_skill
|
||||
|
|
|
|||
|
|
@ -2,10 +2,53 @@ from unittest.mock import AsyncMock
|
|||
|
||||
from haiku.rag.agents.rlm.models import RLMResult
|
||||
from haiku.rag.client import HaikuRAG
|
||||
from haiku.rag.skills.rlm import (
|
||||
STATE_NAMESPACE,
|
||||
STATE_TYPE,
|
||||
RLMState,
|
||||
instructions,
|
||||
skill_metadata,
|
||||
state_metadata,
|
||||
)
|
||||
from haiku.skills.models import SkillMetadata, StateMetadata
|
||||
|
||||
from .conftest import _get_tool, _make_ctx
|
||||
|
||||
|
||||
class TestRLMModuleAPI:
|
||||
def test_state_type_is_rlm_state(self):
|
||||
assert STATE_TYPE is RLMState
|
||||
|
||||
def test_state_namespace(self):
|
||||
assert STATE_NAMESPACE == "rlm"
|
||||
|
||||
def test_state_metadata_returns_state_metadata(self):
|
||||
result = state_metadata()
|
||||
assert isinstance(result, StateMetadata)
|
||||
assert result.namespace == "rlm"
|
||||
assert result.type is RLMState
|
||||
assert result.schema == RLMState.model_json_schema()
|
||||
|
||||
def test_skill_metadata_returns_skill_metadata(self):
|
||||
result = skill_metadata()
|
||||
assert isinstance(result, SkillMetadata)
|
||||
assert result.name == "rag-rlm"
|
||||
|
||||
def test_instructions_returns_string(self):
|
||||
result = instructions()
|
||||
assert isinstance(result, str)
|
||||
assert len(result) > 0
|
||||
|
||||
def test_constants_match_create_skill(self, temp_db_path):
|
||||
from haiku.rag.skills.rlm import create_skill
|
||||
|
||||
skill = create_skill(db_path=temp_db_path)
|
||||
assert skill.state_type is STATE_TYPE
|
||||
assert skill.state_namespace == STATE_NAMESPACE
|
||||
assert skill.metadata == skill_metadata()
|
||||
assert skill.instructions == instructions()
|
||||
|
||||
|
||||
class TestRLMSkillCreation:
|
||||
def test_create_skill_returns_valid_skill(self, temp_db_path):
|
||||
from haiku.rag.skills.rlm import create_skill
|
||||
|
|
|
|||
8
uv.lock
8
uv.lock
|
|
@ -1490,7 +1490,7 @@ requires-dist = [
|
|||
{ name = "cohere", marker = "extra == 'cohere'", specifier = ">=5.20.1" },
|
||||
{ name = "docling", marker = "extra == 'docling'", specifier = "==2.73.1" },
|
||||
{ name = "docling-core", specifier = "==2.65.1" },
|
||||
{ name = "haiku-skills", specifier = ">=0.6.0" },
|
||||
{ name = "haiku-skills", specifier = ">=0.7.0" },
|
||||
{ name = "httpx", specifier = ">=0.28.1" },
|
||||
{ name = "jsonpatch", specifier = ">=1.33" },
|
||||
{ name = "lancedb", specifier = "==0.29.2" },
|
||||
|
|
@ -1522,7 +1522,7 @@ provides-extras = ["docling", "voyageai", "mxbai", "cohere", "zeroentropy", "jin
|
|||
|
||||
[[package]]
|
||||
name = "haiku-skills"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
|
|
@ -1530,9 +1530,9 @@ dependencies = [
|
|||
{ name = "pyyaml" },
|
||||
{ name = "skills-ref" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/10/0f/97d95a6814cec171d97ca7eec6365468f6cbdbfc9fbb2d6346494035aa8a/haiku_skills-0.6.0.tar.gz", hash = "sha256:8352c9157260742b475315f92191fe0c9149aa0faa8c9d20af8c201f1bc71e87", size = 132772, upload-time = "2026-03-03T08:55:59.909Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8c/a6/4fbdfe95e6ff6a574088b9f3ed7543a6e01b4b6c1740440ce07c1cf4893a/haiku_skills-0.7.0.tar.gz", hash = "sha256:ec5c5176f8feab09cc6aa4cda9a64e2446074f3ed111cb7e2587f7b524711364", size = 133420, upload-time = "2026-03-04T09:25:52.729Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/c4/f9f8892da06bdc2defb12191e765486dd8dd87155c63dc6ca045f75c8211/haiku_skills-0.6.0-py3-none-any.whl", hash = "sha256:adfbe2206eb238abb0dfe376ffede669860ffd45a2f4647090dfbd787842cf7f", size = 24631, upload-time = "2026-03-03T08:55:58.901Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/38/5048252c0b68bc77dd0fcf6b5c4e29074d90b3f6888eb282a0fcae967227/haiku_skills-0.7.0-py3-none-any.whl", hash = "sha256:7b7791890cc230fd53c5a9a7d93129e7901fecc1d64153d0386a83603e5b7b25", size = 25022, upload-time = "2026-03-04T09:25:51.872Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
Loading…
Reference in a new issue