最近在基于Qwen2.5-7B-Instruct搭一个简单的Agent,用来做数据库查询。单轮工具调用没问题,但一旦涉及两轮以上(比如先查用户ID再查订单),模型就经常把前面的工具结果“忘掉”,导致第二次调用时参数缺失或者胡编一个ID出来。我试过把历史工具返回都拼进prompt里,也试过用system提示强强调“牢记之前的结果”,但效果不稳定。是不是7B模型本身在这种多跳推理上就是瓶颈?还是我的prompt结构有问题,或者应该加一层记忆机制(比如向量检索)?有经验的朋友能分享下你们的处理方式吗?
Qwen2.5在Agent多轮工具调用时总忘记历史上下文,怎么调?
全部回复
共 85 条说实话我最近也踩了类似的坑,Qwen2.5-7B在长上下文里的“记忆锚点”确实容易漂移,尤其是工具结果被塞进prompt后,模型容易把最近一次对话的优先级拔得太高,反而忽略了更早但更关键的返回。我试过把工具结果按时间戳拆成独立段落,并且每次调用前用一行摘要强制刷新“当前已知信息”,效果比单纯堆历史好一些,但依然会有概率性遗忘。
你提到向量检索我觉得是个方向,但别急着上重机制。可以先试试把工具返回结构化,比如存成固定schema的key-value列表,然后在下一轮构造prompt时,只把跟当前查询相关的字段高亮或前置,而不是把所有历史都平铺。7B模型对信息密度很敏感,你给它铺太多,它反而抓不住重点。
另外我怀疑你“拼历史”的方式可能有问题——是直接拼接原始工具输出还是做了摘要?如果输出特别长,模型注意力会被无关细节带跑。我后来改用“上一步结论+当前需补全的槽位”这种格式,把工具结果浓缩成一句话,模型出错率降了大概一半。
不过说实话,多轮工具调用对7B来说确实接近能力边界,尤其涉及跨轮推理时,它更像在“猜”而不是“查”。我最后是妥协了,用了个轻量级的规则层做校验,发现参数缺失就主动触发重查,而不是让模型硬编。你要是能接受一点工程复杂度,这个方案比纯调prompt稳定得多。
说实话我也踩过类似的坑,7B模型在多轮工具调用上确实容易“断片”,但我觉得不全是模型大小的问题。你试过把工具返回直接塞进prompt,这个方向是对的,但可能格式不够结构化——比如把上一次的查询结果单独拎出来,用明确的标签标记,像“当前已知用户ID:12345”,让模型一眼就能定位到关键信息,而不是混在一大段对话历史里。另外,我怀疑你第二次调用时,模型可能把“工具返回”当成了用户输入的一部分,导致上下文权重被稀释,试试把工具结果放在更靠近当前轮次的位置,或者用特殊分隔符隔开。至于记忆机制,向量检索对这种情况有点重了,而且7B模型本身对检索结果的利用能力也有限,我建议先做一个轻量的状态缓存——比如在代码里维护一个dict,把已经查到的实体ID存好,下次工具调用时直接作为参数传入,不让模型自己从历史里“回忆”。我之前用Qwen2.5-7B时,发现它对明确的“当前状态”提示响应更好,比如在system里写“你有一个内部状态,包含user_id=xxx,请基于此查询”,比单纯强调“记住结果”有效得多。你也可以试试把工具定义改成强制要求某些参数必须来自状态,而不是自由生成。最后,如果效果还是不稳,建议先把多轮拆成几个单轮,每轮用独立prompt传递必要信息,牺牲一点交互性换稳定性,等模型升级到更大尺寸再优化体验。
说实话我之前用Qwen2.5-7B搭Agent也撞过这堵墙,单轮工具调用贼顺,一上多轮就开始“失忆”,尤其数据库这种需要精确ID的场景,胡编乱造真的头疼。后来我仔细排查了一下,发现不纯粹是模型容量的问题,更多是prompt里历史工具结果的组织方式太“平铺”了,模型分不清哪条信息对应哪次调用。我后来把每次工具调用和结果封装成类似“function call log”的结构,带时间戳和参数回显,再明确要求模型在生成下一次调用前先复述“当前已知的实体及来源”,效果好了不少。不过说实话,7B在多跳推理上确实有天花板,你得接受它偶尔“短路”,我最后是加了一层轻量缓存,把关键实体比如用户ID直接提取到单独的“记忆槽”里,每次构造prompt时把它放在最前面,比纯靠模型硬记靠谱。你提到向量检索,我觉得对这类结构化任务有点杀鸡用牛刀,反而容易引入噪声。可以试试把系统提示改成“你只能使用记忆槽中的ID,禁止从对话历史推断”,再配合few-shot给一个两轮调用的完整示例,大概率能压住胡编的毛病。另外,你用的max_tokens和温度设置也检查下,有时候生成中断或者随机性太高也会加剧这个问题。
试试给每次工具结果单独编号塞回对话,比全堆在system里管用,7B对长上下文注意力就是会飘。
我遇到过类似问题,干脆把关键ID抽出来存成结构化变量,下次调用直接引,稳多了。
7B做多跳确实容易断片,试试把工具返回塞进assistant消息而不是全堆prompt里,结构清晰点会稳不少。