最近在学AI Agent,用LangChain搭了个简单的工具调用流程,比如让Agent先查数据库再调用API。但发现多轮对话里,Agent经常记不住之前的工具返回结果,比如用户问“刚才那个订单的状态是什么”,它又去重新查一遍,而不是用缓存里的数据。我试了加memory和chat_history,但感觉工具调用的结果没被塞进上下文。是不是我prompt写的有问题?还是LangChain的AgentExecutor默认就不保留工具输出?求指点正确姿势,最好能贴个代码片段,感谢!
用LangChain搭Agent,工具调用时上下文总丢,大佬们怎么解决的?
全部回复
共 162 条我也碰到过一模一样的问题,折腾了好几天才发现核心在于AgentExecutor默认不会把工具调用的结果自动塞进chat_history,它只保留了你和Agent的对话。你加的memory和chat_history大概率只存了用户的query和Agent的回复,但工具返回的中间结果压根没被写进去。我后来试了个办法:在自定义的tool里直接把返回结果用format_tool_output格式化成ToolMessage,然后手动agent.memory.chat_memory.add_message(ToolMessage(...)),这样下一轮对话时Agent就能从memory里读到之前的工具结果。不过要注意,如果你用的是ConversationBufferMemory,它默认只存HumanMessage和AIMessage,得换成ConversationSummaryBufferMemory或者自己继承一下。另外看你说的“查数据库再调API”,如果工具调用顺序是固定的,不如直接用LangChain的SequentialChain搭个确定性流程,比Agent可控得多。还有个小坑:agent的prompt里最好明确写一句“如果你已经获取过某个信息,直接使用缓存结果”,不然它确实会傻乎乎地重复调用。
我也踩过这个坑,AgentExecutor默认确实不会把工具输出自动塞进chat_history,你得自己在tool的return_direct或者回调里手动把结果拼到messages里。可以试试在agent的prompt里显式加一条“你之前的工具调用结果在历史记录中”的指令,或者用ConversationSummaryMemory把工具输出摘要进去。贴不了代码,但核心思路就是把每次工具调用的输出作为一个system message或者user message追加到memory里。
同感,这个问题我折腾了两周才摸到门道。LangChain的AgentExecutor默认确实不会把工具输出自动塞进chat_history,它只保留agent自己的思考和最终答案。你需要手动在回调里把tool的输出追加到memory里去,或者换个思路,直接在Agent的system prompt里写死“每次工具调用后,把你拿到的结果用自然语言总结一下再回复”,这样结果就自然留在对话历史里了。
另外我推荐试一下把memory的类型从默认的ConversationBufferMemory换成ConversationSummaryMemory,它能自动压缩历史,但记得把工具输出的关键字段单独存到一个dict里,在prompt里用f-string引用。比如在工具返回后,用agent_executor的中间步骤回调去更新一个全局变量,然后在下一次agent思考前注入到前缀prompt里。代码的话可以搜一下“LangChain agent cache tool output”这个issue,里面有个老哥贴了完整的callback handler实现,我就是照搬那个搞定的。
这个问题我之前也踩过坑,LangChain的AgentExecutor默认确实不会把工具输出自动塞进对话历史,你得在memory里显式存一下。可以试试在agent的prompt里加个system指令,要求每次工具调用后把结果格式化成“工具名:输出”这样的记录,然后在callback里手动把工具输出追加到chat_history。另外检查下你的memory类型是不是ConversationBufferMemory,记得设置return_messages=True,不然容易丢消息。
工具返回要显式拼进prompt,memory默认只存对话,试试把中间结果也加到chat_history里。
试试在AgentExecutor里把return_intermediate_steps=True,再手动把结果拼到prompt里。
这个问题我之前也踩过坑,关键在于LangChain的Agent默认不会把工具调用的原始输出自动塞进对话历史,memory只存了用户和AI的对话,工具返回的结果除非你手动处理,否则它根本不在上下文中。我后来是直接在AgentExecutor里加了个callback,每次工具返回后把结果格式化一下塞进chat_history,同时在system prompt里明确告诉Agent“你已经查到的数据别重复查,直接用”。另外,如果你用OpenAI Functions这类工具,记得把工具调用的结果作为function response传回去,而不是让Agent自己重新思考。代码的话,核心就是重写Agent的plan方法,在每次工具返回后把输出拼接进intermediate_steps,并且确保memory能读到这个中间状态。你试试把verbose=True打开,看每一步实际传了什么给LLM,很容易发现问题。
试试把工具输出显式加到SystemMessage里,我这样改完就没丢过上下文了。
我之前也踩过这个坑,LangChain的AgentExecutor默认确实不会把工具输出自动塞进对话历史,得自己手动把tool_output加到memory里。你可以试试在自定义的Agent里,用AgentFinish之前把结果存到chat_history,或者直接改agent_scratchpad的格式。另外,如果用的是ConversationBufferMemory,记得把memory_key设成chat_history,并且在prompt里显式引用它,不然Agent根本看不到之前工具返回的内容。
试试在tool里手动把结果加到messages里,或者把return_direct设成True看看效果。
这个问题我也踩过坑,其实核心在于AgentExecutor默认只把最终答案写进memory,工具调用的中间结果并不会自动塞回去。我试过在tool的返回值里手动拼接历史关键信息,比如把数据库查询结果格式化后直接追加到observation里,同时把memory的return_messages设为True,再配合ConversationSummaryMemory压缩上下文,效果会好不少。另外你也可以看看langchain的AgentTokenBufferMemory,它专门处理这类工具调用历史,挺对症的。
试试在AgentExecutor里把return_intermediate_steps设为True,再把工具输出手动拼到SystemMessage里。
这个问题我最近也踩过坑,关键点其实不在memory本身,而是LangChain的AgentExecutor默认只把Agent的思考链(thought/action/observation)塞进chat_history,但工具返回的大块数据经常被截断或者根本没进到prompt的上下文窗口里。我试过最直接的办法是在tool的return_direct=True参数上做文章,让某些工具结果直接作为最终回答输出,这样就不会被Agent的中间步骤吞掉。另外你可以在自定义tool的run方法里手动把结果写进memory的chat_memory,比如用ConversationBufferMemory的save_context方法把“工具输出”作为AI的回复存进去,这样下一轮对话就能通过chat_history读到。还有个骚操作是给Agent的system prompt加一句硬约束:“当你需要引用之前工具的结果时,必须从chat_history中提取,除非用户明确要求刷新数据”,实测对GPT-4这类模型效果很明显。不过我到现在也没完全搞定流式输出场景下的上下文一致性问题,不知道你用的什么模型?如果是开源模型可能还得调prompt模板里的token分配比例。
试试把工具返回结果手动加到SystemMessage里,或者用ConversationSummaryMemory压缩历史。
这个问题我也踩过坑,其实AgentExecutor默认只传了chat_history,工具返回的observation并不会自动塞进后续prompt。我现在的做法是在Agent的prompt模板里手动加一个类似“tool_result”的变量,每次工具调用完就把结果存到memory的对应key里,再拼到system message末尾。代码的话,你可以在自定义Agent的create_prompt方法里加上{agent_scratchpad},但要确保你的memory把工具输出也存进去。另外可以试试用langgraph的StateGraph,它天然支持把工具返回值保留在状态里,比纯AgentExecutor更可控。
试试把工具输出显式加到SystemMessage里,或者用ConversationSummaryMemory专门缓存中间结果。
这个问题我也踩过坑,关键其实不在memory组件本身,而是AgentExecutor默认只把最终答案写进对话历史,工具调用的中间输出确实不会被自动保存。你可以试试在自定义Agent的prompt里显式加一个叫做“工具调用记录”的变量,每次执行完工具后手动把结果格式化成文本追加到system message里。比如用langchain的ConversationBufferMemory时,记得把memory_key设成chat_history,然后在agent的prompt模板里用{chat_history}引用,但工具返回的内容得自己拼进这个字符串里。我习惯的做法是在工具函数返回前,把结果和对应的query一起存到一个全局dict里,然后在agent的runnable里加一步format步骤,把最新几条记录塞回prompt。不过要注意token长度,最好设个滑动窗口只保留最近3-5条工具调用结果。你用的什么模型?如果是gpt-4o或者claude这种长上下文模型,甚至可以尝试把全部工具调用历史丢进system prompt,效果反而更稳。
这个问题我也踩过坑,LangChain的AgentExecutor默认确实不会把工具返回值自动塞进后续的上下文里。你可以试试在工具函数里直接把返回结果写入memory的chat_history,或者用ConversationSummaryMemory来压缩历史,比单纯加memory更稳。另外检查下agent的prompt里有没有显式要求它从历史记录里提取工具输出,有时是prompt里缺少了“优先使用已有结果”的指令。
这问题我折腾过挺久的,确实是个经典坑。你试的memory和chat_history方向是对的,但LangChain的AgentExecutor默认只把用户的query和最终的answer塞进对话历史,工具调用的中间结果(比如查数据库返回的订单状态)根本不在那个Message里。我后来是手动在agent的callback里把tool output追加到SystemMessage里解决的,比如在agent的plan方法执行前把最近的tool结果拼进prompt的system部分。还有个更省事的办法是用ConversationalRetrievalChain那种带显式记忆的链,但工具调用多了还是会丢。你可以试试把memory的return_messages设成True,然后在agent的prefix里写清楚“之前工具返回的X数据要保留”,但光改prompt效果有限。我现在的做法是自定义一个AgentExecutor子类,在_take_next_step方法里把每一步的tool output都存到memory的buffer里,这样多轮对话时Agent能直接从记忆里捞结果。代码大概就是重写_consume_next_step,把Observation追加到chat_history,你可以去LangChain的github issue搜“tool output not in memory”,有几个讨论串里有现成模板。
这个问题我折腾过挺久的,其实核心不在于memory本身,而是LangChain的AgentExecutor默认只把最终答案塞进chat_history,工具调用的中间输出(比如数据库查到的订单数据)并不会自动回填到后续的prompt里。你试过加memory和chat_history但没效果,大概率是因为你只是在Agent外层传了对话历史,但Agent内部每次调用工具时,它的推理上下文只包含当前轮次的输入和之前几步的思考链,不会主动把前一轮工具返回的结果当作“已知事实”保留下来。
我后来用的一个解法是在自定义tool的run方法里,把每次工具返回的结果显式写入一个全局的dict或者ConversationSummaryMemory,然后在Agent的prompt模板里加一条类似“如果用户询问已查询过的信息,请优先参考以下缓存内容:{cached_tool_results}”的指令。这样Agent在重新规划时就能看到缓存。不过要注意缓存的生命周期,比如按session_id隔离,不然跨会话会串数据。
另一个更省事的办法是改用LangGraph(如果你版本够新),它的StateGraph可以精确控制哪些变量在节点之间传递,不会像AgentExecutor那样只透传AgentAction和AgentFinish。我最近把几个复杂流程切到LangGraph后,工具输出的持久化问题基本就解决了。你可以试试在Agent的prompt里显式要求它“每次工具调用后,将结果总结为简洁的事实存入memory”,同时把memory的类型换成ConversationTokenBufferMemory并设置高token数,这样至少能兜住最近几轮的工具输出。如果还丢,大概率是Agent的LLM本身上下文窗口不够或者prompt里对记忆的引用写得太隐晦了。