feat(v3): Web 应用化基线(异步任务/SSE/llama-server 管理/Vue SPA 四页 + 设置页整页滚动修复)
This commit is contained in:
+4
-4
@@ -13,9 +13,9 @@ def test_exact_hit():
|
||||
|
||||
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????????")
|
||||
c.put("用python写一个快速排序", {"response": "code", "domain": "code"})
|
||||
# 相似改写查询命中 L2 语义缓存
|
||||
hit = c.get("用python写一个快速排序算法")
|
||||
assert hit is not None
|
||||
assert hit[0] == "semantic"
|
||||
|
||||
@@ -24,7 +24,7 @@ def test_promote_to_exact():
|
||||
c = RouterCache(promote_frequency=3)
|
||||
result = {"response": "x", "domain": "general"}
|
||||
c.put("query", result)
|
||||
# ?????? 3 ? ? ???????
|
||||
# 语义命中 3 次后提升为精确缓存
|
||||
for _ in range(3):
|
||||
hit = c.get("query")
|
||||
assert hit is not None
|
||||
|
||||
+44
-44
@@ -1,44 +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"␍
|
||||
"""分类器单元测试。"""
|
||||
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("你好呀")
|
||||
# 未命中任何领域 -> general,低置信度,触发 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"
|
||||
|
||||
+24
-7
@@ -41,14 +41,30 @@ def test_chat_legacy(client):
|
||||
|
||||
|
||||
def test_chat_v2(v2_client):
|
||||
# POST /chat 立即返回 request_id(异步协议)
|
||||
resp = v2_client.post("/chat", json={"query": "请介绍快速排序算法"})
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["response"]
|
||||
assert data["request_id"]
|
||||
assert "fast_path" in data
|
||||
assert "route" in data
|
||||
assert data["status"] in ("fast_path", "done", "escalated", "failed")
|
||||
assert "request_id" in data
|
||||
assert data["status"] == "pending"
|
||||
|
||||
# 轮询 /runs/{id}/status 直到完成
|
||||
import time
|
||||
for _ in range(50): # 最多 5s
|
||||
time.sleep(0.1)
|
||||
status_resp = v2_client.get(f"/runs/{data['request_id']}/status")
|
||||
assert status_resp.status_code == 200
|
||||
s = status_resp.json()
|
||||
if s["status"] in ("done", "failed"):
|
||||
break
|
||||
|
||||
assert s["status"] == "done", f"期望 done,实际 {s['status']},error={s.get('error')}"
|
||||
assert s["response"]
|
||||
assert "pipeline_status" in s
|
||||
assert s["pipeline_status"] in ("fast_path", "done", "escalated")
|
||||
# fast_path 不写 workspace.json,所以 workspace_path 可能为 None
|
||||
if s["pipeline_status"] != "fast_path":
|
||||
assert s["workspace_path"] is not None
|
||||
|
||||
|
||||
def test_chat_empty_query(client):
|
||||
@@ -93,8 +109,9 @@ def test_web_ui_served(client):
|
||||
resp = client.get("/")
|
||||
assert resp.status_code == 200
|
||||
assert resp.headers["content-type"].startswith("text/html")
|
||||
assert "端云协同" in resp.text
|
||||
assert "发送" in resp.text
|
||||
# Vue SPA:由 Vite 生成,特征是 <div id="app"> 和 /static/assets/ 引用
|
||||
assert '<div id="app">' in resp.text
|
||||
assert '/static/assets/' in resp.text
|
||||
|
||||
|
||||
def test_review_flow(client):
|
||||
|
||||
+56
-56
@@ -1,56 +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"]␍
|
||||
"""路由主流程单元测试。"""
|
||||
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 any("classify:" in s for s in r.route)
|
||||
|
||||
|
||||
@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"]
|
||||
|
||||
Reference in New Issue
Block a user