上一篇我们用「查天气并总结」把 Chain 和 Graph 左右对照完了——能跑、能升级、能加记忆。但说句扎心的你改了一行 Prompt、换了个模型、升级了 LangGraph行为可能悄悄退化而你还在靠「手动试几条」自我安慰。传统测试写expect(add(1, 2)).toBe(3)结果确定。Agent 不是同一输入temperature 0 时说法会变ReAct 环里工具调用次数、顺序也可能飘。没有系统化评估边缘 case 的退化进不了 CI合并后再救火成本翻倍。这一篇把评估体系立起来golden set、两种判分方式、本地 eval runner以及怎么把通过率当成合并门槛。老规矩本文以官网最新文档核对过How to evaluate agents、Agent Evals。Agent 入口继续用博客 9 的createAgent——别再抄createReactAgent。本篇主路径是本地轻量 runner适合 Ollama、零 LangSmith 账号也能跑LangSmith /agentevals作升级选项点到为止。一、为什么 Agent 需要评估挑战说明非确定性同一 prompttemperature 0 时输出可能不同多步推理ReAct 环里 tool 调用顺序、次数可能变化回归风险改 prompt、换模型、升级依赖后行为可能静默退化成本与延迟除了「对不对」还要盯 token 与端到端耗时单元测试测的是确定性逻辑解析器、路由函数、工具本体。Eval 测的是Agent 端到端行为该不该调工具、参数对不对、最终答案合不合理。两者互补谁也替不了谁。否是改Prompt或换模型跑golden set通过率达标?CI失败挡住合并合并二、评估什么生产里常见维度维度衡量什么典型指标正确性最终回答是否符合预期pass rate、关键词命中、语义相似工具调用是否调对 tool、参数是否靠谱tool name match、args 校验延迟端到端响应时间p50 / p95 latencyToken 成本输入 输出用量avg tokens / query鲁棒性空输入、超长输入、异常工具返回不崩溃、有友好错误安全性注入、越权 tool拒绝率后文安全篇再展开起步建议ReAct / 工具 Agent至少同时看「最终答案」「是否调用了期望工具」——只看文本模型可能蒙对答案却根本没走 tool。RAG再加检索相关性、引用是否瞎编本篇不展开检索评测细节。三、数据集先有 golden set评估质量 ≈ 数据集质量。三种来源来源特点Golden set人工精选覆盖核心场景 已知边缘 case起步首选合成数据LLM 批量变体扩面快要人工抽检防分布漂移生产抽样最贴真实成本高适合后期建议起步先建10~20 条golden set——happy path 3~5 条边缘 case空输入、不会的工具、超长问句再逐步加。单条 case 建议字段/** 单条评估用例 */exportinterfaceEvalCase{id:string;input:string;/** 期望回答包含的关键词部分匹配即可 pass */expectedKeywords?:string[];/** 期望调用过的 tool 名称 */expectedTools?:string[];/** 分类标签便于按场景统计 */tags:string[];}exportconstgoldenSet:EvalCase[][{id:calc-01,input:3 加 5 等于多少,expectedKeywords:[8],expectedTools:[calculator],tags:[calculator,react],},{id:calc-02,input:100 加 25 是多少,expectedKeywords:[125],expectedTools:[calculator],tags:[calculator,react],},{id:chat-01,input:你好介绍一下自己,expectedKeywords:[助手,帮助],tags:[chat],},];四、两种判分确定 vs 裁判官网把 Agent 评估大致分成两路见 Agent Evals方式何时用代价确定性检查你知道期望工具 / 关键词 / 参考轨迹快、稳、几乎零成本LLM-as-judge开放题、要评「是否合理」「轨迹是否高效」多一次 LLM 调用有偏差确定性这边本篇用关键词 expectedTools更严的trajectory match严格顺序 / 无序 / subset / superset可用agentevals的createTrajectoryMatchEvaluator——知道参考轨迹时优先它。开放题这边给 judge 一张rubric固定模型、temperature: 0关键 case 保留人工复核。Rubric 示例请根据以下标准对 Agent 回答评分1~5 分 5 分完全正确信息准确表述清晰 4 分基本正确有小瑕疵 3 分部分正确有遗漏或轻微错误 2 分大部分错误 1 分完全错误或无关 只输出一个整数分数。极简 judge 骨架本地 Ollamaimport{ChatOllama}fromlangchain/ollama;import*aszfromzod;constjudgeLlmnewChatOllama({model:qwen2.5:7b,temperature:0});constscorerjudgeLlm.withStructuredOutput(z.object({score:z.number().min(1).max(5),reason:z.string()}));asyncfunctionjudgeAnswer(question:string,answer:string){returnscorer.invoke(问题${question}\n回答${answer}\n\n按 1~5 分打分并给一句理由。);}还有pairwise同一输入比 Agent A / B 谁更好适合 Prompt 或模型 A/B。judge 本身也会偏——别把分数神化。五、本地 Eval Runner主路径不接 LangSmith 也能先跑起来读 golden set → 逐条invoke→ 记 pass/fail、延迟 → 打汇总。import{createAgent}fromlangchain;import{ChatOllama}fromlangchain/ollama;import{tool}fromlangchain/core/tools;import*aszfromzod;importtype{EvalCase}from./dataset;import{goldenSet}from./dataset;constcalculatortool(async({a,b}:{a:number;b:number})String(ab),{name:calculator,description:两数相加,schema:z.object({a:z.number(),b:z.number()}),});constllmnewChatOllama({model:qwen2.5:7b,temperature:0});constagentcreateAgent({model:llm,tools:[calculator],systemPrompt:需要计算时必须调用 calculator 工具。,});functioncheckKeywords(content:string,keywords?:string[]):boolean{if(!keywords?.length)returntrue;constlowercontent.toLowerCase();returnkeywords.every((k)lower.includes(k.toLowerCase()));}functionextractToolNames(messages:{tool_calls?:{name:string}[]}[]):string[]{constnames:string[][];for(constmsgofmessages){for(constcallofmsg.tool_calls??[]){names.push(call.name);}}returnnames;}asyncfunctionrunSingleEval(caseItem:EvalCase){conststartDate.now();constresultawaitagent.invoke({messages:[{role:user,content:caseItem.input}],});constlatencyMsDate.now()-start;constlastContentString(result.messages.at(-1)?.content??);constkeywordOkcheckKeywords(lastContent,caseItem.expectedKeywords);constactualToolsextractToolNames(result.messages);consttoolOk!caseItem.expectedTools?.length||caseItem.expectedTools.every((t)actualTools.includes(t));constpasskeywordOktoolOk;return{id:caseItem.id,pass,latencyMs,reason:pass?undefined:keywordOk${keywordOk}, toolOk${toolOk},};}asyncfunctionrunEval(passRateThreshold0.9){constresults[];for(constcofgoldenSet){// 串行更省本地 Ollama要速度可改成有限并发results.push(awaitrunSingleEval(c));}constpassedresults.filter((r)r.pass).length;constratepassed/results.length;constavgLatencyresults.reduce((s,r)sr.latencyMs,0)/results.length;console.log(通过率:${passed}/${results.length}(${(rate*100).toFixed(1)}%));console.log(平均延迟:${avgLatency.toFixed(0)}ms);for(constrofresults.filter((x)!x.pass)){console.log(FAIL${r.id}:${r.reason});}if(ratepassRateThreshold){process.exitCode1;// CI gate}}// await runEval();跑「3 加 5」这类 case 时你真正在验的是既说对了 8又真的调了calculator——这比只includes(8)靠谱得多。六、升级LangSmith 与轨迹评估短本地 runner 够你起步。需要实验对比、历史趋势、团队共享仪表盘时再上 LangSmith建 dataset每条 example 有 inputs / 可选 reference outputs写 target(inputs) agent.invoke(inputs)挂 evaluators调用evaluateJS 里自带异步示意import{evaluate}fromlangsmith/evaluation;import{createTrajectoryLLMAsJudge,TRAJECTORY_ACCURACY_PROMPT,}fromagentevals;consttrajectoryEvaluatorcreateTrajectoryLLMAsJudge({model:openai:o3-mini,// 生产 judge 常用强模型本地可换prompt:TRAJECTORY_ACCURACY_PROMPT,});awaitevaluate(async(inputs){constresultawaitagent.invoke(inputs);returnresult.messages;},{data:your_dataset_name,evaluators:[trajectoryEvaluator],});agentevals还提供trajectory matchstrict/unordered/subset/superset有参考工具序列时优先确定性匹配又快又稳。细节见官网本篇不展开成第二教程。七、回归测试与 CI把 eval 脚本挂进 CIPR 或夜间定时# 示例跑完通过率不达标则 exit 1pnpmeval做法作用通过率阈值如 90%低于阈值 → job 失败挡住合并记录延迟 / 失败 case看是变慢还是变笨与单元测试分工pnpm test测纯逻辑eval 测 Agent 行为别指望 eval 替代单测工具函数、State reducer、路由纯函数该单测还是单测。常见坑手动试几条就当过关盖不住边缘 casePrompt 小改会静默退化。golden set 全是 happy path缺空输入、工具失败、超长输入 → 通过率虚高。LLM-as-judge 无 rubric / temperature 乱飘分数不可比固定 rubric temperature: 0。只评最终文本、不评工具调用模型可能蒙对答案却没走 tool上线就露馅。eval 不进 CI退化合并后才发现修复成本高。一上来就绑死 LangSmith本地 runner 先通仪表盘是加速器不是起步前置。还在用createReactAgent写 eval target主路径换成createAgent。