153 lines
4.8 KiB
Python
153 lines
4.8 KiB
Python
"""MetaStep 暴露 label 与 progress_emits,默认安全回退。"""
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from opensquilla.skills.meta.parser import MetaPlanError, parse_meta_plan
|
|
from opensquilla.skills.meta.types import MetaStep
|
|
|
|
|
|
def test_meta_step_default_label_empty():
|
|
s = MetaStep(id="intake", skill="intake")
|
|
assert s.label == ""
|
|
assert s.progress_emits is True
|
|
|
|
|
|
def test_meta_step_explicit_label():
|
|
s = MetaStep(id="intake", skill="intake", label="意图提取")
|
|
assert s.label == "意图提取"
|
|
|
|
|
|
def test_meta_step_progress_emits_off():
|
|
s = MetaStep(id="tool", skill="tool", kind="tool_call",
|
|
tool="memory_save", progress_emits=False)
|
|
assert s.progress_emits is False
|
|
|
|
|
|
@dataclass
|
|
class _FakeSpec:
|
|
name: str = "fake-meta"
|
|
kind: str = "meta"
|
|
composition_raw: dict[str, Any] = field(default_factory=dict)
|
|
triggers: list[str] = field(default_factory=list)
|
|
meta_priority: int = 0
|
|
content: str = ""
|
|
final_text_mode: str = "auto"
|
|
request_template: dict[str, Any] | None = None
|
|
output_contract: dict[str, Any] | None = None
|
|
eval_prompts: list[dict[str, Any]] = field(default_factory=list)
|
|
preference_keys: list[str] = field(default_factory=list)
|
|
policy_tags: list[str] = field(default_factory=list)
|
|
|
|
|
|
def _spec_with(steps):
|
|
return _FakeSpec(composition_raw={"steps": steps})
|
|
|
|
|
|
def test_parser_reads_label():
|
|
plan = parse_meta_plan(_spec_with([
|
|
{"id": "intake", "kind": "llm_chat", "label": "意图提取"},
|
|
]))
|
|
assert plan is not None
|
|
assert plan.steps[0].label == "意图提取"
|
|
|
|
|
|
def test_parser_reads_progress_emits_false():
|
|
plan = parse_meta_plan(_spec_with([
|
|
{"id": "tool", "kind": "tool_call", "tool": "memory_save",
|
|
"progress_emits": False},
|
|
]))
|
|
assert plan is not None
|
|
assert plan.steps[0].progress_emits is False
|
|
|
|
|
|
def test_parser_defaults_tool_call_progress_emits_false():
|
|
plan = parse_meta_plan(_spec_with([
|
|
{"id": "tool", "kind": "tool_call", "tool": "memory_save"},
|
|
]))
|
|
assert plan is not None
|
|
assert plan.steps[0].progress_emits is False
|
|
|
|
|
|
def test_parser_label_must_be_string():
|
|
with pytest.raises(MetaPlanError, match="label"):
|
|
parse_meta_plan(_spec_with([
|
|
{"id": "intake", "kind": "llm_chat", "label": 123},
|
|
]))
|
|
|
|
|
|
def test_parser_progress_emits_must_be_bool():
|
|
with pytest.raises(MetaPlanError, match="progress_emits"):
|
|
parse_meta_plan(_spec_with([
|
|
{"id": "intake", "kind": "llm_chat", "progress_emits": "yes"},
|
|
]))
|
|
|
|
|
|
def test_parser_label_optional_uses_readable_step_id_fallback():
|
|
plan = parse_meta_plan(_spec_with([
|
|
{"id": "source_digest", "kind": "llm_chat"},
|
|
]))
|
|
assert plan is not None
|
|
assert plan.steps[0].label == "Source Digest"
|
|
assert plan.steps[0].label_by_language["en"] == "Source Digest"
|
|
|
|
|
|
def test_parser_reads_request_template_from_spec():
|
|
spec = _spec_with([
|
|
{"id": "intake", "kind": "llm_chat"},
|
|
])
|
|
spec.request_template = {
|
|
"outcome": "Decision memo",
|
|
"fields": [
|
|
{"name": "decision_criteria", "required": True},
|
|
{"name": "time_window", "required": False, "default": "next 30 days"},
|
|
],
|
|
"assumptions": ["Use public evidence only"],
|
|
}
|
|
|
|
plan = parse_meta_plan(spec)
|
|
|
|
assert plan is not None
|
|
assert plan.request_template["outcome"] == "Decision memo"
|
|
assert plan.request_template["fields"][0]["name"] == "decision_criteria"
|
|
|
|
|
|
def test_parser_reads_output_contract_from_spec():
|
|
spec = _spec_with([
|
|
{"id": "final", "kind": "llm_chat"},
|
|
])
|
|
spec.output_contract = {
|
|
"required_sections": ["Recommendation", "Evidence"],
|
|
"assumptions": ["Criteria inferred from request"],
|
|
"unverified": ["Pricing may change"],
|
|
"artifacts": [{"name": "decision_memo.md", "required": False}],
|
|
}
|
|
|
|
plan = parse_meta_plan(spec)
|
|
|
|
assert plan is not None
|
|
assert plan.output_contract["required_sections"] == ["Recommendation", "Evidence"]
|
|
assert plan.output_contract["artifacts"][0]["name"] == "decision_memo.md"
|
|
|
|
|
|
def test_parser_reads_eval_preference_and_policy_metadata_from_spec():
|
|
spec = _spec_with([
|
|
{"id": "final", "kind": "llm_chat"},
|
|
])
|
|
spec.eval_prompts = [{
|
|
"name": "happy-path",
|
|
"prompt": "Write a decision memo for project X",
|
|
"rubric": ["Recommendation", "Evidence"],
|
|
}]
|
|
spec.preference_keys = ["preferred_language", "briefing_depth"]
|
|
spec.policy_tags = ["public-data-only", "no-pii"]
|
|
|
|
plan = parse_meta_plan(spec)
|
|
|
|
assert plan is not None
|
|
assert plan.eval_prompts[0]["name"] == "happy-path"
|
|
assert plan.preference_keys == ("preferred_language", "briefing_depth")
|
|
assert plan.policy_tags == ("public-data-only", "no-pii")
|