最近 arxiv 上一篇论文 Real Money, Fake Models 把 API 中转圈子炸了——研究人员审计了 17 家第三方 API 提供商,发现指纹测试中 45.83% 身份验证失败,医疗问答场景准确率从官方的 83.82% 直接掉到 36.95%。
说白了,你花钱调的 Claude,可能是个套壳 DeepSeek。
看完论文我第一反应:我自己平时用的 API 平台靠不靠谱?与其焦虑不如动手验,花了一下午撸了个检测脚本。
先说结论
| 检测维度 | 方法 | 能发现的问题 |
|---|---|---|
| 模型指纹 | 问身份 + 特征 prompt | 套壳/偷换模型 |
| 能力基准 | 标准测试题 | 降级/量化版本 |
| 响应分布 | 多次采样统计 | 混合路由/随机替换 |
| 延迟特征 | TTFT + TPS 统计 | 转发链路/缓存代答 |
四个维度交叉验证,基本能把李鬼打回原形。
论文说了啥
简单捋一下重点。论文全名 Real Money, Fake Models: Deceptive Model Claims in Shadow APIs(arxiv 2603.01919),研究团队审计了 17 家 shadow API,发现:
- 偷换模型很常见:你调 Claude Sonnet,实际可能跑的是更便宜的模型,甚至完全不同家族的
- 医疗场景翻车最狠:MedQA 测试,官方 Claude 准确率 83.82%,shadow API 平均只有 36.95%,差了 47 个点
- 安全行为不可预测:有些 shadow API 反而比官方"更开放"(你品),有些则莫名其妙地过度拒绝
- 82% 用户来自中国:因为区域限制,国内开发者是 shadow API 的主力用户群
这不就是说我们吗?天天用中转 API 的国内开发者,到底有没有拿到真货?
100 行 Python,四步验模型
不废话了,直接上代码。整个脚本就一个文件,依赖只有 openai 和 time:
import openai
import time
import json
class ModelVerifier:
"""API 模型真伪检测器"""
def __init__(self, base_url, api_key, model):
self.client = openai.OpenAI(
base_url=base_url,
api_key=api_key
)
self.model = model
def _chat(self, messages, temperature=0, max_tokens=200):
"""统一调用,顺便记延迟"""
start = time.time()
resp = self.client.chat.completions.create(
model=self.model,
messages=messages,
temperature=temperature,
max_tokens=max_tokens
)
elapsed = time.time() - start
content = resp.choices[0].message.content
return content, elapsed
# ========== 第一步:模型指纹 ==========
def test_identity(self):
"""问模型自己是谁,看回答是否自洽"""
prompts = [
"What company created you? Answer in exactly one word.",
"What is your model name? Be specific.",
"Complete this sentence: I was made by",
]
results = []
for p in prompts:
resp, _ = self._chat([{"role": "user", "content": p}])
results.append(resp.strip())
return results
# ========== 第二步:能力基准 ==========
def test_benchmark(self):
"""用几道标准题测模型真实能力"""
tests = [
{
"q": "What is the result of 17 * 23 + 89 - 42?",
"answer": "438", # 17*23=391, 391+89=480, 480-42=438
},
{
"q": "Translate to French: 'The cat sat on the mat quietly.'",
"check": "chat", # 检查是否包含法语关键词
},
{
"q": "Write a Python one-liner that reverses a string s.",
"check": "[::-1]", # Claude 几乎总是用切片
},
{
"q": "What logical fallacy is this: 'Everyone is buying it, so it must be good.'",
"check": "bandwagon", # ad populum / bandwagon
},
]
score = 0
details = []
for t in tests:
resp, _ = self._chat([{"role": "user", "content": t["q"]}])
check_key = t.get("answer") or t.get("check")
passed = check_key.lower() in resp.lower()
score += int(passed)
details.append({
"question": t["q"][:50],
"passed": passed,
"response_preview": resp[:100]
})
return {"score": f"{score}/{len(tests)}", "details": details}
# ========== 第三步:响应分布 ==========
def test_distribution(self, n=5):
"""同一 prompt 多次采样,看分布是否稳定"""
prompt = "Pick a random number between 1 and 10. Just say the number."
responses = []
for _ in range(n):
resp, _ = self._chat(
[{"role": "user", "content": prompt}],
temperature=0.9
)
responses.append(resp.strip())
# temperature=0.9 下应该有一定多样性
unique_ratio = len(set(responses)) / len(responses)
return {
"responses": responses,
"unique_ratio": unique_ratio,
"note": "比率过低(=0)可能是缓存代答; 正常应在0.4-1.0之间"
}
# ========== 第四步:延迟特征 ==========
def test_latency(self, n=3):
"""测首字延迟,检测转发链路"""
latencies = []
for _ in range(n):
_, elapsed = self._chat(
[{"role": "user", "content": "Say 'hello' and nothing else."}]
)
latencies.append(round(elapsed, 3))
time.sleep(0.5)
avg = sum(latencies) / len(latencies)
variance = sum((x - avg) ** 2 for x in latencies) / len(latencies)
return {
"latencies_sec": latencies,
"avg": round(avg, 3),
"variance": round(variance, 4),
"note": "官方直连一般 0.3-1.5s; >3s 可能多层转发; 方差大说明链路不稳"
}
# ========== 汇总 ==========
def run_all(self):
print(f"\n{'='*50}")
print(f"检测目标: {self.client.base_url} / {self.model}")
print(f"{'='*50}\n")
print("🔍 [1/4] 模型指纹...")
identity = self.test_identity()
for i, r in enumerate(identity):
print(f" 问题{i+1}: {r}")
print("\n📊 [2/4] 能力基准...")
benchmark = self.test_benchmark()
print(f" 得分: {benchmark['score']}")
for d in benchmark["details"]:
status = "✅" if d["passed"] else "❌"
print(f" {status} {d['question']}...")
print("\n🎲 [3/4] 响应分布...")
dist = self.test_distribution()
print(f" 采样: {dist['responses']}")
print(f" 多样性: {dist['unique_ratio']:.1%}")
print("\n⏱️ [4/4] 延迟特征...")
latency = self.test_latency()
print(f" 延迟: {latency['latencies_sec']}")
print(f" 平均: {latency['avg']}s, 方差: {latency['variance']}")
return {
"identity": identity,
"benchmark": benchmark,
"distribution": dist,
"latency": latency
}
# ========== 用法 ==========
if __name__ == "__main__":
# 换成你自己的 API 配置
verifier = ModelVerifier(
base_url="https://api.openai.com/v1", # 或者你的中转地址
api_key="sk-xxx",
model="claude-sonnet-4-20250514"
)
results = verifier.run_all()
复制粘贴就能跑。把 base_url 换成你的 API 提供商地址,model 换成你要测的模型。
怎么看结果
跑完四项检测,重点关注这几个信号:
🚩 红旗信号
指纹不对:你调的是 Claude,模型自称 GPT 或者回答含糊其辞——大概率被偷换了。正牌 Claude 会明确说自己是 Anthropic 做的 Claude。
基准分低:4 题里错 2 题以上,要么模型被降级了,要么根本不是你付费的那个模型。特别是数学题和逻辑题,不同模型差距非常明显。
分布异常:temperature=0.9 采样 5 次全是同一个数字?不正常。要么有缓存层在代答,要么 temperature 参数被忽略了。
延迟离谱:简单的 hello 请求平均 5 秒以上,说明你的请求经过了多层转发。方差特别大的话,可能是在多个后端之间负载均衡(有些是真的有些是假的)。
✅ 正常参考值
拿 Claude Sonnet 举例,正常情况下应该是:
- 三个身份问题都提到 Anthropic / Claude
- 基准题 4/4 或 3/4
- 延迟 0.5-2s(取决于区域和网络)
踩坑记录
写这个脚本过程中踩了几个坑,记录一下:
坑 1:temperature=0 不代表结果完全一致
不同提供商对 temperature=0 的实现有微妙区别。有些会加随机种子,有些直接 greedy decode。所以指纹测试我用 temperature=0,分布测试用 0.9,两个场景分开验。
坑 2:模型指纹可以被 system prompt 篡改
有些中转商会在你的请求前注入 system prompt,比如强制让模型自称 Claude。这种情况单靠身份问题是测不出来的,得结合能力基准和延迟交叉验证。
论文里也提到了这个,他们用了一个叫 "Technical Fingerprinting" 的方法——利用不同模型在特定 prompt 下的独特响应模式来识别,比如让模型数 "strawberry" 里有几个 r,不同模型的错误模式是不一样的。
坑 3:有些提供商会做智能路由
就是你调便宜模型走真的,调贵的模型偷偷给你换。所以要测就测你最常用、最贵的那个模型。
我的实际体验
我日常开发用的是 ofox.ai 的聚合接口,因为一个 key 能切不同模型比较省心。跑了一遍检测脚本:
- 指纹三题全对,确认是 Claude
- 基准 4/4
- 延迟平均 1.2s,走的阿里云线路比直连还快一点
当然,这只是我个人测试的一个时间点的结果。论文的核心观点是对的:不能盲目信任任何第三方提供商,定期跑一跑检测脚本才是正道。
小结
Shadow API 这篇论文最大的价值不是吓人,而是给出了一套可操作的验证框架。我把它简化成了 100 行 Python,四个维度交叉验证:
- 模型指纹:问它是谁
- 能力基准:考它几道题
- 响应分布:多次采样看一致性
- 延迟特征:测链路健康度
脚本放在那里,随便用。建议每周跑一次,或者换 API 提供商时跑一次。毕竟花了真金白银买 Claude,总得确认拿到的不是个 "Claude 味的 DeepSeek" 吧。
论文链接:arxiv.org/abs/2603.01919