概览

构建代理(或任何 LLM 应用)的难点在于让它们足够可靠。它们可能适合原型,但在真实用例中经常失败。

代理为什么会失败?

代理失败时,通常是因为代理内部的 LLM 调用采取了错误操作,或者没有按预期行动。LLM 失败通常有两个原因:
  1. 底层 LLM 的能力不足
  2. 没有向 LLM 传入“正确”的上下文
多数情况下,导致代理不可靠的真正原因是第二点。 Context engineering 是以正确格式向 LLM 提供正确的信息和工具,使其能够完成任务。这是 AI Engineers 最重要的工作。缺少“正确”的上下文,是提升代理可靠性的最大阻碍,而 LangChain 的代理抽象专门设计用于支持 context engineering。
如果你刚开始了解 context engineering,请从 conceptual overview 开始,了解不同类型的上下文以及何时使用它们。

代理循环

典型的代理循环包含两个主要步骤:
  1. Model call:使用 prompt 和可用工具调用 LLM,返回响应或执行工具的请求
  2. Tool execution:执行 LLM 请求的工具,并返回工具结果
核心代理循环图
该循环会持续运行,直到 LLM 决定结束。

你可以控制什么

如需构建可靠代理,你需要控制代理循环每一步中发生的事情,以及步骤之间发生的事情。
上下文类型你控制什么临时还是持久
Model Context传入模型调用的内容(指令、消息历史、工具、响应格式)临时
Tool Context工具可以访问和产生的内容(对 state、store、runtime context 的读写)持久
Life-cycle Context模型调用和工具调用之间发生的事情(摘要、guardrails、日志记录等)持久

临时上下文

LLM 在单次调用中看到的内容。你可以修改 messages、tools 或 prompts,而不改变 state 中保存的内容。

持久上下文

跨轮次保存在 state 中的内容。Life-cycle hooks 和工具写入会永久修改这些内容。

数据源

在整个过程中,代理会访问(读取/写入)不同的数据源:
数据源也称为范围示例
Runtime Context静态配置对话范围用户 ID、API keys、数据库连接、权限、环境设置
State短期记忆对话范围当前 messages、上传文件、认证状态、工具结果
Store长期记忆跨对话用户偏好、提取的洞察、记忆、历史数据

工作原理

LangChain middleware 是底层机制,使使用 LangChain 的开发者能够实际落地 context engineering。 Middleware 允许你挂接到代理生命周期中的任意步骤,并:
  • 更新上下文
  • 跳转到代理生命周期中的不同步骤
在本指南中,你会频繁看到使用 middleware API 来实现 context engineering 目标。

Model context

控制每次模型调用传入的内容,包括指令、可用工具、使用哪个模型以及输出格式。这些决策会直接影响可靠性和成本。

System Prompt

开发者提供给 LLM 的基础指令。

Messages

发送给 LLM 的完整 messages 列表(对话历史)。

Tools

代理可访问的、用于执行操作的实用工具。

Model

实际调用的模型(包括配置)。

Response Format

模型最终响应的 schema 规范。
所有这些 model context 都可以来自 state(短期记忆)、store(长期记忆)或 runtime context(静态配置)。

System Prompt

System prompt 会设置 LLM 的行为和能力。不同用户、上下文或对话阶段需要不同指令。成功的代理会利用记忆、偏好和配置,为当前对话状态提供正确指令。
从 state 访问消息数量或对话上下文:
from langchain.agents import create_agent
from langchain.agents.middleware import dynamic_prompt, ModelRequest

@dynamic_prompt
def state_aware_prompt(request: ModelRequest) -> str:
    # request.messages is a shortcut for request.state["messages"]
    message_count = len(request.messages)

    base = "You are a helpful assistant."

    if message_count > 10:
        base += "\nThis is a long conversation - be extra concise."

    return base

agent = create_agent(
    model="gpt-5.4",
    tools=[...],
    middleware=[state_aware_prompt]
)

Messages

Messages 组成了发送给 LLM 的 prompt。 管理 messages 的内容非常关键,这可以确保 LLM 拥有正确的信息来给出高质量响应。
当上传文件上下文与当前查询相关时,从 State 注入该上下文:
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from typing import Callable

@wrap_model_call
def inject_file_context(
    request: ModelRequest,
    handler: Callable[[ModelRequest], ModelResponse]
) -> ModelResponse:
    """Inject context about files user has uploaded this session."""
    # Read from State: get uploaded files metadata
    uploaded_files = request.state.get("uploaded_files", [])

    if uploaded_files:
        # Build context about available files
        file_descriptions = []
        for file in uploaded_files:
            file_descriptions.append(
                f"- {file['name']} ({file['type']}): {file['summary']}"
            )

        file_context = f"""Files you have access to in this conversation:
{chr(10).join(file_descriptions)}

Reference these files when answering questions."""

        # Inject file context before recent messages
        messages = [
            *request.messages,
            {"role": "user", "content": file_context},
        ]
        request = request.override(messages=messages)

    return handler(request)

