思维链要不要展示
支持深度思考的模型会同时产出两类内容:推理过程(reasoning)和最终答案(content)。它们是两条独立的流,语义上完全不同——一条是模型的自言自语,一条是给用户的结论。
于是界面上出现一个选择题:这条推理过程要不要给用户看。
1. 先看一件确定的事:它会计费
无论你展不展示,推理 token 通常都是单独计价、单独出现在 usage 里的。
所以"不展示以节省成本"是个误解——展示与否不影响账单。不展示只是不显示,不是不生成。
反过来,这条也给了一个可观测性抓手:usage 里把 reasoning 和 cached 拆开的话,你能发现一些原本看不见的问题,比如"content 是空的但在扣费"(通常是 max_tokens 设小了,预算被推理耗光)。
2. 展示的三个收益
用户能判断 AI 有没有理解对,并且能早点打断。 推理过程里如果出现了对用户意图的误读,用户看到就能立刻停掉,而不是等它生成完 800 字才发现方向错了。这是实打实的体验收益——省的不只是时间,还有那次生成的钱。
排查问题有据可依。 用户投诉"答错了"的时候,推理轨迹能告诉你模型当时是怎么理解这个问题的。没有它只能靠猜。
透明度建立信任。 这一点在需要解释性的场景(医疗、金融、法律咨询)里权重更高。用户看到推导过程,比只看到一个结论更容易接受——也更容易发现其中的问题。
3. 不展示的三个理由
它是试错记录,不是论证。 推理过程里有假设、有推翻、有"等等这样不对"。用户看到这些会觉得模型很混乱,而实际上那是正常的工作方式。
它会泄露一些不该展示的东西。 推理里可能出现对用户的判断("这个人可能不懂技术")、对内部信息的引用、或者一些中间假设。直接展示会造成困扰。
它占地方。 一次复杂推理可以比答案本身长好几倍。默认展开的话,用户要滚很久才能看到结论。
4. 怎么展示
4.1 默认折叠,流式时展开
主流做法是:默认收起,推理进行中自动展开,推理结束后收起——除非用户手动切换过(手动之后就尊重用户的选择,不再自动变)。
这个"进行中展开、结束后收起"的组合很关键:它让用户看到"它在思考"(减少等待焦虑),又不会让最终消息被一大段推理淹没。
4.2 两条流分开渲染
后端把模型流按字段拆成两类事件(比如 reasoning 和 token),前端按事件类型分别渲染。这样思考区和正文区是两块独立的 UI,样式、行为、存储策略都不同。
样式上通常把思考区做成灰色、弱化、等宽或斜体,与正文形成明确区分——用户应该一眼看出哪部分不是正式答案。
4.3 滚动行为
思考区在流式期间跟随新内容滚动,但如果用户往上翻了,就停止跟随(和《虚拟列表原理与不定高处理》里追加的那一节是同一条规则)。
内容高度要限制(比如最大高度后滚动),否则长推理会把正文推出视口。
无障碍方面,流式期间给容器加 aria-busy,让读屏软件知道这块内容还在变。
4.4 相邻推理合并
模型可能产出多段推理(中间夹着工具调用)。UI 上通常把连续相邻的推理合并成一个折叠块,中间被正文或工具调用打断时才另起一块——否则一次回答里会出现五六个"思考中"的卡片。
如果要把推理和工具调用放进同一个折叠区("思考与操作"),那是另一种产品选择,适合开发者工具类场景。
5. 存储策略:多数情况不该存
这是实现上最容易被忽略的一节。
推理过程要不要进会话历史? 答案不是"要"或"不要",而是要看你带不带工具。
这里有个硬约束:带工具调用时,DeepSeek 一类的接口要求把上一轮 assistant 消息里的 reasoning_content 原样回传,缺了会直接返回 400;不带工具时回传则会被忽略。也就是说,"不存推理"在工具场景下会把下一轮直接打挂——它不是一个省 token 的优化,而是一个接口契约。
所以正确做法是分三层:
- 存储层存下来(至少服务端存),否则带工具的会话无法续接
- 展示层默认不展示,用户回看历史时只看到答案
- 回传与否按供应商规则决定,由服务端在拼请求时处理,不要让前端猜
至于 token:推理内容确实占上下文,但它是"上一轮的输出",不是"这一轮必须重新送"——多数接口允许你只在需要续接工具调用时带它。省成本的办法是控制历史轮数,不是把字段删掉。
代价是回放时看不到当时的推理。审计场景就单独存一个字段,按需加载。
6. 三个容易踩的坑
6.1 不能当引用
推理过程里提到的数字、事实、文档,都不能作为引用展示给用户。它只是模型的思考轨迹,不是经过检索验证的内容。
引用只来自真实检索到的切片(见《答案要能追溯到出处》)。把推理里的某句话当成带引用的结论,是最危险的一种展示错误——它看起来有依据,实际没有。
6.2 中断时的状态
用户点了停止,推理区可能停在半句话。这时要能正确收尾:标记为已中断,而不是继续显示"思考中…"。
这和《工具调用的七种状态》里说的是同一件事——中断是一个独立维度,不是错误。推理区显示"已中断",正文区可能一个字都没有,这是完全正常的终止状态。
6.3 不同模型字段不一样
不是所有模型、每一轮都有推理内容。字段可能叫 reasoning_content、可能是独立的 part 类型、也可能某些 chunk 里根本没有。
实现时要做存在性判断,不能假设它一定在。聚合网关尤其要注意——有些网关只透传最终字符串,把推理丢掉了。
7. 什么时候应该默认展开
规则是"默认折叠",但有两个例外值得考虑:
调试与开发模式。 给内部一个开关,展开完整推理。这在排查提示词问题时价值极高。
高解释性要求的场景。 如果产品定位就是"让你看到它是怎么想的",那展开是产品特性而不是噪音。但即便如此,也应该用折叠块而不是平铺——给用户一个收起来的选项。
8. 清单
- 推理与正文是两条流,分开渲染
- 默认折叠;流式时展开、结束后收起,尊重用户的手动切换
- 高度限制 + 内部滚动;流式跟随,用户上滚后停止
- 相邻推理合并成一个块
- 历史记录里存推理但不展示、不回传进下一轮上下文;是否回传按供应商规则定(DeepSeek 带 tools 时必须原样回传
reasoning_content,否则 400) - 推理内容不作为引用来源
- 中断态单独显示,不伪装成错误
- 字段做存在性判断,不要假设一定有
- 知道它计费,并在看板里单独看 reasoning token
最后补一句判断标准:展示推理是为了让用户更早发现它理解错了,不是为了证明它对。 抱着后一个目的去设计,会做出一个让用户读一大段自证文字的界面——那不是透明,是噪音。