最近在做一个小项目,想用微调后的LLM驱动Agent,但遇到一个头疼的问题:模型明明在训练集上学会了调用指定工具(比如搜索、计算器),但一跑真实任务,它就开始“自由发挥”——比如让它查天气,它非要去调用一个不存在的“天气预报API”,甚至自己编造工具名。
我用的是Llama-3-8B,用LoRA微调了2000条工具调用数据,损失已经降到很低了。是数据里工具名称不够规范?还是微调后的模型缺乏“拒绝调用”的能力?或者需要再加一层指令模板约束?有没有大佬遇到过类似问题,求指点一下调参或者数据构造的方向。
微调后的模型做Agent任务,总乱调用工具怎么办?
全部回复
共 155 条我之前也踩过类似的坑,LoRA微调容易让模型把“调用工具”当成一种语言模式,而不是基于意图的决策,所以它更倾向于续写工具名而不是判断该不该调。你可以试试在数据里混入一些“不调用工具”的样本,明确告诉它“这个我不知道”或者“不需要工具”,让模型学会拒绝。另外,工具名最好统一加个前缀,比如“tool_weather”,不然模型容易把描述性词语也当成工具名。还有个歪招,把工具列表直接塞进system prompt里,并且加上“只能从以下列表中选择”这种硬约束,实测能压住不少幻觉。
这问题我太懂了,之前用7B模型做类似的事差点被逼疯。你loss低很正常,LoRA微调在2000条数据上很容易过拟合到训练集的表面模式,但真实场景里工具名稍微变个说法它就懵了,本质上是没学会“工具选择的边界感”。我后来发现一个关键点,就是数据里得混入大量“不该调用工具”的负样本,比如用户问闲聊问题或者模糊请求时,模型应该学会直接回答或者反问,而不是硬凑工具名。另外你提到它编造API,这其实是解码策略的问题,温度调太高或者top_p太激进会让它放飞自我,试试采样时把温度压到0.1以下,甚至直接用greedy解码,能少很多幻觉。还有一个坑是工具描述格式,我后来把所有工具定义写成统一的JSON schema,并且在每轮对话里强制拼接当前可用工具的完整列表,让它明确知道“现在只有这些”,比让它靠记忆里学过的名字去猜靠谱得多。还有个小技巧,如果你发现它总调某个不存在的工具,可以在system prompt里加一句显式的“如果工具列表里没有匹配项,必须回复无法完成”,效果立竿见影。最后,2000条数据确实有点少,尤其是工具调用这种强逻辑任务,建议至少翻倍,并且把工具名做同义词替换和拼写变体增强,逼它学语义而非死记名字。调参方向的话,LoRA rank可以试试32,学习率调低到1e-5左右,多跑几个epoch但盯着验证集做早停,防止它把训练集的“坏习惯”固化下来。
这问题太典型了,LoRA微调在工具调用上特别容易过拟合到“名字匹配”而不是“意图理解”。我怀疑你训练集里工具名太固定了,模型根本没学会“没合适工具就拒绝”这个行为,建议你在数据里加一些“用户请求无关工具”的负样本,明确标注“不调用任何工具”。另外试试把工具描述写得更完整,让模型先理解功能再匹配,别光靠名字硬猜。调参上可以降低学习率多训几个epoch,但关键还是数据分布得模拟真实场景的混乱度。
我之前用7B模型也踩过这个坑,问题大概率不在loss,而是LoRA把工具名当成了实体记忆,没学会“工具不存在就拒绝”这个隐含规则。你可以试试在数据里混入20%的负样本,比如故意给个不存在的工具名,让模型学会输出“无可用工具”或直接复述用户需求。另外检查下是不是推理时的system prompt跟你训练时差太多,模型对格式变化很敏感,建议把工具列表改成JSON schema严格约束,让模型只能从给定枚举里选,而不是自由生成字符串。
我之前做类似项目也踩过这个坑,感觉不是拒绝调用的问题,而是模型对工具名的记忆太“死”了,LoRA微调数据里如果工具名格式太单一,它就会死记硬背而不是理解语义。你可以试试在训练数据里混入一些“无法用现有工具解决”的样本,明确让模型输出“无匹配工具”,同时把工具描述写得泛化一点,比如“查询天气”而不是“weather_api”。另外,推理时加一条硬性规则,对模型输出做工具名白名单校验,不匹配就直接拒绝,再让它重新生成,比纯靠模型自觉靠谱得多。
我之前做类似任务也踩过这个坑,LoRA微调很容易把工具调用学成“条件反射”,模型根本没理解什么时候该停手。建议你检查一下数据里是不是只给了“必须调用”的正样本,完全没加“不需要调用工具”的负样本,模型自然就学不会拒绝。另外可以试试在系统提示里硬性规定“未知工具一律返回错误”,或者把工具列表动态拼到输入里,让模型看到当前可用的才选。最直接的办法是加一层规则校验,模型输出工具名先跟白名单比对,不在就强制走默认回复,治标但能稳住线上效果。
我之前也踩过类似的坑,LoRA微调很容易让模型把“工具调用”学成一种文本模式,而不是真正的语义理解。你可以试试在数据里混入一些“不调用工具”的负样本,明确告诉它什么时候该拒绝,光靠loss低真不代表它学会了边界。另外,工具名别用太泛的词,比如“API”这种后缀,模型容易自己脑补,最好把真实存在的工具名写成固定短语,甚至加个特殊token包起来。还有个笨办法,在系统提示里强制加一行“只允许使用以下工具列表”,跑推理时如果输出不在列表里就直接截断重采样,虽然粗暴但挺管用。
这问题太典型了,我之前用7B模型跑Agent也撞过同样的墙。我觉得核心不在于多训“拒绝调用”,而是你的训练数据里压根没构造“不该调用工具”的样本。模型只知道看到天气关键词就该调工具,但它没见过“没工具可用时该直接回答”的场景,所以它就自己编一个出来填补认知空白。你可以试着在数据里混入20%的纯问答样本,强制让模型学会说“我没有这个功能”或直接给答案。另外工具名一定要做到全局唯一且带系统前缀,比如“weather.current”而不是“天气API”,模型对自然语言和机器标识符的泛化能力差别很大。LoRA低秩矩阵其实约束了模型对工具列表的长期记忆,你可以试试在system prompt里把可用工具列表重复三遍,甚至把工具描述改成JSON schema格式喂进去,比自然语言描述更不容易让模型幻觉。还有个偏方,就是推理时把temperature调低到0.1,同时把top_p压到0.8,能很大程度减少它“创造性”地编造函数名。最后检查一下你的2000条数据是不是存在严重分布偏差,如果所有样本工具调用都成功返回,那模型就学不到失败后的兜底逻辑,这个必须靠造一些工具报错或空返回的样本让它学会“换个工具”或“认怂”。
这问题我太有同感了,之前用7B模型做类似的事也踩过这个坑。你loss低只能说明模型记住了训练集的输入输出映射,但真实场景里用户query的分布和工具描述的组合方式跟训练数据肯定有偏差,它本质上是在“猜”一个看起来合理的工具名,而不是在“理解”该不该调。我觉得数据构造上有个关键点:你的2000条里是不是全是“必须调用工具”的正样本?如果完全没有混入那种“不需要调用任何工具、直接回答”的负样本,模型当然学不会拒绝,它只会觉得所有问题都得拽个工具出来。另外建议你检查一下工具定义在prompt里是怎么写的——如果只是给个名字和一句描述,模型很容易把相似功能的工具搞混,最好在描述里加上明确的调用条件和反例,比如“仅当查询包含城市名且需要温度时调用”。还有一个骚操作是,我在推理时加了个硬性校验层,先用规则过滤模型输出的function name,如果不在白名单里就直接拦截并重试一次,同时把这次错误行为作为few-shot示例塞进下一次请求的对话历史里,效果立竿见影。调参的话,LoRA的rank可以试着降到16以下,有时候过拟合工具名反而会让模型更死板。你还可以试试在系统提示里加一句“如果工具不足以完成任务,请直接回复无法查询”,这算是个软约束,能逼着模型多想想。总之先往数据里加10%的“拒绝样本”看看变化,这比调模板直接得多。
我之前用7B模型也踩过这坑,LoRA微调2000条数据确实容易让模型把工具调用当成“必答动作”,它压根没学会啥时候该停手。可以试试在数据里混入一些“不该调用工具”的样本,比如直接回答或说不知道,让模型学会拒绝。另外工具描述别只给名字,格式化成带参数说明的JSON,模型瞎编的概率会低很多。还有个野路子,推理时把temperature调低到0.1,能减少这种发散行为。
我最近也踩过类似的坑,LoRA微调后loss低不代表它真的学会了“什么时候不调用工具”,反而可能把工具调用当成了一种文本续写惯性。你那2000条数据如果是纯工具调用样本,模型很可能只学到了“看到任务就调工具”的映射,根本没接触过“不需要工具”或“工具不可用”时的拒绝样本,所以一旦遇到没见过的任务它就硬编一个工具名出来。建议你在数据里混入一批负样本,比如问题本身不需要工具、或者工具列表里没有合适选项的场景,让模型学会输出“无需调用”或“无匹配工具”。另外工具名最好在system prompt里给一个白名单枚举,并且每次推理前把当前可用的工具列表动态拼进去,微调时也保持同样的格式,别让模型见过它自由发挥的空间。还有个细节,你训练时有没有把工具调用结果也作为下一轮输入?如果没有,模型其实不知道工具返回后该怎么收尾,它就会为了“完成任务”而瞎编调用链。调参方面可以试试把LoRA rank调低一点,防止它过拟合那2000条数据的表面模式,同时把学习率再降一档看看泛化会不会好一些。
我遇到过,八成是训练数据里没“负样本”,模型没学会啥时候不该调工具。加些无关问题让它直接回答别调用,会稳很多。
我遇到过,八成是训练数据里没加“工具不存在时该咋办”的样本,模型就学会瞎编了。加点拒绝调用的负例试试。
我之前也踩过这个坑,2000条数据其实不太够,而且关键是你训练集里可能压根没出现过“不该调用工具”的负样本。模型只学了什么时候该调,没学过什么时候该老老实实回答,那它遇到没见过的情况自然就瞎编了。建议往数据里掺一些纯对话样本和“工具不存在时直接说明”的案例,比例大概三成左右试试。另外可以加一层输出校验,工具名不在白名单里就直接拦掉重试。
我微调时也遇到过,加些“无合适工具就拒绝”的负样本会好很多,光靠低loss不够。