你有没有过这种体验:用 LangChain 写了个 RAG 问答,结果答非所问,你盯着代码半天,不知道是检索环节 出问题,还是生成环节在胡说八道,甚至不知道到底调了哪个模型、传了什么 prompt、烧了多少 token。
这就是 LLM 应用的"盲盒感"------你不是在调试一个程序,而是在摸黑一个概率系统。
LangSmith 就是来解决这个问题的。今天这篇先讲清楚它是什么、能干什么,以及那个最神奇的体验:为什么只配三个环境变量,一行埋点代码都不用写,整个应用就"看得见"了。
一、LangSmith 是什么
一句话:LangSmith 是 LangChain 官方出的 LLM 应用开发平台,覆盖从调试、评测到上线监控的完整生命周期。
它不生产模型(模型还是你调用的 OpenAI / DashScope / 你自己的),而是站在你的应用和模型之间,把调用链路上的每一步都录下来,让你能看见、能评测、能监控。
bash
你的应用 ──调用──▶ 模型
│ │
└──── 埋点上报 ──▶ LangSmith 平台(记录每一步的输入/输出/耗时/成本/token)
二、六大模块,解决六个问题
| 模块 | 解决什么问题 | 你会看到的关键概念 |
|---|---|---|
| 可观测性 / Tracing | 调试:看不到中间发生了什么 | Trace、Span、Run |
| 评测 / Evaluation | 改完 prompt 怎么知道变好变坏 | Dataset、Example、Evaluator |
| Prompt 工程 | 集中管理 prompt、做 A/B | Prompt Hub、Playground |
| 监控 & 告警 | 线上质量兜底 | Dashboard、Alert、规则 |
| 反馈收集 | 拿真实用户反馈反哺评测 | User feedback、标注队列 |
| 部署 | 企业版托管 agent | Deployment |
其中 追踪(Tracing) 和 评测(Evaluation) 是九成人的两个核心场景。本系列第一篇先讲追踪,评测放到第二篇。
三、零侵入接入:三个环境变量搞定一切
最惊艳的地方来了。假设你有一个 RAG 应用,核心代码长这样(用 LangGraph 画图):
js
import "dotenv/config";
import { StateGraph, Annotation, START, END } from "@langchain/langgraph";
const GraphState = Annotation.Root({
question: Annotation,
context: Annotation,
answer: Annotation,
});
async function retrieve(state) {
const docs = await retriever.invoke(state.question); // Milvus 向量检索
return { context: docs };
}
async function generate(state) {
const contextText = state.context.map(d => d.pageContent).join("\n\n");
const answer = await chain.invoke({ context: contextText, question: state.question });
return { answer };
}
const workflow = new StateGraph(GraphState)
.addNode("retrieve", retrieve)
.addNode("generate", generate)
.addEdge(START, "retrieve")
.addEdge("retrieve", "generate")
.addEdge("generate", END);
export const ragApp = workflow.compile();
注意:上面这段代码里,你没有写任何跟 LangSmith 有关的代码。 没有埋点、没有 trace() 调用、没有 callback。
你只需要在 .env 里配三行:
bash
LANGSMITH_TRACING=true # 开关
LANGSMITH_API_KEY=lsv2_xxx # 鉴权
LANGSMITH_PROJECT=rag_demo # 归属项目
然后照常跑 ragApp.invoke({ question: "..." })。打开 LangSmith 网页,你会看到这次调用被完整地拆成了 trace:
markdown
ragApp.invoke
├─ retrieve(Milvus 检索,耗时 xx ms)
└─ generate
├─ ChatPromptTemplate(填充模板)
├─ ChatOpenAI(LLM 调用,消耗 xx token)
└─ StringOutputParser(解析输出)
每一步的输入、输出、耗时、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。
js
const chain = RunnableSequence.from([prompt, llm, new StringOutputParser()]);
RunnableSequence 把三个 Runnable 串成一条链,而它自己又是一个 Runnable ,所以它能 .invoke()、能继续 .pipe()。数据流是这样:
question → prompt(填模板)→ llm(生成)→ StringOutputParser(剥成字符串)
"任意小积木都能拼成更大积木、接口始终不变"------这就是 LCEL(LangChain Expression Language)能存在的前提,也是 LangSmith 能统一追踪所有这些异构组件的原因(它们都有同一个 invoke 入口)。
六、所以呢?
到这里你应该明白三件事:
- LangSmith 解决的是"看不见"的问题 ------ LLM 应用不可调试的本质,是缺少可观测性。
- 接入成本几乎为零 ------ 三个环境变量,靠的是 SDK 预埋的 auto-instrumentation。
- 零侵入的前提是统一协议 ------ 所有组件都是 Runnable,SDK 才能在一个统一的入口挂上追踪钩子。
但"看得见"只是第一步。下一篇文章回答一个更实际的问题:我改了 prompt 或模型,怎么用数据证明回答"变好了"? 那就要进入 LangSmith 的第二个核心能力------评测(Evaluation)。
- 下一篇:02-LangSmith-LLM-as-Judge量化评估闭环 --- 用 LLM 当裁判,量化你的 Agent 好不好