CodexQA

行业与实践动态与发布

Agents CLI:用小评测集把智能体质量往上爬

CodexQA 团队阅读约 12 分钟

四条用例的基线平均分 4.0000、标准差 1.4142。误贴厨师提示后降到 2.7500。换成 gemini-2.5-flash 后平均分同样是 2.75,但下降几乎全在 off_topic 从 5.0 到 1.0。

本文目录

用 Agents CLI 的评测把智能体质量往上爬

Pier Paolo Ippolito,产品经理。Elia Secchi,解决方案专家。2026 年 10 月 28 日。

一次现场走通 Agents CLI 的评测环:从第一次运行,到回归跟踪、换模型后端,再到定时运行和自动告警。

图 1:评测环总览。

引言

做出一个演示里能用的智能体,和做出一个在模型升级、工具变动、新对话形态下仍然能用的智能体,是两件不同的事。前者一个周末就够;后者要一条环。团队写测试用例,跑一遍,打分,看哪里失败,改智能体,再跑这些用例。分数上升,这次改动就留下;分数下降,就撤回。这样过上几周,智能体一点点顶到模型、工具和提示合在一起所能达到的上限。质量工程里把这叫做爬山(hill-climbing,分数变好就保留改动,变差就退回)。

Agents CLI 的 eval 命名空间就是围着这条环做的。命令很小,也不绑死某一种智能体框架:eval generate 让智能体跑一组用例,eval grade 给生成的轨迹打分,eval compare 把两次运行的结果并排对照,eval analyze 把失败聚成主题,eval optimize 对智能体的提示做一轮自动爬山。轨迹(trace)是这一次运行里按时间留下的发言和工具调用。

下面的顺序是:基线,回归,换模型,定时运行和告警。

图 2:从基线到告警。

准备

全文用的脚手架是 Agents CLI v1.8.0 自带的 LangChain 模板。准备只需要一条命令:

agents-cli create my-agent \
    --agent google/agents-cli/extensions/langchain/template@v1.8.0 \
    -d cloud_run
cd my-agent && agents-cli install

脚手架自带一份评测集 tests/eval/datasets/basic-dataset.json,一份配置 tests/eval/eval_config.yaml,以及一份自定义的大模型评判指标 tests/eval/response_quality.py。大模型评判(LLM-as-judge)是用另一个模型按给定标准给回答打分,而不是只看字符串是否相等。配置里声明要跑的指标:

# tests/eval/eval_config.yaml
metrics_to_run:
  - custom_response_quality

custom_metrics:
  - name: custom_response_quality
    custom_function_file: response_quality.py

这项指标用 gemini-3.8-flash 当评判,按准确、相关和清楚,把每条回答打成 1 到 5 分。默认数据集只有一条无关紧要的用例,不够爬山。他们先把它扩成四条,分别探不同的行为:

{
  "eval_cases": [
    {"name": "greeting",
     "agent_data": {"turns": [{"events": [{"author": "user", "content": {"parts": [{"text": "hello"}]}}]}]}},
    {"name": "sf_weather",
     "agent_data": {"turns": [{"events": [{"author": "user", "content": {"parts": [{"text": "What is the weather in San Francisco?"}]}}]}]}},
    {"name": "rome_weather",
     "agent_data": {"turns": [{"events": [{"author": "user", "content": {"parts": [{"text": "How is the weather in Rome today?"}]}}]}]}},
    {"name": "off_topic",
     "agent_data": {"turns": [{"events": [{"author": "user", "content": {"parts": [{"text": "Who won the 2024 World Series?"}]}}]}]}}
  ]
}

四条用例:一句问候;一个由模拟工具用写死的有雾回答处理的天气问题;一个工具会走默认的城市天气问题;一个离题问题,要智能体自己决定怎么处理。数据集故意做小。爬山环要划算,数据集就得小到每次改动都能跑。

步骤 1:跑第一次评测

数据集就位之后,先 eval generate,让智能体逐条跑,再用 eval grade 给得到的轨迹打分:

agents-cli eval generate --dataset tests/eval/datasets/basic-dataset.json \
    -o tests/eval/output/
agents-cli eval grade --traces tests/eval/output/eval_dataset_*.json \
    --config tests/eval/eval_config.yaml

基线运行得到:

Evaluation Summary

custom_response_quality:
  num_cases_total: 4
  num_cases_valid: 4
  num_cases_error: 0
  mean_score: 4.0000
  stdev_score: 1.4142

