9558 字
约 31 分钟
1
第21章 Agent评估与可观测性

第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)}%)`);
}
}
第21章 Agent评估与可观测性
http://www.clxhxhhr.top/posts/723/
作者
clxstart
发布于
2026-09-18
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。