最近在做一个基于Qwen2.5-7B的Agent项目,工具调用用的是ReAct格式。跟着教程用LLaMA-Factory做了LoRA微调,训练时loss降得挺漂亮,但一接到真实的Agent框架(用的LangGraph)里就崩:模型经常不输出Action:字段,或者直接幻觉出根本不存在的工具名。
我确认过微调数据里是带了工具描述的,模板也是按官方文档写的。想问问有经验的朋友,这种微调后的模型接入Agent时,是不是还要专门做对齐(比如system prompt、工具定义格式)?还是说我的训练数据本身就有问题?有没有排查的思路?感谢!
用LLaMA-Factory微调完模型后,Agent工具调用一直报错,是哪里没对齐?
全部回复
共 35 条大概率是训练时模板和推理时system prompt没对齐,试试把LangGraph里的工具定义格式改成和微调数据一模一样的。
其实LoRA微调只学了格式,工具名幻觉多半是数据里没做负样本,加几条“无工具可调”的样例就行。
微调loss好看但推理崩,大概率是训练和推理时的格式没对齐,尤其是ReAct的终止条件和工具名枚举。建议先检查LLaMA-Factory的模板里是否严格约束了“只能输出给定工具名”,另外LangGraph那边的system prompt最好和训练数据完全一致,连标点都别改。我之前也踩过这坑,后来发现是训练数据里工具描述顺序和推理时不一致,模型就懵了。你可以先拿一个最简单的工具,手动构造几条gold sample跑推理,看它卡在哪一步输出上。
对了,你微调时有没有屏蔽掉原始模型的工具调用能力?有时候LoRA学偏了,会把工具名和自然语言混在一起。
这问题我太熟了,八成不是模型没学会,而是微调时和推理时的格式没完全对齐。你训练数据里如果只写了工具描述,但没把LangGraph实际用的那套system prompt和工具schema喂进去,模型学到的Action格式就跟你线上跑的完全是两码事。建议先手动拿一条训练样本,原封不动丢给微调后的模型生成,看它能不能稳定输出正确字段,再对比一下训练时的模板和LangGraph里的差异,尤其是工具名和参数格式这种细节。另外LoRA rank如果太低,模型可能记不住那么多工具名,试试调高一点或者加几条硬样本。
这事我踩过一模一样的坑,你这loss好看大概率是训练时把工具调用当成了普通文本生成,但推理时LangGraph的parser对格式是强校验的。我当初是先用模型直接生成几条样本看原始输出,发现它经常把Action和Action Input挤在一行里,或者多出个解释性前缀,这跟微调数据里严格换行的ReAct格式肯定有关。建议你检查下是不是数据里所有工具描述都塞在user消息里,而system prompt在训练时被截断或根本没参与——LLaMA-Factory默认只训练user/assistant部分,system内容是固定不更新的,但推理时LangGraph又很吃system里的工具定义,两边配置一不一致很关键。另外,模型幻觉工具名大概率是负样本不够,你可以在微调数据里故意加一些“当前无可用工具,必须回答用户”的例子。排查思路的话,先关掉LangGraph,用纯对话方式把工具描述贴进去直接问模型“要查天气该调什么”,看它能不能稳定输出正确格式,这能帮你快速定位是模型没学会还是框架对接问题。我后来是把工具定义改成JSON Schema的字符串形式放进user消息末尾,每个样本都重新生成一遍再训,才勉强解决,你可以试试。
说实话你这情况我太熟了,之前用Qwen调工具调用也踩过一模一样的坑。loss降得漂亮只能说明模型记住了训练集里的模式,但LangGraph的system prompt和工具描述格式跟你微调数据里用的模板但凡有点出入,模型就很容易懵。我建议先别急着怪训练数据,你拿几个训练样本直接丢进LangGraph里跑,看能不能复现报错,大概率是推理时的prompt拼接方式跟你微调时不一致。另外,ReAct格式里Action字段的触发其实很依赖模型对“工具列表”的感知,你微调时如果工具描述是放在user消息里,但测试时LangGraph把它塞进了system prompt,那模型可能压根没学会在该输出Action的地方切换格式。还有个偷懒的办法,就是微调完先用vLLM或transformers原生pipeline测一遍纯文本输出,不接框架,看它能不能稳定走完“Thought+Action+Action Input”的循环,这样能快速隔离是模型问题还是框架问题。如果纯文本也崩,那大概率是你训练数据里工具名和真实工具名对不上,或者负样本太少,模型没学会“不知道就闭嘴”的兜底逻辑。
训练数据和推理时的prompt不一致是常见坑,你loss降得漂亮只能说明模型记住了训练集分布,但LangGraph里的system prompt和工具描述格式如果跟微调时不完全一样,模型就会懵。建议先把你训练样本里的完整对话(包括system和工具定义)直接塞给模型做一次纯生成测试,看它能不能正确输出Action,这样能快速定位是模板问题还是模型本身没学会。另外检查下是不是微调时把工具调用相关的special token或者格式符给截断了,LoRA层对这类结构化输出的影响有时候挺隐蔽的。我之前也遇到过类似情况,最后发现是训练时把换行符处理掉了,导致模型分不清字段边界。
大概率是训练数据里ReAct格式和LangGraph的tool calling约束没对齐,试试在数据里加几轮真实Agent交互的负样本。system prompt也得完全统一。
训练数据里ReAct格式的字段分隔符和LangGraph的解析逻辑大概率没对齐,先检查下模板里的换行和冒号。
我之前也踩过这坑,模型在微调时学的是纯文本格式,接入agent框架后温度参数和system prompt稍微变点就崩,建议调低温度固定下格式。
我最近也踩过类似的坑,关键问题往往不在训练数据本身,而是推理时的prompt模板跟训练时不一致。LLaMA-Factory默认的chat模板和LangGraph里用的ReAct格式可能差几个换行符或特殊标记,模型没见过自然就输出歪了。建议你先打印出实际送入模型的完整prompt,跟训练样本逐字比对一下,特别留意工具描述的排列顺序和分隔符。另外,LoRA微调对格式对齐很敏感,你可以试试在验证集上直接跑推理,如果loss低但生成乱,基本就是模板错位而不是数据问题。
训练数据里工具调用格式得跟LangGraph完全一致,模板对不上模型就乱来,建议先拿原始Qwen试试。
我之前也踩过类似的坑,大概率不是LoRA本身的问题,而是训练时和推理时的格式没对齐。LLaMA-Factory默认的模板跟你LangGraph里用的ReAct prompt细节可能差很多,比如换行符、空格、Action:后面有没有冒号空格,这些都会被模型死记下来。建议你先把训练数据里真实的工具调用样本单独拿出来,在LangGraph里用同样的system prompt硬跑一遍看输出格式差异。另外,如果微调数据里工具名和描述跟实际框架传进去的不完全一致,模型很容易去幻觉,最好保证训练语料里的工具列表是动态拼接的,别写死。还有个笨办法,推理时temperature调低到0.1以下,先排除随机采样干扰。
ReAct格式对prompt特别敏感,微调后system prompt最好跟训练时完全一致,不然很容易跑偏。
训练loss低不代表Agent对齐好了,得看推理时模板和工具格式是否跟微调时一致。
我也踩过这个坑,loss好看不代表Agent能用,训练时模型见的是固定模板,推理时框架塞进去的system prompt和工具描述顺序一变,它就容易懵。建议先别急着换数据,把LangGraph实际发给模型的prompt打出来,跟训练样本逐字段对比,大概率是格式没对齐。另外工具名幻觉往往是训练里工具集太单一,模型没学会“不确定就不调”,可以混一些负样本进去。
训练loss低不代表Agent对齐好了,ReAct格式得在推理时把工具描述拼进prompt里对齐才行。