feat(v3): Web 应用化基线(异步任务/SSE/llama-server 管理/Vue SPA 四页 + 设置页整页滚动修复)

This commit is contained in:
tzt
2026-09-01 08:31:47 +08:00
parent 8d36eeec59
commit 3bbdcb7cc7
60 changed files with 7236 additions and 1160 deletions
+121 -80
View File
@@ -1,80 +1,121 @@
"""迷你评估:对带标注的样例查询评估分类准确率、升级率、成本。
用法:
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
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from router_system.router import build_router
# (query, 期望领域)
BENCH = [
("用 Python 实现二分查找", "code"),
("这段 JavaScript 为什么报错:undefined is not a function", "code"),
("帮我优化这个 SQL 查询的索引", "code"),
("求解一元二次方程 ax^2+bx+c=0 的求根公式", "math"),
("证明勾股定理", "math"),
("计算 3x + 5 = 20x 等于多少", "math"),
("劳动合同到期不续签,公司需要支付经济补偿吗", "legal"),
("在合同中约定违约金上限 30%,是否合规", "legal"),
("专利申请的流程和费用大概是多少", "legal"),
("高血压患者可以吃哪些降压药,副作用是什么", "medical"),
("感冒发烧 38.5 度,需要吃退烧药吗", "medical"),
("糖尿病患者的日常饮食建议", "medical"),
("介绍一下 Transformer 架构", "general"),
("写一封请假邮件", "general"),
("为什么天空是蓝色的", "general"),
]
async def main():
parser = argparse.ArgumentParser()
parser.add_argument("--repeat", type=int, default=2, help="重复轮数(验证缓存)")
parser.add_argument("--config", type=str, default=None)
args = parser.parse_args()
router = build_router(args.config)
correct = Counter()
total = 0
upgraded = 0
cache_hits = 0
for round_i in range(args.repeat):
for q, expected in BENCH:
r = await router.route(q)
total += 1
if r.domain == expected:
correct["total"] += 1
else:
correct[f"misclass->{r.domain}"] += 1
if r.upgraded:
upgraded += 1
if r.cache_hit:
cache_hits += 1
acc = correct["total"] / total
print(f"样例数: {len(BENCH)} x {args.repeat} 轮 = {total} 次请求")
print(f"分类准确率: {acc:.1%} ({correct['total']}/{total})")
print(f"升级率: {upgraded/total:.1%} ({upgraded}/{total})")
print(f"缓存命中率: {cache_hits/total:.1%} ({cache_hits}/{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())
"""迷你评估:对带标注的样例查询评估分类准确率、升级率、成本。
用法:
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())