feat: add skill learning runtime
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from skills_learning.models import FlowStep, FlowTemplateSkill, SkillMetadata
|
||||
from skills_learning.store import SkillStore
|
||||
from skills_learning.synthesis import extract_tool_calls, synthesize_flow_skill
|
||||
|
||||
|
||||
def _record(
|
||||
action: str,
|
||||
args: dict[str, object] | None = None,
|
||||
*,
|
||||
result: dict[str, object] | None = None,
|
||||
) -> dict[str, object]:
|
||||
return {
|
||||
"tool_call": {
|
||||
"action": action,
|
||||
"args": args or {},
|
||||
},
|
||||
"result": result or {},
|
||||
}
|
||||
|
||||
|
||||
def test_extract_tool_calls_filters_read_only_tools_in_order() -> None:
|
||||
records = [
|
||||
_record("describe_screen"),
|
||||
_record("launch_app", {"app_id": "com.example"}),
|
||||
_record("screenshot"),
|
||||
_record("tap", {"x": 1, "y": 2}),
|
||||
_record("find_text", {"query": "Send"}),
|
||||
_record("input_text", {"text": "coffee"}),
|
||||
]
|
||||
|
||||
steps = extract_tool_calls("", records)
|
||||
|
||||
assert steps == [
|
||||
FlowStep("launch_app", {"app_id": "com.example"}),
|
||||
FlowStep("tap", {"x": 1, "y": 2}),
|
||||
FlowStep("input_text", {"text": "coffee"}),
|
||||
]
|
||||
|
||||
|
||||
def test_first_time_synthesis_has_literal_steps_and_no_parameters() -> None:
|
||||
skill = synthesize_flow_skill(
|
||||
"search coffee",
|
||||
[_record("input_text", {"text": "coffee"})],
|
||||
)
|
||||
|
||||
assert skill.name == "search coffee"
|
||||
assert skill.steps == [FlowStep("input_text", {"text": "coffee"})]
|
||||
assert skill.parameters == {}
|
||||
|
||||
|
||||
def test_second_execution_promotes_differing_argument_to_parameter() -> None:
|
||||
store = SkillStore()
|
||||
store.create_version(
|
||||
FlowTemplateSkill(
|
||||
metadata=SkillMetadata(
|
||||
name="search",
|
||||
description="Learned search",
|
||||
originating_goal="search coffee",
|
||||
),
|
||||
steps=[FlowStep("input_text", {"text": "coffee"})],
|
||||
parameters={},
|
||||
)
|
||||
)
|
||||
records = [
|
||||
_record(
|
||||
"input_text",
|
||||
{"text": "tea"},
|
||||
result={
|
||||
"semantic_scene": {
|
||||
"page": "Search",
|
||||
"intents": ["search"],
|
||||
"widgets": [
|
||||
{
|
||||
"element_id": "search-input",
|
||||
"purpose": "search field",
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
)
|
||||
]
|
||||
|
||||
skill = synthesize_flow_skill("search tea", records, store=store)
|
||||
|
||||
assert skill.name == "search"
|
||||
assert skill.steps == [FlowStep("input_text", {"text": "{search_field}"})]
|
||||
assert "search_field" in skill.parameters
|
||||
|
||||
|
||||
def test_identical_repeat_does_not_add_parameters() -> None:
|
||||
store = SkillStore()
|
||||
store.create_version(
|
||||
FlowTemplateSkill(
|
||||
metadata=SkillMetadata(
|
||||
name="search",
|
||||
description="Learned search",
|
||||
originating_goal="search coffee",
|
||||
),
|
||||
steps=[FlowStep("input_text", {"text": "coffee"})],
|
||||
parameters={},
|
||||
)
|
||||
)
|
||||
|
||||
skill = synthesize_flow_skill(
|
||||
"search coffee again",
|
||||
[_record("input_text", {"text": "coffee"})],
|
||||
store=store,
|
||||
)
|
||||
|
||||
assert skill.steps == [FlowStep("input_text", {"text": "coffee"})]
|
||||
assert skill.parameters == {}
|
||||
Reference in New Issue
Block a user