免费模型服务器存在「静默漂移」——返回200但内容微妙错误。通过 JSON Schema、响应长度、p95延迟探针检测。
免费模型服务器会漂移。在用户发现之前,这个门控能捕捉到。
免费模型服务器看起来很稳定。然后某个星期二,它就不行了。
端点仍然返回 200。文本看起来仍然合理。输出却悄悄错了。
过去一个月,我一直在为免费模型服务器构建评估套件。最可怕的故障不是超时,而是静默漂移。
披露:本文是 MonkeyCode 产品推广的一部分。
漂移比宕机更糟糕
宕机会叫醒你。漂移不会。
超时触发告警。错误答案被合并进你的应用。用户稍后才注意到,他们怪的是你,不是模型。
免费服务器让这一切更糟。它们在不通知你的情况下改变路由、负载和版本。你需要一个监控输出的门控,而不只是状态码。
这不是天花板测试。这是稳定性测试。
我按照计划对同一端点运行一组固定的探测。每个探测都有一个小巧的、可机器验证的预期。
最小响应长度
就这样。没有 embedding。没有语义相似度。只有脚本可以验证的东西,不需要另一个模型。
产物:drift_gate.py
脚本只使用 Python 标准库。指向任意 chat-completions 风格的端点。
#!/usr/bin/env python3
"""drift_gate.py - regression gate for free model servers."""
import argparse, json, statistics, sys, time, urllib.request
from datetime import datetime, timezone
def call_endpoint(cfg, prompt):
body = {
"model": cfg["model"],
"messages": [{"role": "user", "content": prompt}],
"temperature": 0,
"max_tokens": 300,
}
req = urllib.request.Request(
cfg["endpoint"],
data=json.dumps(body).encode(),
headers={
"Content-Type": "application/json",
"Authorization": "Bearer " + cfg["api_key"],
},
)
start = time.monotonic()
with urllib.request.urlopen(req, timeout=cfg.get("timeout", 30)) as resp:
payload = json.loads(resp.read().decode())
elapsed = time.monotonic() - start
return payload["choices"][0]["message"]["content"], elapsed
def check_output(text, expected):
failures = []
if expected.get("json_schema"):
try:
json.loads(text)
except json.JSONDecodeError:
failures.append("json_invalid")
for required in expected.get("required_substrings", []):
if required not in text:
failures.append("missing:" + required)
if len(text.strip()) < expected.get("min_chars", 10):
failures.append("too_short")
return failures
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--config", required=True)
ap.add_argument("--history", default="drift_history.jsonl")
ap.add_argument("--threshold", type=float, default=0.3)
args = ap.parse_args()
cfg = json.load(open(args.config))
rows = []
for probe in cfg["probes"]:
text, elapsed = call_endpoint(cfg, probe["prompt"])
failures = check_output(text, probe["expected"])
rows.append({
"probe": probe["name"],
"latency": round(elapsed, 2),
"failures": failures,
"ok": not failures,
})
error_rate = 1 - (sum(r["ok"] for r in rows) / len(rows))
latencies = sorted(r["latency"] for r in rows)
p95 = latencies[int(len(latencies) * 0.95) - 1]
score = error_rate + (p95 / cfg.get("latency_budget", 20)) * 0.5
record = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"score": round(score, 3),
"p95": p95,
"rows": rows,
}
with open(args.history, "a") as f:
f.write(json.dumps(record) + "\n")
print(json.dumps(record, indent=2))
if score > args.threshold:
sys.exit(1)
if __name__ == "__main__":
main()
分数是错误率和 p95 延迟的加权混合。它不是质量分数。它是一个漂移信号。
保存为 config.json。将端点、key 和 model 替换为你提供商当前的值。
{
"endpoint": "https://api.example.com/v1/chat/completions",
"api_key": "${FREEMODEL_KEY}",
"model": "free-model-name",
"latency_budget": 20,
"probes": [
{
"name": "json_extract",
"prompt": "Return JSON with keys name and age.",
"expected": {"json_schema": true}
},
{
"name": "short_answer",
"prompt": "What is 2+2?",
"expected": {"required_substrings": ["4"]}
},
{
"name": "empty_guard",
"prompt": "Say nothing.",
"expected": {"min_chars": 1}
}
]
}
empty_guard 探测捕获了一种常见故障模式。一些免费服务器在负载下返回空字符串。人类永远不会注意到。门控会。
这是一个合成示例。你的数字会不同。
{
"timestamp": "2026-08-21T09:00:00Z",
"score": 0.79,
"p95": 18.3,
"rows": [
{"probe": "json_extract", "latency": 2.1, "failures": [], "ok": true},
{"probe": "short_answer", "latency": 18.3, "failures": ["missing:4"], "ok": false},
{"probe": "empty_guard", "latency": 1.2, "failures": [], "ok": true}
]
}
分数超过 0.3 ,门控失败。本例中,模型不再正确回答 2+2。门控以代码 1 退出。
添加一个调度流水线或手动任务。将 key 存为 CI/CD 变量。
drift-gate:
image: python:3.12-slim
variables:
FREEMODEL_KEY: $FREEMODEL_KEY
script:
- python drift_gate.py --config config.json
artifacts:
paths:
- drift_history.jsonl
when: always
产物给了你历史数据。趋势比单次运行更重要。
这种方法在哪失效
这个门控检查稳定性,不是真确性。一个自信的错误答案可以通过。
它需要固定的预期输出。如果你的 prompt 变了,基线也就变了。先重建探测集。
免费服务器有速率限制。在生产环境运行之前添加退避和重试。上面脚本为了清晰省略了它们。
三个探测的 p95 有噪声。要获得真实信号,每次运行至少使用 20 个探测。
不要用这个来比较模型。用它来观察一个端点随时间的变化。
谁不应该用这个
没有固定 prompt 集的团队应该跳过它。答案错误会造成危险的团队也一样。
如果你需要事实准确性,添加人工审核步骤。这个门控是一个绊线,不是一个裁判。
把这个指向你已经在用的任意免费模型服务器。跑一周。第一次探测失败会告诉你比分数更多的东西。
MonkeyCode 的免费模型访问和免费服务器选项与此相关。它们给你一个可以指向的端点。在当前文档中确认请求形状,然后运行门控。
门控不会阻止漂移。它会阻止静默漂移。