Guardando el contenido de razonamiento en LangChain: Parche para modelos CoT
Al trabajar con modelos CoT a través de LangChain, los desarrolladores se encuentran con la pérdida del contenido de razonamiento: el bloque clave de razonamiento generado por el modelo antes de la respuesta final. Esto no es un error, sino una consecuencia de una decisión arquitectónica: clases como ChatOpenAI se adhieren estrictamente a la API oficial de OpenAI, ignorando campos no estándar como reasoning, reasoning_content o reasoning_details. El resultado son tiempos de respuesta más lentos y una peor UX, ya que los usuarios no ven los pasos intermedios de pensamiento de la IA. Aquí te explico cómo solucionarlo a nivel de código sin esperar una actualización oficial.
Por qué LangChain ignora el contenido de razonamiento
LangChain se posiciona como un framework universal compatible con una amplia gama de proveedores de LLM. Para evitar fragmentación, sus clases principales (como ChatOpenAI) implementan solo la especificación oficial de la API de Chat Completion de OpenAI. Campos como reasoning_content, reasoning y similares son extensiones introducidas por DeepSeek, xAI, vLLM y otros. Cada proveedor usa su propia nomenclatura y formato, lo que genera caos:
- DeepSeek →
reasoning_content - vLLM → cambió de
reasoning_contentareasoning - xAI / OpenRouter → puede usar
reasoning_details
Soportar todas las variantes en ChatOpenAI requeriría:
- Monitoreo constante de cambios en las API de proveedores terceros.
- Mantener una lista creciente de mapeos de campos.
- Convertir ChatOpenAI en un adaptador universal, lo cual va en contra de su propósito.
Por eso, los desarrolladores de LangChain recomiendan usar clases especializadas como ChatDeepSeek. Pero la mayoría de proveedores (incluyendo polza.ai, StepFun y otros) aún ofrecen API en formato OpenAI, y ahí persiste el problema.
Cómo aplicar el parche en base.py
La solución es modificar dos funciones en el código fuente de LangChain: _convert_dict_to_message y _convert_delta_to_message_chunk. Estas se encargan de convertir las respuestas crudas de LLM en objetos Message usados dentro del framework. Debes agregar la extracción del campo de razonamiento y guardarlo en additional_kwargs.
Aquí va el parche mínimo:
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 = []
...
De manera similar para el modo streaming:
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
...
Este parche:
- No rompe la arquitectura existente.
- Preserva la compatibilidad con la API oficial.
- Agrega soporte para reasoning_content de cualquier proveedor que devuelva un campo
reasoning.
Ejemplo de uso con salida en streaming
Después de aplicar el parche, el contenido de razonamiento queda disponible en additional_kwargs para cada chunk. Así es como configurar la salida:
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)
Ahora los usuarios pueden ver el proceso de razonamiento del modelo en tiempo real, algo especialmente valioso en apps CoT, donde los pasos intermedios como análisis de datos, comparación de hipótesis y verificaciones lógicas son clave.
Enfoques alternativos y limitaciones
Aunque el parche es efectivo, tiene limitaciones:
- Modificación local: Los cambios solo afectan tu copia de LangChain. Se perderán al actualizar el paquete.
- Incompatibilidad de nombres de campos: Si un proveedor usa
reasoning_detailsen lugar dereasoning, tendrás que adaptar el código. - Falta de estandarización: Sin un acuerdo unificado entre proveedores, cada caso requiere ajustes manuales.
Alternativas limpias:
- Custom MessageConverter: crea tu propia clase que herede de BaseMessageConverter y sobrescriba la lógica de parsing.
- Capa de middleware: intercepta las respuestas HTTP del proveedor antes de que lleguen a LangChain y normaliza los campos.
- LangChain Expression Language (LCEL): envuelve la llamada al modelo en un RunnableLambda que extraiga el razonamiento antes de pasarlo a la cadena.
Sin embargo, estos métodos son más complejos y requieren un conocimiento profundo de los internos del framework. Para un MVP o solución rápida, parchear base.py es la opción óptima.
Lecciones clave
- El contenido de razonamiento se pierde no por un error, sino porque LangChain sigue estrictamente la API de OpenAI.
- El parche agrega soporte para el campo
reasoningen additional_kwargs sin romper la compatibilidad hacia atrás. - Funciona tanto para llamadas regulares como en streaming.
- Requiere reaplicación manual después de cada actualización de LangChain.
- Mejora la UX en apps CoT al hacer transparente el proceso de pensamiento del modelo para los usuarios.
— Editorial Team
Aún no hay comentarios.