test_agent_eval.py 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. """agent_eval 框架单测:用 mock 跑/收产物/judge,验证行为回归评估逻辑(可进 CI)。
  2. 真实 LLM 评估由 evals/run_evals.py 手动/nightly 触发(花钱、要 key),不在默认门禁。
  3. """
  4. from __future__ import annotations
  5. from agentpaas.engine.agent_eval import EvalTask, run_eval_task, summarize
  6. from agentpaas.engine.pipeline import JudgeResult
  7. def _judge(score):
  8. return lambda req: JudgeResult(score=score, passed=score >= req.threshold,
  9. reasons=["mock"], raw="", usage={})
  10. def _run(output="done", steps=10, cost=0.1, ws="/ws"):
  11. return lambda task: {"output": output, "steps": steps, "cost_usd": cost, "workspace_path": ws}
  12. def test_all_good_passes():
  13. task = EvalTask(id="t1", input="x", must_produce=["report.md"], rubric="好不好", threshold=0.6)
  14. r = run_eval_task(task, run_fn=_run(),
  15. collect_artifacts_fn=lambda ws: {"report.md": "完整内容"},
  16. judge_fn=_judge(0.8))
  17. assert r.files_ok and r.judge_passed and r.passed
  18. assert r.judge_score == 0.8 and r.steps == 10
  19. def test_missing_file_fails():
  20. task = EvalTask(id="t2", must_produce=["paper.pdf", "report.md"], rubric="", threshold=0.6)
  21. r = run_eval_task(task, run_fn=_run(),
  22. collect_artifacts_fn=lambda ws: {"report.md": "x"},
  23. judge_fn=_judge(1.0))
  24. assert not r.files_ok
  25. assert r.missing_files == ["paper.pdf"]
  26. assert not r.passed
  27. def test_judge_below_threshold_fails():
  28. task = EvalTask(id="t3", must_produce=[], rubric="质量", threshold=0.7)
  29. r = run_eval_task(task, run_fn=_run(),
  30. collect_artifacts_fn=lambda ws: {"a.md": "敷衍"},
  31. judge_fn=_judge(0.4))
  32. assert not r.judge_passed and not r.passed
  33. def test_error_output_fails():
  34. task = EvalTask(id="t4", rubric="", must_produce=[])
  35. r = run_eval_task(task, run_fn=_run(output="[DASHSCOPE_ERROR] boom"),
  36. collect_artifacts_fn=lambda ws: {},
  37. judge_fn=_judge(1.0))
  38. assert r.error and not r.passed
  39. def test_run_exception_captured():
  40. def boom(task):
  41. raise RuntimeError("run crashed")
  42. task = EvalTask(id="t5")
  43. r = run_eval_task(task, run_fn=boom,
  44. collect_artifacts_fn=lambda ws: {}, judge_fn=_judge(1.0))
  45. assert "run crashed" in r.error and not r.passed
  46. def test_no_rubric_skips_judge():
  47. task = EvalTask(id="t6", must_produce=["x.md"], rubric="")
  48. r = run_eval_task(task, run_fn=_run(),
  49. collect_artifacts_fn=lambda ws: {"x.md": "ok"},
  50. judge_fn=lambda req: (_ for _ in ()).throw(AssertionError("不该调 judge")))
  51. assert r.judge_passed and r.passed # 无 rubric → 不调 judge,judge_passed 默认 True
  52. def test_summarize():
  53. task = EvalTask(id="a", must_produce=["r.md"], rubric="", )
  54. good = run_eval_task(task, run_fn=_run(steps=5, cost=0.2),
  55. collect_artifacts_fn=lambda ws: {"r.md": "x"}, judge_fn=_judge(1))
  56. bad = run_eval_task(EvalTask(id="b", must_produce=["miss.pdf"]),
  57. run_fn=_run(steps=9, cost=0.3),
  58. collect_artifacts_fn=lambda ws: {}, judge_fn=_judge(1))
  59. s = summarize([good, bad])
  60. assert s["total"] == 2 and s["passed"] == 1 and s["pass_rate"] == 0.5
  61. assert s["failures"] == ["b"]
  62. assert s["total_cost_usd"] == 0.5