如果你无法度量它,就无法管理它------LangSmith 让 Agent 的每一步都有迹可循
摘要:用 LangChain/LangGraph 开发 Agent,最大的痛点是"盲盒感"------调用了哪个工具、每步耗时多少、Token 花在哪,全靠猜。本文从 LangSmith 的可观测性定位出发,拆解 Tracing、Datasets、Evaluators、Experiments 四大能力,并通过一个真实 RAG 项目的量化评估,展示如何让 Agent 从"黑盒"变成"白盒"。
📑 目录
- Agent 开发为什么充满"盲盒感"?
- LangSmith 的定位:观察者,不是构建者
- 四大核心板块:Tracing、Monitoring、Datasets、Evaluators
- 接入方式:三种场景,三种写法
- Datasets:给 Agent 准备一套"考卷"
- Evaluators:定义"怎样算通过"
- 实战:RAG Agent 的三个核心评估器
- Experiments:跑一次完整的"考试"
- 完整链路:从问答对到评估报告
- 互动讨论
Agent 开发为什么充满"盲盒感"?
用 LangChain/LangGraph 开发 Agent 时,你会遇到一个普遍的痛点:你不知道它到底在干什么。
- 它调用了哪个工具?
- 每一步耗时多少?
- 流式输出了多少 Token,总消耗多少?
- 条件边走了哪条分支?
- 为什么这次回答质量突然下降了?
如果没有可观测性,你只能靠 console.log 和直觉来猜。而 Agent 的执行是动态的、多步的、可能并行的------靠肉眼看日志,根本管不过来。
一句话:如果你无法度量它,你就无法管理它。
给 Agent 加上全生命周期的可观测性,LangSmith 就是那块不可或缺的仪表盘。
LangSmith 的定位:观察者,不是构建者
LangSmith 是与框架无关的可观测性与评估平台,贯穿 Agent 开发和上线的全生命周期。
| 工具 | 负责什么 |
|---|---|
| LangChain / LangGraph | 构建与编排 Agent |
| LangSmith | 追踪、评估、监控、调试 |
关键认知:LangSmith 是观察者,不是构建者。它不参与 Agent 的执行逻辑,只记录和分析执行过程。即使不用 LangChain/LangGraph,纯手写 Agent 也可以接入 LangSmith。
类比:LangGraph 是你盖的房子,LangSmith 是遍布全屋的监控摄像头、温度传感器和验收报告。它不决定房子怎么盖,但让你知道哪里漏水、哪里过热、装修改动后结构是否还安全。
四大核心板块:Tracing、Monitoring、Datasets、Evaluators
| 板块 | 定位 | 解决什么问题 |
|---|---|---|
| Tracing | 单次执行的完整链路可视化 | 这次调用到底发生了什么? |
| Monitoring | 生产环境持续聚合趋势 | 整体延迟、错误率、Token 成本走势如何? |
| Datasets | 可重复的质量度量 | 用统一的测试集来验证 |
| Evaluators | 定义"怎样算通过"的评估函数 | 自动打分,替代人工检查 |
Tracing:不只是"看有没有报错"
Tracing 的核心价值是完整执行链可视化 ------一次 invoke 拆成 Trace Tree,每个节点、LLM 调用、工具执行的输入输出、耗时、Token 消耗、走了哪条条件边,全部可见。
报错只是其中一个信号。更常见的场景是"没报错但结果不对"------这时靠 Tracing 定位是哪一步偏了:是检索召回了不相关的文档?还是 Prompt 拼接丢了信息?还是模型自己编造了内容?
Monitoring:看的是趋势,不是单次
Monitoring 是在生产环境持续聚合 Trace 数据,看延迟分布、错误率趋势、Token 成本变化、调用量波动。
| 维度 | Tracing | Monitoring |
|---|---|---|
| 视角 | 单次细节 | 整体趋势 |
| 用途 | Debug 具体问题 | 发现异常、容量规划 |
| 时间粒度 | 一次调用 | 小时/天/周聚合 |
Datasets & Experiments:可重复的质量度量
Datasets 是带输入和期望输出的测试用例集合;Experiments 是用某个版本的 Agent/Prompt/模型跑整个数据集,用于版本对比和回归检测。
Evaluators:定义通过标准
Evaluators 是评估函数,分两类:
| 类型 | 说明 | 适用场景 |
|---|---|---|
| 规则型(Code) | 精确匹配、包含关键词、JSON schema 校验 | 有明确标准答案、格式校验 |
| LLM-as-Judge | 让另一个 LLM 按评分标准打分 | 主观性、开放性答案 |
四者关系
text
lua
Datasets 提供测试输入
↓
Evaluators 定义通过标准
↓
Experiments 把两者跑起来产出结果
↓
Tracing 提供单次运行细节用于 debug
↓
Monitoring 在生产环境持续聚合
接入方式:三种场景,三种写法
场景一:LangChain/LangGraph 代码(零代码改动)
配好环境变量就自动上报:
bash
ini
export LANGSMITH_TRACING=true
export LANGSMITH_API_KEY=<你的 API Key>
export LANGSMITH_PROJECT=my-project # 指定项目名
export LANGCHAIN_CALLBACKS_BACKGROUND=true # 非 Serverless 环境
关键提醒:
- 环境变量只是开关,还需要在 LangSmith 里建项目,Trace 才有地方落。默认落到
default项目,生产环境应设LANGSMITH_PROJECT指定项目名,否则所有 Trace 混在一起,没法按项目隔离。 - Serverless 环境 必须设
LANGCHAIN_CALLBACKS_BACKGROUND=false,否则函数可能在 tracing 上报完成前被销毁,Trace 丢失。
场景二:手动挂载 Tracer
需要在代码里显式控制时:
typescript
php
import { LangChainTracer } from "@langchain/core/tracers/tracer_langchain";
const tracer = new LangChainTracer({ projectName: "my-agent" });
await graph.invoke(input, {
callbacks: [tracer],
configurable: { thread_id: "user-123" },
});
场景三:非 LangChain 代码(必须包装)
纯手写 Agent、自定义工具,必须用 traceable() 包装,否则 LangSmith 看不到任何东西:
typescript
php
import { traceable } from "langsmith/traceable";
const myCustomTool = traceable(
async (query: string) => {
return result;
},
{ name: "custom_search_tool", run_type: "tool" }
);
包装后,这个函数的执行会出现在 Trace Tree 中,与 LangGraph 节点的调用并列展示。
Datasets:给 Agent 准备一套"考卷"
Dataset 是结构化的测试用例集,核心是 Example ,每个 Example 包含 inputs、outputs(期望输出)、metadata。
在 RAG 项目中,我们用 build_dataset.mjs 创建了一个客服问答数据集:
javascript
javascript
import { Client } from "langsmith";
const DATASET_NAME = "rag-eval-v1";
const EXAMPLES = [
{
inputs: { question: "无理由退货要在几天内申请?" },
outputs: { answer: "自签收之日起 7 天内支持无理由退货。" },
},
{
inputs: { question: "质量问题换货期限是多久?" },
outputs: { answer: "15 天内出现质量问题可免费换货。" },
},
{
inputs: { question: "无理由退货运费谁承担?" },
outputs: { answer: "无理由退货由买家承担退货运费。" },
},
// ... 更多问答对
];
async function main() {
const client = new Client({
apiKey: process.env.LANGCHAIN_API_KEY
});
let dataset;
try {
dataset = await client.readDataset({ datasetName: DATASET_NAME });
console.log('数据集已存在');
} catch (err) {
dataset = await client.createDataset(DATASET_NAME, {
description: "RAG Agent 回归评估集",
});
console.log(`已创建数据集`);
}
const created = await client.createExamples(
EXAMPLES.map(e => ({
dataset_id: dataset.id,
inputs: e.inputs,
outputs: e.outputs,
}))
);
console.log(`已创建 ${created.length} 条样例`);
}
设计要点:
- 先尝试
readDataset,存在就复用,不存在才创建------避免重复运行时报错 - 每条 Example 都有
inputs(用户问题)和outputs(标准答案) - 标准答案用于"有参考答案"的评估器(如精确匹配),也用于 LLM-as-Judge 的参考
Evaluators:定义"怎样算通过"
Evaluator 是一个函数,接收 inputs、outputs、referenceOutputs,返回带 key 和 score 的结果对象。
typescript
typescript
import type { EvaluationResult } from "langsmith/evaluation";
// 精确匹配
const exactMatch = async ({
outputs,
referenceOutputs,
}: {
outputs: Record<string, any>;
referenceOutputs?: Record<string, any>;
}): Promise<EvaluationResult> => {
const score = outputs?.answer === referenceOutputs?.answer ? 1 : 0;
return { key: "exact_match", score };
};
// 包含关键词
const containsKeyword = async ({
outputs,
}: {
outputs: Record<string, any>;
}): Promise<EvaluationResult> => {
const score = outputs?.answer?.includes("蓝鲸") ? 1 : 0;
return { key: "contains_keyword", score };
};
评估器的四种类型
| 类型 | 说明 | 适用场景 |
|---|---|---|
| Code(代码) | 用 TS/Python 写的规则函数 | 有明确标准答案、格式校验 |
| LLM-as-Judge | 用另一个 LLM 按评分标准打分 | 主观性、开放性答案 |
| Decision Model | 专门的决策模型作为裁判 | 需要更稳定、可解释的评判 |
| Human(人工) | 人工标注队列 | 高 stakes 场景、校准自动评估器 |
实战:RAG Agent 的三个核心评估器
在 RAG 项目中,我们使用 openevals 提供的三个 RAG 评估器 Prompt,通过 createLLMAsJudge 创建评估器。
三个评估器分别看什么
| 评估器 | 评估关系 | 核心问题 | 需要标准答案 |
|---|---|---|---|
| RAG_RETRIEVAL_RELEVANCE | 输入问题 vs 检索上下文 | 检索资料跟问题相关吗? | 否 |
| RAG_GROUNDEDNESS | 最终回答 vs 检索上下文 | 回答忠实于资料吗?还是在编造? | 否 |
| RAG_HELPFULNESS | 最终回答 vs 输入问题 | 回答有没有帮助用户解决问题? | 否 |
关键设计 :三个评估器都不依赖标准答案 (reference_outputs),靠裁判 LLM 的推理判断,可以在没有人工标注答案的情况下评估 RAG 系统健康度。
评估器的创建
javascript
php
import {
createLLMAsJudge,
RAG_GROUNDEDNESS_PROMPT,
RAG_HELPFULNESS_PROMPT,
RAG_RETRIEVAL_RELEVANCE_PROMPT,
} from "openevals";
import { ChatOpenAI } from "@langchain/openai";
const judge = new ChatOpenAI({
model: process.env.DEEPSEEK_MODEL_FLASH,
apiKey: process.env.DEEPSEEK_API_KEY,
configuration: {
baseURL: process.env.DEEPSEEK_API_BASE_URL
},
temperature: 0 // 要求绝对严谨
});
// 工厂设计模式:用一个类制造不同指标的 RAG 检测器
const ragGroundnessJudge = createLLMAsJudge({
prompt: RAG_GROUNDEDNESS_PROMPT,
feedbackKey: "rag_groundness", // 反馈结果时使用的指标名称
judge,
continuous: true,
});
const ragHelpfullnessJudge = createLLMAsJudge({
prompt: RAG_HELPFULNESS_PROMPT,
feedbackKey: "rag_helpfullness",
judge,
continuous: true,
});
const ragRetrievalRelevanceJudge = createLLMAsJudge({
prompt: RAG_RETRIEVAL_RELEVANCE_PROMPT,
feedbackKey: "rag_retrieval_relevance",
judge,
continuous: true,
});
createLLMAsJudge 的工作流程:
- 接收数据:从应用输出中提取
outputs、context等变量 - 构建 Prompt:将变量填充进模板
- 调用裁判 LLM:发送给指定模型
- 解析结果:格式化为
EvaluationResult
评估器函数的包装
javascript
javascript
// 幻觉评估器
export async function ragGroundnessEvaluator({ outputs }) {
return ragGroundnessJudge({
context: { documents: outputs.context },
outputs: { answer: outputs.answer }
});
}
// 帮助性评估器
export async function ragHelpfullnessEvaluator({ inputs, outputs }) {
return ragHelpfullnessJudge({
inputs,
outputs: { answer: outputs.answer }
});
}
// 相关性评估器
export async function ragRetrievalRelevanceEvaluator({ inputs, outputs }) {
return ragRetrievalRelevanceJudge({
inputs,
context: { documents: outputs.context }
});
}
export const ragEvaluators = [
ragGroundnessEvaluator,
ragHelpfullnessEvaluator,
ragRetrievalRelevanceEvaluator
];
三个关键认知
认知一:createLLMAsJudge 直接返回评估器函数
它内部封装了填 Prompt 模板、调裁判 LLM、解析结果三件事。拿到的是最终可用的 evaluator,不需要再自己"结合"。
认知二:应用输出结构必须匹配 Prompt 期望的变量名
RAG_GROUNDEDNESS_PROMPT 期望 outputs 和 context,应用就必须返回 { outputs, context },否则评估器拿不到 context 会失败。
认知三:导入的 Prompt 不是评估器
RAG_GROUNDEDNESS_PROMPT 是字符串模板,留了占位符,本身不能评估任何东西。createLLMAsJudge 才把它包装成评估器。你可以复制 Prompt 改评分标准,再传给 createLLMAsJudge 得到自定义评估器。
裁判模型的选择
裁判模型的推理能力直接影响评估可靠性。gpt-4o 或 o3-mini 这类强模型更可信。本项目中用了 DEEPSEEK_MODEL_FLASH,temperature: 0 保证评分严谨。
Experiments:跑一次完整的"考试"
Experiment 是在一个 Dataset 上,用特定版本的 Agent/Prompt/模型跑完所有用例的完整记录。evaluate() 自动创建 Experiment 并触发所有评估器。
javascript
javascript
import "dotenv/config";
import { Client } from "langsmith";
import { evaluate } from "langsmith/evaluation";
import { ask } from "../rag_agent.mjs";
import { ragEvaluators } from "./evaluators.mjs";
const DATASET_NAME = "rag-eval-v1";
const client = new Client({
apiKey: process.env.LANGCHAIN_API_KEY
});
// 把 RAG Agent 包装成评估目标函数
async function runRagAgent(inputs) {
const { answer, context } = await ask(inputs.question);
return {
answer,
context: context.map(d => d.pageContent)
};
}
async function main() {
const result = await evaluate(runRagAgent, {
data: DATASET_NAME,
evaluators: ragEvaluators,
client,
experimentPrefix: `rag-deepseekevals-${process.env.DEEPSEEK_MODEL_FLASH}`,
maxConcurrency: 2 // 最大同时进行检测的题目数量
});
}
main().catch(err => {
console.error(err);
process.exitCode = 1;
});
关键设计
1. 目标函数的输入输出契约
runRagAgent 接收 inputs(数据集中的 { question }),返回 { answer, context }。这个返回结构必须匹配评估器的期望------context 是字符串数组,因为 RAG_GROUNDEDNESS_PROMPT 需要 context.documents。
2. experimentPrefix 做版本标记
rag-deepseekevals-${DEEPSEEK_MODEL_FLASH} 让每次评估都带上模型名,方便在 LangSmith 中对比不同模型的实验结果。
3. maxConcurrency 控制并发
评估要调 LLM,并发太高容易触发限流。设成 2 是一个保守的起点。
运行后的结果
程序正常执行后,可以在 LangSmith 网站的 Dataset 板块查看这次评估的各项结果指标:
- 每条用例的
rag_groundness分数(0~1) - 每条用例的
rag_helpfullness分数 - 每条用例的
rag_retrieval_relevance分数 - 整体平均分和分布
完整链路:从问答对到评估报告
将整个流程串联起来:
text
scss
1. 准备阶段
build_dataset.mjs → 在 LangSmith 创建 Dataset + 12 条问答对
↓
2. 评估器定义
evaluators.mjs → 三个 RAG 评估器(groundedness / helpfulness / relevance)
↓
3. 执行评估
run_eval.mjs → evaluate(ragAgent, { data, evaluators, client })
↓
4. 每条用例:
ragAgent(question) → { answer, context }
↓
三个评估器分别调用裁判 LLM 打分
↓
5. 结果写入 LangSmith
Dataset 板块查看每条用例的分数 + 整体趋势
一次评估能回答什么问题?
| 指标 | 低了说明什么 | 优化方向 |
|---|---|---|
| groundedness 低 | 回答在编造,没有忠实于检索资料 | 优化 Prompt 约束、加强"不知道就说不知道" |
| helpfulness 低 | 回答没解决用户问题 | 优化 Prompt、换更强的生成模型 |
| retrieval_relevance 低 | 检索召回了不相关的文档 | 优化 embedding 模型、调整 chunk 策略、加关键词检索 |
回归检测的价值
当你改了 Prompt、换了模型、调整了 chunk 参数,重新跑一次 run_eval.mjs,对比新旧 Experiment 的分数:
- 如果
groundedness提升了但helpfulness下降了,说明改动有副作用 - 如果三个指标都提升了,说明改动有效
- 如果三个指标都下降了,说明改动回退了
这就是量化评估的核心价值:让"感觉变好了"变成"数据证明变好了"。
互动讨论
💬 LangSmith 是另一个 Agent 框架吗?
不是。它是观察者,不是构建者。不参与 Agent 的执行逻辑,只记录和分析执行过程。即使不用 LangChain/LangGraph,纯手写 Agent 也可以接入。
💬 配好环境变量就够了吗?
不够。环境变量只是开关,还需要在 LangSmith 里建项目,Trace 才有地方落。非 LangChain 代码必须用 traceable() 包装,否则 LangSmith 看不到任何东西。
💬 createLLMAsJudge 创建的是什么?
它直接返回一个评估器函数,内部封装了填 Prompt 模板、调裁判 LLM、解析结果三件事。拿到的是最终可用的 evaluator,不需要再自己"结合"。
💬 三个 RAG 评估器为什么不需要标准答案?
它们靠裁判 LLM 的推理判断,而不是和标准答案做比对。groundedness 检查回答是否忠实于上下文,helpfulness 检查回答是否解决问题,retrieval_relevance 检查检索内容是否相关------这些都可以由 LLM 直接判断。
💬 评估器分数低了怎么优化?
先看是哪个指标低:groundedness 低说明有幻觉,优化 Prompt 约束;helpfulness 低说明回答质量差,换更强的生成模型;retrieval_relevance 低说明检索不准,优化 embedding 或 chunk 策略。