指南/AI API 参数透传
thinking 和 max_tokens 写进 AI API 请求,为什么没有生效
请求 JSON 里明明写了 thinking、reasoning.effort 或输出上限,回答却和原来一样。这里至少有四种可能:模型按自己的规则解释了参数,字段发错了接口,中转层丢弃或换算了字段,或者客户端最终展示的并非这一笔上游响应。仅凭答案长短,分不清它们。
JSON 里有这个字段,只能证明客户端发了它
调用方常把「序列化成功」「服务器返回 200」和「模型按预期执行」当成同一件事。它们属于不同阶段。
SDK 可以把任意扩展字段放进请求;接口可以接受但忽略不支持的字段;中转站还可以重命名、限幅或删除字段,再调用另一套上游接口。即使字段抵达模型服务,effort 这类控制也未必让每次答案出现肉眼可见的变化。
Anthropic 的官方 OpenAI SDK 兼容说明提供了一个明确例子:该兼容接口会接受多项 OpenAI 风格字段,但 reasoning_effort 等字段被忽略,多数不支持字段不会报错。因而 HTTP 成功与参数执行之间没有必然关系。
同一个意图,在不同接口里不是同一个字段
「多想一点」与「最多生成多少」要分开看。
OpenAI Responses API 使用 reasoning.effort 调节支持该能力模型的推理投入,并用 max_output_tokens 限制一次响应可生成的输出。可用的 effort 档位随模型而异,官方的模型指南要求按目标模型支持范围设置,而不是把一组值套给全部模型。Chat Completions、Responses 与旧 Completions 的字段也不完全相同;迁移时只改路径、不改参数名,字段可能落在错误的协议层。接口边界可继续参照“兼容 OpenAI API”到底兼容到哪一层。
Claude 原生 Messages 把全局投入放在 output_config.effort 中;thinking 控制思考模式。Anthropic 的 Effort 文档强调,effort 影响文字、工具调用与思考的总投入,却不稳定控制可见回答长度。max_tokens 才是思考与正文合计的硬上限。不同 Claude 型号支持的思考模式也不同,旧的手动预算不能无条件搬到新型号,需按当前 Thinking 说明核对。
Gemini 又是另一套配置。Google 的 Thinking 文档说明,新一代模型使用 thinkingLevel,部分旧系列使用 thinkingBudget;两者的支持范围与默认行为取决于具体模型。OpenAI 兼容入口还会把 reasoning_effort 映射到 Gemini 的思考配置,映射关系由兼容层定义,见 Gemini OpenAI compatibility。
字段名称相似,不代表预算口径、默认值和允许档位相同。排查前应固定「供应商原生接口、接口版本、模型 ID」这三个条件。
字段拼写也要原样保存。max_token、max_tokens 与 max_output_tokens 是不同的 JSON 键;接口或 SDK 没有明确记录别名映射时,不能假定它们会自动互换。中转层若接受未知字段又不报错,表面上的请求成功会把拼写问题留到响应阶段才暴露。
effort 是方向,输出上限是边界
推理投入通常是软控制。模型会结合任务难度、工具使用与内部策略决定消耗,低档位不保证每次都少用固定数量的 Token,高档位也不保证正文更长。两次答案字数接近,仍可能用了不同的推理量;答案更长,也可能只是措辞展开。
输出上限则规定不能越过的边界,但它不是目标长度。模型可以正常结束、调用工具、触发停止序列,也可以因思考消耗了可用输出空间而提前停止。判断是否碰到边界,应看响应的终止原因和官方定义的 usage 分类,不能用「少写了几段」代替。
这也是为什么参数比较必须与模型版本绑定。别名切换、模型快照更新或默认 effort 改变,都会让相同请求呈现新行为;这类长期变化见模型版本为什么会漂移。
Token 不是中文字符数
max_tokens、max_output_tokens 和 usage 里的计数单位都是 Token。Token 由对应模型的 tokenizer 划分,可以是一整个词、词的一部分、标点或其他文本片段。中文字符、英文字母、空格、JSON 符号与代码的切分方式不同,不存在跨模型通用的「一个汉字等于几个 Token」。
Google 的 Token 说明和 Anthropic 的 Token counting都要求按实际模型计数;Anthropic 还说明计数接口给出的是估算,最终 usage 可能因系统侧处理而有差异。三家 usage 的包含关系也不同,详见 OpenAI、Claude、Gemini 的 Token 为什么不能直接横向比较。
所以「设置了很大的 Token 上限,却只返回几百个汉字」本身没有诊断价值。上限不是最低产量,汉字数也不是 Token 用量。
反过来,如果同一笔原始响应报告的输出 Token 超过该接口已经接受的硬上限,证据会强很多。下结论前仍要排除字段名用错、客户端把重试结果合并、流式片段重复累计,以及界面把字符数当 Token 数。完成这些核对后,记录可以写成「请求上限与服务报告的单次输出用量不一致」,先不要延伸到模型身份或运营方动机。
中转层可能做了三类处理
兼容服务需要把客户端协议转换成上游协议,常见结果可归为三类:原样转发;按目标模型规则换算字段;对无法支持的字段删除、限幅或使用默认值。服务还可能把模型别名解析到不同上游,使原本有效的字段在新模型上失去意义。
这些处理未必带有欺骗意图。兼容层要面对多家接口差异,无法总是保持一一对应。问题在于调用方是否拿得到明确的支持范围、最终模型标识、终止原因和用量明细。若服务只返回一个统一答案对象,很多差异会被外形遮住。
客户端记录应区分三份对象:业务代码构造的参数、网络层最终发出的请求体、服务返回的原始响应。只有代码参数时,无法证明 SDK 插件或网关没有改写;只有返回对象时,也无法还原发出的字段。
重试与流式拼接会制造另一种错觉
重试中间件可能在超时或限流后重新发起请求。调用方看到的是后一次成功结果,前一次请求的参数、模型选择或中转路由未必相同。若日志只保留逻辑任务 ID,不保留每次尝试的请求 ID,就会把多次调用误认为一笔。
流式响应还会把思考块、工具参数增量、正文增量和结束事件分开发送。客户端若只拼接文字 delta,已经发生的思考也不会出现在最终文本里;连接中断后直接展示已收到的片段,则会把不完整输出误判为模型主动收尾。完整性边界见流式响应返回 200,为什么仍然可能失败。
公开结论应该停在哪里
单次答案变长或变短,只能描述可见输出。字段出现在客户端请求里,只能说明调用方尝试设置它。响应报告了某个模型名,也只能说明服务返回了该标识。
要支持「参数在某一层被丢弃或改写」,还需要该层的请求日志、供应商请求 ID、原始响应或账户侧记录。拿不到这些材料时,更稳妥的表述是:「当前接口没有提供足够证据确认该字段的执行语义」。这比依据一个答案猜测上游行为更接近协议事实。
LinkyMonitor 持续记录请求结构、响应终止原因与用量字段,让参数支持范围发生变化时能回到原始记录核对。