agent = create_agent(
    model="gpt-5.4",
    tools=[...],
    middleware=[inject_file_context]
)
临时和持久的 message 更新:上面的示例使用 wrap_model_call 进行临时更新,即修改单次调用发送给模型的 messages,而不改变 state 中保存的内容。如需进行会修改 state 的持久更新,可以:
  • wrap_model_call 返回带有 CommandExtendedModelResponse,从模型调用层注入 state 更新。
  • 使用 before_modelafter_modelwrap_tool_call(用于工具返回)等 life-cycle hooks 更新对话历史。更多详情请参阅 middleware documentation
更多信息请参阅 State updates

Tools

Tools 让模型能够与数据库、API 和外部系统交互。你如何定义和选择 tools,会直接影响模型能否有效完成任务。

定义 tools

每个 tool 都需要清晰的名称、描述、参数名称和参数描述。这些不仅是 metadata,它们会引导模型推理何时以及如何使用该工具。
from langchain.tools import tool

@tool(parse_docstring=True)
def search_orders(
    user_id: str,
    status: str,
    limit: int = 10
) -> str:
    """Search for user orders by status.

    Use this when the user asks about order history or wants to check
    order status. Always filter by the provided status.

    Args:
        user_id: Unique identifier for the user
        status: Order status: 'pending', 'shipped', or 'delivered'
        limit: Maximum number of results to return
    """
    # Implementation here
    pass

选择 tools

并非每个 tool 都适合所有场景。工具太多可能会压垮模型(上下文过载)并增加错误;工具太少则会限制能力。动态工具选择会根据认证状态、用户权限、feature flags 或对话阶段调整可用工具集。
仅在达到特定对话里程碑后启用高级工具:
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]
)
如需了解过滤预注册 tools 和在 runtime 注册 tools(例如来自 MCP 服务器)的方式,请参阅 Dynamic tools

Model

不同模型有不同优势、成本和上下文窗口。请为当前任务选择合适模型,该选择可能会在代理运行期间变化。
根据 State 中的对话长度使用不同模型:
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from langchain.chat_models import init_chat_model
from typing import Callable

# Initialize models once outside the middleware
large_model = init_chat_model("claude-sonnet-4-6")
standard_model = init_chat_model("gpt-5.4")
efficient_model = init_chat_model("gpt-5.4-mini")

@wrap_model_call
def state_based_model(
    request: ModelRequest,
    handler: Callable[[ModelRequest], ModelResponse]
) -> ModelResponse:
    """Select model based on State conversation length."""
    # request.messages is a shortcut for request.state["messages"]
    message_count = len(request.messages)

    if message_count > 20:
        # Long conversation - use model with larger context window
        model = large_model
    elif message_count > 10:
        # Medium conversation
        model = standard_model
    else:
        # Short conversation - use efficient model
        model = efficient_model

    request = request.override(model=model)

    return handler(request)

agent = create_agent(
    model="gpt-5.4-mini",
    tools=[...],
    middleware=[state_based_model]
)
更多示例请参阅 Dynamic model

Response format

Structured output 会将非结构化文本转换为经过验证的结构化数据。当需要提取特定字段或为下游系统返回数据时,自由格式文本并不足够。 工作原理: 当你提供一个 schema 作为响应格式时,模型的最终响应会保证符合该 schema。代理会运行模型/工具调用循环,直到模型完成工具调用,然后将最终响应强制转换为所提供的格式。

定义格式

Schema 定义会引导模型。字段名称、类型和描述会明确指定输出应遵循的格式。
from pydantic import BaseModel, Field

class CustomerSupportTicket(BaseModel):
    """Structured ticket information extracted from customer message."""

    category: str = Field(
        description="Issue category: 'billing', 'technical', 'account', or 'product'"
    )
    priority: str = Field(
        description="Urgency level: 'low', 'medium', 'high', or 'critical'"
    )
    summary: str = Field(
        description="One-sentence summary of the customer's issue"
    )
    customer_sentiment: str = Field(
        description="Customer's emotional tone: 'frustrated', 'neutral', or 'satisfied'"
    )

选择格式

动态响应格式选择会根据用户偏好、对话阶段或角色调整 schema,在早期返回简单格式,并随着复杂度增加返回详细格式。
根据对话状态配置 structured output:
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from pydantic import BaseModel, Field
from typing import Callable

class SimpleResponse(BaseModel):
    """Simple response for early conversation."""
    answer: str = Field(description="A brief answer")

class DetailedResponse(BaseModel):
    """Detailed response for established conversation."""
    answer: str = Field(description="A detailed answer")
    reasoning: str = Field(description="Explanation of reasoning")
    confidence: float = Field(description="Confidence score 0-1")

