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.
249 lines
8.5 KiB
Python
249 lines
8.5 KiB
Python
"""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 "| 3600–7200 | 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
|