Yady API
不是模型变笨了,是你的请求被动过手脚:中转层完整性自测六步法

不是模型变笨了,是你的请求被动过手脚:中转层完整性自测六步法

次阅读

浏览、点赞、踩均为真实统计:同一访客对同一篇文章每天只计一次浏览,点赞与踩一人一票,可改票也可撤回。

← 返回技术博客

上一篇我们讲了钱——《你的余额是怎么没的》,一套五步对账法,验的是你有没有被多扣

配图 1
配图 1

这一篇要讲一件更严重的事:就算它一分钱没多扣你,你拿到的那个答案,本身可能已经不是模型的原话了。

这类问题的典型症状,做过生产接入的人应该都眼熟:

  • 同一段 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。
  • 参数强制覆盖——无视你传入的 temperaturemax_tokens。今年 7 月 Linux.do 社区曝出的一起案例里,开发者实测证明某平台把用户的输入 token 上限强制压到 4K,同时存在系统提示词注入;后续泄露的内部群聊记录显示该团队对此心知肚明。
  • 上下文窗口偷偷砍短——官方支持 200K,中转给你砍到 32K 甚至更短。表现是长文档处理到一半断掉、模型"忘了"前面说过的话。上下文越短,中转站付给上游的钱越少,这是一笔非常直接的成本账。

位置二:响应侧(上游返回之后,你收到之前)

  • 输出掺水——二次文本加工,插入冗余废话、强行扩充字数;
  • 删减与改写——删掉片段,甚至替换关键数据;
  • 缓存伪造——重复的 Prompt 不真的请求上游,直接返回历史缓存结果。对平台是纯省钱,对你是调试地狱:你改了 Prompt 结果没变,或者结果莫名其妙"回退"到上一版。

位置三:路由侧(换掉模型本身)

CISPA 那篇论文归纳出三种模式,第三种尤其要注意:

  1. 静默降级——claude-opus-4 的请求由 sonnet 或 haiku 承接,metadata 里的模型名照原样回填。casual 眼看看不出来,只在长链推理、数学、小语种上掉;
  2. 跨厂商替换——声称的模型换成完全另一家的开源权重模型,模型名字段强制改回你请求的那个。前面医学基准 37 分的"Gemini-2.5"就是这一类,它根本不是 Gemini;
  3. 部分路由(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:如果它明显小于你本地用分词器算出来的数(tiktokeno200k_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"不再是不用交代的理由。

一句话:从今天起,"我的输出是怎么来的"这个问题,会有人开始问了。

所以这六步别只当一次性验货。建议做成定时任务,一天跑一遍,留档。 一个月不到一块钱,换来的是一条你说得清的链路。

---

延伸阅读

配图 2
配图 2