AgentEvalTool/tests/unit/test_report_render.py
sinohqb 14b09e1ac6 feat(campaigns): 周期对比纳入 Markdown 导出,活动导出排版重优化
- 导出端点解析评测对象名与 completed 周期对比行(基线元信息、分析模型、现算机械 diff),渲染 `## 周期对比` 附录(趋势 + 指标变化表 + 问题演变 + 建议落实),紧跟智能分析之后;非 completed 则完全无痕
- 头部排版重优化:状态中文化、窗口与时段人类可读(24h、0h–1h)、友好时间戳、头部补评测对象名、「正式线」/「加速调试线 ×N」措辞(加速线附注压缩后实际耗时);Run 级导出不动
- 测试:渲染器黄金断言更新 + 附录/头部/缺省用例,集成测试新增导出含对比、无对比行、failed 行三例
2026-08-03 15:13:11 +08:00

349 lines
13 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 "| 1h2h | 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
def test_render_campaign_markdown_header_readability():
md = render_campaign_markdown(_campaign_report(), target_name="客服机器人")
assert "**评测对象**: 客服机器人" in md
assert "**状态**: 已完成" in md
assert "**窗口**: 2h正式线" in md
assert "**开始时间**: 2026-07-30 00:00" in md
assert "**完成时间**: 2026-07-30 02:00" in md
assert "| 0h1h | 2 | 75.0% | 100.0% | 150ms |" in md
def test_render_campaign_markdown_header_falls_back_to_target_id():
md = render_campaign_markdown(_campaign_report())
assert "**评测对象**: t-1" in md
def test_render_campaign_markdown_accelerated_line():
report = _campaign_report()
report["window_seconds"] = 86400
report["time_scale"] = 4.0
md = render_campaign_markdown(report)
assert "**窗口**: 24h加速调试线 ×4压缩后实际耗时约 6h" in md
def _comparison() -> dict:
"""A hand-built dict matching the comparison context the export endpoint resolves."""
return {
"result": {
"trend": "regressing",
"summary": "整体质量下滑,售后场景恶化",
"problem_evolution": [
{
"status": "persisting",
"title": "售后答非所问",
"scenario_ids": ["s-1"],
"detail": "问题仍未收敛",
},
{"status": "resolved", "title": "响应偏慢", "scenario_ids": [], "detail": ""},
],
"suggestion_tracking": [
{"status": "partial", "text": "补充售后知识库", "note": "仅覆盖部分问题"},
{"status": "new", "text": "新增建议:监控时延", "note": ""},
],
},
"baseline_name": "上一期巡检",
"baseline_completed_at": "2026-07-29T02:00:00+00:00",
"model_name": "qwen-max",
"updated_at": "2026-07-30T03:00:00+00:00",
"metric_diff": {
"overall": {
"pass_rate": {"baseline": 0.8, "current": 0.75, "delta": -0.05},
"availability": {"baseline": 1.0, "current": 0.5, "delta": -0.5},
"avg_latency_ms": {"baseline": 120.0, "current": 150.0, "delta": 30.0},
},
"scenarios": [
{
"scenario_id": "s-1",
"scenario_name": "售后场景",
"pass_rate": {"baseline": 0.7, "current": 0.6, "delta": -0.1},
"availability": {"baseline": None, "current": None, "delta": None},
"avg_latency_ms": {"baseline": 100.0, "current": 140.0, "delta": 40.0},
},
],
},
}
def test_render_campaign_markdown_appends_comparison_section():
md = render_campaign_markdown(
_campaign_report(),
comparison=_comparison(),
scenario_names={"s-1": "售后场景"},
)
assert "## 周期对比" in md
assert "基线:「上一期巡检」(完成于 2026-07-29 02:00" in md
assert "分析模型qwen-max" in md
assert "生成于2026-07-30 03:00" in md
assert "**趋势**:退化 — 整体质量下滑,售后场景恶化" in md
assert "### 指标变化" in md
assert "| 整窗(总体) | 80.0% → 75.0%-5.0pp | 100.0% → 50.0%-50.0pp | 120ms → 150ms+30.0ms |" in md
assert "| 售后场景 | 70.0% → 60.0%-10.0pp | — → —(—) | 100ms → 140ms+40.0ms |" in md
assert "### 问题演变" in md
assert "**[持续] 售后答非所问**(场景:售后场景)" in md
assert " 问题仍未收敛" in md
assert "**[消解] 响应偏慢**" in md
assert "### 建议落实情况" in md
assert "**[部分落实] 补充售后知识库**" in md
assert " 仅覆盖部分问题" in md
assert "**[新增] 新增建议:监控时延**" in md
def test_render_campaign_markdown_comparison_comes_after_analysis():
md = render_campaign_markdown(_campaign_report(), analysis=_analysis(), comparison=_comparison())
assert md.index("## 智能分析") < md.index("## 周期对比")
def test_render_campaign_markdown_without_comparison_unchanged():
md = render_campaign_markdown(_campaign_report())
assert "周期对比" not in md