最近在学AI Agent,用LangChain搭了个简单的工具调用流程,比如让Agent先查数据库再调用API。但发现多轮对话里,Agent经常记不住之前的工具返回结果,比如用户问“刚才那个订单的状态是什么”,它又去重新查一遍,而不是用缓存里的数据。我试了加memory和chat_history,但感觉工具调用的结果没被塞进上下文。是不是我prompt写的有问题?还是LangChain的AgentExecutor默认就不保留工具输出?求指点正确姿势,最好能贴个代码片段,感谢!
用LangChain搭Agent,工具调用时上下文总丢,大佬们怎么解决的?
全部回复
共 162 条你这问题多半是memory只管聊天记录,工具返回得自己塞回prompt,试试在agent里加个变量存结果。
这个问题我上周刚踩过一模一样的坑,最后发现不是prompt的问题,而是AgentExecutor默认只保留最后一步的observation,中间工具结果全被丢掉了。你如果查一下源码,会发现它的scratchpad是动态拼接的,不会自动把历史工具输出塞进memory。我后来是直接改用了create_react_agent,手动在prompt里加了一个tool_history变量,每次迭代把之前的工具结果格式化后拼进去,才解决。另外你如果用langchain的ConversationBufferMemory,记得把它传给AgentExecutor的memory参数,但光这样还不够,因为那个memory只管聊天记录,不管工具输出——你得自己写个回调函数,在工具执行完后把结果存进一个全局dict,然后在下次LLM调用前手动注入到prompt里。代码上我给你个思路:定义个自定义的AgentOutputParser,在parse里把last_output抓出来,然后塞进你的prompt模板的{history}位置。还有个更省事的办法,直接用langgraph,把每个工具节点返回的状态显式定义为state的一部分,这样天然就能跨轮保留,省得自己拼。不过你要是不想换框架,就记住一个原则:所有工具返回都必须在prompt模板里显式占位,别指望框架自动帮你做。我之前还试过把工具结果用Pickle序列化存到memory里,但对话一长token爆炸,不推荐。
这问题我当初也踩过坑,核心不是prompt,是LangChain的AgentExecutor默认只把最后一次工具输出放进中间步骤,历史中间结果确实不会自动累积到上下文。我后来是把每次工具调用结果手动存进memory的buffer,然后自定义prompt里显式引用memory变量,比如“根据历史工具结果:{memory}”来强制塞进去。另外你可以试试把Agent换成langgraph,它对状态流的控制更直观,工具输出直接作为节点数据传递,不会丢。要是还坚持用AgentExecutor,就重写一下_plan_and_execute的中间步骤,把全部tool output拼进scratchpad,代码上多几行但稳。
这个问题在LangChain 0.1版本后尤其明显,其实不是prompt的锅,是AgentExecutor的迭代逻辑只保留最近一步的中间输出。我当时是写了个自定义回调,把每次工具返回的内容存成全局变量,再在prompt模板里用f-string拼进system消息,注意要放在聊天历史之前,不然模型注意力会被冲淡。你也可以试试用ConversationBufferWindowMemory配合memory_key='chat_history',但工具结果得单独存,不然会被截断。你要是用tool calling模式,建议直接看langgraph的预构建Agent节点,那个是官方推荐的新姿势,状态管理清晰很多。
我遇到过一模一样的现象,查了下源码发现AgentExecutor的scratchpad
工具结果确实不会自动进上下文,得自己在AgentExecutor里把中间步骤塞回prompt,或者用memory存一下。
这问题我踩过坑,LangChain默认确实不会把工具输出自动塞回memory,得手动在Agent里把中间步骤的observation拼进prompt。我之前是把工具结果存到st.session_state之类的临时变量里,然后每次对话前把最近几轮的工具输出格式化后追加到system message里,效果还行。另外你查一下是不是AgentExecutor的return_intermediate_steps没开,开了之后还得自己处理这个字段,不然光靠chat_history确实会丢。可以试试换个思路,别依赖Agent自己记忆,直接在工具内部做缓存,比如查数据库前先查一个全局dict,命中就直接返回,这样至少能省一次调用。
说实话这个问题我当初也踩过,AgentExecutor默认确实不会把工具输出自动塞进下一轮对话的上下文里,它只保留最终回复。你加了memory和chat_history,但那个只缓存了用户和AI的对话,工具调用的中间结果根本没进去,所以它每次都得重新查。
我后来是直接在tool的func里把返回结果手动追加到memory的buffer,或者更粗暴一点,在自定义agent的prompt模板里把最近几次工具输出硬编码进去,比如每次调用完就把结果格式化成“工具X返回:...”,然后拼到system message后面。这样虽然丑,但至少能记住。
还有个坑是,如果工具返回的数据特别大,塞进上下文容易爆token,所以最好只提取关键字段存进去,别整个JSON都丢给模型。另外你可以看看LangSmith的trace,确认一下每一轮到底传了什么给LLM,问题基本一目了然。
代码片段我没法贴完整的,但思路就是重写_create_scratchpad,或者用langchain的memory里的ConversationSummaryBufferMemory,把工具结果压缩成摘要再存。你可以试试看,要是搞不定咱们再聊。
这问题太典型了,AgentExecutor默认确实不会把工具输出塞回给模型,你光加memory没用,得自己把中间结果拼到prompt里。我之前也是卡这儿,后来干脆在工具函数里把结果格式化好,返回时直接附带一句“已获取数据:xxx”,再配合自定义的agent_kwargs把历史步骤传进去,基本就稳了。你可以试试重写一下Agent的prompt模板,把scratchpad里的内容显式加进去,官方文档里那个“intermediate_steps”字段就是干这个的。另外,如果对话轮次多,建议用ConversationBufferWindowMemory限制长度,不然token爆炸也容易丢上下文。
这个问题我刚开始搞的时候也踩过同样的坑,后来发现不是memory的问题,而是AgentExecutor的机制本身就不把工具输出自动喂回prompt。你观察得很准,它只保留最终回复,中间步骤的observation在下一轮就被丢掉了。
我当时试了个笨办法,就是自己在工具函数里把结果手动塞进一个全局变量,然后在每次调用前拼到system prompt里,虽然能work但特别脏。后来看了LangChain的源码,发现其实有个return_intermediate_steps=True的参数,配合ConversationSummaryBufferMemory,可以手动把intermediate steps格式化成字符串追加到memory里。
不过最干净的做法还是换个思路:不要依赖框架自动管理,直接在Agent的prompt模板里写死一个“最近一次工具结果”的占位符,然后在每次执行前用RunnablePassthrough把之前的输出传进去。代码大概就是agent_executor = create_react_agent(llm, tools, prompt) | RunnablePassthrough.assign(previous_result=lambda x: x.get("intermediate_steps", ""))。
另外也提醒下,如果你用的是新版的langchain.agents.create_agent,它默认是支持verbose=True时能看到完整上下文,但确实不会自动缓存工具结果。建议你直接看下AgentExecutor的handle_parsing_errors和early_stopping_method,有时候上下文丢失是解析失败导致的静默重试,跟记忆无关。
这个问题我当初也踩过坑,核心真不在prompt,而是AgentExecutor默认只把最终输出塞回memory,中间工具返回的observation根本不进上下文。你加memory和chat_history只存了对话历史,但工具结果那一步是临时变量,多轮后就没了。我当时是直接改了agent的callback,在tool执行后手动把结果append到memory的chat_history里,或者更简单粗暴——在tool的func内部把返回值自己写进一个全局dict,然后在系统prompt里动态拼接最近的工具结果。还有个野路子是干脆不用AgentExecutor,自己写个循环,每次把tool输出显式加进messages列表再传给下一次LLM调用,这样上下文永远不会丢。你试试把工具返回的内容格式化成“工具名:结果”这样的字符串,塞进memory的summary,比单纯存原始输出更稳。另外确认下你用的memory类型,ConversationBufferMemory默认只存人类和AI消息,工具消息得用ConversationSummaryBufferMemory或者自定义类才行。
我之前也踩过这个坑,LangChain默认的AgentExecutor确实不会自动把工具输出塞进后续的prompt里,得自己在agent的scratchpad里做文章。你可以试试把工具返回的内容显式拼到observation里,然后配合ConversationBufferMemory一起用,或者在每次调用工具后手动把结果追加到chat_history。另外检查下你的prompt里有没有明确要求模型“基于最近一次工具结果回答”,有时候模型不是记不住,而是根本不知道你希望它用旧数据。我后来改成自定义Agent类重写plan逻辑,把工具输出存成一个全局变量,下一轮直接注入,比调框架自带的memory稳多了。
这问题我也踩过坑,AgentExecutor确实不会自动回填工具输出,得自己在prompt里显式拼接历史observation。
我后来是重写了个callback把tool结果塞进memory,或者干脆用langgraph状态机管理,比硬调memory稳多了。
AgentExecutor默认确实不保留中间tool output,得手动塞进memory的AIMessage里,或者换langgraph自定义状态流。
这问题我也踩过,后来干脆在tool返回结果里加个摘要再存进chat_history,多轮直接查摘要,省得重跑。
我之前也踩过这个坑,核心问题不是memory,而是AgentExecutor默认只把最终输出塞回给LLM,中间工具返回的原始结果并不会自动进入下一轮对话的上下文。你可以试着重写Agent的plan_and_execute逻辑,或者直接用langgraph,把每一步tool output显式追加到state里,类似state["messages"].extend([ToolMessage(content=result)])。另外检查下你的prompt里有没有明确要求“基于已获取信息回答,避免重复查询”,有时候模型会为了“保险”而重新调用工具。我之前用create_react_agent配合自定义的messages_modifier把历史工具结果压缩进system prompt,效果好了不少,你可以试试。
我之前也踩过这个坑,问题多半出在AgentExecutor的中间步骤没被回写到memory里,工具输出只是临时变量。你可以试试自定义一个CallbackHandler,把工具结果主动塞进chat_history,或者干脆用ConversationalAgent加上memory的显式传参,别指望默认行为。另外,prompt里明确要求“基于已有信息回答,不要重复查询”也能缓解,但治标不治本。我后来换成了直接手动拼接上下文,每次调用工具前把历史结果格式化好再喂给LLM,逻辑反而更可控。
我遇到类似情况时发现,LangChain的AgentExecutor确实默认不保留工具输出,它只把最终回复存进记忆。你可以在工具函数内部自己维护一个全局缓存dict,键是订单号,值是结果,然后让Agent在工具返回时就把缓存写进去,下次用户问就直接读缓存。不过这样得小心并发和过期问题,简单demo够用了。另外试试把memory的return_messages改成True,有时候格式不对也会丢信息。
说实话,这个问题我折腾了一周,最后发现是memory的human_prefix和ai_prefix没对齐导致的,LangChain拼接历史时把工具输出当成了过期消息。你可以先打印一下memory的buffer,看看工具结果到底有没有进去。如果进去了但Agent还是忽略,那就是prompt里没强调“优先使用已有数据”。我当时的解法是给工具加了个装饰器
这问题我踩过一模一样的坑,AgentExecutor默认确实不会自动把tool output塞回上下文,得靠显式的memory或者自己拼history。我当时是把工具返回结果手动append到chat_history里,再作为prompt的一部分传给Agent,比单纯加memory靠谱。另外你试试把memory的return_messages设成True,用ConversationBufferMemory,别用字符串那种,不然格式不对照样丢。代码上大概就是在tool callback里把结果存一下,然后每次调用前重新构建messages列表,你可以搜下“LangChain agent intermediate steps”这个关键词,官方文档里有个例子就是干这个的。
这个问题我上周刚踩过坑,LangChain的AgentExecutor确实默认不把中间工具输出塞回prompt,它只维护thought和action那几步。你光加chat_history没用,那管的是用户和assistant的对话,工具返回得自己手动拼进下次迭代的context里。我后来是把工具结果存成一个全局dict,然后每次agent执行前在prompt末尾动态加一段“已知信息”,再让agent优先引用那段。你试下重写一下agent的prompt模板,把memory里的key单独拎出来跟工具输出合并,别让它们混在一起。另外有个偷懒办法,直接换langgraph,它的StateGraph能显式传递tool output,比AgentExecutor可控多了,就是学习曲线陡一点。你那个查完数据库再调API的流程,如果用langgraph的话,可以在node之间显式传data,基本不会丢。要是非要留在AgentExecutor,就记得每次工具调用后把result强制append到scratchpad里,别只依赖自带机制。
你这问题我当初也踩过,AgentExecutor本身确实不会自动把工具输出塞进后续轮次的上下文,得自己在回调里把结果拼到messages里。我之前是重写了个自定义的agent,或者用create_react_agent然后显式把tool output追加到memory,比硬调prompt靠谱。另外你试试把工具返回的内容精简一下再存,有时候信息太多反而干扰模型提取关键状态,就更容易“失忆”了。
我之前也踩过这个坑,LangChain的AgentExecutor确实不会自动把工具输出塞回memory,得自己在工具函数里把结果手动append到chat_history里,或者用langchain的ConversationBufferMemory配合return_messages=True才行。另外你可以试试给Agent的system prompt加一句“如果用户提到之前的数据,直接引用历史记录中的内容,不要重复调用工具”,这样能减少很多误触发。不过说实话,多轮工具调用这块LangChain做得挺糙的,后来我直接换用CrewAI或者自己写个状态机反而更可控。
这问题我也踩过,光塞chat_history没用,得把工具输出显式拼回prompt里,或者用memory的return_messages=True试试。
这题我踩过类似的坑,关键不是memory,而是工具返回的内容根本没进到Agent的中间步骤里。你可以在自定义工具里把结果显式拼到observation里,或者用langchain的IntermediateStepsReporter插件,它能强制把每轮工具输出都塞回给模型。另外试试把工具描述写得更细,比如“查询订单状态(返回JSON含最新时间戳)”,这样Agent会更容易判断该复用还是重查。我之前也是折腾半天,最后直接改用了create_agent的return_intermediate_steps=True,然后手动把历史steps拼进prompt,效果立竿见影。