Files
projectAIpopular/tests/test_routerarena_adapter.py
T
tzt ce0f6170d3 chore: T-P-1 工作区收敛——并行会话成果与历史未入库文件整理入库
- 入库历史遗漏源码/测试:router_system 9 模块(agent/executors/inference/knowledge/
  memory/planner/skills/trace)、tests 11 个测试文件、config/knowledge 领域知识
- 入库根目录方案文档(v2/v3/可行性×2)、references 文献(arxiv 14-18/cnki_open/
  参考文献清单)、research 论文素材(routerarena/paper/中文文献 PDF)
- 前端构建产物刷新(新 hash);webapp 误写文档删除
- gitignore 增补:deepseek-harness、research/_refs、.mimosa/.zcode、网关日志/pid、
  临时调试脚本、tests/e2e/node_modules、AI代理功能开发/prefix
- 基线确认:318 passed
2026-09-05 08:28:25 +08:00

159 lines
5.6 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.
"""RouterArena 适配器最小测试。
覆盖:
- BaseRouter 接口契约:返回模型名必须在 config.models
- 路由决策:code→gpt-4o-mini、math→claude-3-haiku、低置信度→mistral-medium
- 预测文件格式:包含 RouterArena 必填字段
- Arena Score 公式与官方一致
"""
from __future__ import annotations
import json
import math
import os
import sys
import tempfile
from pathlib import Path
# 让 tests/ 目录能找到项目根
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from research.routerarena.adapter import ( # noqa: E402
DOMAIN_TO_MODEL_SLOT,
ESCALATION_MODEL_SLOT,
ESExpertRouter,
LOW_CONFIDENCE_THRESHOLD,
)
from research.routerarena.base_router import BaseRouter # noqa: E402
from research.routerarena.local_runner import ( # noqa: E402
MODEL_PRICING,
build_mock_dataset,
compute_arena_score,
)
CONFIG_PATH = str(
Path(__file__).resolve().parent.parent
/ "research"
/ "routerarena"
/ "config"
/ "es-expert.json"
)
def test_base_router_loads_config():
"""BaseRouter 应能加载 config 并提取 models 列表。"""
r = ESExpertRouter(router_name="es-expert", config_path=CONFIG_PATH)
assert "gpt-4o-mini" in r.models
assert "claude-3-haiku-20240307" in r.models
assert "mistral-medium" in r.models
assert len(r.models) == 5
def test_prediction_in_models():
"""所有 get_prediction 返回值必须在 config.models 中。"""
r = ESExpertRouter(router_name="es-expert", config_path=CONFIG_PATH)
samples = [
"用 Python 写一个快速排序函数",
"Solve x^2 = 4",
"Is a non-compete clause enforceable?",
"高血压患者日常饮食",
"How to calculate ROI on a fund",
"Why is the sky blue",
]
for q in samples:
m = r.get_prediction(q)
assert m in r.models, f"{q} -> {m} not in {r.models}"
def test_domain_to_slot_mapping_evidence():
"""映射表与 00_integration_plan.md §3.2 严格一致。"""
assert DOMAIN_TO_MODEL_SLOT["code"] == "gpt-4o-mini"
assert DOMAIN_TO_MODEL_SLOT["math"] == "claude-3-haiku-20240307"
assert DOMAIN_TO_MODEL_SLOT["legal"] == "claude-3-haiku-20240307"
assert DOMAIN_TO_MODEL_SLOT["medical"] == "claude-3-haiku-20240307"
assert DOMAIN_TO_MODEL_SLOT["finance"] == "claude-3-haiku-20240307"
assert DOMAIN_TO_MODEL_SLOT["life"] == "gemini-2.0-flash-001"
assert DOMAIN_TO_MODEL_SLOT["education"] == "deepseek-chat"
assert DOMAIN_TO_MODEL_SLOT["general"] == "gemini-2.0-flash-001"
def test_escalation_logic():
"""_decide 在低置信度或低质量时升级到 ESCALATION_MODEL_SLOT。"""
r = ESExpertRouter(router_name="es-expert", config_path=CONFIG_PATH)
# 高置信度 + 高质量 → 主映射
assert r._decide("code", 0.95, 0.97) == "gpt-4o-mini"
# 低置信度 → 升级
assert r._decide("code", 0.50, 0.97) == ESCALATION_MODEL_SLOT
# 低质量 → 升级
assert r._decide("code", 0.95, 0.50) == ESCALATION_MODEL_SLOT
# 未知 domain → general
assert r._decide("unknown_xyz", 0.95, 0.97) == "gemini-2.0-flash-001"
def test_compute_arena_score_matches_formula():
"""与 RouterArena run.py L65-87 公式逐项核对(0-1 标度)。"""
# 已知:Hybrid Router $0.04/1K, 71.38% acc, leaderboard arena=72.08
# 官方公式 raw 范围是 [0, 1]leaderboard 显示 ×100 标度
s = compute_arena_score(0.04, 0.7138, beta=0.1, c_max=200, c_min=0.0044)
# raw 期望:~0.7208(×100 = leaderboard 的 72.08
assert 0.715 < s < 0.725, f"expected ~0.7208, got {s:.4f}"
# 同时验证 leaderboard 标度对齐
assert 71.5 < s * 100 < 72.5, f"×100 期望 ~72.08, got {s*100:.4f}"
def test_compute_arena_score_clamping():
"""成本超 c_max/c_min 应被 clamp。"""
s1 = compute_arena_score(0.005, 0.7)
s2 = compute_arena_score(0.01, 0.7)
# 越便宜分越高(在范围内)
assert s1 > s2 > 0
# cost = c_max 时 C=0, S=0(公式定义)
s_max = compute_arena_score(200, 0.7)
assert s_max == 0.0
# cost 被 clamp 到 c_min
s_under = compute_arena_score(0.001, 0.7)
s_at_min = compute_arena_score(0.0044, 0.7)
assert abs(s_under - s_at_min) < 1e-9
def test_mock_dataset_protocol():
"""mock 数据集符合 RouterArena 协议字段。"""
data = build_mock_dataset()
assert len(data) >= 80 # 接近 sub_10 的 809
for entry in data:
assert "global index" in entry
assert "prompt" in entry
assert "prompt_formatted" in entry
assert "domain" in entry # 仅本地诊断字段
# prompt 非空
assert len(entry["prompt"]) > 0
def test_prediction_file_schema():
"""预测文件应包含 RouterArena 必填字段。"""
from research.routerarena.local_runner import run_local
with tempfile.TemporaryDirectory() as tmp:
summary = run_local(
source="mock",
router_name="es-expert",
config_path=CONFIG_PATH,
do_mock_inference=True,
output_dir=tmp,
)
# summary 含关键字段
assert summary["n_queries"] >= 80
assert "routing_distribution" in summary
assert "mock_accuracy" in summary
assert "arena_score_mock" in summary
# 预测文件存在 + 格式合规
with open(summary["prediction_file"], "r", encoding="utf-8") as f:
preds = json.load(f)
for p in preds:
assert "global index" in p
assert "prompt" in p
assert "prediction" in p
assert "for_optimality" in p
assert p["prediction"] in MODEL_PRICING or p["prediction"] is None