MineExplorer:把多模态大模型放进开放世界,重新检验长程规划能力

2026-07-27 24 预计阅读时间: 1 分钟
来源: tech.meituan.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:11 分钟

许多多模态评测像一场开卷考试:模型看到一张图片,回答一个问题,几秒内结束。但真实世界并不会把所有条件整齐地摆在上下文里。智能体需要持续观察环境、记住早先发现的信息、识别隐藏的前置条件,并在行动失败后调整计划。美团 LongCat 团队构建的 MineExplorer,正是为了把评测从这种相对静态的“温室”搬到动态开放世界中。

MineExplorer 的关键不只是任务更长,而是任务需要分钟级持续交互,并可能包含未明确告知的前置条件。这让它能够暴露一个容易被短任务掩盖的能力断层:模型也许能识别当前画面、解释工具用途,甚至生成看似合理的计划,却未必能把这些能力稳定地串联成一次完整行动。

分钟级任务改变了评测对象

单轮视觉问答主要检验“看到什么”和“知道什么”。开放世界中的长程任务则至少同时检验四类能力:

  • 状态理解:从连续画面中判断位置、资源、工具和环境变化。
  • 任务分解:把远期目标拆成当前可执行的步骤,并安排依赖顺序。
  • 工作记忆:保存已经探索过的区域、收集过的物品和失败原因。
  • 闭环控制:根据每一步的真实结果更新计划,而不是机械执行最初生成的动作序列。

时间跨度一旦从几秒延长到几分钟,小错误就会累积。一次目标识别偏差可能导致智能体走错方向;遗漏一个资源可能使后续动作无法执行;没有确认动作结果,则可能让内部状态与环境状态逐渐分离。

因此,长程任务的完成率并不是若干单步准确率的简单平均。假设智能体需要连续完成 30 个关键步骤,即使每一步独立成功的概率达到 95%,理想化的全链路成功率也只有:

0.95 ^ 30 ≈ 21.5%

真实环境中的错误通常还会相互影响,结果可能更差。这解释了为什么在静态榜单上表现接近的模型,进入开放世界后可能拉开明显差距。

隐藏前置条件考验的不是知识,而是发现机制

“获得目标物品”看起来像一个简单指令,但开放世界不会总是直接提供执行条件。智能体可能需要先找到基础资源,再制作工具,然后进入特定区域,最终才能完成目标。任务只描述终点,依赖链却藏在环境规则和当前状态中。

这类问题不能只靠一次性规划解决。模型需要在执行中不断回答三个问题:

  1. 当前动作缺少什么条件?
  2. 这个条件可以通过哪些中间目标获得?
  3. 新观察是否推翻了原来的计划?

一个可靠的智能体应把“动作失败”视为新证据。例如,环境返回 missing_tool 时,不应原样重复动作,而应把“获得工具”插入计划;发现路径受阻后,也应重新探索,而不是继续输出无效移动指令。

这揭示了静态推理与具身执行之间的差别。前者只要求模型说出正确步骤,后者要求模型确认每一步确实发生,并把环境反馈写回状态。

可以这样搭建一个最小长程评测器

下面的 Python 示例不是 MineExplorer 官方接口,而是一个可运行的简化实验,用来复现两项核心压力:隐藏前置条件和有限步数。将代码保存为 mini_explorer.py,然后运行 python mini_explorer.py

from dataclasses import dataclass, field
from typing import Callable


@dataclass
class World:
    inventory: set[str] = field(default_factory=set)
    steps: int = 0
    max_steps: int = 8

    def act(self, action: str) -> dict:
        self.steps += 1
        if self.steps > self.max_steps:
            return {"ok": False, "reason": "step_limit"}

        rules: dict[str, tuple[set[str], str]] = {
            "collect_wood": (set(), "wood"),
            "craft_pickaxe": ({"wood"}, "pickaxe"),
            "mine_iron": ({"pickaxe"}, "iron"),
            "craft_compass": ({"iron"}, "compass"),
        }

        if action not in rules:
            return {"ok": False, "reason": "unknown_action"}

        required, produced = rules[action]
        missing = required - self.inventory
        if missing:
            return {
                "ok": False,
                "reason": "missing_precondition",
                "missing": sorted(missing),
            }

        self.inventory.add(produced)
        return {
            "ok": True,
            "produced": produced,
            "inventory": sorted(self.inventory),
        }


