LangChain 学习与拆解(2)动态系统
本文介绍了LangChain中Agent动态系统的实现方法,重点讲解了如何通过包装式钩子(Hooks)实现模型、工具和提示词的动态配置。文章首先分析了最小静态系统的问题,然后详细阐述了Agent调用过程中的React循环和钩子机制,特别是wrap_model_call和wrap_tool_call这两个包装式钩子的特点和使用方法。通过具体代码示例,展示了如何实现动态模型选择、工具权限控制、运行时添
本文对应参考文档:代理
https://docs.langchain.org.cn/oss/python/langchain/agents#structured-output详细教程请参照上述链接。下述内容仅为个人理解,无法保证即时性。
前述章节
LangChain 学习与拆解(1)Agent 最小系统 | 此章节介绍了Agent最小系统包括:大模型、系统提示词、工具集、记忆机制。本章节是介绍在最小系统基础上,引入大模型、系统提示词、工具集的动态配置。
最小系统的问题
最小系统是静态的,模型配置、系统提示词、工具集等相关配置在定义 Agent 时就已经固化。因此,它无法在运行时根据任务需求或环境变化,对模型、工具等核心要素进行动态调整。
本文讲述的动态系统就是实现对模型配置、系统提示词、工具集3部分的动态配置,即实现在Agent运行时,动态更换调用模型、工具、系统提示词。
动态系统基础
Agent invoke调用过程——React循环

上图为Agent调用过程的基本循环,官方称为React循环。
在将用户指令通过invoke传递给Agent后,Agent执行如下步骤:
- 读取模型配置、工具集、上下文(包括系统提示词、长期记忆、短期记忆、状态等)和用户指令一起组织为ModelRequest
- 调用大模型进行推理,大模型推理内容一般包括文本回答、工具选择两部分
- 接收ModelResponse,解析是否存在ToolCallRequest(包括模型选择执行的工具、工具参数、id等)
- 解析ToolCallRequest
- 如不存在ToolCallRequest,则输出大模型的文本回复
- 如存在ToolCallRequest,则通过执行器执行选择的工具,并将执行结果添加到上下文中重新组织ModelRequest,调用大模型,重复步骤前述步骤
Agent invoke调用的钩子(Hooks)
钩子(Hooks)就是在上述调用过程(React循环)的特定节点增加方法,以支持自定义处理(即中间件)。
以下是LangChain提供的钩子:
| 钩子方法 (Hook) | 触发时机 | 典型应用场景 |
|---|---|---|
before_agent |
Agent 整体开始执行前 | 验证用户输入、加载长期记忆、初始化上下文 |
before_model |
每次调用大模型(LLM)前 | 动态修改提示词(Prompt)、裁剪过长的历史消息 |
wrap_model_call |
包裹每次大模型调用 | 动态切换模型、动态修改工具清单、请求/响应拦截 |
wrap_tool_call |
包裹每次工具调用 | 动态绑定工具、工具权限检查、工具调用重试 |
after_model |
每次大模型返回响应后 | 检查模型输出是否包含敏感词、应用安全护栏(Guardrails) |
after_agent |
Agent 整体执行结束后 | 保存对话结果、清理临时资源、记录最终日志 |

