GAIA 基准测试:构建 Rust AI Agent 系列教程
GAIA(General AI Assistants)是业界公认最难且最真实的通用 AI 助手评测基准,它揭示了纯大语言模型在解决真实复杂任务时的硬伤——即使最基础的 Level One 任务也高度依赖外部工具的协同。
核心要点
- 大语言模型在处理真实世界复杂任务时存在根本性局限:只能依赖训练语料中的有限知识,无法访问实时数据或处理用户文件。
- GAIA 基准测试是业界公认的最具挑战性和真实性的通用 AI 助手评测标准,适用于衡量 agent 的工具使用能力。
- GAIA Level One 任务看似简单,但即便入门级任务也需要至少三种工具的协同:联网搜索、文件查询和计算器。
- 开发者过度专注于“提示词微调”而缺乏可行的评估手段,如同航海时没有指南针。
- 基准测试结果(Rust 实现,10 道采样题)显示:Claude 4.5 的正确率仅约 30%,GPT-4.1 mini 约为 20%;20 道题规模的第三方测试中普通模型最高仅约 25%。
- 大语言模型具有“自知之明”——在无法作答时会选择拒绝回答而非胡说八道,这对 agent 设计而言是重要优势。
详细解析
为什么构建 AI Agent 离不开工具?
问题引入:提示词工程的评估困境
- 很多开发者在开发 agent 时,习惯花很长时间微调提示词(prompt),但往往无法确定这样做是否真的有进步。
- 作者将这种现象比作“航海的时候没带指南针”——缺少客观、系统的评估标准。
- 解决方向:引入被业界公认为“难”且“真实”的通用 AI 助手评测基准——GAIA。
工具必要性的直观案例(GAIA Level One 经典题)
题目:若马拉松巨星基普乔格(Eliud Kipchoge)能以他打破世界纪录的配速无限跑下去,需要多少千小时才能跑完地球和月球在近地点时的最近距离(四舍五入)。
- 这道题对人类解决者有以下要求:
- 需要知道基普乔格世界纪录的准确配速。
- 需要获取月球近地点距离的确切数值(通常来自维基百科等外部来源)。
- 需要进行高精度除法计算。
- 对没有工具的大语言模型而言,它只能:
- 依赖训练语料中的知识“猜”这些数字;
- 即便记住了维基百科数值,一旦记错,或大数字除法算错一位——按照 GAIA 规则,这道题就是零分。
结论:即使是最简单的 GAIA Level One 任务,也至少需要以下三种工具的协同:
- 联网搜索(获取实时/外部知识)
- 文件/网页查询(获取非结构化数据)
- 计算器(做精确数学运算)
GAIA 基准测试的分级体系
| 等级 | 名称/定位 | 难度 | 工具使用特征 | 推理步骤长度 | 任务性质 |
|---|---|---|---|---|---|
| Level One | 基线级别 | 最低 | 只需 0~1 个工具 | ≤ 5 步 | 基础事实查阅与简单推理 |
| Level Two | 中级 | 复杂 | 多个不同工具组合使用 | 5~10 步 | 需要跨工具编排的推理 |
| Level Three | 终极形态 | 最难 | 任意数量工具调用,无步骤限制 | 无限制 | 要求 agent 像真人助理一样,在复杂网页和本地文件中进行长程规划 |
开发者策略:不要指望一步登天,应该从 Level One 开始做基准测试(baseline)。目的是搞清楚:在剥夺一切工具的情况下,纯粹的大语言模型到底能干成什么事。
实验设计:用 Rust 实现 GAIA 评测
评测思路总览
实验的目标:用 Rust 编写代码,通过 API 并发调用多个大模型,让它们在没有工具的情况下直接解答 GAIA Level One 题目,然后与官方标准答案对比。
具体流程:
- 从 HuggingFace 加载 GAIA Level One 验证集(原始代码加载 100 道,本地实验改为 10 道)。
- 通过 API 并发调用多个大模型(如 GPT、Claude、DeepSeek 等)。
- 将大模型结果与 GAIA 官方正确答案做精确匹配验证。
- 计算出准确率,得到纯语言模型的能力“天花板”。
运行前准备:HuggingFace 数据集与 Token 配置
- 访问 HuggingFace 网站(huggingface.co)。
- 打开 GAIA 数据集页面,浏览数据集结构。
- 点击用户头像图标,进入 Access Tokens 页面;若没有 Token 则新建一个,将创建好的 Token 复制。
- 在 VS Code 中创建环境变量
HF_HUGGINGFACE_TOKEN(存储在.env文件中),将复制的 Token 粘贴进去。
代码组织与模块结构
主要涉及以下 Rust 源文件:
| 文件 | 职责 |
|---|---|
gaia/models.rs | 定义 GAIA API 相关的类型结构 |
gaia/dataset.rs | 加载 Level One 数据集(HTTP 请求 HuggingFace API) |
gaia/server.rs | 解题:调用大模型 API,处理提示词、响应与重试逻辑 |
gaia/evaluator.rs | 评测:将模型结果与标准答案对比 |
bin/gaia_level_one.rs(或 bin/gaia.rs) | 主程序:串联全流程并运行基准测试 |
类型定义(models.rs)
代码中定义了多个与 GAIA API 对应的 Rust 类型:
HuggingFaceResponse
- 一个响应结构体:内部包含一个向量(
rows),每个row即一个GAIA_Row。
GAIA_Row
- 核心字段(一套整体结构):
task_id:任务 IDquestion:问题文本level:难度等级final_answer:官方标准答案
GAIA_Output(大模型返回格式)
- 定义了统一的 JSON Schema,要求模型必须以特定结构输出结果。
- 字段后续还包括:
is_solvable(是否可解决)、拒绝原因(若有)、final_answer(最终答案)。
GAIA_EvaluationResult(评测结果)
评测完成后的记录字段:
task_id:对应哪道题- 使用了哪个模型
correct:是否正确- 标准答案是什么
error:是否出错- 其余三个字段(来自模型的输出结果,即
GAIA_Output的内容)
数据集加载模块(dataset.rs)
- 使用
reqwest(request)HTTP 库,并在 Cargo.toml 中启用其jsonfeature。 - 加载函数核心流程:
- 从环境变量
HF_TOKEN(即之前设置的 HF_HUGGINGFACE_TOKEN)获取认证信息,通过.env文件的加载实现。 - 创建
reqwest::Client。 - 发送 HTTP GET 请求到 HuggingFace 数据集 API。
- 指定数据集为 GAIA benchmark;
- 配置(config)为
2023_level_one; - 数据拆分(split)为
validation(验证集); - 从索引 0 开始拉取 100 条数据;
- 带上 Bearer Token 认证。
- 解析 JSON 响应为
HuggingFaceResponse类型。 - 取出
.rows字段,做成集合返回。
解题模块(server.rs)
该模块负责调用大模型 API。
核心函数签名
三个主要参数:
model:模型名称system_prompt:系统提示词user_prompt:用户提示词
执行流程
- 准备
GAIA_Output输出格式(JSON Schema)。 - 创建
AsyncOpenAI客户端(OpenAI 风格接口)。 - 构造请求参数:选择模型、系统提示词、用户提示词、回复的输出格式。
- 发送请求并接收响应。
关键处理:检查模型是否拒绝回答
- 问题:模型不一定回答所有问题,受安全策略影响可能拒绝回答某些问题。
- 处理方式:收到结果后首先检查模型状态字段,确认是否因安全原因停止生成(如
content_filter等)。 - 如果模型拒绝回答:
- 生成一个特殊的
GAIA_Output: is_solvable设置为false;- 记录“模型拒绝回答”;
final_answer为空字符串。- 如果模型正常回答:将输出解析为
GAIA_Output结构并返回。
重试机制(solve_problem_with_retry)
- 网络请求可能不一次成功:可能超时或遇到 HTTP 429(请求过多)。
- 不直接让用户调用
solve_problem,而是加了一层自动重试包装: - 失败后按指数退避策略(Exponential Backoff)重新请求;
- 最大重试次数共 3 次;
- 目的:提高整体运行的稳定性。
GAIA 的 system prompt 常量
代码中定义了一个 GAIA_PROMPT 常量,作为后续推理测试时固定使用的系统提示词。
评测模块(evaluator.rs)
判断正确性函数
- 逻辑非常简单:将答题结果与标准答案比较。
- 具体步骤:
- 若结果为空字符串:标记为答错(包含拒绝回答的情况,均判错)。
- 否则将结果
trim(去除首尾空格)、全部转为小写,再与标准答案比较相等性。 - 相等则判对,否则判错。
单题完整评测函数(将各零件串联)
接收两个参数:一道题目 + 一个模型名称。执行以下流程:
- 调用
solve_problem_with_retry。 - 传入
GAIA_PROMPT作为系统提示词。 - 将题目中的问题本身作为用户提示词。
- 对返回结果判断正确性。
- 最终生成
GAIA_EvaluationResult。 - 若发生异常或请求失败:
- 不让程序崩溃退出;
- 仍返回一个
GAIA_EvaluationResult类型记录; correct设为false,其余某些字段为None;error字段记录错误。
主程序(binary gaia.rs)
模型选择建议
- 不建议使用 OpenRouter 上的免费模型(并发限制极低)。
- 推荐方案:
- 直接使用 DeepSeek;
- 或使用 OpenRouter 上更低版本(即较便宜)的 OpenAI / Anthropic 模型。
- 实验实际选了 2 个模型,以保证成本较低。
运行逻辑
- 下载题目:100 道 Level One 题。
- 对每个模型(
for循环):
- 对每一道题并发执行解题任务;
- 评估每道题答对与否;
- 收集全部结果。
- 打印正确率百分比(作者修正了原代码中的一处笔误:关键字分隔符应为点号)。
Main 函数与环境变量加载
- 基础版本
main函数非常简单。 - 代码最初运行报“环境变量未找到”错误,原因:没有在
main函数中加载.env文件。 - 修复:在
main函数顶部调用dotenv的加载函数(dotenv().ok())。 - 为了排查输出问题,还复制配置了 tracing(日志追踪)。
本地实验的设置调整
- 在实际本地运行时,将数据集加载从 100 道改为 10 道,以提高验证管道能跑通的速度并控制成本。
- 使用两个模型进行测试。
案例与数据:实际评测结果
本地运行结果(样本量:10 道题)
| 模型 | 正确率 |
|---|---|
| Claude 4.5 | 30% |
| GPT-4.1 mini | 20% |
由于仅采样 10 道题,此为小规模、初步验证性质的结论。
第三方测试结果对比(样本量:20 道题)
| 模型 | 正确率 |
|---|---|
| GPT-5 | 50%(不可靠,疑似训练数据泄露) |
| GPT-5 mini | 20% |
| Claude 3.5 Sonnet | 15% |
| Claude 3.5 Haiku | 15% |
| 其他多数模型 | ~20%(最高 25%) |
重要解释:
- GPT-5 的 50% 结果不可靠:有说法指出 GPT-5 在训练时已将这些公开测试集“背下来”,因此存在训练数据泄露。
- 其余模型的理论上限约 25%,该区间较为可信。
- 其他模型得分较低是因为它们表现出“自知之明”:在无法解答时拒绝回答并判定为“不可解答”——这是一种有利于 agent 应用场景的特性,即不懂装懂反而有害。
错误原因归类
分析模型答错的题,发现错误来源主要有两大类:
- 相当一部分题需要联网搜索 与网页内容获取。
- 另一部分题需要外部文件的读取与解析。
关键结论: 当任务涉及用户文件或实时互联网动态搜索时,无论训练数据集有多大,都无法替代一条能读取文件、访问网页的代码。大语言模型并非万能。
核心教训与行动建议(三条“铁律”)
本节课通过基准测试结果,给出了三条指导原则:
- 让模型主动“求助”
- 大语言模型知道自己什么时候“不会”(拒绝回答的倾向)。
- 在 Agent 架构设计时,可让模型在发现信息缺乏时主动抛出调用工具的请求,依赖这种“自知”避免错误。
- 训练数据庞大 ≠ 可替代工具
- Agent 需要的是 100% 的准确数据和最新数据。
- 即便是看起来最简单的任务,也有高达 75% 的情况依赖外部工具配合完成。
- Rust 开发者的机会
- 可以利用 Rust 在异步网络和类型安全方面的优势,为 Agent 构建一套健壮的联网搜索组件和轻量计算工具。
- 目标:从物理上打破大约 25% 的能力天花板。
总结
GAIA 基准测试清楚证明了纯大语言模型的固有边界:它既无法获得实时资讯,也不能精确读取用户文件,数字运算容易出错,且不存在可靠的外挂能力。对于 Rust 开发者而言,这意味着巨大的工程机会——以 Rust 的系统级性能、异步 I/O 与类型系统为依托,设计真正能够“触达外部世界”的 Agent 工具层(联网搜索、文档解析、精确计算等),从而系统性弥补模型基因里的上述短板,使 Agent 在 GAIA 等真实复杂任务上实现突破。