思考过程Token的处理策略各厂商不一:有的返回原文、有的只返回摘要、有的完全不可见,且几乎都按输出费率收费。
三个独立的选择
各厂商每次做出其中一个选择,所有排列组合在市面上都能找到。
是否返回 trace?三种答案:原文返回、模型写摘要后返回、或者根本不返回。Anthropic 的 extended thinking 返回 thinking content blocks;DeepSeek 的 API 在 content 旁边返回 reasoning_content 字段;OpenAI 的推理模型返回摘要而非原始 trace。
是否计费?实际上总是计费,按输出费率,无论你能否读取。这是选择中唯一没有差异化的选项,也是最令开发者惊讶的。
是否在多轮对话间持久化?有些 API 要求你把上一个 trace 原样发回去——或者作为不透明的加密项——才能让下一轮正常工作。另一些则明确禁止,尝试发送会直接报错。
第三个选项最容易写出 bug,因为它在不构建多轮或 tool-calling 循环时完全不可见,一旦构建了就成为整个问题的核心。
两条声明的理由,值得都了解一下,因为它们在对 trace 可见性的态度上方向一致,在信任问题上却背道而驰。
第一条是商业竞争层面的考量。原始 trace 是训练数据:来自前沿模型的超长、高质量推理轨迹,正是蒸馏运行想要的那种输入,推理蒸馏的研究结果已经证明了这一点。以 API 调用的价格发布它们,等于公开了配方。
第二条在 OpenAI 2024 年 9 月关于 o1 的文章中声明过,更加有意思:他们认为,让原始思维链保持不受约束——不训练它礼貌、安全或符合政策——反而能让它作为监控层面更加有用,而展示给用户会给"清洗"它施加压力。所以 trace 保持诚实、保持隐藏,用户看到的是摘要。无论你觉得这个论点是否有说服力,它是一个真实的论点,而非仅仅是商业考量。
判断自己处于哪种状态
发送一个触发 thinking 的请求,然后检查原始响应体,而不是 SDK 的便捷访问器——后者通常只拼接可见文本,刚好会把你正在找的东西隐藏起来。
// 1. 是否有推理 token 计数值?
usage.completion_tokens_details?.reasoning_tokens // OpenAI 的字段形状
usage.output_tokens // Anthropic: thinking
// blocks 被包含在内
// 2. 是否有任何 trace 文本存在?
content.find(b => b.type === "thinking") // Anthropic
message.reasoning_content // DeepSeek 的字段形状
output.filter(i => i.type === "reasoning") // OpenAI Responses
// 3. 可见答案是否远短于你付费的内容?
// reasoning_tokens / completion_tokens > 0.5 是常态,不是 bug。
如果存在计数但没有文本,你已被计费但处于盲区。如果文本存在,判断它是 trace 还是关于 trace 的叙述——摘要会说"我考虑了两个方案",原始 trace 包含失败尝试、算术计算和被放弃的分支。这对调试很重要:摘要告诉你模型说它做了什么,这和实际做了什么不是同一件事,而这个差距正是 faithfulness 文献的主题。
多轮问题
这里它就不再是玩票性质的小问题了。当推理模型调用工具时,对话在工具结果之后继续——而模型需要自己之前的 thinking 才能连贯地继续。各厂商用不兼容的方式解决了这个问题。
Anthropic 对每个 thinking block 返回一个加密签名,要求在你继续 tool-use 轮次时将 block 原样传回;篡改文本请求会被拒绝。OpenAI 的 Responses API 在你允许的情况下将 reasoning 条目保存在服务端,并为不想存储状态的调用者提供加密的 reasoning payload——你持有一个不透明 blob 再交回去。DeepSeek 走了另一条路:其文档说明 reasoning_content 不得包含在下一条请求的 messages 中。
这对于任何写 gateway、agent 框架或者仅仅是个 wrapper 的人来说意味着:对话历史不再是可移植的 role-and-content 配对列表了。它携带了厂商特定的不透明状态,"用不同的模型重放这段对话"这件事悄悄变成了不受支持的操作。现在就为此做设计——把原生 provider 的 blocks 和你自己的规范化历史放在一起,而不是尝试把一个 round-trip 成另一个。
搞错这件事的失败模式值得认识,因为它看起来不像状态 bug。模型完成了工具调用,然后表现得好像忘记了为什么调用它:重复一个步骤、重新读取已经读过的文件、或者产生一个无视工具返回内容的答案。没有任何报错。如果一个 tool-using 推理 agent 在任务中途表现出奇怪的健忘,先检查你在发回去什么,再开始重写 prompt。
盲区的实际代价
你无法审计一个决策。在受监管的工作流中,"模型考虑了申请人的历史"是你被交付的摘要,不是证据。
你无法缓存最贵的部分。Prompt caching 覆盖的是你发送的前缀。Trace 每次都重新生成,所以请求中最贵的 token 恰恰是任何缓存都触碰不到的。
成本预测有厚尾分布。可见答案的长度分布是可预测的。隐藏的 trace 不是——同一个 prompt 可以产生 400 或 14000 个 thinking token,取决于模型觉得这个问题有没有意思。按 p95 预算,而不是均值。
上下文窗口也在买单。在 trace 被携带前进的多轮对话中,它们和可见历史一起占据上下文。一段本来可以轻松容纳的对话会在比预期早好几轮的地方耗尽空间,而症状——在一段正常运作的对话深处出现的截断错误——并不会明显指向原因。
如果你在比较两个模型,一个显示 trace 一个不显示,模型目录会记录推理支持情况以及 trace 是否作为独立属性返回——这是在写代码假设 reasoning_content 会存在之前要检查的字段。
What Is a Reasoning Model? A Definition You Can Check
Streaming Partial Thoughts: UX for a Model That Thinks for 40 Seconds
Can You Trust a Model's Stated Reasoning?