为什么Agent评估比LLM评估更难

LLM评估可以简化为"输入→输出→对比",因为LLM是单轮的。但Agent是多步、多工具、有状态的,评估维度爆炸式增长:

  • 不仅看最终答案对不对,还要看过程是否合理
  • 不仅看单任务表现,还要看跨任务迁移能力
  • 不仅看成功率,还要看效率、成本、安全性

本文构建一个从单任务到多任务的综合评估框架。

第一层:单任务评测

任务级指标矩阵

from dataclasses import dataclass, field
from typing import Any
from enum import Enum

class MetricCategory(Enum):
    ACCURACY = "准确性"
    EFFICIENCY = "效率"
    SAFETY = "安全性"
    ROBUSTNESS = "鲁棒性"

@dataclass
class TaskMetric:
    name: str
    category: MetricCategory
    value: float
    weight: float = 1.0
    description: str = ""

@dataclass
class TaskResult:
    task_id: str
    task_type: str
    success: bool
    metrics: list[TaskMetric] = field(default_factory=list)
    steps_taken: int = 0
    tools_used: list[str] = field(default_factory=list)
    error_log: list[str] = field(default_factory=list)

    def weighted_score(self) -> float:
        total_weight = sum(m.weight for m in self.metrics)
        weighted_sum = sum(m.value * m.weight for m in self.metrics)
        return weighted_sum / total_weight if total_weight > 0 else 0.0

核心指标定义

指标 计算方式 说明
任务成功率 成功次数/总次数 基础指标
步骤效率 最优步数/实际步数 0-1,越高越好
工具选择准确率 正确工具调用/总调用 反映工具使用能力
格式合规率 格式正确输出/总输出 结构化输出能力
错误恢复率 恢复成功次数/错误次数 容错能力
成本效率 最优成本/实际成本 token消耗评估
安全违规率 违规次数/总次数 越低越好

评测执行框架

import asyncio
from abc import ABC, abstractmethod

class TaskEvaluator(ABC):
    @abstractmethod
    async def evaluate(self, agent, task) -> TaskResult:
        pass

class CodeGenerationEvaluator(TaskEvaluator):
    async def evaluate(self, agent, task) -> TaskResult:
        # 执行Agent
        result = await agent.run(task["prompt"])
        
        metrics = []
        
        # 1. 功能正确性(单元测试通过率)
        test_pass = await self._run_tests(result.code, task["test_cases"])
        metrics.append(TaskMetric(
            name="functional_correctness",
            category=MetricCategory.ACCURACY,
            value=test_pass,
            weight=2.0
        ))
        
        # 2. 步骤效率
        optimal_steps = task.get("optimal_steps", 3)
        actual_steps = result.steps
        efficiency = min(optimal_steps / actual_steps, 1.0) if actual_steps > 0 else 0
        metrics.append(TaskMetric(
            name="step_efficiency",
            category=MetricCategory.EFFICIENCY,
            value=efficiency,
            weight=1.0
        ))
        
        # 3. 代码质量(LLM-as-Judge)
        quality_score = await self._llm_judge(
            result.code, 
            criteria=["可读性", "性能", "安全性"]
        )
        metrics.append(TaskMetric(
            name="code_quality",
            category=MetricCategory.ACCURACY,
            value=quality_score,
            weight=1.5
        ))
        
        # 4. 安全检查
        violations = self._check_safety(result.code)
        metrics.append(TaskMetric(
            name="safety_compliance",
            category=MetricCategory.SAFETY,
            value=1.0 - violations / max(len(result.code.split("\n")), 1),
            weight=1.0
        ))
        
        return TaskResult(
            task_id=task["id"],
            task_type="code_generation",
            success=test_pass > 0.8,
            metrics=metrics,
            steps_taken=actual_steps,
            tools_used=result.tools_used,
            error_log=result.errors
        )

    async def _run_tests(self, code: str, test_cases: list) -> float:
        passed = 0
        for tc in test_cases:
            try:
                result = self._execute_code(code, tc["input"])
                if result == tc["expected"]:
                    passed += 1
            except Exception:
                pass
        return passed / len(test_cases) if test_cases else 0

    async def _llm_judge(self, code: str, criteria: list[str]) -> float:
        prompt = f"评估以下代码质量,维度{criteria},给出0-1的分数:\n{code}"
        score = await judge_llm(prompt)
        return score

第二层:多任务综合评测

能力维度模型

class CapabilityModel:
    """Agent能力维度定义"""
    CAPABILITIES = {
        "reasoning": "逻辑推理",
        "coding": "代码生成",
        "extraction": "信息抽取",
        "planning": "任务规划",
        "tool_use": "工具使用",
        "creativity": "创意生成",
        "safety": "安全合规",
        "multilingual": "多语言能力"
    }

    def __init__(self):
        self.dimension_scores: dict[str, list[float]] = {
            dim: [] for dim in self.CAPABILITIES
        }

    def add_result(self, capability: str, score: float):
        if capability in self.dimension_scores:
            self.dimension_scores[capability].append(score)

    def aggregate(self) -> dict[str, float]:
        return {
            dim: sum(scores) / len(scores) if scores else 0.0
            for dim, scores in self.dimension_scores.items()
        }

