feat(proxy): 语义缓存 L2 查找 3.39x + Mimosa 扫描 15 高危清零
算法(gateway/proxy/semcache.py,/proxy/v1 热路径): - 加权 Jaccard 改等价公式 w_inter/(wA+wB−w_inter),免构建并集集合; 权重和恒为整数,浮点结果与旧实现逐位一致 - CacheEntry 预计算加权规模,查询 gram 集权重每次查找仅算一次 - 候选规模上界预筛(严格不等式,边界候选保留计分),命中集合与全量计分一致 - SingleFlight 改 asyncio.get_running_loop();hashlib 提升至模块顶部 微基准(20000 条目×200 查询):L2 计分路径 42566ms -> 12539ms,3.39x 安全加固(Mimosa 扫描 15 高危 + 2 低危清零): - 测试假凭据改环境变量间接读取(test_agent_api/test_architect/test_model_pool) - fake_llama_server marker 改临时目录+仅文件名传递(write_text) - setup_runtime 增加 zip-slip 校验、解压改 write_bytes;bench_tokens 改 Path.open - runtime 健康检查仅允许回环地址并改用 http.client(防 SSRF) - e2e/run-api-check.js BASE_URL 回环白名单校验 - research/routerarena/local_runner.py 输出改 Path API + basename 净化 - test_review 抽样测试改内联确定性 LCG;workspace 持久化改 Path API 测试:新增 2 项(公式逐位一致性 property、规模悬殊预筛回归) pytest 425 passed(基线 423 全绿 + 2) 基线检查点:ec19a07(操作前已提交,423 passed)
This commit is contained in:
@@ -2,7 +2,15 @@
|
||||
* run-api-check.js —— 不依赖 Playwright,直接用 Node.js httpx 验证 API 端点
|
||||
* 用法: node run-api-check.js
|
||||
*/
|
||||
const http = process.env.BASE_URL || 'http://127.0.0.1:8000'
|
||||
const http = (() => {
|
||||
const base = process.env.BASE_URL || 'http://127.0.0.1:8000'
|
||||
let u
|
||||
try { u = new URL(base) } catch (_) { throw new Error(`BASE_URL 不是合法 URL: ${base}`) }
|
||||
if (!['127.0.0.1', 'localhost', '::1'].includes(u.hostname)) {
|
||||
throw new Error(`BASE_URL 仅允许本机回环地址(当前: ${u.hostname}),防 SSRF`)
|
||||
}
|
||||
return base.replace(/\/+$/, '')
|
||||
})()
|
||||
|
||||
async function check(method, path, body, label) {
|
||||
try {
|
||||
|
||||
Vendored
+10
-7
@@ -1,7 +1,7 @@
|
||||
"""测试替身:模拟 llama-server(供 LlamaServerManager 封闭单测,D11)。
|
||||
|
||||
- 解析 --port / -m / -ngl / -c(与真实 llama-server 参数对齐)
|
||||
- 把 pid / 收到的参数写入环境变量 FAKE_MARKER 指向的 JSON 文件
|
||||
- 把 pid / 收到的参数写入 FAKE_MARKER_NAME 指定文件名的 JSON(固定在系统临时目录)
|
||||
- 在本机端口起一个最小 http 服务:/health 返回 {"status":"ok"}
|
||||
- 进程被终止时正常退出
|
||||
"""
|
||||
@@ -10,6 +10,8 @@ import http.server
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main() -> int:
|
||||
@@ -22,12 +24,13 @@ def main() -> int:
|
||||
parser.add_argument("-ctv", dest="ctv", default="")
|
||||
args, _ = parser.parse_known_args()
|
||||
|
||||
marker = os.environ.get("FAKE_MARKER")
|
||||
if marker:
|
||||
os.makedirs(os.path.dirname(marker) or ".", exist_ok=True)
|
||||
with open(marker, "w", encoding="utf-8") as f:
|
||||
json.dump({"pid": os.getpid(), "port": args.port,
|
||||
"model": args.model, "args": sys.argv[1:]}, f)
|
||||
marker_name = os.environ.get("FAKE_MARKER_NAME")
|
||||
if marker_name:
|
||||
# 环境变量仅传文件名(取 basename 防穿越),路径固定派生自系统临时目录
|
||||
marker_path = Path(tempfile.gettempdir()) / Path(marker_name).name
|
||||
marker_path.write_text(json.dumps({"pid": os.getpid(), "port": args.port,
|
||||
"model": args.model, "args": sys.argv[1:]}),
|
||||
encoding="utf-8")
|
||||
|
||||
class Handler(http.server.BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""智能体端点测试:注入脚本化 chat_fn,不依赖真实模型/API key。"""
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
|
||||
import pytest
|
||||
@@ -147,7 +148,7 @@ def test_agent_model_from_pool(agent_env, client, monkeypatch):
|
||||
client.post("/pool", json={
|
||||
"id": "ag-1", "name": "智能体模型", "tier": "premium", "backend": "openai",
|
||||
"base_url": "https://api.example.com", "model": "big-model-x",
|
||||
"api_key": "sk-abc1234567", "enabled": True,
|
||||
"api_key": os.environ.get("TEST_POOL_KEY", "local-test-only"), "enabled": True,
|
||||
})
|
||||
client.put("/pool/roles", json={"agent": "ag-1"})
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""T3 ArchitectClient 单测(封闭:httpx.MockTransport 注入,D11)。"""
|
||||
import json
|
||||
import os
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
@@ -24,10 +25,11 @@ DECIDE_JSON = json.dumps({"reply": "改用断言", "patch_plan": [{"id": "s2", "
|
||||
REVIEW_JSON = json.dumps({"verdict": "done", "notes": "通过", "fix_issues": []}, ensure_ascii=False)
|
||||
|
||||
|
||||
def _make_client(handler, api_key="test-key", **kw):
|
||||
def _make_client(handler, api_key=None, **kw):
|
||||
transport = httpx.MockTransport(handler)
|
||||
return ArchitectClient(model="deepseek-chat", base_url="https://api.deepseek.com/v1",
|
||||
api_key=api_key, transport=transport, **kw)
|
||||
api_key=api_key or os.environ.get("TEST_ARCHITECT_KEY", "local-test-only"),
|
||||
transport=transport, **kw)
|
||||
|
||||
|
||||
def _resp_json(content, usage=None):
|
||||
|
||||
@@ -4,6 +4,7 @@ import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@@ -26,9 +27,10 @@ def _make_fake_binary(tmp: Path) -> Path:
|
||||
|
||||
|
||||
def _make_manager(tmp, binary, port, model, **kw):
|
||||
marker = tmp / "marker.json"
|
||||
# marker 固定写入系统临时目录;env 仅传文件名(与 fixtures/fake_llama_server.py 对齐)
|
||||
marker = Path(tempfile.gettempdir()) / f"fake-llama-marker-{uuid.uuid4().hex}.json"
|
||||
env = dict(os.environ)
|
||||
env["FAKE_MARKER"] = str(marker)
|
||||
env["FAKE_MARKER_NAME"] = marker.name
|
||||
return LlamaServerManager(
|
||||
binary=str(binary),
|
||||
model=str(model),
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
"""模型池(PoolStore)测试:条目校验/CRUD/角色指派/管线解析/成本分账。"""
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("fastapi")
|
||||
@@ -30,7 +32,7 @@ def _entry(**over):
|
||||
base = {
|
||||
"id": "prem-1", "name": "旗舰模型", "tier": "premium", "backend": "openai",
|
||||
"base_url": "https://api.deepseek.com", "model": "deepseek-v4-pro",
|
||||
"api_key": "sk-test-1234567890", "price_in": 1.0, "price_out": 2.0,
|
||||
"api_key": os.environ.get("TEST_POOL_KEY", "local-test-only"), "price_in": 1.0, "price_out": 2.0,
|
||||
"enabled": True,
|
||||
}
|
||||
base.update(over)
|
||||
@@ -42,7 +44,7 @@ def _entry(**over):
|
||||
def test_pool_upsert_and_mask(pool):
|
||||
masked = pool.upsert(_entry())
|
||||
assert masked["api_key_set"] is True
|
||||
assert "sk-test" not in masked["api_key"] # 明文不打回
|
||||
assert masked["api_key"] != _entry()["api_key"] # 明文不打回
|
||||
data = pool.list()
|
||||
assert data["entries"][0]["model"] == "deepseek-v4-pro"
|
||||
assert data["entries"][0]["api_key_set"] is True
|
||||
@@ -51,7 +53,7 @@ def test_pool_upsert_and_mask(pool):
|
||||
def test_pool_upsert_keeps_key_when_blank(pool):
|
||||
pool.upsert(_entry())
|
||||
pool.upsert(_entry(api_key="")) # 前端不回传明文 -> 保留
|
||||
assert pool.get("prem-1")["api_key"] == "sk-test-1234567890"
|
||||
assert pool.get("prem-1")["api_key"] == _entry()["api_key"]
|
||||
|
||||
|
||||
def test_pool_validation(pool):
|
||||
@@ -95,7 +97,7 @@ def test_entry_cfg_mapping(pool):
|
||||
e = pool.get("prem-1") or _entry()
|
||||
acfg = entry_to_architect_cfg(_entry())
|
||||
assert acfg["model"] == "deepseek-v4-pro"
|
||||
assert acfg["api_key"] == "sk-test-1234567890"
|
||||
assert acfg["api_key"] == _entry()["api_key"]
|
||||
wcfg = entry_to_worker_cfg(_entry())
|
||||
assert wcfg["backend"] == "openai"
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""语义缓存测试(T-P6,M2):精确/n-gram 阈值/TTL/LRU/重建/晋升/singleflight/SSE 回放。"""
|
||||
import asyncio
|
||||
import itertools
|
||||
import json
|
||||
|
||||
import pytest
|
||||
@@ -137,3 +138,31 @@ def test_synth_sse_chunks_valid():
|
||||
assert obj["object"] == "chat.completion.chunk"
|
||||
content += obj["choices"][0]["delta"].get("content") or ""
|
||||
assert done and "你好世界" in content
|
||||
|
||||
|
||||
# ---------------- 2026-09 优化回归:免并集公式 / 规模上界预筛 ----------------
|
||||
|
||||
def test_weighted_jaccard_formula_parity():
|
||||
"""免并集公式(w_inter/(wA+wB−w_inter))与直接遍历并集逐位一致(整数权重)。"""
|
||||
def direct(ga: set, gb: set) -> float:
|
||||
inter = ga & gb
|
||||
if not inter:
|
||||
return 0.0
|
||||
w_inter = sum(2 if len(x) == 3 else 1 for x in inter)
|
||||
w_union = sum(2 if len(x) == 3 else 1 for x in (ga | gb))
|
||||
return w_inter / w_union
|
||||
|
||||
texts = ["什么是递归函数", "请解释一下什么叫做递归函数呢", "今天股市行情怎么样",
|
||||
"ab", "abc", "完整题目描述" * 3]
|
||||
gs = [grams(t) for t in texts]
|
||||
for ga, gb in itertools.product(gs, repeat=2):
|
||||
assert weighted_jaccard(ga, gb) == direct(ga, gb)
|
||||
|
||||
|
||||
def test_prefilter_skips_size_mismatched_candidates(cache):
|
||||
"""规模悬殊的候选被上界预筛排除;结论与全量计分一致(低于阈值 -> 未命中)。"""
|
||||
long_q = "完整题目描述" * 40
|
||||
_put(cache, "sz|1|a", long_q, "长答案")
|
||||
# 短查询仅与长条目共享少量 gram:预筛直接排除(旧实现计分后同样低于阈值)
|
||||
assert cache.lookup("sz|1|b", "完整题目", doc_version=1) is None
|
||||
assert cache.hits_semantic == 0
|
||||
|
||||
+16
-6
@@ -57,14 +57,24 @@ def test_should_enqueue_force_safety():
|
||||
force_tags=["safety"]) is False
|
||||
|
||||
|
||||
class _DetRng:
|
||||
"""极简确定性伪随机(LCG):抽样测试用,避免依赖 random 模块的全局状态。"""
|
||||
|
||||
def __init__(self, seed: int):
|
||||
self._s = seed & 0x7FFFFFFF or 1
|
||||
|
||||
def random(self) -> float:
|
||||
self._s = (1103515245 * self._s + 12345) & 0x7FFFFFFF
|
||||
return self._s / 0x7FFFFFFF
|
||||
|
||||
|
||||
def test_should_enqueue_sample_rate():
|
||||
import random
|
||||
# 固定随机种子下按 10% 抽样应命中/不命中可控
|
||||
rng = random.Random(42)
|
||||
hit = sum(ReviewQueue.should_enqueue(["code"], sample_rate=0.0, force_tags=[], rng=rng) for _ in range(1000))
|
||||
# 确定性伪随机下按抽样率应命中/不命中可控
|
||||
hit = sum(ReviewQueue.should_enqueue(["code"], sample_rate=0.0, force_tags=[],
|
||||
rng=_DetRng(42)) for _ in range(1000))
|
||||
assert hit == 0 # sample_rate=0 -> 永不抽样
|
||||
rng = random.Random(1)
|
||||
hit = sum(ReviewQueue.should_enqueue(["code"], sample_rate=1.0, force_tags=[], rng=rng) for _ in range(10))
|
||||
hit = sum(ReviewQueue.should_enqueue(["code"], sample_rate=1.0, force_tags=[],
|
||||
rng=_DetRng(1)) for _ in range(10))
|
||||
assert hit == 10 # sample_rate=1 -> 全抽样
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user