✅ 评估与测试
普通软件有确定性输出,写断言测试不难。但 Agent 非确定(同输入不同输出)、多步骤、依赖 LLM——怎么测它"好不好"?本章精读 LangChain 的评估体系(EvaluatorCallbackHandler、标准化测试套件、LLM-as-judge),为 RAG Agent 设计可量化的评估方案。
本章目标
- 理解 Agent 评估为什么难(非确定性、多维度)
- 掌握 Agent 的核心评估维度(忠实度/相关性/正确性/效率)
- 精读 EvaluatorCallbackHandler 运行时评估机制
- 了解 langchain_tests 标准化测试套件
- 用 LLM-as-judge 实战评估一个 RAG Agent
Agent 评估为什么难
| 挑战 | 普通软件 | Agent |
|---|---|---|
| 输出确定性 | 固定输入→固定输出 | 同输入可能不同输出(温度/模型版本) |
| 正确性标准 | 断言 == 期望值 | "好回答"是主观的,难定义客观标准 |
| 测试维度 | 功能对错 | 忠实度/相关性/有用性/安全/成本...多维度 |
| 多步骤 | 单函数调用 | Agent 走的路径每次不同,中间步骤也要评估 |
| 回归测试 | 改代码跑测试 | 改 prompt/换模型,需重新评估全量 |
普通测试是二元的(pass/fail)。Agent 评估通常是连续打分(0-1 的相关性分数)+ 多维评估(同时看忠实度、相关性、有用性)。工具上:LLM-as-judge(用另一个 LLM 评判)是当前主流方案——因为"判断回答好坏"本身就是 LLM 擅长的。
评估维度
EvaluatorCallbackHandler
LangChain 的运行时评估机制——把评估器挂成 callback,每次 run 结束自动评估:
class EvaluatorCallbackHandler(BaseTracer):
"""运行时评估的桥:把评估器挂成 tracer。
每个 run 结束时自动用挂载的 evaluators 评估。
_persist_run (:205) 在 run 完成后用线程池跑 evaluators。
max_concurrency (:93):并发评估数。
"""
# 本质是"追踪即评估":
# - 普通 tracer 只记录 run(上报 LangSmith)
# - 这个 tracer 额外对 run 做【评估打分】
# - 评估结果也上报,形成"执行+评估"的完整闭环
回顾 AD7:callbacks 是可观测基础。EvaluatorCallbackHandler 是特殊的 callback——它不只记录,还评估。所以评估和追踪共享同一套基础设施。这也是为什么评估通常和 LangSmith 深度集成——评估结果可视化在追踪旁边。
标准化测试套件
LangChain 为集成(chat model/embedding/vectorstore/tool)提供标准化测试——保证集成质量一致:
# langchain_tests 包:每个集成继承对应基类即获一致性测试
# base.py:4 BaseStandardTests —— 根基类
# 按类型分的标准测试:
# chat_models.py:194 ChatModelIntegrationTests
# - test_invoke (:767)
# - test_stream (:822)
# - test_stream_events_v3 (:937)
# - test_batch (:1130)
# ... 覆盖所有标准行为
# unit_tests/:单元测试(不需真实 API)
# _langsmith_plugin.py:pytest 插件,自动注入 tracing tags
# 用法:你的 ChatModel 集成继承 ChatModelIntegrationTests
class TestMyChatModel(ChatModelIntegrationTests):
@pytest.fixture
def chat_model_class(self):
return MyChatModel
# 自动获得几十个标准测试,保证你的实现符合规范
这不是评估"Agent 好不好",而是评估"集成是否符合规范"。比如你写了个新的 ChatModel 集成,继承 ChatModelIntegrationTests 就能验证它是否正确实现了 invoke/stream/batch/stream_events——保证生态质量。这是开源框架维护的关键工程实践。
LLM-as-judge 实战
核心思想:用评估 LLM对被评估 Agent的输出打分。
# pip install langchain langchain-openai pydantic
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
from typing import Literal
judge_model = ChatOpenAI(model="gpt-4o", temperature=0) # 评判用更强模型,温度0保证稳定
# ============ 定义三个评估维度的打分 schema ============
class FaithfulnessScore(BaseModel):
"""忠实度:答案是否完全基于上下文(有无幻觉)。"""
score: Literal[0, 1] = Field(description="1=忠实 0=有幻觉")
reasoning: str = Field(description="判断理由")
class RelevanceScore(BaseModel):
"""相关性:答案是否回答了问题。"""
score: Literal[0, 1] = Field(description="1=相关 0=跑题")
reasoning: str
class CorrectnessScore(BaseModel):
"""正确性:答案是否事实正确(对照标准答案)。"""
score: Literal[0, 1] = Field(description="1=正确 0=错误")
reasoning: str
# ============ 评估函数 ============
def evaluate_faithfulness(answer: str, context: str) -> FaithfulnessScore:
"""评估忠实度:答案的每个声明是否都能在 context 找到支持。"""
return judge_model.with_structured_output(FaithfulnessScore).invoke(
f"上下文:{context}\n答案:{answer}\n"
f"答案中的每个陈述是否都能从上下文推导出?只回 JSON。"
)
def evaluate_relevance(answer: str, question: str) -> RelevanceScore:
return judge_model.with_structured_output(RelevanceScore).invoke(
f"问题:{question}\n答案:{answer}\n答案是否切题回答了问题?"
)
def evaluate_correctness(answer: str, ground_truth: str) -> CorrectnessScore:
return judge_model.with_structured_output(CorrectnessScore).invoke(
f"标准答案:{ground_truth}\n待评答案:{answer}\n两者意思是否一致?"
)
# ============ 评估数据集(离线评估)============
eval_dataset = [
{
"question": "LCEL 是什么?",
"context": "LCEL 是 LangChain 的表达式语言,用 | 组合组件...",
"ground_truth": "LCEL 是 LangChain 的管道组合语法",
},
# ... 更多评估样本
]
# ============ 运行评估 ============
def run_evaluation(agent, dataset):
results = []
for item in dataset:
answer = agent.invoke(item["question"]) # 被评估 Agent 的输出
results.append({
"faithfulness": evaluate_faithfulness(answer, item["context"]).score,
"relevance": evaluate_relevance(answer, item["question"]).score,
"correctness": evaluate_correctness(answer, item["ground_truth"]).score,
})
# 汇总
n = len(results)
print(f"=== 评估报告({n} 个样本)===")
print(f"忠实度: {sum(r['faithfulness'] for r in results)/n:.1%}")
print(f"相关性: {sum(r['relevance'] for r in results)/n:.1%}")
print(f"正确性: {sum(r['correctness'] for r in results)/n:.1%}")
# 改 prompt / 换模型 / 调 RAG 策略后,重跑评估对比分数
# 这就是 Agent 的"回归测试"
用 LLM 评估 LLM 有自身偏差:①评判模型可能偏好某风格;②强模型评判弱模型更准,反过来不行;③对长答案/复杂推理可能判断不稳。缓解:用比被评估模型更强的评判模型、温度设0、多评判取平均、结合人工抽检。LLM-as-judge 是当前最实用方案,但不是完美方案。
RAG 专项评估指标
RAG 有成熟的评估框架(如 RAGAS),核心指标对应 AD4 的反思点:
| 指标 | 衡量什么 | 计算方式 |
|---|---|---|
| Faithfulness | 答案忠实于文档(防幻觉) | 答案声明能否在文档找到支持 |
| Answer Relevance | 答案切题 | 答案是否回答了问题 |
| Context Precision | 检索精度 | 检索的文档里相关的比例 |
| Context Recall | 检索召回 | 需要的文档是否都检索到 |
注意 Context Precision/Recall 评估的是检索质量(AD3),Faithfulness/Relevance 评估的是生成质量。分离评估能定位问题在检索还是生成。
在线评估 vs 离线评估
| 维度 | 离线评估 | 在线评估 |
|---|---|---|
| 时机 | 发布前,固定数据集 | 运行时,真实流量 |
| 目的 | 回归测试、对比方案 | 监控质量、发现退化 |
| 成本 | 可控(固定样本) | 高(每个请求都评) |
| 实现 | 评估脚本 | EvaluatorCallbackHandler |
| 典型 | 本章的 run_evaluation | LangSmith 在线评估 |
与生产实践对照
| 评估概念 | OpenCode 对应 | |
|---|---|---|
| LLM-as-judge 自动评估 | → | 无(依赖人工使用反馈) |
| 标准化测试套件 | → | effect-drizzle-sqlite 测试基建 |
| EvaluatorCallbackHandler 运行时 | → | OTel span + 事件流 |
| 评估数据集 | → | 无标准化评估集 |
| 成本/效率监控 | → | session.ts getUsage 成本统计 |
对比可观测/容错/部署(都有成熟方案),Agent 评估至今仍是开放难题——没有银弹,业界还在探索。LangChain 提供了工具(EvaluatorCallbackHandler、standard-tests、LangSmith 评估),但如何定义"好"、如何构建评估集、如何应对非确定性仍需大量人工设计。这恰恰说明:掌握评估能力,是高级 Agent 工程师的稀缺技能。
进阶阶段完成!
恭喜完成全部 10 个进阶章节。回顾四大主题:
- 多 Agent 协作(AD1-2)—— Supervisor/层级拓扑、Handoff、子图通信
- 高级 RAG(AD3-4)—— 检索策略、CRAG/Self-RAG 自我反思
- 记忆系统(AD5-6)—— Checkpoint 深度、Store 语义长期记忆
- 可观测/生产(AD7-10)—— 追踪、容错、部署、评估
从"会用框架"到"精通原理与生产工程",你已掌握构建生产级 Agent 系统的完整知识体系。
小结
- Agent 评估难在:非确定、多维度、多步骤、无客观标准。
- 核心思路:从"断言"转向"多维打分",LLM-as-judge 是当前主流。
- 四大维度:忠实度(防幻觉)/相关性(切题)/正确性(事实)/效率(成本)。
- LangChain 工具:EvaluatorCallbackHandler(运行时)、standard-tests(集成规范)、LangSmith(平台)。
- RAG 专项:Context Precision/Recall 评检索,Faithfulness/Relevance 评生成。
- 评估仍是开放难题,掌握它是稀缺技能。