Dify 深度思考模型 reasoning_content 获取与前端渲染实践 做 Dify 应用接深度思考模型的时候很多人都会卡在同一个地方模型明明已经“想”了一大段可前端拿到手的始终只有最终答案那一大段关键的 reasoning_content 就像被中间层偷偷丢掉了一样。我在 Dify 1.11.4 社区版上踩完这个坑把配置 LLM 深度思考、拿到 reasoning_content 并支持前端渲染的完整链路整理了出来这篇就把它讲透。这篇文章适合正在用 Dify 搭建对话应用、工作流应用并且希望把模型的思考过程呈现给终端用户的开发者。我会按照“方案选型 - Dify 侧配置 - 前端渲染 - 问题排查”四个部分来写每一步都会给出可以直接落地的配置和代码尽量少说废话。1. 方案选型获取 reasoning_content 的三条路线1.1 先搞清楚深度思考模型的输出结构深度思考类模型比如 DeepSeek-Reasoner、Qwen 的 reasoner 系列、Kimi 思考版的响应体里核心内容通常被拆成两个字段reasoning_content模型内部的思考过程也就是我们平时说的“深度思考”内容content模型给出的最终可展示答案。两者在同一个 API 响应里返回但在 Dify 的默认处理流程中reasoning_content并不会自动暴露给前端。原因很简单Dify 的对话协议里没有专门承载这个字段的通道它的answer字段只绑定最终 content。很多用户查了半天日志发现模型节点里其实有这个字段但不是被丢弃就是保存在了不好取的地方。另外要注意不同模型厂商对思考过程的处理方式不一样。有的是独立字段有的是直接拼在 content 里用特定标记包裹比如早期的...let me think...格式。你在动手配置前最好先用官方 API 文档确认一下自己模型的输出结构后面所有方案都依赖这一步。1.2 路线对比透传、HTTP 直连、自建代理我实际验证下来想把reasoning_content从 Dify 里安全地拿到前端基本有三条路可以走路线 A让 Dify 模型层透传思考内容。Dify 在 1.11.x 版本的模型供应商配置里对 OpenAI-API-compatible 类的兼容供应商增加了“启用推理内容”的开关。开启后Dify 会尝试把模型响应里的reasoning_content解析并保留到 LLM 节点的输出变量中多轮对话时也能尽量把它回传给上游模型。这条路线对模型供应商有要求必须是走 OpenAI 兼容协议的推理模型而且你的模型供应商地址本身不能把该字段过滤掉。路线 B在工作流里用 HTTP 请求节点直接调用模型 API。如果模型比较特殊或者 Dify 就是没透传那就绕过 Dify 的模型管理直接在编排里加一个 HTTP 请求节点把用户问题发给模型 API再从响应体里手动拆出reasoning_content和content。这种方案最灵活适应任何模型但你要自己管 API Key多轮上下文也得自己拼接适合做单轮分析、一次性问答类应用。路线 C自建一个后端代理。生产环境下我更推荐这条前端不直接对接 Dify而是接你自己的后端服务后端统一负责调用 Dify 工作流或模型 API解析出reasoning_content后通过 SSE 把“思考中”和“回答中”两个通道分别推给前端。代价是要多写一套后端逻辑但可维护性和可控性是最好的。我在这次 1.11.4 实测里同时用了路线 A 和路线 B下面分别说明操作细节。你可以根据自己的场景选不一定非要上自建代理。2. Dify 1.11.4 配置 LLM 深度思考的实操步骤2.1 模型供应商侧启用推理内容支持先说路线 A 的标准操作。登录 Dify 管理后台进入“设置 - 模型供应商”添加一个新的 OpenAI-API-compatible 供应商。需要填的内容包括 API Base 地址、API Key、模型名称比如deepseek-reasoner或者qwen3-reasoner这些按你自己的模型服务填就行。关键的一步在高级配置里找到 “Enable Reasoning Content” 或者叫“支持推理内容”的开关必须打开。Dify 1.11.4 的社区版里这个开关在你新增兼容供应商的时候就能看到。如果没有这个选项通常是供应商配置类型不对或者版本较老可以先升级再试或者直接跳到 2.2 的 HTTP 节点兜底方案。保存之后到应用编排里选择刚才配置的模型。这里有两个地方会影响最终效果模型参数比如 temperature、max_tokens建议把 max_tokens 调高一点因为思考过程和最终答案都在这同一个模型里产出token 不够容易被截断。调试运行在右上角“运行”窗口发起一次测试然后查看 LLM 节点的输出明细。如果配置成功你会看到输出里出现reasoning_content字段如果看不到就要检查模型供应商是否真的返回了这个字段。有个容易忽略的点如果你的模型走的是自建网关或第三方中转网关可能会主动移除reasoning_content即使源模型返回了。这种情况其实很常见建议先在网关层打开字段透传或者在模型响应日志里确认字段确实存在再回过头排查 Dify 配置。2.2 工作流里把思考内容变成结构化输出Dify 的chat-messages接口标准事件流里没有专门放reasoning_content的通道所以我更推荐用工作流的方式把思考内容变成一个显式的输出变量。这个方案不依赖自建后端前端拿到的是一个干净的结构化 JSON。具体做法是在工作流里LLM 节点成功返回后接一个“代码执行”节点用 Python 把 reasoning 和 answer 拼成一个 JSON 字符串。示例代码import json def main(thinking: str, answer: str) - dict: return { result: json.dumps( {thinking: thinking, answer: answer}, ensure_asciiFalse ) }代码节点的输入变量thinking和answer分别取自上游 LLM 节点的输出。如果你的 LLM 节点确实没有暴露reasoning_content那就在上游再加一个 HTTP 请求节点自己调用模型 API 获取并把响应体里的两个字段分别存入变量即可。下面是一个 HTTP 请求节点的配置参考请求方法: POST URL: https://api.deepseek.com/chat/completions 请求头: Authorization: Bearer sk-xxxx Content-Type: application/json 请求体: { model: deepseek-reasoner, messages: [ {role: user, content: {{#sys.query#}}} ], stream: false }注意这里stream设置为 false是为了让响应一次性返回HTTP 节点才好在后续步骤里取值。响应体中的choices[0].message.reasoning_content和choices[0].message.content分别就是思考内容和最终答案。拿到这两个值之后再通过代码节点包装成 JSON 字符串放到工作流的“输出变量”里。前端调用workflows/run接口时就可以在返回结果的outputs字段中直接取到类似{thinking: ..., answer: ...}的数据。这一步是整篇文章的核心也是最容易被忽略的关键点Dify 能力本身不缺缺的是把reasoning_content放到正确传递通道里的思路。2.3 thinking 模式下必须回传 reasoning_content否则报 400这是我在实践里踩得最深的一个坑。现在部分模型 API特别是 DeepSeek对思考模式有硬性要求当上下文里存在上一轮 assistant 的消息并且该消息带有reasoning_content字段时下一轮请求必须把原来的 reasoning 内容原样回传。如果你在拼接多轮对话时把这个字段丢了API 会直接抛出类似下面的错误the reasoning_content in the thinking mode must be passed back to the api.这个问题的根源是模型厂商为了保证思维链的连贯性要求思考内容随上下文一起提交。OpenAI 的规范里没有这个要求所以很多人第一次遇到会懵。如果你用的是 Dify 原生的模型管理来做多轮对话这个问题 Dify 内部通常会处理好但如果你按 2.2 的路线 B 自己用 HTTP 请求节点拼消息就一定要把上一轮 assistant 消息里的reasoning_content缓存到会话变量里并在下一轮请求时放回 messages 数组的 assistant 消息中。简单来说多轮上下文结构应该是{ model: deepseek-reasoner, messages: [ {role: user, content: 第一轮问题}, { role: assistant, content: 第一轮最终答案, reasoning_content: 第一轮思考内容 }, {role: user, content: 第二轮问题} ] }如果不缓存、不传回等待你的就是 400。2.4 用命令行验证模型到底返回了什么在配置过程中我建议你先用一条 curl 直接请求模型 API确认输出结构这样能快速把问题定位在模型侧还是 Dify 侧。命令示例curl https://api.deepseek.com/chat/completions \ -H Authorization: Bearer $DEEPSEEK_API_KEY \ -H Content-Type: application/json \ -d {model:deepseek-reasoner,messages:[{role:user,content:请解释一下什么是递归}],stream:false}返回体里找choices[0].message你会看到reasoning_content和content两个字段。如果这条命令返回正常说明模型 API 没问题后面就是 Dify 配置和通道的问题如果这条命令本身就看不到 reasoning 字段那要检查的则是模型选择或者账户权限。这个验证步骤不到一分钟但能节省后面排查问题的绝大部分时间。3. 前端渲染 reasoning_content 的落地细节3.1 流式协议设计把 thinking 和 answer 分成两个通道当思考内容比较长的时候用户等待答案的过程可能长达十几秒。如果一次性等全部返回再渲染体验非常糟糕所以生产环境一般会走流式。我推荐的自建后端代理输出 SSE 协议格式如下event: thinking data: {content:正在分析问题的背景...} event: answer data: {content:这是一个典型的递归问题...} event: done data: {}前面说自己处理多轮上下文比较麻烦但如果你的场景只是单轮问答加深度思考展示那么后端代理只需要做三件事接收前端请求、调用 Dify 工作流或模型 API、把解析后的字段分别塞进 SSE 事件。前端也不需要关心 Dify 协议的细节。如果你不想写后端那么建议用 2.2 的方式通过workflows/run拿到 JSON 后一次性渲染。到底选哪种核心看你对首字延迟的容忍度能等就简单化不能等就上流式。3.2 前端思考面板组件默认折叠支持展开拿到 thinking 内容后前端界面最好不要把它和最终答案平铺展示。思考过程往往又长又杂用户真正需要的是“知道模型想过”而不是必须读完。我的建议是设计一个可折叠面板思考过程中面板显示“正在思考…”状态并实时展示增量文本思考结束后面板自动折叠只显示“已展示思考过程”和耗时用户点击展开加载并渲染完整的 Markdown 内容。一个 React 版本的简化实现如下import { useState } from react; import ReactMarkdown from react-markdown; function ReasoningPanel({ content, status }) { const [collapsed, setCollapsed] useState(true); return ( div classNamereasoning-panel div classNamereasoning-header onClick{() setCollapsed((v) !v)} span{status thinking ? 正在思考… : 已展示思考过程}/span /div {!collapsed ( div classNamereasoning-content ReactMarkdown{content}/ReactMarkdown /div )} /div ); }如果思考内容是以流式片段到达的建议在父组件里维护一个累积字符串每次新增片段只更新 state不要用重新赋值覆盖。渲染层要做增量追加而不是整块替换否则长文本场景下会明显卡顿。3.3 Markdown 安全与长文本性能优化reasoning_content是模型自由生成的文本里面可能出现 Markdown、代码块、数学公式甚至 HTML 片段。为了避免 XSS 隐患不要直接用dangerouslySetInnerHTML去渲染。我这边用的是 ReactMarkdown 配合 rehype 生态做代码高亮和公式支持默认转义机制对安全更友好。长文本性能方面有几个实测有效的做法设置一个阈值比如内容大于 2000 字符时默认只渲染前后各 500 字符中间用“点击展开查看完整思考过程”代替避免初始渲染卡顿代码块高亮放在懒加载逻辑里用户展开面板后再初始化高亮思考过程中只是实时更新文本节点不要每次都走 Markdown 全量解析等思考结束后再渲染正式视图。这些细节看起来小但思考内容往往是普通答案的好几倍长度不做优化页面会明显吃帧。4. 常见问题排查与避坑速查4.1 模型日志里有 reasoning_content但前端拿不到这个问题出现频率最高。先确认你的前端是走chat-messages还是workflows/run。如果走的是chat-messages标准事件流里确实没有对应字段answer只包含最终回答。解决办法有两个切换成工作流输出变量的方式或者自建后端代理解析后重新推送。如果你还想继续用chat-messages那你必须自己做一轮中间层转换把 Dify 的流式响应拿到后端解析再向前端转发自定义事件。4.2 多轮对话报 400reasoning_content must be passed back to the api其实就是 2.3 节讲的回传问题。排查步骤先检查请求体里上一轮 assistant 消息是否带了reasoning_content再检查是不是自己在上游 HTTP 节点里只在第一次请求时获取了它后续轮次没有从会话变量里取出来拼接。解决方式是在会话结构里用一个字段专门缓存最近一轮的 reasoning 内容每次请求前拼进去。4.3 思考内容出现乱码或者换行丢失大部分情况是 SSE 解析里对 JSON 字符串的转义处理不完整。当reasoning_content中包含换行符时后端在拼接事件数据时必须用JSON.stringify序列化不能直接手工拼字符串。前端解析时也不要用简单split(\n)就结束要按 SSE 规范处理data:前缀并在JSON.parse前去掉多余的空白字符。4.4 一个容易被忽略的体验问题思考内容和最终答案属于两种不同的信息形态交互设计上建议明确区分思考过程可以用浅色背景、折叠样式、非等宽字体答案用正常阅读布局。不要把两者混在同一个气泡里。用户如果只需要答案折叠状态不会打扰他如果他想验证模型逻辑展开就能看到完整推理链路。这个交互细节做得好整个应用的“专业感”会明显不一样。最后再说一个我个人的体会把 reasoning_content 真正跑通之后最大的收获不是多了一个字段而是整个应用的可解释性变强了。用户能看到模型先分析、再回答信任感会高很多。遇到问题不要急着怪 Dify先按照“模型接口验证 - Dify 配置验证 - 前端通道验证”的顺序排查大部分坑都能快速定位。这套链路在你后续接入更多推理模型时也能复用值得认真跑一遍。