最近在学AI Agent,用LangChain搭了个简单的工具调用流程,比如让Agent先查数据库再调用API。但发现多轮对话里,Agent经常记不住之前的工具返回结果,比如用户问“刚才那个订单的状态是什么”,它又去重新查一遍,而不是用缓存里的数据。我试了加memory和chat_history,但感觉工具调用的结果没被塞进上下文。是不是我prompt写的有问题?还是LangChain的AgentExecutor默认就不保留工具输出?求指点正确姿势,最好能贴个代码片段,感谢!
用LangChain搭Agent,工具调用时上下文总丢,大佬们怎么解决的?
全部回复
共 162 条这问题我踩过一样的坑,AgentExecutor默认确实不会把工具输出自动塞回prompt,得靠你自己在tool的return_direct=True或者手动把结果append到messages里。我后来是直接魔改agent的prompt,把工具输出格式化成“工具名:结果”塞进chat_history,不然多轮必丢上下文。你试试在agent的memory里加个自定义的memory_key,专门存工具返回,别跟常规对话历史混在一起。另外检查下是不是tool的description写太短,模型判断不需要记住结果就直接调新工具了。
之前搞RAG也碰到过,后来发现是tool的response_format没设置好,默认只返回最后一步的结果,中间过程全被吞了。你可以在tool函数里把每次调用的返回都存到个全局dict,然后在prompt里明确告诉agent“查询过订单状态后要引用之前的结果”。代码上我一般习惯用ConversationSummaryBufferMemory配合custom_prefix,把工具输出单独注入到SystemMessage里,比纯靠chat_history稳。
我建议直接看下LangChain的AgentExecutor源码,它其实有max_iterations限制,工具输出默认只保留最后一步当observation。你可以在tool里直接返回一个包含完整上下文的JSON,然后让agent从里面提取,而不是靠memory。或者干脆不用AgentExecutor,自己写个循环,手动把每次工具结果作为UserMessage塞
这问题我踩过一模一样的坑,刚开始也以为是自己prompt写得不够清楚,后来翻源码才发现AgentExecutor默认确实不会把工具输出自动塞进下一轮对话的上下文里,它只保留最终回复。你加的那两个memory可能方向不太对,得用专门处理中间步骤的机制。我现在的做法是在工具函数内部就把结果格式化成一个标准字符串,然后同时返回给Agent和手动append到一个全局缓存dict里,再在每次对话前把这份缓存拼到system prompt里。另外有个取巧的办法,就是让工具在调用完后直接返回“已查询到结果:xxx,请直接基于此回答”,这样Agent会倾向于复用而不是再查一遍。不过最稳妥的还是自定义个CallbackHandler去拦截工具输出,把它存进一个变量,再和chat_history一起注入到下一次prompt,你可以试试这个思路。顺便问下,你用的LangChain版本是多少,0.1.x和0.2.x在memory这块行为差别挺大的。
把工具输出写进memory的键里,或者用langgraph的checkpoint机制,比硬塞chat_history靠谱多了。
我之前也踩过这个坑,后来发现不是memory的问题,是AgentExecutor默认只把最终结果塞回给LLM,中间工具的输出确实不会自动累积。你可以试试在tool的func里自己把返回值格式化成字符串,然后手动append到chat_history的最前面,或者直接用langchain的ConversationBufferWindowMemory配合return_messages=True,再把memory的key改成“history”传进prompt。还有个小技巧,把工具描述写清楚,让Agent判断该不该复用之前的查询结果,比硬塞上下文更省token。
这个问题我踩过一模一样的坑,后来发现根子不在memory,而在AgentExecutor的迭代机制——它每次调用工具后,返回的observation默认只存在当前step的中间变量里,并不会自动追加到对话历史。你加的chat_history只是用户和助手的对话,工具输出压根没进去。
我试过最笨但有效的办法,是在tool的func里手动把结果格式化后return一个包含原始内容的字符串,同时把这个字符串用callback塞进memory的buffer,相当于自己维护一份“工具结果日志”。另外你也可以改一下prompt,在system消息里明确写“每次工具调用后,把返回的JSON摘要复述一遍再继续”,这样至少能让模型在生成下一轮回复时有个参照。
不过更省事的方案是直接换用LangGraph,它的StateGraph能显式控制消息流转,把工具输出作为节点状态保存,彻底绕开AgentExecutor的隐藏逻辑。代码上你就把上次的tool结果存进state,然后在后续节点判断如果用户问的是“刚才那个”,就直接从state里取,不用再触发工具。
对了,还有个细节,如果你用OpenAI函数调用,记得把tool的response也作为message(role=tool)传回给模型,LangChain新版里这步经常被忽略,少了它模型根本看不到工具结果。你先检查一下你的AgentExecutor有没有显式传tool_response_message这个参数,八成是这儿漏了。
试试在AgentExecutor里把return_intermediate_steps设为True,再把中间步骤手动塞回prompt模板里。
这问题我当初也踩过坑,LangChain的AgentExecutor默认确实只保留最近的中间步骤,但工具返回的详细内容经常被截断或者只留个summary,你得自己把关键信息显式塞回prompt里。我试过最土但有效的办法是自定义一个memory类,在工具执行后把输出解析成结构化字段,然后和chat_history一起拼成新的system prompt,这样下一轮就能直接引用。另外你检查下是不是用的ConversationBufferMemory,它默认只存对话不存工具输出,得换成ConversationSummaryMemory或者自己写个combined memory。还有个坑是agent的prompt模板里要明确告诉它“之前查过的订单数据在memory里,别重复查询”,不然它总觉得自己该重新确认。代码的话,你可以重写AgentExecutor的_areturn_response,把last_output追加到memory里,或者干脆用langgraph的StateGraph手动管理状态流,比AgentExecutor可控得多。
这问题太典型了,我刚踩完同一个坑。AgentExecutor默认确实只把最后一次工具输出塞进当前step的上下文,不会自动累积到历史里,所以你得自己把工具结果显式写进memory。我是这么搞的:在tool的func里return之前,手动把结果append到memory的一个专门字段,然后在prompt里用{fetch_history}占位符把它拼进去,这样多轮就能记住了。不过要注意别把整个数据库结果都塞进去,token会爆,最好只保留关键摘要。另外你提到“重新查一遍”其实不一定是bug,有时候agent自己判断需要最新数据,你可以给tool描述里加上“如果用户没要求刷新,直接返回上次结果”这种指令,能减少很多无效调用。我回头把我改过的agent代码贴你,核心就是重写AgentExecutor的_plan或者用自定义Agent类,绕开默认的scratchpad逻辑。
工具结果得自己塞回prompt,默认AgentExecutor确实不缓存,我都是把最近几轮工具输出手动拼进memory里。
你试试用ConversationBufferMemory加return_messages=True,再把工具输出显式存进去,不然它真就丢。
我也踩过这坑,核心是得把工具输出显式写进memory,光靠chat_history不够,试试自定义个回调塞进去。
这个问题我上周刚踩过坑,AgentExecutor默认确实不会把工具输出自动塞回上下文,它只保留最终回复,所以多轮里你问“刚才订单状态”它只能重新查。我的做法是自定义一个memory,把每一步的Observation和Action都追加进chat_history,然后传给prompt里的{agent_scratchpad},这样模型就能看到之前的工具结果了。另外你试下把工具输出格式化成“工具名+时间戳+结果摘要”,这样即使对话长,模型也能快速定位到关键信息,不然它容易在长上下文里“迷失”。还有个坑是memory和agent的message类型要匹配,比如你用ConversationBufferMemory,就得手动把工具输出转成AIMessage或HumanMessage塞进去,不然它不识别。代码上我建议直接改create_react_agent的prompt,把scratchpad模板里加上“之前的工具结果”这一栏,比硬塞memory更可控。你可以先打印一下每次调用的中间步骤,看看工具输出到底有没有进到LLM的输入里,八成是prompt里没引用那个变量。
我之前也踩过这个坑,LangChain的AgentExecutor确实默认不会把工具输出自动塞进memory,它只保留给LLM看的observation,但对话历史里存的那份和工具返回的是两回事。我后来是把工具结果显式format成一段文本,然后手动append到chat_history里,再传给下一轮,相当于自己维护一个“工具记忆”列表,不然光靠自带memory不太够。另外检查一下你的prompt里有没有让模型优先参考历史而不是重新调用工具,有时候是它觉得重查更可靠,不一定是上下文丢了。代码上可以试试在tool的return里直接拼上“已查询结果:xxx,下次不要再调用”这种强指令,实测对模型有点用。
这问题我当初也踩过坑,LangChain的AgentExecutor确实不会自动把工具输出塞回对话上下文,它只保留最后一步的observation。你光加memory没用,因为memory管的是用户和assistant的对话历史,跟工具调用的中间结果完全是两回事。我试过比较靠谱的做法是,在工具返回的结果里直接做个摘要,然后手动append到chat_history里,相当于伪造一轮assistant回复。或者干脆不用AgentExecutor,自己写个while循环,每轮把工具结果拼成一条新消息塞进messages列表再传给LLM,这样上下文就不会丢。另外你那个“重新查一遍”的问题,也可能是工具本身没做缓存,跟上下文丢失是两码事,建议先区分清楚。如果你用的是OpenAI函数调用,可以试试把工具结果放在function message里,然后确保上轮tool_message_id传对了,这块儿容易漏。prompt写法也有讲究,可以在system里明确告诉它“如果历史中有查询结果,直接引用,不要重复调用”。代码片段的话,核心就是别依赖默认的AgentExecutor,手动管理messages列表最可控。
试试把工具输出用Memory的returnMessages=True存进去,再在prompt里显式引用下,我这么改完就好了。
工具结果得用中间步骤变量带进下一轮,光靠chat_history不顶用,建议看下create_agent里怎么维护scratchpad。
这个问题我踩过一样的坑,AgentExecutor确实不会自动把工具输出塞回memory,你得自己在tool的return里把结果拼进一个全局变量或者直接在agent的prompt里手动加一段“当前已知信息”的占位符。我当时的做法是重写了个自定义的agent,把工具调用后的结果用Observation形式写回chat_history,而不是依赖默认的memory机制。你可以试试在每次工具执行后把结果格式化追加到messages里,再传给下一次迭代,别光靠LangChain的Memory组件。还有个坑是如果用create_react_agent,那个system prompt里得明确写“你可以引用之前获取的数据,不要重复查询”,不然模型真会当失忆症。
这问题太典型了,AgentExecutor默认确实不会把中间的工具输出自动塞回prompt,你光加memory没用,得自己在工具返回后手动拼到观察里。我之前也卡这儿,后来干脆把工具结果写入一个持久化的dict,然后在每次迭代前把最新状态作为system message的一部分传进去,或者直接用带缓存功能的agent类型比如Plan-and-Execute,能省不少事。还有个坑是prompt里得明确告诉模型“优先用历史观察,别重复查”,不然它总是倾向于调工具,你试试这个方向。
我之前也踩过这个坑,LangChain的AgentExecutor确实不会自动把工具结果写进memory,你得自己在工具函数里return时就把关键信息拼到observation里,或者手动往chat_history塞一条包含返回值的消息。另外建议试一下把memory的return_messages设成True,然后agent的prompt里明确引用{chat_history},不然它容易只看到最近一轮的对话。我之前是干脆写了个自定义的callback,每次工具跑完就把结果存进一个全局dict,再在下次tool调用前先查一下,比硬靠memory稳多了。
我之前也踩过这坑,LangChain的AgentExecutor确实不会自动把工具返回塞回memory里,你得自己在工具函数里return前手动把结果append到chat_history,或者用个全局变量存一下最近几次的工具输出。另外你那个prompt里最好明确写一句“如果用户问的是之前查过的数据,直接引用历史结果”,不然模型分不清该不该重新调工具。我之前是把数据库查询结果转成简短的摘要塞回prompt,比存完整JSON好用很多,你可以试试。
试过把工具结果写进memory的buffer窗口吗?我之前发现光靠chat_history不够,因为默认的ConversationBufferWindow只存对话不存tool的observation,后来我直接把工具返回拼成一条assistant消息塞回去,效果立竿见影。还有个小坑是Agent的system prompt里要提醒它“优先复用已有信息”,不然它每次都觉得重新查更保险。
我猜问题不在memory配置,而是你工具返回的内容太长了,塞进上下文后被截断或者被模型忽略了。我之前是把数据库结果先做个聚合,比如只返回订单状态和关键时间点,然后丢进一个持久化的dict里,每次对话开始前把最近的几条记录注入prompt。另外你可以试试把AgentExecutor换成langgraph,它对状态流的控制更细,不会像老版那样糊里糊涂丢上下文。
这情况太典型了,LangChain的Agent
这问题我踩过类似的坑,关键不在memory,而是AgentExecutor默认只把工具返回的最终答案塞回prompt,中间的中间步骤(比如数据库查询结果)会被丢掉。你可以试试把工具的Observation显式写进返回的字符串里,或者用langchain的create_history_aware_retriever那套思路,手动把上次的工具输出拼到HumanMessage里再传给下一次调用。我后来是直接在tool的func里把结果存到全局变量,然后在下轮prompt里引用,虽然丑但比调memory靠谱多了。
这个问题我也踩过坑,AgentExecutor确实不会自动把工具输出塞回memory里,你得在tool的return值里手动拼一下,或者干脆自己写个callback把结果追加到chat_history。我之前是把每次工具调用的输出格式化后塞进prompt的system部分,效果比单纯靠memory稳定,不过token会涨得很快。还有个笨办法,就是工具返回时直接把关键信息提炼成一句摘要,别让大模型去翻原始结果,这样多轮对话基本就不会丢了。