很多 Agent 项目在刚开始时都很简单:一个模型、几个工具,再加一段 System Prompt,就可以完成大部分任务。

但随着业务增加,问题会逐渐出现。简单查询和复杂分析共用同一个模型,成本难以控制;所有工具同时暴露给模型,权限和选择都变得复杂;面对不同用户,同一套 Prompt 也开始显得不合适。

这时,与其继续拆出多个 Agent,更合理的做法,是让 Agent 根据当前上下文调整自己的运行配置。

在 LangChain 中,模型、工具和 Prompt 都可以在运行过程中动态选择。本文重点介绍这种动态机制,以及它在真实项目中应该怎样设计。

一、动态配置的需要

我们先从一个示例开始。

假如有一个订单助手,它可以完成这些事情:

代码片段Text
查询订单
修改订单
分析订单数据
回答业务问题

针对上述功能,要实现一个 Agent 可以这样写:

代码片段Python
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 能够灵活应付上述那些场景,应根据上下文动态选择模型、工具以及提示词。如下:

代码片段Text
当前上下文

选择模型
选择工具
生成 Prompt

执行本轮模型调用

在 LangChain 中,可通过 Middleware 实现上述功能。

二、动态 Agent 工作方式

这里先说明一个容易混淆的地方。

使用动态模型、动态工具之后,create_agent() 创建的仍然是同一个 Agent。模型真正被调用之前,Middleware 会拿到当前的 ModelRequest,读取 State、Runtime Context 等信息,再决定是否调整请求。

常见写法:

代码片段Python
@wrap_model_call
def middleware(request, handler):
    ...
    return handler(request.override(...))

整个过程大致如下:

代码片段Text
Agent

Middleware

读取当前上下文

调整 ModelRequest

调用模型

ModelRequest 中包含当前消息、模型、工具、System Message、State、Runtime 等信息。

例如:

代码片段Python
request.override(model=another_model)

这一轮模型调用会改用 another_model

再比如:

代码片段Python
request.override(tools=selected_tools)

模型在这一轮只能看到 selected_tools 中的工具。

Prompt 也可以在模型调用前重新生成。

因此,Agent 的执行框架保持不变,每一轮送给模型的配置则可以根据上下文调整。

三、动态切换模型

之所以要动态切换模型,通常是基于一个常规场景:对普通任务使用轻量模型;对复杂任务使用更强模型。

可以使用 @wrap_model_call 实现:

代码片段Python
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],
)

下面为示例输出:

代码片段Text
message count: 1
selected model: gpt-5.5-mini

当对话不断变长,消息数量超过 10:

代码片段Text
message count: 12
selected model: gpt-5.5

可以看出,create_agent() 中设置的是默认模型(fast_model),Middleware 可以在调用模型前为当前请求指定另一个模型。

在实际项目中,判断使用哪个模型的条件可以结合实际需要,比如:

  • 当前任务类型;
  • 用户套餐;
  • Token 数量;
  • 延迟要求;
  • 已消耗成本;
  • Agent 当前执行阶段。

还有一点,动态模型还需要和 Fallback(备用模型)区分开。

动态模型路由发生在调用之前,系统根据当前条件决定使用哪个模型。

Fallback 通常发生在一次调用失败之后,例如主模型超时,再换备用模型执行。

前者属于路由策略,后者属于异常恢复。

四、动态选择工具

Tool 也可以根据当前请求进行动态调整。

假设系统里有三个工具:

代码片段Python
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"

普通用户只能查订单,管理员可以执行全部操作。

我们可以这样过滤:

代码片段Python
@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 创建时仍然注册全部工具:

代码片段Python
agent = create_agent(
    model="openai:gpt-5.5-mini",
    tools=[
        search_order,
        update_order,
        delete_order,
    ],
    middleware=[filter_tools],
    context_schema=Context,
)

运行结果如下。

普通用户:

代码片段Text
available tools:
["search_order"]

管理员:

代码片段Text
available tools:
[
    "search_order",
    "update_order",
    "delete_order"
]

这里的工具已经注册到了 Agent 中,Middleware 负责根据当前用户进行筛选:

代码片段Text
Agent 已注册的工具

根据上下文过滤

本轮提供给模型的工具

这种方式很适合权限控制、执行阶段控制和功能开关。

五、动态生成 Prompt

Prompt 也可以随着上下文变化。

LangChain 提供了 @dynamic_prompt,可以在模型调用前,根据当前 ModelRequest 生成 System Prompt。

例如,同一个 Agent 同时服务新手和专家:

代码片段Python
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,
)

专家用户调用:

代码片段Python
result = agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": "解释什么是 Agent State"
            }
        ]
    },
    context=Context(user_level="expert"),
)

运行结果。

代码片段Text
Agent State 是 Agent 执行过程中持续传递的状态数据,
通常包含 messages,也可以扩展自定义字段。

如果改成:

代码片段Python
context=Context(user_level="beginner")

输出可能变成:

代码片段Text
可以把 Agent State 理解成 Agent 工作时使用的一份“临时记录”。

模型说过什么、工具返回了什么,
后面的步骤都可以继续读取。

模型和用户问题都没有变化,System Prompt 发生了调整。

这种方式适合处理用户级别、回答风格、业务角色、运行环境等差异。

它也比在业务代码里到处拼 Prompt 更容易管理,因为生成 Prompt 的逻辑被集中放到了 Middleware 中。

六、上下文从哪里来

模型、工具和 Prompt 都需要依据某些信息做判断。这些信息的来源通常有:State、Runtime Context 和 Store。

来源适合保存
State当前执行过程中的状态
Runtime Context本次调用带进来的信息
Store跨会话长期保存的数据

State 适合保存执行过程中不断变化的信息。

例如:

代码片段Python
len(request.messages)

随着 Agent 继续运行,消息数量会发生变化,因此可以用于判断对话长度或者当前执行阶段。

Runtime Context 更适合保存调用开始前已经确定的信息。

比如:

代码片段Python
request.runtime.context.role

这里可以放:

代码片段Text
user_id
role
tenant_id
environment
mode

这些数据往往来自登录状态、请求参数或者业务系统。

Store 适合长期保存的数据,比如:

代码片段Text
用户偏好
长期记忆
个性化配置
跨会话信息

选择位置时,可以直接按照生命周期判断。

执行过程中需要持续更新的数据放 State。

只对本次调用有效的数据放 Runtime Context。

多个会话之间仍然需要保留的数据,再考虑 Store。

如果这些数据的职责混在一起,后面的 Middleware 逻辑也会越来越难维护。

七、什么时候适合使用

动态能力并不是项目一开始就要加入的设计。

如果系统只有一个模型、几个工具,所有用户权限相同,Prompt 也基本固定,那么直接写:

代码片段Python
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。

实际项目中,如果模型、权限和用户类型都比较固定,静态配置通常已经够用;当有明确的个性化需求,再引入动态配置,会更容易控制成本、权限和维护复杂度。