LLM Agent 评估工程:你的 Agent 靠感觉上线,迟早会出大事

大多数团队上线 Agent 的流程是:跑几个 demo,同事点头说"挺好",然后就上了。这不是评估,这是赌博。

赌赢了皆大欢喜。赌输了,你会在两周后收到用户反馈:"它明明可以用搜索工具,为什么每次都在瞎猜?""它把数据库写了两次。""它传的 API 参数格式全错了。"

这些问题靠 demo 根本发现不了。它们藏在决策链的中段,藏在工具调用的参数里,藏在 Agent 自作主张的那几步里。

这篇文章讲的是:你真的要落地一个 Agent 系统时,怎么建立一套靠谱的质检机制。不是学术 benchmark,是工程。

为什么 Agent 评估比 LLM 评估难得多

评估一个普通的 LLM 接口,逻辑很简单:给输入,看输出,打分。评估对象是确定的。

Agent 不一样。

想象一下:你叫了个外卖,结果外卖员迷路了、走错楼道、敲错门,最终还是把餐送到了------但花了 90 分钟。你盯着那碗还算热的饭,该打几星?

Agent 就是这个外卖员。一次用户请求,可能触发 5 次工具调用,每一步的决策都会影响下一步。最终输出错了,可能是因为:

• 第一步理解了意图,但选了错误的工具

• 工具选对了,但传参格式不对

• 前三步都对,第四步推理断了

• 全程逻辑正确,最后总结时编造了一个不存在的数据

只看最终输出,你知道"出问题了",但不知道"哪里出的"。不知道哪里出的,就没法针对性地修------你只能靠感觉调 prompt,然后盯着下一次 demo 看"感觉好多了"。这是个死循环。

Langfuse 团队最近发布的 Agent 评估指南里,有个判断我觉得说到点子上了:评估粒度决定了你能修哪层 bug

三层评估架构:黑盒、破壳、白盒

三层,解决三个不同问题,按需叠加,不需要一次全上。

第一层:最终响应评估(黑盒)

只看输入和最终输出,不管中间发生了什么。成本最低,适合快速验证"到底有没有严重问题"。

工程实现上,用 LLM-as-a-judge 就够。关键不是用哪个模型打分,而是评判 prompt 要写得足够具体。"评估回答是否有用"这种写法,打出来的分方差大、复现性差,等于没用。

推荐写法是:把场景具体化。

ini 复制代码
EVAL_PROMPT = """
你是一个严格的评估员。

用户问题:{user_query}
AI 助手的回答:{agent_response}
参考标准答案:{ground_truth}

请从以下维度打分(1-5分):
1. 事实准确性:答案是否包含错误信息?
2. 完整性:是否回答了用户的核心问题?
3. 有用性:如果用户是初级工程师,按此回答操作,
           能在 30 分钟内解决问题的概率有多大?

输出 JSON:
{{
  "accuracy": 分数,
  "completeness": 分数,
  "usefulness": 分数,
  "reason": "简要说明扣分原因(不扣分就写 OK)"
}}

宁可严格,不要宽松。
"""

第二层:轨迹评估(破壳)

检查 Agent 走的路径对不对------把实际的工具调用序列,和你预期的序列对比。

这层是最有价值、也最容易被忽视的。黑盒评估告诉你"什么出错了",轨迹评估告诉你"哪里出错了"。两者加起来,你才能精确定位 bug 所在。

轨迹评估需要你提前建"标准轨迹"的概念------不只标注正确答案,还要标注预期的执行路径:

css 复制代码
# 测试用例结构:不只有"正确答案",还有"正确路径"
test_case = {
    "user_input": "帮我查一下北京明天的天气,并提醒我带伞",
    "expected_final_answer_keys": ["天气", "温度", "提醒"],
    "expected_trajectory": [
        {
            "step": 1,
            "tool": "weather_query",
            "required_params": ["city", "date"],
            "param_values": {"city": "北京", "date": "tomorrow"}
        },
        {
            "step": 2,
            "tool": "set_reminder",
            "required_params": ["content", "time"],
            "conditions": "only_if_rain_probability > 0.5"
        }
    ]
}

