广告位 · 页头(后台-模块管理-新建模块后替换此区域)

如何用LangSmith追踪推理模型的完整推理路径?

推理模型在返回最终结果前,往往已经完成了一段相当复杂的内部推导。DeepSeek R1、OpenAI o系列等模型会把推理过程拆成大量隐藏token,这些token既不同于提示词的输入token,也不同于最终答案的输出token。它们负责多步计算、交叉验证和方案比较,但多数API默认不会把完整思考链直接展示在content字段里。LangSmith的价值在于把这些隐藏推理内容连同上下文、工具调用和耗时一起记录下来,形成可回放的推理路径。对于做复杂问答、数学推理或代码生成的产品,只看最终答案很难判断模型是真推理还是蒙对,也很难发现哪些步骤在绕圈。

如何用LangSmith追踪推理模型的完整推理路径?

为什么普通日志不够用

推理token与普通输出token在API响应里通常是分开的。OpenAI o系列会返回reasoning_tokens用量,DeepSeek R1会额外返回reasoning_content字段。常规的请求日志如果只打印message.content,就会把整个推理过程丢掉。即使把完整的响应对象存下来,没有统一的可视化视图,面对几百条trace也很难快速比较推理长度、重复模式和失败分支。

LangSmith的LLM span会记录输入消息、输出消息、token使用和附加字段。以DeepSeek R1为例,调用完成后进入LangSmith控制台,可以在输出区域看到一个独立的reasoning_content字段,里面是模型在给出答案前的完整推导。这个字段在LangChain的AIMessage中被放在additional_kwargs里,如果手动序列化时只取content,就会丢失路径信息。

另一个容易被忽略的问题是层级关系。单次调用只有一层LLM span,但真正的推理型应用很少只调用一次模型。RAG检索、Agent循环、多轮工具调用都会产生复杂的前后依赖。如果每个步骤都散落在不同日志系统里,很难把某一段推理和某个工具决策对上。LangSmith用树状span把这些关系组织起来,相当于为推理路径提供了一个调用栈。

接入LangSmith并保留推理字段

接入工作并不复杂。先安装langchain、langchain-openai和langsmith,然后在环境变量中开启追踪。下面的例子使用DeepSeek R1模型,因为它会直接在additional_kwargs中返回reasoning_content,便于观察。

import os
from langchain_openai import ChatOpenAI
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "lsv2_xxxx"
os.environ["LANGCHAIN_PROJECT"] = "reasoning-trace-demo"
llm = ChatOpenAI(
    model="deepseek-reasoner",
    api_key="sk-xxxx",
    base_url="https://api.deepseek.com/v1",
    temperature=1.0,
)
resp = llm.invoke("三箱苹果和两箱梨一共多少件?每箱12件。")
print(resp.content)
print(resp.additional_kwargs.get("reasoning_content"))

上面的代码通过LangChain的ChatOpenAI类访问DeepSeek接口,并设置LANGCHAIN_PROJECT为reasoning-trace-demo。invoke完成后,LangSmith会自动上报一条trace。进入控制台找到对应项目,点开LLM span,在输出中可以看到content是最终答案,而reasoning_content保存了完整推导。这里有两个容易踩的坑:一是不要用print(resp)代替print(resp.content),因为resp对象可能将additional_kwargs折叠显示;二是如果使用流式接口,需要把每个chunk中的additional_kwargs合并,只拼接content同样会丢失路径。

对于OpenAI o系列模型,推理内容通常不会以完整文本形式返回,但LangSmith会记录reasoning_tokens。你可以通过usage字段判断模型在内部推导上花费了多少token,再结合最终答案判断是否出现推理过重。比如一个简单分类任务如果reasoning_tokens占比超过80%,可能需要调低推理强度或更换模型。

多步推理与Agent路径可视化

当推理模型被用于Agent场景时,模型通常需要先生成一段推理,再决定调用哪个工具;工具返回结果后,模型会继续推理并修正下一步动作。LangSmith会把这些步骤记录成嵌套的span树,根span代表整个Agent运行,下面包含LLM span、Tool span和解析器span。定位问题时可以先看根span的状态,再逐层展开,找出是哪一次推理导致工具选择偏离,或者哪一次工具返回后模型忽略了关键信息。

如果不想依赖现成的Agent框架,也可以用@traceable装饰器包装自定义推理循环。下面这个例子把数学题拆成两步:先让模型列出解题步骤,再让它根据第一步的推理结果进行校验。

from langsmith import traceable
from langchain_openai import ChatOpenAI
@traceable(run_type="chain", name="solve_math")
def solve_with_reasoning(question: str):
    llm = ChatOpenAI(model="deepseek-reasoner", base_url="https://api.deepseek.com/v1")
    first = llm.invoke(f"先列出解题步骤:{question}")
    check = llm.invoke(f"检查上述步骤是否有遗漏,并修正:{first.additional_kwargs.get('reasoning_content', first.content)}")
    return check.content
print(solve_with_reasoning("一个水池,甲管注满需要3小时,乙管需要6小时,两管同开需要多久?"))

这个函数被traceable包装后,LangSmith会把solve_math作为一个run记录,并把函数内部两次llm.invoke自动关联为子span。打开trace后可以看到,第一次调用的reasoning_content被传递给第二次调用,而不是被静默丢弃。这种方式让推理路径从模型内部隐式状态变成可操作的输入,后续想替换推理结构、增加校验轮次或记录不同推理强度,都可以直接改函数而不影响追踪逻辑。

如果需要批量对比不同推理强度或模型版本,建议在调用时附加元数据。LangChain的invoke方法支持通过config参数传入metadata,比如config={"metadata": {"case_id": "math_001", "model_version": "r1"}}。这些字段会出现在LangSmith的run详情中,也可以在后续通过SDK过滤查询。不要把所有实验都放在同一个项目里,项目名最好按任务或数据集划分,否则trace数量增长后很难快速定位。

批量分析与推理质量优化

控制台适合人工排查单条异常trace,但当评测集有几百道题时,逐条点开效率太低。LangSmith的Python SDK支持按项目、时间范围、run_type等条件拉取runs,可以把每次运行的total_tokens、end_time、outputs等字段导出到本地。结合pandas统计reasoning_content长度、推理token占比、耗时分布,能够快速发现哪些样本推理路径过长,哪些样本推理链在反复重复。

分析时建议建立几个基本指标:平均推理token、平均输出token、推理token占比、P95耗时、失败样本的推理长度分布。如果某一类问题的推理token占比持续偏高,但准确率没有同步提升,说明模型可能在无效推理。如果失败样本的推理链在某个节点后突然变短,可能是工具返回格式异常或上下文被截断。将这些指标与LangSmith中的具体trace关联起来,就能从统计结果回到单条路径,找到需要调整的提示词或检索策略。

推理路径可视化不是一次性工具,而应当嵌入到日常回归流程中。每次修改提示词、切换模型版本或调整推理强度后,让LangSmith先跑一组固定用例,对比路径结构和token消耗。这样既能避免模型行为静默退化,也能在线上问题出现前发现推理质量下降的趋势。

广告位 · 文章底部(后台-模块管理替换此区域)