report_agent_scenarios.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. import argparse
  2. import hashlib
  3. import json
  4. from collections import Counter, defaultdict
  5. from pathlib import Path
  6. from zhifu.evaluation import Dataset, write_report
  7. def main():
  8. parser = argparse.ArgumentParser(description="汇总真实 Agent 场景记录,保留全部调用与审核来源")
  9. parser.add_argument("execution", type=Path)
  10. parser.add_argument("--output", type=Path, required=True)
  11. parser.add_argument("--review", type=Path)
  12. args = parser.parse_args()
  13. dataset = Dataset.model_validate_json(Path("evaluations/agent-scenarios.json").read_bytes())
  14. records = []
  15. for path in sorted(args.execution.glob("*/*.json")):
  16. content = path.read_bytes()
  17. records.append({**json.loads(content),
  18. "evidence_sha256": hashlib.sha256(content).hexdigest(),
  19. "execution_round": path.parent.name})
  20. records.sort(key=lambda row: row["started_at"])
  21. latest = {row["id"]: row for row in records}
  22. attempts = [attempt for row in records for attempt in row.get("attempts", [])]
  23. categories = defaultdict(Counter)
  24. rounds = defaultdict(list)
  25. for row in records:
  26. if row.get("finished_at"):
  27. rounds[row["execution_round"]].append(row)
  28. scope_reviews = json.loads(Path("evaluations/agent-scenario-scope-review.json").read_bytes())
  29. cases = []
  30. for case in dataset.cases():
  31. row = latest.get(case["id"], {})
  32. verdict = row.get("machine_verdict", "NOT_RUN")
  33. scope = scope_reviews["cases"].get(case["id"], {})
  34. scoped_verdict = scope.get("scope_verdict") if scope.get("evidence_sha256") == row.get(
  35. "evidence_sha256") else None
  36. categories[case["category"]][scoped_verdict or verdict] += 1
  37. cases.append({"id": case["id"], "split": case["split"], "machine_verdict": verdict,
  38. "scope_verdict": scoped_verdict,
  39. "human_review": "REQUIRED", "run_status": row.get("run_status"),
  40. "error_code": row.get("error_code"),
  41. "safety_verdict": row.get("safety_verdict", "NOT_EVALUATED"),
  42. "interaction_verdict": row.get("interaction_verdict"),
  43. "model_calls": len(row.get("attempts", [])),
  44. "graph_version": (row.get("release_snapshot") or {}).get("graph_version"),
  45. "source_sha256": row.get("source_sha256")})
  46. report = {"case_count": 200, "execution_records": len(records), "cases": cases,
  47. "rounds": {name: {"case_count": len(rows),
  48. "machine_verdicts": dict(Counter(row["machine_verdict"] for row in rows)),
  49. "graph_versions": dict(Counter((row.get("release_snapshot") or {}).get(
  50. "graph_version", "UNRECORDED") for row in rows))}
  51. for name, rows in rounds.items()},
  52. "categories": {key: dict(value) for key, value in categories.items()},
  53. "budget": json.loads((args.execution / "budget.json").read_text(encoding="utf-8")),
  54. "recorded_attempts": len(attempts),
  55. "usage": {key: sum((row["usage"] or {}).get(key, 0) for row in attempts)
  56. for key in ("input_tokens", "output_tokens", "total_tokens")},
  57. "unknown_usage_attempts": sum(row["usage"] is None for row in attempts),
  58. "cached_input_tokens": sum((row["usage"] or {}).get(
  59. "input_token_details", {}).get("cache_read", 0) for row in attempts),
  60. "output_limit_exceeded_attempts": sum((row["usage"] or {}).get(
  61. "output_tokens", 0) > 512 for row in attempts),
  62. "review_kind": "agent", "human_review": "REQUIRED"}
  63. write_report(args.output, report)
  64. if args.review:
  65. sections = ["# Agent 场景语义审阅资料", "", "机器结果与人工审核分别登记。"]
  66. for case in dataset.cases():
  67. row = latest.get(case["id"], {})
  68. if not row.get("finished_at"):
  69. continue
  70. answers = [message["data"]["content"] for message in row.get("messages", [])
  71. if message["type"] == "ai" and not message["data"].get("tool_calls")]
  72. names = [call["name"] for message in row.get("messages", [])
  73. for call in message["data"].get("tool_calls", [])]
  74. sections += ["", f"## {case['id']}", "", f"请求:{case['prompt']}",
  75. f"预期:{row.get('effective_expected', case['expected'])}",
  76. f"执行范围:{row.get('execution_scope', '原始场景范围')}",
  77. f"机器结果:{row.get('machine_verdict', 'NOT_RUN')}",
  78. f"工具:{', '.join(names)}", "最终会话答复(含系统生成的等待提示):",
  79. "\n".join(answers) or "无最终会话答复"]
  80. args.review.parent.mkdir(parents=True, exist_ok=True)
  81. args.review.write_text("\n\n".join(sections) + "\n", encoding="utf-8")
  82. print(json.dumps({key: value for key, value in report.items() if key != "cases"},
  83. ensure_ascii=False, indent=2))
  84. if __name__ == "__main__":
  85. main()