def evaluate_trajectory(actual_trace, expected_trajectory):
    results = []
    for expected_step in expected_trajectory:
        step_num = expected_step["step"]
        
        # 步骤缺失
        if step_num > len(actual_trace):
            results.append({
                "step": step_num,
                "status": "MISSING",
                "issue": f"步骤{step_num}未执行"
            })
            continue
        
        actual_step = actual_trace[step_num - 1]
        
        # 工具选错
        if actual_step["tool"] != expected_step["tool"]:
            results.append({
                "step": step_num,
                "status": "WRONG_TOOL",
                "expected": expected_step["tool"],
                "actual": actual_step["tool"]
            })
            continue
        
        # 缺参数
        missing_params = [            p for p in expected_step.get("required_params", [])
            if p not in actual_step.get("params", {})
        ]
        
        if missing_params:
            results.append({"step": step_num, "status": "MISSING_PARAMS", "missing": missing_params})
        else:
            results.append({"step": step_num, "status": "OK"})
    
    return results

第三层:单步评估(白盒)

类似单元测试,只测某一个决策点,不跑完整流程。

最适合两种场景:一是改了某个工具的 prompt,想验证这个改动有没有副作用;二是某类工具调用总出错,你想单独 debug 那一步的行为逻辑。

python 复制代码
import pytest
from unittest.mock import patch

class TestAgentSearchStep:
    """单步测试:搜索查询的构造质量"""
    
    @pytest.mark.parametrize("user_query,expected_keywords", [
        (
            "Python 里怎么做异步 HTTP 请求",
            ["python", "async", "http"]
        ),
        (
            "帮我找 2024 年最新的 RAG 论文",
            ["RAG", "2024"]
        ),
    ])
    def test_search_query_has_key_terms(self, user_query, expected_keywords):
        agent = YourAgent()
        
        # 拦截实际搜索,只检查查询词构造
        with patch.object(agent, '_call_tool') as mock_tool:
            mock_tool.return_value = {"results": []}
            first_call = agent.get_first_tool_call(user_query)
        
        assert first_call["tool"] == "search"
        query_str = first_call["params"].get("query", "").lower()
        
        for kw in expected_keywords:
            assert kw.lower() in query_str, f"关键词缺失: '{kw}'"
    
    def test_no_hallucination_on_empty_results(self):
        """搜索返回空时,Agent 不能自己编答案"""
        agent = YourAgent()
        
        with patch.object(agent, '_call_tool') as mock_tool:
            mock_tool.return_value = {"results": [], "message": "no_results"}
            response = agent.run("查一下 xxx-nonexistent-package-zzz 的文档")
        
        # 应该承认没找到,不能编
        assert any(w in response for w in ["没有找到", "未找到", "找不到"])

测试数据从哪来:生产失败是金矿

卡在这里的团队很多:三层架构听懂了,但测试用例怎么搞?

最没用的来源是"工程师手工构造典型场景"。工程师能想到的,基本都是系统能处理好的。真正让 Agent 翻车的,是那些你没想到的边界。

最有价值的来源是生产环境里的失败轨迹。具体操作:

• 所有 Agent 调用开启追踪,记录完整的工具调用日志

• 用黑盒评估的 LLM 打分,自动过滤出低分 trace

• 每天人工审 10-20 条低分 trace,标注正确答案和预期轨迹

• 标注完直接进测试数据集

这个流程的好处:数据集自然反映真实用户的行为分布,不是工程师脑补的。跑一个月,你的测试集里全是真正能难倒系统的问题。

追踪实现不需要依赖第三方:

python 复制代码
import time, uuid
from functools import wraps

