Automate create_category via normal-mode generation of the decision model
This commit is contained in:
@@ -12,8 +12,10 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
from semif_agent.cli import build_scheduler, load_config
|
||||
from semif_agent.decisions import Request
|
||||
from semif_agent.dream import dream
|
||||
from semif_agent.engine import EngineUnavailable
|
||||
from semif_agent.skills import CategoryDraft, generate_category
|
||||
|
||||
|
||||
def require_real(config: dict):
|
||||
@@ -88,4 +90,35 @@ def test_busy_choice_path(tmp_path):
|
||||
status, detail = scheduler.submit("send my girlfriend an email that says I'm going to be late")
|
||||
print(f"[{status}] {detail}")
|
||||
assert status in ("preempted", "queued", "dropped")
|
||||
scheduler.idle()
|
||||
scheduler.idle()
|
||||
|
||||
|
||||
def test_engine_generation_normal_mode(tmp_path):
|
||||
"""The pinned decision model must also generate text in the normal way."""
|
||||
config = load_config()
|
||||
require_real(config)
|
||||
scheduler, config = build_scheduler(config)
|
||||
out = scheduler.engine.generate(
|
||||
[
|
||||
{"role": "system", "content": "Reply with the single word ok."},
|
||||
{"role": "user", "content": "say ok"},
|
||||
],
|
||||
max_tokens=16,
|
||||
)
|
||||
print(f"generation: {out!r}")
|
||||
assert isinstance(out, str) and out.strip()
|
||||
|
||||
|
||||
def test_generate_category(tmp_path):
|
||||
"""Authoring a category stub through the real decision model."""
|
||||
config = load_config()
|
||||
require_real(config)
|
||||
scheduler, config = build_scheduler(config)
|
||||
draft = generate_category(
|
||||
scheduler.engine,
|
||||
Request("tell me if my package was delivered"),
|
||||
scheduler.tree,
|
||||
)
|
||||
print(f"draft: {draft.name!r} — {draft.description!r}")
|
||||
assert isinstance(draft, CategoryDraft)
|
||||
assert draft.name and draft.description
|
||||
@@ -0,0 +1,93 @@
|
||||
"""Pure-stdlib tests for skill-tree authoring: category prompts, draft parsing,
|
||||
the category registry, and the error path when the engine is unavailable.
|
||||
|
||||
No mocking: engine-dependent success paths are exercised only by the box
|
||||
integration tests against the real decision model.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from semif_agent.decisions import Request
|
||||
from semif_agent.engine import EngineConfig, EngineUnavailable, SemIfEngine
|
||||
from semif_agent.skills import (
|
||||
CategoryDraft,
|
||||
CategoryRegistry,
|
||||
build_category_prompt,
|
||||
build_skills,
|
||||
build_tree,
|
||||
generate_category,
|
||||
parse_category_draft,
|
||||
)
|
||||
|
||||
|
||||
def test_build_category_prompt_contains_request_and_tree():
|
||||
tree = build_tree(build_skills({"skills": {}}))
|
||||
messages = build_category_prompt(Request("tracking for my drone delivery"), tree)
|
||||
assert messages[0]["role"] == "system"
|
||||
assert "category" in messages[0]["content"]
|
||||
joined = messages[1]["content"]
|
||||
assert "tracking for my drone delivery" in joined
|
||||
assert "email: email.compose" in joined
|
||||
|
||||
|
||||
def test_parse_category_draft_plain_json():
|
||||
draft = parse_category_draft(
|
||||
'{"title": "delivery", "description": "Track and manage package deliveries."}'
|
||||
)
|
||||
assert draft.name == "delivery"
|
||||
assert "package" in draft.description
|
||||
|
||||
|
||||
def test_parse_category_draft_json_in_prose():
|
||||
draft = parse_category_draft(
|
||||
'Sure! Here you go:\n{"title": "home_automation", "description": "Control '
|
||||
'lights, locks, and appliances around the house."}\nHope that helps.'
|
||||
)
|
||||
assert draft.name == "home_automation"
|
||||
|
||||
|
||||
def test_parse_category_draft_normalizes_title():
|
||||
draft = parse_category_draft(
|
||||
'{"title": "Home Automation", "description": "Control household devices."}'
|
||||
)
|
||||
assert draft.name == "home_automation"
|
||||
|
||||
|
||||
def test_parse_category_draft_missing_fields_raises():
|
||||
with pytest.raises(ValueError):
|
||||
parse_category_draft('{"title": "only_title"}')
|
||||
with pytest.raises(ValueError):
|
||||
parse_category_draft("not json at all")
|
||||
|
||||
|
||||
def test_parse_category_draft_rejects_unclean_title():
|
||||
with pytest.raises(ValueError):
|
||||
parse_category_draft('{"title": "ca$h!", "description": "nope"}')
|
||||
|
||||
|
||||
def test_category_registry_roundtrip(tmp_path):
|
||||
registry = CategoryRegistry(str(tmp_path / "categories.json"))
|
||||
assert registry.read() == {}
|
||||
registry.register("delivery", "Track and manage package deliveries.")
|
||||
registry.register("delivery", "Track and manage package deliveries, re-registered.")
|
||||
loaded = registry.read()
|
||||
assert set(loaded) == {"delivery"}
|
||||
assert loaded["delivery"]["description"] == (
|
||||
"Track and manage package deliveries, re-registered."
|
||||
)
|
||||
assert loaded["delivery"]["skills"] == []
|
||||
|
||||
|
||||
def test_generate_category_without_engine_raises():
|
||||
engine = SemIfEngine(EngineConfig())
|
||||
with pytest.raises(EngineUnavailable):
|
||||
generate_category(engine, Request("anything"), {})
|
||||
|
||||
|
||||
def test_build_tree_includes_registry_stubs(tmp_path):
|
||||
registry = CategoryRegistry(str(tmp_path / "categories.json"))
|
||||
registry.register("delivery", "Track and manage package deliveries.")
|
||||
tree = build_tree(build_skills({"skills": {}}))
|
||||
for category in registry.read():
|
||||
tree.setdefault(category, [])
|
||||
assert tree["delivery"] == []
|
||||
Reference in New Issue
Block a user