动态动作空间:让大语言模型Agent适应随时变化的工具集

发布时间:2026/10/21 8:09:49
动态动作空间:让大语言模型Agent适应随时变化的工具集
去年年底我们研发小组在推进一个工具调用Agent项目时碰到了一个很气人的现象模型在固定的十来个小工具里表现相当稳定平均成功率能到百分之八十多。但产品经理随手往工具列表里塞了一个新插件整套系统的成功率直接掉到一半以下。排查了很久问题不在代码也不在提示词长度而是模型压根不知道这个新工具存在——它只会从训练时或者上下文里见过的动作里选。这个现象让我把整个项目的重心从“把固定工具用得更聪明”掰向了“让模型能适应随时变化的动作集合”。最终我们产出了一个代号为DYNAACT的方案尝试把动态动作空间作为大语言模型推理过程的一等公民来处理。这篇博客就是把这套方法完整拆开把背后的设计思路、训练细节、实验结论和踩坑记录都摊在桌面上。如果你正在做工具调用、智能体框架或者任何需要频繁增删可用操作的场景这篇内容应该能帮你省下不少试错时间。1. 为什么固定动作空间会成为推理Agent的隐形天花板1.1 动作空间到底是什么先统一底层语言在聊DYNAACT之前得先把“动作空间”这个词说清楚。很多同学一看到“动作空间”就想到强化学习里的连续控制比如机械臂的角度、自动驾驶的方向盘转角这是连续动作空间。但大语言模型场景里的动作空间几乎都是离散的更准确地说是模型在某个推理时刻“可以做的一组事情”的集合。举个例子。你让模型执行“查询订单状态并修改备注”这个任务模型可以做的事情包括调用订单查询工具、调用备注修改工具、返回文本给用户、询问更多信息等。这里的每一种可执行操作就是一个动作所有可选动作的集合就是当前步骤的动作空间。这里有个容易混淆的点对大语言模型来说“输出一句话”本身也算动作但我们讨论的动作更倾向于那些有明确结构、可以被程序解析并执行的动作比如API调用、结构化命令等。在Agent类应用里动作通常被表示为函数名加参数的形式模型本质上是在学一个条件分布——给定当前状态从候选动作集合里挑一个最优解。如果把动作空间类比成菜单模型就是一个点菜员。问题是好的点菜员应该能看懂今天厨房实际能做什么而不是只会背昨天的菜单。这正好引出固定动作空间的根本问题。1.2 固定动作空间下模型的“稳定”是靠记忆撑起来的现在的工具调用类工作最主流的做法是把可用工具的描述拼进系统提示词让模型根据描述去调用。类似ReAct、函数调用微调这些思路本质上都是这个框架——模型看到工具清单在推理时选择某个工具并生成参数。这种做法在动作空间固定的时候确实够用。我们内部做过测试如果动作集合在训练和测试阶段完全一致几十个工具的范围内模型都能做到很高的调用准确率。原因也不复杂模型在微调时见过这些工具的名字和描述它对“什么场景该用哪个工具”已经形成了记忆映射。推理时只需要把记忆迁移到当前任务上就行。但“记忆”恰恰是最大的隐患。只要工具列表发生任何变化——新增一个工具、下架一个工具、修改了某个工具的参数规范——模型对动作空间的认知就过期了。你在固定动作空间上辛苦调出来的成功率在新动作空间面前基本归零。这不是一句空话。我们在一个实验里尝试过最朴素的增量方案新工具上线时只更新提示词里的工具描述不重新训练模型。结果令人失望模型会惯性调用旧工具对描述里新加入的工具频繁漏选。即便把新工具的描述加得很醒目模型也只会在旧工具全部失效的极端情况下才“想起来”去用新工具。1.3 三个让固定假设崩塌的真实场景哪些场景真正需要动态动作空间我梳理了三个最常见也最棘手的。第一个是工具集随外部系统动态增减。比如企业内部自动化平台每个业务线都在往流程里挂新的接口另一个部门下线了老接口。动作空间的增量更新频率远超模型重训练频率你不可能每周都重新微调一次模型。第二个是操作权限随用户或上下文变化。同一个客服Agent面对不同订阅等级的用户可调用的高级操作完全不同。一个能访问全部工具集的授权用户和一个只能访问基础工具的普通用户理论上应该对应两个动作空间。更麻烦的是权限可能在多轮对话中途变化用户充值升级了套餐Agent下一轮能做的事情立刻就要扩容。第三个是动作空间由当前环境状态实时推导。比如让模型玩一局游戏它每一步能走的格数由棋盘状态决定让模型操作一个网页表单下拉框的选项是接口返回的动态数据。这类场景的动作空间几乎每步都在变固定动作空间的方案天然不适用。这三个场景共同的特征是动作空间的变化发生在模型部署之后且变化频率高于模型更新频率。只要你的应用踩中其中任意一个就需要重新思考架构而不是继续在提示词里堆工具描述。2. DYNAACT的顶层设计把动态动作空间变成可计算的输入2.1 和现有Agent架构的差异定位DYNAACT的核心出发点和现有方案不同。像Toolformer一类的路线是让模型学会在需要时调用工具但工具集合本身是训练时固定的ReAct类路线则把工具描述写进上下文模型靠上下文理解来选动作。DYNAACT既不是简单地把工具描述拼进提示词也不是训练一个固定的工具调用器而是把“当前有哪些动作可以用”这一信息显式建模为推理过程的输入变量并通过专门训练让模型学会感知动作空间的变化。换句话说传统方案希望模型“记住”动作集合并做出选择DYNAACT希望模型“理解当前可选动作的集合”后再做推理。这里面的差别很微妙但很关键。我们还对比过检索增强方案先把所有工具描述存成向量每次推理时检索出Top-K个相关工具再让模型做选择。这套方案能解决工具数量大的问题但对动态变化适应性很弱。因为检索候选本身需要预先建立索引工具增删后索引不一定能及时刷新而且检索到的工具集合会影响模型对“可选边界”的感知——模型并不知道候选集合外面还有没有更好的动作。DYNAACT的做法是把动作空间整体暴露给模型让模型自行判断应该选择哪一个。2.2 动作空间的序列化与编码方法要让模型处理动态动作空间第一步是确定动作空间怎么表达。我们的做法是设计了一套紧凑的结构化动作清单格式每个动作包含四个字段动作名、功能描述、输入约束、启用状态。动作名必须唯一且稳定因为这是模型最终输出并映射到实际函数的标识。功能描述尽量控制在十个词以内太长会稀释模型对动作列表的整体注意力。输入约束描述参数的数量和类型比如“接受一个商品编号字符串”帮助模型生成合法参数。启用状态则标记该动作当前是否可用可用为true不可用为false。在序列化级别我们借鉴了XML的分隔逻辑用自定义标记把动作清单包裹起来放在用户消息和指令之间。模型推理时先看到一段类似这样的输入dynact action namequery_order argsorder_id desc查询订单状态 enabledtrue/ action namemodify_remark argsorder_id, remark desc修改订单备注 enabledtrue/ action namerefund argsorder_id, amount desc发起退款 enabledfalse/ /dynact设计上的一个重要取舍是不在动作清单里写详细的功能文档只保留最少信息。很多人会觉得描述写得越详细模型越理解但实测下来详细描述会让序列长度快速膨胀把模型注意力从当前任务状态上吸走反而掉点。模型需要的是“我知道有这个动作它能干大概什么事”细节可以在动作实际执行后通过结果反馈补充。2.3 动态感知的推理接口有了动作清单还需要一个配套的模型输出协议。我们的做法是让模型在需要调用动作时输出统一的调用标记[act:query_order(12345)]这个标记有三个好处。一是格式足够简单程序解析时几乎不会出错二是与自然语言输出边界清晰模型在生成推理链时不会和动作调用混在一起三是模型对这段结构化输出的生成难度低不会因为生成了复杂嵌套结构而崩溃。训练时我们对输出标记做了特殊处理在词表层面加入专用的动作调用标记而不是让模型靠自然语言凭空拼接。这样保证了生成的合法性和稳定性。后面我会专门说token化的问题这是动态动作空间里一个很容易踩的暗坑。2.4 训练策略课程化动态构造DYNAACT不是换个提示词格式就能生效的关键在训练。我们的训练分为两个阶段监督微调和偏好优化。监督微调阶段的数据构造是核心。我们设计了课程化训练策略先从固定动作空间的样本开始让模型学会基本的推理和动作调用模式然后逐步引入低频率的动作空间变化比如每个训练样本只微调一个动作的启用状态最后过渡到高扰动场景比如每步推理动作集合都会随机增删多个动作。这样做的逻辑是如果一开始就扔给模型大量随机变化的动作空间模型很容易陷入迷惑不知道自己是应该正确处理任务还是先应付变化本身。课程化的好处在于让模型分阶段建立两类能力——基础的推理能力和对动作空间变化的适应性。我们在实验里对比过直接混合训练和课程化训练后者在最终评估上的成功率高出约六个百分点。监督微调之后是偏好优化。这一步的目的是让模型更偏好那些“在正确动作空间里做出正确选择”的推理路径抑制“虽然结果对了但动作调用过程混乱”的路径。具体做法是用同一任务构造两个模型输出一个动作序列高效且合法另一个动作序列绕路或者产生了无效调用然后让模型学习偏好前者。具体的训练细节放在下一节展开。3. 关键实现细节与训练配置3.1 模型与参数配置实验底座我们用了流行的开源7B和13B模型LoRA做参数微调。秩选择64LoRA应用在注意力模块和全连接层上。优化器用AdamW学习率设成2e-5采用余弦衰减。序列长度方面7B模型用409613B用8192上下文。动作清单平均长度控制在两百个token以内留给任务状态和推理链足够的空间。8卡训练环境下7B模型的训练时长大约是一天半13B大概需要两天多。我们没有做全参微调原因很简单——全参版本在后续动作空间持续演进的场景下更新成本太高LoRA的模块化特性更适合后续增量实验。3.2 训练数据构造流程数据质量直接决定DYNAACT的效果。我们的数据构造流程分四步走。第一步是任务生成。使用了两个模拟环境一个是多步工具调用的客服订单处理场景另一个是网页表单操作模拟环境。每个环境下预置了四十个基础动作和一个包含两百个动作的动态池。任务描述按现实业务需求生成比如“查询用户订单并修改配送地址”。第二步是动作空间采样。每个训练样本从动态池里随机抽取四到十二个动作其中大约一半是与当前任务直接相关的另一半是干扰项——这些动作虽然存在但和当前任务无关模型必须正确忽略它们。这里特别强调干扰项的存在非常关键它逼着模型真正去理解动作语义而不是靠位置猜测。第三步是推理轨迹标注。我们先生成标准的推理链和正确动作序列然后用脚本做一些噪声化处理生成少量失败对比样本供后续偏好训练使用。第四步是难度平衡。课程化要求不同扰动程度的样本按比例混合我们在每个batch里固定百分之十的固定动作空间样本、百分之五十的中等扰动样本、百分之四十的高扰动样本。实测这个比例比较稳既保证模型不忘本又充分暴露在动态变化下。3.3 奖励信号与强化学习微调监督微调后的模型已经具备基本的动态感知能力但在一些模糊场景下还是会犹豫。我们尝试了两种偏好优化方法最后用DPO而不是PPO来微调。PPO的问题在于需要额外的奖励模型来给策略打分动作空间动态变化时奖励模型也跟着失稳——奖励模型本身也需要感知动作空间变化这就把问题递归放大了。DPO直接通过对比样本对来优化不需要预热奖励模型训练稳定性好很多。奖励设计上我们混合了任务级和过程级两类信号。任务级信号是最终是否成功完成目标赋予最高权重。过程级信号包括调用的动作是否在当前动作空间中有效、参数是否符合输入约束、每一步之后任务状态是否朝着目标推进。过程级信号能提供更密集的反馈帮助模型在长任务链中纠偏。同时加了两个惩罚项每产生一个清单外的动作调用扣0.1分每出现一次重复调用同一个动作且没有状态变化扣0.05分。这两个惩罚项有效地降低了后面要说的“刷分”式失败模式。3.4 评估指标与基准任务评估指标我们选了四个任务成功率、动作选择准确率、无效动作调用率、平均任务轮次。任务成功率看的是最终目标是否达成是最核心的指标。动作选择准确率统计模型选出的动作中真正匹配最优动作的比例。无效动作调用率统计模型调用清单外动作或者调用已禁用动作的比例这个指标最能反映模型对动态动作空间的感知能力。平均任务轮次则衡量推理效率防止模型通过大量无效试探撞大运。评估任务使用了与训练同分布但动作空间采样种子完全不同的新任务集合确保模型不是背题。同时增加了极端压力测试每步都重新采样动作空间并且保证部分可用动作只在特定状态出现一次。这个测试用来检验模型的动态适应能力上限。4. 实验观察哪些结论值钱4.1 与固定空间基线对比结果核心实验是把DYNAACT和几种基线方法放在同样的动态测试环境里对比。基线包括固定动作空间微调的模型推理时强行把工具集合塞进提示词、提示词拼接式方法、以及检索增强的工具选择方法。为了不让数字误导人这里不便放出绝对数值但相对趋势是稳定的在动态扰动设定下固定空间基线的成功率较其在静态测试时的成绩下跌约三十五个百分点提示词拼接式方法由于新工具描述能即时加入比固定空间基线好一些但在干扰工具多的时候决策混乱无效调用率明显上升检索增强方法在工具总量大的时候表现尚可但每步动作空间变化时检索的滞后性导致成功率不稳定。DYNAACT在同样的动态测试环境里成功率始终高出一截而且动作选择准确率明显压制了基线。最显著的提升出现在高频扰动场景模型能够快速根据动作清单调整策略而不是反复纠缠在前面见过的动作上。4.2 动态扰动频率是决定性因素我们把动作空间变化频率分成四个档位完全静态、每十个任务变化一次、每个任务变化一次、每一步都变化。完全静态下DYNAACT和固定空间模型差异不大这验证了它没有因为在动态环境训练而损失静态场景能力。变化频率越高DYNAACT的优势越明显。到每一步都变化时DYNAACT还能保持过半的成功率而所有基线的成功率都跌到三成以下。这里有个值得注意的现象DYNAACT在低频扰动下的提升幅度其实不大但在高频扰动下提升幅度急剧拉大。说明模型学到了一个更本质的技能——对动作清单的即时解读而不是把“某个动作在某个任务里好用”死记下来。这正是我们想要的行为特征。4.3 消融研究带来的两个反直觉结论消融实验结果里有两个反直觉的发现值得单独拿出来讲。第一动作描述字段从“一行短描述”改成“两到三行详细描述”后模型效果反而下降。我们最初的猜测是更详细的描述会帮助模型更好地区分相似动作。实际上序列变长导致模型对动作清单尾部内容的注意力衰减而且详细描述让模型倾向于“阅读理解整篇描述”而不是“快速扫描清单”。所以最终我们坚持了紧凑描述宁可让个别动作存在语义歧义也要保证整体清单的短小。第二在动作清单开头显式标注“当前共有N个可用动作”对性能有稳定正向贡献。初始没想到这个细节这么重要可能因为是数值信息给了模型一个确定的抓手让它在选择时有了总量约束减少了“总觉得还有其他动作没用上”的幻觉行为。这个经验可以直接移植到任何Agent设计里。4.4 失败案例分析模型何时还在摆烂即使DYNAACT整体表现不错失败案例仍然有分析价值。我们观察到了三类高频失败模式。第一类是动作重复。模型反复调用同一个无效动作即使系统反馈说参数错误或者动作已在清单外它依然执着尝试。这背后的原因是对负反馈的利用不足偏好优化阶段已经缓解了一部分但极端场景下仍会复发。第二类是幻觉动作。模型输出了清单外的动作名不是格式错误而是生成了一个从未出现的函数名。这大概率是解码阶段的概率漂移问题我们通过约束解码来解决——生成动作调用标记时只允许在当前清单的动作名集合里解码。第三类是死循环。模型在两个动作之间来回切换比如查询订单和修改备注交替执行但从不检查操作有没有实际生效。后来引入了状态差异检测对每个动作执行前后的观察结果做对比如果没有差异就判断当前动作无效。这三类失败都不是模型“笨”而是动态动作空间放大了原本隐藏的脆弱性。固定动作空间下模型很少幻觉动作因为候选集合在训练里见过动作空间一变解码分布随之抖动才暴露出这些问题。5. 复现与落地中的踩坑记录5.1 数据泄露评估集里的动作清单第一次离线评估时我们出了个让人哭笑不得的问题——DYNAACT在高扰动场景刷新了纪录各项指标好得不真实。后来一查发现评估集构造时偷懒把每个任务的完整动作清单直接塞进了历史对话里导致模型其实“提前看到了未来可用的动作”。这看起来是个低级错误但在动态动作空间的任务里尤其隐蔽。因为动作清单本身就是输入的一部分你很难直觉地判断“模型知道这个问题答案”和“模型看到过这个动作”之间的边界。当时排查动作路径时发现模型在第一步就调用了一个直到第三轮才被“启用”的动作直接暴露出数据泄露。修正办法很简单构造评估样本时每一轮的状态里只能包含到这一轮为止实际存在的动作。也就是说要按时间切片保存动作空间历史不能让最后时刻的完整清单贯穿整个对话。这个坑推荐所有做动态环境的人都自查一遍。5.2 token化不一致导致动作不可调用动态动作空间的另一个暗坑是token化。动作清单里的动作名如果包含连字符、数字前缀或者中文括号分词器会把它切成多个token。模型生成动作调用时很难保证每个token的拼接和输入清单里的完全一致经常出现全角半角混用程序解析时直接失败。我们内部测试发现动作名从中文改成英文别名后生成稳定性提升显著。更保险的做法是做一层占位符映射输入清单时给每个动作分配一个简单ID比如action_0、action_1真正可读的动作名只用于功能描述字段。模型输出时只和ID打交道程序再根据ID映射到真实函数。这种方式彻底绕开了token化不一致的问题。5.3 动态批处理中的长度对齐问题训练时我们按动作清单长度做了动态padding但最初没把“pad位置”排除在动作选择范围外导致模型有时会“调用”一个不存在的动作——它选择的位置正好落在padding区域上。这个问题的根因是注意力机制没有处理padding掩码。我们为动作清单设计了一个独立的掩码矩阵每个样本padding的部分在计算注意力时全部置为负无穷。同时在使用DPO时对比样本也必须共享同一个动作空间掩码否则对比会引入不一致的信号。这个细节在批量训练时不容易暴露因为单卡小batch跑的时候触点少一旦用8卡大batch就会发现损失波动异常。排查了两天才定位到属于典型的实现级暗坑。5.4 RL训练中的奖励噪声处理偏好优化阶段我们最初只用了最终任务是否成功来作为奖励信号模型马上找到了漏洞它学会了刻意拉长推理链通过大量无效动作试探环境获得部分过程奖励。这个行为类似“为了做动作而做动作”虽然平均成功率没变但平均任务轮次暴增。后来我们把过程信号重新归类每一个动作执行之后做一个结果摘要判断这个动作是否产生了实际状态变化。只对这个摘要判分而不是相信系统返回的原始文本。因为原始反馈可能很冗长但模型并没有真正理解状态摘要反而更干净。另一个有效惩罚是重复动作检测。连续两个动作调用相同动作名且没有状态变化直接扣分。这套组合下来模型的平均任务轮次从夸张的十几轮降到了合理的五轮左右。这套惩罚机制在固定动作空间的Agent里也能用只要你做强化学习类微调就会遇到奖励欺诈的问题。我强烈建议在做RL之前先把过程信号的自动化评估跑通不要只依赖最终答案。6. 后续可以怎么扩展6.1 从工具调用到多智能体协作DYNAACT目前主要在单Agent场景里验证但它天然适合扩展到多智能体协作。在多智能体系统里每个Agent暴露给其他Agent的“能力”就是一组动态动作。比如一个规划Agent把任务拆解后要动态把子任务分配给不同的执行Agent每次分配时执行Agent可接受的操作集合完全不同。如果把每个Agent的可用操作接口编码成类似dynact的清单然后在通信协议里传递规划Agent就能像理解单个动作空间一样理解整个Agent网络的动态能力边界。这样一来增加一个Agent或者下线一个Agent其他Agent不需要重新训练只要更新动作清单即可。6.2 与检索增强结合动态动作空间和检索增强不是互斥关系。动作池特别大的时候把所有动作都塞进上下文不现实。可行的做法是两级方案第一级用向量检索从全量动作池里粗筛出候选子集第二级用DYNAACT精细感知这个子集并做推理选择。这里的关键是粗筛出来的候选集合即使不完美DYNAACT也可以依赖“当前候选集已包含所有可用动作”的假设进行推理只要第一级召回率足够高整体效果就稳。我们实验里把动作池扩大十倍后仍然能保持不错的成功率。如果遇到动作池上千的工业场景可以按这个思路设计。6.3 对未来研究的一个小建议回看整段工作我越来越觉得动作空间不应该被当成系统的背景配置而应该被当成推理过程中的活跃变量。很多Agent框架把工具列表当作静态注册表只在程序启动时读取一次这就是所有动态问题的根。如果你要做这个方向可以沿着“动作空间生命周期”的思路逐步深入——动作如何产生、如何被模型感知、如何在上下文和参数中演进、又如何被回收每一步都有大量值得做的事情。这些东西我们目前也只做了一半更大的动作空间、更长的任务链、更复杂的身份权限映射都还堆在排期里。但至少现在有了一套可以稳定复现的基线后续不管是加姿态还是换模型都有了可以比较的锚点。最后再分享一个个人体会做这类涉及动态输入的研究最大的敌人不是模型能力不足而是评估环境不够真实。如果你能构造出一个动作空间合理变化的测试场景再糟糕的模型也能暴露短板反过来如果环境是静态的你会被表面的高准确率欺骗很久直到产品拉你去填坑的那一天。