论文阅读 - 智能体工具调用

工欲善其事,必先利其器

智能体强化学习总览GUI 智能体 里, 我们已经反复看到一个现象: 现在的多模态智能体 (Multimodal Agent) 不缺工具, 缺的是稳定地选择、组合和复用工具的能力.

XSkill

XSkill Teaser

论文 [] Unknown-material 不更新模型参数, 而是从历史多模态工具调用轨迹中提炼两类外部知识:

  • 经验 (Experience): 面向局部决策的条件-动作建议
  • 技能 (Skill): 面向任务流程的结构化工作流和工具模板

论文把当前多模态智能体的问题概括为两点:

  1. 工具使用低效. 模型可能对简单问题过度调用工具, 对复杂问题又探索不够深.
  2. 工具编排僵硬. 很多系统偏向单路径执行, 缺少跨任务迁移的工具组合模式.

XSkill 认为这两个问题需要两种粒度不同的知识来解决. 技能负责高层结构, 经验负责局部策略.

知识表示

论文把技能库和经验库分开维护:

$$ KB=(K,E) $$

其中 $K$ 是 Markdown 形式的 Skill Library, $E$ 是 JSON 形式的 Experience Bank.

技能定义为:

$$ k=(M,W,P) \in K $$

$M$ 是元信息, 包括技能名称、描述和版本; $W$ 是工作流序列; $P$ 是可复用工具模板. 比如一个视觉识别技能里, $W$ 可能包含“检查朝向、裁剪目标、增强图像、搜索图片来源”的流程, $P$ 则包含 Python 图像旋转、裁剪、放大的代码模板.

经验定义为:

$$ e=(c,a,v_e) \in E $$

$c$ 是触发条件, $a$ 是建议动作, $v_e$ 是用于检索的语义向量. 论文限制单条经验的文本长度, 使它更像短的策略性 prompt, 而非完整轨迹复述.

问题建模

论文把多模态工具调用任务建模成 POMDP. 一个任务为:

$$ T=(q,I) $$

其中 $q$ 是自然语言问题, $I=\{I_1,I_2,\ldots,I_m\}$ 是相关图像集合. 工具集合为:

$$ F=\{f_1,f_2,\ldots,f_n\} $$

包括代码执行、网页搜索、图片搜索、网页访问等. 每一步 agent 根据当前状态和观察选择工具动作, 形成轨迹:

$$ \tau=[(s_0,a_0,o_0),\ldots,(s_T,a_T,o_T)] $$

最终构造外部知识库:

$$ \max_{KB} P[\hat{y}=y^\ast \mid T, KB] $$

整体框架

XSkill 分成两个阶段: 积累 (Accumulation) 和求解 (Inference). 论文中还区分了两个模型角色:

  • $\mathrm{MLLM}_{exec}$: 负责实际执行工具调用和回答问题.
  • $\mathrm{MLLM}_{kb}$: 负责总结轨迹、抽取经验、合并技能、重写检索结果.

算法XSkill: 双流知识循环

输入 > 训练任务集合 $\mathcal{D}_{train}$, 测试任务 $T=(q,I)$, 工具集合 $F$, 执行模型 $\mathrm{MLLM}_{exec}$, 知识管理模型 $\mathrm{MLLM}_{kb}$, 技能库 $K$, 经验库 $E$.

