一、概述
构建代理(或任何大型语言模型应用)最难的是让它们足够可靠。
虽然它们可能适合原型,但在实际应用中常常失败。
为什么代理会失败?
代理失败通常是因为代理内部的 LLM 调用做错了操作,或者没有按预期执行。LLM 失败的原因有两个:
- 底层的 LLM 能力不足
- "正确"的上下文没有传递给 LLM
大多数情况下——实际上是第二个原因导致代理不可靠。
什么是上下文工程
上下文工程是以合适的格式提供正确的信息和工具,使 LLM 能够完成任务。
- 这是人工智能工程师的首要任务
- 缺乏"正确"上下文是更可靠代理的头号障碍
- LangChain 的代理抽象设计独特地促进了上下文工程
刚接触上下文工程?先从「概念概述」理解不同类型的语境以及何时使用它们。
二、智能体循环(Agent Loop)
典型的代理环路主要包括两个步骤:
- 模型调用—— 调用 LLM 并提供提示词和可用工具,返回响应或执行工具的请求
- 工具执行—— 执行 LLM 请求的工具,返回工具结果
┌──────────────────────────────────────┐ │ │ ▼ │ ┌─────────┐ 工具调用请求 ┌──────────────┴───┐ │ Model ├─────────────────►│ Tool Execution │ │ Call │◄─────────────────┤ (工具执行) │ └────┬────┘ 工具结果 └──────────────────┘ │ │ 无工具调用 → 完成 ▼ END这个循环会持续,直到 LLM 决定完成。
三、你能控制什么
要构建可靠的代理,你需要控制代理循环的每一步发生的事情,以及步骤之间发生的事情。
三类上下文
| 上下文类型 | 你能控制什么 | 瞬态还是持久 |
|---|---|---|
| 模型上下文(Model context) | 模型调用包含了哪些内容(指令、消息历史、工具、响应格式) | 瞬态 |
| 工具上下文(Tool context) | 哪些工具可以访问和生成(读写状态、存储、运行时上下文) | 持久 |
| 生命周期上下文(Life-cycle context) | 模型调用和工具调用之间会发生什么(总结、护栏、日志记录等) | 持久 |
瞬态 vs 持久
| 类型 | 含义 |
|---|---|
| 瞬态上下文(Transient) | LLM 对单次通话的感知。你可以修改消息、工具或提示,而不改变保存在状态中的内容 |
| 持久上下文(Persistent) | 在各回合中被保存在状态内的内容。生命周期钩子和工具写入会永久修改这一点 |
四、数据来源
在整个过程中,你的代理访问(读写)不同的数据源:
| 数据来源 | 也被称为 | 范围 | 示例 |
|---|---|---|---|
| 运行时上下文(Runtime Context) | 静态配置 | 对话范围 | 用户 ID、API 密钥、数据库连接、权限、环境设置 |
| 状态(State) | 短期记忆 | 对话范围 | 当前消息、上传文件、认证状态、工具结果 |
| 存储(Store) | 长期记忆 | 跨对话 | 用户偏好、提取的洞察、记忆、历史数据 |
五、工作原理
LangChain 中间件(middleware)是让上下文工程对开发者切实可行的底层机制。
Middleware 允许你钩入代理生命周期的任何步骤并:
- 更新上下文(Update context)
- 跳转到生命周期中的不同步骤(Jump)
本指南中会频繁使用 middleware API 作为达成上下文工程的手段。
六、模型上下文(Model context)
控制每次模型调用包含什么—— 指令、可用工具、使用哪个模型、输出格式。
这些决策直接影响可靠性和成本。
| 类型 | 说明 |
|---|---|
| System Prompt | 开发者给 LLM 的基础指令 |
| Messages | 发送给 LLM 的完整消息列表(会话历史) |
| Tools | 代理可用于采取行动的工具 |
| Model | 实际要调用的模型(含配置) |
| Response Format | 模型最终响应的 schema 规范 |
所有这些类型的模型上下文都可以取自 state(短期记忆)、store(长期记忆)或 runtime context(静态配置)。
6.1 System Prompt
系统提示词设定 LLM 的行为和能力。
不同的用户、上下文或对话阶段需要不同的指令。
成功的代理会借助记忆、偏好和配置,为当前对话状态提供正确的指令。
从 State 读取(访问消息数或对话上下文):
fromlangchain.agentsimportcreate_agentfromlangchain.agents.middlewareimportdynamic_prompt,ModelRequest@dynamic_promptdefstate_aware_prompt(request:ModelRequest)->str:# request.messages 是 request.state["messages"] 的快捷方式message_count=len(request.messages)base="You are a helpful assistant."ifmessage_count>10:base+="\nThis is a long conversation - be extra concise."returnbase agent=create_agent(model="gpt-5.5",tools=[...],middleware=[state_aware_prompt])三种数据来源:State / Store / Runtime Context均可用于动态系统提示词。
6.2 Messages
消息构成发送给 LLM 的提示词。
管理消息内容至关重要,以确保 LLM 拥有正确信息来做出好的响应。
从 State 注入已上传文件的上下文(当与当前查询相关时):
fromlangchain.agentsimportcreate_agentfromlangchain.agents.middlewareimportwrap_model_call,ModelRequest,ModelResponsefromtypingimportCallable@wrap_model_calldefinject_file_context(request:ModelRequest,handler:Callable[[ModelRequest],ModelResponse])->ModelResponse:"""Inject context about files user has uploaded this session."""# 从 State 读取:获取上传文件的元数据uploaded_files=request.state.get("uploaded_files",[])ifuploaded_files:# 构建关于可用文件的上下文file_descriptions=[]forfileinuploaded_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."""# 在最近的消息之前注入文件上下文messages=[*request.messages,{"role":"user","content":file_context},]request=request.override(messages=messages)returnhandler(request)agent=create_agent(model="gpt-5.5",tools=[...],middleware=[inject_file_context])⭐ 瞬态 vs 持久的消息更新
上面的例子使用wrap_model_call进行瞬态更新——
修改单次调用发送给模型的消息,不改变 state 中保存的内容。
对于修改 state 的持久更新,你可以:
- 从
wrap_model_call返回带有Command的ExtendedModelResponse,从模型调用层注入 state 更新 - 使用生命周期钩子,如
before_model、after_model或wrap_tool_call(用于工具返回)来更新对话历史
详见 middleware 文档与 State updates。
6.3 Tools
工具让模型与数据库、API 和外部系统交互。
你如何定义和选择工具直接影响模型能否有效完成任务。
定义工具
每个工具需要清晰的名称、描述、参数名和参数描述。
这些不只是元数据——它们指导模型关于何时以及如何使用该工具的推理。
fromlangchain.toolsimporttool@tool(parse_docstring=True)defsearch_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 herepass
@tool(parse_docstring=True)可解析 Google 风格的 docstring,自动填充参数描述。
选择工具
并非每个工具都适合每种情况:
- 工具太多可能压垮模型(上下文过载)并增加错误
- 工具太少限制能力
动态工具选择使工具集能根据认证状态、用户权限、功能标志或对话阶段进行适配。
fromlangchain.agentsimportcreate_agentfromlangchain.agents.middlewareimportwrap_model_call,ModelRequest,ModelResponsefromtypingimportCallable@wrap_model_calldefstate_based_tools(request:ModelRequest,handler:Callable[[ModelRequest],ModelResponse])->ModelResponse:"""Filter tools based on conversation State."""# 从 State 读取:检查用户是否已认证state=request.state is_authenticated=state.get("authenticated",False)message_count=len(state["messages"])# 仅在认证后启用敏感工具ifnotis_authenticated:tools=[tfortinrequest.toolsift.name.startswith("public_")]request=request.override(tools=tools)elifmessage_count<5:# 对话早期限制工具tools=[tfortinrequest.toolsift.name!="advanced_search"]request=request.override(tools=tools)returnhandler(request)agent=create_agent(model="gpt-5.5",tools=[public_search,private_search,advanced_search],middleware=[state_based_tools])参见 Dynamic tools 了解过滤预注册工具和运行时注册工具(例如从 MCP 服务器)。
6.4 Model
不同模型有不同的优势、成本和上下文窗口。为当前任务选择合适的模型,这在代理运行期间可能会变化。
fromlangchain.agentsimportcreate_agentfromlangchain.agents.middlewareimportwrap_model_call,ModelRequest,ModelResponsefromlangchain.chat_modelsimportinit_chat_modelfromtypingimportCallable# 在中间件外部一次性初始化模型large_model=init_chat_model("claude-sonnet-4-6")standard_model=init_chat_model("gpt-5.5")efficient_model=init_chat_model("gpt-5.4-mini")@wrap_model_calldefstate_based_model(request:ModelRequest,handler:Callable[[ModelRequest],ModelResponse])->ModelResponse:"""Select model based on State conversation length."""# request.messages 是 request.state["messages"] 的快捷方式message_count=len(request.messages)ifmessage_count>20:# 长对话 - 使用上下文窗口更大的模型model=large_modelelifmessage_count>10:# 中等对话model=standard_modelelse:# 短对话 - 使用高效模型model=efficient_model request=request.override(model=model)returnhandler(request)agent=create_agent(model="gpt-5.4-mini",tools=[...],middleware=[state_based_model])⭐最佳实践:模型实例在中间件外部一次性初始化,避免每次调用重复创建。
6.5 Response Format
结构化输出将非结构化文本转换为经过验证的结构化数据。
当提取特定字段或为下游系统返回数据时,自由文本是不够的。
工作原理:当你提供 schema 作为响应格式时,模型的最终响应保证符合该 schema。
代理运行模型/工具调用循环直到模型完成工具调用,然后将最终响应强制转换为提供的格式。
定义格式
Schema 定义指导模型。字段名、类型和描述明确指定了输出应遵循的格式。
frompydanticimportBaseModel,FieldclassCustomerSupportTicket(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 能根据用户偏好、对话阶段或角色进行调整——
早期返回简单格式,随着复杂度增加返回详细格式。
fromlangchain.agentsimportcreate_agentfromlangchain.agents.middlewareimportwrap_model_call,ModelRequest,ModelResponsefrompydanticimportBaseModel,FieldfromtypingimportCallableclassSimpleResponse(BaseModel):"""Simple response for early conversation."""answer:str=Field(description="A brief answer")classDetailedResponse(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_calldefstate_based_output(request:ModelRequest,handler:Callable[[ModelRequest],ModelResponse])->ModelResponse:"""Select output format based on State."""# request.messages 是 request.state["messages"] 的快捷方式message_count=len(request.messages)ifmessage_count<3:# 对话早期 - 使用简单格式request=request.override(response_format=SimpleResponse)else:# 已建立的对话 - 使用详细格式request=request.override(response_format=DetailedResponse)returnhandler(request)agent=create_agent(model="gpt-5.5",tools=[...],middleware=[state_based_output])七、工具上下文(Tool context)
工具的特殊之处在于它们既读又写上下文。
在最基本的情况下,工具执行时接收 LLM 的请求参数并返回一个工具消息。工具完成工作并产生结果。
工具还可以为模型获取重要信息,使其能够执行和完成任务。
Reads(读)
大多数现实世界的工具需要的不只是 LLM 的参数:
数据库查询需要用户 ID、外部服务需要 API 密钥、决策需要当前会话状态。
工具从state、store 和 runtime context读取这些信息。
fromlangchain.toolsimporttool,ToolRuntimefromlangchain.agentsimportcreate_agent@tooldefcheck_authentication(runtime:ToolRuntime)->str:"""Check if user is authenticated."""# 从 State 读取:检查当前认证状态current_state=runtime.state is_authenticated=current_state.get("authenticated",False)ifis_authenticated:return"User is authenticated"else:return"User is not authenticated"agent=create_agent(model="gpt-5.5",tools=[check_authentication])Writes(写)
工具结果可用于帮助代理完成给定任务。
工具可以既直接向模型返回结果,又更新代理的记忆,使重要上下文对未来的步骤可用。
fromlangchain.toolsimporttool,ToolRuntimefromlangchain.agentsimportcreate_agentfromlanggraph.typesimportCommand@tooldefauthenticate_user(password:str,runtime:ToolRuntime)->Command:"""Authenticate user and update State."""# 执行认证(简化)ifpassword=="correct":# 写入 State:使用 Command 标记为已认证returnCommand(update={"authenticated":True},)else:returnCommand(update={"authenticated":False})agent=create_agent(model="gpt-5.5",tools=[authenticate_user])参见 Tools 获取在工具中访问 state、store 和 runtime context 的全面示例。
八、生命周期上下文(Life-cycle context)
控制核心代理步骤之间发生的事情—— 拦截数据流以实现横切关注点,
如摘要(summarization)、护栏(guardrails)和日志记录(logging)。
中间件是让上下文工程切实可行的机制。Middleware 允许你钩入代理生命周期的任何步骤并:
- 更新上下文—— 修改 state 和 store 以持久化更改、更新对话历史或保存洞察
- 在生命周期中跳转—— 根据上下文移动到代理循环中的不同步骤
(例如,满足条件时跳过工具执行、使用修改后的上下文重复模型调用)
代理循环中的中间件钩子
┌─────────────────────────────────────────────────────────┐ │ 代理循环 │ │ │ │ before_model ┌───────────┐ after_model │ │ ───────────────────► │ Model │ ───────────────────► │ │ │ Call │ │ │ └─────┬─────┘ │ │ │ 工具调用请求 │ │ ▼ │ │ ┌──────────────────┐ │ │ wrap_tool_call │ Tool Execution │ │ │ ─────────────────►│ (工具执行) │ │ │ └────────┬─────────┘ │ │ │ 工具结果 │ │ └──────► 回到模型调用 │ └─────────────────────────────────────────────────────────┘ wrap_model_call ──► 包裹整个模型调用(可重写 request / 跳转到别的步骤) dynamic_prompt ──► 动态生成 system prompt常用钩子:
| 钩子 | 时机 | 典型用途 |
|---|---|---|
@before_model | 模型调用前 | 裁剪消息、注入上下文 |
@after_model | 模型调用后 | 校验/删除敏感内容 |
@wrap_model_call | 包裹模型调用 | 换模型、过滤工具、注入消息、改 response_format |
@wrap_tool_call | 包裹工具调用 | 工具错误兜底、改写工具返回 |
@dynamic_prompt | 生成提示词时 | 基于 state/context 动态 system prompt |
示例:摘要(Summarization)
最常见的生命周期模式之一是当对话记录过长时自动压缩。
⭐ 与「模型上下文」中所示的瞬态消息修剪不同,摘要会持久更新状态——
永久替换旧消息,用保存到未来所有回合的摘要。
LangChain 提供了内置的中间件来实现这一点:
fromlangchain.agentsimportcreate_agentfromlangchain.agents.middlewareimportSummarizationMiddleware agent=create_agent(model="gpt-5.5",tools=[...],middleware=[SummarizationMiddleware(model="gpt-5.4-mini",trigger={"tokens":4000},keep=("messages",20),),],)九、速查表
request.override()可覆盖的维度
| 参数 | 效果 | 上下文类型 |
|---|---|---|
messages= | 改发送给模型的消息 | 瞬态 |
tools= | 过滤/替换工具集 | 瞬态 |
model= | 切换模型 | 瞬态 |
response_format= | 切换输出 schema | 瞬态 |
⭐ 这四个都通过
request.override(...)实现,是模型上下文工程的核心手段。
中间件钩子速查
fromlangchain.agents.middlewareimport(before_model,# 模型调用前:持久更新 stateafter_model,# 模型调用后:持久更新 statewrap_model_call,# 包裹模型调用:瞬态重写 requestwrap_tool_call,# 包裹工具调用:处理工具错误/结果dynamic_prompt,# 动态 system promptSummarizationMiddleware,# 内置:自动摘要)三大数据源访问方式
| 场景 | 访问方式 |
|---|---|
| 中间件中读 State | request.state/request.state["messages"]/request.messages |
| 工具中读 State | runtime.state |
| 工具中读 Store | runtime.store |
| 工具中读 Context | runtime.context |
| 工具中写 State | 返回Command(update={...}) |
十、要点小结
- 代理失败多数不是模型能力问题,而是"正确的上下文没有传给 LLM"——上下文工程是 AI 工程师的首要任务。
- 代理循环 =模型调用+工具执行,循环持续到 LLM 决定完成。
- 三类上下文:模型上下文(瞬态)、工具上下文(持久)、生命周期上下文(持久)。
- 三大数据源:Runtime Context(静态配置)、State(短期记忆)、Store(长期记忆/跨对话)。
- Middleware 是上下文工程的落地机制:更新上下文 + 在生命周期中跳转。
- ⭐瞬态 vs 持久:
wrap_model_call+override()只影响本次调用;
要持久修改需用before_model/after_model或返回Command的ExtendedModelResponse。 - 工具的名/描述/参数描述不是元数据,而是引导模型推理的指令;
parse_docstring=True可自动解析 Google 风格 docstring。 - 工具是唯一既能读又能写上下文的组件:靠
ToolRuntime读写 State/Store/Context。 - 动态化的四个维度都可用
request.override():
工具集(按权限过滤)、模型(按对话长度/成本)、消息(注入文件上下文)、输出格式(按对话阶段)。 - ⭐摘要 vs 修剪:修剪是瞬态的(只影响本次调用);摘要是持久的(永久改写 state,影响未来所有回合)。
- 模型实例应在中间件外部一次性初始化,避免重复开销。
- 响应格式在代理完成所有工具调用后,才把最终响应强制转换为指定 schema。