Saved full results to artifacts/grade_results/results_20261003_224516.json
Saved HTML results to artifacts/grade_results/results_20261003_224516.html

四条用例的平均分是 5 分制里的 4.0,标准差 1.41。这个标准差立刻说明各条分数并不整齐,这比全场都是平坦的 5.0 更适合当起点。平坦的 5.0 要么是智能体完美(不太可能),要么是数据集太容易,看不出差别。4.0 再加上真实的分散,才是一座可以爬的山。

步骤 2:改一处,再跑一遍

为了演示这条环,他们放进一次说得通的回归。一个同时改好几个智能体项目的开发者,把别的代码库里的厨师人设,误贴进这个天气智能体的系统提示。位置在 app/agent.py:

# Before
root_agent: CompiledStateGraph = create_agent(
    model=llm, tools=[get_weather], system_prompt="You are a helpful assistant"
)

# After
root_agent: CompiledStateGraph = create_agent(
    model=llm, tools=[get_weather],
    system_prompt=(
        "You are a professional chef. Help users with recipes, cooking "
        "techniques, and ingredient substitutions. Always suggest a recipe "
        "at the end of every response."
    ),
)

改动能编译,智能体仍能跑,评测之前的流水线(lint、单元测试、类型检查)什么都抓不到。把同一串 eval generate 加 eval grade 再跑一遍,得到:

Evaluation Summary

custom_response_quality:
  num_cases_total: 4
  num_cases_valid: 4
  num_cases_error: 0
  mean_score: 2.7500
  stdev_score: 0.9574

Saved full results to artifacts/grade_results/results_20261003_225158.json

平均分从 4.0 降到 2.75。评判和他们的直觉一致:天气智能体套上厨师人设,比一个通用的有用助手更差。标准差也从 1.41 收到 0.96,和厨师提示把多数用例推向同一档平庸相符。

在 1 到 5 的尺度上掉 1.25 分,幅度不小。差异看起来很小(“只改了一个字符串字面量”)时,这种回归能从代码评审里滑过去。这条环在智能体到达生产之前,就把改动暴露出来,并且附上一个具体数字。

步骤 3:对照两次运行

图 3:两次运行对照。

平均分只是开头。爬山发生在逐案差异上。eval compare 读两份结果文件,产出一份有结构的 JSON 差异:

agents-cli eval compare \
    artifacts/grade_results/results_20261003_224516.json \
    artifacts/grade_results/results_20261003_225158.json

输出列出哪些键变了、哪些没变:

{
  "baseline_keys": ["agent_info", "eval_case_results", "evaluation_dataset", "metadata", "summary_metrics", "win_rates"],
  "candidate_keys": ["agent_info", "eval_case_results", "evaluation_dataset", "metadata", "summary_metrics", "win_rates"],
  "changed_keys": ["eval_case_results", "metadata.creation_timestamp"],
  "unchanged_keys": ["agent_info", "evaluation_dataset", "metadata", "summary_metrics", "win_rates"],
  "differences": { "... per-case score diffs ...": true }
}

differences 对象里是完整的逐案分解,基线和候选的值并排放。爬山流程里,他们通常用 jq 或一小段 Python 把信号抽出来;原始 JSON 才是完整证据。

步骤 4:读逐案分数

把两份结果文件对齐到每条用例,就能看见回归落在哪里:

case                  baseline → candidate   what the chef prompt did
greeting              5.0 → 4.0  ↓          launched into an unsolicited recipe pitch after "hello"
sf_weather            4.0 → 2.0  ↓          ignored the weather tool, hallucinated conditions
rome_weather          2.0 → 2.0  ·          already hallucinated at baseline; chef persona did not make it worse
off_topic             5.0 → 3.0  ↓          answered the Dodgers question but tacked on a recipe

三条下降,一条保持。三条里最要紧的是 sf_weather:厨师人设把模型的注意力从已注册的 get_weather 工具上拉开,智能体不再调用它,开始编天气。单元测试抓不到:代码能跑,工具仍注册着,但模型围着它的行为变了。只有在上下文里给回答打分的端到端评测,才能把这暴露出来。

图 4:逐案分数。

平均分自己下降 1.25,已经是清楚的信号,但告诉开发者该看哪里的,是逐案分解。一行系统提示的改动,以三种方式穿过三条用例:问候在语气上掉 1 分,sf_weather 在工具使用上掉 2 分,off_topic 在越界上掉 2 分。

步骤 5:更换模型后端

爬山时常见的问题是:更便宜或更快的模型能不能把事情做到可接受。LangChain 模板里,模型写在 app/agent.py:

