模型术语差异:Anthropic与OpenAI同名不同义的技术词汇详解
跨AI供应商迁移后,同一术语(如System prompt、Token)在不同API下含义不同导致的隐蔽bug,逐一解析四个高频踩坑点。
跨AI供应商迁移后,同一术语(如System prompt、Token)在不同API下含义不同导致的隐蔽bug,逐一解析四个高频踩坑点。
面对「迁移后文档过时」这一问题的惯常反应,是打开词表页,把旧提供商的术语替换成新提供商的。这能处理无害的情况——只有一个提供商使用的术语,读起来明显过时,第一次绊倒任何人时就会被当场纠正。
但它对真正耗费时间的情况毫无帮助:两个提供商都用同一个词,但指代的概念足够相似,以至于没人注意到差异。这些词条在全局替换中毫发无损,因为词本身是对的。工程师读到句子,用自己已知的意思去理解,然后写出的代码对应的契约实际上并不成立。词表并没有错;它只是定义不够精确,而迁移暴露了这一点。
有四个值得逐一分析,因为每一个都产生了一类真实的缺陷。
"System prompt"。在 Anthropic Messages API 中,它是与 messages 并列的顶层系统参数。在 OpenAI 的 Chat Completions 中,它是消息数组中带有 role 属性的一个成员。说「把角色设定放在第一条消息中」的内部文档,在一个 API 上是正确的指令,在另一个 API 上则会产生一条校验失败的请求,或者被静默当作用户文本处理。
"Max tokens"。三个标识符对应三个相邻的概念:Messages API 上的 max_tokens,它限制输出且为必填;OpenAI Chat Completions 上的 max_completion_tokens,它取代了推理模型的 max_tokens,同时计入推理 token 和可见 token;以及 Responses API 上的 max_output_tokens。说「将 max tokens 设为 1000 以获得简短回答」的文档,在三个 API 上对应三种不同的预算。这个概念在输出长度页面有完整解析。
"Cache"。一方面是显式的、由开发者控制的产物,你可以为其设置断点,报为 cache_creation_input_tokens 和 cache_read_input_tokens。另一方面是自动行为,你无法控制,报为 prompt_tokens_details.cached_tokens。一份手册说「在 tools 块之前添加一个 cache 断点」,对于没有断点概念的 API 毫无意义;而假设启用缓存是免费的成本模型,对于需要为写入付费的 API 也不成立。
"Stop reason"。这个字段在一处叫 stop_reason,在另一处叫 finish_reason,而且两者的值集合并非简单的重命名——一个枚举了 end_turn、tool_use、pause_turn、model_context_window_exceeded 等值,另一个则是 stop、length、tool_calls、content_filter。说「检查 stop reason 是否正常」的文字在两个 API 上都无法执行。
值得额外补充第五个,因为它是一个单位而非字段,而单位是最难捕获的东西。"Token" 是提供商相关的:每个提供商各自发布自己的分词器,因此一篇在一个提供商侧恰好容纳一千个 token 的文档,在另一个提供商侧可能放不下,且在非拉丁文字脚本上差距会进一步拉大。任何以 token 表示的内部数字——分块大小、截断限制、每请求预算、成本估算——因此都是一个隐含附带了提供商的数字。其词表条目应该在一条中说明这一点,因为另一种做法是让人读到「分块上限为 800 token」并合理地认为这是一个可移植的事实。
贯穿这五个例子的共同模式是:这个词命名了一个在两侧都存在的概念,但标识符、控制面、值集合或单位不同。这恰恰是词表应该记录的内容,也恰恰是一行定义无法承载的东西。
给每个词条五个字段。第一个是你自己的术语——一个你可控的名称,出现在你的正文和代码中,这样文档就不必选一个提供商的词作为规范术语。剩下的字段将其锚定。
# glossary/output-cap.yaml
term: output cap
definition: >
The maximum number of tokens the provider will generate for one
response before truncating. Ours; used in prose and in code.
maps_to:
provider_a: { field: max_tokens, required: true, counts: "visible output only" }
provider_b: { field: max_completion_tokens, required: false,
counts: "visible output plus reasoning tokens" }
differs: >
On provider_b a reasoning-heavy request can consume the whole budget
before emitting visible text, returning empty content with a
length-style finish reason.
used_in:
- services/summarise/config.ts
- docs/runbooks/truncation.md
differs 字段是它真正发挥作用的地方,也是人们最容易留空的字段。如果是空的,要么概念确实相同——这种情况下明确说出来,本身就有价值——要么是没有人认真审视过。used_in 列表是让词表可持续维护的关键:在下一次迁移时它告诉你该打开哪些文件,这就是一个小时内完成和花一周完成的区别。
词表纠正一次但不强制执行,两个季度后又会过时,因为产生混合术语的压力——人们在看提供商的参考页时写文档——并没有消失。廉价的强制执行方式是对文档目录跑 lint 检查。
从每个词表条目的 maps_to 块中构建提供商特定标识符集合。这是自动生成的而非人工维护,因此不会落后于词表。
扫描 Markdown 文件中那些出现在正文中的标识符——行内代码片段和围栏代码块内的不算。在代码中它们是正确的、预期的;在句子中出现则意味着有人在应该用自己术语的地方写了提供商的词。
报错信息要指出应该使用的词表术语,这样修复是明确的而不是一个谜。只会说「禁用词」的 lint 检查会被直接禁用。
允许为真正只涉及某一个提供商 API 表面的页面设置内联转义,并要求转义时指明是哪个提供商。这些页面是存在的,不应该与之对抗。
只在文档变更上跑 CI。这是卫生检查,不是代码发布的关卡——把它当作后者是它最终被禁用的原因。
有两样东西应该放到别处。模型名称、上下文长度和按模型的能力不属于词表条目——它们的变更节奏不同,应该放到一个尽可能自动生成的能力矩阵中;参见迁移能力矩阵和模型参考文档。价格也不属于这里:它们是带有生效日期的数据,把它们写进正文是陈旧数字最终进入业务案例的途径。
属于这里的是你的团队每天说出口的那一小套词汇,在不同提供商之间含义略有不同。大概会有十几个条目。一份十几个条目、每个都是消歧义的词表,价值远高于一百个只是复述任何供应商文档中都能找到的定义词条。
Migrating a Prompt Engineering Team's Internal Documentation
Migrating an Internal Model Capability Matrix
Migrating an Internal Model Zoo Reference Doc Across a Provider Swap