视觉 Agent 的可复现测试:宝可梦游戏案例
提出多模态 Agent 的可复现评测协议,通过真实游戏环境对比 Agent 性能,适合多模态 Agent 开发者参考。
提出多模态 Agent 的可复现评测协议,通过真实游戏环境对比 Agent 性能,适合多模态 Agent 开发者参考。
AL
Agent Lab Journal
Guides
Glossary
可重复评估 · 视觉智能体
等级:高级
阅读时间:60 分钟
成果:一套在相同游戏环境中通过进度、总成本、操作数和记忆错误来对比两个模型的协议
廉价的操作不是高效的操作。如果一个视觉智能体忘记了它已经收集了橡树的包裹,五次穿过同一走廊,重新打开它三十秒前看过的菜单以获取信息,或者反复尝试不可能的转换,其低廉的单次调用价格掩盖了昂贵的停滞。有用的对比单位不是成本每操作,而是在相同控制回合下经过验证的进度每单位成本。
证据边界。本文提供了完整的实验协议,而非虚构的基准数字。没有声称哪个模型获胜。你必须从自己的模拟器录像、模型账单、操作日志和人工标注中填充结果表。
实验要回答的问题
一个视觉智能体观察截图,对可见状态进行推理,并选择控制器输入。在游戏中,它的表观智能不仅取决于图像识别。它必须在许多决策中保留相关事实:它去过哪里、哪个目标是活跃的、某个 NPC 说了什么、当前的菜单状态是什么,以及前一个操作是否改变了世界。
该协议在一个有界的 Pokémon FireRed 任务上对比两种模型配置。它回答:
在相同操作和成本预算内,哪个模型完成了更多验证的里程碑?
在相同操作和成本预算内,哪个模型完成了更多验证的里程碑?
每个验证的里程碑成本是多少?
每个验证的里程碑成本是多少?
需要多少控制器操作和模型决策?
需要多少控制器操作和模型决策?
每个智能体多久会忘记、矛盾、复制或破坏有用的状态?
每个智能体多久会忘记、矛盾、复制或破坏有用的状态?
有多少活动是有成效的,有多少是重复的导航或恢复?
有多少活动是有成效的,有多少是重复的导航或恢复?
该协议不询问哪个模型总体上"玩 Pokémon 更好"。这种说法需要多个任务、地图、战斗条件和随机种子。更窄的目标是在一个游戏开场部分中对比记忆依赖的进度的受控对比。
为什么成本每操作具有误导性
假设模型 A 的每次推理成本低于模型 B。如果模型 A 需要更多的观察、产生无效命令、重新访问已完成的房间或丢失当前目标,它在每个里程碑上仍然可能更昂贵。因此核心数量是:
cost_per_verified_milestone =
total_episode_cost / verified_milestones_completed
actions_per_verified_milestone =
accepted_controller_actions / verified_milestones_completed
productive_action_rate =
productive_actions / accepted_controller_actions
progress_efficiency =
weighted_progress_points / total_episode_cost
操作是来自允许的操作集合的一个被接受的模拟器输入。模型决策可能发出一个操作或一个有界的序列。保持这些数量分开。一个十按钮的宏是一次推理但十个操作,将其视为一个操作不公平地奖励了批处理。
状态是区分当前情况与实质不同情况所需的信息:可用时的地图和坐标、屏幕类型、菜单深度、库存标志、目标标志、战斗状态和近期转换。截图只是对该状态的观察。它可能是模糊的、部分遮挡的,或即使内部标志改变时也保持不变。
具体案例:橡树的包裹循环
使用从你拥有的媒体上转储的合法获得的 Pokémon FireRed ROM,受你居住地的法律约束。不要发布 ROM、从受版权保护的游戏数据衍生的存档文件或捆绑的游戏资产。私下记录确切的 ROM SHA-256,以便每次运行使用相同的字节。
推荐的回合从玩家卧室中的规范存档状态开始,在第一个移动输入前。目标是完成这个有序序列:
离开卧室和玩家的房子。
离开卧室和玩家的房子。
在 Pallet Town 北部触发 Professor Oak 的中断。
在 Pallet Town 北部触发 Professor Oak 的中断。
到达实验室并获得初始 Pokémon。
到达实验室并获得初始 Pokémon。
完成必需的宿敌战斗。
完成必需的宿敌战斗。
到达 Viridian City 并从 Poké Mart 店员那里收到 Oak 的包裹。
到达 Viridian City 并从 Poké Mart 店员那里收到 Oak 的包裹。
返回 Professor Oak 并交付包裹。
返回 Professor Oak 并交付包裹。
这个案例之所以有用,是因为它结合了导航、对话、菜单处理、战斗、一个远方目标和通过先前访问过的空间的返回路线。智能体必须区分"我看到了 Mart"与"我收到了包裹"以及"我拥有包裹"与"我已经交付了它"。这些正是弱记忆倾向于崩溃的区别。
初始精灵选择是混淆因子,因为它改变了宿敌战斗。在清单中固定一个初始精灵。不要让一个模型自由选择而强制另一个。同样,固定文本速度、战斗风格、模拟器速度、帧跳、音频、语言和任何操作重复行为。
定义两个条件后再运行任何一个
一个公平的测试每次改变一个主要变量。有两个有效的设计:
设计 A:对比模型产品
模型 A 和模型 B 接收相同的截图、系统提示、记忆接口、操作模式、预算和环境。这在相同的工具下衡量完整的模型。它不隔离记忆架构,因为模型在视觉、规划、延迟和指令遵循上可能不同。
设计 B:隔离持久记忆
使用相同的模型两次。在基线条件中,仅提供固定的最近窗口。在记忆条件中,添加一个具有相同读写规则的结构化持久存储。这更直接地估计了智能体记忆的效果,尽管记忆机制引入了额外的令牌和操作。
除非你分别测试了感知和规划,否则不要将设计 A 描述为证明一个模型有"更好的记忆"。安全的结论是一个完整的配置在该协议下更有效地保留和使用了与任务相关的状态。
实验控制
必须保持相同的变量
| 控制 | 需要的值 | 为什么重要 |
|---|---|---|
| ROM | 相同的私有 SHA-256 | 不同的修订版本可以改变行为或内存地址。 |
| 初始存档状态 | 相同的不可变文件和 SHA-256 | 防止不同的库存、标志、位置或 RNG 历史。 |
| 模拟器 | 相同的二进制、版本、核心和配置 | 控制时序、帧步进和存档状态行为。 |
| 视口 | 相同的原生帧、裁剪、缩放和图像编码 | 像素的改变可以改变视觉识别。 |
| 输入集 | UP、DOWN、LEFT、RIGHT、A、B、START、SELECT、NOOP | 防止隐藏的宏或模型特定工具。 |
| 每操作帧数 | 固定的按下和释放持续时间 | 长按可能会移动多个瓷砖或跳过对话。 |
| 提示 | 字节相同,除了模型需要的包装器 | 提示差异可以充当外部记忆。 |
| 预算 | 相同的操作、决策、壁时间和货币限制 | 每个模型必须有相同的机会来进展。 |
| 重试 | 相同的策略和最大值 | 无声的重试隐藏了无效输出和额外成本。 |
| 评分 | 在模型名称被揭示给标注者前冻结 | 减少结果驱动的解释。 |
如果供应商不支持确定的种子,记录该事实。游戏回合仍然可以被对比,但重复的试验变得必要。零温度不保证在托管基础设施中的确定性输出。
构建一个不可变的实验清单
为整个对比创建一个清单。将占位符替换为真实值。永远不要在此文件中放置 API 密钥。
experiment_id: firered-memory-v1
protocol_version: 1
created_utc: "YYYY-MM-DDTHH:MM:SSZ"
game:
title: "Pokemon FireRed"
rom_sha256: "REPLACE_WITH_PRIVATE_HASH"
initial_state_sha256: "REPLACE_WITH_HASH"
language: "English"
starter: "Bulbasaur"
text_speed: "fast"
battle_style: "shift"
emulator:
name: "REPLACE_WITH_EMULATOR"
version: "REPLACE_WITH_VERSION"
config_sha256: "REPLACE_WITH_HASH"
frame_width: 240
frame_height: 160
press_frames: 2
settle_frames: 8
speed_multiplier: 1
frame_skip: 0
agent:
allowed_actions:
- UP
- DOWN
- LEFT
- RIGHT
- A
- B
- START
- SELECT
- NOOP
max_actions_per_decision: 4
screenshot_every_decision: true
recent_action_window: 12
invalid_output_retries: 1
budgets:
accepted_actions: 2500
model_decisions: 1200
wall_time_minutes: 180
total_cost_usd: "SET_BEFORE_RUN"
trials:
per_condition: 5
order: "counterbalanced"
restore_initial_state_before_each_trial: true
conditions:
- id: model_a
model: "REPLACE_WITH_PINNED_MODEL_ID"
memory_policy: "shared-policy-v1"
- id: model_b
model: "REPLACE_WITH_PINNED_MODEL_ID"
memory_policy: "shared-policy-v1"
托管模型别名可能会在没有警告的情况下改变。记录提供商的精确模型标识符和每次决策的响应元数据。如果只有浮动别名可用,说明该限制并在最短的实际时间窗口内完成两个条件。
使用受约束的动作契约
自由形式的文本使解析失败难以与推理失败区分。需要结构化输出并拒绝模式外的命令。
{
"reasoning_summary": "short operational explanation",
"observed": {
"screen": "overworld|dialogue|menu|battle|transition|unknown",
"location_guess": "string or unknown",
"objective_guess": "string",
"certainty": 0.0
},
"memory_read": ["fact_id"],
"memory_write": [
{
"kind": "objective|location|event|inventory|warning",
"value": "atomic fact",
"confidence": 0.0,
"evidence": "visible cue or transition"
}
],
"actions": ["UP", "A"]
}
工具应验证类型、枚举值、序列长度和数值范围。在重试前记录无效响应。修复的响应仍消耗延迟和成本,因此在总成本中包含两个尝试。
不要将内部仿真器状态暴露给 AI 智能体,若被测试的接口仅为视觉界面。内部状态可由评估者用作隐藏的 oracle,但不得放入提示。
使用两个显式层。工作记忆包含最新的截图、最近的动作、最近的观察和当前目标。情景记忆包含持久事件,例如"在常磐超市收到橡树的包裹",包括证据和决策索引。
一个实用的记录看起来像这样:
{
"fact_id": "event-0042",
"kind": "inventory",
"subject": "player",
"predicate": "has_item",
"object": "Oak's Parcel",
"status": "active",
"confidence": 0.96,
"first_seen_decision": 318,
"last_confirmed_decision": 321,
"evidence_frame_sha256": "FRAME_HASH",
"supersedes": null
}
事实必须是原子的和可修订的。交付后,不要仅仅追加"包裹已交付"而留下"拥有包裹"处于活动状态。将持有事实标记为已被取代。否则内存存储本身会产生矛盾。
对两个模型应用相同的规则:
只有由可见线索或验证转换支持的事实才能写入为已确认。
只有由可见线索或验证转换支持的事实才能写入为已确认。
猜测必须具有较低的置信度,且不得默认演变为事实。
猜测必须具有较低的置信度,且不得默认演变为事实。
已完成的目标必须标记为完成,而非删除。
已完成的目标必须标记为完成,而非删除。
相互矛盾的活动事实必须一起返回,以便 AI 智能体可以解决它们。
相互矛盾的活动事实必须一起返回,以便 AI 智能体可以解决它们。
记忆检索必须使用相同的最大记录数和令牌预算。
记忆检索必须使用相同的最大记录数和令牌预算。
每次读取、写入、更新和驱逐都必须被记录。
每次读取、写入、更新和驱逐都必须被记录。
不泄露地面真实给 AI 智能体
评估者需要比 AI 智能体接收到的更强的证据。使用三个同步的来源:
无损截图或每个决策边界的视频。
无损截图或每个决策边界的视频。
已接受的输入流,带有帧计数。
已接受的输入流,带有帧计数。
隐藏状态探针(如在技术和法律上适当)或从录像中进行手动里程碑标注。
隐藏状态探针(如在技术和法律上适当)或从录像中进行手动里程碑标注。
检查点是可验证的里程碑,而非模型声称发生的事情。在测试前定义检查点:
| ID | 里程碑 | 分数 | 验证 |
|---|---|---|---|
| M1 | 离开玩家的房子 | 1 | 在真新镇记录到野外帧 |
| M2 | 橡树事件被触发 | 1 | 对话和强制转换可见 |
| M3 | 获得初始宝可梦 | 2 | 选择对话加上队伍确认 |
| M4 | 对手战斗完成 | 2 | 战斗退出和战斗后状态可见 |
| M5 | 到达常磐城市 | 2 | 位置转换已确认 |
| M6 | 收到橡树的包裹 | 3 | 店员对话和库存或标志确认 |
| M7 | 包裹已交付 | 4 | 橡树对话和包裹不再活跃 |
| M8 | 收到 Pokédex | 5 | 对话加上菜单或隐藏标志确认 |
一个里程碑仅计分一次。重新进入常磐城市不会再次获得 M5。加权分数表示任务深度,而有序里程碑计数仍然是最容易解释的结果。报告两者;不要在看到结果后调整权重。
以操作方式定义记忆错误
记忆错误不是每一个不好的动作。走进墙可能是感知或控制错误。仅当记录的证据显示保留的状态不存在、错误、矛盾或被忽视时,才计算记忆错误。
| 类型 | 说明 |
|---|---|
| 遗漏 | 一个以前验证过的、仍然相关的事实在需要时不可用。示例:AI 智能体在收到包裹后询问当前交付目标是什么。 |
| 虚假持久性 | 一个过时的事实仍然处于活动状态。示例:记忆在验证交付后仍然说持有包裹。 |
| 杜撰 | AI 智能体存储或声称一个从未发生过的事件。示例:它记录在返回橡树之前收到了 Pokédex。 |
| 矛盾 | 两个不兼容的事实同时被视为真。示例:"包裹未收集"和"带着包裹返回橡树"。 |
| 重复目标 | 一个已完成的目标因为完成未被保留而被重新启动。 |
| 上下文替换 | 来自另一个位置、菜单或试验的事实被应用于当前状态。 |
| 记忆使用失败 | 正确的事实被检索,但选定的计划与其矛盾。 |
| 不支持的覆盖 | 正确的记忆被较低置信度的猜测替换,且没有新证据。 |
分别记录严重程度:
轻微:最多浪费五个接受的动作,不会导致里程碑倒退。
轻微:最多浪费五个接受的动作,不会导致里程碑倒退。
重大:浪费超过五个动作、导致重复路线或延迟里程碑。
重大:浪费超过五个动作、导致重复路线或延迟里程碑。
严重:使事件在预算内无法恢复或破坏实验状态。
严重:使事件在预算内无法恢复或破坏实验状态。
保持原始计数和标准化速率:
memory_error_rate =
confirmed_memory_errors / model_decisions * 100
material_memory_error_rate =
material_or_critical_errors / model_decisions * 100
memory_recovery_cost =
sum(actions_between_error_and_recovery)
repeat_overhead =
repeated_navigation_actions / accepted_controller_actions
检测重复路线
路线重复是记忆质量和成本之间最清晰的桥梁。若评估者可获得隐藏坐标,构建转换键:
transition = (
map_id_before,
tile_x_before,
tile_y_before,
action,
map_id_after,
tile_x_after,
tile_y_after
)
当相同的有序转换序列(至少四个转换)在没有新里程碑、库存变化、战斗结果或两次出现间的合理恢复事件的情况下再次出现时,计算重复路线段。排除所需的回溯:从常磐城市返回真新镇是任务的一部分。仅当 AI 智能体在没有推进活动目标的情况下遍历一个段时,它才成为浪费性重复。
若坐标不可用,从帧中标注房间和地标。这不太准确,因此报告标注者间的一致性并保留争议案例,而不是强制确定性。
为每个 AI 智能体决策写入一条仅追加的跟踪记录。JSON Lines 很方便,因为部分运行仍然可读。
{
"experiment_id": "firered-memory-v1",
"condition_id": "model_a",
"trial_id": 1,
"decision_id": 318,
"timestamp_utc": "YYYY-MM-DDTHH:MM:SS.sssZ",
"frame_sha256": "HASH",
"prompt_sha256": "HASH",
"model_id": "PINNED_ID",
"response_id": "PROVIDER_RESPONSE_ID_IF_AVAILABLE",
"input_tokens": null,
"output_tokens": null,
"cached_tokens": null,
"provider_cost_usd": null,
"latency_ms": null,
"raw_response_path": "responses/000318.json",
"parsed_actions": ["UP", "UP"],
"accepted_actions": 2,
"invalid_output": false,
"memory_reads": ["event-0042"],
"memory_writes": [],
"milestones_before": ["M1", "M2", "M3", "M4", "M5", "M6"],
"milestones_after": ["M1", "M2", "M3", "M4", "M5", "M6"],
"termination_reason": null
}
不可用的测量值使用 null;永远不要用零替代。零表示已测量但不存在。尽可能捕获提供者报告的使用情况,而不是估计 token 数。如果提供者没有公开货币成本,从单独保存的价格表计算,并将该值标记为"derived"。
firered-memory-v1/
├── manifest.yaml
├── prompts/
│ ├── system.txt
│ └── action-schema.json
├── conditions/
│ ├── model-a.yaml
│ └── model-b.yaml
├── trials/
│ ├── model-a-01/
│ │ ├── trace.jsonl
│ │ ├── frames/
│ │ ├── responses/
│ │ ├── memory.jsonl
│ │ └── annotations.jsonl
│ └── model-b-01/
├── scoring/
│ ├── milestones.yaml
│ └── memory-error-rubric.yaml
└── SHA256SUMS
每次试验后,对证据包进行哈希:
find prompts conditions trials scoring \
-type f -print0 |
sort -z |
xargs -0 sha256sum > SHA256SUMS
sha256sum --check SHA256SUMS
不要将 SHA256SUMS 本身包含在其输入集中。在分析前运行验证命令,在分享结果前再运行一次。
冻结协议。对 manifest、提示、schema、里程碑定义和错误评分标准进行哈希。
验证恢复。将初始状态恢复两次,确认第一帧哈希和评估器状态相同。
测试运行框架。使用脚本化策略而不是任何测试模型,验证输入时序、帧捕获、日志记录和终止。
检查隔离。确认没有记忆记录、会话 ID、缓存或提供者会话跨越试验边界。
分配盲化标签。在标注者无法访问的文件中将模型映射到条件 X 和条件 Y。
平衡顺序。每种条件运行五次试验,按 X, Y, Y, X, X, Y, Y, X, X, Y 顺序,或使用另一个预先声明的平衡顺序。
恢复规范状态。在每次试验前验证其哈希。
同时启动所有预算。从第一次决策起计算推理重试、解析修复、记忆调用和控制器操作。
机制性终止。在 M8 处停止,或当任何声明的预算耗尽、框架失败或模拟器发散时停止。
独立标注。让两名审阅者标注里程碑、重复路线和疑似记忆错误,不知道模型身份。
解决分歧。保留两个原始标签并记录仲裁原因。
评分后解盲。仅在试验级标注冻结后计算总结。
试验在首次匹配以下条件时结束:
接受的操作预算耗尽;
模型决策预算耗尽;
挂钟时间预算耗尽;
货币预算耗尽;
模拟器崩溃或产生无法恢复的状态;
智能体进入无法恢复的循环,定义为同一路线段重复三次而无新进展。
不要因为模型"即将成功"就额外授予操作。报告终止原因。框架失败通常应使配对试验失效,并从相同时间表位置触发两种条件的重新运行;模型生成的无效命令仍然是该模型结果的一部分。
保留试验级数据。不要将所有操作合并成一个巨大的片段。对每次试验报告:
最深有序里程碑和总加权进度;
总提供者成本,包括重试和记忆操作;
接受的控制器操作和模型决策;
wal