输出 > 最终答案 $\hat{y}$, 更新后的知识库 $KB=(K,E)$.

  1. 对训练任务 $T_i=(q_i,I_i)$ 执行 $N$ 条独立 rollout: $$ R_i=\{\tau_i^{(1)},\ldots,\tau_i^{(N)}\} $$
  2. 用 $\mathrm{MLLM}_{kb}$ 对轨迹、图像、问题和标准答案做视觉锚定总结: $$ S_{R_i},\Delta K_i=\mathrm{MLLM}_{kb}(R_i,I_i,q_i,y_i^\ast,K_{adapted}) $$ 其中 $S_{R_i}$ 记录关键决策点、工具模式和失败原因, $\Delta K_i$ 是从成功轨迹中抽出的技能片段.
  3. 做跨 rollout 批判 (Cross-Rollout Critique), 对成功和失败轨迹进行对比: $$ \Delta E_i=\mathrm{MLLM}_{kb}(S_{R_i},y_i^\ast,E_{ret}) $$
  4. 将 $\Delta E_i$ 中的经验加入 Experience Bank, 对相似经验按向量相似度合并, 并删除低质量或过度具体的条目.
  5. 将 $\Delta K_i$ 合并到 Skill Library, 对冗长技能做压缩、去重和变量化.
  6. 对测试任务 $T=(q,I)$, 先把问题分解成若干抽象子任务: $$ G=\{g_1,\ldots,g_{n_g}\} $$
  7. 对每个子任务检索相关经验: $$ E_{ret}=\bigcup_{g\in G}\mathrm{Top}\text{-}k(\{e\in E\mid \cos(v_g,v_e)>\tau_{min}\}) $$
  8. 结合当前图像和问题重写经验: $$ E_{rewritten}=\mathrm{MLLM}_{kb}(E_{ret},q,I) $$
  9. 结合重写后的经验, 对全局技能文档做任务适配: $$ K_{adapted}=\mathrm{MLLM}_{kb}(K,E_{rewritten},q,I) $$
  10. 将 $K_{adapted}$ 和 $E_{rewritten}$ 作为非强制参考注入 system prompt, 由 $\mathrm{MLLM}_{exec}$ 执行工具调用并输出答案.
  11. 记录实际使用了哪些技能和经验, 将使用历史回流到下一轮积累.

可以看到, XSkill 在总结、检索和改写时都显式把图像观察放进去了.

XSkill

积累阶段

rollout summary. $\mathrm{MLLM}_{exec}$ 对同一个训练任务执行 $N$ 次, 得到成功和失败轨迹. $\mathrm{MLLM}_{kb}$ 看到的是完整多模态上下文: 图像、工具调用、中间输出、最终答案和 ground truth.

然后是 cross-rollout critique. 这一步对比成功和失败轨迹, 抽取导致结果差异的因素, 输出对经验库的结构化操作:

$$ \Delta E_i=\{op_1,op_2,\ldots,op_M\} $$

每个操作是 addmodify. 如果新经验和已有经验相似度超过阈值, 系统会让 $\mathrm{MLLM}_{kb}$ 合并这些条目.

技能库的合并也类似. 从 rollout 中抽出的 workflow 和 tool template 会被合并进全局 Markdown 技能文档. 当技能文档超过长度阈值时, 系统会删除过度具体的细节. 这一步把 agent 轨迹整理成一份会不断演化的 SKILL.md.

推理阶段

推理阶段有三个设计点: 任务分解、上下文改写、非强制注入.

直接用原始问题去检索经验通常不够好, 所以 XSkill 先让 $\mathrm{MLLM}_{kb}$ 根据问题和图像分解出多个技术子任务, 再分别检索经验. 检索到的经验也不会直接塞进 prompt. 系统会先做 experience rewrite, 把通用经验改写成当前任务相关的建议.

随后系统会做 skill adaptation, 从全局技能库中剪掉无关章节, 把相关经验融进工作流, 并调整代码模板. 论文强调注入方式是非强制的参考.

ReSeek

论文 [] Unknown-material 讨论的是搜索智能体的自我纠错问题. ReSeek 直接把判断检索信息是否有用变成 agent 的一个内部动作, 并用强化学习训练这个动作.

现有搜索 agent 常见的问题是: 一旦第一次 search query 走错, 后续推理会沿着错误证据继续滚下去. ReSeek 的核心改动是加入一个特殊动作 JUDGE.

JUDGE 动作

ReSeek 把搜索过程建模为 MDP. 策略 $\pi_\theta$ 在每一步根据状态 $s_t$ 生成动作 $a_t$. 动作空间不仅包括搜索和回答, 还包括判断. 论文的训练方法使用 GRPO.

当 agent 得到一次搜索结果 $o_t$ 后, 它生成判断 $j_t\in\{\mathrm{Yes},\mathrm{No}\}$. 这个 judgment 不会物理删除历史, 而是作为 soft removal 信号写回上下文:

