AgentEvalTool/tests/unit/test_report_render.py
sinohqb 5d04455664 feat(campaigns): auto-trigger analysis and include it in markdown export
The scheduler loop enqueues the analysis task when a realtime campaign
completes; accelerated or cancelled campaigns and a missing analysis
model skip silently. The campaign markdown export appends the analysis
appendix (overall, problems, narratives, suggestions) when a completed
analysis exists.
2026-08-03 02:25:07 +08:00

249 lines
8.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Unit tests for the pure renderers: dict in, HTML/Markdown/JSON out (no DB)."""
import json
from agenteval.evaluation.report_render import (
render_campaign_markdown,
render_html,
render_json,
render_markdown,
)
def _run_report(**overrides) -> dict:
"""A hand-built report dict matching generate_report's shape."""
report = {
"run_id": "run-1",
"target_id": "t-1",
"target_name": "客服机器人",
"scenario_id": "s-1",
"scenario_name": "售后场景",
"scenario_version": 2,
"triggered_by": "manual",
"status": "completed",
"started_at": "2026-07-30T10:00:00+00:00",
"completed_at": "2026-07-30T10:05:00+00:00",
"summary": {
"total_cases": 2,
"passed_cases": 1,
"failed_cases": 1,
"total_rules": 3,
"passed_rules": 2,
"pass_rate": 0.5,
"connectivity_cases": 1,
"judged_pass_rate": 0.0,
},
"cases": [
{
"case_id": "case-a",
"passed": True,
"connectivity": True,
"turns": [
{
"round": 0,
"sent_text": "你好",
"reply_text": "您好,请问有什么可以帮您?",
"latency_ms": 120,
"question_msg_id": "m-1",
}
],
"results": [],
},
{
"case_id": "case-b",
"passed": False,
"connectivity": False,
"turns": [
{
"round": 0,
"sent_text": "退货流程",
"reply_text": None,
"latency_ms": None,
"question_msg_id": "m-2",
}
],
"results": [
{"rule_type": "keyword_match", "passed": False, "score": 0.0, "reason": "缺少关键词"},
],
},
],
}
report.update(overrides)
return report
def _campaign_report() -> dict:
"""A hand-built dict matching generate_campaign_report's shape."""
return {
"campaign_id": "c-1",
"name": "夜间巡检",
"target_id": "t-1",
"status": "completed",
"window_seconds": 7200,
"time_scale": 1.0,
"started_at": "2026-07-30T00:00:00+00:00",
"completed_at": "2026-07-30T02:00:00+00:00",
"summary": {
"total_runs": 2,
"completed_runs": 2,
"overall_pass_rate": 0.75,
"overall_availability": 1.0,
"avg_latency_ms": 150.0,
},
"time_trend": [
{
"bucket_index": 0,
"start_seconds": 0.0,
"end_seconds": 3600.0,
"run_count": 2,
"pass_rate": 0.75,
"availability": 1.0,
"avg_latency_ms": 150.0,
},
{
"bucket_index": 1,
"start_seconds": 3600.0,
"end_seconds": 7200.0,
"run_count": 0,
"pass_rate": None,
"availability": None,
"avg_latency_ms": None,
},
],
"capability_summary": [
{
"scenario_id": "s-1",
"scenario_name": "售后场景",
"run_count": 2,
"pass_rate": 0.75,
"availability": 1.0,
"avg_latency_ms": 150.0,
},
],
}
# ── render_html ─────────────────────────────────────────────────────────────
def test_render_html_contains_names_and_summary():
html = render_html(_run_report())
assert "客服机器人" in html
assert "售后场景" in html
assert "50.00%" in html # pass_rate 0.5
def test_render_html_contains_turns_and_rule_badges():
html = render_html(_run_report())
assert "退货流程" in html
assert "keyword_match" in html
assert "失败" in html
# ── render_markdown ─────────────────────────────────────────────────────────
def test_render_markdown_summary_table():
md = render_markdown(_run_report())
assert "| 总用例数 | 2 |" in md
assert "| 通过率 | 50.0% |" in md
assert "| 连通用例 | 1 |" in md
def test_render_markdown_connectivity_badge_and_no_reply():
md = render_markdown(_run_report())
assert "🔗" in md # connectivity case badge
assert "(连通用例,未配置判定标准)" in md
assert "(无回复)" in md
def test_render_markdown_judged_pass_rate_dash_when_none():
report = _run_report()
report["summary"]["judged_pass_rate"] = None
md = render_markdown(report)
assert "| 判定型通过率 | — |" in md
# ── render_json ─────────────────────────────────────────────────────────────
def test_render_json_roundtrips():
report = _run_report()
parsed = json.loads(render_json(report))
assert parsed == report
# ── render_campaign_markdown ────────────────────────────────────────────────
def test_render_campaign_markdown_summary_and_axes():
md = render_campaign_markdown(_campaign_report())
assert "# 活动周期报告 — 夜间巡检" in md
assert "| 整窗通过率 | 75.0% |" in md
assert "## 时间趋势" in md
assert "## 能力汇总" in md
assert "| 售后场景 | 2 | 75.0% | 100.0% | 150ms |" in md
def test_render_campaign_markdown_empty_bucket_dashes():
md = render_campaign_markdown(_campaign_report())
# bucket 1 has no runs: pass_rate/availability/latency all render as —
assert "| 36007200 | 0 | — | — | — |" in md
def _analysis() -> dict:
"""A hand-built dict matching the stored campaign analysis result shape."""
return {
"overall": "整窗通过率偏低,售后场景拖后腿",
"problems": [
{
"severity": "high",
"title": "售后答非所问",
"description": "多轮对话中反复偏离用户问题",
"scenario_ids": ["s-1"],
"evidence_run_ids": ["run-abc", "run-def"],
},
{
"severity": "low",
"title": "响应偏慢",
"description": "高峰时段时延偏高",
"scenario_ids": ["s-2"],
"evidence_run_ids": [],
},
],
"scenario_narratives": [
{"scenario_id": "s-1", "narrative": "售后场景表现不稳定"},
{"scenario_id": "s-2", "narrative": "售前场景表现稳定"},
],
"suggestions": [
{"priority": 2, "text": "次要建议:扩容"},
{"priority": 1, "text": "首要建议:补充售后知识库"},
],
}
def test_render_campaign_markdown_appends_analysis_sections():
md = render_campaign_markdown(
_campaign_report(),
analysis=_analysis(),
scenario_names={"s-1": "售后场景", "s-2": "售前场景"},
)
assert "## 智能分析" in md
assert "### 总体结论" in md
assert "整窗通过率偏低,售后场景拖后腿" in md
assert "### 问题诊断" in md
assert "**[高] 售后答非所问**(场景:售后场景)" in md
assert "`run-abc`" in md and "`run-def`" in md
assert "**[低] 响应偏慢**(场景:售前场景)" in md
assert "### 分场景叙述" in md
assert "**售后场景**:售后场景表现不稳定" in md
assert "### 改善建议" in md
# 建议按 priority 升序
assert md.index("首要建议") < md.index("次要建议")
def test_render_campaign_markdown_analysis_falls_back_to_id_prefix():
md = render_campaign_markdown(_campaign_report(), analysis=_analysis(), scenario_names={})
assert "**[高] 售后答非所问**场景s-1" in md
def test_render_campaign_markdown_without_analysis_unchanged():
md = render_campaign_markdown(_campaign_report())
assert "智能分析" not in md