给 RAG Agent 装上"仪表盘":LangSmith 全链路观测与量化评估实战
写 LLM 应用最痛苦的是什么?盲盒感。 调用了哪个工具?每步耗时多少?流式吐了多少 token?为什么这轮回答崩了? 如果无法度量它,就无法管理它。LangSmith 就是那个把 Agent 从"黑盒"变成"透明仪表盘"的观测平台。
这篇文章,我们从一个故意写错的 LangGraph 图 开始,一路走到RAG 问答系统的自动化量化评估 ,全程基于真实可运行的代码(langsmith-demo 仓库),把 LangSmith 的四大核心能力一次讲透。
目录
- 为什么需要 LangSmith:LLM 开发的"盲盒感"
- LangChain / LangGraph / LangSmith 铁三角
- 核心功能四件套:Tracing / Monitoring / Datasets / Evaluators
- 实战一:给 LangGraph 装上"行车记录仪"(Trace 调试)
- 实战二:RAG 全链路接入 LangSmith
- 重难点详解:StateGraph 状态机、Milvus 向量库、防幻觉设计、OpenAI 兼容接入
- 实战三:RAG 量化评估(Dataset + Evaluators + Experiment)
- 知识点总表 & 一句话记忆
- 真实踩坑实录(都是血泪)
- 面试金句
1. 为什么需要 LangSmith:LLM 开发的"盲盒感"
传统后端出 Bug 不可怕:报错有堆栈、接口有日志、数据库有慢查询,一层层查总能定位。
但 LLM 应用不一样。你写一个 Agent:
js
const answer = await agent.invoke({ question: "无理由退货要几天?" });
这一行背后可能发生了 10 次 LLM 调用、3 次工具调用、1 次向量检索。问题来了:
- 它调用检索了吗?还是直接瞎答?
- 每一步花了多久?哪个环节最慢?
- 这轮回答消耗了多少 token?烧钱大户是谁?
- 为什么上次好好的,这次就崩了?
没有观测手段时,你只能靠 console.log 和"再跑一次看看"------这就是盲盒感。
LangSmith 就是给 LLM 应用装的"行车记录仪 + 仪表盘" :每一次运行都被完整记录成一条 trace,像一棵树一样展示每一步的输入、输出、耗时、token 用量,并且能批量回放、对比、评估。
一句话记忆:LangSmith = LLM 应用的 Grafana + Sentry + 自动化测试平台。
2. LangChain / LangGraph / LangSmith 铁三角
这三个名字长得像,经常搞混。用一句话区分:
| 库 | 定位 | 类比 |
|---|---|---|
| LangChain | LLM 应用开发框架(模型、提示词、检索、链) | 脚手架 |
| LangGraph | 有状态、有环的图编排框架(Agent 工作流) | 流程图 |
| LangSmith | 全链路观测与评估平台(SaaS) | 仪表盘 |
它们的关系:
markdown
你的代码(LangChain/LangGraph 写的 Agent)
│ 自动埋点(零侵入)
▼
LangSmith SDK(收集 trace)
│ HTTP 上报
▼
LangSmith 云端(可视化、监控、评估)
本仓库的 demo/ 和 rag-demo/ 就分别演示了 LangGraph 写图 和 LangChain+LangGraph 写 RAG,而它们全都自动把数据喂给了 LangSmith。
关键认知 :接入 LangSmith 不需要改业务代码 。只要装好 SDK、在 .env 里配好 API Key,LangChain/LangGraph 内部会自动上报。这是它最爽的一点。
3. 核心功能四件套
仓库根目录的 readme.md 把 LangSmith 的核心能力总结为四件套:
① Tracing(追踪)
每次 Agent 执行生成一条完整 trace:哪个节点、哪步检索、哪次 LLM 调用、输入输出、耗时、token。调试 Bug 的利器。
② Monitoring(监控)
Agent 上线后的后台实时监控:token 开销、耗时分布、工具调用统计。看整体表现。
③ Datasets(数据集)
把"用户提问 + 标准答案"统一存成测试样本(比如仓库里的 12 条退货/物流/会员问答对)。这是自动评估的"考卷"。
④ Evaluators(评估器)
定义打分规则,让 LLM 当"考官",对 Agent 的回答批量打分:有没有幻觉?回答够不够好?检索到的内容相关吗?这是量化优化 Agent 的依据。
一句话记忆:Tracing 看过程,Monitoring 看整体,Datasets 出考卷,Evaluators 打分数。
4. 实战一:给 LangGraph 装上"行车记录仪"
仓库的 demo/ 是最小演示:一个 故意抛错 的 LangGraph 图。
4.1 代码解剖
js
// demo/src/trigger-error.mjs
import "dotenv/config"; // ① 加载 .env,LangSmith 配置自动生效
import { Annotation, END, START, StateGraph } from "@langchain/langgraph";
// ② 定义状态:像"背包"一样在节点间传递数据
const StateAnnotation = Annotation.Root({
text: Annotation({
reducer: (_prev, next) => next, // 后写的覆盖先写的
default: () => "",
})
});
const stepOk = (state) => ({ text: `${state.text}[ok]` }); // 正常节点
const stepThrow = () => { // 故意抛错的节点
throw new Error("DemoError:节点内故意跑错:(trigger-error.mjs)")
}
// ③ 建图:START → step_ok → step_throw → END
const graph = new StateGraph(StateAnnotation)
.addNode("step_ok", stepOk)
.addNode("step_throw", stepThrow)
.addEdge(START, "step_ok")
.addEdge("step_ok", "step_throw")
.addEdge("step_throw", END)
.compile();
try {
await graph.invoke({ text: "start" });
console.log("不应执行");
} catch(err) {
console.error("已捕获:", err?.message ?? err);
process.exitCode = 1;
}
4.2 为什么用 LangGraph 而不用普通函数链?
因为 Agent 的工作流不是一条直线 :可能要循环(问→答→再问)、要分支(判断要不要检索)、要多人协作。LangGraph 用图来表达这种复杂流程,每个节点是一个函数,状态在节点间流动。
4.3 重难点详解:StateGraph 的状态机制
这是 LangGraph 最容易懵的地方,用背包比喻:
把 State 想象成一个背包 。每个节点执行完,把新东西塞进背包(返回值 merge 进状态);下一个节点从背包里取自己需要的东西。
reducer就是"塞东西时发生冲突怎么处理"的规则。
js
reducer: (_prev, next) => next, // 新值直接覆盖旧值
reducer: (prev, next) => [...prev, ...next], // 数组就拼接
本仓库 RAG Agent 里的状态有三个字段:
js
const GraphState = Annotation.Root({
question: Annotation, // 用户问题(进)
context: Annotation, // 检索到的文档片段(中间)
answer: Annotation // 最终回答(出)
});
节点 retrieve 往背包塞 context,节点 generate 从背包取 question + context,再塞 answer。就这么简单。
4.4 这个 demo 的看点:错误也能被 trace
节点故意抛错,LangGraph 的 trace 照样把错误记录在案。去 LangSmith 控制台打开这条 trace,你会看到:
step_ok节点:正常输出step_throw节点:红色错误标记 + 完整报错信息- 整条链路的耗时、每一步的输入输出
这就是"行车记录仪":即使翻车了,记录仪也拍下了翻车全过程 。比裸奔时只看到一个 Error 强太多。
5. 实战二:RAG 全链路接入 LangSmith
rag-demo/ 是一个完整的 RAG(检索增强生成)问答系统:先往向量库灌知识,再用 LangGraph 组织"检索→生成",所有运行都自动 trace 到 LangSmith。
5.1 项目结构一览
bash
rag-demo/
├── .env # 密钥与配置(LangSmith / Milvus / 通义千问)
├── data/ # 知识库原始文档(会员、支付、售后、物流...)
├── src/
│ ├── milvus_insert.mjs # ① 灌库:文档→切块→向量→写入 Milvus
│ ├── rag_agent.mjs # ② RAG Agent:LangGraph 编排检索+生成
│ ├── cli.mjs # ③ 命令行问答入口
│ └── eval/ # ④ 量化评估三件套
│ ├── build_dataset.mjs # 建数据集
│ ├── evaluators.mjs # 定义评估器
│ └── run_eval.mjs # 跑评估
5.2 .env 配置:一条链路三套密钥
env
# ---- LangSmith 观测 ----
LANGSMITH_API_KEY=lsv2_pt_xxxx
LANGSMITH_PROJECT=rag-demo
LANGSMITH_TRACING_V2=true
# ---- Milvus 向量库 ----
MILVUS_URI=http://localhost:19530
MILVUS_COLLECTION=rag_docs
# ---- 通义千问(OpenAI 兼容) ----
OPENAI_API_KEY=sk-xxxx
OPENAI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
EMBEDDING_MODEL=text-embedding-v3
MODEL_NAME=qwen-plus
三个重点:
LANGSMITH_TRACING_V2=true是总开关 。LangChain core 的isTracingEnabled会检查它(LANGSMITH_TRACING_V2/LANGSMITH_TRACING/LANGCHAIN_TRACING_V2任一为true即开启)。代码一行不改,全局自动 trace。- 通义千问走 OpenAI 兼容协议 :
baseURL指到阿里云 DashScope,apiKey用百炼的 key,model用qwen-plus。意味着@langchain/openai全家桶(ChatOpenAI / OpenAIEmbeddings)直接可用,不用换 SDK。 - Embedding 和 Chat 用同一套 baseURL ,但模型不同:
text-embedding-v3(向量化)vsqwen-plus(对话)。
5.3 灌库:milvus_insert.mjs 详解
这一步把知识文档变成向量存进 Milvus,让 Agent 之后"查得到"。
js
// 关键链路:读文件 → 切块 → 向量化 → 建集合建索引 → 插入
const chunks = await loadChunks(); // ① 读 data/ 下所有 md/txt
const vectors = await embeddings.embedDocuments( // ③ 每块文本 → 1536维向量
chunks.map(c => c.pageContent)
);
const dim = vectors[0].length; // 向量维度
await client.createCollection({ // ④ 建集合(表)
collection_name: COLLECTION,
fields: [
{ name: "langchain_primaryid", is_primary_key: true, data_type: DataType.Int64, autoID: true },
{ name: "langchain_vector", data_type: DataType.FloatVector, dim },
{ name: "langchain_text", data_type: DataType.VarChar, max_length: 8000 },
{ name: "source", data_type: DataType.VarChar, max_length: 256 }
]
});
await client.createIndex({ // ⑤ 建索引(加速检索)
field_name: "langchain_vector",
index_type: IndexType.IVF_FLAT, // 聚类加速
metric_type: MetricType.L2, // 欧氏距离
params: { nlist: 128 }
});
重难点详解 A:为什么要"切块"(Chunking)?
LLM 有上下文窗口限制(比如 8k token),而且"长文档整篇塞进去"既浪费 token 又不精准。所以要把文档切成 500 字左右的小块:
js
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 500, // 每块目标长度
chunkOverlap: 50, // 相邻块重叠 50 字,防止语义被拦腰截断
});
用裁缝比喻:整匹布没法直接给人穿,要按身材裁成块 。
overlap就像裁缝留的"缝份",保证拼接处不丢内容。
重难点详解 B:索引 IVF_FLAT 和度量 L2 是什么?
- IVF_FLAT :把向量空间先聚类成 128 个桶(
nlist: 128),检索时只搜最近的几个桶,用精度换速度。类比图书馆先按类目分区,找书不用一本本翻。 - L2(欧氏距离) :衡量两个向量"离得多远",距离越小越相似。另外还有余弦距离(
COSINE)、内积(IP)等,按业务选。
重难点详解 C:向量数据库 vs 传统数据库?
| 维度 | 传统数据库(MySQL) | 向量数据库(Milvus) |
|---|---|---|
| 存的 | 结构化数据(行+列) | 高维向量(如 1536 维) |
| 查的 | 精确匹配 / 范围 | 语义相似("最像的 K 条") |
| 核心操作 | WHERE name = '张三' |
ORDER BY 距离 LIMIT 4 |
"苹果"和"手机"在文字上毫无共同点,但向量距离很近------这就是向量库能实现语义搜索的原因。
5.4 RAG Agent:rag_agent.mjs 详解
这是整个项目的灵魂,LangGraph 编排了两个节点。
js
// ① 模型层:Embedding + Chat(通义千问,OpenAI 兼容)
const embeddings = new OpenAIEmbeddings({
apiKey: process.env.OPENAI_API_KEY,
configuration: { baseURL: process.env.OPENAI_BASE_URL },
model: process.env.EMBEDDING_MODEL ?? "text-embedding-v3",
});
const llm = new ChatOpenAI({
apiKey: process.env.OPENAI_API_KEY,
configuration: { baseURL: process.env.OPENAI_BASE_URL },
model: process.env.MODEL_NAME ?? "qwen-plus",
temperature: 0, // 客服场景要确定性,不发挥
});
// ② 检索器:从 Milvus 现有集合加载,取最相似的 4 条
const vectorStore = await Milvus.fromExistingCollection(embeddings, {
collectionName: process.env.MILVUS_COLLECTION ?? "rag_docs",
url: process.env.MILVUS_URI ?? "http://localhost:19530",
});
const retriever = vectorStore.asRetriever({ k: 4 });
// ③ 提示词:防幻觉!明确"没提到的就说不知道"
const prompt = ChatPromptTemplate.fromMessages([
["system", `你是客服助手。仅根据下面[上下文]回答:上下文没有的信息请明确说不知道,不要编造。\n\n上下文: {context}`],
["human", "{question}"]
]);
const chain = RunnableSequence.from([prompt, llm, new StringOutputParser()]);
// ④ 状态:问题进,答案出,上下文中间传
const GraphState = Annotation.Root({
question: Annotation,
context: Annotation,
answer: Annotation
});
// ⑤ 节点1:检索 ------ 用问题去向量库捞 4 条最相关的片段
async function retrieve(state) {
const docs = await retriever.invoke(state.question);
return { context: docs };
}
// ⑥ 节点2:生成 ------ 把片段拼进上下文,让 LLM 基于事实回答
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 };
}
// ⑦ 组装图:START → retrieve → generate → END
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();
// ⑧ 对外接口
export async function ask(question) {
const result = await ragApp.invoke({ question });
return { answer: result.answer, context: result.context ?? [] };
}
重难点详解 D:为什么 temperature: 0?
客服场景要"给什么答什么",不能自由发挥。temperature 越低回答越确定;0 表示贪心解码,每次基本一致。写诗写小说才需要高温度。
重难点详解 E:防幻觉 Prompt ------ RAG 的灵魂
RAG 最大的敌人是幻觉:LLM 一本正经地编造知识库没有的内容。解决办法一半在代码一半在提示词:
提示词明确说:"上下文没有的信息请明确说不知道,不要编造。"
实测效果:问"会员积分怎么获取?",Agent 答出消费积分、签到奖励、生日福利后,会主动补一句"其他积分获取方式(如活动赠送、邀请好友)在提供的上下文中未提及,因此不知道"。这就是防幻觉机制生效了。
面试常问:RAG 怎么防止幻觉?答:①检索质量(召回相关片段)②提示词约束(不知道就说不知道)③评估兜底(下文讲的 groundedness 评估器检测幻觉)。
重难点详解 F:RunnableSequence 是什么?
js
RunnableSequence.from([prompt, llm, new StringOutputParser()])
一条流水线 :prompt 把模板+变量拼成消息 → llm 调用模型 → StringOutputParser 把复杂输出解析成纯字符串。链式调用,前者的输出是后者的输入。
重难点详解 G:LangGraph 和 RunnableSequence 的区别?
- RunnableSequence :一条固定直线,无状态,适合"模板→模型→解析"。
- LangGraph :一张图,有状态、有环、可分支,适合 Agent 级编排。
本仓库两样都用:小链路用 Sequence,大流程用 Graph。
5.5 命令行提问
js
// cli.mjs
const questions = args.length > 0 ? [args.join(" ")] : ["无理由退货要在几天内?"];
const { answer } = await ask(question);
跑一下:node src/cli.mjs "满多少元包邮",Agent 就会基于知识库回答"满 99 元包邮(部分大件/冷链除外)"------顺便,这次调用也自动出现在 LangSmith 的 trace 里了。
6. 实战三:RAG 量化评估(重点中的重点)
跑通 RAG 只是第一步。怎么证明它回答得好? 靠感觉不行,要量化。LangSmith 的评估体系是四步:
① 建数据集(考卷)→ ② 定义评估器(考官)→ ③ 跑评估(考生答题)→ ④ 看结果(打分)
6.1 第一步:建数据集 build_dataset.mjs
12 条"问题 + 标准答案":
js
const EXAMPLES = [
{ inputs: { question: "无理由退货要在几天内申请?" },
outputs: { answer: "自签收之日起 7 天内支持无理由退货。" } },
{ inputs: { question: "满多少元包邮?" },
outputs: { answer: "满 99 元包邮(部分大件/冷链除外)。" } },
// ... 共 12 条
];
js
// 已存在就读,不存在就建
try {
dataset = await client.readDataset({ datasetName: DATASET_NAME });
} catch(err) {
dataset = await client.createDataset(DATASET_NAME, { description: "RAG Agent 回归评估集" });
}
await client.createExamples(EXAMPLES.map(e => ({
dataset_id: dataset.id,
inputs: e.inputs,
outputs: e.outputs,
})));
这是"考卷":
inputs是题目,outputs是标准答案。之后 Agent 每次用这份卷子考试,分数可横向对比(回归测试)。
6.2 第二步:定义评估器 evaluators.mjs
用 LLM-as-a-Judge(LLM 当考官) 模式,让一个 LLM 给 Agent 的回答打分。这里用了 openevals 库的三个官方 RAG 评估器:
js
import { createLLMAsJudge, RAG_GROUNDEDNESS_PROMPT, RAG_HELPFULNESS_PROMPT, RAG_RETRIEVAL_RELEVANCE_PROMPT } from "openevals";
import { ChatOpenAI } from "@langchain/openai";
// 考官 = 通义千问
const judge = new ChatOpenAI({
apiKey: process.env.OPENAI_API_KEY,
configuration: { baseURL: process.env.OPENAI_BASE_URL },
model: process.env.MODEL_NAME ?? "qwen-plus",
temperature: 0,
});
// 考官一号:幻觉检测(回答有没有依据)
const ragGroundnessJudge = createLLMAsJudge({
prompt: RAG_GROUNDEDNESS_PROMPT,
feedbackKey: "rag_groundness",
judge,
continuous: true, // 输出 0~1 连续分,而不是只有 0/1
});
三个评估器分别查什么?
| 评估器 | 中文 | 考察点 | 查什么问题 |
|---|---|---|---|
rag_groundness |
幻觉检测 | 回答是否有上下文依据 | Agent 有没有编造知识库没有的内容 |
rag_helpfullness |
帮助性 | 回答是否准确解决问题 | 答非所问?漏了关键信息? |
rag_retrieval_relevance |
检索相关性 | 检索回的片段和问题是否相关 | 向量检索是不是捞了无关内容 |
重难点详解 H:LLM-as-a-Judge 怎么打分?
createLLMAsJudge 会把"标准答案、Agent 回答、检索上下文"一起发给考官 LLM,让考官按官方提示词模板输出一个分数(continuous: true 时是 0~1 的连续值)。整个过程不需要人工标注,用 AI 批改 AI 的作业。
面试亮点话术:"我们用 LLM-as-a-Judge 做评估,把人工抽查的成本降到接近零,而且评估维度可定制。"
6.3 第三步:跑评估 run_eval.mjs
js
import { evaluate } from "langsmith/evaluation";
import { ask } from "../rag_agent.mjs";
import { ragEvaluators } from "./evaluators.mjs";
const result = await evaluate(runRagAgent, {
data: DATASET_NAME, // 用哪份考卷
evaluators: ragEvaluators, // 哪些考官
client,
experimentPrefix: `rag-openevals-${process.env.MODEL_NAME ?? "qwen"}`,
maxConcurrency: 2 // 并发控制,防止把 LLM 打爆
});
runRagAgent 就是考生:拿数据集里的每个问题去跑真实的 RAG Agent,把回答和检索上下文交出来给考官打分。
运行后输出:
ruby
Starting evaluation of experiment: rag-openevals-qwen-plus-7d4a3a76
View results at https://smith.langchain.com/o/xxx/datasets/xxx/compare?selectedSessions=xxx
6.4 重难点详解 I:Experiment 是什么?
一次评估 = 一次 experiment(实验)。它把"考卷 + 考生回答 + 考官分数"打包存档。
为什么要用 experiment? 因为你可以改代码后重跑,两次实验横向对比 :比如把 k=4 改成 k=6,或者换了提示词,看幻觉分、帮助性分是升是降。这就是回归测试------优化 Agent 不再靠玄学。
6.5 重难点详解 J:三份分数的闭环价值
三个评估器不是各看各的,它们能帮你定位问题在哪一环:
检索相关性低 → 问题在检索层(换 chunk 策略、调 k、换索引)
幻觉分低 → 问题在生成层(加强提示词约束、提高检索质量)
帮助性低 → 问题在回答层(prompt 指令、模型选择)
一句话记忆:检索相关性查"有没有捞对",幻觉查"有没有瞎编",帮助性查"有没有答好"。
7. 知识点总表
| 知识点 | 一句话解释 | 实战位置 |
|---|---|---|
| Tracing | 每次运行生成完整调用树 | demo/ + 全项目 |
| Monitoring | 后台实时监控 token/耗时 | LangSmith 控制台 |
| Datasets | 问题+标准答案的"考卷" | eval/build_dataset.mjs |
| Evaluators | LLM 当考官打分 | eval/evaluators.mjs |
| Experiment | 一次评估存档,可横向对比 | eval/run_eval.mjs |
| StateGraph | 有状态图编排,节点间传背包 | rag_agent.mjs |
| Annotation.Root | 定义状态结构 | rag_agent.mjs |
| reducer | 状态合并规则 | demo/trigger-error.mjs |
| RecursiveCharacterTextSplitter | 递归切块,500字+50重叠 | milvus_insert.mjs |
| IVF_FLAT | 聚类索引,加速检索 | milvus_insert.mjs |
| L2 度量 | 欧氏距离衡量相似度 | milvus_insert.mjs |
| OpenAIEmbeddings | 文本→向量 | milvus_insert.mjs |
| ChatOpenAI | 对话模型客户端 | rag_agent.mjs |
| OpenAI 兼容 baseURL | 通义千问等国产模型接入 | .env |
| RunnableSequence | 线性流水线 | rag_agent.mjs |
| LLM-as-a-Judge | 用 LLM 评估 LLM | evaluators.mjs |
| openevals | 官方评估器库 | evaluators.mjs |
| 防幻觉 Prompt | "没有的信息说不知道" | rag_agent.mjs |
| temperature: 0 | 客服场景要确定性 | rag_agent.mjs |
8. 真实踩坑实录(都是血泪)
坑 1:键名不匹配,靠默认值"碰巧"跑通
代码里写 process.env.LANGCHAIN_API_KEY,但 .env 里只有 LANGSMITH_API_KEY。结果没报错?因为 LangSmith Client 传 undefined 时会自动 fallback 读环境变量。看起来能用,其实是隐性炸弹。
教训:配置键名必须和代码严格对齐。统一后:
js
const client = new Client({ apiKey: process.env.LANGSMITH_API_KEY });
坑 2:Milvus 容器崩溃(Exited 134)
etcdserver: authentication is not enabled ------ 根因是 bind-mount 数据卷残留旧配置 。docker compose down -v 只清 named volume,清不掉 bind-mount 。把 volumes/{etcd,minio,milvus} 备份改名后重启才恢复。
教训:用 bind-mount 的数据卷,出问题要手动清目录。
坑 3:普通脚本不打印 trace 链接
跑脚本时控制台没有"View current trace"链接,以为没连上 LangSmith。实际用 API 一查:run 早就上报了,只是脚本环境不打印这个提示(那是交互式环境的行为)。
教训 :判断是否连接成功,用 client.listRuns() 实查,别靠肉眼。
坑 4:Milvus 地址要剥掉协议头
js
const MILVUS_ADDRESS = process.env.MILVUS_URI.replace(/^https?:\/\//, "") ?? "localhost:19530";
MilvusClient 会自动加协议前缀,传入带协议的地址反而报错。
9. 面试金句
- "LangSmith 是 LLM 应用的可观测性与评估平台,一句话概括就是 LLM 应用的 Grafana + Sentry + 自动化测试。"
- "接入零侵入:配好环境变量,LangChain/LangGraph 内部自动上报 trace,不需要改业务代码。"
- "RAG 防幻觉三板斧:检索质量、提示词约束(不知道就说不知道)、评估兜底(groundedness 评估器检测)。"
- "评估体系四件套:Datasets 出考卷、Evaluators 当考官、Experiment 存档、横向对比做回归。"
- "我们用 LLM-as-a-Judge 做自动化评估,评估维度可定制,人工成本趋近于零。"
10. 写在最后
从 demo/ 里一个故意报错的图,到 rag-demo/ 里跑通的全链路 RAG,再到 eval/ 里的自动化评估------这条路径恰好是一个 LLM 应用从调试、上线到优化的完整生命周期。
- Tracing 让你看得见(调试)
- Monitoring 让你管得住(上线)
- Datasets + Evaluators 让你优化得动(迭代)
你无法度量它,就无法管理它。给 Agent 装好仪表盘,剩下的就是持续迭代。
仓库代码都在这:langsmith-demo/,clone 下来配好 .env 就能跑。动手试一次,比看十篇文章都管用。