wrap_model_call、wrap_tool_call称为包装式钩子,其他4个钩子称为节点式钩子。
节点式钩子特点
before_agent、after_agent、before_model、after_model为节点式钩子,具有如下特点:
- 查看、修改数据,可以访问修改state(跨次全局共享)、context(模型单次调用),无法直接修改ModelRequest、ModelResponse,仅能通过修改state,间接调整ModelRequest部分属性
- 无法阻断流程,执行完自定义方法后,就进入正常流程,如想阻断只能通过抛出错误的方式阻断。
包装式钩子特点(动态系统实现基础)
wrap_model_call、wrap_tool_call为包装式钩子,具有如下特点:
- 可以直接访问修改state、context、ModelRequest、ModelResponse
- 拦截流程,选择放行、阻断或者其他特殊处理。
动态系统
动态系统的实现均是通过包装式钩子实现的。包装式钩子的使用方法包括两步:
- 定义钩子处理方法
- 通过
@wrap_model_call、@wrap_tool_call实现from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse from typing import Callable # 如果底层调用的是异步大模型,记得加上 async 和 await @wrap_model_call async def my_wrap_model_middleware( request: ModelRequest, handler: Callable[[ModelRequest], ModelResponse] ) -> ModelResponse: """自定义包装式钩子""" # --- 【放行前】处理 --- # 1. 读取或修改请求参数 (如 request.model, request.messages) # 2. 获取上下文信息 (request.state, request.runtime.context) print(f"准备调用模型,当前消息数: {len(request.state['messages'])}") # 3. 【核心控制】决定是否放行 # 场景A:直接短路(例如命中缓存),不调用 handler,直接返回伪造的响应 # if cache_hit: # return cached_response # 场景B:正常放行,将请求传递给下一个环节 try: response = await handler(request) except Exception as e: # --- 【异常处理】 --- # 可以在这里捕获模型调用的报错,进行重试或返回兜底回复 print(f"模型调用失败: {e}") raise e # --- 【放行后】处理 --- # 修改模型的返回结果 (response) # 记录耗时、Token消耗等 return response 通过继承AgentMiddleware类实现from langchain.agents.middleware import AgentMiddleware, ModelRequest, ModelResponse from typing import Callable class MyCustomMiddleware(AgentMiddleware): """自定义中间件类""" # 可以在这里定义一些中间件专属的配置或状态 # state_schema = ... def wrap_model_call( self, request: ModelRequest, handler: Callable[[ModelRequest], ModelResponse] ) -> ModelResponse: """重写包装模型调用的方法""" # --- 【放行前】处理 --- # 同样可以访问 self 来获取类的内部属性 print("通过类中间件拦截到了模型请求") # 执行实际的模型调用(如果是异步环境,这里同样需要配合 async/await) response = handler(request) # --- 【放行后】处理 --- # 对响应结果进行二次加工 return response # 💡 使用时,直接将这个类实例化放入 create_agent 的 middleware 列表中即可 # agent = create_agent(model=..., tools=[...], middleware=[MyCustomMiddleware()])
- 通过
- 定义Agent时,将定义的方法作为中间件传入
agent = create_agent( middleware=[my_wrap_model_middleware(),MyCustomMiddleware()], )
动态模型
与动态模型相对的静态模型是指:在最小系统中,模型的配置是在create_agent(model=model)时配置的,model的参数是固化的,只能访问单个模型,无法根据不同条件实现对不同模型的访问。
动态模型是通过wrap_model_call,修改Modelrequest,根据不同条件实现对不同模型的访问。一般使用场景是针对上下文大小、任务类型等实现对不同能力模型的动态访问,节约成本。
官方示例
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
basic_model = ChatOpenAI(model="gpt-5.4-mini")
advanced_model = ChatOpenAI(model="gpt-5.4")
@wrap_model_call
def dynamic_model_selection(request: ModelRequest, handler) -> ModelResponse:
"""Choose model based on conversation complexity."""
message_count = len(request.state["messages"])
if message_count > 10:
# Use an advanced model for longer conversations
model = advanced_model
else:
model = basic_model
return handler(request.override(model=model))
agent = create_agent(
model=basic_model, # Default model
tools=tools,
middleware=[dynamic_model_selection]
)
- 通过@wrap_model_call定义的中间件的入参为ModelRequest、Handler,返回值为ModelResponse
- ModelRequest、ModelResponse无法直接修改,但是可以通过override方法生成新的ModelRequest、ModelResponse
- handler功能是放行,入参是ModelRequest,返回值是ModelResponse
执行逻辑
动态模型实现包括如下部分:
- 多模型定义
- Agent创建指定默认模型
- 中间件定义
- 模型选用判断条件
- 新ModelRequest生成
- handler放行

