LangSmith 入门:让你的Agent链路可追踪

文章来源声明: 原文作者:不好听613; 来源站点:掘金; 原文链接:https://juejin.cn/post/7690770096785063990; 本文基于上述来源整理/加工,觅优补充点评,仅供技术学习交流。版权归原作者所有。
觅优短评

文章把「配三个变量就能追踪」的原理讲得清楚易懂,适合刚上手 LangChain/LangGraph、正被 RAG 答非所问困扰的开发者,作为可观测性入门第一步很实用。

你有没有过这种体验:用 LangChain 写了个 RAG 问答,结果答非所问,你盯着代码半天,不知道是**检索环节**出问题,还是**生成环节**在胡说八道,甚至不知道到底调了哪个模型、传了什么 prompt、烧了多少 token。

这就是 LLM 应用的"盲盒感"——你不是在调试一个程序,而是在摸黑一个概率系统。

LangSmith 就是来解决这个问题的。今天这篇先讲清楚它是什么、能干什么,以及那个最神奇的体验:为什么只配三个环境变量,一行埋点代码都不用写,整个应用就"看得见"了。

一、LangSmith 是什么

一句话:LangSmith 是 LangChain 官方出的 LLM 应用开发平台,覆盖从调试、评测到上线监控的完整生命周期。

它不生产模型(模型还是你调用的 OpenAI / DashScope / 你自己的),而是站在你的应用和模型之间,把调用链路上的每一步都录下来,让你能看见、能评测、能监控。

你的应用 ──调用──▶ 模型
    │                │
    └──── 埋点上报 ──▶ LangSmith 平台(记录每一步的输入/输出/耗时/成本/token)

二、六大模块,解决六个问题

模块解决什么问题你会看到的关键概念
**可观测性 / Tracing**调试:看不到中间发生了什么Trace、Span、Run
**评测 / Evaluation**改完 prompt 怎么知道变好变坏Dataset、Example、Evaluator
**Prompt 工程**集中管理 prompt、做 A/BPrompt Hub、Playground
**监控 & 告警**线上质量兜底Dashboard、Alert、规则
**反馈收集**拿真实用户反馈反哺评测User feedback、标注队列
**部署**企业版托管 agentDeployment

其中 追踪(Tracing) 和 评测(Evaluation) 是九成人的两个核心场景。本系列第一篇先讲追踪,评测放到第二篇。

三、零侵入接入:三个环境变量搞定一切

最惊艳的地方来了。假设你有一个 RAG 应用,核心代码长这样(用 LangGraph 画图):

<span>import</span> <span>"dotenv/config"</span>;
<span>import</span> { <span>StateGraph</span>, <span>Annotation</span>, <span>START</span>, <span>END</span> } <span>from</span> <span>"@langchain/langgraph"</span>;

<span>const</span> <span>GraphState</span> = <span>Annotation</span>.<span>Root</span>({
  <span>question</span>: <span>Annotation</span>,
  <span>context</span>: <span>Annotation</span>,
  <span>answer</span>: <span>Annotation</span>,
});

<span>async</span> <span>function</span> <span>retrieve</span>(<span>state</span>) {
  <span>const</span> docs = <span>await</span> retriever.<span>invoke</span>(state.<span>question</span>); <span>// Milvus 向量检索</span>
  <span>return</span> { <span>context</span>: docs };
}

<span>async</span> <span>function</span> <span>generate</span>(<span>state</span>) {
  <span>const</span> contextText = state.<span>context</span>.<span>map</span>(<span><span>d</span> =></span> d.<span>pageContent</span>).<span>join</span>(<span>"\n\n"</span>);
  <span>const</span> answer = <span>await</span> chain.<span>invoke</span>({ <span>context</span>: contextText, <span>question</span>: state.<span>question</span> });
  <span>return</span> { answer };
}

<span>const</span> workflow = <span>new</span> <span>StateGraph</span>(<span>GraphState</span>)
  .<span>addNode</span>(<span>"retrieve"</span>, retrieve)
  .<span>addNode</span>(<span>"generate"</span>, generate)
  .<span>addEdge</span>(<span>START</span>, <span>"retrieve"</span>)
  .<span>addEdge</span>(<span>"retrieve"</span>, <span>"generate"</span>)
  .<span>addEdge</span>(<span>"generate"</span>, <span>END</span>);

