Files

122 lines
5.4 KiB
Python
Raw Permalink 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.
"""迷你评估:对带标注的样例查询评估分类准确率、升级率、成本。
用法:
python scripts/eval.py [--repeat 2] [--config path]
--repeat 用于把样例跑 N 遍,验证语义缓存命中与降本效果。
"""
from __future__ import annotations
import argparse
import asyncio
import sys
from collections import Counter
from pathlib import Path
# 兼容 GBK 控制台(中文 Windows 默认编码),避免打印 ✅/⚠️ 时 UnicodeEncodeError
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from router_system.router import build_router
# (query, 期望领域, 期望大领域组, 期望子领域)
# 覆盖 8 领域 × 3 条 = 24 条 + 两级路由/三级子领域指标
BENCH = [
# ---- techcode / math ----
("用 Python 实现二分查找", "code", "tech", "algorithm"),
("这段 JavaScript 为什么报错:undefined is not a function", "code", "tech", "debugging"),
("帮我优化这个 SQL 查询的索引", "code", "tech", "database"),
("求解一元二次方程 ax^2+bx+c=0 的求根公式", "math", "tech", "algebra"),
("证明勾股定理", "math", "tech", "geometry"),
("计算 3x + 5 = 20x 等于多少", "math", "tech", "algebra"),
# ---- professionallegal / medical / finance ----
("劳动合同到期不续签,公司需要支付经济补偿吗", "legal", "professional", "labor"),
("在合同中约定违约金上限 30%,是否合规", "legal", "professional", "contract"),
("加班费怎么计算", "legal", "professional", "labor"),
("高血压患者可以吃哪些降压药,副作用是什么", "medical", "professional", "medication"),
("感冒发烧 38.5 度,需要吃退烧药吗", "medical", "professional", "medication"),
("烫伤后怎么处理", "medical", "professional", "firstaid"),
("基金定投的收益率怎么计算", "finance", "professional", "investing"),
("信用卡逾期了怎么办", "finance", "professional", "credit"),
("房贷利率是 LPR 加多少", "finance", "professional", "loan"),
# ---- lifestylelife / education ----
("日本旅行攻略", "life", "lifestyle", "travel"),
("健身增肌计划怎么安排", "life", "lifestyle", "fitness"),
("家常菜谱推荐", "life", "lifestyle", "food"),
("考研英语怎么备考", "education", "lifestyle", "exam"),
("高效学习方法", "education", "lifestyle", "study"),
("面试技巧有哪些", "education", "lifestyle", "career"),
# ---- general ----
("介绍一下 Transformer 架构", "general", "general", "explain"),
("写一封请假邮件", "general", "general", "writing"),
("为什么天空是蓝色的", "general", "general", "explain"),
]
async def main():
parser = argparse.ArgumentParser()
parser.add_argument("--repeat", type=int, default=2, help="重复轮数(验证缓存)")
parser.add_argument("--config", type=str, default=None)
parser.add_argument("--group", type=str, default=None,
help="指定大领域组测试两级路由(如 tech);默认自动检测")
args = parser.parse_args()
router = build_router(args.config)
# 指定组时只评测组内样例(组路由只认识本组领域)
bench = BENCH
if args.group is not None:
bench = [b for b in BENCH if b[2] == args.group]
if not bench:
print(f"组 {args.group} 无评测样例,可用组: {sorted({b[2] for b in BENCH})}")
return
correct = Counter()
group_correct = 0
subdomain_correct = 0
total = 0
upgraded = 0
cache_hits = 0
decomposed = 0 # 被 Planner 拆解为多子任务的请求数
for round_i in range(args.repeat):
for q, expected, expected_group, expected_sub in bench:
r = await router.route(q, domain_group=args.group)
total += 1
if r.domain == expected:
correct["total"] += 1
else:
correct[f"misclass->{r.domain}"] += 1
if args.group is None and r.domain_group == expected_group:
group_correct += 1
if r.subdomain == expected_sub:
subdomain_correct += 1
if r.upgraded:
upgraded += 1
if r.cache_hit:
cache_hits += 1
if any("plan:multi" in s for s in r.route):
decomposed += 1
acc = correct["total"] / total
print(f"样例数: {len(bench)} x {args.repeat} 轮 = {total} 次请求")
print(f"分类准确率: {acc:.1%} ({correct['total']}/{total})")
if args.group is None:
print(f"大领域组识别准确率: {group_correct/total:.1%} ({group_correct}/{total})")
print(f"子领域识别准确率: {subdomain_correct/total:.1%} ({subdomain_correct}/{total})")
print(f"升级率: {upgraded/total:.1%} ({upgraded}/{total})")
print(f"缓存命中率: {cache_hits/total:.1%} ({cache_hits}/{total})")
print(f"任务拆解率: {decomposed/total:.1%} ({decomposed}/{total})")
print()
print("运行指标:", router.stats.summary())
print("缓存统计:", router.cache.stats())
print()
if acc < 0.8:
print("⚠️ 准确率低于 80%,请检查分类规则。")
else:
print("✅ 分类准确率达标(≥80%)。")
if __name__ == "__main__":
asyncio.run(main())