第21章 Agent评估与可观测性
来源:https://ai-agent-guide.xiaofuge.cn/chapters/ch17-evaluation.html 所属:第七篇-工程化
第六篇:工程化 — 怎么知道你的 Agent 好不好?
21.1 Agent 评估的挑战
传统软件有明确的对错——单元测试通过就行。但 Agent 的输出是自然语言,"好不好"变得模糊:
传统软件评估
- 输入确定 → 输出确定
- 单元测试覆盖即可
- 有明确的对错标准
- 性能指标:延迟、吞吐量
Agent 评估
- 相同输入 → 不同输出(LLM 随机性)
- 难以定义"正确"
- 多步骤、多轮交互
- 需评估:准确度、相关性、安全性、效率
21.2 Agent 评估的六个维度
评估一个 Agent 不能只看"最终答案对不对"。传统软件测试断言的是确定性输出,而 Agent 的价值恰恰体现在达成目标的过程——它可能最终答案正确但绕了 20 步弯路(低效),也可能答案正确但中途泄露了敏感信息(不安全)。因此需要从六个正交维度立体评估,分别对应 Agent 的不同能力侧面。效果和质量衡量"做得对不对、好不好",效率和成本衡量"做得快不快、省不省",鲁棒性和安全性衡量"遇到意外和不怀好意时扛不扛得住"。下面这张图展开这六个维度及其典型指标:
21.3 四种评估方法
🔬 怎么评估 Agent? | 方法 | 原理 | 优缺点 | 适合场景 | | --- | --- | --- | --- | | 人工标注 | 人判断输出好不好 | ✓最准 ✗贵、慢 | 小规模、最终验收 | | LLM-as-Judge | 用另一个 LLM 评判 | ✓快、便宜 ✗有偏差 | 大规模、迭代优化 | | 基准测试集 | 预设问答对,比对标准答案 | ✓可重复 ✗覆盖有限 | 回归测试、CI/CD | | 用户反馈 | 收集真实用户评分 | ✓最真实 ✗噪声大 | 线上监控、持续改进 | ### LLM-as-Judge 示例
eval_prompt = """
你是一个严格的评估员。请评估以下 Agent 回答的质量。
用户问题:{question}
Agent 回答:{answer}
参考答案:{reference}
请从以下维度打分(1-5分):
1. 准确性:回答是否正确,与参考答案一致
2. 完整性:是否回答了问题的所有方面
3. 相关性:回答是否切题,没有多余内容
4. 表达清晰度:语言是否清晰易懂
输出 JSON 格式:
{
"accuracy": 4,
"completeness": 3,
"relevance": 5,
"clarity": 4,
"overall": 4,
"comments": "回答基本正确但缺少XX细节"
}
"""
# 批量评估
scores = []
for sample in test_dataset:
result = judge_llm.invoke(
eval_prompt.format(
question=sample.question,
answer=sample.agent_answer,
reference=sample.reference
)
)
scores.append(parse_json(result))
const evalPrompt = `
你是一个严格的评估员。请评估以下 Agent 回答的质量。
用户问题:{question}
Agent 回答:{answer}
参考答案:{reference}
请从以下维度打分(1-5分):
1. 准确性:回答是否正确,与参考答案一致
2. 完整性:是否回答了问题的所有方面
3. 相关性:回答是否切题,没有多余内容
4. 表达清晰度:语言是否清晰易懂
输出 JSON 格式:
{
"accuracy": 4,
"completeness": 3,
"relevance": 5,
"clarity": 4,
"overall": 4,
"comments": "回答基本正确但缺少XX细节"
}
`;
// 批量评估
const scores = [];
for (const sample of testDataset) {
const result = await judgeLlm.invoke(
evalPrompt
.replace('{question}', sample.question)
.replace('{answer}', sample.agentAnswer)
.replace('{reference}', sample.reference)
);
scores.push(JSON.parse(result));
}
package main
import (
"encoding/json"
"fmt"
"strings"
)
const evalPrompt = `
你是一个严格的评估员。请评估以下 Agent 回答的质量。
用户问题:{question}
Agent 回答:{answer}
参考答案:{reference}
请从以下维度打分(1-5分):
1. 准确性:回答是否正确,与参考答案一致
2. 完整性:是否回答了问题的所有方面
3. 相关性:回答是否切题,没有多余内容
4. 表达清晰度:语言是否清晰易懂
输出 JSON 格式:
{
"accuracy": 4,
"completeness": 3,
"relevance": 5,
"clarity": 4,
"overall": 4,
"comments": "回答基本正确但缺少XX细节"
}
`
// 批量评估
func batchEvaluate(testDataset []Sample, judgeLlm LLMClient) []map[string]interface{} {
var scores []map[string]interface{}
for _, sample := range testDataset {
prompt := strings.ReplaceAll(evalPrompt, "{question}", sample.Question)
prompt = strings.ReplaceAll(prompt, "{answer}", sample.AgentAnswer)
prompt = strings.ReplaceAll(prompt, "{reference}", sample.Reference)
result := judgeLlm.Invoke(prompt)
var score map[string]interface{}
json.Unmarshal([]byte(result), &score)
scores = append(scores, score)
}
return scores
}
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.*;
public class AgentEvaluator {
static final String EVAL_PROMPT = """
你是一个严格的评估员。请评估以下 Agent 回答的质量。
用户问题:{question}
Agent 回答:{answer}
参考答案:{reference}
请从以下维度打分(1-5分):
1. 准确性:回答是否正确,与参考答案一致
2. 完整性:是否回答了问题的所有方面
3. 相关性:回答是否切题,没有多余内容
4. 表达清晰度:语言是否清晰易懂
输出 JSON 格式:
{
"accuracy": 4,
"completeness": 3,
"relevance": 5,
"clarity": 4,
"overall": 4,
"comments": "回答基本正确但缺少XX细节"
}
""";
// 批量评估
public List> batchEvaluate(List testDataset, LLMClient judgeLlm) {
List> scores = new ArrayList<>();
ObjectMapper mapper = new ObjectMapper();
for (Sample sample : testDataset) {
String prompt = EVAL_PROMPT
.replace("{question}", sample.getQuestion())
.replace("{answer}", sample.getAgentAnswer())
.replace("{reference}", sample.getReference());
String result = judgeLlm.invoke(prompt);
try {
Map score = mapper.readValue(result, Map.class);
scores.add(score);
} catch (Exception e) {
scores.add(Map.of("error", e.getMessage()));
}
}
return scores;
}
}
21.4 Agent 可观测性
评估是离线的——测试集跑完看分数。可观测性是在线的——生产环境中实时监控 Agent 行为。 📊 Agent 可观测性的三个层次
1. 日志(Logging)
记录每一步:用户输入、LLM 调用、工具调用、返回结果。最基本的可观测性。
2. 指标(Metrics)
聚合统计:Token 消耗、响应延迟、成功率、工具调用次数。用于监控和告警。
3. 追踪(Tracing)
全链路追踪:一次用户请求经过的所有步骤、每次 LLM 调用的完整 prompt 和 response。
主流可观测性工具 | 工具 | 定位 | 特点 | | --- | --- | --- | | LangSmith | LangChain 官方 | 与 LangChain/LangGraph 深度集成 | | Langfuse | 开源可观测 | 开源、自部署、框架无关 | | Phoenix | Arize AI 出品 | 支持评估+追踪+幻觉检测 | | OpenTelemetry | 通用标准 | 与现有 APM 系统集成 | ## 21.5 Agent 监控的黄金指标
📈 生产环境必须监控的指标
质量指标
- 任务成功率(应该 > 90%)
- 幻觉率(应该 80%)
- 工具调用成功率
性能指标
- 首 token 延迟(P95 90% | | 步数效率 | 实际步数 / 最优步数(比值越接近1越好) | Agent 规划能力评估 | 95% | | 鲁棒性 | 异常输入下正常处理数 / 异常输入总数 × 100% | 压力测试、对抗测试 | > 85% | | 工具调用准确率 | 正确工具调用数 / 总工具调用数 × 100% | 工具选择与参数生成质量评估 | > 95% | | 幻觉率 | 含幻觉回答数 / 总回答数 × 100% | 事实准确性监控 | - 任务完成率 (95) 准确率 (90) 效率 (85) 鲁棒性 (80) 安全性 (92)
理想目标值
当前版本评分
21.7 LangSmith 完整实战
LangSmith 是 LangChain 官方推出的 Agent 评估与可观测性平台,提供了从 Trace 收集、数据分析到评估数据集管理的完整工具链。下面通过一个完整的实战案例,演示如何将 LangSmith 集成到 Agent 项目中,实现全链路追踪和自动化评估。
评估流水线图
🔄 LangSmith 评估流水线
下图展示了 LangSmith 评估的完整流程:从数据集创建、Agent 运行、评估器打分到结果对比分析。每个环节都通过 API 自动化完成,无需手动操作。
Dataset 创建评估数据集
Run Agent 执行并追踪
Evaluate 评估器打分
Compare 对比实验结果
create_dataset() @traceable + invoke() evaluate() experiment_prefix
LangSmith 集成与评估代码
import os
from langsmith import Client, traceable
from langchain.chat_models import ChatOpenAI
from langchain.agents import create_react_agent, AgentExecutor
from langchain import hub
from langchain.tools import Tool
from langchain_community.tools import DuckDuckGoSearchRun
# 1. 配置环境变量
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "lsv2_pt_xxx"
os.environ["LANGCHAIN_PROJECT"] = "ai-agent-eval"
os.environ["OPENAI_API_KEY"] = "sk-xxx"
# 2. 定义工具
search_tool = DuckDuckGoSearchRun()
calc_tool = Tool(
name="calculator",
description="用于数学计算,输入数学表达式",
func=lambda expr: str(eval(expr)),
)
# 3. 装饰需要追踪的函数
@traceable(run_type="chain", name="qa_agent")
def run_qa_agent(question: str) -> str:
llm = ChatOpenAI(model="gpt-4o", temperature=0)
prompt = hub.pull("hwchase17/react")
tools = [search_tool, calc_tool]
agent = create_react_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
result = executor.invoke({"input": question})
return result["output"]
# 4. 创建评估数据集
client = Client()
dataset = client.create_dataset(
dataset_name="qa-eval-v1",
description="问答 Agent 评估集"
)
examples = [
{"inputs": {"question": "北京今天天气?"}, "outputs": {"answer": "晴,25度"}},
{"inputs": {"question": "1+1等于几?"}, "outputs": {"answer": "2"}},
{"inputs": {"question": "AI Agent 是什么?"}, "outputs": {"answer": "AI Agent 是能自主使用工具完成任务的智能体"}},
]
client.create_examples(dataset_id=dataset.id, examples=examples)
# 5. 定义评估器
def correctness(run, example):
"""检查预测结果是否包含参考答案"""
pred = run.outputs["output"]
ref = example.outputs["answer"]
return {"score": 1.0 if ref in pred else 0.0}
def conciseness(run, example):
"""评估回答简洁性:少于100词得满分"""
pred = run.outputs["output"]
word_count = len(pred.split())
score = 1.0 if word_count
import { Client } from 'langsmith'; import { ChatOpenAI } from '@langchain/openai'; import { createReactAgent, AgentExecutor } from '@langchain/langgraph'; import { Tool } from '@langchain/core/tools'; import { DuckDuckGoSearchRun } from '@langchain/community/tools/duckduckgo_search';
// 1. 配置环境变量 process.env.LANGCHAIN_TRACING_V2 = 'true'; process.env.LANGCHAIN_API_KEY = 'lsv2_pt_xxx'; process.env.LANGCHAIN_PROJECT = 'ai-agent-eval'; process.env.OPENAI_API_KEY = 'sk-xxx';
// 2. 定义工具 const searchTool = new DuckDuckGoSearchRun(); const calcTool = new Tool({ name: 'calculator', description: '用于数学计算,输入数学表达式', func: async (expr: string) => String(eval(expr)), });
// 3. 需要追踪的函数 async function runQaAgent(question: string): Promise { const llm = new ChatOpenAI({ model: 'gpt-4o', temperature: 0 }); const tools = [searchTool, calcTool]; const agent = createReactAgent({ llm, tools }); const executor = new AgentExecutor({ agent, tools, verbose: true }); const result = await executor.invoke({ input: question }); return result.output; }
// 4. 创建评估数据集 const client = new Client(); const dataset = await client.createDataset({ datasetName: 'qa-eval-v1', description: '问答 Agent 评估集', });
const examples = [ { inputs: { question: '北京今天天气?' }, outputs: { answer: '晴,25度' } }, { inputs: { question: '1+1等于几?' }, outputs: { answer: '2' } }, { inputs: { question: 'AI Agent 是什么?' }, outputs: { answer: 'AI Agent 是能自主使用工具完成任务的智能体' } }, ]; await client.createExamples({ datasetId: dataset.id, examples });
// 5. 定义评估器 function correctness(run: any, example: any) { const pred = run.outputs.output; const ref = example.outputs.answer; return { score: ref.includes(pred) ? 1.0 : 0.0 }; }
function conciseness(run: any, example: any) { const pred = run.outputs.output; const wordCount = pred.split(' ').length; const score = wordCount
package main
import (
"fmt"
"os"
)
// 1. 配置环境变量
func init() {
os.Setenv("LANGCHAIN_TRACING_V2", "true")
os.Setenv("LANGCHAIN_API_KEY", "lsv2_pt_xxx")
os.Setenv("LANGCHAIN_PROJECT", "ai-agent-eval")
os.Setenv("OPENAI_API_KEY", "sk-xxx")
}
// 2. 定义工具类型
type Tool struct {
Name string
Description string
Func func(string) (string, error)
}
// 3. 需要追踪的函数
func runQaAgent(question string, llm LLMClient, tools []Tool) (string, error) {
// 创建 Agent 执行器并调用
result, err := agentExecutor(question, llm, tools)
if err != nil {
return "", err
}
return result, nil
}
// 4. 评估数据集结构
type EvalExample struct {
Inputs map[string]string `json:"inputs"`
Outputs map[string]string `json:"outputs"`
}
// 5. 评估器
type EvalResult struct {
Score float64 `json:"score"`
Comment string `json:"comment,omitempty"`
}
func correctness(pred, ref string) EvalResult {
if contains(pred, ref) {
return EvalResult{Score: 1.0}
}
return EvalResult{Score: 0.0}
}
func conciseness(pred string) EvalResult {
wordCount := len(splitWords(pred))
if wordCount
import java.util.*;
public class LangSmithEval { // 1. 配置环境变量 static { System.setProperty("LANGCHAIN_TRACING_V2", "true"); System.setProperty("LANGCHAIN_API_KEY", "lsv2_pt_xxx"); System.setProperty("LANGCHAIN_PROJECT", "ai-agent-eval"); System.setProperty("OPENAI_API_KEY", "sk-xxx"); }
// 2. 定义工具 static class Tool { String name; String description; ToolFunc func; }
// 3. 需要追踪的函数 public String runQaAgent(String question, LLMClient llm, List tools) { AgentExecutor executor = new AgentExecutor(llm, tools); Map result = executor.invoke(Map.of("input", question)); return result.get("output"); }
// 4. 评估数据集 static class EvalExample { Map inputs; Map outputs; }
// 5. 评估器 public Map correctness(String pred, String ref) { double score = pred.contains(ref) ? 1.0 : 0.0; return Map.of("score", score); }
public Map conciseness(String pred) { int wordCount = pred.split("\s+").length; double score = wordCount dataset) { for (int i = 0; i c = correctness(pred, ex.outputs.get("answer")); System.out.printf("Case %d: correctness=%.1f%n", i, c.get("score")); } } }
上面的代码展示了 LangSmith 的完整工作流:先通过 `@traceable` 装饰器自动收集 Trace,再通过 `Client` 创建评估数据集和样本,最后用 `evaluate` 函数批量运行评估。每次评估都会在 LangSmith 平台生成一个实验记录,支持多版本对比分析。
### Trace 分析与调试
from langsmith import Client from datetime import datetime, timedelta
client = Client()
查询最近24小时的追踪记录
runs = client.list_runs( project_name="ai-agent-eval", start_time=datetime.now() - timedelta(hours=24), run_type="chain", error=False, )
分析 Token 消耗与延迟
for run in runs: total_tokens = run.prompt_tokens + run.completion_tokens latency = (run.end_time - run.start_time).total_seconds() print(f"Run: {run.name} | Tokens: {total_tokens} | Latency: {latency:.1f}s | Status: {run.status}")
找出耗时最长的 Top 5 请求
slowest = sorted(runs, key=lambda r: (r.end_time - r.start_time), reverse=True)[:5] for run in slowest: print(f"[SLOW] {run.name}: {(run.end_time - run.start_time).total_seconds():.1f}s")
导出失败 Trace 用于离线分析
failed_runs = client.list_runs( project_name="ai-agent-eval", start_time=datetime.now() - timedelta(hours=24), error=True, ) for run in failed_runs: print(f"[ERROR] {run.name}: {run.error}")
import { Client } from 'langsmith';
const client = new Client();
// 查询最近24小时的追踪记录 const now = new Date(); const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
const runs = await client.listRuns({ projectName: 'ai-agent-eval', startTime: yesterday, runType: 'chain', error: false, });
// 分析 Token 消耗与延迟
for (const run of runs) {
const totalTokens = (run.promptTokens ?? 0) + (run.completionTokens ?? 0);
const latency = (new Date(run.endTime).getTime() - new Date(run.startTime).getTime()) / 1000;
console.log(Run: ${run.name} | Tokens: ${totalTokens} | Latency: ${latency.toFixed(1)}s | Status: ${run.status});
}
// 找出耗时最长的 Top 5 请求
const slowest = [...runs]
.sort((a, b) => {
const aDur = new Date(a.endTime).getTime() - new Date(a.startTime).getTime();
const bDur = new Date(b.endTime).getTime() - new Date(b.startTime).getTime();
return bDur - aDur;
})
.slice(0, 5);
for (const run of slowest) {
const dur = (new Date(run.endTime).getTime() - new Date(run.startTime).getTime()) / 1000;
console.log([SLOW] ${run.name}: ${dur.toFixed(1)}s);
}
// 导出失败 Trace 用于离线分析
const failedRuns = await client.listRuns({
projectName: 'ai-agent-eval',
startTime: yesterday,
error: true,
});
for (const run of failedRuns) {
console.log([ERROR] ${run.name}: ${run.error});
}
package main
import ( "fmt" "time" )
func main() { client := NewLangSmithClient()
// 查询最近24小时的追踪记录 now := time.Now() yesterday := now.Add(-24 * time.Hour) runs, err := client.ListRuns("ai-agent-eval", yesterday, now, "chain", false) if err != nil { panic(err) }
// 分析 Token 消耗与延迟 for _, run := range runs { totalTokens := run.PromptTokens + run.CompletionTokens latency := run.EndTime.Sub(run.StartTime).Seconds() fmt.Printf("Run: %s | Tokens: %d | Latency: %.1fs | Status: %s\n", run.Name, totalTokens, latency, run.Status) }
// 找出耗时最长的 Top 5 请求 sort.Slice(runs, func(i, j int) bool { return runs[i].EndTime.Sub(runs[i].StartTime) > runs[j].EndTime.Sub(runs[j].StartTime) }) for i, run := range runs { if i >= 5 { break } dur := run.EndTime.Sub(run.StartTime).Seconds() fmt.Printf("[SLOW] %s: %.1fs\n", run.Name, dur) }
// 导出失败 Trace 用于离线分析 failedRuns, _ := client.ListRuns("ai-agent-eval", yesterday, now, "", true) for _, run := range failedRuns { fmt.Printf("[ERROR] %s: %s\n", run.Name, run.Error) } }
import java.time.; import java.util.; import java.util.stream.*;
public class TraceAnalyzer { public static void main(String[] args) { LangSmithClient client = new LangSmithClient();
// 查询最近24小时的追踪记录 LocalDateTime now = LocalDateTime.now(); LocalDateTime yesterday = now.minusHours(24); List runs = client.listRuns("ai-agent-eval", yesterday, now, "chain", false);
// 分析 Token 消耗与延迟 for (Run run : runs) { int totalTokens = run.getPromptTokens() + run.getCompletionTokens(); double latency = Duration.between(run.getStartTime(), run.getEndTime()).toSeconds(); System.out.printf("Run: %s | Tokens: %d | Latency: %.1fs | Status: %s%n", run.getName(), totalTokens, latency, run.getStatus()); }
// 找出耗时最长的 Top 5 请求 List slowest = runs.stream() .sorted((a, b) -> Long.compare( Duration.between(b.getStartTime(), b.getEndTime()).toMillis(), Duration.between(a.getStartTime(), a.getEndTime()).toMillis())) .limit(5) .collect(Collectors.toList()); for (Run run : slowest) { double dur = Duration.between(run.getStartTime(), run.getEndTime()).toSeconds(); System.out.printf("[SLOW] %s: %.1fs%n", run.getName(), dur); }
// 导出失败 Trace 用于离线分析 List failedRuns = client.listRuns("ai-agent-eval", yesterday, now, null, true); for (Run run : failedRuns) { System.out.printf("[ERROR] %s: %s%n", run.getName(), run.getError()); } } }
## 21.8 评估驱动开发(EDD)方法论
评估驱动开发(Evaluation-Driven Development,简称 EDD)是一种以评估为核心的 Agent 开发方法论。它的核心理念是:先定义评估标准和数据集,再开发 Agent 逻辑。这类似于测试驱动开发(TDD),但评估集替代了单元测试,LLM 评估器替代了断言。
**🔄 EDD 开发流程**
**❌ 传统开发**
开发 Agent 逻辑
- 手动测试几个 case
- 上线后发现效果不好
- 回头改 prompt、改工具
- 反复试错,效率低下
**✅ EDD 开发**
- 设计评估数据集(50+ case)
- 定义评估器和通过标准
- 跑基线评估(即使 Agent 还没写好)
- 开发 Agent,每改一版跑评估
- 评估分数达标后再上线
### 评估集设计原则
📐 评估集设计四原则 | 原则 | 说明 | 示例 | | --- | --- | --- | | 代表性 | 覆盖真实用户场景的典型问题 | 从真实日志中提取高频问题 | | 多样性 | 包含简单、中等、困难不同难度 | 简单30% + 中等50% + 困难20% | | 边界覆盖 | 包含异常输入和边界条件 | 空输入、超长文本、注入攻击 | | 可演进 | 持续补充新发现的 bad case | 线上 bad case 定期归入评估集 | ### 离线评估 vs 在线评估
**离线评估(Offline)**
在固定数据集上运行 Agent,用评估器自动打分。
**特点**:可重复、快速迭代、成本低。
**用途**:开发阶段 prompt 优化、模型选型、回归测试。
**局限**:评估集覆盖有限,无法发现新问题。
**在线评估(Online)**
在生产环境中收集真实用户反馈和行为数据。
**特点**:最真实、持续运行、噪声大。
**用途**:线上质量监控、发现新 bad case、A/B 测试。
**局限**:反馈滞后、需要流量、不可重复。
### A/B 测试在 Agent 中的应用
import random from dataclasses import dataclass from typing import Optional
@dataclass class ABTestConfig: """A/B 测试配置""" experiment_name: str variant_a_name: str # 对照组(如 gpt-4o) variant_b_name: str # 实验组(如 gpt-4o-mini) traffic_split: float # B 组流量比例,如 0.2 表示 20% min_sample_size: int = 100 # 最小样本量 metrics: list = None # 关注的指标列表
class AgentABTester: def init(self, config: ABTestConfig): self.config = config self.results_a = [] # A 组结果 self.results_b = [] # B 组结果
def assign_variant(self, user_id: str) -> str: """根据用户 ID 确定性分配分组,保证同一用户始终在同一组""" hash_val = hash(user_id) % 100 / 100 return self.config.variant_b_name if hash_val dict: """分析 A/B 测试结果,计算关键指标差异""" def stats(results): if not results: return {"success_rate": 0, "avg_latency": 0, "avg_tokens": 0} n = len(results) return { "success_rate": sum(r["success"] for r in results) / n, "avg_latency": sum(r["latency"] for r in results) / n, "avg_tokens": sum(r["tokens"] for r in results) / n, } return {self.config.variant_a_name: stats(self.results_a), self.config.variant_b_name: stats(self.results_b)}
interface ABTestConfig { experimentName: string; variantAName: string; // 对照组 variantBName: string; // 实验组 trafficSplit: number; // B 组流量比例 minSampleSize: number; // 最小样本量 metrics?: string[]; // 关注的指标列表 }
interface TestResult { success: boolean; latency: number; tokens: number; }
class AgentABTester { private config: ABTestConfig; private resultsA: TestResult[] = []; private resultsB: TestResult[] = [];
constructor(config: ABTestConfig) { this.config = config; }
/** 根据用户 ID 确定性分配分组 */ assignVariant(userId: string): string { const hashVal = (this.hashString(userId) % 100) / 100; return hashVal { if (results.length === 0) { return { successRate: 0, avgLatency: 0, avgTokens: 0 }; } const n = results.length; return { successRate: results.filter(r => r.success).length / n, avgLatency: results.reduce((sum, r) => sum + r.latency, 0) / n, avgTokens: results.reduce((sum, r) => sum + r.tokens, 0) / n, }; }; return { [this.config.variantAName]: stats(this.resultsA), [this.config.variantBName]: stats(this.resultsB), }; }
private hashString(s: string): number { let hash = 0; for (let i = 0; i resultsA = new ArrayList<>(); private List resultsB = new ArrayList<>();
public AgentABTester(ABTestConfig config) { this.config = config; }
/** 根据用户 ID 确定性分配分组 */ public String assignVariant(String userId) { int hash = Math.abs(userId.hashCode()); double hashVal = (hash % 100) / 100.0; return hashVal > analyze() { return Map.of( config.variantAName, stats(resultsA), config.variantBName, stats(resultsB) ); }
private Map stats(List results) { if (results.isEmpty()) { return Map.of("success_rate", 0.0, "avg_latency", 0.0, "avg_tokens", 0.0); } int n = results.size(); int successSum = 0; double latencySum = 0; int tokenSum = 0; for (TestResult r : results) { if (r.success) successSum++; latencySum += r.latency; tokenSum += r.tokens; } return Map.of( "success_rate", (double) successSum / n, "avg_latency", latencySum / n, "avg_tokens", (double) tokenSum / n ); } }
A/B 测试是 Agent 线上迭代的核心手段。通过流量分割,将一部分用户导向新版本 Agent,对比关键指标(成功率、延迟、Token 消耗)的差异,决定是否全量发布。注意:Agent 的 A/B 测试需要更大的样本量,因为 LLM 输出方差大,单次请求的随机性可能掩盖真实差异。
### 回归测试设计
import json from pathlib import Path
回归测试集结构
class RegressionTestSuite: def init(self, test_data_path: str): self.path = Path(test_data_path) self.test_cases = self._load()
def _load(self): """加载回归测试集""" with open(self.path) as f: return json.load(f)
def run(self, agent_fn, evaluators): """运行回归测试,返回通过率和退化用例""" results = [] for case in self.test_cases: pred = agent_fn(case["input"]) scores = [ev(pred, case) for ev in evaluators] passed = all(s["score"] >= 0.8 for s in scores) results.append({ "case_id": case["id"], "passed": passed, "scores": scores, "input": case["input"][:50], }) passed_count = sum(r["passed"] for r in results) regressions = [r for r in results if not r["passed"]] return {"pass_rate": passed_count / len(results), "regressions": regressions}
使用示例
suite = RegressionTestSuite("tests/agent_regression_v1.json") report = suite.run(run_qa_agent, [correctness, conciseness]) print(f"通过率: {report['pass_rate']:.0%}") print(f"退化用例: {len(report['regressions'])}")
import * as fs from 'fs';
interface TestCase { id: string; input: string; expected?: string; }
interface TestResult { case_id: string; passed: boolean; scores: Array; input: string; }
// 回归测试集 class RegressionTestSuite { private testCases: TestCase[];
constructor(testDataPath: string) { this.testCases = JSON.parse(fs.readFileSync(testDataPath, 'utf-8')); }
/** 运行回归测试,返回通过率和退化用例 */ run( agentFn: (input: string) => Promise, evaluators: Array { score: number; comment?: string }> ): { passRate: number; regressions: TestResult[] } { const results: TestResult[] = []; for (const testCase of this.testCases) { const pred = agentFn(testCase.input); // sync for example const scores = evaluators.map(ev => ev(pred, testCase)); const passed = scores.every(s => s.score >= 0.8); results.push({ case_id: testCase.id, passed, scores, input: testCase.input.slice(0, 50), }); } const passedCount = results.filter(r => r.passed).length; const regressions = results.filter(r => !r.passed); return { passRate: passedCount / results.length, regressions, }; } }
// 使用示例
const suite = new RegressionTestSuite('tests/agent_regression_v1.json');
const report = suite.run(runQaAgent, [correctness, conciseness]);
console.log(通过率: ${(report.passRate * 100).toFixed(0)}%);
console.log(退化用例: ${report.regressions.length});
package main
import ( "encoding/json" "fmt" "os" )
type TestCase struct {
ID string json:"id"
Input string json:"input"
}
type ScoreResult struct {
Score float64 json:"score"
Comment string json:"comment,omitempty"
}
type TestResult struct {
CaseID string json:"case_id"
Passed bool json:"passed"
Scores []ScoreResult json:"scores"
Input string json:"input"
}
// RegressionTestSuite 回归测试集 type RegressionTestSuite struct { testCases []TestCase }
func NewRegressionTestSuite(path string) (*RegressionTestSuite, error) { data, err := os.ReadFile(path) if err != nil { return nil, err } var cases []TestCase if err := json.Unmarshal(data, &cases); err != nil { return nil, err } return &RegressionTestSuite{testCases: cases}, nil }
// Run 运行回归测试 func (s *RegressionTestSuite) Run(agentFn func(string) (string, error), evaluators []func(string, TestCase) ScoreResult) (float64, []TestResult) { var results []TestResult for _, c := range s.testCases { pred, _ := agentFn(c.Input) var scores []ScoreResult for _, ev := range evaluators { scores = append(scores, ev(pred, c)) } passed := true for _, sc := range scores { if sc.Score testCases;
static class TestCase { public String id; public String input; }
static class ScoreResult { public double score; public String comment; ScoreResult(double score) { this.score = score; } ScoreResult(double score, String comment) { this.score = score; this.comment = comment; } }
static class TestResult { public String caseId; public boolean passed; public List scores; public String input; }
public RegressionTestSuite(String testDataPath) throws Exception { ObjectMapper mapper = new ObjectMapper(); testCases = mapper.readValue(new File(testDataPath), mapper.getTypeFactory().constructCollectionType(List.class, TestCase.class)); }
/** 运行回归测试 */ public Map run( java.util.function.Function agentFn, List> evaluators) { List results = new ArrayList<>(); for (TestCase testCase : testCases) { String pred = agentFn.apply(testCase.input); List scores = new ArrayList<>(); for (var ev : evaluators) { scores.add(ev.apply(pred, testCase)); } boolean passed = scores.stream().allMatch(s -> s.score >= 0.8); TestResult tr = new TestResult(); tr.caseId = testCase.id; tr.passed = passed; tr.scores = scores; tr.input = testCase.input.substring(0, Math.min(50, testCase.input.length())); results.add(tr); } long passedCount = results.stream().filter(r -> r.passed).count(); List regressions = results.stream() .filter(r -> !r.passed) .collect(Collectors.toList()); return Map.of( "pass_rate", (double) passedCount / results.size(), "regressions", regressions ); } }
回归测试是 EDD 方法论的安全网。每次修改 Agent 的 prompt、工具或模型后,都跑一遍回归测试集,确保改动不会引入退化。建议将回归测试集成到 CI/CD 流水线中,在每次 PR 合并前自动运行,退化用例数必须为 0 才允许合并。
## 21.9 评估框架:Ragas 与 DeepEval
手动评估效率低、不可重复。专业评估框架可以**自动化**评估流程,提供量化指标,让你像跑单元测试一样跑 Agent 评估。
### 21.9.1 Ragas:RAG 评估的事实标准
**Ragas**(Retrieval Augmented Generation Assessment)是专为 RAG 系统设计的评估框架,提供 4 个核心指标: | 指标 | 评估什么 | 计算方式 | | --- | --- | --- | | **Faithfulness(忠实度)** | 回答是否基于检索内容?有没有编造? | 将回答拆分语句,逐条验证是否可从上下文推导 | | **Answer Relevance(回答相关性)** | 回答是否切题? | 用 LLM 从回答反推可能的问题,与原问题比较相似度 | | **Context Precision(上下文精确率)** | 检索到的文档中有多少是相关的? | 判断每个检索文档是否包含答案,计算精确率 | | **Context Recall(上下文召回率)** | 答案所需的信息是否都被检索到了? | 将标准答案拆分,逐条验证是否在检索内容中 | ```
from ragas import evaluate
from ragas.metrics import (
faithfulness, # 忠实度
answer_relevancy, # 回答相关性
context_precision, # 上下文精确率
context_recall # 上下文召回率
)
from datasets import Dataset
# 准备评估数据集
eval_data = {
"question": [
"Agent 的记忆系统有哪些类型?",
"MCP 协议解决了什么问题?",
"LangGraph 的 Checkpoint 机制有什么用?",
],
"answer": [
"Agent 的记忆分为短期记忆和长期记忆……", # Agent 的回答
"MCP 解决了工具定义不统一的问题……",
"Checkpoint 机制用于状态保存和人工干预……",
],
"contexts": [
["短期记忆存储对话历史……", "长期记忆使用向量数据库……"], # 检索到的文档
["MCP 之前每个框架有自己的工具格式……"],
["LangGraph 的 Checkpoint 每步自动保存……"],
],
"ground_truth": [
"记忆分为短期记忆(对话历史)和长期记忆(向量存储)", # 标准答案
"MCP 统一了 Agent 与工具的连接标准",
"用于状态持久化、人工干预、错误恢复",
],
}
dataset = Dataset.from_dict(eval_data)
# 运行评估
result = evaluate(
dataset=dataset,
metrics=[faithfulness, answer_relevancy, context_precision, context_recall],
llm=ChatOpenAI(model="gpt-4o"), # 评估用的 LLM
embeddings=OpenAIEmbeddings(), # 用于 answer_relevancy
)
print(result)
# {'faithfulness': 0.85, 'answer_relevancy': 0.92,
# 'context_precision': 0.78, 'context_recall': 0.88}
import { evaluate } from 'ragas';
import { faithfulness, answerRelevancy, contextPrecision, contextRecall } from 'ragas/metrics';
import { ChatOpenAI } from '@langchain/openai';
import { OpenAIEmbeddings } from '@langchain/openai';
// 准备评估数据集
const evalData = {
question: [
'Agent 的记忆系统有哪些类型?',
'MCP 协议解决了什么问题?',
'LangGraph 的 Checkpoint 机制有什么用?',
],
answer: [
'Agent 的记忆分为短期记忆和长期记忆……',
'MCP 解决了工具定义不统一的问题……',
'Checkpoint 机制用于状态保存和人工干预……',
],
contexts: [
['短期记忆存储对话历史……', '长期记忆使用向量数据库……'],
['MCP 之前每个框架有自己的工具格式……'],
['LangGraph 的 Checkpoint 每步自动保存……'],
],
ground_truth: [
'记忆分为短期记忆(对话历史)和长期记忆(向量存储)',
'MCP 统一了 Agent 与工具的连接标准',
'用于状态持久化、人工干预、错误恢复',
],
};
// 运行评估
const result = await evaluate({
dataset: evalData,
metrics: [faithfulness, answerRelevancy, contextPrecision, contextRecall],
llm: new ChatOpenAI({ model: 'gpt-4o' }),
embeddings: new OpenAIEmbeddings(),
});
console.log(result);
// {'faithfulness': 0.85, 'answer_relevancy': 0.92,
// 'context_precision': 0.78, 'context_recall': 0.88}
package main
import (
"fmt"
)
// EvalSample 评估样本
type EvalSample struct {
Question string
Answer string
Contexts []string
GroundTruth string
}
// EvalResult 评估结果
type EvalResult struct {
Faithfulness float64 `json:"faithfulness"`
AnswerRelevancy float64 `json:"answer_relevancy"`
ContextPrecision float64 `json:"context_precision"`
ContextRecall float64 `json:"context_recall"`
}
func main() {
// 准备评估数据集
evalData := []EvalSample{
{
Question: "Agent 的记忆系统有哪些类型?",
Answer: "Agent 的记忆分为短期记忆和长期记忆……",
Contexts: []string{"短期记忆存储对话历史……", "长期记忆使用向量数据库……"},
GroundTruth: "记忆分为短期记忆(对话历史)和长期记忆(向量存储)",
},
{
Question: "MCP 协议解决了什么问题?",
Answer: "MCP 解决了工具定义不统一的问题……",
Contexts: []string{"MCP 之前每个框架有自己的工具格式……"},
GroundTruth: "MCP 统一了 Agent 与工具的连接标准",
},
}
// 运行评估(通过调用 ragas HTTP API 或 Python 服务)
result := evaluateRagas(evalData)
fmt.Printf("%+v\n", result)
// {Faithfulness:0.85 AnswerRelevancy:0.92 ContextPrecision:0.78 ContextRecall:0.88}
}
func evaluateRagas(samples []EvalSample) EvalResult {
// Go 生态中无原生 Ragas 库,通常通过 HTTP 调用 Python Ragas 服务
// 或使用通用 LLM 评估框架实现等效逻辑
return EvalResult{0.85, 0.92, 0.78, 0.88}
}
import java.util.*;
public class RagasEvaluation {
// 评估样本
static class EvalSample {
String question;
String answer;
List contexts;
String groundTruth;
}
// 评估结果
static class EvalResult {
double faithfulness;
double answerRelevancy;
double contextPrecision;
double contextRecall;
public String toString() {
return String.format("{faithfulness=%.2f, answer_relevancy=%.2f, " +
"context_precision=%.2f, context_recall=%.2f}",
faithfulness, answerRelevancy, contextPrecision, contextRecall);
}
}
public static void main(String[] args) {
// 准备评估数据集
List evalData = new ArrayList<>();
EvalSample s1 = new EvalSample();
s1.question = "Agent 的记忆系统有哪些类型?";
s1.answer = "Agent 的记忆分为短期记忆和长期记忆……";
s1.contexts = Arrays.asList("短期记忆存储对话历史……", "长期记忆使用向量数据库……");
s1.groundTruth = "记忆分为短期记忆(对话历史)和长期记忆(向量存储)";
evalData.add(s1);
// 运行评估(Java 生态中通常通过 HTTP 调用 Python Ragas 服务)
EvalResult result = evaluateRagas(evalData);
System.out.println(result);
// {faithfulness=0.85, answer_relevancy=0.92, context_precision=0.78, context_recall=0.88}
}
static EvalResult evaluateRagas(List samples) {
// Java 生态中无原生 Ragas 库
// 可通过 HTTP 调用 Python Ragas 服务,或使用 LangChain4j 的评估工具
EvalResult r = new EvalResult();
r.faithfulness = 0.85;
r.answerRelevancy = 0.92;
r.contextPrecision = 0.78;
r.contextRecall = 0.88;
return r;
}
}
21.9.2 DeepEval:通用 Agent 评估框架
DeepEval 不局限于 RAG,提供更通用的 Agent 评估指标,支持单元测试风格的断言。
from deepeval import assert_test
from deepeval.test_case import LLMTestCase
from deepeval.metrics import (
AnswerRelevancyMetric,
FaithfulnessMetric,
HallucinationMetric,
ToxicityMetric,
GEval # 自定义评估指标
)
# 定义评估指标
answer_relevancy = AnswerRelevancyMetric(threshold=0.7)
faithfulness = FaithfulnessMetric(threshold=0.8)
hallucination = HallucinationMetric(threshold=0.5) # 幻觉分数越低越好
# 创建测试用例(类似单元测试)
test_case = LLMTestCase(
input="什么是 ReAct 模式?",
actual_output="ReAct 是让 LLM 在推理和行动之间交替的框架……", # Agent 回答
expected_output="ReAct 让 Agent 在推理(Thought)和行动(Action)之间循环", # 期望
context=["ReAct 模式由 Yao 等人提出,核心是 Thought-Action-Observation 循环"], # 上下文
)
# 运行评估 + 断言(CI/CD 友好)
assert_test(test_case, [answer_relevancy, faithfulness, hallucination])
# 如果任何指标低于阈值,测试失败 → CI 阻止发布
# 自定义指标:用 G-Eval 定义你自己的评估标准
correctness = GEval(
name="Correctness",
criteria="""判断回答是否在事实上正确,逻辑是否严密,
是否完整覆盖了用户问题的所有方面""",
threshold=0.7
)
import { assertTest, LLMTestCase } from 'deepeval';
import {
AnswerRelevancyMetric,
FaithfulnessMetric,
HallucinationMetric,
ToxicityMetric,
GEval,
} from 'deepeval/metrics';
// 定义评估指标
const answerRelevancy = new AnswerRelevancyMetric({ threshold: 0.7 });
const faithfulness = new FaithfulnessMetric({ threshold: 0.8 });
const hallucination = new HallucinationMetric({ threshold: 0.5 });
// 创建测试用例(类似单元测试)
const testCase = new LLMTestCase({
input: '什么是 ReAct 模式?',
actualOutput: 'ReAct 是让 LLM 在推理和行动之间交替的框架……',
expectedOutput: 'ReAct 让 Agent 在推理(Thought)和行动(Action)之间循环',
context: ['ReAct 模式由 Yao 等人提出,核心是 Thought-Action-Observation 循环'],
});
// 运行评估 + 断言(CI/CD 友好)
assertTest(testCase, [answerRelevancy, faithfulness, hallucination]);
// 如果任何指标低于阈值,测试失败 → CI 阻止发布
// 自定义指标:用 G-Eval 定义你自己的评估标准
const correctness = new GEval({
name: 'Correctness',
criteria: `判断回答是否在事实上正确,逻辑是否严密,
是否完整覆盖了用户问题的所有方面`,
threshold: 0.7,
});
package main
import (
"fmt"
"testing"
)
// LLMTestCase 测试用例
type LLMTestCase struct {
Input string
ActualOutput string
ExpectedOutput string
Context []string
}
// Metric 评估指标接口
type Metric interface {
Measure(testCase LLMTestCase) float64
GetThreshold() float64
GetName() string
}
// AnswerRelevancyMetric 回答相关性指标
type AnswerRelevancyMetric struct{ Threshold float64 }
func (m AnswerRelevancyMetric) Measure(tc LLMTestCase) float64 { return 0.85 }
func (m AnswerRelevancyMetric) GetThreshold() float64 { return m.Threshold }
func (m AnswerRelevancyMetric) GetName() string { return "answer_relevancy" }
// FaithfulnessMetric 忠实度指标
type FaithfulnessMetric struct{ Threshold float64 }
func (m FaithfulnessMetric) Measure(tc LLMTestCase) float64 { return 0.90 }
func (m FaithfulnessMetric) GetThreshold() float64 { return m.Threshold }
func (m FaithfulnessMetric) GetName() string { return "faithfulness" }
// assertTest 断言测试
func assertTest(t *testing.T, testCase LLMTestCase, metrics []Metric) {
for _, m := range metrics {
score := m.Measure(testCase)
if score context;
}
// 评估指标接口
interface Metric {
double measure(LLMTestCase testCase);
double getThreshold();
String getName();
}
// 回答相关性指标
static class AnswerRelevancyMetric implements Metric {
double threshold;
AnswerRelevancyMetric(double threshold) { this.threshold = threshold; }
public double measure(LLMTestCase tc) { return 0.85; }
public double getThreshold() { return threshold; }
public String getName() { return "answer_relevancy"; }
}
// 忠实度指标
static class FaithfulnessMetric implements Metric {
double threshold;
FaithfulnessMetric(double threshold) { this.threshold = threshold; }
public double measure(LLMTestCase tc) { return 0.90; }
public double getThreshold() { return threshold; }
public String getName() { return "faithfulness"; }
}
// 断言测试
void assertTest(LLMTestCase testCase, List metrics) {
for (Metric m : metrics) {
double score = m.measure(testCase);
assertTrue(score >= m.getThreshold(),
String.format("Metric %s: score %.2f 90%)、幻觉率(80%)
**性能指标**:首 token 延迟(P9590%。
**② 平均推理步数**:步数越少 → 模型推理成本越低 → 响应速度越快 → 体验更好。反映 Agent 的"效率"而非"能力"。
**③ 工具调用准确率**:避免无效/错误的工具调用。反映 Agent "决策精准度"——选对了工具、传对了参数才算成功。
**④ 影子测试**:生产环境同时运行新旧两套 Agent 逻辑,对比输出差异,精准验证优化效果。这是专业评估的标志,避免"凭感觉说优化了"的模糊表述。
面试要点:四维框架覆盖能力(成功率)、效率(步数)、精准度(工具准确率)、验证(影子测试),体现系统化评估思维。
## 21.10 代码与 Agent 评测基准
评估一个 Agent "到底有多强",不能只靠主观感受,需要标准化的评测基准。2025-2026 年,业界形成了几大权威评测榜单,是企业招聘和技术选型的核心参考。
### 21.10.1 SWE-bench:Coding Agent 的黄金标准
**SWE-bench** 是普林斯顿大学提出的软件工程评测基准,被 OpenAI、Anthropic、Google、字节跳动等头部厂商公认为 Coding Agent 的权威排行榜。
**SWE-bench 的评测方式**:从 12 个开源 Python 项目(Django、Flask、scikit-learn 等)中提取 **2,294 个真实 GitHub Issue**。Agent 需要自主理解 Issue → 定位代码 → 编写修复 → 提交 PR。评测标准:Agent 提交的代码是否能通过项目的**隐藏单元测试**。
**SWE-bench Lite**:精选 300 个单文件修复题,降低评测成本,适合快速验证。
**SWE-bench Verified**:人工标注的 500 题,确保题目描述清晰、可解,是目前最权威的子集。 | 模型/Agent | SWE-bench Verified | 排名时间 | 关键技术 | | --- | --- | --- | --- | | Claude Sonnet 4 + Claude Code | 72.7% | 2025.05 | Harness + 工具调用 + 自验证 | | GPT-4.1 + Codex | 68.3% | 2025.04 | 云沙箱 + 异步执行 | | DeepSeek V3 + Agent | 38.8% | 2025.01 | 开源模型最佳 | | 纯 GPT-4o(无工具) | 8.4% | 2024.10 | 无 Harness 基线 | 从基线 8.4% 到 72.7% 的飞跃,证明的不是模型变强了多少,而是 **Harness Engineering**(测试沙箱 + 工具调用 + 自验证钩子)的价值。同一个模型加不加 Harness,成绩差 5-8 倍。
### 21.10.2 HumanEval / MBPP:代码生成评测
**HumanEval** 是 OpenAI 提出的经典代码生成评测,包含 164 个 Python 编程题,评估指标为 **pass@k**(生成 k 个方案中至少一个通过测试的概率)。 | 评测基准 | 题目数 | 语言 | 评测指标 | 特点 | | --- | --- | --- | --- | --- | | HumanEval | 164 | Python | pass@1 / pass@10 | 函数级代码生成 | | HumanEval+ | 164 | Python | pass@1 | 增强测试用例(80k+ 断言) | | MBPP | 974 | Python | pass@1 | 基础编程题,难度偏低 | | LiveCodeBench | 持续更新 | 多语言 | pass@1 | 从 LeetCode/Codeforces 实时抓取新题 | | BigCodeBench | 1140 | Python | pass@1 | 需调用标准库 + 第三方库 | **面试要点**:HumanEval 评测的是"单次代码生成能力"(给定函数签名和文档,生成正确实现),不涉及 Agent 的工具调用、多轮交互、代码调试能力。**SWE-bench 评测的才是"Coding Agent 综合能力"**(理解 Issue → 搜索代码 → 编写修复 → 运行测试 → 修复错误)。面试时如果被问"怎么评测 Coding Agent",回答 SWE-bench 比 HumanEval 更专业。
### 21.10.3 AgentBench:多维度 Agent 评测
**AgentBench** 是清华大学的提出的多维度 Agent 评测框架,覆盖 8 个不同场景,全面评估 Agent 的综合能力: | 评测维度 | 场景 | 评估能力 | | --- | --- | --- | | Operating System | Linux 终端操作 | CLI 工具调用、文件操作 | | Database | SQL 查询与数据操作 | 数据库交互、NL2SQL | | Knowledge Graph | 知识图谱问答 | 多跳推理、知识检索 | | Card Game | 卡牌对战 | 策略推理、博弈论 | | House Holding | 家务模拟(ALFRED) | 多步任务规划 | | Web Shopping | 网购模拟 | Web 浏览、表单填写 | | Web Browsing | 网页浏览问答 | 信息提取、页面导航 | | Mind2Web | 真实网站操作 | GUI Agent 能力 | ### 21.10.4 评测基准选型决策
#### 📋 评测目标 → 基准选择
▸ 代码生成能力 → HumanEval / MBPP
▸ Coding Agent 综合能力 → SWE-bench
▸ 多场景 Agent 通用能力 → AgentBench
▸ RAG 系统质量 → Ragas / TruLens
▸ 对话质量 → MT-Bench / AlpacaEval
▸ 安全性 → Garak / OWASP LLM Top 10
#### 🎯 企业实战建议
→ 不要只看榜单分数,要在自己的业务数据上评测
→ 构建"私有评测集":从真实业务中抽取 100-200 个 case
→ 评测集要持续更新,防止 Agent 过拟合到固定测试题
→ 同时跟踪公开榜单 + 私有评测集分数
→ 评测自动化:CI/CD 中每次模型/Prompt 变更自动跑评测
### 21.10.5 构建私有 Agent 评测集
Python
TypeScript
```python
# agent_eval_suite.py — 私有 Agent 评测框架
import json
from dataclasses import dataclass
from typing import List, Optional
@dataclass
class EvalCase:
case_id: str
input: str # 用户输入
expected_tools: List[str] # 期望调用的工具
expected_output_contains: Optional[List[str]] # 输出应包含的关键信息
max_steps: int = 10 # 最大允许步数
max_tokens: int = 10000 # Token 预算
category: str = "general"
@dataclass
class EvalResult:
case_id: str
passed: bool
actual_tools: List[str]
steps_used: int
tokens_used: int
failure_reason: str = ""
class AgentEvalSuite:
"""Agent 评测套件"""
def __init__(self, cases: List[EvalCase]):
self.cases = cases
def run(self, agent_executor):
"""运行所有评测用例"""
results = []
for case in self.cases:
result = self._run_case(case, agent_executor)
results.append(result)
# 生成报告
total = len(results)
passed = sum(1 for r in results if r.passed)
avg_steps = sum(r.steps_used for r in results) / total
avg_tokens = sum(r.tokens_used for r in results) / total
print(f"评测报告: {passed}/{total} 通过 ({passed/total*100:.1f}%)")
print(f"平均步数: {avg_steps:.1f}")
print(f"平均Token: {avg_tokens:.0f}")
# 失败用例详情
failures = [r for r in results if not r.passed]
if failures:
print(f"\n失败用例 ({len(failures)}):")
for f in failures:
print(f" ❌ {f.case_id}: {f.failure_reason}")
return results
def _run_case(self, case, agent_executor):
"""运行单个用例"""
trace = agent_executor(case.input, max_steps=case.max_steps)
# 检查工具调用
actual_tools = [t["name"] for t in trace.tool_calls]
tools_match = set(case.expected_tools).issubset(set(actual_tools))
# 检查输出
output_match = True
if case.expected_output_contains:
for expected in case.expected_output_contains:
if expected.lower() not in trace.final_output.lower():
output_match = False
break
# 检查预算
within_budget = trace.total_tokens Promise): Promise {
const results: EvalResult[] = [];
for (const testCase of this.cases) {
const trace = await agentFn(testCase.input, testCase.maxSteps);
const passed = this.evaluate(trace, testCase);
results.push({
caseId: testCase.caseId,
passed: passed.ok,
actualTools: trace.toolCalls.map(t => t.name),
stepsUsed: trace.steps.length,
tokensUsed: trace.totalTokens,
failureReason: passed.reason,
});
}
this.printReport(results);
return results;
}
private printReport(results: EvalResult[]) {
const total = results.length;
const passed = results.filter(r => r.passed).length;
console.log(`评测报告: ${passed}/${total} 通过 (${(passed/total*100).toFixed(1)}%)`);
}
}