1. 为什么工程化 Agent 的评测不能只看跑分
做 Agent 开发的人都有一个共同的困惑:Demo 跑起来很惊艳,一上生产就拉胯。你问它一个多跳推理问题,它可能第一步就找错了方向;你让它调用三个工具完成一个任务,它可能在第二个工具的参数上就开始胡编。更麻烦的是,这些问题在单轮对话里根本看不出来,只有把它扔进一个足够复杂、足够真实的任务环境里,才能暴露出来。
这就是 GAIA 这类基准测试存在的意义。GAIA 全称 General AI Assistants benchmark,它的设计思路和传统的 NLP 评测集完全不同。传统评测集往往是“给定一段文本,回答一个问题”,考的是模型的语言理解和知识储备。GAIA 考的是“给你一个真实世界的复杂问题,你需要自己规划步骤、调用工具、整合信息,最终给出一个精确答案”。它把任务分成三个难度等级,Level 1 基本是单步检索或简单推理,Level 2 需要多步推理加工具组合,Level 3 则涉及长链条规划、跨模态信息处理、甚至需要写代码来验证中间结果。
羲和 XiheAgent 是我们内部搭建的一个工程化 Agent 框架,定位不是“又一个聊天机器人”,而是能真正接入业务系统、调用外部工具、完成端到端任务的智能体。它的核心能力包括任务规划、工具编排、记忆管理和结果验证四个模块。我们拿 GAIA 来做全流程评测,目的不是刷一个好看的分数,而是通过这个基准测试来检验框架在真实复杂场景下的鲁棒性、可扩展性和可调试性。
这篇文章适合两类人看:一类是正在做 Agent 开发、想知道怎么系统性地评测自己框架的工程师;另一类是对 Agent 评测方法论感兴趣、想了解 GAIA 到底怎么跑、坑在哪里的技术负责人。我会把整个评测流程拆开,从环境搭建、任务拆解、工具接入、结果验证到问题排查,一步步讲清楚。你不需要有 GAIA 的使用经验,但最好对 Agent 的基本概念(比如 ReAct 循环、工具调用、上下文管理)有一些了解。
2. GAIA 评测体系的核心设计与 XiheAgent 的适配思路
2.1 GAIA 的任务结构到底在考什么
GAIA 的题目看起来像是一些“奇怪的知识问答”,比如“某个特定年份的某个小众奖项的获奖者,他后来参与的那部电影的票房是多少”。这种题目的特点是:答案唯一且可验证,但获取路径非常不确定。你可能需要先查奖项,再查获奖者,再查他的作品列表,再查票房数据,中间任何一步出错,最终答案就是错的。
这和传统的多跳问答有什么区别?传统多跳问答通常有明确的推理链,比如 A 是 B 的父亲,B 是 C 的父亲,问 A 和 C 的关系。GAIA 的推理链是隐式的,你需要自己决定先查什么、后查什么,甚至需要判断哪些信息是干扰项。更关键的是,GAIA 的题目往往需要调用外部工具——搜索引擎、网页抓取、文件解析、代码执行——而不是单纯靠模型内部知识。
从评测维度来看,GAIA 实际上在考四件事:第一是任务规划能力,你能不能把一个大问题拆成可执行的子步骤;第二是工具选择能力,面对一个子任务,你知道该用哪个工具、怎么传参;第三是信息整合能力,从多个来源拿到碎片信息后,你能不能拼出完整答案;第四是自我验证能力,你能不能判断自己给出的答案是否合理。
2.2 XiheAgent 的架构为什么适合跑 GAIA
XiheAgent 的架构设计从一开始就考虑了评测需求。它的核心是一个基于状态机的任务执行引擎,而不是简单的 ReAct 循环。为什么不用 ReAct?因为 ReAct 在长链条任务中容易“迷失”,每一步都依赖上一步的输出,一旦某一步产生幻觉,后面全错。状态机的方式是把任务拆成明确的状态节点,每个节点有输入输出定义,节点之间通过条件跳转连接。这样做的好处是,当某个节点执行失败时,你可以精确定位到是哪一步出了问题,而不是面对一个黑盒。
具体来说,XiheAgent 包含四个核心模块。规划器(Planner)负责把用户输入的任务拆解成子任务序列,它内部维护一个任务图,每个子任务是一个节点,节点之间有依赖关系。执行器(Executor)负责调用具体工具,它支持同步和异步两种模式,对于需要等待外部 API 返回的任务,可以并行执行多个子任务。记忆模块(Memory)分为短期记忆和长期记忆,短期记忆保存当前任务的上下文,长期记忆保存历史任务的成功经验和失败教训。验证器(Validator)负责检查每个子任务的输出是否符合预期,如果不符合,会触发重试或回退。
这套架构跑 GAIA 的优势在于:GAIA 的题目天然适合拆成任务图,而且验证器可以在每个子任务完成后立即检查结果,避免错误累积。另外,记忆模块可以让 Agent 在遇到类似题目时复用之前的策略,这对于 GAIA 这种题目类型相对固定的基准测试来说,能显著提升效率。
2.3 评测流程的整体设计
整个评测流程分为五个阶段。第一阶段是环境准备,包括 GAIA 数据集的下载、依赖工具的安装、API 密钥的配置。第二阶段是任务加载与预处理,把 GAIA 的题目转换成 XiheAgent 能理解的输入格式,同时提取题目中的关键信息(比如是否需要文件解析、是否需要代码执行)。第三阶段是执行与记录,Agent 开始跑任务,系统记录每一步的输入输出、耗时、工具调用次数。第四阶段是结果验证,把 Agent 的答案和 GAIA 的标准答案对比,计算准确率。第五阶段是问题分析,对失败的题目进行分类,找出是规划问题、工具问题还是验证问题。
这个流程看起来简单,但每个阶段都有坑。比如环境准备阶段,GAIA 的题目里有一些需要解析 PDF 或 Excel 文件的,如果你的工具链不支持这些格式,题目根本没法跑。再比如结果验证阶段,GAIA 的答案有时候是数值,有时候是字符串,有时候是列表,对比逻辑需要根据答案类型动态调整。
3. 从零搭建 GAIA 评测环境的实操细节
3.1 数据集获取与题目结构解析
GAIA 的数据集在 Hugging Face 上可以找到,官方提供了验证集和测试集。验证集有 165 道题,测试集有 301 道题。每道题包含以下字段:task_id(唯一标识)、Question(题目文本)、Level(难度等级 1-3)、Final answer(标准答案)、file_name(如果有附件的话)、file_path(附件路径)。附件是 GAIA 的一个重要特点,很多题目需要你读取一个 PDF、Excel 或图片文件才能回答。
我建议先用验证集跑通流程,因为验证集有公开答案,方便调试。测试集的答案不公开,需要提交到官方 leaderboard 才能看到分数。对于内部评测来说,验证集已经足够暴露大部分问题。
加载数据集的时候要注意,GAIA 的题目文本里有时候会包含一些特殊格式,比如用反引号包裹的文件名,或者用方括号标注的链接。这些需要在预处理阶段提取出来,作为工具调用的参数。我的做法是写一个简单的解析器,用正则表达式提取题目中的文件引用和 URL,然后把它们作为元数据附加到任务对象上。
3.2 工具链的选型与配置
GAIA 题目涉及的工具类型主要有四类:搜索引擎、网页抓取、文件解析、代码执行。搜索引擎我用的是 SerpAPI,因为它返回的结构化数据比较干净,解析成本低。网页抓取用 Playwright,因为它能处理 JavaScript 渲染的页面,而 GAIA 里有些题目需要访问动态加载的网页。文件解析用 PyPDF2 处理 PDF,用 openpyxl 处理 Excel,用 Pillow 处理图片。代码执行用 Docker 容器隔离,避免 Agent 生成的代码影响宿主机。
这里重点说一下代码执行工具的设计。GAIA 里有一些题目需要计算,比如“某个数的阶乘的各位数字之和”,这种题目如果让模型直接算,很容易出错。更好的做法是让 Agent 生成一段 Python 代码,然后在沙箱里执行,把结果返回给 Agent。XiheAgent 的代码执行工具支持两种模式:一种是直接执行代码字符串,返回标准输出;另一种是执行代码文件,返回文件内容。对于 GAIA 的题目,第一种模式就够了。
配置 API 密钥的时候要注意,不要把密钥硬编码在代码里。我用的是环境变量加配置文件的方式,配置文件里只写密钥的名称,实际值从环境变量读取。这样既方便切换密钥,也避免了密钥泄露的风险。
3.3 任务加载器的实现
任务加载器的核心工作是把 GAIA 的原始题目转换成 XiheAgent 的任务对象。任务对象包含以下字段:task_id、question、level、expected_answer、attachments、tools_required、max_steps。其中tools_required是根据题目内容推断出来的,比如题目里出现了“根据附件”这样的字眼,就自动加上文件解析工具;出现了“计算”或“求和”,就加上代码执行工具。
max_steps是一个重要的参数,它限制了 Agent 最多执行多少步。GAIA 的 Level 1 题目通常 3-5 步就能解决,Level 2 需要 8-12 步,Level 3 可能需要 20 步以上。如果max_steps设得太小,Agent 还没找到答案就被强制终止;设得太大,又容易让 Agent 陷入无效循环。我的经验是,Level 1 设 8 步,Level 2 设 15 步,Level 3 设 25 步,这个配置在验证集上的表现比较均衡。
加载器还需要处理附件。GAIA 的附件有时候是单独的文件,有时候是压缩包。对于压缩包,需要先解压再处理。附件的路径要转换成绝对路径,避免因为工作目录变化导致文件找不到。
4. XiheAgent 执行 GAIA 任务的核心环节拆解
4.1 任务规划器的配置与调优
规划器是 XiheAgent 跑 GAIA 的第一个环节,也是最容易出问题的环节。规划器的输入是题目文本和附件信息,输出是一个任务图。任务图的每个节点包含:node_id、description(子任务描述)、tool(需要调用的工具)、params(工具参数)、dependencies(依赖的节点 ID)。
规划器的 prompt 设计很关键。我试过几种不同的 prompt 结构,最后发现最有效的是“先分类,再拆解”的两阶段方式。第一阶段让模型判断题目类型:是纯检索题、多跳推理题、文件解析题还是计算题。第二阶段根据题目类型,套用不同的拆解模板。比如检索题拆成“确定搜索关键词 -> 执行搜索 -> 提取答案”三步;多跳推理题拆成“识别第一跳实体 -> 搜索第一跳信息 -> 识别第二跳实体 -> 搜索第二跳信息 -> 整合答案”五步。
这种方式的优势是拆解结果更稳定,不会因为题目表述的微小变化就产生完全不同的任务图。缺点是灵活性稍差,遇到模板覆盖不到的题目类型时,需要回退到通用的 ReAct 拆解方式。我的做法是维护一个题目类型到拆解模板的映射表,同时保留一个兜底的通用拆解器。
规划器的输出需要做校验。我遇到过规划器生成的任务图里有循环依赖的情况,比如节点 A 依赖节点 B,节点 B 又依赖节点 A。这种任务图执行时会死锁。解决办法是在规划器输出后加一个环检测步骤,用拓扑排序检查任务图是否有环,如果有环就触发重新规划。
4.2 工具调用的参数传递与错误处理
工具调用是执行器的核心工作。每个工具都有明确的输入输出定义,执行器根据任务图节点的tool和params字段来调用对应的工具。参数传递有两种方式:一种是直接传递,比如搜索工具的query参数直接来自规划器的输出;另一种是引用传递,比如第二个搜索的query参数需要引用第一个搜索的结果。
引用传递是容易出错的地方。如果第一个搜索返回的结果格式和预期不符,第二个搜索的参数就会出错。我的做法是在工具的输出定义里加上 schema 校验,确保输出符合预期格式。如果不符合,执行器会触发重试,重试时会把错误信息附加到 prompt 里,让规划器重新生成参数。
错误处理策略分为三级。一级错误是参数格式错误,比如搜索关键词为空,这种错误直接重试,重试次数上限 3 次。二级错误是工具返回空结果,比如搜索没有找到任何相关内容,这种错误会触发规划器重新规划,换一个搜索策略。三级错误是工具调用超时或抛出异常,这种错误会记录到日志,然后跳过当前节点,继续执行后续节点。如果后续节点依赖当前节点的输出,则整个任务标记为失败。
4.3 记忆模块的读写策略
记忆模块在 GAIA 评测中的作用经常被低估。GAIA 的题目虽然各不相同,但很多题目在解题思路上是相似的。比如“某本书的作者的出生地”和“某部电影的导演的代表作”,本质上都是两跳检索题。如果 Agent 能把第一次解题的成功经验保存下来,遇到类似题目时直接复用策略,效率会高很多。
XiheAgent 的记忆模块分为两层。短期记忆保存当前任务的执行历史,包括每一步的输入输出、工具调用记录、中间结果。短期记忆的容量有限,我设的是 20 条记录,超过后自动淘汰最早的记录。长期记忆保存跨任务的经验,包括成功的任务图模板、常用的工具组合、常见的错误模式。长期记忆用向量数据库存储,检索时根据题目文本的语义相似度召回相关经验。
写入长期记忆的时机很关键。不是所有成功任务都值得保存,只有那些执行步数少、工具调用次数少、且答案正确的任务才值得作为模板。我设的阈值是:Level 1 任务步数不超过 5 步,Level 2 不超过 10 步,Level 3 不超过 18 步。超过这个阈值的任务,即使答案正确,也不保存为模板,因为它们的执行路径可能包含偶然因素。
4.4 验证器的规则设计与动态调整
验证器负责检查每个子任务的输出是否合理。对于搜索类工具,验证规则是检查返回结果的数量是否大于 0,以及结果中是否包含任务描述里的关键词。对于文件解析工具,验证规则是检查解析出的文本长度是否大于某个阈值,以及是否包含预期的字段。对于代码执行工具,验证规则是检查退出码是否为 0,以及标准输出是否为空。
验证器的规则不是一成不变的。在评测过程中,我发现有些题目的搜索关键词比较生僻,搜索结果数量很少,但结果确实是正确的。如果严格按照“结果数量大于 0”来验证,这些题目会被误判为失败。解决办法是引入动态阈值:对于生僻关键词,把结果数量阈值降到 0,但增加一个“结果相关性”检查,用简单的关键词匹配来判断结果是否和题目相关。
验证器的另一个作用是触发回退。如果某个子任务的输出验证失败,验证器会通知执行器回退到上一个成功的节点,然后重新规划后续步骤。回退机制在 GAIA 的 Level 3 题目中特别有用,因为 Level 3 题目往往有多条解题路径,一条路走不通可以换另一条。
5. 评测结果分析与常见问题排查
5.1 准确率、步数与工具调用次数的关系
跑完验证集的 165 道题后,我整理了一份数据。整体准确率是 68.5%,其中 Level 1 准确率 89.2%,Level 2 准确率 64.7%,Level 3 准确率 41.3%。这个成绩在同类框架里属于中等偏上,但距离我们的目标还有差距。
从步数来看,Level 1 平均 4.2 步,Level 2 平均 9.8 步,Level 3 平均 17.5 步。工具调用次数和步数基本成正比,Level 1 平均 3.1 次,Level 2 平均 7.6 次,Level 3 平均 14.2 次。值得注意的是,失败任务的步数普遍比成功任务多 30%-50%,说明 Agent 在遇到困难时容易陷入无效循环,反复尝试相似的策略。
| 难度等级 | 题目数量 | 准确率 | 平均步数 | 平均工具调用次数 |
|---|---|---|---|---|
| Level 1 | 52 | 89.2% | 4.2 | 3.1 |
| Level 2 | 78 | 64.7% | 9.8 | 7.6 |
| Level 3 | 35 | 41.3% | 17.5 | 14.2 |
| 整体 | 165 | 68.5% | 9.6 | 7.4 |
5.2 典型失败案例的分类与归因
失败案例可以分成四类。第一类是规划错误,占比约 35%。典型表现是任务图拆解不合理,比如把一道需要先查 A 再查 B 的题目,拆成了同时查 A 和 B,导致信息无法整合。第二类是工具调用错误,占比约 28%。典型表现是搜索关键词选择不当,比如题目问的是“某部电影的票房”,Agent 搜索的是“某部电影 评价”,结果找不到票房数据。第三类是信息整合错误,占比约 22%。典型表现是拿到了所有需要的信息,但整合时搞错了逻辑关系,比如把“A 的出生年份”和“B 的出生年份”搞混了。第四类是验证器误判,占比约 15%。典型表现是验证器认为某个中间结果不合格,触发了不必要的回退,导致任务超时。
规划错误是最难解决的,因为它涉及到模型对题目意图的理解。我的改进方向是增加一个“规划预检”步骤,在任务图生成后,用一个轻量级的模型检查任务图是否合理,比如检查节点之间的依赖关系是否完整、是否有冗余节点。这个预检步骤增加了约 5% 的耗时,但把规划错误率降低了 12%。
5.3 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| Agent 反复搜索同一个关键词 | 规划器陷入循环 | 查看任务图是否有环 | 增加环检测,触发重新规划 |
| 搜索结果为空但题目确实有答案 | 关键词过于具体 | 检查搜索关键词是否包含生僻词 | 放宽关键词,用同义词替换 |
| 文件解析失败 | 文件格式不支持 | 检查文件扩展名和实际格式 | 增加格式转换步骤 |
| 代码执行超时 | 代码逻辑有死循环 | 查看代码内容 | 设置执行超时,强制终止 |
| 答案格式正确但内容错误 | 信息整合逻辑错误 | 对比中间结果和最终答案 | 增加整合步骤的验证规则 |
| 任务步数超过上限 | 规划器拆解过细 | 查看任务图节点数量 | 合并相似节点,减少步数 |
5.4 提升评测效果的几个实操技巧
第一个技巧是预热记忆库。在正式评测前,先用一小部分题目(比如 20 道)跑一遍,把成功的任务图保存到长期记忆里。正式评测时,遇到相似题目可以直接复用模板,能显著减少步数和工具调用次数。我实测下来,预热后 Level 2 的平均步数从 9.8 降到了 7.2。
第二个技巧是动态调整 max_steps。不要给所有题目设同一个 max_steps,而是根据题目的 Level 和预估复杂度动态设置。我的做法是先用一个较小的 max_steps 跑一遍,记录每道题的实际步数,然后根据实际步数的分布,给每道题设置一个略高于实际步数的上限。这样既能避免步数浪费,又能防止 Agent 提前终止。
第三个技巧是工具调用的缓存。GAIA 的题目里有一些搜索关键词是重复的,比如多道题都涉及同一个实体。如果每次搜索都重新调用 API,不仅慢,还可能触发 API 的速率限制。我的做法是在工具层加一个缓存,用搜索关键词的哈希作为 key,缓存搜索结果。缓存有效期设为 24 小时,对于 GAIA 评测来说足够了。
第四个技巧是失败任务的二次利用。失败的任务不要直接丢掉,而是把失败原因分类保存。如果是因为工具调用错误导致的失败,可以把正确的工具调用方式补充到长期记忆里;如果是因为规划错误导致的失败,可以把错误的规划方式和正确的规划方式对比保存,作为后续规划的负样本。
6. 从 GAIA 评测反推 Agent 框架的优化方向
跑完这一轮 GAIA 评测,最大的收获不是那个 68.5% 的准确率数字,而是暴露出来的框架设计问题。规划器的两阶段拆解方式在 Level 1 和 Level 2 上表现不错,但在 Level 3 上明显吃力,因为 Level 3 的题目往往没有固定的拆解模板,需要更灵活的规划策略。下一步我打算引入基于蒙特卡洛树搜索的规划器,让 Agent 在多个可能的任务图之间做选择,而不是只生成一个任务图。
工具层的问题主要是错误处理不够精细。现在的三级错误处理策略太粗糙,很多错误其实有更具体的处理方法。比如搜索返回空结果,可能是因为关键词太具体,也可能是因为搜索引擎的索引里确实没有这个信息。这两种情况的处理方式完全不同,前者应该换关键词,后者应该换搜索源。我打算在工具层增加一个错误分类器,根据错误的具体表现来决定处理策略。
记忆模块的检索精度也有提升空间。现在的向量检索用的是通用的文本嵌入模型,对 GAIA 这种专业领域的题目,检索效果一般。我试过用 GAIA 的题目文本微调一个嵌入模型,检索精度提升了约 15%,但微调成本比较高。另一个思路是用关键词和向量混合检索,先用关键词过滤掉明显不相关的记忆,再用向量做精细排序。
验证器的规则目前还是手工配置的,维护成本高,而且容易遗漏。我打算把验证器的规则也交给模型来生成,具体做法是让模型根据任务描述自动生成验证规则,然后人工审核。这样既能减少手工配置的工作量,又能覆盖更多的任务类型。
最后说一个容易被忽略的点:评测的可复现性。GAIA 评测涉及多个外部工具和 API,这些外部依赖的状态变化会影响评测结果。比如搜索引擎的索引更新了,同样的关键词可能返回不同的结果。为了保证评测的可复现性,我把所有工具调用的输入输出都记录到了日志里,评测时可以选择“回放模式”,直接用日志里的结果,而不重新调用工具。这样即使外部环境变了,也能复现之前的评测结果。
这个内容后续还可以这样扩展:把 GAIA 的评测流程封装成一个通用的 Agent 评测平台,支持接入不同的 Agent 框架和不同的基准测试集。平台的核心是一个评测编排器,负责管理评测任务的生命周期,包括环境准备、任务分发、结果收集和报告生成。这样每次优化 Agent 框架后,只需要在平台上重新跑一遍评测,就能快速看到优化效果。