<span>export</span> <span>const</span> ragApp = workflow.<span>compile</span>();

注意:上面这段代码里,你没有写任何跟 LangSmith 有关的代码。 没有埋点、没有 trace() 调用、没有 callback。

你只需要在 .env 里配三行:

LANGSMITH_TRACING=<span>true</span>       <span># 开关</span>
LANGSMITH_API_KEY=lsv2_xxx   <span># 鉴权</span>
LANGSMITH_PROJECT=rag_demo   <span># 归属项目</span>

然后照常跑 ragApp.invoke({ question: "..." })。打开 LangSmith 网页,你会看到这次调用被完整地拆成了 trace:

ragApp.invoke
  ├─ retrieve(Milvus 检索,耗时 xx ms)
  └─ generate
<span>       ├─ ChatPromptTemplate(填充模板)
       ├─ ChatOpenAI(LLM 调用,消耗 xx token)
       └─ StringOutputParser(解析输出)
</span>

每一步的输入、输出、耗时、token 消耗,全都在。这就是零侵入追踪。

四、为什么配一下就能用?(原理拆解)

这不是魔法,是两个机制叠加:

1. dotenv/config 把 .env 变成环境变量

文件顶部 import "dotenv/config",把 .env 里的 KEY=VALUE 加载进 process.env。

2. SDK 内置了 auto-instrumentation

LangChain / LangGraph 的 SDK 内置了 tracing 回调,进程启动时会主动去读那几个约定俗成的变量名:

  • LANGSMITH_TRACING → 决定要不要上报
  • LANGSMITH_API_KEY → 决定"我是谁",用来鉴权
  • LANGSMITH_PROJECT → 决定上报到哪个项目

只要名字对得上,SDK 就自动把每次 invoke() 的调用树序列化成 trace,通过 API 发到 smith.langchain.com。

这种"读环境变量 + 自动上报"的套路,业界叫 auto-instrumentation(自动埋点),跟 OpenTelemetry 的自动埋点是同一个思路。所以"配一下就能用"的本质是:有人(SDK 作者)已经替你写好了埋点,你只需要用约定的方式打开它。

一个容易忽略的坑:变量名必须完全一致

SDK 只认固定名字。如果你 .env 里写 LANGSMITH_API_KEY,代码里却读 LANGCHAIN_API_KEY,读出来就是 undefined,然后静默回退到默认行为——你以为配了,其实没生效。

五、底座:为什么这些东西能拼起来(LCEL / Runnable)

聊 LangSmith 就绕不开 LangChain 的底座。你代码里那些能 .invoke() 的东西——prompt、llm、parser、retriever、甚至编译好的 ragApp——本质都是同一个抽象基类 Runnable 的子类。

Runnable 规定了统一的协议:

类别方法作用
执行`invoke` / `batch` / `stream`怎么跑
组合`pipe` / `bind` / `withFallbacks`怎么拼

最关键的一条设计:组合器本身也是 Runnable。

<span>const</span> chain = <span>RunnableSequence</span>.<span>from</span>([prompt, llm, <span>new</span> <span>StringOutputParser</span>()]);

RunnableSequence 把三个 Runnable 串成一条链,而它自己又是一个 Runnable,所以它能 .invoke()、能继续 .pipe()。数据流是这样:

question → prompt(填模板)→ llm(生成)→ StringOutputParser(剥成字符串)

"任意小积木都能拼成更大积木、接口始终不变"——这就是 LCEL(LangChain Expression Language)能存在的前提,也是 LangSmith 能统一追踪所有这些异构组件的原因(它们都有同一个 invoke 入口)。

六、所以呢?

到这里你应该明白三件事:

  1. LangSmith 解决的是"看不见"的问题 —— LLM 应用不可调试的本质,是缺少可观测性。
  2. 接入成本几乎为零 —— 三个环境变量,靠的是 SDK 预埋的 auto-instrumentation。
  3. 零侵入的前提是统一协议 —— 所有组件都是 Runnable,SDK 才能在一个统一的入口挂上追踪钩子。

但"看得见"只是第一步。下一篇文章回答一个更实际的问题:我改了 prompt 或模型,怎么用数据证明回答"变好了"? 那就要进入 LangSmith 的第二个核心能力——评测(Evaluation)。