| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- import json
- from app.journey import build_journey
- from app.narrator import narrate_contract
- def _contract(task_id: str, task_kind: str, goal_ids: list[str]) -> dict:
- return {
- "task_id": task_id,
- "contract_ref": f"script-build://task-contracts/sha256/{task_id}",
- "task_kind": task_kind,
- "objective": "组织目标并形成可执行的内容结构",
- "goal_ids": goal_ids,
- "input_decision_refs": [
- {
- "decision_id": "direction-accepted",
- "expected_task_kind": "direction",
- "artifact_ref": {
- "uri": "script-build://artifact-versions/9",
- "kind": "direction",
- },
- }
- ],
- "base_artifact_ref": None,
- "criteria": [
- {"criterion_id": "complete", "description": "结构覆盖所有核心目标", "hard": True}
- ],
- }
- def test_contract_narrator_uses_only_frozen_contract_facts() -> None:
- narration = narrate_contract(_contract("task-1", "structure", ["goal-1", "goal-2"]))
- assert narration.action == "创建整体结构 Task"
- assert narration.goals == ["goal-1", "goal-2"]
- assert narration.inputs == ["已接受的创作方向"]
- assert narration.output == "整体结构"
- assert narration.criteria == ["结构覆盖所有核心目标"]
- def test_journey_links_real_lifecycle_and_marks_retry_and_parallel_work() -> None:
- source = {
- "snapshot": {
- "root_trace_id": "root-7",
- "root_objective": "为选题生成一份可发布的脚本",
- "tasks": [
- {
- "task_id": "root-task",
- "parent_task_id": None,
- "status": "running",
- "created_at": "2026-07-21T01:00:00+00:00",
- "current_spec": {"objective": "root", "context_refs": []},
- "child_task_ids": ["task-1", "task-2"],
- },
- {
- "task_id": "task-1",
- "parent_task_id": "root-task",
- "status": "needs_replan",
- "created_at": "2026-07-21T01:01:00+00:00",
- "current_spec": {"objective": "structure", "context_refs": []},
- "child_task_ids": [],
- },
- {
- "task_id": "task-2",
- "parent_task_id": "root-task",
- "status": "running",
- "created_at": "2026-07-21T01:01:00+00:00",
- "current_spec": {"objective": "paragraph", "context_refs": []},
- "child_task_ids": [],
- },
- ],
- "attempts": [
- {
- "attempt_id": "attempt-1",
- "task_id": "task-1",
- "worker_trace_id": "worker-1",
- "status": "submitted",
- "operation_id": "operation-1",
- "started_at": "2026-07-21T01:02:00+00:00",
- "duration_ms": 2000,
- "execution_stats": {"primary_model": "qwen", "total_tokens": 120},
- "submission": {
- "artifact_refs": [
- {
- "uri": "script-build://artifact-versions/11",
- "kind": "structure",
- }
- ]
- },
- },
- {
- "attempt_id": "attempt-2",
- "task_id": "task-2",
- "worker_trace_id": "worker-2",
- "status": "running",
- "operation_id": "operation-1",
- "started_at": "2026-07-21T01:02:00+00:00",
- "execution_stats": {},
- "submission": None,
- },
- ],
- "validations": [
- {
- "validation_id": "validation-1",
- "task_id": "task-1",
- "attempt_id": "attempt-1",
- "status": "completed",
- "verdict": "failed",
- "started_at": "2026-07-21T01:03:00+00:00",
- "duration_ms": 500,
- "validation_plan": {
- "mode": "deterministic",
- "preflight_rule_ids": ["goal-coverage"],
- },
- "criterion_results": [
- {
- "criterion_id": "complete",
- "verdict": "failed",
- "reason": "缺少 goal-2",
- }
- ],
- "execution_stats": {"total_tokens": 0},
- }
- ],
- "decisions": [
- {
- "decision_id": "decision-1",
- "task_id": "task-1",
- "attempt_id": "attempt-1",
- "validation_id": "validation-1",
- "action": "retry",
- "reason": "补齐 goal-2 后重新生成",
- "created_at": "2026-07-21T01:04:00+00:00",
- }
- ],
- "operations": [
- {
- "operation_id": "operation-1",
- "attempt_ids": ["attempt-1", "attempt-2"],
- }
- ],
- },
- "input_summary": {
- "topic": "一次反转如何改变信念",
- "persona": {"account_name": "每天心理学", "point_count": 3},
- "strategies": [{"name": "先冲突后解法"}],
- "input_snapshot_id": "11",
- "input_digest": "sha256:input",
- },
- "contracts": {
- "task-1": _contract("task-1", "structure", ["goal-1", "goal-2"]),
- "task-2": _contract("task-2", "paragraph", ["goal-2"]),
- },
- "messages": {
- "root-7": [
- {
- "role": "assistant",
- "sequence": 1,
- "content": {
- "text": "当前缺少整体结构,需要组织目标。",
- "tool_calls": [
- {
- "id": "call-1",
- "function": {
- "name": "plan_script_tasks",
- "arguments": json.dumps(
- {"contracts": [{"task_kind": "structure"}]}
- ),
- },
- }
- ],
- },
- },
- {
- "role": "tool",
- "tool_call_id": "call-1",
- "content": {"result": json.dumps({"task_ids": ["task-1", "task-2"]})},
- },
- ],
- "worker-1": [
- {
- "role": "assistant",
- "content": {
- "text": "我先建立结构骨架。",
- "tool_calls": [
- {"function": {"name": "create_script_structure", "arguments": "{}"}}
- ],
- },
- }
- ],
- "worker-2": [],
- },
- "events": [
- {"sequence": 10, "payload": {"task_ids": ["task-1", "task-2"]}},
- {"sequence": 20, "payload": {"attempt_id": "attempt-1"}},
- {"sequence": 21, "payload": {"attempt_id": "attempt-2"}},
- {"sequence": 30, "payload": {"validation_id": "validation-1"}},
- {"sequence": 40, "payload": {"decision_id": "decision-1"}},
- ],
- }
- journey = build_journey(7, source)
- assert [step.step_type for step in journey.steps].count("plan") == 2
- execute = next(step for step in journey.steps if step.step_id == "execute:attempt-1")
- assert execute.parallel_total == 2
- assert execute.reasoning_source == "observable"
- validation = next(step for step in journey.steps if step.step_type == "validate")
- assert "缺少 goal-2" in validation.reasoning
- loop = next(edge for edge in journey.edges if edge.relation == "loop")
- assert loop.source == "decide:decision-1"
- assert loop.target == "execute:attempt-1"
- assert any(edge.relation == "validates" for edge in journey.edges)
- assert [step.sequence for step in journey.steps] == list(range(1, len(journey.steps) + 1))
|