Agno Eval Suite 评测套件实战指南:多 Case 聚合、标签筛选、JSON 报告与 CI 退出码
【免费下载链接】agnoBuild, run, and manage agent platforms.项目地址: https://gitcode.com/GitHub_Trending/ag/agno
本篇技术指南以 Agno 官方 Cookbook 中的 Eval Suite Cookbooks 为核心,系统讲解如何将多个评测用例(Case)聚合为一个评测套件(Suite)运行。你将掌握:通过内置cli()一键跑全量或按标签/名称筛选的评测、为每个 Case 配置独立超时与 Tag、输出结构化 JSON 报告并读取 CI 退出码,以及面向 CI 工作流和嵌入式场景的程序化调用方式(run_cases/arun_cases+SuiteResult.to_dict()),并深入理解其底层实现。
一、什么是 Eval Suite
在 Agno 的评测体系中,单个Case描述"一个输入喂给一个 Agent 或 Team,加上若干可选的检查项"。Eval Suite 则是把这些 Case 组织起来,作为一次聚合的评测套件统一运行,并提供四个关键能力:
- Tag 选择:按标签(如
smoke)筛选要运行的 Case 子集; - 每 Case 超时:为每个 Case 独立设置超时上限,防止单个用例无限期挂起拖垮整条流水线;
- JSON 报告:将汇总结果与逐 Case 明细导出为机器可读的 JSON;
- CI 退出码:通过进程退出码区分"全部通过 / 存在失败 / 未选中任何用例",可直接对接 CI 门禁。
该目录位于 cookbook/09_evals/suite,属于 Agno Evals Cookbook 的组成部分,与accuracy(准确性)、agent_as_judge(LLM 裁判)、reliability(工具调用可靠性)、performance(性能基准)等目录并列,参见 cookbook/09_evals/README.md。套件运行器的核心实现位于 libs/agno/agno/eval/suite.py,是本文所有行为描述的事实依据。
二、目录文件总览
该目录包含两个可直接运行的示例脚本:
| 文件 | 说明 |
|---|---|
suite_basic.py | 两个 Case(judge 判定 + reliability 可靠性检查)通过内置的cli()运行;被测试对象是单个 Agent |
suite_team_scoring.py | 一个 Team 通过套件运行(leader 委派给 calculator 成员和 writer 成员);reliability 检查能看到成员的真实工具调用,每个答案由 1-10 数值型 judge 打分 |
两者都从agno.eval导入Case与cli,其余导入分别对应 Agent 与 Team 场景:
- suite_basic.py:
from agno.eval import Case, cli - suite_team_scoring.py:
from agno.eval import Case, JudgeMode, cli
三、标准用法:内置 CLI
两个示例脚本的if __name__ == "__main__"分支都以sys.exit(cli(CASES))结束,即把命令行解析、运行、渲染与退出码全部交给内置 CLI。以下命令可直接从仓库根目录执行:
python cookbook/09_evals/suite/suite_basic.py # 运行全部 cases python cookbook/09_evals/suite/suite_basic.py --list # 仅列出 cases,不运行 python cookbook/09_evals/suite/suite_basic.py --tag smoke # 只运行带 smoke 标签的子集 python cookbook/09_evals/suite/suite_basic.py --name factorial_uses_calculator # 只运行指定名称的 case python cookbook/09_evals/suite/suite_basic.py --json-output tmp/evals.json # 导出 JSON 报告 python cookbook/09_evals/suite/suite_basic.py -v # 每个 case 渲染完整运行面板命令行的完整参数由 suite.py 中的argparse定义:
| 参数 | 默认值 | 作用 |
|---|---|---|
--name | None | 只运行指定名称的 Case |
--tag | None | 只运行带该标签的 Case |
--timeout | 套件默认default_timeout(120 秒) | 默认的每 Case 超时(秒),Case 自身设置了timeout_seconds时以 Case 为准 |
--json-output | None | 将机器可读的 JSON 结果写入指定路径 |
--list | False | 只列出被选中的 Case,不运行 |
-v/--verbose | False | 每个 Case 结束后渲染完整运行面板(Message、Tool Calls、Response) |
3.1 退出码约定(CI 门禁依据)
cli()返回的退出码由 suite.py 决定,是 CI 判断的关键契约:
- 0:所有选中的 Case 全部通过;
- 1:存在任何失败(包括
--json-output写入失败); - 2:没有 Case 匹配选择器(例如标签拼写错误、名称不存在)。
特别注意一个防御性设计:空套件也算失败。在 SuiteResult.status 的实现中,status在没有结果时直接返回"FAIL"——原因在于 CI 门禁通常比较== "PASS",一个拼错标签的空套件绝不能"什么都没运行却绿灯放行"。这也正是--tag打错时进程以 2 退出的原因:CLI 会打印no cases selected并列出所有可用 Case 名称。
四、示例一:suite_basic.py(单 Agent 双检查)
suite_basic.py 是套件的最小完整示例,展示了两种检查类型的组合:
import sys from agno.agent import Agent from agno.eval import Case, cli from agno.models.openai import OpenAIResponses from agno.tools.calculator import CalculatorTools # 1. 创建被测 Agent agent = Agent( id="math-tutor", model=OpenAIResponses(id="gpt-5.5"), tools=[CalculatorTools()], instructions="Use the calculator tools for any arithmetic.", ) # 2. 声明 Cases CASES = ( Case( name="factorial_uses_calculator", agent=agent, input="What is 10! (ten factorial)?", tags=("smoke",), criteria="States that 10! equals 3628800.", expected_tool_calls=("factorial",), ), Case( name="explains_compound_interest", agent=agent, input="Explain compound interest in one short paragraph.", criteria="Explains that interest is earned on both the principal and previously earned interest.", ), ) # 3. 运行套件 if __name__ == "__main__": sys.exit(cli(CASES))两个 Case 各侧重一种检查:
factorial_uses_calculator:同时启用judge 检查(criteria判定回答内容是否正确陈述 10! = 3628800)与reliability 检查(expected_tool_calls=("factorial",)要求运行期间真实调用过factorial工具),并打了smoke标签;explains_compound_interest:仅启用 judge 检查(criteria要求解释"利息同时基于本金和已赚利息产生"),无标签。
五、示例二:suite_team_scoring.py(Team + 数值裁判)
suite_team_scoring.py 将套件能力延伸到 Team 场景,并引入JudgeMode.NUMERIC数值评分:
import sys from agno.agent import Agent from agno.eval import Case, JudgeMode, cli from agno.models.openai import OpenAIResponses from agno.team.team import Team from agno.tools.calculator import CalculatorTools # 1. 创建 Team 及其成员 calculator = Agent( id="calculator", model=OpenAIResponses(id="gpt-5.5"), tools=[CalculatorTools()], instructions="Use the calculator tools for every arithmetic operation. Never compute arithmetic yourself.", ) writer = Agent( id="writer", model=OpenAIResponses(id="gpt-5.5"), instructions="Answer in one clear paragraph.", ) assistant_team = Team( id="assistant-team", model=OpenAIResponses(id="gpt-5.5"), members=[calculator, writer], instructions="Delegate arithmetic to the calculator member and writing to the writer member, then report the member's result.", ) # 2. 声明 Cases——均针对 Team CASES = ( Case( name="team_uses_calculator", team=assistant_team, input="What is 4891 multiplied by 7238?", tags=("smoke",), criteria="States that the product is 35,401,058.", judge_mode=JudgeMode.NUMERIC, judge_threshold=7, expected_tool_calls=("multiply",), ), Case( name="team_explains_clearly", team=assistant_team, input="Explain compound interest in one paragraph.", criteria="Explains that interest is earned on both the principal and previously earned interest.", judge_mode=JudgeMode.NUMERIC, judge_threshold=7, ), ) # 3. 运行套件 if __name__ == "__main__": sys.exit(cli(CASES))该示例与示例一的关键差异:
- 被测对象是 Team 而非 Agent:
Case(team=assistant_team, ...)传入的是Team实例,leader 会把算术任务委派给 calculator 成员、把写作任务委派给 writer 成员; - reliability 检查深入到成员层级:
expected_tool_calls=("multiply",)校验的是成员真正执行的multiply工具调用,而不是 leader 的delegate_task_to_member委派调用; - 每个回答都由数值裁判打分:
judge_mode=JudgeMode.NUMERIC让裁判输出 1-10 的分数,judge_threshold=7为及格线(分数 ≥ 7 判 PASS)。
5.1 JudgeMode 与阈值语义
JudgeMode定义在 suite.py,是一个 str 枚举:
JudgeMode.BINARY(默认):裁判给出 pass/fail 二元判定;JudgeMode.NUMERIC:裁判给出 1-10 分数,judge_threshold(默认 7)作为及格线,分数达到阈值才判 PASS。
需要说明的是,Case 中的judge_threshold会被透传给AgentAsJudgeEval的threshold字段(见 suite.py),最终由 agent_as_judge.py 中的数值评分指令执行——分数 1-2 为完全不符、5-6 为部分成功但有明显问题、9-10 为完全符合或超出标准。Case 构造时会校验judge_threshold必须在 1-10 之间(见 suite.py),越界直接抛ValueError。
六、Case 字段与构造校验
Case是一个frozen dataclass(见 suite.py),完整字段如下:
| 字段 | 默认值 | 含义 |
|---|---|---|
name | 必填 | Case 名称,用于--name筛选与报告标识 |
input | 必填 | 喂给 Agent/Team 的输入文本 |
agent/team | None | 被测对象,二选一必填 |
tags | () | 标签元组,用于--tag筛选 |
timeout_seconds | None | 每 Case 独立超时(秒);未设置时回退到套件级default_timeout(120 秒) |
criteria | None | 设置后启用 judge 检查(AgentAsJudgeEval) |
judge_model | None | 每 Case 的裁判模型覆盖;未设置时回退到套件级judge_model,再回退到AgentAsJudgeEval默认模型 |
judge_mode | BINARY | 裁判评分模式 |
judge_threshold | 7 | 数值模式及格线(1-10),仅在NUMERIC模式下生效 |
expected_tool_calls | None | 设置后启用 reliability 检查(ReliabilityEval) |
allow_additional_tool_calls | True | 宽松模式:允许出现预期之外的工具调用(子集匹配) |
setup/teardown | None | 生命周期钩子:setup在运行前执行(超时计时之外),其返回值作为context传给teardown;teardown在 setup 完成后无论通过/失败/出错/超时都会执行,并接收(context, result)以便检查result.error/result.timed_out |
scorer/expected | None | 进程内 scorer 检查(agno.scorer 协议),运行在 Case 超时之内,仅对可评分的运行执行 |
Case在构造时(__post_init__)会做三类校验(见 suite.py):
- agent/team 互斥:两者都不传或都传都会抛
ValueError; - 必须存在至少一种检查:
criteria、expected_tool_calls、scorer三者全空时抛错。这里用的是真值判断而非is None,因此criteria=""或expected_tool_calls=()这类"空检查"也会被拒绝——防止构造出"绿色通过但什么都没验证"的假 CI 门禁; - judge_mode 合法性:未知模式直接抛错;
NUMERIC模式下judge_threshold超出 1-10 抛错。
七、程序化调用:run_cases / arun_cases
除命令行外,套件运行器还暴露了两个面向程序化调用的 API(CI 工作流、嵌入式场景),关键特性是:runner 本身不做任何控制台 I/O——所有展示都通过on_case_start/on_run_event/on_case_end三个钩子完成(见 suite.py),cli()本身也只是这些公开 API 的一个消费者。
from agno.eval import run_cases, arun_cases # 同步版本 suite = run_cases(CASES) # 整个套件运行在单个事件循环上 payload = suite.to_dict() # 机器可读结果,CI 直接消费 # 异步版本(用于已运行事件循环的场景) suite = await arun_cases(CASES)run_cases/arun_cases的完整签名(见 suite.py)支持与 CLI 对齐的筛选与定制:
| 参数 | 默认值 | 含义 |
|---|---|---|
cases | 必填 | 待筛选的 Case 序列 |
tag/name | None | 按标签/名称筛选 |
default_timeout | 120 | 每 Case 默认超时(秒),Case.timeout_seconds优先 |
judge_model | None | 套件级默认裁判模型,Case.judge_model优先 |
db | None | 透传给AgentAsJudgeEval/ReliabilityEval,用于把评测结果写入存储 |
on_case_start/on_case_end/on_run_event | None | 展示钩子,分别在每个 Case 运行前、结束后以及每个流式运行事件时回调 |
钩子还有两条实现细节值得注意:
- 展示钩子必须是同步可调用对象,直接在事件循环上内联执行;传入异步钩子会被检测并以
TypeError拒绝(见 suite.py)。而setup/teardown则相反,同步可调用对象通过asyncio.to_thread执行、异步可调用对象被直接await; - 套件运行被取消(如服务端 cancel、或 agno 将 KeyboardInterrupt 转换成的取消)时会中止后续 Case,未运行的 Case 以
skipped=True和error="skipped: suite aborted after cancelled run"的形式补进结果列表(见 suite.py),保证报告与to_dict()的 Case 数量始终一致。
7.1 SuiteResult 与 to_dict() 载荷
SuiteResult聚合了每个 Case 的结果,其to_dict()输出是一个对 CI 消费者稳定的契约(见 suite.py),顶层结构为:
{ "summary": { "total": 2, "passed": 2, "failed": 0, "status": "PASS" }, "cases": [ { "name": "factorial_uses_calculator", "agent_id": "math-tutor", "team_id": null, "tags": ["smoke"], "session_id": "eval-factorial_uses_calculator-1a2b3c4d", "duration_seconds": 4.213, "judge_passed": true, "judge_reason": "The output states that 10! equals 3628800.", "judge_score": null, "reliability_passed": true, "output": "10! is 3628800.", "tools_called": ["factorial"], "timed_out": false, "skipped": false, "passed": true, "error": null, "score_value": null, "score_passed": null, "score_reason": null } ] }各字段语义(CaseResult定义见 suite.py):
- agent_id / team_id:拆分记录被测对象——Agent Case 记
agent_id、Team Case 记team_id,另一个保持null(这也是为什么 TEST_LOG 中 team 用例的载荷是team_id: "assistant-team"、agent_id: null); - session_id:每个 Case 独享的评测会话(格式
eval-{case_name}-{8位随机hex}),设置db时用它把 Case 关联到存储的会话/轨迹;被跳过的 Case 为空字符串; - judge_passed / judge_reason / judge_score:裁判结论。
judge_score仅在数值模式下有值(1-10),二元模式下为null——保留原始分数是为了在纯 pass/fail 之外还能追踪质量漂移; - reliability_passed:reliability 检查结论,未配置时为
null; - tools_called:运行期间按顺序触发的工具名(Team 场景会深入一层收集成员的调用,见下文);
- timed_out / skipped / error:超时、被跳过标记与错误信息(运行错误、裁判错误、清理错误以
;拼接); - score_value / score_passed / score_reason:进程内 scorer 的结论,未配置 scorer 时三者均为
null(自 2.8.0 起追加,纯增量字段,不破坏既有 CI 消费者)。
注意:原始运行输出对象(RunOutput/TeamRunOutput)存放在CaseResult.response中,不包含在to_dict()里,以便程序化调用方通过result.response获取完整的内容、工具调用与指标。
八、源码级原理:Case 的执行流水线
理解套件行为的关键在于_arun_case与_run_case_body(见 suite.py)组成的执行流水线:
- 结果初始化:为每个 Case 生成专属
session_id,并用time.perf_counter()开始计时,避免评测流量污染 Agent/Team 自身的会话历史; - setup 钩子:在超时计时之外执行(
asyncio.wait_for包裹的是_run_case_body而非 setup),失败时该 Case 直接记录错误; - 流式运行:通过
runner.arun(input=..., stream=True, stream_events=True, yield_run_output=True)消费事件流。RunOutput/TeamRunOutput在流中捕获并立即提交响应与证据字段——即使流在最终输出后卡住(如持久化或用户清理挂起),超时也不会丢弃已经产出的结果;运行错误则通过错误事件记录而不是抛出异常(见 suite.py); - 可评分性判定:只有"无组件错误、存在响应、且
RunStatus.completed"的运行才进入评分。暂停/取消的运行携带占位内容(如 HITL 样板),不能作为真实答案参与评分(见 suite.py); - judge 检查:设置
criteria时,构造AgentAsJudgeEval(show_spinner=False、telemetry=False,保持套件静默),把judge_mode直接透传为scoring_strategy(见 suite.py); - reliability 检查:设置
expected_tool_calls时构造ReliabilityEval。Agent Case 传agent_response=,Team Case 传team_response=——这一点至关重要:如果 Team 场景错误地走agent_response=,reliability 只会看到 leader 的顶层消息(即delegate_task_to_member委派调用),而看不到成员的真正工具调用(见 suite.py); - scorer 检查:设置
scorer时在同一超时窗口内执行scorer.ascore(response, case.expected);Team Case 的response是TeamRunOutput,因此针对 Agent 内容编写的 scorer 看到的是 leader 的综合结果(见 suite.py); - teardown 钩子:在
finally中执行,超时/出错也照常运行——"超时前可能已有变更落盘",清理失败必须在载荷中可见而非只在控制台出现(见 suite.py); - 汇总:
CaseResult.passed为所有已配置检查(judge、reliability、scorer)的与运算(见 suite.py),有 error 直接判失败;SuiteResult.status仅在全部通过时为"PASS"。
8.1 Team 场景的可靠性检查如何"看到"成员调用
这是 Team 评测中最容易踩坑的机制。在 reliability.py 的_evaluate中,Team 场景通过_collect_member_evidence(见 reliability.py)递归收集所有嵌套层级的成员响应:包括每个成员的tools执行记录与消息。这样expected_tool_calls=("multiply",)才能命中 calculator 成员真正执行的multiply调用,而不是 leader 的委派调用。TEST_LOG 的实测载荷印证了这一点:tools_called: ["delegate_task_to_member", "multiply"]——reliability 同时看到了委派动作和成员的真实工具。
另外,reliability 判定以执行记录(executions)而非请求记录为准:一个期望的工具只有在存在"干净执行"(无tool_call_error、非is_paused)时才计入通过;被拒绝/出错/参数非法的调用会以"requested but refused/errored"的形式出现在missing_tool_calls中并标注原因,保证"红色的 CI 门禁能一眼看出是评测错了还是 Agent 错了"(见 reliability.py)。
九、验证结果与已知行为(TEST_LOG)
仓库附带的 cookbook/09_evals/suite/TEST_LOG.md 记录了这两个示例的实测验证结果,可作为行为预期的参考:
- suite_basic.py:PASS。两个 Case(judge + reliability 组合、纯 judge)全部通过,退出码 0;
--list、全量运行加--json-output均验证通过,JSON 载荷形状符合预期(tools_called: ["factorial"]、status: "PASS");未知--tag以退出码 2 结束并列出了可用 Case 名称; - suite_team_scoring.py:PASS。两个针对 Team 的 Case 全部通过,退出码 0;
--list、--list --json-output、--tag smoke、--name、全量加--json-output以及程序化run_cases/arun_cases入口均被覆盖验证;载荷中team_id: "assistant-team"、agent_id: null,算术 Case 的tools_called包含delegate_task_to_member与multiply,两个 Case 的judge_score均为 10。
十、实践建议
结合源码与示例,这里给出几个落地建议:
- 把套件写成一个独立入口文件:参考两个示例的写法,在
__main__中sys.exit(cli(CASES)),让 Python 退出码直接成为 CI 结果;若你的流程里已有一个运行中的事件循环(服务端、notebook),改用await acli(CASES)避免嵌套asyncio.run; - 为冒烟/全量分流打 Tag:把核心快速用例打上
smoke标签,日常 CI 跑--tag smoke,夜间或发版前跑全量;--list可先确认筛选结果; - 给 Case 设置合理超时:通过
Case.timeout_seconds为昂贵用例单独设限,套件级--timeout兜底,避免单个用例拖垮整条流水线; - Team 场景务必传
team=字段:既保证agent_id/team_id在报告中的正确归属,也保证 reliability 能通过team_response=收集到成员层级的真实工具调用; - 数值裁判适合质量追踪:
JudgeMode.NUMERIC+judge_threshold不仅给出 pass/fail,还能通过judge_score观察质量随迭代的漂移; - 空检查是陷阱:
criteria=""或expected_tool_calls=()会在构造期被拒绝,撰写 Case 时确保每个检查都有实质内容,否则会出现"绿色但无意义"的假门禁。
至此,你可以基于 suite_basic.py 与 suite_team_scoring.py 两个模板,结合 suite.py 的实现细节,为自己的 Agent / Team 搭建可筛选、可超时、可出报告、可接 CI 的聚合评测套件。
【免费下载链接】agnoBuild, run, and manage agent platforms.项目地址: https://gitcode.com/GitHub_Trending/ag/agno
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考