LLM = "gemini-3.8-flash"
llm = ChatGoogleGenerativeAI(model=LLM, temperature=0)

他们先把有用的系统提示恢复回来,再把 gemini-3.8-flash 换成 gemini-2.5-flash,重新跑 eval generate 加 eval grade,并对照两份结果。平均分从 4.00 降到 2.75,幅度和厨师提示那次回归一样,但逐案分解讲的是完全不同的故事:

case                  3.8-flash → 2.5-flash
greeting              5.0 → 5.0  ·
sf_weather            4.0 → 3.0  ↓
rome_weather          2.0 → 2.0  ·
off_topic             5.0 → 1.0  ↓↓

更便宜的模型在问候和 rome_weather 上与 3.8-flash 完全一样,在 sf_weather 上掉 1 分(措辞没那么利落)。能看见的整段下降都落在 off_topic:更便宜的模型用“这件事还没发生”这种训练截止日期造成的含糊说法拒绝回答,评判给得很严。

教训是汇总数字会误导。只看平均分,会把 gemini-2.5-flash 说得和厨师提示一样差。看逐案分解,会把它看成对智能体大部分工作量都说得通的替换,只有一条用例(处理关于近期事件的越界问题)露出真正的限制。如果生产里用户问题的分布更像前三条而不是第四条,这次替换值得考虑。如果 off_topic 这类问题常见,就不值得。

图 5:换模型对照。

这次实验有两条限制。第一,换模型和提示会互相作用:为更强模型调过的提示,搬到更弱的模型上常常漏掉能力,所以正确的比较是“更弱的模型配上为它调过的提示”,对上“更强的模型配上它自己调过的提示”。第二,Vertex AI Eval Service(用评判模型给轨迹打分的评测服务)在这次对照里保持同一个评判。更换评判是另一次实验,应当分开做。

步骤 6:把环排进日程

Agents CLI 自己不带调度器,但 eval 命令是为了放进常规基础设施。这里可用的模式是 cron 作业或 Cloud Scheduler 触发器,按节奏跑 eval generate 加 eval grade,把结果写到一个持久位置:

#!/usr/bin/env bash
# scripts/scheduled_eval.sh — invoked nightly by cron or Cloud Scheduler
set -euo pipefail
cd "$(dirname "$0")/.."

DATE=$(date +%Y%m%d)
OUT=artifacts/scheduled_runs/$DATE

mkdir -p $OUT
agents-cli eval generate --dataset tests/eval/datasets/regression.json -o $OUT/
agents-cli eval grade --traces $OUT/eval_dataset_*.json \
    --config tests/eval/eval_config.yaml \
    --output $OUT/

对一份稳定回归集做夜间运行,能抓住模型更新、智能体工具里的数据变化,以及在智能体脚下移动的依赖所带来的漂移。对生产智能体,定时运行也能抓住模型供应商不声张的升级,这种升级可以在他们这边没有任何改动时改变智能体行为。定时运行用的数据集通常比内环更大(几十到几百条,而不是几条),因为它一天只跑一次。

步骤 7:分数下降时告警

定时运行每天产出一份结果文件,但没人看的文件不是告警。这里的模式可以是一层包装脚本:把每次新运行的平均分和基线比较(上一次运行,或一份已提交的已知良好结果),下降超过阈值就以非零状态退出。周围的调度器靠这个非零退出去发告警。

页面上的语法高亮把记号拆到了单独的行。下面按源码顺序写出这些语句:

#!/usr/bin/env python3
"""Exit non-zero if an eval result dips below baseline by more than --threshold."""
import argparse, json, sys

def mean(path):
    with open(path) as f:
        return json.load(f)['summary_metrics'][0]['mean_score']

p = argparse.ArgumentParser()
p.add_argument('candidate')
p.add_argument('--baseline', required=True)
p.add_argument('--threshold', type=float, default=0.05)
args = p.parse_args()

b, c = mean(args.baseline), mean(args.candidate)
drop = b - c

if drop > args.threshold:
    print(f"FAIL: mean dropped {b:.3f} → {c:.3f} "
          f"(drop {drop:.3f} > threshold {args.threshold})")
    sys.exit(1)

print(f"OK: mean {b:.3f} → {c:.3f} (drop {drop:.3f} ≤ threshold {args.threshold})")

对着他们的回归演示来跑(基线 4.0,候选是厨师提示那次的 2.75):

