很多 Agent 项目在刚开始时都很简单:一个模型、几个工具,再加一段 System Prompt,就可以完成大部分任务。
但随着业务增加,问题会逐渐出现。简单查询和复杂分析共用同一个模型,成本难以控制;所有工具同时暴露给模型,权限和选择都变得复杂;面对不同用户,同一套 Prompt 也开始显得不合适。
这时,与其继续拆出多个 Agent,更合理的做法,是让 Agent 根据当前上下文调整自己的运行配置。
在 LangChain 中,模型、工具和 Prompt 都可以在运行过程中动态选择。本文重点介绍这种动态机制,以及它在真实项目中应该怎样设计。
一、动态配置的需要
我们先从一个示例开始。
假如有一个订单助手,它可以完成这些事情:
查询订单
修改订单
分析订单数据
回答业务问题
针对上述功能,要实现一个 Agent 可以这样写:
agent = create_agent(
model="openai:gpt-5.5",
tools=[
search_order,
update_order,
analyze_order,
],
system_prompt="你是一个订单助手。",
)
虽然 Agent 可以正常运行,但模型、工具、系统提示词都固定的。
两个不同的任务,“帮我查一下 A1024 的订单状态”,和“分析最近三个月退款率上升的原因”,对模型能力的要求明显不同。
对于工具,普通用户只需要 search_order,管理员才需要 update_order。如果所有工具始终都提供给模型,一方面会增加上下文,另一方面也不利于权限控制。
Prompt 也存在类似问题。对开发人员,可以保留更多技术术语;面对普通业务人员,则更适合减少术语,多解释结果。
因此,要使 Agent 能够灵活应付上述那些场景,应根据上下文动态选择模型、工具以及提示词。如下:
当前上下文
↓
选择模型
选择工具
生成 Prompt
↓
执行本轮模型调用
在 LangChain 中,可通过 Middleware 实现上述功能。
二、动态 Agent 工作方式
这里先说明一个容易混淆的地方。
使用动态模型、动态工具之后,create_agent() 创建的仍然是同一个 Agent。模型真正被调用之前,Middleware 会拿到当前的 ModelRequest,读取 State、Runtime Context 等信息,再决定是否调整请求。
常见写法:
@wrap_model_call
def middleware(request, handler):
...
return handler(request.override(...))
整个过程大致如下:
Agent
↓
Middleware
↓
读取当前上下文
↓
调整 ModelRequest
↓
调用模型
ModelRequest 中包含当前消息、模型、工具、System Message、State、Runtime 等信息。
例如:
request.override(model=another_model)
这一轮模型调用会改用 another_model。
再比如:
request.override(tools=selected_tools)
模型在这一轮只能看到 selected_tools 中的工具。
Prompt 也可以在模型调用前重新生成。
因此,Agent 的执行框架保持不变,每一轮送给模型的配置则可以根据上下文调整。
三、动态切换模型
之所以要动态切换模型,通常是基于一个常规场景:对普通任务使用轻量模型;对复杂任务使用更强模型。
可以使用 @wrap_model_call 实现:
from collections.abc import Callable
from langchain.agents import create_agent
from langchain.agents.middleware import (
ModelRequest,
ModelResponse,
wrap_model_call,
)
from langchain.chat_models import init_chat_model
fast_model = init_chat_model("openai:gpt-5.5-mini")
strong_model = init_chat_model("openai:gpt-5.5")
@wrap_model_call
def select_model(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
if len(request.messages) > 10:
model = strong_model
else:
model = fast_model
print(f"message count: {len(request.messages)}")
print(f"selected model: {model.model_name}")
return handler(request.override(model=model))
agent = create_agent(
model=fast_model,
tools=[],
middleware=[select_model],
)
下面为示例输出:
message count: 1
selected model: gpt-5.5-mini
当对话不断变长,消息数量超过 10:
message count: 12
selected model: gpt-5.5
可以看出,create_agent() 中设置的是默认模型(fast_model),Middleware 可以在调用模型前为当前请求指定另一个模型。
在实际项目中,判断使用哪个模型的条件可以结合实际需要,比如:
- 当前任务类型;
- 用户套餐;
- Token 数量;
- 延迟要求;
- 已消耗成本;
- Agent 当前执行阶段。
还有一点,动态模型还需要和 Fallback(备用模型)区分开。
动态模型路由发生在调用之前,系统根据当前条件决定使用哪个模型。
Fallback 通常发生在一次调用失败之后,例如主模型超时,再换备用模型执行。
前者属于路由策略,后者属于异常恢复。
四、动态选择工具
Tool 也可以根据当前请求进行动态调整。
假设系统里有三个工具:
from langchain.tools import tool
@tool
def search_order(order_id: str) -> str:
"""查询订单。"""
return f"Order {order_id}: paid"
@tool
def update_order(order_id: str) -> str:
"""修改订单。"""
return f"Order {order_id} updated"
@tool
def delete_order(order_id: str) -> str:
"""删除订单。"""
return f"Order {order_id} deleted"
普通用户只能查订单,管理员可以执行全部操作。
我们可以这样过滤:
@wrap_model_call
def filter_tools(request: ModelRequest, handler) -> ModelResponse:
role = request.runtime.context.role
if role == "admin":
tools = request.tools
else:
tools = [
tool
for tool in request.tools
if tool.name == "search_order"
]
return handler(request.override(tools=tools))
Agent 创建时仍然注册全部工具:
agent = create_agent(
model="openai:gpt-5.5-mini",
tools=[
search_order,
update_order,
delete_order,
],
middleware=[filter_tools],
context_schema=Context,
)
运行结果如下。
普通用户:
available tools:
["search_order"]
管理员:
available tools:
[
"search_order",
"update_order",
"delete_order"
]
这里的工具已经注册到了 Agent 中,Middleware 负责根据当前用户进行筛选:
Agent 已注册的工具
↓
根据上下文过滤
↓
本轮提供给模型的工具
这种方式很适合权限控制、执行阶段控制和功能开关。
五、动态生成 Prompt
Prompt 也可以随着上下文变化。
LangChain 提供了 @dynamic_prompt,可以在模型调用前,根据当前 ModelRequest 生成 System Prompt。
例如,同一个 Agent 同时服务新手和专家:
from dataclasses import dataclass
from langchain.agents import create_agent
from langchain.agents.middleware import dynamic_prompt, ModelRequest
@dataclass
class Context:
user_level: str
@dynamic_prompt
def user_prompt(request: ModelRequest) -> str:
level = request.runtime.context.user_level
if level == "expert":
return (
"你是一名技术助手。"
"请使用精确的术语和实现细节。"
)
return (
"你是一名技术助手。"
"请用简单的语言和示例来解释概念。"
)
agent = create_agent(
model="openai:gpt-5.5-mini",
tools=[],
middleware=[user_prompt],
context_schema=Context,
)
专家用户调用:
result = agent.invoke(
{
"messages": [
{
"role": "user",
"content": "解释什么是 Agent State"
}
]
},
context=Context(user_level="expert"),
)
运行结果。
Agent State 是 Agent 执行过程中持续传递的状态数据,
通常包含 messages,也可以扩展自定义字段。
如果改成:
context=Context(user_level="beginner")
输出可能变成:
可以把 Agent State 理解成 Agent 工作时使用的一份“临时记录”。
模型说过什么、工具返回了什么,
后面的步骤都可以继续读取。
模型和用户问题都没有变化,System Prompt 发生了调整。
这种方式适合处理用户级别、回答风格、业务角色、运行环境等差异。
它也比在业务代码里到处拼 Prompt 更容易管理,因为生成 Prompt 的逻辑被集中放到了 Middleware 中。
六、上下文从哪里来
模型、工具和 Prompt 都需要依据某些信息做判断。这些信息的来源通常有:State、Runtime Context 和 Store。
| 来源 | 适合保存 |
|---|---|
| State | 当前执行过程中的状态 |
| Runtime Context | 本次调用带进来的信息 |
| Store | 跨会话长期保存的数据 |
State 适合保存执行过程中不断变化的信息。
例如:
len(request.messages)
随着 Agent 继续运行,消息数量会发生变化,因此可以用于判断对话长度或者当前执行阶段。
Runtime Context 更适合保存调用开始前已经确定的信息。
比如:
request.runtime.context.role
这里可以放:
user_id
role
tenant_id
environment
mode
这些数据往往来自登录状态、请求参数或者业务系统。
Store 适合长期保存的数据,比如:
用户偏好
长期记忆
个性化配置
跨会话信息
选择位置时,可以直接按照生命周期判断。
执行过程中需要持续更新的数据放 State。
只对本次调用有效的数据放 Runtime Context。
多个会话之间仍然需要保留的数据,再考虑 Store。
如果这些数据的职责混在一起,后面的 Middleware 逻辑也会越来越难维护。
七、什么时候适合使用
动态能力并不是项目一开始就要加入的设计。
如果系统只有一个模型、几个工具,所有用户权限相同,Prompt 也基本固定,那么直接写:
create_agent(
model=model,
tools=tools,
system_prompt=prompt,
)
代码更简单,也更容易理解。
当下面这些情况逐渐出现时,再引入动态配置会比较合适:
- 不同任务对模型能力要求差异明显;
- Tool 数量开始增加;
- 用户之间存在权限差异;
- Prompt 需要根据用户或环境变化;
- 一个 Agent 服务多个租户;
- Agent 不同执行阶段需要不同能力。
同时,还需要注意一个实践问题:
不要把模型路由、工具权限、Prompt 生成全部堆在一个 Middleware 里。更适合的做法是结合业务按职责拆分 Middleware。
总结
本文介绍了 LangChain 中动态 Agent 的实现原理:通过 Middleware 读取当前的 State、Runtime Context 或 Store,在模型调用前动态调整 Model、Tools 和 Prompt。通过动态调整,同一个 Agent 就可以根据任务复杂度选择不同模型,根据用户角色开放不同工具,也可以针对不同用户生成不同的 System Prompt。
实际项目中,如果模型、权限和用户类型都比较固定,静态配置通常已经够用;当有明确的个性化需求,再引入动态配置,会更容易控制成本、权限和维护复杂度。
社区讨论
参与讨论
有问题或想法?欢迎继续讨论。