动态工具
动态工具是对工具集的动态实现,与动态模型实现大致相同,也是通过wrap_model_call钩子定义中间件,实现对ModelRequest中Tools的替换。
与动态模型不同的是,ModelRequest中Tools是供模型选择的文本,不包括函数实例。通过ModelRequest中Tools替换,可以用来做已注册工具集的减法,增加新工具需要实现对函数实例的调用。
已注册工具集减法
已注册工具集减法与动态模型实现方式一致,不再赘述。主要使用场景是做权限区分,或者避免工具干扰。
官方示例
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from typing import Callable
@wrap_model_call
def state_based_tools(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse]
) -> ModelResponse:
"""Filter tools based on conversation State."""
# Read from State: check if user has authenticated
state = request.state
is_authenticated = state.get("authenticated", False)
message_count = len(state["messages"])
# Only enable sensitive tools after authentication
if not is_authenticated:
tools = [t for t in request.tools if t.name.startswith("public_")]
request = request.override(tools=tools)
elif message_count < 5:
# Limit tools early in conversation
tools = [t for t in request.tools if t.name != "advanced_search"]
request = request.override(tools=tools)
return handler(request)
agent = create_agent(
model="gpt-5.4",
tools=[public_search, private_search, advanced_search],
middleware=[state_based_tools]
)
新工具加法
新工具的加法关键在于实现对新工具的调用,包括两步:
- 通过wrap_model_call定义中间件,实现ModelRequest中的tools替换;
- 通过wrap_tool_call定义中间件,实现将新工具添加到ToolCallRequest.
tool中。
工具调用逻辑
调用Agent时,Agent会将定义时注册的tools添加到ModelRequest的tools中,tools中的每个tool都包含名称、描述、参数规范。
模型会根据用户指令和工具的描述,选择一个工具,并生成参数,在存储在响应内容的tool_call中。
接收到模型响应后,Agent会在根据模型选择的工具名称,在工具集中加载函数实例,生成ToolCallRequest,由执行器执行。

通过wrap_model_call定义中间件添加tool,只能让大模型能够选择到工具。在组织ToolCallRequest是,无法找到工具实例,所以ToolCallRequest的tool为none,无法执行。所以需要通过wrap_tool_call定义中间件,将新工具添加到ToolCallRequest.tool中。
动态工具逻辑

官方示例
from langchain.tools import tool
from langchain.agents import create_agent
from langchain.agents.middleware import AgentMiddleware, ModelRequest, ToolCallRequest
# A tool that will be added dynamically at runtime
@tool
def calculate_tip(bill_amount: float, tip_percentage: float = 20.0) -> str:
"""Calculate the tip amount for a bill."""
tip = bill_amount * (tip_percentage / 100)
return f"Tip: ${tip:.2f}, Total: ${bill_amount + tip:.2f}"
class DynamicToolMiddleware(AgentMiddleware):
"""Middleware that registers and handles dynamic tools."""
def wrap_model_call(self, request: ModelRequest, handler):
# Add dynamic tool to the request
# This could be loaded from an MCP server, database, etc.
updated = request.override(tools=[*request.tools, calculate_tip])
return handler(updated)
def wrap_tool_call(self, request: ToolCallRequest, handler):
# Handle execution of the dynamic tool
if request.tool_call["name"] == "calculate_tip":
return handler(request.override(tool=calculate_tip))
return handler(request)
agent = create_agent(
model="gpt-4o",
tools=[get_weather], # Only static tools registered here
middleware=[DynamicToolMiddleware()],
)
# The agent can now use both get_weather AND calculate_tip
result = agent.invoke({
"messages": [{"role": "user", "content": "Calculate a 20% tip on $85"}]
})
动态系统提示词
动态系统提示词实现逻辑与动态模型相同,只是官方提供了@dynamic_prompt,中间件直接返回提示词即可,无需再调用override生成新的ModelRequest,也无需调用handler放行。具体逻辑不再赘述。以下为官方示例:
from typing import TypedDict
from langchain.agents import create_agent
from langchain.agents.middleware import dynamic_prompt, ModelRequest
class Context(TypedDict):
user_role: str
@dynamic_prompt
def user_role_prompt(request: ModelRequest) -> str:
"""Generate system prompt based on user role."""
user_role = request.runtime.context.get("user_role", "user")
base_prompt = "You are a helpful assistant."
if user_role == "expert":
return f"{base_prompt} Provide detailed technical responses."
elif user_role == "beginner":
return f"{base_prompt} Explain concepts simply and avoid jargon."
return base_prompt
agent = create_agent(
model="gpt-5.4",
tools=[web_search],
middleware=[user_role_prompt],
context_schema=Context
)
# The system prompt will be set dynamically based on context
result = agent.invoke(
{"messages": [{"role": "user", "content": "Explain machine learning"}]},
context={"user_role": "expert"}
)
更多推荐



所有评论(0)