LangChain v1 Agent 核心能力详解:从模型调用到可控执行
大语言模型擅长理解和生成文本,但一个真正可用的智能体还需要具备更多能力:根据任务选择合适的模型,调用外部工具,维护会话状态,在高风险操作前请求人工确认,并将执行过程实时反馈给用户。
LangChain v1 将这些能力统一到了 create_agent 接口及中间件体系中。开发者可以先用少量代码创建一个基本 Agent,再通过模型路由、动态工具、结构化输出、自定义状态、人机协作和流式传输逐步增强它。
本文将围绕这些核心能力展开,并给出相应的实现方式与工程建议。
从一个最小 Agent 开始
一个 Agent 通常由三部分组成:
- 模型:负责理解、推理与决策。
- 工具:负责搜索、查询、计算、写入等具体操作。
- 系统提示词:定义角色、目标和行为边界。
python
from langchain.agents import create_agent
from langchain.tools import tool
@tool
def get_weather(city: str) -> str:
"""查询指定城市的天气。"""
return f"{city}天气晴朗。"
agent = create_agent(
model="openai:gpt-4o-mini",
tools=[get_weather],
system_prompt="你是一名简洁、准确的天气助手。",
)
result = agent.invoke(
{"messages": [{"role": "user", "content": "上海天气如何?"}]}
)
print(result["messages"][-1].content)
这个例子虽然简单,却已经形成了完整的 Agent 工作链路:模型理解问题,判断是否需要调用工具,读取工具结果,再组织最终回答。
模型配置:从固定调用到动态路由
模型是 Agent 的推理引擎。实际项目既可以固定使用一个模型,也可以根据任务复杂度、用户等级、成本预算或对话状态动态选择模型。
静态模型
最直接的方式是在创建 Agent 时指定模型标识符:
python
agent = create_agent(
model="openai:gpt-4o-mini",
tools=[get_weather],
)
如果需要控制温度、最大输出长度和超时时间,可以传入模型实例:
python
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
model="gpt-4o-mini",
temperature=0.1,
max_tokens=1000,
timeout=30,
)
agent = create_agent(model=model, tools=[get_weather])
静态模型配置简单、行为稳定,适合任务类型较单一的应用。
动态模型
当简单任务和复杂任务共用一个 Agent 时,可以通过 @wrap_model_call 在运行时替换模型。下面的示例根据消息数量进行路由:短对话使用轻量模型,长对话切换到能力更强的模型。
python
from langchain.agents.middleware import (
ModelRequest,
ModelResponse,
wrap_model_call,
)
from langchain_openai import ChatOpenAI
fast_model = ChatOpenAI(model="gpt-4o-mini")
strong_model = ChatOpenAI(model="gpt-4o")
@wrap_model_call
def select_model(request: ModelRequest, handler) -> ModelResponse:
message_count = len(request.state["messages"])
selected = strong_model if message_count > 4 else fast_model
return handler(request.override(model=selected))
agent = create_agent(
model=fast_model,
tools=[get_weather],
middleware=[select_model],
)
动态模型的价值不只是"自动切换更强模型",而是建立一套可观测的路由规则。例如:
- 普通问答使用低成本模型,复杂分析使用高能力模型。
- 免费用户和付费用户使用不同模型。
- 接近上下文限制时切换到支持更长上下文的模型。
- 高风险任务使用稳定性更高的模型,并降低随机性。
路由条件应尽量明确、可测试。仅根据消息数量判断复杂度适合演示,生产环境更适合结合任务类型、Token 数量、权限和历史执行结果。
工具体系:让 Agent 从"会说"变成"会做"
工具赋予 Agent 与外部世界交互的能力。它可以查询数据库、调用业务接口、搜索资料、发送邮件或执行计算。LangChain Agent 支持串行调用、并行调用、按前序结果继续选择工具,以及在多次工具调用之间保持状态。
静态注册工具
对于工具集合固定的应用,可以直接在创建 Agent 时注册:
python
from langchain.tools import tool
@tool
def search_product(keyword: str) -> str:
"""按照关键词搜索商品。"""
return f"已找到与 {keyword} 相关的商品。"
@tool
def check_inventory(product_id: str) -> str:
"""查询指定商品的库存。"""
return f"商品 {product_id} 当前库存为 10。"
agent = create_agent(
model="openai:gpt-4o-mini",
tools=[search_product, check_inventory],
)
如果传入空工具列表,Agent 会退化为只有模型节点的对话应用,不再具备外部操作能力。
根据状态动态筛选工具
工具并不是越多越好。大量无关工具会增加模型选择难度,也可能让未授权用户获得不应使用的能力。一个常见做法是在运行时根据认证状态或角色筛选工具。
python
from langchain.agents import AgentState
from langchain.agents.middleware import ModelRequest, ModelResponse, wrap_model_call
from langchain.tools import tool
@tool
def public_search(query: str) -> str:
"""查询公开信息。"""
return f"公开结果:{query}"
@tool
def private_search(query: str) -> str:
"""查询仅限认证用户访问的数据。"""
return f"私有结果:{query}"
class AuthState(AgentState):
authenticated: bool
@wrap_model_call(state_schema=AuthState)
def filter_tools(request: ModelRequest, handler) -> ModelResponse:
if not request.state.get("authenticated", False):
allowed = [
tool for tool in request.tools
if tool.name.startswith("public_")
]
request = request.override(tools=allowed)
return handler(request)
agent = create_agent(
model="openai:gpt-4o-mini",
tools=[public_search, private_search],
middleware=[filter_tools],
)
筛选条件除了来自 state,还可以来自运行时 context 或持久化 store。因此,权限、租户、功能开关和业务阶段都可以参与工具选择。
需要特别注意:动态隐藏工具只是一层 Agent 能力约束,不能代替后端授权。真正敏感的接口仍然必须在工具实现内部执行身份校验和权限检查。
运行时加入新工具
有些工具只在特定阶段出现,甚至不需要在创建 Agent 时注册。此时可以用类中间件同时处理模型可见工具和工具执行逻辑。
python
from langchain.agents.middleware import (
AgentMiddleware,
ModelRequest,
ToolCallRequest,
)
from langchain.tools import tool
@tool
def calculate_tip(
bill_amount: float,
tip_percentage: float = 20.0,
) -> str:
"""计算账单小费与总金额。"""
tip = bill_amount * tip_percentage / 100
total = bill_amount + tip
return f"小费 {tip:.2f} 元,总计 {total:.2f} 元。"
class DynamicToolMiddleware(AgentMiddleware):
def wrap_model_call(self, request: ModelRequest, handler):
updated = request.override(
tools=[*request.tools, calculate_tip]
)
return handler(updated)
def wrap_tool_call(self, request: ToolCallRequest, handler):
if request.tool_call["name"] == "calculate_tip":
request = request.override(tool=calculate_tip)
return handler(request)
动态加入工具时,必须同时解决两个问题:模型需要看到工具定义,执行阶段也需要找到对应工具。只完成前者会导致模型生成了工具调用,但运行时无法执行。
工具错误处理
外部接口超时、参数不合法或服务不可用都可能导致工具失败。不要让异常直接终止整个 Agent,可以将错误转换为 ToolMessage 返回给模型,让模型修正参数、选择替代工具或向用户解释问题。
python
from langchain.agents.middleware import wrap_tool_call
from langchain.messages import ToolMessage
@wrap_tool_call
def handle_tool_errors(request, handler):
try:
return handler(request)
except Exception as exc:
return ToolMessage(
content=f"工具执行失败,请检查输入后重试:{exc}",
tool_call_id=request.tool_call["id"],
)
生产环境不建议把完整异常、堆栈或内部地址直接暴露给模型和用户。更稳妥的方式是返回经过分类的错误码与简洁说明,同时将完整异常写入服务端日志。
ReAct:Agent 如何连续解决任务
Agent 调用工具不是一次性的函数跳转,而是一个"推理、行动、观察、再推理"的循环,也就是 ReAct 模式。
以"找出热门无线耳机并确认库存"为例,执行过程可能是:
- 模型判断商品热度属于实时信息,调用商品搜索工具。
- 搜索工具返回排名最高的商品型号。
- 模型发现还缺少库存信息,继续调用库存查询工具。
- 工具返回库存数量,模型据此生成最终答复。
这种循环让 Agent 可以把复杂目标拆成多个可执行步骤,并根据每一步的观察结果决定下一步动作。与此同时,它也带来了新的工程问题:如何限制最大循环次数、如何处理工具失败、如何避免重复调用,以及哪些动作必须由人工确认。
提示词:静态规则与动态上下文
系统提示词定义 Agent 的角色、表达方式和决策边界。
静态系统提示词
python
agent = create_agent(
model="openai:gpt-4o-mini",
tools=[get_weather],
system_prompt=(
"你是一名企业客服助手。回答应简洁、准确;"
"无法确认的信息必须明确说明,不得编造。"
),
)
静态提示词适合全局不变的规则,例如品牌语气、安全边界和固定输出要求。
动态系统提示词
同一个问题面对初学者和专家时,回答方式应当不同。@dynamic_prompt 可以根据运行时上下文生成提示词:
python
from typing import TypedDict
from langchain.agents.middleware import ModelRequest, dynamic_prompt
class UserContext(TypedDict):
user_role: str
@dynamic_prompt
def role_prompt(request: ModelRequest) -> str:
role = request.runtime.context.get("user_role", "初学者")
base = "你是一名严谨的技术助手。"
if role == "专家":
return f"{base} 给出技术细节、限制条件和工程权衡。"
return f"{base} 使用易懂语言解释,减少不必要的术语。"
agent = create_agent(
model="openai:gpt-4o-mini",
tools=[],
middleware=[role_prompt],
context_schema=UserContext,
)
动态提示词适合处理用户角色、地区、语言、业务阶段和个性化偏好,但不应塞入大量与当前任务无关的信息。上下文越长,成本越高,关键规则也越容易被稀释。
结构化输出:让结果直接进入业务流程
自然语言适合展示给人,但系统间协作更需要稳定的数据结构。LangChain 可以通过 response_format 将 Agent 输出约束为 Pydantic 模型。
python
from pydantic import BaseModel
class ContactInfo(BaseModel):
name: str
email: str
phone: str
结构化输出主要有两种策略:
| 策略 | 实现方式 | 适用场景 |
|---|---|---|
ProviderStrategy |
使用模型提供商的原生结构化输出 | 模型原生支持时优先使用,可靠且高效 |
ToolStrategy |
用工具调用参数承载结构化数据 | 适用于支持工具调用但缺少原生结构化输出的模型 |
使用 ToolStrategy:
python
from langchain.agents.structured_output import ToolStrategy
agent = create_agent(
model="openai:gpt-4o-mini",
tools=[],
response_format=ToolStrategy(ContactInfo),
)
使用 ProviderStrategy:
python
from langchain.agents.structured_output import ProviderStrategy
agent = create_agent(
model="openai:gpt-4o",
tools=[],
response_format=ProviderStrategy(ContactInfo),
)
LangChain v1 还支持直接传入 Pydantic 模型:
python
agent = create_agent(
model="openai:gpt-4.1",
response_format=ContactInfo,
)
此时框架会优先尝试原生结构化输出,不支持时再回退到工具策略。最终结果可从 result["structured_response"] 中读取。
需要注意,预先执行过 bind_tools 的模型不能直接与这种结构化输出方式组合。动态模型路由也应传入尚未预绑定工具的模型实例。
自定义 State:为当前会话增加短期记忆
Agent 默认会在 messages 中维护对话历史。但用户偏好、临时标志、中间结果和认证状态并不适合全部塞入聊天消息,此时可以扩展 State。
自定义状态需要继承 AgentState,并使用 TypedDict 风格声明。在 LangChain v1 中,推荐通过中间件绑定状态结构,使状态与相关逻辑保持在同一作用域。
python
from typing import Any
from langchain.agents import AgentState, create_agent
from langchain.agents.middleware import AgentMiddleware
class PreferenceState(AgentState):
user_preferences: dict
class PreferenceMiddleware(AgentMiddleware):
state_schema = PreferenceState
def before_model(
self,
state: PreferenceState,
runtime,
) -> dict[str, Any] | None:
preferences = state.get("user_preferences", {})
print(f"当前偏好:{preferences}")
return None
agent = create_agent(
model="openai:gpt-4o-mini",
tools=[],
middleware=[PreferenceMiddleware()],
)
result = agent.invoke(
{
"messages": [{"role": "user", "content": "解释什么是大模型"}],
"user_preferences": {
"style": "技术性",
"verbosity": "详细",
},
}
)
也可以通过 state_schema 参数直接传入状态类型,但这种方式的作用域更宽,主要用于简单场景或兼容既有代码。新项目中,将状态定义放到对应中间件通常更清晰。
State 表示当前会话生命周期内的短期状态。如果需要跨会话长期保存用户信息,应使用持久化存储,而不是把所有数据长期堆积在 State 中。
Human-in-the-loop:在关键操作前加入人工决策
Agent 一旦拥有写文件、执行 SQL、发送邮件或修改业务数据的能力,就不能只依赖模型自行判断。Human-in-the-loop,简称 HITL,可以在敏感工具执行前暂停流程,让人工审核调用参数。
人工通常可以做出三类决策:
approve:按原参数执行。edit:修改工具名称或参数后执行。reject:拒绝操作,并将原因反馈给 Agent。
下面的配置对写文件操作开放全部决策,对 SQL 操作只允许批准或拒绝,而只读工具自动放行。
python
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langgraph.checkpoint.memory import InMemorySaver
agent = create_agent(
model="openai:gpt-4.1",
tools=[write_file, execute_sql, read_data],
middleware=[
HumanInTheLoopMiddleware(
interrupt_on={
"write_file": True,
"execute_sql": {
"allowed_decisions": ["approve", "reject"]
},
"read_data": False,
},
description_prefix="该操作需要人工确认",
)
],
checkpointer=InMemorySaver(),
)
HITL 依赖检查点保存和恢复执行状态。调用时还需要提供稳定的 thread_id:
python
config = {"configurable": {"thread_id": "task-123"}}
result = agent.invoke(
{
"messages": [
{
"role": "user",
"content": "删除 data 表中 id 为 1 的记录",
}
]
},
config=config,
version="v2",
)
print(result.interrupts)
审核通过后,使用相同的 thread_id 恢复:
python
from langgraph.types import Command
result = agent.invoke(
Command(resume={"decisions": [{"type": "approve"}]}),
config=config,
version="v2",
)
拒绝操作时可以附带原因,编辑操作时可以提供新的工具参数。多个工具同时进入审核时,决策顺序必须与中断请求中的动作顺序一致。
内存检查点适合本地开发。生产环境应使用数据库支持的持久化检查点,保证服务重启后仍能恢复等待审核的任务。
流式传输:让执行过程可见
Agent 可能经历模型推理、工具调用、人工审核和再次推理。若用户只能等待最终结果,体验会显得迟钝,也难以判断系统当前状态。LangChain 提供三种主要流模式:
| 模式 | 输出内容 | 典型用途 |
|---|---|---|
updates |
每个 Agent 步骤结束后的状态更新 | 展示模型、工具和中断节点的进度 |
messages |
模型生成的增量消息块 | 实现逐字输出、显示工具参数片段 |
custom |
应用主动发送的自定义数据 | 展示查询进度、中间计算和业务状态 |
使用 updates 展示执行步骤
python
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "上海天气如何?"}]},
stream_mode="updates",
version="v2",
):
if chunk["type"] == "updates":
for step, data in chunk["data"].items():
print("步骤:", step)
print("内容:", data["messages"][-1].content_blocks)
输出通常会依次出现模型发起工具调用、工具返回结果、模型生成最终答案等状态。
使用 messages 实现 Token 级输出
python
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "上海天气如何?"}]},
stream_mode="messages",
version="v2",
):
if chunk["type"] == "messages":
token, metadata = chunk["data"]
print(metadata["langgraph_node"], token.content_blocks)
messages 不只会输出最终文本,也可能包含工具调用参数的增量 JSON 片段。前端渲染时应根据内容块类型分别处理文本、推理信息和工具调用数据。
使用 custom 汇报业务进度
工具内部可以通过 get_stream_writer() 主动发送进度:
python
from langgraph.config import get_stream_writer
def get_weather(city: str) -> str:
"""查询天气并报告进度。"""
writer = get_stream_writer()
writer(f"正在连接 {city} 的天气服务...")
writer(f"已经获取 {city} 的天气数据。")
return f"{city}天气晴朗。"
消费自定义数据:
python
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "上海天气如何?"}]},
stream_mode="custom",
version="v2",
):
if chunk["type"] == "custom":
print(chunk["data"])
组合多种流模式
一个完整界面通常同时需要步骤进度、模型文本和业务通知,可以把多种模式一起传入:
python
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "上海天气如何?"}]},
stream_mode=["messages", "updates", "custom"],
version="v2",
):
mode = chunk["type"]
payload = chunk["data"]
print(mode, payload)
version="v2" 提供统一的 type、data 和命名空间格式,需要 LangGraph 1.1 或更高版本。旧格式在组合模式下通常返回 (mode, data) 元组,消费代码更容易分支混乱。
流式处理与人工审核结合
在流式执行中,HITL 中断会出现在 updates 模式的 __interrupt__ 节点。应用可以暂停输出,展示待审核工具和参数,收集人工决策,再通过 Command(resume=...) 使用同一 thread_id 恢复流式执行。
这使前端可以形成完整交互:
- 实时显示模型准备调用哪个工具。
- 敏感操作出现时弹出审核界面。
- 用户批准、修改或拒绝。
- Agent 从检查点继续运行并输出结果。
流式展示工具调用
messages 模式中的 tool_call_chunks 适合实时展示参数生成过程,updates 模式中的 tool_calls 则包含模型节点完成后的完整解析结果。需要将工具调用写入日志、执行审批或驱动后续业务逻辑时,应以完整解析结果为准。
对于支持推理输出的模型,还可以从 content_blocks 中读取类型为 reasoning 的内容块。不同模型提供商的配置方式可能不同,应用应把这类内容作为可选能力,并避免假设所有模型都会返回相同格式或完整内部推理。
工程落地建议
将这些能力放在一起后,一个可靠的 Agent 系统通常应遵循以下原则。
能力最小化
只向模型暴露当前任务需要的工具。根据权限、租户和业务阶段动态筛选工具,同时在工具服务端再次执行授权校验。
高风险操作默认中断
读取类操作可以自动执行,写入、删除、转账、发送等不可逆操作应进入人工审核。允许 edit 还是只允许 approve 与 reject,应根据操作风险决定。
错误可恢复
把工具异常转换为模型能够理解的结果,设置合理的超时和重试上限,并避免把内部异常细节直接暴露给最终用户。
状态边界清晰
对话消息、会话短期状态、运行时上下文和长期存储承担不同职责。不要用单一状态对象承载所有信息,也不要让无关中间件随意修改共享字段。
输出面向系统设计
需要进入数据库、审批流或前端组件的数据,应优先使用结构化输出。自然语言可以作为展示层,结构化对象才是更稳定的系统接口。
全流程可观测
同时记录模型选择、工具调用、耗时、错误、中断决策和最终状态。流式输出负责改善用户体验,服务端日志和链路追踪负责定位问题,两者不能互相替代。
总结
LangChain v1 的 Agent 并不只是一个"模型加工具"的封装。它提供了一套完整的运行时机制:
- 静态或动态选择模型,在成本、速度和能力之间做路由。
- 静态注册、动态筛选或临时加入工具,并对调用错误进行恢复。
- 根据用户和业务上下文动态生成系统提示词。
- 使用结构化输出把模型结果接入稳定的业务接口。
- 通过 State 保存会话内的额外信息。
- 在高风险工具调用前暂停执行,交由人工批准、编辑或拒绝。
- 通过多种流模式展示模型输出、执行进度和自定义业务状态。
真正决定 Agent 是否能进入生产环境的,不只是模型回答得是否聪明,而是整个执行过程是否有边界、可恢复、可审核、可观察。围绕这些能力建立清晰的控制面,才能让 Agent 从演示代码成长为可靠的应用系统。