$ python3 scripts/check_score.py \
      artifacts/grade_results/results_20261003_225158.json \
      --baseline artifacts/grade_results/results_20261003_224516.json \
      --threshold 0.1
FAIL: mean dropped 4.000 → 2.750 (drop 1.250 > threshold 0.1)
$ echo "exit: $?"
exit: 1

再对着一对完全相同的结果做自比较:

$ python3 scripts/check_score.py \
      artifacts/grade_results/results_20261003_224516.json \
      --baseline artifacts/grade_results/results_20261003_224516.json \
      --threshold 0.1
OK: mean 4.000 → 4.000 (drop 0.000 ≤ threshold 0.1)
$ echo "exit: $?"
exit: 0

非零退出码,就是 cron 作业、Cloud Scheduler 或 GitHub Actions 步骤用来发出告警的东西。

步骤 8:自动优化提示

对 ADK 项目,eval optimize 用 GEPA 框架对智能体提示做一轮自动爬山。GEPA 是按目标指标提出提示变体、留下变好的那些再继续迭代的框架。LangChain 扩展目前不支持这条命令。命令接收数据集和目标指标,提出提示变体,给每一个打分,留下让指标变好的变体,然后迭代。输出是按评测分数排序的一组候选提示,由人审阅后再提升进智能体定义。

agents-cli eval optimize \
    --dataset tests/eval/datasets/regression.json \
    --target-metric final_response_quality \
    --config tests/eval/eval_config.yaml

关于 eval optimize 要明白两件事。第一,它的范围是提示优化,不是任意的智能体改动:工具、结构和模型保持不动。第二,GEPA 有时找到真正的改进,有时找到的提示变体只是贴着这项指标的措辞过拟合,所以分数更高。审阅的人是控制点。这条环适用的项目,纪律是在回归集上跑 eval optimize(改进在这里量得到),并在提升之前仔细看候选提示。

从这条环里学到的

下面是他们自己跑这条环、也看团队这么做时见到的几种做法和反模式。

先用小而有代表性的数据集,不要一上来就求全。刚开始做评测时,人会想盖住智能体永远可能见到的每一种边界。结果是一份要跑一小时、没人会在每次改动时去跑的数据集。像上面这样四条、一分钟内能跑完的用例,锻炼主要行为,并且每次改动都会跑。三百条、要跑一小时的回归集,最好的下场是变成发布闸门,最坏是变成没人用的搁置件。

给结果文件做快照,而不是只留分数。分数是汇总;结果文件才是证据。一次提示改动三周后才露出回归时,改动前后的完整结果 JSON(而不只是平均分)才能让 eval compare 指出具体是哪一条动了。上面步骤 4 的逐案分解,单靠分数做不出来。

把数据集和它的期望分开。数据集是输入。评测配置是期望。它们按不同的节奏变化:生产里出现新的真实用例时数据集长大,团队对“好”的定义成熟时配置才变。两者分成文件、都提交进版本控制,分数一动就能看清变的是哪一半。

对汇总指标保持怀疑。步骤 5 的换模型实验是最清楚的例子。原文写平均分掉了半分,但逐案分解显示下降集中在一条用例上,其余保持或变好。只看平均分的团队会做出错误决定。

更多评测不会自动带来更多修复。他们最近做的一次对照研究发现:使用 agents-cli 的编码智能体跑的评测次数,是不用它的编码智能体的四倍,但评测之后去改智能体的频率和基线差不多。行为上的收益来自技能在构建一开始放进去的东西,不是来自内环里的反复迭代。评测对质量是必要的,但质量来自按评测所显示的去行动。

开始

用几条命令搭一个 Agents CLI 项目,并跑通第一圈评测:

uvx google-agents-cli setup
agents-cli create my-agent
cd my-agent && agents-cli install

agents-cli eval generate --dataset tests/eval/datasets/basic-dataset.json \
    -o tests/eval/output/
agents-cli eval grade --traces tests/eval/output/eval_dataset_*.json \
    --config tests/eval/eval_config.yaml

完整的 eval 命名空间文档在 https://google.github.io/agents-cli/cli/ 。源代码,包括一方评测原语,在 https://github.com/google/agents-cli 。

出处:Pier Paolo Ippolito,Elia Secchi,Hill-Climbing Agent Quality with Agents CLI Evaluation,2026-10-28,https://developers.googleblog.com/hill-climbing-agent-quality-with-agents-cli-evaluation/ ,CC BY 4.0。

觉得有用,转给同事

微信扫码

用微信扫一扫,在手机上打开后即可转发。

用 RSS 订阅

提交勘误