@wrap_model_call
def state_based_output(
    request: ModelRequest,
    handler: Callable[[ModelRequest], ModelResponse]
) -> ModelResponse:
    """Select output format based on State."""
    # request.messages is a shortcut for request.state["messages"]
    message_count = len(request.messages)

    if message_count < 3:
        # Early conversation - use simple format
        request = request.override(response_format=SimpleResponse)
    else:
        # Established conversation - use detailed format
        request = request.override(response_format=DetailedResponse)

    return handler(request)

agent = create_agent(
    model="gpt-5.4",
    tools=[...],
    middleware=[state_based_output]
)

Tool context

Tools 的特殊之处在于它们既会读取上下文,也会写入上下文。 在最基本的情况下,工具执行时会接收 LLM 的请求参数,并返回一条工具消息。工具完成自己的工作并生成结果。 Tools 也可以为模型获取重要信息,使模型能够执行并完成任务。

读取

大多数真实世界的 tools 需要的不只是 LLM 参数。它们可能需要用于数据库查询的用户 ID、用于外部服务的 API keys,或用于决策的当前会话状态。Tools 会从 state、store 和 runtime context 中读取这些信息。
从 State 读取信息,以检查当前会话信息:
from langchain.tools import tool, ToolRuntime
from langchain.agents import create_agent

@tool
def check_authentication(
    runtime: ToolRuntime
) -> str:
    """Check if user is authenticated."""
    # Read from State: check current auth status
    current_state = runtime.state
    is_authenticated = current_state.get("authenticated", False)

    if is_authenticated:
        return "User is authenticated"
    else:
        return "User is not authenticated"

agent = create_agent(
    model="gpt-5.4",
    tools=[check_authentication]
)

写入

工具结果可用于帮助代理完成给定任务。Tools 既可以直接向模型返回结果,也可以更新代理记忆,让重要上下文可供后续步骤使用。
使用 Command 写入 State,以追踪会话专属信息:
from langchain.tools import tool, ToolRuntime
from langchain.agents import create_agent
from langgraph.types import Command

@tool
def authenticate_user(
    password: str,
    runtime: ToolRuntime
) -> Command:
    """Authenticate user and update State."""
    # Perform authentication (simplified)
    if password == "correct":
        # Write to State: mark as authenticated using Command
        return Command(
            update={"authenticated": True},
        )
    else:
        return Command(update={"authenticated": False})

agent = create_agent(
    model="gpt-5.4",
    tools=[authenticate_user]
)
如需查看在 tools 中访问 state、store 和 runtime context 的完整示例,请参阅 Tools

Life-cycle context

控制核心代理步骤之间发生的事情,即拦截数据流以实现摘要、guardrails 和日志记录等横切关注点。 如你在 Model ContextTool Context 中所见,middleware 是让 context engineering 可落地的机制。Middleware 允许你挂接到代理生命周期中的任意步骤,并执行以下操作之一:
  1. 更新上下文:修改 state 和 store 以持久化更改、更新对话历史或保存洞察
  2. 在生命周期中跳转:根据上下文移动到代理循环中的不同步骤,例如在满足条件时跳过工具执行,或使用修改后的上下文重复模型调用
代理循环中的 middleware hooks

示例:摘要

最常见的 life-cycle 模式之一,是在对话历史过长时自动压缩它。与 Model Context 中展示的临时消息裁剪不同,摘要会持久更新 state,即使用摘要永久替换旧 messages,并保存供所有未来轮次使用。 LangChain 为此提供了内置 middleware:
from langchain.agents import create_agent
from langchain.agents.middleware import SummarizationMiddleware

agent = create_agent(
    model="gpt-5.4",
    tools=[...],
    middleware=[
        SummarizationMiddleware(
            model="gpt-5.4-mini",
            trigger={"tokens": 4000},
            keep={"messages": 20},
        ),
    ],
)
当对话超过 token 限制时,SummarizationMiddleware 会自动:
  1. 使用单独的 LLM 调用总结较早 messages
  2. 在 State 中用摘要 message 替换它们(永久替换)
  3. 保留最近 messages 作为上下文
摘要后的对话历史会被永久更新,未来轮次会看到摘要,而不是原始 messages。
如需查看内置 middleware、可用 hooks 以及如何创建自定义 middleware 的完整列表,请参阅 Middleware documentation

最佳实践

  1. 从简单开始:先使用静态 prompts 和 tools,仅在需要时添加动态逻辑
  2. 增量测试:一次只添加一个 context engineering 功能
  3. 监控性能:追踪模型调用、token 用量和延迟
  4. 使用内置 middleware:利用 SummarizationMiddlewareLLMToolSelectorMiddleware
  5. 记录上下文策略:清楚说明传递了什么上下文以及为什么传递
  6. 理解临时和持久的区别:Model context 更改是临时的(按调用生效),而 life-cycle context 更改会持久保存到 state

相关资源