上一篇我们讲了钱——《你的余额是怎么没的》,一套五步对账法,验的是你有没有被多扣。
这一篇要讲一件更严重的事:就算它一分钱没多扣你,你拿到的那个答案,本身可能已经不是模型的原话了。
这类问题的典型症状,做过生产接入的人应该都眼熟:
- 同一段 Prompt,直连官方结果正常,过了中转站就开始"偷工减料",答得又短又敷衍;
- 你明明设了
max_tokens=4000,它总在几百 token 就"自然结束",finish_reason还写着stop; - 塞了 8 万字文档进去做抽取,它像是只看了开头,后半段的内容问它就开始编;
- 昨天跑通的 Prompt 今天结果就变了,改回去也复现不了;
- 官方模型该有的 function calling、图片识别、长输出,调过来发现是残血的。
绝大多数人的第一反应是"这模型是不是被降智了"。但更常见的真相是:模型没变,是你的请求和它的回答,在中转这一层被人动了手。
这件事的严重性和多扣钱不在一个量级。多扣钱你损失的是余额;请求被篡改,你损失的是业务结果的正确性——知识库问答给出错误答案、代码生成埋下缺陷、数据抽取漏字段、报表跑出错数。而这些账,最后是记在你头上的。
---
学术界已经把这件事量化了(顺便订正一个被用错的数字)
这不是社区里的口耳相传,已经有可查证的一手研究。
2026 年 3 月,CISPA 亥姆霍兹信息安全中心发表《Real Money, Fake Models: Deceptive Model Claims in Shadow APIs》。 研究团队挑了 17 个被引用最多的第三方"影子 API",把它们和其声称封装的官方端点做对照测试,结论是:
- 45.83% 未能通过模型身份指纹验证;
- 性能偏离最高达到 47.21 个百分点;
- 一个对外标注为 Gemini-2.5 的代理,在医学基准测试上只拿到 37 分,而真实官方端点通常在 84 分左右;
- 这 17 个服务出现在 187 篇学术论文的方法章节里,其中 116 篇(62%)被 ACL、CVPR、ICLR 等会议接收。换句话说,一批顶会论文的实验结论,建立在"作者以为自己调用的那个模型"之上。
先做一个必要的订正。 中文社区里流传最广的一句话是"将近一半的中转站存在模型替换",很多文章直接拿这个 45.83% 去指代整个中转站行业。这个读法不成立。 那篇论文的样本是被学术界实际引用过的第三方接口(最流行的一个到 2025 年底累计近 5966 次引用、58,639 个 GitHub star),不是中文语境下面向个人开发者的消费级中转站。样本口径不同,结论不能平移。
我们把这一点专门写出来,是因为这类文章最容易犯的毛病,就是用一个耸动的数字去替代论证。45.83% 是"学术界常用的那 17 个影子 API 里有 45.83% 没通过指纹验证",不是"中转站里有一半掺假"。 真实情况是:这个行业的掺水程度目前没有可靠的全行业抽样数据——但它在技术上不需要任何门槛,这才是你必须自己动手验的理由。
第二篇更值得看的是2026 年 4 月 UCSB、UCSD 等机构团队的《Your Agent Is Mine: Measuring Malicious Intermediary Attacks on the LLM Supply Chain》。 它指出了一个结构性事实,也是这整篇文章的立论基础:
这类路由服务是运行在应用层的代理,对每一个流经的明文 JSON 载荷拥有完全访问权限;而客户端与上游模型之间,没有任何一方强制施加密码学完整性校验。
这句话翻译成大白话:你和模型之间没有任何"防篡改封条"。 中转站要改你的 Prompt、改你的参数、改模型的回答,技术上不需要突破任何东西,因为压根就没有东西需要突破。这不是某一家的道德问题,是这个链路的结构缺陷。
第三篇是 2026 年 7 月的《One Token Is Enough》(行为指纹):大模型在"说一个 1 到 100 之间的随机数"这种任务上并不真的随机,它的输出分布受训练数据与对齐方式影响,稳定到足以当指纹用。论文给的实测:GPT-4o 偏向 42 和 37,Claude Sonnet 5 偏爱 47,Qwen3-Max 三十次全部返回 42。约 120 次请求即可完成一次模型鉴伪,准确率超过 90%,成本约三美分。 这一招下面会用到。
---
黑箱在哪几个位置动手
把一次调用拆开看,中转层有三个下手位置。
位置一:请求侧(你发出去之后,上游收到之前)
- 系统提示词注入——在你的 system prompt 前面追加隐藏指令,最常见的是"尽量简短作答"。效果就是你觉得模型变懒了,其实是有人在替你省 token。
- 参数强制覆盖——无视你传入的
temperature、max_tokens。今年 7 月 Linux.do 社区曝出的一起案例里,开发者实测证明某平台把用户的输入 token 上限强制压到 4K,同时存在系统提示词注入;后续泄露的内部群聊记录显示该团队对此心知肚明。 - 上下文窗口偷偷砍短——官方支持 200K,中转给你砍到 32K 甚至更短。表现是长文档处理到一半断掉、模型"忘了"前面说过的话。上下文越短,中转站付给上游的钱越少,这是一笔非常直接的成本账。
位置二:响应侧(上游返回之后,你收到之前)
- 输出掺水——二次文本加工,插入冗余废话、强行扩充字数;
- 删减与改写——删掉片段,甚至替换关键数据;
- 缓存伪造——重复的 Prompt 不真的请求上游,直接返回历史缓存结果。对平台是纯省钱,对你是调试地狱:你改了 Prompt 结果没变,或者结果莫名其妙"回退"到上一版。
位置三:路由侧(换掉模型本身)
CISPA 那篇论文归纳出三种模式,第三种尤其要注意:
- 静默降级——
claude-opus-4的请求由 sonnet 或 haiku 承接,metadata 里的模型名照原样回填。casual 眼看看不出来,只在长链推理、数学、小语种上掉; - 跨厂商替换——声称的模型换成完全另一家的开源权重模型,模型名字段强制改回你请求的那个。前面医学基准 37 分的"Gemini-2.5"就是这一类,它根本不是 Gemini;
- 部分路由(partial routing)——短上下文给你真模型(成本差不多、指纹测试轻松通过),一旦对话超过某个 token 阈值就切便宜的。
第三种是本文最想强调的一点:论文里约 38% 的替换行为能够躲过简单的第一轮检查。 因为绝大多数人做验证只发一个短请求。你测的那一发,正好落在它"诚实区间"里。
还有一个容易被忽略的细节:部分中转站的篡改是条件触发的——只在长请求、或者命中特定关键词时才启用。日常随手测几发,什么都测不出来。
顺带一句,有些人会去看第三方"中转站评测网站"的打分。要留个心:模型评分服务本身也已经是这条灰产链的一部分,不少评测站 10 到 20 秒就出一个分,真实性无从考证。
---
六步完整性自测(可复现,附代码)
原则先说清楚:不要试图证明"它没作弊",你要做的是让作弊在你的测试里变得不划算。 下面六步,半小时能跑完,任何中转站都适用——包括我们。
第一步:参数回显与截断自测
发一个明确要求长输出的请求,把 max_tokens 设大,然后看两个东西:实际输出长度、finish_reason。
r = client.chat.completions.create(
model="deepseek-v4-flash",
max_tokens=3000,
temperature=0.0,
messages=[{"role": "user", "content": "请连续输出 1 到 800 的整数,用逗号分隔,不要任何解释。"}],
)
txt = r.choices[0].message.content
print("finish_reason:", r.choices[0].finish_reason)
print("完成 token:", r.usage.completion_tokens)
print("实际数到:", txt.strip().split(",")[-1])
判读:数到 800 且 finish_reason=stop,正常。只数到两三百就 stop,而你要的是长输出——max_tokens 大概率在网关层被暗改压成本了。另外 temperature=0 下同一请求跑三次,输出应当高度一致;如果差异很大,说明参数没有真的传到上游。
第二步:暗号探针(测上下文有没有被砍)
这一招比看 usage 更直接、更难糊弄:在超长材料的最末尾埋一个暗号,然后只问暗号。
filler = "这是一段用于填充上下文的测试文本。" * 4000 # 约 8 万字
prompt = filler + "\n【暗号:紫色犀牛在第七行】\n\n请问上面材料里提到的暗号是什么?"
r = client.chat.completions.create(
model="deepseek-v4-flash",
messages=[{"role": "user", "content": prompt}],
)
print("回答:", r.choices[0].message.content)
print("平台计的 prompt_tokens:", r.usage.prompt_tokens)
判读:
- 答得出"紫色犀牛" → 上下文完整送达;
- 答不出或开始编 → 上下文被截断在暗号之前;
- 再看
prompt_tokens:如果它明显小于你本地用分词器算出来的数(tiktoken的o200k_base编码可作近似),说明中转层只转发了前面一段,你为完整上下文付了钱,模型只看到了片段。
第三步:阈值扫描(专治 partial routing)
前两步都只是单点。要抓"过了阈值才换模型",必须分档扫。
for ctx in [2_000, 8_000, 32_000, 64_000, 128_000]: # 按目标模型窗口调整
filler = "测试文本。" * (ctx // 4)
q = filler + "\n【暗号:蓝色海豚在第九行】\n\n材料里的暗号是什么?只回答暗号内容。"
r = client.chat.completions.create(
model="deepseek-v4-flash",
messages=[{"role": "user", "content": q}],
)
ok = "蓝色海豚" in (r.choices[0].message.content or "")
print(f"ctx≈{ctx:>7} 暗号命中={ok} prompt_tokens={r.usage.prompt_tokens}")
再加一道:在每个档位上顺带做一次能力测试(一道需要多步推理的题,最好带随机数、避免命中训练语料原题)。如果短上下文答得漂亮、长上下文能力断崖式下跌,那不是"长文本更难",那是过阈值换模型了。
第四步:行为指纹(比分布,不比单次)
用《One Token Is Enough》的方法。关键纪律:这套方法单次错误率约 10.6%,一次结果说明不了任何问题,必须比分布。
from collections import Counter
c = Counter()
for _ in range(120):
r = client.chat.completions.create(
model="deepseek-v4-flash",
max_tokens=6, temperature=1.0,
messages=[{"role": "user", "content": "说一个 1 到 100 之间的随机数,只回答数字。"}],
)
t = (r.choices[0].message.content or "").strip()
if t.isdigit():
c[int(t)] += 1
print(c.most_common(10))
正确用法是对照:先拿官方直连(或你信任的渠道)跑一份基线分布,再拿中转站跑 120 次,比分布形状。持续偏离基线,就该问问题或者切流量。某个数字 30 次全中这种极端塌缩,通常意味着后面根本不是你以为的那个模型。
顺便说一句这招有多便宜:一次审计一百多个请求,几分钱,挂个定时任务每天跑一遍,一个月不到一块钱。它值得成为你监控体系的一部分,而不是一次性验货。
第五步:缓存伪造检测
同一段 Prompt 连发两次,第二次在末尾加一个随机 nonce(不影响语义的注释)。
import time, uuid
base = "用一句话解释 TCP 三次握手。"
for i, p in enumerate([base, base, base + f" "]):
t0 = time.time()
r = client.chat.completions.create(model="deepseek-v4-flash",
messages=[{"role": "user", "content": p}], temperature=1.0)
print(i, f"{time.time()-t0:.2f}s", (r.choices[0].message.content or "")[:40])
判读:temperature=1.0 下三次结果应当各不相同。如果前两次输出一模一样、第二次延迟明显低于第一次,而加了 nonce 的第三次又变正常——基本可以确认命中缓存了。缓存本身不一定是恶意(有时是为了省钱和加速),但它必须让你知道、并且允许你关掉,否则你的调试结论全是假的。
第六步:功能完整性清单
同一个模型名,官方能力和中转能力可能不是一套。逐项过一遍:
- function calling / tool use:能不能正常返回
tool_calls; - 图片、文件输入(如果模型支持);
- 流式返回:
finish_reason是否正确、有没有中途静默断流; - 失败请求:状态码是否透传,失败的请求扣不扣钱(这条和上一篇的对账法接得上);
- JSON mode / 结构化输出是否真的受约束。
被砍掉的能力通常不会写在页面上,你只能自己撞一遍。
---
为什么这套自测必须由你来做
回到 UCSB/UCSD 那篇论文的结论:这条链路上没有强制的密码学完整性校验。
这意味着一件相当反直觉的事——任何中转站(包括我们)都无法用技术手段自证"我没动过你的请求"。 我可以在这里把承诺写得很漂亮,但你没有办法验证这段文字。这不是话术问题,是能力问题:签名和存证机制根本不存在于当前的 API 生态里。
所以一个诚实的平台该做的,不是让你相信它的承诺,而是给你验它的方法,并且欢迎你去验。这也是这篇文章的写法——上面六步测的是所有人,包括写这篇文章的我们。
---
Yady 在这件事上能给什么、给不了什么
能给的(可自行验证):
- 透明转发:不注入、不改写你的 system prompt;不覆盖你传入的
temperature/max_tokens;不对上游响应做二次文本加工;不启用伪造并发的缓存回复。用第一、第二、第五步就能测。 - 上游是火山引擎方舟官方渠道(
ark.cn-beijing.volces.com),模型名与实际调用一致。这里有一个我们乐意坦白的结构性事实:我们目前只上架了一个模型,反而根本没有"降级"和"跨厂商替换"的操作空间——没有第二个更便宜的模型可以给你换。 - 上下文窗口就是上游原生窗口,我们不做额外截断。用第二、第三步扫。
- 调用明细可导出:每条含时间戳、模型名、prompt / completion token 数、花费、请求状态,可自己加总复算。
- 欢迎 A/B 对照:你完全可以自己在火山方舟开一个官方直连,拿同样的 Prompt 双跑,比输出、比 usage、比指纹分布。这是最硬的验证方式,我们不怕这个对照。
给不了的(说清楚,不含糊):
- 我们无法用密码学手段自证清白。 上面说过了,行业里没有这个机制。我们能做的只是把方法给你、把明细开给你、把对照的门留着。
- 模型广度受限。 火山方舟侧目前只激活了 DeepSeek-V4-Flash 一个模型。如果你的需求是多模型选型、跨厂商比价,现阶段我们不是最优选,这一点我们从第一天就写在公告里。
- 拿不到上游原始结算账单逐条比对。 那是我们与上游之间的商业协议数据,无法向第三方公开。这条和上一篇一致,不改口。
---
最后:这件事很快就不只是"用得值不值"了
公安部令第 176 号《公安机关网络空间安全监督检查办法》将于 10 月 1 日施行,把数据安全与算法安全纳入公安日常检查范围。算法安全这四个字,落到具体场景就包括:你对外提供的 AI 功能,用的到底是哪个模型?输出经过了谁的加工?这条链路你说得清吗?
如果你的产品跑在一个会偷改 Prompt、会截断上下文、会静默换模型的中转层上面,那么当你需要向客户、向合规部门、向检查人员解释"我的输出是怎么来的"时,你会发现你解释不了——因为你自己也不知道。
而且这个"很快",精确到天就是今天。 写这篇稿子的时候是 2026 年 9 月 1 日——往前数,距离公安部令第 176 号 10 月 1 日施行,正好还剩 30 天;往后看,同一天(今天)起,强制性国家标准 GB 45438-2025《网络安全技术 人工智能生成合成内容标识方法》正式施行,文本、图片、音频、视频等 AI 生成内容都要带显式与隐式标识。上海网信办 8 月 31 日刚公布的登记信息也明确:通过 API 调用已备案模型能力、面向公众提供具有舆论属性服务的,同样要开展登记——"我只是调了个 API"不再是不用交代的理由。
一句话:从今天起,"我的输出是怎么来的"这个问题,会有人开始问了。
所以这六步别只当一次性验货。建议做成定时任务,一天跑一遍,留档。 一个月不到一块钱,换来的是一条你说得清的链路。
---
延伸阅读
- 《你的余额是怎么没的:中转站计费黑箱的五步对账法》——这一篇验钱,那一篇验账 → /blog/billing-blackbox-audit.html
- 《2026 中转站横评:Yady vs TeamoRouter vs 硅基流动 vs OpenRouter》 → /blog/relay-benchmark-2026.html
- 《176 号令 10 月 1 日施行倒计时:中转站用户的 30 天合规冲刺清单》 → /blog/176-deadline-30day-action.html
- 《你的公司要过等保 2.0 三级了?先问一句:中转站这一环卡不卡你》 → /blog/dengbao-level3-relay-gate.html