最近在做一个能查数据库的AI Agent,用的是ReAct那套思路。但发现一个很头疼的问题:如果把工具的描述(比如每个函数的参数、返回值格式)全塞进System Prompt里,模型有时候会忽略,尤其是在多轮对话后。但要是放在User的每轮输入里,又特别占token,而且感觉模型会混淆当前任务和工具说明。看了LangChain和AutoGPT的源码,它们做法也不一样。想问问有实际落地经验的老哥,工具定义到底放哪层更稳?有没有什么结构化的写法能兼顾稳定性和成本?
写Agent的Prompt时,到底该把工具说明放System还是User里?
全部回复
共 8 条我之前也踩过这个坑,工具说明放System确实容易被多轮对话稀释,后来改成System里只放工具名和一句话用途,详细的参数schema用结构化JSON单独走一个tool定义字段,模型反而更稳。你用的框架如果支持原生function calling,就别硬塞prompt里了,省token还准。
工具定义我一般放System里,但会精简成核心schema,详细的参数说明和示例拆到单独的检索文档里按需注入。多轮对话后模型忽略工具,往往是历史消息把System挤到注意力边缘了,可以在每轮user消息末尾加一句轻量的工具提醒,只列函数名不展开。LangChain那种全量塞System的做法在工具少时没问题,超过十个就容易翻车。
工具描述放哪层这个事我也踩过坑,后来发现关键不在system还是user,而在于模型对工具列表的注意力衰减。我现在的做法是工具签名和简短用途放system里固定住,但每轮在user消息末尾用一行紧凑格式重新点一下当前可用的工具名,不带完整参数说明。这样既不会每轮烧掉大段token,又能把模型的注意力拉回来。多轮之后忽略工具的情况确实存在,尤其是历史一长,system里的内容就被稀释了。结构化写法上我推荐用类似JSON schema的极简版,参数只写名字和类型,详细描述放到工具执行报错时再反馈给模型,让它按需学习。另外AutoGPT那种把工具说明塞进每条user的做法我觉得太奢侈了,除非你上下文窗口特别大。真正稳的方案可能是把工具定义做成独立的一层,在每次调模型前动态拼接到system末尾,而不是写死在开头。
我实际跑过几个这种查数Agent,说下踩过的坑。工具描述塞System里确实容易被多轮对话稀释,尤其上下文一长,模型注意力就飘了。后来我的做法是System里只放工具名和一句话用途,参数细节用结构化JSON写在User每轮的开头,但用固定分隔符包起来,比如===TOOLS===。这样既不会每轮都膨胀,模型也能稳定看到格式。另外ReAct里那个Thought/Action的格式约束,我觉得比工具定义本身更关键,得在System里强调死。成本上可以按需注入,只有模型上一轮没正确调工具时才把完整说明补进User。还有个技巧是用few-shot示例替代大段描述,两三个调用样例比写一堆参数说明管用得多。不过多轮下模型还是会遗忘工具存在,我试过在System末尾加一句“可用工具见每轮输入”,稍微好点但治标不治本。
工具定义还是放System里稳,User层只留任务指令,多轮容易飘就加个工具schema摘要每轮重述。
我现在的做法是工具定义只放System里,但会在每轮User消息末尾加一句极简的“可用工具:xxx”,相当于做个提醒锚点,实测多轮后调用准确率明显好一些。全塞User里token确实扛不住,而且模型容易把工具说明当成任务上下文来理解。结构化的话可以试试把工具签名压缩成一行JSON Schema,System里只留这一份,别写大段自然语言描述。
我这边是把工具schema放system里,但每轮在user末尾用一行轻量提醒“可用工具见system,本轮优先调xxx”,实测比全塞user省token也稳。多轮后忽略的问题,我加了个工具调用结果的摘要回填到system尾部,效果还行。结构化写法可以试试把工具名和参数拆成JSON schema,别写大段自然语言描述。
我之前也遇到过类似问题,后来换了方案。