用官方接口对照排查大模型中转服务
文章提供 Python 脚本,将相同请求分别发送至中转服务与官方 API,对比响应差异。重点排查中转层可能造成的回答质量、计费和工具调用问题。
AI 深度解读
把 OpenAI SDK 的 base_url 换成中转地址,接入通常很快。但接口能返回文本,不代表模型、计费、参数和工具调用都按约定工作。作者给出了一套检查办法:把同一份请求分别发给中转和官方 API,观察差异。
这套方法适合给准备接入 AI 功能的团队做基础验收。它能暴露一些具体故障,却不能证明中转绝对可信。还有两个前提要先交代:原文的异常输出来自作者编写的模拟服务,所附「完整脚本」也在末尾被截断了。
为什么要拿官方 API 做对照
单独看一次回答,很难判断中转有没有问题。答案差,可能是模型能力有限;响应慢,可能是网络波动;工具调用失败,也可能是请求写错了。
作者的办法是固定模型和请求内容,同时测试两个端点。官方 API 提供一个参照,帮助缩小排查范围。准备项包括中转地址、中转密钥,以及能调用同一模型的官方密钥。原文称,基础检查只需几美分调用费用,但没有给出完整费用明细。
运行环境通过 pip install openai 安装 SDK,再配置 RELAY_BASE_URL、RELAY_API_KEY、OFFICIAL_API_KEY 和 MODEL=gpt-4.1-mini,最后执行 python relay_check.py。脚本还允许用 OFFICIAL_BASE_URL 覆盖官方地址,默认值为 https://api.openai.com/v1。做基准测试时,要确认这个变量指向预期端点。
对照测试能发现行为差异,不能直接证明差异背后的原因。
比如返回的 model 不符合预期,应该向服务商核实;但模型名完全一致,也不足以证明后端确实调用了对应模型。
模型名、token 和参数,哪些地方容易露馅
第一项检查是读取响应的 model 字段。请求 gpt-4.1-mini,却收到 gpt-4o-mini-2024-07-18,显然需要追问。不过,官方也可能把模型别名解析成带日期的快照,如 gpt-4.1-mini-2025-04-14,不能只因多了日期就判异常。
更大的限制是,中转可以自行填写这个字段。至于直接问模型「你是什么模型」,作者不建议采用,因为模型自述并不可靠。
第二项检查是输入 token。脚本用相同提示词、temperature=0 和 max_tokens=60,比较 usage.prompt_tokens 与 usage.completion_tokens。示例提示词把同一句英文重复 40 次,尽量固定输入。
在同一模型、同一消息及相同处理方式下,输入 token 数应该接近一致。如果中转长期多报 20%–30%,作者认为可能存在计费虚报,或中转偷偷加入了系统提示词。两种情况都需要解释,但仅凭计数还分不清是哪一种。输出 token 会随生成结果变化,需要多次比较。
第三项检查利用 max_tokens 制造明显的输出差异:
r = client.chat.completions.create(
model=MODEL,
messages=[{"role": "user", "content": "Count from 1 to 200, separated by spaces."}],
max_tokens=10,
)
print(r.choices[0].finish_reason, r.usage.completion_tokens)
关键是 max_tokens=10:要求数到 200,却只允许很短的输出。按原文的预期,官方会提前结束,返回 finish_reason="length";如果中转完整输出全部数字,并返回 "stop",就说明限制没有按预期生效。
至于 temperature、seed、response_format 和 logprobs,也应分别设计能观察到效果的请求。一个参数失效足以触发排查,但不能直接推定其他参数全部失效。
工具调用要测第二轮,长上下文要测实际长度
对 Agent 来说,返回一句正常文本只是起点。中转可能把工具调用写成普通文本,也可能返回无法解析的 JSON,或者第一轮正常,收到工具结果后就报错。
因此,作者要求测试完整往返:取得结构化 tool_calls,解析参数,回传工具结果,再让模型继续回答。
下面是原文的核心流程,执行前需要先定义 TOOLS:
msgs = [{"role": "user", "content": "What's the weather in Lisbon? Use the tool."}]
r = client.chat.completions.create(model=MODEL, messages=msgs, tools=TOOLS, temperature=0)
call = r.choices[0].message.tool_calls[0]
args = json.loads(call.function.arguments) # 必须能解析
msgs += [r.choices[0].message,
{"role": "tool", "tool_call_id": call.id, "content": json.dumps({"temp_c": 19, "sky": "clear"})}]
r2 = client.chat.completions.create(model=MODEL, messages=msgs, tools=TOOLS)
print(r2.choices[0].message.content) # 应提到返回的温度
要盯住的是 tool_call_id:回传结果必须关联前一轮调用。原文定义的工具名为 get_weather,参数要求包含字符串 city;这里没有查询真实天气,而是返回 temp_c=19、sky="clear",检查模型能否使用结果。
较长脚本还设置了 tool_choice="auto",并处理了没有工具调用、JSON 解析失败的情况。但最终只检查答案是否包含 "19",这个断言偏宽松;解析 JSON 也不等于验证参数符合工具要求。依赖连续调用的应用,还要扩展到两个工具串联。
长上下文测试则是藏一个随机口令:生成 8 位大写字母和数字,放进 600 段填充文本的第 100–500 段之间,再以 max_tokens=20、temperature=0 要求模型找回。脚本注释称输入约 6,000 个英文词。
如果官方成功、中转失败,截断输入或更换模型都是排查方向,但不能据此定案。更实用的做法,是把输入扩大到业务实际使用的长度;通过这一档测试,并不能证明整个上下文窗口都可用。
延迟要看首个有效文本,也要看使用时段
作者建议通过 stream=True 测首 token 延迟:请求前用 time.perf_counter() 计时,收到第一个非空 delta.content 时停止,重复几次取中位数。
这测的是用户等到首段文本的时间,不是完整回答耗时。对于先返回工具调用的请求,也需要另行确定测量口径。
原文认为,额外几百毫秒可能来自转发,多出几秒或波动很大则可能涉及排队、绕路。这里属于经验判断,没有跨地区、跨服务商的对照数据。国内团队尤其要固定测试环境,并在用户活跃时段重复测量,避免把不同网络条件混在一起比较。
作者也强调,一次失败不应直接判服务商有问题,要跨天重复观察。
我的判断
这套方案最有价值的地方,是把「兼容 OpenAI」拆成了可以逐项验证的行为。相比只让模型答几道题,它更贴近应用会依赖的接口约定。
但原文没有提供真实中转服务的测评结果。样例中的 token 数为中转 473、官方 364,输出限制为中转 ('stop', 200)、官方 ('length', 10),延迟为 0.40s 和 0.00s,这些都来自作者自建的 mock,不能用于判断任何真实服务商。
另外,所谓完整脚本在 def check_latency(runs=3): 后的文档字符串处截断,缺少函数实现和执行入口,直接复制无法运行。它目前更像检查方案与代码片段集合。
作者披露自己参与开发 byok.icu,并称其提供 7 个 Agent 测试用例、链式调用、错误恢复、100 分评分及 Anthropic 兼容端点检查。这些是作者的产品说明,原文没有提供独立验证。
我会把这套检查当作接入前的基础验收,再补上业务自己的回归用例。
中转还会接触发送给它的提示词和请求密钥。原文建议使用可撤销、有消费上限的密钥,停用后轮换;这也是选中转时需要计入的成本。
能拿来做什么
- 给 Node 或其他后端中的中转配置建立一组固定验收请求,记录
model、输入 token 数、finish_reason和首段文本延迟,换线路后重复比较。 - 如果 AI 编程流程依赖工具调用,就验证完整的「调用工具、回传结果、继续回答」过程;依赖连续工具调用时,再加入两工具串联用例。
- 用接近日常代码和文档长度的输入测试口令找回,并逐步调整藏入位置,观察中转与官方的差异,避免只测短问答。
- 使用前先补齐被截断的脚本,明确断言和失败记录方式;把异常当作复测与询问服务商的依据,不把单次结果当作最终结论。