class AgentTracer:
    def __init__(self, storage_backend):
        self.storage = storage_backend
    
    def trace_tool_call(self, tool_name: str):
        """装饰器:自动追踪工具调用的入参、出参、耗时、状态"""
        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                trace_id = getattr(self, '_current_trace_id', str(uuid.uuid4()))
                start_time = time.time()
                step = {
                    "trace_id": trace_id,
                    "step_id": str(uuid.uuid4()),
                    "tool": tool_name,
                    "input": kwargs,
                    "start_time": start_time,
                }
                try:
                    result = func(*args, **kwargs)
                    step.update({"status": "success", "output": result,
                                 "duration_ms": (time.time() - start_time) * 1000})
                    return result
                except Exception as e:
                    step.update({"status": "error", "error": str(e),
                                 "duration_ms": (time.time() - start_time) * 1000})
                    raise
                finally:
                    self.storage.save_step(step)
            return wrapper
        return decorator

# 用法
tracer = AgentTracer(storage_backend=YourStorage())

class MyAgent:
    @tracer.trace_tool_call("web_search")
    def search(self, query: str, top_k: int = 5):
        return search_api.query(query, top_k=top_k)
    
    @tracer.trace_tool_call("database_query")
    def query_db(self, sql: str):
        return db.execute(sql)

LLM-as-a-Judge 的三个工程坑

用 LLM 打分是目前最主流的方式,但有几个坑踩过才会知道。

坑一:评判模型和被评判模型是同一个

用 GPT-4o 跑 Agent,再用 GPT-4o 做评判------会出现系统性的"自我宽容"偏差。GPT-4o 对自己输出格式的偏好,会让它给同类输出打高分。建议用不同供应商的模型做评判,或者 ensemble 多个取平均。

坑二:评判 prompt 太模糊,分数方差大

把场景具体化,参考上面给出的例子。"30 分钟内能解决"比"是否有用"的评分稳定性高得多,复现性也好。

坑三:只做事后打分,没有接入 CI/CD

评估体系的价值不是算出一个数,而是驱动改进。建议每次改 prompt 或换模型,自动在基准集上跑评估,如果某项分数下降超过阈值,阻断部署:

bash 复制代码
# .github/workflows/agent_eval.yml
name: Agent Evaluation Gate

on:
  pull_request:
    paths:
      - 'prompts/**'
      - 'agent/**'
      - 'tools/**'

jobs:
  evaluate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Run Benchmark
        run: |
          python eval/run_benchmark.py \
            --dataset eval/benchmark_v2.jsonl \
            --output /tmp/eval_results.json \
            --parallel 4
      
      - name: Check Score Thresholds
        run: |
          python eval/check_thresholds.py \
            --results /tmp/eval_results.json \
            --thresholds eval/thresholds.yaml
        # thresholds.yaml 示例:
        # accuracy: 0.85           # 准确性不低于 85%
        # trajectory_match: 0.75   # 轨迹匹配率不低于 75%
        # tool_param_accuracy: 0.90 # 参数准确性不低于 90%

一个被低估的维度:工具参数精确性

大多数团队评估 Agent 时盯的是"最终答案对不对""选了哪个工具",但很少专门测参数的精确性

这个维度其实很关键。Agent 选对了工具,但传了个格式错误的参数------工具调用失败。然后 Agent 可能重试、可能换策略、可能直接编答案。三种情况都会产生问题,根因是同一个:参数格式错了。

参数精确性最适合做白盒测试:

python 复制代码
@pytest.mark.parametrize("user_input,tool_name,param_checks", [
    (
        "查一下明天北京到上海的航班",
        "flight_search",
        {
            "from_city": lambda v: v in ["北京", "PEK", "beijing"],
            "to_city": lambda v: v in ["上海", "SHA", "PVG", "shanghai"],
            # 日期必须是 YYYY-MM-DD 格式,不能是"明天"这种自然语言
            "date": lambda v: re.match(r'\d{4}-\d{2}-\d{2}', str(v)) is not None,
        }
    ),
    (
        "帮我查一下最近 10 篇关于 RAG 的论文",
        "paper_search",
        {
            "query": lambda v: "RAG" in str(v) or "retrieval" in str(v).lower(),
            # limit 必须是整数,不能是字符串 "10"
            "limit": lambda v: isinstance(v, int) and v >= 10,
        }
    ),
])
def test_param_construction(user_input, tool_name, param_checks):
    agent = YourAgent()
    tool_calls = agent.plan_tool_calls(user_input)  # 只规划,不执行
    
    target_call = next((tc for tc in tool_calls if tc["tool"] == tool_name), None)
    assert target_call is not None, f"未调用 {tool_name}"
    
    params = target_call["params"]
    for param_name, check_fn in param_checks.items():
        assert param_name in params, f"缺少参数: {param_name}"
        assert check_fn(params[param_name]), \
            f"参数 {param_name} 不符合预期: {params[param_name]}"