$$ C_t=\tau_{t-1}\oplus a_t\oplus o_t\oplus j_t $$

如果 $j_t=\mathrm{Yes}$, 后续策略应该继续利用这条证据; 如果 $j_t=\mathrm{No}$, 后续策略应该语义上忽略这条路径, 重新搜索.

奖励函数

ReSeek 的奖励由两部分组成. 终止时使用答案正确性奖励, 即精准匹配预测答案和标准答案.

关键问题是怎么知道一次检索结果到底有没有用。论文用 bge-reranker-large 计算观察 $o_t$ 和真实答案 $gt$ 的相关性:

$$ j_t^\ast= \begin{cases} \mathrm{Yes}, & \mathrm{score}(o_t,gt)>0.7 \\ \mathrm{No}, & \mathrm{otherwise} \end{cases} $$

然后用 agent 生成的 $j_t$ 和理想判断 $j_t^\ast$ 比较:

$$ R_{\mathrm{judge}}(j_t,j_t^\ast)= \begin{cases} R_{\mathrm{match}}, & j_t=j_t^\ast \\ R_{\mathrm{mismatch}}, & j_t\ne j_t^\ast \end{cases} $$

论文里匹配奖励是 $+0.3$. 惩罚是不对称的: 错把无用信息判断为有用, 惩罚 $-0.6$; 错把有用信息丢掉, 惩罚 $-0.3$.

结构化 Prompt

ReSeek 的 prompt 强制规定每次 search 后必须 judge:

  1. 新信息进入后先在 <think> 中推理.
  2. 需要外部知识时调用 <search> query </search>.
  3. 收到搜索结果后, 必须输出 <judge>Yes</judge><judge>No</judge>.
  4. 如果信息有用但还不够, 必须继续搜索.
  5. 如果信息无用, 必须继续搜索, 不能直接回答.
  6. 只有信息足够时才输出 <answer>...</answer>.

算法ReSeek: 搜索自纠错

输入 > 问题 $q$, 搜索环境 $\mathcal{E}$, 策略 $\pi_\theta$, 最大工具步数 $T$, reranker 阈值 $\tau=0.7$.

输出 > 答案 $\hat{a}$, 搜索轨迹 $\mathcal{T}$.

  1. 初始化上下文 $C_0=q$.
  2. 对 $t=1,\ldots,T$: $$ a_t\sim\pi_\theta(\cdot|C_{t-1}) $$
  3. 如果 $a_t$ 是搜索 $\texttt{query}$, 搜索环境返回观察: $$ o_t=\mathcal{E}(query) $$
  4. agent 对 $o_t$ 执行判断: $$ j_t\in\{\mathrm{Yes},\mathrm{No}\} $$
  5. 训练时, 用 reranker 和标准答案构造理想判断: $$ j_t^\ast=\mathbf{1}\{\mathrm{score}(o_t,gt)>0.7\} $$
  6. 根据 $j_t$ 和 $j_t^\ast$ 给出 $R_{\mathrm{judge}}$.
  7. 更新上下文: $$ C_t=C_{t-1}\oplus a_t\oplus o_t\oplus j_t $$
  8. 如果 $j_t=\mathrm{No}$, 下一步必须继续搜索; 如果 $j_t=\mathrm{Yes}$ 且信息不足, 下一步继续搜索; 如果信息足够, 输出 <answer>.
  9. 终止时根据 Exact Match 给出 $R_{\mathrm{answer}}$.

Reseek

FictionalHot

论文还提出了一个新的 Benchmark FictionalHot. 动机是搜索类 QA benchmark 很容易被预训练数据污染, 模型可能不是通过搜索推理答对, 而是早就记住了答案.

FictionalHot 的构造方法是:

  1. 从 NQ、TriviaQA、PopQA、HotpotQA、2Wiki、Musique 等数据集中抽样.
  2. 用 GPT-5 改写问题, 把真实实体替换成虚构实体, 但保留原有推理结构.
  3. 同时生成新的 Wikipedia 风格文档, 给这些虚构实体赋予世界事实.
  4. 把合成样本插入 2018 Wikipedia corpus, 作为新的检索语料.

