feat: 多专业小模型+路由模型系统 MVP(mock 全链路 + FastAPI 网关 + 论文调研)

This commit is contained in:
tzt
2026-08-12 10:40:04 +08:00
commit 1e51167ea5
50 changed files with 49382 additions and 0 deletions
View File
+14
View File
@@ -0,0 +1,14 @@
import sys
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from router_system.router import build_router
@pytest.fixture()
def router():
"""????????? mock ?????????????"""
return build_router()
+42
View File
@@ -0,0 +1,42 @@
from router_system.cache import RouterCache
def test_exact_hit():
c = RouterCache()
result = {"response": "hello", "domain": "general"}
assert c.get("query") is None
c.put("query", result)
level, got = c.get("query")
assert level == "exact"
assert got["response"] == "hello"
def test_semantic_hit():
c = RouterCache(semantic_enabled=True, similarity_threshold=0.5)
c.put("?python?????", {"response": "code", "domain": "code"})
# ?????????? L2
hit = c.get("?python????????")
assert hit is not None
assert hit[0] == "semantic"
def test_promote_to_exact():
c = RouterCache(promote_frequency=3)
result = {"response": "x", "domain": "general"}
c.put("query", result)
# ?????? 3 ? ? ???????
for _ in range(3):
hit = c.get("query")
assert hit is not None
assert c.stats()["exact_size"] == 1
def test_stats():
c = RouterCache()
c.put("q", {"response": "r"})
c.get("q")
c.get("q")
c.get("miss")
s = c.stats()
assert s["exact_hits"] == 2
assert s["misses"] == 1
+44
View File
@@ -0,0 +1,44 @@
"""分类器单元测试。"""
from router_system.classifier import RuleClassifier
def test_code_classification():
clf = RuleClassifier()
r = clf.classify("用 Python 写一个快速排序函数")
assert r.domain == "code"
assert r.confidence > 0.7
def test_math_classification():
clf = RuleClassifier()
r = clf.classify("求解方程 x^2 - 5x + 6 = 0")
assert r.domain == "math"
assert r.confidence > 0.7
def test_legal_classification():
clf = RuleClassifier()
r = clf.classify("劳动合同到期不续签需要支付经济补偿吗")
assert r.domain == "legal"
def test_medical_classification():
clf = RuleClassifier()
r = clf.classify("高血压患者日常饮食需要注意什么")
assert r.domain == "medical"
def test_general_low_confidence():
clf = RuleClassifier()
r = clf.classify("今天天气怎么样")
# 未命中任何领域 -> 低置信度,触发 should_fallback
assert r.domain == "general"
assert clf.should_fallback(r, 0.6) is True
def test_difficulty_estimation():
clf = RuleClassifier()
easy = clf.classify("1 + 1 = ?")
hard = clf.classify("证明费马大定理并推导其推论,给出详细步骤")
assert hard.difficulty in ("medium", "hard")
assert easy.difficulty == "easy"
+44
View File
@@ -0,0 +1,44 @@
"""???????? fastapi + httpx??"""
import pytest
pytest.importorskip("fastapi")
pytest.importorskip("httpx")
from fastapi.testclient import TestClient
from gateway.api import app
@pytest.fixture()
def client():
return TestClient(app)
def test_health(client):
resp = client.get("/health")
assert resp.status_code == 200
data = resp.json()
assert data["status"] == "ok"
assert "code" in data["domains"]
def test_chat(client):
resp = client.post("/chat", json={"query": "? Python ???????"})
assert resp.status_code == 200
data = resp.json()
assert data["response"]
assert data["domain"] == "code"
assert "route" in data
def test_chat_empty_query(client):
resp = client.post("/chat", json={"query": ""})
assert resp.status_code == 422
def test_metrics(client):
resp = client.get("/metrics")
assert resp.status_code == 200
data = resp.json()
assert "router" in data
assert "cache" in data
+56
View File
@@ -0,0 +1,56 @@
"""路由主流程单元测试。"""
import pytest
from router_system.router import build_router
@pytest.mark.asyncio
async def test_normal_flow_code(router):
r = await router.route("用 Python 写一个快速排序函数")
assert r.domain == "code"
assert r.response
assert r.model_used
assert r.latency_ms >= 0
assert "expert" in r.route[1] or "classify" in r.route[1]
@pytest.mark.asyncio
async def test_low_confidence_direct_fallback(router):
r = await router.route("今天天气怎么样")
assert r.upgraded is True
assert "direct_fallback" in r.route
assert r.model_used == router.fallback.model
@pytest.mark.asyncio
async def test_cache_second_round(router):
q = "用 Python 写一个快速排序函数"
r1 = await router.route(q)
assert r1.cache_hit is False
r2 = await router.route(q)
assert r2.cache_hit is True
assert r2.cache_level in ("exact", "semantic")
assert r2.response == r1.response
@pytest.mark.asyncio
async def test_judge_route_present(router):
r = await router.route("高血压患者日常饮食需要注意什么")
assert any(step.startswith("judge:") for step in r.route)
@pytest.mark.asyncio
async def test_stats_recorded(router):
await router.route("写一个 python 函数")
await router.route("写一个 python 函数")
s = router.stats.summary()
assert s["total_requests"] == 2
assert s["domain_distribution"]["code"] == 2
assert s["cache_hit_rate"] > 0
def test_health(router):
h = router.health()
assert h["status"] == "ok"
assert "code" in h["domains"]
assert "math" in h["domains"]