在 LangChain 中保存推理内容:CoT 模型的补丁
在使用 LangChain 处理 CoT 模型时,开发者常常遇到推理内容丢失的问题——这是模型在给出最终答案前生成的推理关键块。这不是 bug,而是架构决策的结果:像 ChatOpenAI 这样的类严格遵守官方 OpenAI API,忽略了如 reasoning、reasoning_content 或 reasoning_details 等非标准字段。这导致响应时间变慢,用户体验变差,因为用户看不到 AI 的中间思考步骤。下面介绍如何在代码层面修复这个问题,而无需等待官方更新。
LangChain 为什么忽略推理内容
LangChain 被定位为兼容多种大语言模型提供商的通用框架。为了避免碎片化,其核心类(如 ChatOpenAI)仅实现官方 OpenAI Chat Completion API 规范。像 reasoning_content、reasoning 等字段是由 DeepSeek、xAI、vLLM 等引入的扩展。每家提供商使用自己的命名和格式,造成混乱:
- DeepSeek →
reasoning_content - vLLM → 从
reasoning_content切换到reasoning - xAI / OpenRouter → 可能使用
reasoning_details
在 ChatOpenAI 中支持所有变体将需要:
- 持续监控第三方提供商的 API 变更。
- 维护不断增长的字段映射列表。
- 将 ChatOpenAI 变成通用适配器——这违背了其目的。
这就是为什么 LangChain 开发者推荐使用像 ChatDeepSeek 这样的专用类。但大多数提供商(包括 polza.ai、StepFun 等)仍提供 OpenAI 格式的 API——问题就持续存在。
在 base.py 中应用补丁
解决方案是修改 LangChain 源代码中的两个函数:_convert_dict_to_message 和 _convert_delta_to_message_chunk。这些函数负责将原始大语言模型响应转换为框架内使用的 Message 对象。您需要添加推理字段的提取,并将其保存到 additional_kwargs 中。
以下是最小补丁:
def _convert_dict_to_message(_dict):
...
content = _dict.get("content", "") or ""
reasoning = _dict.get("reasoning", "") or "" # added
additional_kwargs: dict = {}
if reasoning: # added
additional_kwargs['reasoning_content'] = reasoning # added
if function_call := _dict.get("function_call"):
additional_kwargs["function_call"] = dict(function_call)
tool_calls = []
...
流式模式类似:
def _convert_delta_to_message_chunk(_dict):
...
id_ = _dict.get("id")
role = cast(str, _dict.get("role"))
content = cast(str, _dict.get("content") or "")
reasoning = cast(str, _dict.get("reasoning", "") or "") # added
additional_kwargs: dict = {}
if reasoning: # added
additional_kwargs['reasoning_content'] = reasoning # added
...
这个补丁:
- 不破坏现有架构。
- 保持与官方 API 的兼容性。
- 为任何返回
reasoning字段的提供商添加对 reasoning_content 的支持。
带流式输出的示例用法
应用补丁后,推理内容可在每个 chunk 的 additional_kwargs 中获取。以下是设置输出的方式:
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
model="stepfun/step-3.5-flash",
openai_api_base="https://api.polza.ai/v1",
openai_api_key="your_api_key",
)
for chunk in model.stream('Hello!'):
if "reasoning_content" in chunk.additional_kwargs:
print(chunk.additional_kwargs['reasoning_content'], end="", flush=True)
else:
print(chunk.content, end="", flush=True)
现在,用户可以实时看到模型的推理过程——这在 CoT 应用中尤其宝贵,其中数据分析、假设比较和逻辑检查等中间步骤至关重要。
替代方法和限制
虽然补丁有效,但有局限性:
- 本地修改:变更仅影响您的 LangChain 副本。包更新时会丢失。
- 字段名不兼容:如果提供商使用
reasoning_details而非reasoning,需调整代码。 - 缺乏标准化:没有提供商间的统一协议,每个情况都需要手动调整。
干净的替代方案:
- 自定义 MessageConverter——创建继承自 BaseMessageConverter 的类,并覆盖解析逻辑。
- 中间件层——在响应到达 LangChain 前拦截提供商的 HTTP 响应,并规范化字段。
- LangChain 表达式语言 (LCEL)——用 RunnableLambda 包装模型调用,在传递给链前提取推理。
然而,这些方法更复杂,需要深入理解框架内部。对于最小可行产品 (MVP) 或快速修复,修补 base.py 是最佳解决方案。
关键要点
- 推理内容丢失不是因为 bug,而是因为 LangChain 严格遵循 OpenAI API。
- 补丁在 additional_kwargs 中添加对
reasoning字段的支持,而不破坏向后兼容性。 - 适用于常规调用和流式调用。
- 每次 LangChain 更新后需要手动重新应用。
- 通过让用户看到模型的思考过程,提升 CoT 应用的用户体验。
— Editorial Team
暂无评论。