Agno Eval Suite 评测套件实战指南:多 Case 聚合、标签筛选、JSON 报告与 CI 退出码
2026/9/10 1:57:39 网站建设 项目流程

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导入Casecli,其余导入分别对应 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定义:

参数默认值作用
--nameNone只运行指定名称的 Case
--tagNone只运行带该标签的 Case
--timeout套件默认default_timeout(120 秒)默认的每 Case 超时(秒),Case 自身设置了timeout_seconds时以 Case 为准
--json-outputNone将机器可读的 JSON 结果写入指定路径
--listFalse只列出被选中的 Case,不运行
-v/--verboseFalse每个 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 而非 AgentCase(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会被透传给AgentAsJudgeEvalthreshold字段(见 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/teamNone被测对象,二选一必填
tags()标签元组,用于--tag筛选
timeout_secondsNone每 Case 独立超时(秒);未设置时回退到套件级default_timeout(120 秒)
criteriaNone设置后启用 judge 检查(AgentAsJudgeEval)
judge_modelNone每 Case 的裁判模型覆盖;未设置时回退到套件级judge_model,再回退到AgentAsJudgeEval默认模型
judge_modeBINARY裁判评分模式
judge_threshold7数值模式及格线(1-10),仅在NUMERIC模式下生效
expected_tool_callsNone设置后启用 reliability 检查(ReliabilityEval)
allow_additional_tool_callsTrue宽松模式:允许出现预期之外的工具调用(子集匹配)
setup/teardownNone生命周期钩子:setup在运行前执行(超时计时之外),其返回值作为context传给teardownteardown在 setup 完成后无论通过/失败/出错/超时都会执行,并接收(context, result)以便检查result.error/result.timed_out
scorer/expectedNone进程内 scorer 检查(agno.scorer 协议),运行在 Case 超时之内,仅对可评分的运行执行

Case在构造时(__post_init__)会做三类校验(见 suite.py):

  1. agent/team 互斥:两者都不传或都传都会抛ValueError
  2. 必须存在至少一种检查criteriaexpected_tool_callsscorer三者全空时抛错。这里用的是真值判断而非is None,因此criteria=""expected_tool_calls=()这类"空检查"也会被拒绝——防止构造出"绿色通过但什么都没验证"的假 CI 门禁;
  3. 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/nameNone按标签/名称筛选
default_timeout120每 Case 默认超时(秒),Case.timeout_seconds优先
judge_modelNone套件级默认裁判模型,Case.judge_model优先
dbNone透传给AgentAsJudgeEval/ReliabilityEval,用于把评测结果写入存储
on_case_start/on_case_end/on_run_eventNone展示钩子,分别在每个 Case 运行前、结束后以及每个流式运行事件时回调

钩子还有两条实现细节值得注意:

  • 展示钩子必须是同步可调用对象,直接在事件循环上内联执行;传入异步钩子会被检测并以TypeError拒绝(见 suite.py)。而setup/teardown则相反,同步可调用对象通过asyncio.to_thread执行、异步可调用对象被直接await
  • 套件运行被取消(如服务端 cancel、或 agno 将 KeyboardInterrupt 转换成的取消)时会中止后续 Case,未运行的 Case 以skipped=Trueerror="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)组成的执行流水线:

  1. 结果初始化:为每个 Case 生成专属session_id,并用time.perf_counter()开始计时,避免评测流量污染 Agent/Team 自身的会话历史;
  2. setup 钩子:在超时计时之外执行(asyncio.wait_for包裹的是_run_case_body而非 setup),失败时该 Case 直接记录错误;
  3. 流式运行:通过runner.arun(input=..., stream=True, stream_events=True, yield_run_output=True)消费事件流。RunOutput/TeamRunOutput在流中捕获并立即提交响应与证据字段——即使流在最终输出后卡住(如持久化或用户清理挂起),超时也不会丢弃已经产出的结果;运行错误则通过错误事件记录而不是抛出异常(见 suite.py);
  4. 可评分性判定:只有"无组件错误、存在响应、且RunStatus.completed"的运行才进入评分。暂停/取消的运行携带占位内容(如 HITL 样板),不能作为真实答案参与评分(见 suite.py);
  5. judge 检查:设置criteria时,构造AgentAsJudgeEvalshow_spinner=Falsetelemetry=False,保持套件静默),把judge_mode直接透传为scoring_strategy(见 suite.py);
  6. reliability 检查:设置expected_tool_calls时构造ReliabilityEvalAgent Case 传agent_response=,Team Case 传team_response=——这一点至关重要:如果 Team 场景错误地走agent_response=,reliability 只会看到 leader 的顶层消息(即delegate_task_to_member委派调用),而看不到成员的真正工具调用(见 suite.py);
  7. scorer 检查:设置scorer时在同一超时窗口内执行scorer.ascore(response, case.expected);Team Case 的responseTeamRunOutput,因此针对 Agent 内容编写的 scorer 看到的是 leader 的综合结果(见 suite.py);
  8. teardown 钩子:在finally中执行,超时/出错也照常运行——"超时前可能已有变更落盘",清理失败必须在载荷中可见而非只在控制台出现(见 suite.py);
  9. 汇总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_membermultiply,两个 Case 的judge_score均为 10。

十、实践建议

结合源码与示例,这里给出几个落地建议:

  1. 把套件写成一个独立入口文件:参考两个示例的写法,在__main__sys.exit(cli(CASES)),让 Python 退出码直接成为 CI 结果;若你的流程里已有一个运行中的事件循环(服务端、notebook),改用await acli(CASES)避免嵌套asyncio.run
  2. 为冒烟/全量分流打 Tag:把核心快速用例打上smoke标签,日常 CI 跑--tag smoke,夜间或发版前跑全量;--list可先确认筛选结果;
  3. 给 Case 设置合理超时:通过Case.timeout_seconds为昂贵用例单独设限,套件级--timeout兜底,避免单个用例拖垮整条流水线;
  4. Team 场景务必传team=字段:既保证agent_id/team_id在报告中的正确归属,也保证 reliability 能通过team_response=收集到成员层级的真实工具调用;
  5. 数值裁判适合质量追踪JudgeMode.NUMERIC+judge_threshold不仅给出 pass/fail,还能通过judge_score观察质量随迭代的漂移;
  6. 空检查是陷阱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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询