雷达图生成

import numpy as np

class RadarChart:
    def __init__(self, capabilities: dict[str, float]):
        self.capabilities = capabilities
        self.angles = np.linspace(0, 2 * np.pi, len(capabilities), endpoint=False).tolist()
        self.angles += self.angles[:1]

    def to_plotly_data(self) -> dict:
        values = list(self.capabilities.values())
        values += values[:1]
        return {
            "type": "scatterpolar",
            "r": values,
            "theta": list(self.capabilities.keys()) + [list(self.capabilities.keys())[0]],
            "fill": "toself",
            "name": "Agent能力"
        }

跨任务迁移评测

class TransferEvaluator:
    """评估Agent的跨任务迁移能力"""
    
    async def evaluate_transfer(self, agent, 
                                  source_task: dict, 
                                  target_task: dict) -> float:
        """在源任务上训练/调整后,在目标任务上的表现"""
        
        # 1. 记录基线表现
        baseline = await self._run_task(agent, target_task)
        
        # 2. 在源任务上的经验
        await self._run_task(agent, source_task)
        
        # 3. 再测目标任务
        after = await self._run_task(agent, target_task)
        
        # 4. 迁移增益
        transfer_gain = after - baseline
        
        # 5. 归一化
        max_possible = 1.0 - baseline
        normalized = transfer_gain / max_possible if max_possible > 0 else 0
        
        return normalized

第三层:系统级评估

长期稳定性评测

from collections import defaultdict
import statistics

class StabilityMonitor:
    def __init__(self, window_size: int = 100):
        self.window_size = window_size
        self.results: dict[str, list[float]] = defaultdict(list)

    def record(self, task_type: str, score: float):
        self.results[task_type].append(score)
        if len(self.results[task_type]) > self.window_size:
            self.results[task_type].pop(0)

    def get_stability_metrics(self) -> dict[str, dict]:
        metrics = {}
        for task_type, scores in self.results.items():
            if len(scores) < 10:
                continue
            metrics[task_type] = {
                "mean": statistics.mean(scores),
                "stdev": statistics.stdev(scores),
                "min": min(scores),
                "max": max(scores),
                "cv": statistics.stdev(scores) / statistics.mean(scores) if statistics.mean(scores) > 0 else 0,
                "trend": self._trend(scores)
            }
        return metrics

    def _trend(self, scores: list[float]) -> str:
        if len(scores) < 5:
            return "insufficient_data"
        first_half = statistics.mean(scores[:len(scores)//2])
        second_half = statistics.mean(scores[len(scores)//2:])
        if second_half > first_half * 1.05:
            return "improving"
        elif second_half < first_half * 0.95:
            return "declining"
        return "stable"

评测报告模板

def generate_eval_report(agent_name: str, results: list[TaskResult]) -> str:
    capability = CapabilityModel()
    
    for r in results:
        # 映射任务类型到能力维度
        dim_map = {
            "code_generation": "coding",
            "logical_reasoning": "reasoning",
            "information_extraction": "extraction",
            "task_planning": "planning"
        }
        dim = dim_map.get(r.task_type, "reasoning")
        capability.add_result(dim, r.weighted_score())
    
    scores = capability.aggregate()
    
    report = f"""# Agent评测报告: {agent_name}

## 总览
- 评测任务数: {len(results)}
- 平均得分: {sum(r.weighted_score() for r in results)/len(results):.2f}
- 整体成功率: {sum(r.success for r in results)/len(results):.1%}

## 能力雷达
{scores}

## 详细指标
| 任务类型 | 成功率 | 平均步骤 | 平均得分 |
|----------|--------|----------|----------|
"""
    by_type = {}
    for r in results:
        by_type.setdefault(r.task_type, []).append(r)
    
    for ttype, task_results in by_type.items():
        success_rate = sum(r.success for r in task_results) / len(task_results)
        avg_steps = sum(r.steps_taken for r in task_results) / len(task_results)
        avg_score = sum(r.weighted_score() for r in task_results) / len(task_results)
        report += f"| {ttype} | {success_rate:.1%} | {avg_steps:.1f} | {avg_score:.2f} |\n"
    
    return report

评测集构建原则

原则 说明 示例
覆盖性 覆盖所有能力维度 每个维度≥20题
难度梯度 简单/中等/困难均匀 3:5:2比例
防污染 避免训练数据泄露 用私有数据集
可扩展 支持动态新增 模块化任务定义
可复现 固定随机种子 temperature=0

总结

Agent评估是一个多层次工程:单任务看指标,多任务看雷达,长期看稳定性。核心原则:

  1. 多维度:不只是"做对了没",还要看"做得好不好"
  2. 分层次:单任务→跨任务→系统级,逐层深入
  3. 可对比:标准化评测集 + 固定参数,确保结果可比较
  4. 防过拟合:定期更新评测集,防止Agent针对测试集优化

好的评估框架是Agent持续进步的基石。没有度量就没有改进。