文章目录
-
- [1. 指定模型](#1. 指定模型)
-
- [1.1 静态模型](#1.1 静态模型)
- [1.2 动态模型](#1.2 动态模型)
- [2. 指定工具](#2. 指定工具)
-
- [2.1 静态工具](#2.1 静态工具)
- [2.2 动态工具](#2.2 动态工具)
-
- [2.2.1 运行时,根据条件动态选择预先注册好的工具](#2.2.1 运行时,根据条件动态选择预先注册好的工具)
- [2.2.2 运行时,动态加入新工具](#2.2.2 运行时,动态加入新工具)
- [2.3 工具错误处理](#2.3 工具错误处理)
- [3. 指定提示词](#3. 指定提示词)
-
- [3.1 静态系统提示](#3.1 静态系统提示)
- [3.2 动态系统提示](#3.2 动态系统提示)
- [4. 结构化输出](#4. 结构化输出)
-
- [4.1 说明](#4.1 说明)
- [4.2 默认行为](#4.2 默认行为)
- [5. 定义State](#5. 定义State)
-
- [5.1 通过 Middleware 定义](#5.1 通过 Middleware 定义)
- [5.2 通过 state_schema 参数定义](#5.2 通过 state_schema 参数定义)
- [6. 人机协作(Human-in-the-loop)](#6. 人机协作(Human-in-the-loop))
-
- [6.1 三种中断决策类型](#6.1 三种中断决策类型)
- [6.2 配置与响应中断](#6.2 配置与响应中断)
-
- [6.2.1 添加中间件与检查点](#6.2.1 添加中间件与检查点)
- [6.2.2 响应中断](#6.2.2 响应中断)
- [7. 支持的流模式](#7. 支持的流模式)
-
- [7.1 模式一:updates - 流式传输代理进度](#7.1 模式一:updates - 流式传输代理进度)
- [7.2 模式二:messages - 流式传输LLM Token](#7.2 模式二:messages - 流式传输LLM Token)
- [7.3 模式三:custom - 流式传输自定义更新](#7.3 模式三:custom - 流式传输自定义更新)
- [7.4 模式四:组合模式 - 同时使用多种流模式](#7.4 模式四:组合模式 - 同时使用多种流模式)
- [7.5 模式实践](#7.5 模式实践)
-
- [7.5.1 流式传输推理 / 思考 Token](#7.5.1 流式传输推理 / 思考 Token)
- [7.5.2 流式传输工具调用](#7.5.2 流式传输工具调用)
- [7.5.3 流式传输与人机协作 (Human-in-the-Loop)](#7.5.3 流式传输与人机协作 (Human-in-the-Loop))
1. 指定模型
模型是Agent的推理引擎,其配置方式分为静态模型和动态模型两种。
1.1 静态模型
在创建Agent时一次性配置,执行期间保持不变。有两种指定方式:
- 使用模型标识符字符串(最直接):
python
agent = create_agent("openai:gpt-4o-mini", tools=tools)
支持自动推断(如 "gpt-5" 自动映射为 "openai:gpt-5")。
- 使用模型实例(更精细控制):
python
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
model="gpt-5.4",
temperature=0,
api_key=os.getenv("PACKYAPI_API_KEY"),
base_url="https://www.packyapi.com/v1",
)
agent = create_agent(model, tools=tools)
适合设置 temperature、max_tokens、timeout 等特定参数。
1.2 动态模型
在运行时根据当前状态或上下文动态选择模型。通过中间件(middleware)配合 @wrap_model_call 装饰器实现:
python
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from langchain.tools import tool
from langchain_openai import ChatOpenAI
model1 = ChatOpenAI(
model="gpt-5.4",
temperature=0,
api_key=os.getenv("PACKYAPI_API_KEY"),
base_url="https://www.packyapi.com/v1",
)
model2 = ChatOpenAI(
model="gpt-5.5",
temperature=0,
api_key=os.getenv("PACKYAPI_API_KEY"),
base_url="https://www.packyapi.com/v1",
)
@tool
def get_weather_for_location(city: str) -> str:
"""获取指定城市的天气信息"""
return f"{city}的天气是晴天,温度25摄氏度。"
@wrap_model_call
# request为发送给模型的完整请求对象,handler为可调用函数,负责继续执行后面中间件,最终真正调用模型
def retry_model(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
"""根据消息数量来选择模型"""
count=len(request.state["messages"])
if count < 5:
final_model = model1
else:
final_model = model2
return handler(request.override(model=final_model)) # 动态选择进行调用
# 构造agent
agent=create_agent(
model=model1,
tools=[get_weather_for_location],
middleware=[retry_model],
)
print(
agent.invoke(
{
"messages": [
{
"role": "user",
"content": "What is the weather in Beijing?"
}
]
}
)
)
打印:
plain
{'messages': [HumanMessage(content='What is the weather in Beijing?', additional_kwargs={}, response_metadata={}, id='ea5f09d3-e982-408a-b6ce-939cc3f234af'), AIMessage(content='', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 21, 'prompt_tokens': 4427, 'total_tokens': 4448, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_provider': 'openai', 'model_name': 'gpt-5.4', 'system_fingerprint': None, 'id': 'chatcmpl-adbbb889-71aa-4112-9895-ba118', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--019fa6cc-963a-7b42-a1c2-4c73caf7d3cd-0', tool_calls=[{'name': 'get_weather_for_location', 'args': {'city': 'Beijing'}, 'id': 'call_64T5LpfKwgHwcDFzwm3lw523', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 4427, 'output_tokens': 21, 'total_tokens': 4448, 'input_token_details': {}, 'output_token_details': {}}), ToolMessage(content='Beijing的天气是晴天,温度25摄氏度。', name='get_weather_for_location', id='0518d319-0742-4e87-847a-eb9bd10ae3db', tool_call_id='call_64T5LpfKwgHwcDFzwm3lw523'), AIMessage(content='北京现在是晴天,气温 `25°C`。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 4476, 'total_tokens': 4493, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_provider': 'openai', 'model_name': 'gpt-5.4', 'system_fingerprint': None, 'id': 'chatcmpl-f2af79d1-db7f-44e5-8df9-50eb4', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--019fa6cc-9e54-7510-80de-7df6dfa58215-0', tool_calls=[], invalid_tool_calls=[], usage_metadata={'input_tokens': 4476, 'output_tokens': 17, 'total_tokens': 4493, 'input_token_details': {}, 'output_token_details': {}})]}
2. 指定工具
工具赋予Agent执行具体操作的能力。Agent不仅支持模型直接调用工具,还提供以下增强功能:
- 顺序多工具调用(一次提示词触发多次工具调用)
- 并行工具调用(适用时同时执行)
- 基于前序结果动态选择工具
- 工具重试逻辑与错误处理
- 跨工具调用的状态持久化
2.1 静态工具
在创建Agent时通过 tools 参数传入工具列表,执行期间保持不变。工具可以定义为:
- 普通Python函数
- 使用
@tool装饰器(可自定义名称、描述、参数schema等)
python
from langchain.tools import tool
from langchain.agents import create_agent
@tool
def get_weather_for_location(city: str) -> str:
"""获取指定城市的天气信息。"""
return f"在{city}总是阳光明媚!"
# 定义 agent
agent = create_agent(
model="gpt-o-mini",
tools=[get_weather_for_location],
system_prompt="你是一位乐于助人的助手。",
)
若提供空列表,Agent将仅包含一个不带工具调用能力的LLM节点。
2.2 动态工具
静态工具适用于大多数场景,但在运行时,有些场景需要灵活调整工具集如:
- 根据认证状态、用户权限、功能开关或对话阶段,再决定哪些工具可用。
- 避免工具过多导致模型上下文过载或出错,同时避免工具过少限制能力。
这就需要用到动态工具,动态工具有两种实现方式:
- 运行时,根据条件动态选择预先注册好的工具
- 运行时,动态加入新工具
2.2.1 运行时,根据条件动态选择预先注册好的工具
python
from typing import Callable
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from langchain.tools import tool
@tool
def public_search(query: str) -> str:
"""公开搜索: 无需认证即可使用,返回基础信息。"""
print(f"[公开搜索结果] 关于 '{query}' 的基础信息: 这是公开可获取的内容。")
return f"[公开搜索结果] 关于 '{query}' 的基础信息: 这是公开可获取的内容。"
@tool
def private_search(query: str) -> str:
"""私有搜索: 仅已认证用户可用,返回敏感或个性化数据。"""
print(f"[私有搜索结果] 关于 '{query}' 的私密数据: 仅限认证用户查看。")
return f"[私有搜索结果] 关于 '{query}' 的私密数据: 仅限认证用户查看。"
@tool
def advanced_search(query: str) -> str:
"""高级搜索: 提供深度分析。"""
print(f"[高级搜索结果] 关于 '{query}' 的深度分析报告: 包含详细统计和趋势。")
return f"[高级搜索结果] 关于 '{query}' 的深度分析报告: 包含详细统计和趋势。"
class State(AgentState):
authenticated: bool
@wrap_model_call(state_schema=State)
def state_based_tools(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse]
) -> ModelResponse:
"""基于对话状态的过滤工具。"""
# 获取状态: 检查用户是否已认证(举例)
state = request.state
is_authenticated = state.get("authenticated", False)
# 未认证用户只能使用以 "public_" 开头的工具
if not is_authenticated:
tools = [
t for t in request.tools
if t.name.startswith("public_")
]
request = request.override(tools=tools)
else:
# 其他条件
pass
return handler(request)
# 定义 agent
agent = create_agent(
model=model1,
tools=[public_search,private_search,advanced_search],
system_prompt="你是一位乐于助人的客服助手。根据用户的问题,选择合适的工具来提供答案。",
middleware=[state_based_tools],
)
# 执行 agent
response = agent.invoke(
{
"messages": [
{
"role": "user",
"content": "北京的天气如何?"
}
],
"authenticated": False,
}
)
运行代码后,根据过滤条件,会打印 public_search 中的日志。除了可以从state中获取数据进行过滤,还可以从 store(request.runtime.store)、context(request.runtime.context)中获取并基于获取到的数据进行筛选。
2.2.2 运行时,动态加入新工具
python
from langchain.tools import tool
from langchain.agents import create_agent
from langchain.agents.middleware import AgentMiddleware, ModelRequest, ToolCallRequest
from langchain.tools import tool
from langchain.agents import create_agent
from langchain.agents.middleware import (
AgentMiddleware,
ModelRequest,
ToolCallRequest
)
@tool
def get_weather_for_location(city: str) -> str:
"""获取指定城市的天气信息。"""
return f"在{city}总是阳光明媚!"
# 该工具将在运行时动态添加的工具
@tool
def calculate_tip(
bill_amount: float,
tip_percentage: float = 0.1
) -> str:
"""计算一笔账单的小费金额。"""
print("计算账单中...")
tip = bill_amount * tip_percentage
return (
f"小费: {tip:.2f}元, "
f"一共: {bill_amount + tip:.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":
try:
return handler(
request.override(
tool=calculate_tip # 真正调用工具
)
)
except Exception as e:
return ToolMessage(
content=f"工具错误: 请检查您的输入并重新尝试。({str(e)})",
tool_call_id=request.tool_call["id"]
)
return handler(request)
agent = create_agent(
model=model1,
tools=[get_weather_for_location], # 只注册天气工具
system_prompt=(
"你是一位乐于助人的客服助手。"
"根据用户的问题,必须用合适的工具来提供答案。"
),
middleware=[DynamicToolMiddleware()],
)
# agent 可以同时使用这两个工具
result = agent.invoke(
{
"messages": [
{
"role": "user",
"content": "计算100元的账单小费是多少"
}
]
}
)
print(result["messages"][-1].content)
2.3 工具错误处理
错误处理中间件可提高Agent的鲁棒性,避免因工具调用失败而中断流程。通过 @wrap_tool_call 装饰器创建中间件,自定义工具执行失败时的错误响应:
python
from langchain.agents import create_agent
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 e:
# 向模型返回自定义错误消息
return ToolMessage(
content=f"工具错误: 请检查您的输入并重新尝试。({str(e)})",
tool_call_id=request.tool_call["id"]
)
agent = create_agent(
model="gpt-4o-mini",
tools=[
search,
get_weather
],
middleware=[
handle_tool_errors
]
)
错误发生时,Agent会向模型返回定制的 ToolMessage,帮助模型更好地恢复。
3. 指定提示词
通过 system_prompt 参数控制Agent的初始行为,支持静态字符串和动态生成两种方式。
3.1 静态系统提示
字符串形式或SystemMessage形式:直接传入提示文本。
python
from langchain.messages import SystemMessage
agent = create_agent(
model="anthropic:claude-sonnet-4-5",
system_prompt=SystemMessage(
content=[
{
"type": "text",
"text": "你是一名负责分析文学作品的人工智能助手。"
}
]
)
)
若未提供 system_prompt,Agent会根据输入消息自行推断任务。
3.2 动态系统提示
通过 @dynamic_prompt 中间件,可以根据请求参数(如用户角色、对话阶段)动态生成系统提示。
python
from typing import TypedDict
from langchain.agents import create_agent
from langchain.agents.middleware import dynamic_prompt, ModelRequest
class Context(TypedDict):
user_role: str
@dynamic_prompt # 动态系统提示
def user_role_prompt(request: ModelRequest) -> str:
"""根据用户角色生成系统提示。"""
user_role = request.runtime.context.get("user_role","初学者")
base_prompt = "你是一位乐于助人的助手。"
if user_role == "专家":
return f"{base_prompt} 提供详尽的技术解答。"
elif user_role == "初学者":
return (
f"{base_prompt} "
"用简单易懂的语言解释概念,避免使用专业术语。"
)
return base_prompt
agent = create_agent(
model=model1,
middleware=[user_role_prompt],
context_schema=Context
)
# 系统提示将根据具体情境动态设定
result = agent.invoke(
{
"messages": [
{
"role": "user",
"content": "解释机器学习"
}
]
},
context={
"user_role": "初学者" #设置静态下上文
}
)
print(
result["messages"][-1].content
)
输出结果:
plain
机器学习,可以理解成:
**让电脑"自己从经验里学规律"**,而不是每一步都由人提前写死。
### 举个简单例子
比如你想让电脑分辨"这是猫"还是"这不是猫"。
传统做法是:
- 人告诉电脑:猫有耳朵、胡须、尾巴......
- 然后按这些规则去判断
机器学习的做法是:
- 给电脑看很多猫的图片,也给它看很多不是猫的图片
- 电脑会慢慢找到一些"共同特点"
- 以后再来一张新图片,它就能猜这是不是猫
### 它是怎么"学"的?
可以把它想成一个学生:
1. **先看很多例子**
2. **从例子里找规律**
3. **用找到的规律去判断新东西**
4. **如果判断错了,就继续调整**
### 常见用途
机器学习现在很常见,比如:
- 垃圾邮件过滤
- 短视频推荐
- 语音识别
- 人脸识别
- 商品推荐
- 自动驾驶里的部分功能
### 它和普通程序有什么不同?
普通程序像是:
- 人写好明确规则
- 电脑照着执行
机器学习像是:
- 人给很多数据和答案
- 电脑自己总结规则
### 说得再简单一点
**机器学习 = 用大量数据训练电脑,让它学会做预测或判断。**
### 一个生活化比喻
像教小朋友认水果:
- 你不给他背一大堆死规则
- 而是让他看很多苹果、香蕉、橙子
- 看多了以后,他自己就能认出来
机器学习也是这样,只不过"看"的是数据。
如果你愿意,我还可以继续用**"做菜"**或者**"教小孩"**的比喻,解释一下:
- 深度学习是什么
- 训练数据是什么
- 模型是什么
4. 结构化输出
LangChain通过 create_agent 中的 response_format 参数提供了实现此功能的策略。核心步骤:
- 使用Pydantic定义期望的输出格式(
BaseModel)。 - 在创建Agent时,将
response_format参数设置为对应的策略,并传入定义好的格式模型。
4.1 说明
LangChain提供了两种主要策略,可以根据模型的支持情况和具体需求进行选择。
| 策略 | 原理 | 适用场景 | 特点 |
|---|---|---|---|
| ToolStrategy (工具策略) | 利用模型的工具调用(Tool Calling)能力,通过创建一个"虚拟工具"来迫使模型以调用该工具参数的形式输出结构化数据。 | 任何支持工具调用的模型。当模型不支持原生结构化输出或原生输出不可靠时使用。 | 通用性强,兼容性好。 |
| ProviderStrategy (提供者策略) | 直接使用模型提供商提供的原生结构化输出功能。 | 仅限支持原生结构化输出的模型(如GPT-4O、Claude 3等)。 | 更可靠、效率更高,是首选方案。 |
示例:从一段文本中提取联系人信息(姓名、邮箱、电话)。
定义输出格式
python
from pydantic import BaseModel
class ContactInfo(BaseModel):
name: str
email: str
phone: str
使用 ToolStrategy
python
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy
# 创建Agent, 指定使用ToolStrategy
agent = create_agent(
model = "gpt-xx", # 任何支持工具调用的模型
tools=[], # 此处为简化, 可以无工具或传入其他工具
response_format=ToolStrategy(ContactInfo) # 传入ToolStrategy
)
使用 ProviderStrategy
python
from langchain.agents.structured_output import ProviderStrategy
# 创建Agent, 指定使用ProviderStrategy
agent = create_agent(
model="gpt-xx", # 必须使用支持原生结构化输出的模型
response_format=ProviderStrategy(ContactInfo) # 传入ProviderStrategy
)
4.2 默认行为
从 langchain 1.0 开始,提供了一个便捷的简化写法。可以直接将定义好的Pydantic模型传给 response_format。
python
# 简化写法: 直接传递模型
agent = create_agent(
model="gpt-.",
response_format=ContactInfo # 直接传入模型, 而不是策略对象
)
默认行为:
- 当直接传入模型(如
ContactInfo)时,LangChain会自动处理:
a. 优先尝试使用ProviderStrategy(如果模型支持原生结构化输出)。
b. 若不支持,则自动回退使用ToolStrategy。 - 这种"自动选择"的策略兼顾了便捷性和兼容性。
重要注意事项:预绑定工具(pre-bound)的模型不支持与结构化输出一起使用。如果需要动态模型选择与结构化输出结合,请确保传入中间件的模型没有预先调用 bind_tools。
5. 定义State
LangChain Agent会自动维护完整的对话历史。历史信息以消息列表(messages)的形式存储在Agent的状态中,开发者无需额外配置即可实现基本的对话上下文跟踪。
在某些场景下,仅靠对话历史不够,Agent需要记住额外的信息(例如:用户偏好、临时标志、中间结果)。这可以通过自定义状态实现。
核心概念:
- 自定义状态是Agent的短期记忆,仅在当前会话生命周期内有效。
- 自定义状态必须继承自
AgentState并定义为TypedDict(在LangChain 1.0及之后版本)。
两种实现方式:
| 方式 | 适用场景 | 特点 |
|---|---|---|
| 通过Middleware定义 | 自定义状态需要在中间件钩子或特定工具中被访问时使用。 | 推荐方式。作用域清晰,状态与相关中间件、工具绑定。 |
通过 state_schema 参数定义 |
自定义状态仅需在工具中使用,无需复杂中间件逻辑时的简化用法。 | 快速实现,但作用域较广。仅用于向后兼容,不推荐新项目使用。 |
5.1 通过 Middleware 定义
官方推荐优先使用通过Middleware定义的方式,因为它能将状态的扩展与特定中间件、工具的作用域绑定,结构更清晰。
步骤:
- 定义一个继承自
AgentState的TypedDict。 - 创建一个继承自
AgentMiddleware的类,设置state_schema为该TypedDict。
类型要求:从LangChain 1.0开始,自定义状态必须是TypedDict类型,不再支持Pydantic模型或dataclass。 - 在
create_agent时通过middleware参数传入该中间件。
代码示例:
python
from langchain.agents import AgentState, create_agent
from langchain.agents.middleware import AgentMiddleware
from typing import Any
# 定义自定义状态
class CustomState(AgentState):
user_preferences: dict
# 创建中间件,关联状态
class CustomMiddleware(AgentMiddleware):
state_schema = CustomState
tools = [] # 可选:为该中间件绑定工具
def before_model(
self,
state: CustomState,
runtime
) -> dict[str, Any] | None:
# 在模型调用前可以访问和修改 state
print(
f"User preferences: {state.get('user_preferences')}"
)
return None
# 创建 Agent 并传入中间件
agent = create_agent(
model="openai:gpt-4o-mini",
tools=[],
middleware=[
CustomMiddleware()
] # 关键:通过 middleware 注入自定义状态
)
# 调用时传入额外状态
result = agent.invoke(
{
"messages": [
{
"role": "user",
"content": "什么是大模型?"
}
],
"user_preferences": {
"style": "技术性",
"verbosity": "详细的"
},
}
)
5.2 通过 state_schema 参数定义
步骤:
- 定义一个继承自
AgentState的TypedDict。 - 在
create_agent时直接通过state_schema参数传入该类型。
代码示例:
python
from langchain.agents import AgentState, create_agent
# 定义自定义状态
class CustomState(AgentState):
user_preferences: dict
# 创建 Agent 时传入 state_schema
agent = create_agent(
model="openai:gpt-4o-mini",
tools=[], # 工具可以访问此状态
state_schema=CustomState
)
# 调用时传入额外状态
result = agent.invoke(
{
"messages": [
{
"role": "user",
"content": "什么是大模型?"
}
],
"user_preferences": {
"style": "技术性",
"verbosity": "详细的"
}
}
)
通过灵活运用自定义状态,可以为Agent赋予更丰富的短期记忆能力,使其在多轮交互中表现更智能、更贴合用户需求。
6. 人机协作(Human-in-the-loop)
Human-in-the-loop(HITL)为AI Agent添加人工审核与干预能力。HITL中间件允许在Agent执行敏感工具调用前,暂停流程并等待人工审批。HITL在自动化与风险控制之间建立平衡,适用于写文件、执行SQL等需要人工确认的操作。
其实现依赖LangGraph的持久化层(checkpointer),实现执行状态的保存与恢复。
6.1 三种中断决策类型
当Agent触发中断时,人工可做出以下三种响应(由策略配置决定哪些可用):
| 决策类型 | 说明 | 示例用例 |
|---|---|---|
| approve | 完全批准,工具按原参数执行 | 发送邮件草稿 |
| edit | 允许修改工具参数后再执行 | 修改邮件收件人后发送 |
| reject | 拒绝执行,将反馈添加到对话中 | 拒绝SQL删除操作并说明原因 |
注意:当多个工具调用同时中断时,需按顺序逐一决策;编辑参数时请保守修改,避免影响模型后续判断。
6.2 配置与响应中断
6.2.1 添加中间件与检查点
使用 HumanInTheLoopMiddleware 中间件完成人机协作,其配置选项主要包含以下两部分:
参数一:interrupt_on(必选)
一个字典,用于定义哪些工具需要触发人工中断以及允许哪些决策类型。键为工具名称(字符串),值可以是以下三种形式:
| 值类型 | 含义 | 示例 |
|---|---|---|
| True | 中断该工具,允许全部三种决策(approve、edit、reject) |
"工具1": True |
| False | 不中断,自动批准(工具调用直接执行) | "工具2": False |
| InterruptOnConfig 对象 | 精细控制:可指定允许的决策列表和自定义描述 | 自己配 |
InterruptOnConfig 对象的属性:
allowed_decisions:列表,可选值"approve"、"edit"、"reject"。决定人工可用的操作类型。description:字符串或可调用函数,用于覆盖该工具的中断提示消息。若未指定,则使用全局description_prefix拼接而成。
示例:
python
interrupt_on={
"write_file": True, # 全决策可用
"execute_sql": {"allowed_decisions": ["approve", "reject"]}, # 禁止编辑
"read_data": False, # 自动通过
}
参数二:description_prefix(可选)
字符串。作为中断消息的全局前缀,默认会在每个中断请求的描述前加上此文本。
最终消息格式:description_prefix + "\n\nTool: <tool_name>\nArgs: <arguments>"
工具级覆盖:若在 InterruptOnConfig 中指定了 description,则忽略全局前缀。
示例:
python
description_prefix="工具执行尚待批准"
中断时用户看到的消息开头为:
python
工具执行尚待批准
Tool: execute_sql
Args: {...}
通过合理配置 interrupt_on 和 description_prefix,开发者可以灵活控制哪些操作需要人工介入,以及如何向审核者展示信息。
示例如下:
python
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langgraph.checkpoint.memory import InMemorySaver
@tool
def write_file(
file_path: str,
content: str
) -> str:
"""
写入文件内容。
该操作会修改外部文件,需要人工审核。
"""
print(f"正在写入文件: {file_path}")
# 模拟写文件
return f"文件 {file_path} 写入成功"
@tool
def execute_sql(
sql: str
) -> str:
"""
执行SQL语句。
支持:
- SELECT 查询
- INSERT 插入
- UPDATE 修改
- DELETE 删除
"""
print(f"执行SQL: {sql}")
# 模拟执行SQL
return f"SQL执行成功: {sql}"
@tool
def read_data(
table_name: str
) -> str:
"""
查询数据库中的数据。
只读操作,不会修改数据。
"""
print(f"读取数据表: {table_name}")
# 模拟查询
return f"{table_name} 查询结果: 数据列表..."
agent = create_agent(
model=model1,
tools=[
write_file,
execute_sql,
read_data
],
middleware=[
HumanInTheLoopMiddleware(
interrupt_on={
"write_file": True,
# 允许 approve / edit / reject 三种决策
"execute_sql": {
"allowed_decisions": [
"approve",
"reject"
]
},
# 禁止 edit,只允许批准或拒绝
"read_data": False, # 自动批准,不触发中断
},
description_prefix="工具执行尚待批准",# 中断提示前缀
),
],
checkpointer=InMemorySaver(),
)
关键条件:
- 必须配置
checkpointer以支持中断恢复。 - 调用时需传入
thread_id以标识会话线程。 - 策略设计:对只读工具(如
read_data)可设False自动放行;对写操作建议至少配置approve和reject。
代码中 HumanInTheLoopMiddleware 配置表明:
write_file会中断,并允许批准、编辑、拒绝;execute_sql会中断,但仅允许批准或拒绝(不可编辑);read_data不中断,自动执行;- 中断提示将以"工具执行尚待批准"开头。
6.2.2 响应中断
- 触发中断后获取待审核动作:
python
config = {
"configurable": {
"thread_id": "1"
}
}
result = agent.invoke(
{
"messages": [
{
"role": "user",
"content": "删除数据库中的data表中id=的旧记录。"
}
]
},
config=config,
version="v2", # 必须使用 v2 版本获取中断信息
)
# result.interrupts 包含待审核的工具调用详情
print(result.interrupts)
输出包含 action_requests(工具名和参数)与 review_configs(允许的决策类型),如下所示:
python
# (Interrupt(value={'action_requests': [{
# 'name': 'execute_sql',
# 'args': {'sql': 'DELETE FROM data WHERE id = 123;'},
# 'description': "工具执行尚待批准\n\nTool: execute_sql\nArgs: {'sql': 'DELETE FROM data WHERE id = 123;'}"}], 'review_configs': [{'action_name': 'execute_sql', 'allowed_decisions': ['approve', 'reject']}]}, id='78c7c01b95f755b13888d95b025d8060'),)
- 提交决策继续执行:
plain
print(
agent.invoke(
Command(
resume={
"decisions": [
{
"type": "approve"
}
]
}
),
config=config,
version="v2",
)
)
输出如下:
plain
# GraphOutput(value={'messages':
# [HumanMessage(content='请调用 execute_sql 工具,执行DELETE语句,删除数据库中的data表中id=123的旧记录。', additional_kwargs={}, response_metadata={}, id='af891d1e-ca8c-49fd-a93e-bcba2e6e32f0'),
# AIMessage(content='我先直接执行删除语句,删掉 `data` 表里 `id=123` 的旧记录。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 57, 'prompt_tokens': 4532, 'total_tokens': 4589, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_provider': 'openai', 'model_name': 'gpt-5.4', 'system_fingerprint': None, 'id': 'chatcmpl-8c7d246c-32aa-424b-8147-94118', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--019fa803-122c-7610-b40d-4f1e8da0eb62-0', tool_calls=[{'name': 'execute_sql', 'args': {'sql': 'DELETE FROM data WHERE id = 123;'}, 'id': 'call_kDVD1c0GmTDbUMY0nR4oSNEf', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 4532, 'output_tokens': 57, 'total_tokens': 4589, 'input_token_details': {}, 'output_token_details': {}}),
# ToolMessage(content='SQL执行成功: DELETE FROM data WHERE id = 123;', name='execute_sql', id='8b1d30d5-2a43-41f6-9ccd-f26317010bf5', tool_call_id='call_kDVD1c0GmTDbUMY0nR4oSNEf'),
# AIMessage(content='已执行删除:`DELETE FROM data WHERE id = 123;`\n\n- 结果:删除语句执行成功\n- 目标表:`data`\n- 条件:`id = 123`\n\n如果你需要,我也可以继续帮你查询确认该记录是否已不存在。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 64, 'prompt_tokens': 4613, 'total_tokens': 4677, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_provider': 'openai', 'model_name': 'gpt-5.4', 'system_fingerprint': None, 'id': 'chatcmpl-10c50646-18e0-43d0-9476-9c683', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--019fa803-208a-7cd0-9743-e69c1b907aaa-0', tool_calls=[], invalid_tool_calls=[], usage_metadata={'input_tokens': 4613, 'output_tokens': 64, 'total_tokens': 4677, 'input_token_details': {}, 'output_token_details': {}})]}, interrupts=())
7. 支持的流模式
LangChain Agent流式系统能做什么?
- 流式传输代理进度:在每个代理步骤后获取状态更新。
- 流式传输LLM token:实时生成语言模型产生的token。
- 流式传输推理token:输出模型的内部思考过程。
- 流式传输自定义更新:在代码中定义并发出用户自定义的信号(如"已获取10/100条记录")。
- 支持多种流模式 :可根据需要选择
updates(代理进度)、messages(LLM消息块)、custom(自定义数据)。
7.1 模式一:updates - 流式传输代理进度
在每个代理步骤(如一次LLM调用、一次工具执行)完成后,流式传输完整的状态更新。
python
from langchain.agents import create_agent
def get_weather(city: str) -> str:
"""获取城市天气"""
return f"{city} 天气晴朗!"
agent = create_agent(
model="gpt-5-nano",
tools=[get_weather],
)
# 使用 stream_mode="updates"
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(f"步骤: {step}")
print(f"内容: {data['messages'][-1].content_blocks}")
输出:
步骤: model
内容: [{'type': 'tool_call', 'name': 'get_weather', 'args': {'city': '上海'}, 'id': 'call_Ayr5Q9GL7EwHHAi0vBdzZg9'}]
步骤: tools
内容: [{'type': 'text', 'text': '上海 天气晴朗!'}]
步骤: model
内容: [{'type': 'text', 'text': '上海的天气晴朗!'}]
注意:将 version="v2"(需要LangGraph >= 1.1)传递给 stream() 或 astream() 以获取统一的输出格式。每个数据块都是一个具有 type、ns 和 data 键的 StreamPart 字典
7.2 模式二:messages - 流式传输LLM Token
从任何调用了LLM的节点中,流式传输token级别的增量消息块,实现类似"逐字输出"效果。每块是一个元组 (token, metadata)。
同上,使用 stream_mode="messages"
python
# ... (代理定义与上文相同)
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "上海天气如何?"}]},
stream_mode="messages",
version="v2",
):
if chunk["type"] == "messages":
token, metadata = chunk["data"]
print(f"节点: {metadata['langgraph_node']}")
print(f"内容块: {token.content_blocks}\n")
输出解读(部分):
model节点逐块输出工具调用的JSON参数(如'{','city','":"','上海', ...`)。- 然后
tools节点输出工具执行结果。 - 最后
model节点逐块输出最终回复文本(如'上海','的','天气'...)。
7.3 模式三:custom - 流式传输自定义更新
与LangGraph用法一致,通过 get_stream_writer() 函数获取写入器,可以主动向流中推送任意自定义数据。
适用场景:报告工具执行进度(如"正在查询数据库...")、中间计算结果、调试信息等。
python
from langchain.agents import create_agent
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} 天气晴朗!"
agent = create_agent(
model="gpt-4o-mini",
tools=[get_weather],
)
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "上海天气如何?"}]},
stream_mode="custom",
version="v2",
):
if chunk["type"] == "custom":
print(chunk["data"]) # 直接输出自定义消息
输出:
正在查询 上海 的天气数据...
成功获取 上海 的天气数据。
7.4 模式四:组合模式 - 同时使用多种流模式
将模式作为列表传入,如 stream_mode=["updates", "custom"]。每次迭代返回一个包含 type(标识模式)和 data(对应负载)的字典。
示例:同时使用 updates 和 custom 模式
python
# ... (代理定义与 custom 模式示例相同)
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "上海天气如何?"}]},
stream_mode=["updates", "custom"],
version="v2",
):
print(f"流模式: {chunk['type']}")
print(f"内容: {chunk['data']}\n")
plain
model {'messages': [AIMessage(content='', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 20, 'prompt_tokens': 76, 'total_tokens': 96, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_provider': 'openai', 'model_name': 'gpt-5.4', 'system_fingerprint': None, 'id': 'chatcmpl-aebb1d63-403a-4379-9e3b-8f910', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--019fa870-b63e-7032-96f9-64a7310c0aab-0', tool_calls=[{'name': 'get_weather_for_location', 'args': {'city': '北京'}, 'id': 'call_3PewB69q4pfgl2cwgQcW8add', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 76, 'output_tokens': 20, 'total_tokens': 96, 'input_token_details': {}, 'output_token_details': {}})]}
正在查询 北京 的天气数据...
成功获取 北京 的天气数据。
tools {'messages': [ToolMessage(content='北京的天气是晴天,温度25摄氏度。', name='get_weather_for_location', id='fe0f1dc5-796f-4a27-8bbb-c02184954a11', tool_call_id='call_3PewB69q4pfgl2cwgQcW8add')]}
model {'messages': [AIMessage(content='北京现在是晴天,气温 25°C。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 16, 'prompt_tokens': 123, 'total_tokens': 139, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_provider': 'openai', 'model_name': 'gpt-5.4', 'system_fingerprint': None, 'id': 'chatcmpl-2657453a-058b-47cc-8b35-4386c', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--019fa870-c512-7c50-a2b1-264e470513c5-0', tool_calls=[], invalid_tool_calls=[], usage_metadata={'input_tokens': 123, 'output_tokens': 16, 'total_tokens': 139, 'input_token_details': {}, 'output_token_details': {}})]}
会交替输出 type: updates(包含代理状态更新)和 type: custom(包含自定义消息)。
7.5 模式实践
7.5.1 流式传输推理 / 思考 Token
某些模型(如Claude、OpenAI系列)在生成最终答案前会进行内部推理。将这些推理内容实时输出,可以增强应用的透明度与可解释性。
实现原理:LangChain 将不同提供商的推理内容(Anthropic的thinking块、OpenAI的reasoning摘要等)统一规范为 content_blocks 中 type: "reasoning" 的块。
代码示例:
python
from langchain.agents import create_agent
from langchain.messages import AIMessageChunk
from langchain_openai import ChatOpenAI
# 1. 配置模型,启用推理输出
model = ChatOpenAI(
model="gpt-5.4",
temperature=0,
api_key=os.getenv("PACKYAPI_API_KEY"),
base_url="https://www.packyapi.com/v1",
reasoning={ # 关键配置:
"effort": "medium", # 推理程度: 'low', 'medium', or 'high'
"summary": "detailed", # 推理摘要: 'detailed', 'auto', or None
}
)
@tool
def get_weather(city: str) -> str:
"""获取天气"""
return f"{city} 天气晴朗!"
agent = create_agent(model=model, tools=[get_weather])
# 2. 使用 stream_mode="messages" 并过滤 reasoning 块
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "上海的天气如何?"}]},
stream_mode="messages",
version="v2"
):
if chunk["type"] == "messages":
token,metadata=chunk["data"]
# 推理内容
reasoning = [b for b in token.content_blocks if b["type"] == "reasoning"]
# 响应内容
text = [b for b in token.content_blocks if b["type"] == "text"]
if reasoning and 'reasoning' in reasoning[0]:
print(f"{reasoning[0]['reasoning']}", end="")
if text:
print(text[0]["text"], end="")
关键点
- 必须确保模型支持并开启了推理输出(如
reasoning参数)。注意:模型不同,参数不同(Anthropic 系列参数为 thinking) - 无论使用哪个提供商,都可通过
content_blocks中的type: "reasoning"统一访问推理内容。 - 通常配合
stream_mode="messages"使用,逐块输出推理文本。
7.5.2 流式传输工具调用
在代理调用工具时,你可能希望:
- 实时显示工具调用的参数JSON片段(增量)。
- 最终获取完整解析后的工具调用消息(用于后续逻辑)。
实现原理:
stream_mode="messages":提供AIMessageChunk,其中包含tool_call_chunks(增量参数)。stream_mode="updates":在步骤(如model节点)完成后,提供完整的AIMessage,其中包含已解析的tool_calls。
代码示例(组合模式):
python
from langchain.agents import create_agent
from langchain.messages import AIMessage, AIMessageChunk, ToolMessage
from langchain_core.messages import AnyMessage
def get_weather(city: str) -> str:
"""获取天气"""
return f"{city} 天气晴朗!"
agent = create_agent(model=model, tools=[get_weather])
def render_chunk(token: AIMessageChunk):
if token.text: # 普通的文本
print(token.text, end="|")
if token.tool_call_chunks: # 工具调用碎片
print(token.tool_call_chunks) # 增量块
def render_completed(msg: AnyMessage):
if isinstance(msg, AIMessage) and msg.tool_calls: # ai消息中有工具调用
print(f"完整工具调用: {msg.tool_calls}")
if isinstance(msg, ToolMessage): # 工具调用结果
print(f"工具响应: {msg.content_blocks}")
input_msg = [{"role": "user", "content": "上海天气如何?"}]
for chunk in agent.stream(
{"messages": input_msg},
stream_mode=["messages", "updates"],
version="v2",
):
if chunk["type"] == "messages":
token, meta = chunk["data"]
if isinstance(token, AIMessageChunk): # 如果是AIMessageChunk类型,则渲染增量块
render_chunk(token)
elif chunk["type"] == "updates":
for source, update in chunk["data"].items():
if source in ("model", "tools"): # 关注模型和工具节点
render_completed(update["messages"][-1])
plain
[{'name': 'get_weather', 'args': '', 'id': 'call_o7zUpe9xzl8ieKTB3biAF9HT', 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '{"', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': 'city', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '":"', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '上海', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '"}', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
完整工具调用: [{'name': 'get_weather', 'args': {'city': '上海'}, 'id': 'call_o7zUpe9xzl8ieKTB3biAF9HT', 'type': 'tool_call'}]
工具响应: [{'type': 'text', 'text': '上海 天气晴朗!'}]
上海|天气|晴|朗|。|
关键点
tool_call_chunks是增量数据,可用于实时显示(如"正在输入参数...")。- 完整的
tool_calls需从updates模式中获取,通常在model节点完成后出现。 - 如果消息未保存在状态中,可通过累积
AIMessageChunk(使用 + 操作符)来重构完整消息。
7.5.3 流式传输与人机协作 (Human-in-the-Loop)
代理执行到某些关键操作(如调用工具)时,需要暂停并等待人工审批或编辑,然后继续执行。
实现原理:
- 使用
HumanInTheLoopMiddleware中间件,指定需要中断的工具。 - 流式传输时,捕获
updates模式中的__interrupt__节点。 - 构造
Command(resume=...)来恢复执行。
代码示例:
python
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langchain_core.messages import AIMessageChunk, AnyMessage, AIMessage, ToolMessage
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command, Interrupt
def get_weather(city: str) -> str:
"""获取天气"""
return f"{city} 天气晴朗!"
agent = create_agent(
"gpt-5-mini",
tools=[get_weather],
# 允许全部三种决策 (approve、edit、reject)
middleware=[HumanInTheLoopMiddleware(interrupt_on={"get_weather": True})],
checkpointer=InMemorySaver(),
)
def render_interrupt(interrupt: Interrupt) -> None:
interrupts = interrupt.value
for request in interrupts["action_requests"]:
print(request["description"])
def render_chunk(token: AIMessageChunk):
if token.text:
print(token.text, end="|")
if token.tool_call_chunks:
print(token.tool_call_chunks) # 增量块
def render_completed(msg: AnyMessage):
if isinstance(msg, AIMessage) and msg.tool_calls:
print(f"完整工具调用: {msg.tool_calls}")
if isinstance(msg, ToolMessage):
print(f"工具响应: {msg.content_blocks}")
config = {"configurable": {"thread_id": "1"}}
interrupts = []
# 第一次流式: 遇到中断
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "查询北京和上海的天气"}]},
config=config,
stream_mode=["updates"],
version="v2",
):
if chunk["type"] == "updates":
for source, update in chunk["data"].items():
if source == "__interrupt__":
interrupts.extend(update)
# 解析中断, 展示给用户, 收集决策...
render_interrupt(update[0])
输出:
Tool execution requires approval # 获取北京天气被拦列
Tool: get_weather
Args: {'city': '北京'}
Tool execution requires approval # 获取上海天气被拦列
Tool: get_weather
Args: {'city': '上海'}
批准第一个调用,编辑第二个调用(城市改为西安):
python
decisions = {
interrupts[0].id: {
"decisions": [
{"type": "approve"}, # 第一个工具调用批准
{ # 第二个工具调用编辑
"type": "edit",
"edited_action": {
"name": "get_weather",
"args": {"city": "西安"},
},
}
]
}
}
# 第二次流式: 恢复执行
for chunk in agent.stream(
Command(resume=decisions),
config=config,
stream_mode=["messages", "updates"],
version="v2",
):
if chunk["type"] == "messages":
token, metadata = chunk["data"]
if isinstance(token, AIMessageChunk):
render_chunk(token)
elif chunk["type"] == "updates":
for source, update in chunk["data"].items():
if source in ("model", "tools"):
render_completed(update["messages"][-1])
if source == "__interrupt__":
interrupts.extend(update)
render_interrupt(update[0])
输出:
工具响应: [{'type': 'text', 'text': '西安 天气晴朗!'}]
工具响应: [{'type': 'text', 'text': '北京 天气晴朗!'}]
[{'name': 'get_weather', 'args': '', 'id': 'call_EV8v7AALzzyjpj4cYWqJHPrO', 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '{', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': 'city', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '":"', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '上海', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '"}', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
完整工具调用: [{'name': 'get_weather', 'args': {'city': '上海'}, 'id': 'call_EV8v7AALzzyjpj4cYWqJHPrO', 'type': 'tool_call'}]
Tool execution requires approval # 这里由于Agent遵循ReAct模式, 发现问题是咨询上海天气, 由于没有拿到答案, 则继续循环。
# 实际上与模型也有关系, 可以换成gpt-4o-mini看结果
Tool: get_weather
Args: {'city': '上海'}
Human-in-the-loop 中间件通过可配置的策略、灵活的决策类型和状态持久化,使 Agent 能够在关键操作上获得人工监督,既保持了自动化效率,又增加了安全性和可控性。结合流式处理,开发者可以构建出既流畅又可靠的交互体验。