feat(v3): Web 应用化基线(异步任务/SSE/llama-server 管理/Vue SPA 四页 + 设置页整页滚动修复)
This commit is contained in:
+75
-65
@@ -1,65 +1,75 @@
|
||||
"""CLI 演示:构建 mock 全链路路由系统,跑一组样例查询并打印结果。
|
||||
|
||||
用法:
|
||||
python scripts/demo.py [--query "自定义查询"] [--batch]
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from router_system.router import build_router
|
||||
|
||||
SAMPLE_QUERIES = [
|
||||
"用 Python 写一个快速排序函数,并解释时间复杂度",
|
||||
"求解方程 x^2 - 5x + 6 = 0",
|
||||
"劳动合同里约定离职后两年内不得从事同行业,是否有效?",
|
||||
"高血压患者日常饮食需要注意什么?",
|
||||
"给我总结一下深度学习中注意力机制的优缺点",
|
||||
"为什么天空是蓝色的?",
|
||||
"帮我调试这段代码:def f(x): return x + 1 报 TypeError",
|
||||
"求 ∫ x^2 dx 从 0 到 1 的定积分是多少?",
|
||||
]
|
||||
|
||||
|
||||
async def run_demo(router, queries, verbose: bool = False):
|
||||
for q in queries:
|
||||
r = await router.route(q)
|
||||
print("=" * 72)
|
||||
print(f"Q: {q}")
|
||||
print(f" domain={r.domain} difficulty={r.difficulty} conf={r.confidence:.2f} "
|
||||
f"upgraded={r.upgraded} quality={r.quality_score:.2f} model={r.model_used} "
|
||||
f"latency={r.latency_ms:.1f}ms cache={r.cache_hit}({r.cache_level}) cost=${r.cost_est:.6f}")
|
||||
print(f" route: {' -> '.join(r.route)}")
|
||||
if verbose:
|
||||
print(f" --- response ---\n{r.response[:400]}")
|
||||
|
||||
|
||||
async def main():
|
||||
parser = argparse.ArgumentParser(description="多专家路由系统 CLI 演示")
|
||||
parser.add_argument("--query", type=str, default=None, help="单条查询(覆盖默认样例)")
|
||||
parser.add_argument("--batch", action="store_true", help="批量模式(打印全部响应)")
|
||||
parser.add_argument("--verbose", action="store_true", help="打印响应正文")
|
||||
args = parser.parse_args()
|
||||
|
||||
router = build_router()
|
||||
print("系统组件:", router.health())
|
||||
print()
|
||||
|
||||
if args.query:
|
||||
await run_demo(router, [args.query], verbose=True)
|
||||
else:
|
||||
await run_demo(router, SAMPLE_QUERIES, verbose=args.verbose)
|
||||
|
||||
print()
|
||||
print("=" * 72)
|
||||
print("运行指标:", router.stats.summary())
|
||||
print("缓存统计:", router.cache.stats())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())␍
|
||||
"""CLI 演示:构建专家系统内核路由(默认 L0 零参数模式),跑样例查询并打印推理链。
|
||||
|
||||
用法:
|
||||
python scripts/demo.py [--query "自定义查询"] [--batch] [--trace] [--verbose]
|
||||
--trace 打印完整推理链(分类 → 拆解 DAG → 规则轨迹 → 子任务执行 → Judge)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
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
|
||||
|
||||
SAMPLE_QUERIES = [
|
||||
"用 Python 写一个快速排序函数,并解释时间复杂度",
|
||||
"求解方程 x^2 - 5x + 6 = 0",
|
||||
"劳动合同里约定离职后两年内不得从事同行业,是否有效?",
|
||||
"高血压患者日常饮食需要注意什么?",
|
||||
"给我总结一下深度学习中注意力机制的优缺点",
|
||||
"为什么天空是蓝色的?",
|
||||
"帮我调试这段代码:def f(x): return x + 1 报 TypeError",
|
||||
"求 ∫ x^2 dx 从 0 到 1 的定积分是多少?",
|
||||
]
|
||||
|
||||
|
||||
async def run_demo(router, queries, verbose: bool = False, trace: bool = False):
|
||||
for q in queries:
|
||||
r = await router.route(q)
|
||||
print("=" * 72)
|
||||
print(f"Q: {q}")
|
||||
print(f" domain={r.domain} difficulty={r.difficulty} conf={r.confidence:.2f} "
|
||||
f"upgraded={r.upgraded} quality={r.quality_score:.2f} model={r.model_used} "
|
||||
f"latency={r.latency_ms:.1f}ms cache={r.cache_hit}({r.cache_level}) cost=${r.cost_est:.6f}")
|
||||
print(f" route: {' -> '.join(r.route)}")
|
||||
if trace:
|
||||
# 拆解轨迹从 route 中展开为更可读的形式
|
||||
plan_steps = [s for s in r.route if s.startswith("plan:") or ":" in s]
|
||||
print(" 推理链: " + " -> ".join(r.route))
|
||||
if verbose:
|
||||
print(f" --- response ---\n{r.response[:600]}")
|
||||
|
||||
|
||||
async def main():
|
||||
parser = argparse.ArgumentParser(description="多专家路由系统(专家系统内核)CLI 演示")
|
||||
parser.add_argument("--query", type=str, default=None, help="单条查询(覆盖默认样例)")
|
||||
parser.add_argument("--batch", action="store_true", help="批量模式(打印全部响应)")
|
||||
parser.add_argument("--verbose", action="store_true", help="打印响应正文")
|
||||
parser.add_argument("--trace", action="store_true", help="打印完整推理链")
|
||||
args = parser.parse_args()
|
||||
|
||||
router = build_router()
|
||||
print("系统组件:", router.health())
|
||||
print()
|
||||
|
||||
if args.query:
|
||||
await run_demo(router, [args.query], verbose=True, trace=True)
|
||||
else:
|
||||
await run_demo(router, SAMPLE_QUERIES, verbose=args.verbose, trace=args.trace)
|
||||
|
||||
print()
|
||||
print("=" * 72)
|
||||
print("运行指标:", router.stats.summary())
|
||||
print("缓存统计:", router.cache.stats())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
+121
-80
@@ -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 = 20,x 等于多少", "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 = [
|
||||
# ---- tech:code / 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 = 20,x 等于多少", "math", "tech", "algebra"),
|
||||
# ---- professional:legal / 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"),
|
||||
# ---- lifestyle:life / 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())
|
||||
|
||||
+11
-6
@@ -1,4 +1,4 @@
|
||||
"""启动路由网关服务(后台、无窗口)。
|
||||
"""启动路由网关服务(后台、无窗口)。
|
||||
|
||||
用法:
|
||||
python scripts/serve.py [--port 8000] [--stop]
|
||||
@@ -41,11 +41,16 @@ def stop():
|
||||
return
|
||||
pid = int(PID_FILE.read_text().strip())
|
||||
try:
|
||||
import signal
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
print(f"已发送终止信号 pid={pid}")
|
||||
except ProcessLookupError:
|
||||
print(f"进程 {pid} 不存在,清理 pid 文件。")
|
||||
# Windows 下 SIGTERM 对 detached 进程不可靠,改用 taskkill 强制结束进程树
|
||||
subprocess.run(
|
||||
["taskkill", "/PID", str(pid), "/T", "/F"],
|
||||
capture_output=True, text=True, timeout=15,
|
||||
)
|
||||
print(f"已终止服务 pid={pid}")
|
||||
except subprocess.TimeoutExpired:
|
||||
print(f"终止超时 pid={pid},请手动结束进程。")
|
||||
except Exception as e:
|
||||
print(f"终止失败(进程可能不存在): {e}")
|
||||
PID_FILE.unlink(missing_ok=True)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user