v0.20 alpha skill-driven python core
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(REPO_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(REPO_ROOT))
|
||||
|
||||
from scripts.lib.model_config import resolve_model_profile
|
||||
from scripts.runtime.roles import resolve_runtime_profile
|
||||
from scripts.runtime.skills import SkillRegistry
|
||||
from scripts.runtime.tasks import (
|
||||
TaskCard,
|
||||
detect_dependency_cycles,
|
||||
generate_task_cards,
|
||||
validate_packet,
|
||||
validate_task_cards,
|
||||
)
|
||||
|
||||
|
||||
def test_skill_registry_uses_agents_skills_as_canonical() -> None:
|
||||
registry = SkillRegistry()
|
||||
|
||||
names = registry.list_names()
|
||||
|
||||
assert "search-strategy" in names
|
||||
assert "search-gateway" in names
|
||||
assert "source-quality" in names
|
||||
assert "document-ingest" in names
|
||||
assert "deep-research" in names
|
||||
assert registry.validate()["ok"] is True
|
||||
|
||||
|
||||
def test_model_profile_exposes_task_types_and_role_defaults() -> None:
|
||||
resolved = resolve_model_profile(profile="medium")
|
||||
|
||||
assert resolved["roles"]["dr_pm"]
|
||||
assert resolved["task_types"]["source_discovery"] == "dr_searcher"
|
||||
assert resolved["task_types"]["chapter_assembly"] == "dr_analyst"
|
||||
|
||||
|
||||
def test_runtime_profile_resolves_task_model_and_skills() -> None:
|
||||
runtime = resolve_runtime_profile(profile="medium")
|
||||
|
||||
worker = runtime.role_for_task("evidence_packet")
|
||||
|
||||
assert worker.name == "dr_analyst"
|
||||
assert worker.model
|
||||
assert "evidence-table" in worker.skills
|
||||
assert "search-gateway" in worker.skills
|
||||
assert worker.max_concurrency >= 1
|
||||
|
||||
|
||||
def test_generate_task_cards_from_chinese_framework() -> None:
|
||||
framework = """
|
||||
# 研究框架
|
||||
|
||||
## 第1章 GLP-1 产业链的增量来自适应症扩张
|
||||
|
||||
研究思路:围绕临床、监管、竞争格局和生产供应链展开。
|
||||
|
||||
## 第2章 供应链瓶颈决定国产替代窗口
|
||||
|
||||
研究思路:围绕专利、上游原料、产能和中国市场展开。
|
||||
"""
|
||||
|
||||
cards = generate_task_cards("glp1-test", framework, axes=["clinical", "regulatory"])
|
||||
|
||||
assert [card.task_id for card in cards] == [
|
||||
"ch01-clinical",
|
||||
"ch01-regulatory",
|
||||
"ch02-clinical",
|
||||
"ch02-regulatory",
|
||||
]
|
||||
assert cards[0].output_packet == "phase2/packets/ch01-clinical.json"
|
||||
|
||||
|
||||
def test_task_card_validation_rejects_duplicates_and_cycles() -> None:
|
||||
cards = [
|
||||
TaskCard(task_id="a", chapter_ids=["ch01"], topic_axis="clinical", questions=["q"], search_routes=["scholar"], output_packet="phase2/packets/a.json", dependencies=["b"]),
|
||||
TaskCard(task_id="b", chapter_ids=["ch01"], topic_axis="regulatory", questions=["q"], search_routes=["general"], output_packet="phase2/packets/b.json", dependencies=["a"]),
|
||||
]
|
||||
|
||||
with pytest.raises(ValueError, match="dependency cycle"):
|
||||
detect_dependency_cycles(cards)
|
||||
|
||||
with pytest.raises(ValueError, match="duplicate task_id"):
|
||||
validate_task_cards([cards[0], cards[0]])
|
||||
|
||||
|
||||
def test_packet_validation_requires_sources_and_counter_evidence() -> None:
|
||||
packet = {
|
||||
"task_id": "ch01-clinical",
|
||||
"claims": [{"claim": "结论", "source_ids": ["src_001"]}],
|
||||
"evidence_items": [{"source_id": "src_001", "summary": "证据"}],
|
||||
"counter_evidence": [],
|
||||
"source_ids": ["src_001"],
|
||||
"source_quality_notes": ["Tier 1"],
|
||||
"open_questions": [],
|
||||
"raw_quotes_or_notes": ["Original English excerpt allowed."],
|
||||
}
|
||||
|
||||
with pytest.raises(ValueError, match="counter_evidence"):
|
||||
validate_packet(packet)
|
||||
|
||||
packet["counter_evidence"] = [{"claim": "限制", "source_ids": ["src_002"]}]
|
||||
with pytest.raises(ValueError, match="not declared"):
|
||||
validate_packet(packet)
|
||||
|
||||
packet["source_ids"].append("src_002")
|
||||
validate_packet(packet)
|
||||
|
||||
|
||||
def test_skill_sync_copies_to_adapter_dirs(tmp_path: Path) -> None:
|
||||
canonical = tmp_path / "skills"
|
||||
target = tmp_path / "adapter" / "skills"
|
||||
source_skill = canonical / "demo"
|
||||
source_skill.mkdir(parents=True)
|
||||
(source_skill / "SKILL.md").write_text("---\nname: demo\n---\n\nBody\n", encoding="utf-8")
|
||||
|
||||
registry = SkillRegistry(canonical_dir=canonical)
|
||||
copied = registry.sync_to([target])
|
||||
|
||||
assert copied == 1
|
||||
assert (target / "demo" / "SKILL.md").read_text(encoding="utf-8").endswith("Body\n")
|
||||
|
||||
|
||||
def test_skill_sync_skips_canonical_dir_to_avoid_deleting_source(tmp_path: Path) -> None:
|
||||
canonical = tmp_path / "skills"
|
||||
source_skill = canonical / "demo"
|
||||
source_skill.mkdir(parents=True)
|
||||
(source_skill / "SKILL.md").write_text("---\nname: demo\n---\n\nBody\n", encoding="utf-8")
|
||||
|
||||
registry = SkillRegistry(canonical_dir=canonical)
|
||||
copied = registry.sync_to([canonical])
|
||||
|
||||
assert copied == 0
|
||||
assert (source_skill / "SKILL.md").exists()
|
||||
Reference in New Issue
Block a user