在线 vs 离线:两个都要有

离线评估(固定数据集跑测试)和在线评估(生产流量持续打分)解决的是不同问题,二选一都是错的。

离线评估的硬伤:数据集会老化。用户的提问方式会变,新功能带来新的查询模式,三个月前的数据集可能已经不代表真实情况。

在线评估的硬伤:没有标准答案。线上流量很少有 ground truth,只能做相对质量评估,或者实时用 LLM 打分------引入额外的延迟和成本。

平衡策略很简单:

• 离线评估做发布门控(PR 合并前跑,不过不让上线)

• 在线评估做健康监控(每天看分数趋势是否下滑)

• 在线发现的失败案例,标注后定期补充进离线数据集

• 离线数据集每季度滚动更新:剔除过时的,加入新的高频失败模式

先追踪,后评分------这个顺序不能反

很多团队想一步到位,上来就设计评分体系。这是个常见失误。

在你真正了解 Agent 在生产里的行为模式之前,任何评分体系都是想象出来的。就像你从没见过这条街,就去设计路牌系统------大概率会贴错地方。

正确做法:先追踪,手动看 100 条 trace,你会发现很多意外的模式。Agent 总是在某类查询上绕远路;某个工具的返回值 Agent 经常误读;某种格式的用户输入会触发死循环......

有了这些一手观察,再设计评分维度和测试用例,才是有根基的。Langfuse 的建议是"追踪优先,评分在后",这个顺序很关键。

一张落地清单

如果要给一个 Agent 系统从零建评估体系,可以按这个节奏:

第 1 周:接入追踪,所有调用记完整 trace,先看不评

第 2 周:手动审 trace,找高频失败模式,标注 20-30 个失败案例

第 3 周:建离线数据集,写黑盒评估脚本,看当前基础分是多少

第 4 周:加轨迹评估,针对最高频失败类型写白盒测试

之后持续:集成进 CI/CD,建在线监控,数据集每季度滚动更新

评估体系不是一次建好就不动的基础设施,它是随着系统成熟而持续演进的。那些靠"感觉还不错"就上线的 Agent,迟早会以一种你没预想到的方式出问题。

下一个值得探的方向:当系统里有多个子 Agent 协作时,评估会复杂很多------分布式追踪、跨 Agent 的 trace 关联、怎么给"编排层"的决策质量单独打分,这些都还没有标准答案。有机会单独写一篇。

相关推荐
Fzuim3 小时前
从CLI到分布式智能体:重新理解AI Agent的演进路径与工程现实
人工智能·分布式·ai·agent·agentic
Detachym3 小时前
AI Agent 平台开发与优化:Day1
人工智能·agent
大大花猫4 小时前
Agent Skills:赋予AI智能体专业能力的开放标准
agent
LuoQuHen4 小时前
第四章:Agent架构全景图—— 从最小可行体到完整分层设计
ai·agent
Roselind_Yi5 小时前
【吴恩达2026 Agentic AI】面试向+项目实战(含面试题+项目案例)-2
人工智能·python·机器学习·面试·职场和发展·langchain·agent
Roselind_Yi5 小时前
【吴恩达2026 Agentic AI】面试向+项目实战(含面试题+项目案例)-1
人工智能·python·面试·职场和发展·langchain·gpt-3·agent
deephub5 小时前
多 Agent 验证架构实战:从输出评分到过程验证
人工智能·深度学习·大语言模型·agent
攻城狮_老李5 小时前
从零开始理解 Agent Skills:Skill 创建最佳实践
aigc·agent·ai编程
16Miku6 小时前
飞书 lark-cli 深度解读:当办公软件遇上 AI Agent
人工智能·ai·飞书·agent·claudecode