作为LLM提示词的一个重要组成部分,表示对话历史的消息列表在结构上有一个基本的要求:如果LLM返回的AIMessage包含ToolCall对象,那么Agent会期望每个ToolCall对象都有对应的ToolMessage。但是Agent在执行过程会因为一些异常导致LLM返回的AIMessage中的ToolCall对象没有得到正确的处理,最终导致后续的消息列表中缺失了对应的ToolMessage。PatchToolCallsMiddleware就在面临这种情况是提供了一种补救机制,自动创建并添加缺失的ToolMessage。
1. ReAct工作模式导致的对话历史的交替结构
作为Agent对话历史的消息列表之所以具有确定的结构,是源于Agent基于ReAct的工作模式。ReAct是一种让LLM将推理与行动相结合的交互模式。它解决了模型空谈不干活(只推理不调用工具)或盲目干活(只调工具不分析原因)的问题。ReAct的核心是一个循环过程,通常被称为Thought-Action-Observation循环:
- Thought(推理):模型先写下当前的思考,分析任务目标、目前的进度以及下一步需要做什么;
- Action(行动):模型根据推理结果,决定调用哪个工具(如搜索、计算器、API)并给出参数;
- Observation(观察):系统执行工具后,将结果反馈给模型;
- 循环:模型根据观察到的结果,开始新一轮的循环,直到得出最终答案。
create_agent函数创建的Agent由model和tools两个核心节点组成,model节点负责生成推理内容和生成工具调用,tools节点则负责执行工具调用并返回结果。消息列表是Agent状态的核心成员,ReAct模式清晰地体现在Agent的执行流程上:
- 当用户指定输入调用Agent时,
model节点生成提供上下文调用LLM,这里的上下文包括对话历史、工具集描述、系统指令(提示词)和响应数据格式等。经过LLM的推理,如果它觉得此时能够提供最终的答案,它会将答案封装成一个AIMessage对象返回。如果它觉得还需要调用工具来获取更多信息或进行计算,它会在生成的AIMessage对象中包含一个或多个ToolCall对象来调用工具; - Agent会将接收到的
AIMessage对象添加到消息列表中:- 如果此
AIMessage的ToolCall列表为空,执行流程到此结束; - 否则Agent会根据工具调用的信息来调用
tools节点执行工具。相关的工具以并发形式的执行后,执行的结果会以ToolMessage的形式被添加到消息列表中。
- 如果此
- 包含
ToolMessage的消息列表会被再次提交给model节点,进入下一轮的推理循环。
这就决定了在一个推理步骤结束时,作为对话历史的消息应该具有这样的结构:
- 如果某条
AIMessage中包含了一个或多个ToolCall对象,那么在消息列表中必须有对应的一个或多个ToolMessage来描述工具调用的结果; - 每条
ToolMessage必须包含一个tool_call_id字段来与对应的ToolCall进行匹配,以保证消息列表的结构完整性。
2. 对话历史的结构为何会被破坏?
在ReAct模式中,消息列表的严格交替结构(Assistant提议 -> Tool执行 -> Assistant总结)是其逻辑闭环的基础。但是很多异常情况下会导致结构被破环,比如:
- 并发调用与乱序返回:模型一次性发出了三个工具调用(A, B, C),但外部执行环境返回结果的顺序变成了B, A, C。如果直接按接收顺序插入消息列表,而没有与其对应的
tool_call_id严格匹配,模型会把B的结果当成A的反馈; - 强制插入用户干预:在模型发出工具调用之后返回结果之前,用户突然又发了一条新消息(比如算了,别查了,换个任务)。这会导致包含
ToolCall的AIMessage后面直接跟了一条HumanMessage,中间缺失了必要的ToolMessage提供工具执行的结果; - 溢出导致的截断:由于ReAct循环非常多,历史记录太长,开发者简单粗暴地删除了中间的某些消息。如果删除了某条
ToolMessage,但保留了后面基于该工具结果做出的推理,思维链就会出现断层; - 模型解析失败:模型由于幻觉或受干扰,没有输出合法的
ToolCall,系统无法提取出Action。
3. 错乱消息结构导致的问题
ReAct模式的循环特性决定了消息列表的具有上述的交替结构,而这种消息结构也反映了LLM过去的推理流程,并用以指导后续的推理。一旦这种链条断裂,模型就会陷入逻辑精神分裂,会产生以下后果:
- 逻辑幻觉 (Hallucination) :模型会试图脑补缺失的部分。如果它提出了查询请求但没收到结果,它可能会根据训练数据编造一个虚假的结果,并以此为基础继续推理;
- 无限递归循环 (Infinite Loop) :模型发现上一个
ToolCall没有对应的结果,它会认为我刚才没做成,于是再次发起工具调用。如果系统逻辑不健壮,会陷入请求-等待-再次请求的死循环,极快地消耗Token; - 拒绝执行 (Model Refusal) :很多LLM对上下文一致性有要求。如果
AIMessage消息包含ToolCall但紧接着不是ToolMessage,API 往往会直接报错,导致程序崩溃; - 因果倒置 (Causal Confusion) :如果消息顺序错位,模型会基于错误的前提得出结论。例如搜索A 的股价,结果返回了B 的股价,模型会非常自信地告诉你A涨了(实际上是B涨了)。
4. 利用PatchToolCallsMiddleware修补错乱的消息结构
我们利用下面这个实例来演示一下PatchToolCallsMiddleware针对对话历史的修补功能。我们创建了一个Agent,并注册了一个工具get_weather用于获取天气信息。我们在调用Agent的时候,构造了一个AIMessage,其中包含了一个针对get_weather工具的ToolCall对象,但是我们没有提供对应的ToolMessage。
from langchain.agents import create_agent
from deepagents.middleware.patch_tool_calls import PatchToolCallsMiddleware
from langchain.tools import tool
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage,AIMessage, ToolCall
from dotenv import load_dotenv
import asyncio
load_dotenv()
@tool
def get_weather(location: str) -> str:
"""Get the current weather for a given location."""
return f"The current weather in {location} is sunny with a high of 25°C."
agent = create_agent(
model=ChatOpenAI(model="gpt-5.2-chat"),
tools=[get_weather],
middleware=[PatchToolCallsMiddleware()])
human_message = HumanMessage(content="What's the weather like in Suzhou today?")
ai_message = AIMessage(content="")
tool_call:ToolCall = {"id": "tool_call_1", "name": "get_weather", "args": {"location": "Suzhou"}}
ai_message.tool_calls.append(tool_call)
async def main():
result = await agent.ainvoke(input={"messages": [human_message, ai_message]})
for message in result["messages"]:
message.pretty_print()
asyncio.run(main())
注册的PatchToolCallsMiddleware会自动检测到这个问题,并且为这个ToolCall对象创建一个对应的ToolMessage。在如下所示的对话列表中,出现的第一个ToolMessage就是PatchToolCallsMiddleware自动创建的。正是这个ToolMessage告诉LLM针对get_weather工具调用被取消,所以它决定再次对它发起调用,使得Agent可以继续执行后续的推理和行动。
================================ Human Message =================================
What's the weather like in Suzhou today?
================================== Ai Message ==================================
Tool Calls:
get_weather (tool_call_1)
Call ID: tool_call_1
Args:
location: Suzhou
================================= Tool Message =================================
Name: get_weather
Tool call get_weather with id tool_call_1 was cancelled - another message came in before it could be completed.
================================== Ai Message ==================================
Tool Calls:
get_weather (call_Exlkyg84lyVTMvOEfGuzgu6N)
Call ID: call_Exlkyg84lyVTMvOEfGuzgu6N
Args:
location: Suzhou, China
================================= Tool Message =================================
Name: get_weather
The current weather in Suzhou, China is sunny with a high of 25°C.
================================== Ai Message ==================================
Today in **Suzhou, China**, the weather is **sunny** with a **high around 25 °C (77 °F)**. It should be a pleasant day overall—great for being outdoors.
PatchToolCallsMiddleware的实现非常简单,它通过重写before_agent/after_agent方法来检测AIMessage中的ToolCall对象,并且在发现缺失对应的ToolMessage时,自动创建一个新的ToolMessage来进行补充。这个新创建的ToolMessage会使用一个预定义的模板来生成内容。
class PatchToolCallsMiddleware(AgentMiddleware):
def before_agent(self, state: AgentState, runtime: Runtime[Any]) -> dict[str, Any] | None
410



被折叠的 条评论
为什么被折叠?