def evaluate(policy: Callable[[dict], str]) -> dict:
    world = World()
    observation = {
        "goal": "obtain_compass",
        "inventory": [],
        "last_result": None,
    }
    trace = []

    while world.steps < world.max_steps and "compass" not in world.inventory:
        action = policy(observation)
        result = world.act(action)
        trace.append({"action": action, "result": result})
        observation = {
            "goal": "obtain_compass",
            "inventory": sorted(world.inventory),
            "last_result": result,
        }

    return {
        "success": "compass" in world.inventory,
        "steps": world.steps,
        "trace": trace,
    }


def dependency_aware_policy(observation: dict) -> str:
    inventory = set(observation["inventory"])
    if "wood" not in inventory:
        return "collect_wood"
    if "pickaxe" not in inventory:
        return "craft_pickaxe"
    if "iron" not in inventory:
        return "mine_iron"
    return "craft_compass"


if __name__ == "__main__":
    report = evaluate(dependency_aware_policy)
    print(f"success={report['success']} steps={report['steps']}")
    for index, item in enumerate(report["trace"], start=1):
        print(index, item)

预期输出如下:

success=True steps=4
1 {'action': 'collect_wood', ...}
2 {'action': 'craft_pickaxe', ...}
3 {'action': 'mine_iron', ...}
4 {'action': 'craft_compass', ...}

接入真实多模态模型时,可以保留 evaluate 的闭环结构,把 dependency_aware_policy 替换成模型调用,并让模型只返回结构化动作:

{
  "action": "collect_wood",
  "reason": "A pickaxe requires wood, which is not in the inventory."
}

评测器应校验动作是否合法、记录每次观察与结果,并设置超时、最大步数和无进展终止条件。不要让模型直接修改环境状态,否则测到的只是文本自洽性,而不是交互能力。

比完成率更有诊断价值的指标

只记录“成功”或“失败”会丢掉大量信息。面向长程任务,可以同时收集以下指标:

  • 任务完成率:模型是否在预算内达到最终目标。
  • 有效动作比例:成功改变环境状态的动作占比。
  • 前置条件发现率:遇到依赖缺失后,模型能否找到正确的中间目标。
  • 恢复成本:首次错误后,还需要多少步才能回到有效路径。
  • 重复动作率:模型是否在相同状态下反复执行同一失败动作。
  • 状态一致性:模型记录的物品、位置和进度是否与环境一致。
  • 资源与时间开销:完成任务消耗的模型调用次数、令牌量和实际时间。

还需要固定或记录环境随机种子,并对多个初始状态重复测试。否则,模型可能只是碰巧遇到了更短的路线或更有利的资源分布。对视觉输入、动作接口和失败反馈也应保持一致,避免把控制器差异误判为模型能力差异。

如何把开放世界评测用于模型选型

MineExplorer 所代表的评测方向提醒我们:为智能体选择模型时,不能只看单轮视觉理解和短链推理成绩。真正影响落地的,往往是模型能否维持状态、发现依赖、验证结果,并从错误中恢复。

采用这类基准时,可以按以下顺序推进:

  • 先用固定任务和确定性环境验证评测器本身,排除控制接口与视觉采样问题。
  • 再逐步增加任务长度、环境随机性和隐藏依赖,观察性能从哪里开始明显下降。
  • 保存完整轨迹,而不只保存最终得分,以便区分感知错误、规划错误和执行错误。
  • 同时统计成功率、成本与延迟,避免选出“偶尔成功但代价不可控”的模型。
  • 把真实业务中的关键依赖改写成类似任务,例如先鉴权再查询、先确认库存再下单、先读取告警上下文再执行运维动作。

开放世界不是为了让评测看起来更复杂,而是为了让失败更接近生产环境。模型能说出答案,只代表它具备某些局部能力;模型能在动态反馈、隐藏条件和有限预算下持续完成任务,才更接近一个可部署的智能体。


相关推荐