FictionalHot

实验

多跳任务上的提升更明显.

  • 7B 模型在 Bamboogle 上, Search-R1 是 0.368, ZeroSearch 是 0.278, ReSeek 是 0.392;
  • 在 FictionalHot 上, 7B 的直接推理只有 0.001, ReSeek 是 0.061;
  • 3B 的直接推理也是 0.001, ReSeek 是 0.059.

这说明 FictionalHot 确实削弱了参数记忆优势, 更考验检索和过程推理.

OS-Symphony

论文 [] Unknown-material (ACL 2026, Zichen Ding) 讨论的是 CUA 框架, 针对的是 GUI agent 在执行未见长程任务时的鲁棒性问题. 它把当前 GUI agent 容易失败的地方拆成几个可协作模块: 一个负责决策的 Orchestrator, 一个负责运行时审计和长期记忆的 Reflection-Memory Agent (RMA), 以及 Searcher / Grounder / Coder 等工具 agent.

整体框架

OS-Symphony 的核心是 Orchestrator. 它在每一步输出 thought 和 action:

$$ t_i,a_i=F_O(I,R_i,o_i,T,H_{\mathrm{short}}) $$

其中 $I$ 是用户指令, $R_i$ 是 RMA feedback, $o_i$ 是当前截图, $T$ 是 Searcher 返回的教程, $H_{\mathrm{short}}$ 是最近 $K$ 步的滑动窗口:

$$ H_{\mathrm{short}}=\{(o_j,t_j,a_j)\}_{j=i-K+1}^{i-1} $$

也就是说 Orchestrator 只看最近几步和 RMA 提炼后的关键信息. 完整历史的压缩和纠错责任交给 RMA.

算法OS-Symphony

输入 > 任务指令 $I$, GUI 环境 $\mathcal{E}$, Orchestrator $F_O$, RMA $F_R$, 步骤总结器 $F_S$, 工具集合 $\mathcal{T}$.

输出 > 任务执行结果和轨迹 $\tau$.

  1. 初始化环境, 得到初始截图 $o_1$.
  2. 初始化短期历史 $H_{\mathrm{short}}$ 和长期里程碑记忆 $H_{\mathrm{long}}$.
  3. 对第 $i$ 步, Orchestrator 根据当前上下文生成: $$ t_i,a_i=F_O(I,R_i,o_i,T,H_{\mathrm{short}}) $$
  4. 如果 $a_i$ 是普通 GUI action, 调用 Grounder 定位并用 PyAutoGUI 执行.
  5. 如果 $a_i=\mathrm{call\_search\_agent}(q)$, 在隔离浏览器环境中搜索并返回教程 $T$.
  6. 如果 $a_i=\mathrm{call\_code\_agent}(g)$, 调用 Coder 通过命令行或脚本完成子任务, 再由 GUI 状态验证.
  7. 执行动作后得到新截图 $o_{i+1}$.
  8. 用步骤总结器比较动作前后截图: $$ S_i,s_i=F_S(O_{i-1},o_{i-1},o_i,\tilde{o}_{i-1}) $$ 其中 $S_i$ 是单步摘要, $s_i$ 表示这一步是否执行成功.
  9. RMA 基于长期记忆生成反思、里程碑标记和可复用知识: $$ R_i,m_i,k_i=F_R(O_{i-1},o_i,H_{\mathrm{long}}) $$
  10. 如果 $m_i=\mathrm{true}$, 把当前摘要和截图写入 $H_{\mathrm{long}}$.
  11. 如果 RMA 判断任务 Completed / Infeasible, 则结束; 否则继续下一步.

OS-Symphony

Reflection-Memory Agent

RMA 是这篇文章最关键的模块. 它的目标是选择性保留里程碑截图, 并把历史轨迹压缩成可审计的摘要.

每一步执行后, 系统先做步骤级别的总结.

$$ S_i,s_i=F_S(\mathcal{O}_{i-1},o_{i-1},o_i,\tilde{o}_{i-1}) $$

