feat(v2): T7 网关扩展 + T9 token 计量与账单(/chat v2,/chat/legacy,/runs,/review,/metrics)

This commit is contained in:
tzt
2026-08-30 21:15:19 +08:00
parent ea150129b4
commit c874382130
6 changed files with 482 additions and 101 deletions
+41 -3
View File
@@ -1,4 +1,4 @@
"""???????? fastapi + httpx??"""
"""FastAPI 网关测试:v1 legacy 端点保持 + v2 端点(封闭,注入 mock 管线)。"""
import pytest
pytest.importorskip("fastapi")
@@ -6,6 +6,7 @@ pytest.importorskip("httpx")
from fastapi.testclient import TestClient
import gateway.api as ga
from gateway.api import app
@@ -14,6 +15,14 @@ def client():
return TestClient(app)
@pytest.fixture()
def v2_client(client):
# 用 mock worker 构建真实 v2 管线并注入(无需 API key / 真实模型)
pipe = ga.build_v2_pipeline(worker_cfg_override={"backend": "mock"})
ga.set_pipeline(pipe)
return client
def test_health(client):
resp = client.get("/health")
assert resp.status_code == 200
@@ -22,8 +31,8 @@ def test_health(client):
assert "code" in data["domains"]
def test_chat(client):
resp = client.post("/chat", json={"query": "? Python ???????"})
def test_chat_legacy(client):
resp = client.post("/chat/legacy", json={"query": " Python 写一个快速排序函数"})
assert resp.status_code == 200
data = resp.json()
assert data["response"]
@@ -31,6 +40,17 @@ def test_chat(client):
assert "route" in data
def test_chat_v2(v2_client):
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")
def test_chat_empty_query(client):
resp = client.post("/chat", json={"query": ""})
assert resp.status_code == 422
@@ -42,3 +62,21 @@ def test_metrics(client):
data = resp.json()
assert "router" in data
assert "cache" in data
assert "v2" in data
assert "review" in data
def test_workspace_not_found(client):
resp = client.get("/runs/nonexistent/workspace")
assert resp.status_code == 404
def test_review_flow(client):
q = ga.get_review()
rid = q.enqueue("req-x", "q", "ans", tags=["safety"], reason="test")
assert q.count() >= 1
resp = client.get("/review/queue")
assert resp.status_code == 200
resp2 = client.post(f"/review/{rid}", params={"verdict": "approve"})
assert resp2.status_code == 200
assert resp2.json()["ok"] is True
+81
View File
@@ -0,0 +1,81 @@
"""推理链轨迹存储 / 查询测试(T3:整体项目部分拆解·先行实现)。"""
import pytest
from router_system.router import build_router
from router_system.trace import TraceStore
def test_trace_store_put_get():
ts = TraceStore()
ts.put("abc", {"query": "q1", "route": ["a", "b"]})
t = ts.get("abc")
assert t["query"] == "q1"
assert ts.get("not-exist") is None
def test_trace_store_ring_eviction():
ts = TraceStore(max_entries=3)
for i in range(5):
ts.put(f"id{i}", {"i": i})
assert ts.size() == 3
assert ts.get("id0") is None # 最旧被淘汰
assert ts.get("id4") is not None
def test_trace_store_overwrite():
ts = TraceStore()
ts.put("a", {"v": 1})
ts.put("a", {"v": 2})
assert ts.get("a")["v"] == 2
assert ts.size() == 1
@pytest.mark.asyncio
async def test_router_records_trace():
r = build_router()
res = await r.route("求解方程 x^2 - 5x + 6 = 0")
assert res.request_id
trace = r.trace_store.get(res.request_id)
assert trace is not None
assert trace["domain"] == "math"
assert trace["domain_group"] == "tech"
assert "plan:multi" in " ".join(trace["route"])
assert trace["quality_score"] > 0
@pytest.mark.asyncio
async def test_trace_cache_hit_roundtrip():
r = build_router()
q = "加班费怎么计算"
r1 = await r.route(q)
r2 = await r.route(q) # 缓存命中
assert r2.cache_hit is True
trace = r.trace_store.get(r2.request_id)
assert trace["cache_hit"] is True
assert trace["cache_level"] == "exact"
@pytest.mark.asyncio
async def test_trace_subdomain_fields():
r = build_router()
res = await r.route("基金定投的收益率怎么计算")
trace = r.trace_store.get(res.request_id)
assert trace["subdomain"] == "investing"
assert trace["subdomain2"] == "investing"
assert trace["domain_group"] == "professional"
def test_gateway_trace_endpoint():
from fastapi.testclient import TestClient
from gateway.api import app
c = TestClient(app, raise_server_exceptions=False)
chat = c.post("/chat/legacy", json={"query": "请用 Python 实现快速排序的迭代版本,并分析其时间与空间复杂度"})
rid = chat.json().get("request_id")
assert rid
t = c.get(f"/traces/{rid}")
assert t.status_code == 200
body = t.json()
assert body["domain"] == "code"
assert "subdomain:algorithm" in " ".join(body["route"])
miss = c.get("/traces/不存在的id")
assert miss.status_code == 404