$\mathcal{O}_{i-1}$ 是上一轮 Orchestrator 的输出, $o_{i-1}$ 和 $o_i$ 分别是动作前和动作后的截图, $\tilde{o}_{i-1}$ 是一个动作区域的 zoom-in crop. $S_i$ 是步骤总结, $s_i$ 是一个布尔值, 表示这一步是否执行成功.

长期记忆写成:

$$ H_{\mathrm{long}}=\{(S_j,o_j,m_j)\}_{j=1}^{i-1} $$

其中 $S_j$ 是单步摘要, $o_j$ 是保留下来的截图, $m_j$ 是里程碑标记. RMA 在每一步输出:

$$ R_i,m_i,k_i=F_R(O_{i-1},o_i,H_{\mathrm{long}}) $$

其中 $R_i$ 是给 Orchestrator 的反馈, $m_i$ 决定是否作为里程碑保存, $k_i$ 是从页面里抽取的潜在知识 (去向不明).

RMA 的协议把状态分成四类:

  • On-track: 当前轨迹正常, 继续执行.
  • Completed: 任务已经完成.
  • Infeasible: 任务不可完成.
  • Off-track: 轨迹偏离, 需要纠正.

Off-track 又分成四种错误:

  • GUI Error: 底层 GUI 操作没有达到预期.
  • Lack of Tutorial: agent 缺少流程知识.
  • Code Error: Coder 执行结果和 GUI 状态不一致.
  • Other Error: 目标漂移、幻觉、事实错误等其他问题.

为了检测循环, 论文还写了一个 rule-based 信号. 如果最近 $N$ 步和更早的一段轨迹在截图和动作上都高度相似, 就触发循环检测, 给 RMA 一个比较强的 Lack of Tutorial 信号.

RMA

工具 Agent

OS-Symphony 的工具 agent 包括 Searcher、Grounder 和 Coder.

  • Searcher 是论文特别强调的部分. 传统 RAG 往往只抓网页文本, 但 GUI 教程里很多信息藏在截图、按钮布局和菜单层级里. 因此论文的方法是 Orchestrator 在发现缺少流程知识时, 生成一个 “How to” 查询, 然后 Searcher 在一个隔离的浏览器 sandbox 里像人一样浏览网页.

    Searcher 的动作包含点击、输入、滚动、完成和失败, 会访问多个页面交叉确认信息. 如果没有找到高相关内容, 返回 fail; 只有在确定教程和当前问题高度相关时, 才返回结构化的逐步教程 $T$.

  • Grounder 分两类, 其一是 GUI grounding 模型直接根据元素描述输出坐标. 其二是对 Word、PowerPoint 这类文本密集场景, 先 OCR 得到 {text,id,bbox} 表, 再让 VLM 选 ID, 最后查坐标.

  • Coder 则负责适合用代码完成的子任务, 例如文件编辑、配置修改、批处理. Orchestrator 把一个自包含子任务交给 Coder, Coder 执行后返回简短摘要, 再由 Orchestrator 通过 GUI 状态验证. 如果 Coder 失败, 再退回 GUI 操作. (这种混动是否是一种 trick?)

实验

OSWorld 和 WindowsAgentArena 上达到 SOTA. 这里强调一下消融实验:

Setting Daily Workflow Avg.
w/o Search, Refl. 49.60 37.33 51.90
w/o Search 50.65 48.06 53.78
+ Unimodal Search 56.10 39.30 54.81
+ Multimodal Search 61.86 47.37 58.05
w/o Reflection 60.20 39.23 54.38
+ Reflection w/ STM 56.10 39.49 54.01
+ Reflection w/ LTM 61.86 47.37 58.05
  • Multimodal Search 比纯文本搜索更适合 GUI 任务, 尤其在 Daily domain 里收益明显. 这类任务经常需要软件使用教程或外部知识, 单纯 parse 网页文本容易丢掉按钮位置和菜单层级.
  • 反思模块不是“有就好”. Reflection w/ STM 只看短期历史, 效果甚至比 w/o Reflection 更差. 这说明 naive reflection 会带来额外上下文和错误反馈, 反而干扰决策. 真正有效的是带 milestone long-term memory 的 RMA.
使用 Hugo 构建