文章目录
- [1. 指定模型](#1. 指定模型)
-
- [1.1 静态模型](#1.1 静态模型)
- [1.1 动态模型](#1.1 动态模型)
- [2. 指定工具](#2. 指定工具)
-
- [2.1 静态工具](#2.1 静态工具)
- [2.1 动态工具](#2.1 动态工具)
- [2.3 工具错误处理](#2.3 工具错误处理)
- [3. 指定提示词](#3. 指定提示词)
-
- [3.1 静态系统提示](#3.1 静态系统提示)
- [3.2 动态系统提示](#3.2 动态系统提示)
- [4. 指定结构化输出策略](#4. 指定结构化输出策略)
- [5. 定义State](#5. 定义State)
- [6. 人机协作(Human-in-the-loop)](#6. 人机协作(Human-in-the-loop))
-
- [6.1 配置](#6.1 配置)
- [6.2 HITL一般流程:](#6.2 HITL一般流程:)
- [6.3 响应中断与恢复](#6.3 响应中断与恢复)
- [7. 流模式](#7. 流模式)
-
- [模式一: updates - 流式传输代理进度](#模式一: updates - 流式传输代理进度)
- [模式二: messages - 流式传输LLM Token](#模式二: messages - 流式传输LLM Token)
- [模式三: custom - 流式传输自定义更新](#模式三: custom - 流式传输自定义更新)
- [模式四: 组合模式 - 同时使用多种流模式](#模式四: 组合模式 - 同时使用多种流模式)
1. 指定模型
1.1 静态模型
在创建 Agent 时一次性配置,执行期间保持不变:
方式一: 使用模型标识符字符串: 这是一种快捷方式,create_agent 内部会调用 init_chat_model 来初始化模型。你只能指定模型标识,虽然也能配置部分参数, 但是能配置的参数有限
python
agent = create_agent("openai:gpt-4o-mini", tools=tools)
方式二: 使用模型实例: 精细的模型参数:像 temperature、max_tokens、timeout、max_retries 这些生成参数,以及更底层的 use_responses_api、store 等提供商特定配置,只有创建模型实例时才能设定
python
model_5 = ChatOpenAI(model="gpt-5-mini", tempeture=0.1, max_tokens=1000)
# 定义 agent
agent = create_agent(
model=model_5,
tools=[get_weather_for_location],
system_prompt="你是一位乐于助人的助手。",
middleware=[dynamic_model_selection],
)
1.1 动态模型
在运行时根据当前状态或上下文动态选择模型。通过中间件(middleware)配合
实现方式: @wrap_model_call 装饰器
python
@wrap_model_call
def dynamic_model_selection(request: ModelRequest, handler) -> ModelResponse:
"""根据对话的复杂程度(如消息数量)来选择模型。"""
message_count = len(request.state["messages"])
if message_count > 1:
# 使用高级模型进行更长时间的对话
final_model = model
else:
final_model = model_mini
return handler(request.override(model=final_model))
2. 指定工具
工具赋予 Agent 执行具体操作的能力
2.1 静态工具
在创建Agent时通过 tools 参数传入工具列表,执行期间保持不变。工具可以定义为:
普通的python函数
使用@tool的装饰器(可自定义名称、描述、参数schema等)
若提供空列表,Agent 将仅包含一个不带工具调用能力的 LLM 节点
python
agent = create_agent(
model="gpt-5-mini",
tools=[get_weather_for_location],
)
2.1 动态工具
静态工具适用于大多数场景,但在运行时,有些场景需要灵活调整工具集如:
根据认证状态、用户权限、功能开关或对话阶段,再决定哪些工具可用
避免工具过多导致模型上下文过载或出错,同时避免工具过少限制能力。
动态工具有两种实现方式
- 运行时,根据条件动态选择预先注册好的工具
- 运行时,动态加入新工具
动态选择: 运行时,根据条件动态选择预先注册好的工具
python
@wrap_model_call(state_schema=State)
def state_based_tools(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
"""根据条件筛选已存在的工具,例如根据状态筛选"""
state = request.state
is_auth = state.get("auth", False)
# 未认证用户只能使用public_的工具
if not is_auth:
# 筛选
tools = [t for t in request.tools if t.name.startswith("public_")]
request = request.override(tools=tools) # 更新替换
else:
tools = [t for t in request.tools if t.name.startswith("private_")]
request = request.override(tools=tools) # 更新替换
return handler(request) # llm 执行,这一步是为了选择工具,因此要提前筛选工具
动态加入: 运行时,动态加入新工具
python
class DynamicToolMiddleware(AgentMiddleware):
"""能够注册并处理动态工具的中间件。"""
def wrap_model_call(
self,
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
# 将新的工具添加进来
updated_request = request.override(tools=[*request.tools, calculate_tip])
return handler(updated_request) # llm调用(选择工具)
def wrap_tool_call(
self,
request: ToolCallRequest,
handler: Callable[[ToolCallRequest], ToolMessage | Command],
) -> ToolMessage | Command:
# 处理动态工具的执行过程
# 走到此处已经把tool选好了,只是根据tool_calls知道要调用哪个工具和参数
try:
if request.tool_call["name"] == "calculate_tip":
return handler(request.override(tool=calculate_tip)) # calculate_tip.invoke()
return handler(request) # tool.invoke()
except Exception as e:
# print(e)
# 自定义错误消息返回 ToolMessage(content="工具错误:xxx")
return ToolMessage(
content=f"工具错误:请检查您的输入并重新尝试。({str(e)})",
tool_call_id=request.tool_call["id"]
)
*request.tools 是Python 列表解包,把原工具列表展开,再拼上新工具
2.3 工具错误处理
错误处理中间件可提高 Agent 的鲁棒性,避免因工具调用失败而中断流程。通过@wrap_tool_call 装饰器创建中间件,自定义工具执行失败时的错误响应
python
@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"]
)
3. 指定提示词
通过 system_prompt 参数控制 Agent 的初始行为,支持静态字符串和动态生成两种方式
3.1 静态系统提示
字符串形式或 SystemMessage 形式:直接传入提示文本。
字符串写法日常使用就足够了
python
agent = create_agent(model, tools, system_prompt="你是一位乐于助人的助手。请简洁准确地表达。")
python
agent = create_agent(
model="gpt-5-mini",
system_prompt=SystemMessage(
content=[
{"type": "text",
"text": "你是一名乐于助人的人工智能助手."}
]
),
)
3.2 动态系统提示
通过 @dynamic_prompt 中间件,可以根据请求参数(如用户角色、对话阶段)动态生成系统提示
python
@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="gpt-5-mini",
context_schema=Context,
# system_prompt= # 静态提示词
middleware=[user_role_prompt]
)
4. 指定结构化输出策略
LangChain 通过 create_agent 中的 response_format 参数提供了实现此功能的策略。核心步骤:
- 使用 Pydantic 定义期望的输出格式( BaseModel )
- 在创建 Agent 时,将 response_format 参数设置为对应的策略,并传入定义好的格式模型
LangChain 提供了两种主要策略,可以根据模型的支持情况和具体需求进行选择
ToolStrategy : 利用模型的工具调用(Tool Calling) 能力,通过创建一个"虚拟工具"来迫使模型以调用该工具参数的形式输出结构化数据
ProviderStrategy : 直接使用模型提供商提供的原生结构化输出功能, 是首选方案
这两种策略感兴趣可自行学习
从 langchain 1.0 开始,提供了一个便捷的简化写法。可以直接将定义好的Pydantic模型传给response_format
python
class ContactInfo(BaseModel):
first_name: str
last_name: str
agent = create_agent(
model="gpt-4.1",
response_format=ContactInfo # 直接传入模型,而不是策略对象
)
重要注意事项:预绑定工具(pre-bound)的模型不支持与结构化输出一起使用。如果需要动态模型选择与结构化输出结合,请确保传入中间件的模型没有预先调用 bind_tools
5. 定义State
LangChain Agent 会自动维护完整的对话历史。历史信息以消息列表( messages ) 的形式存储在Agent 的状态中,开发者无需额外配置即可实现基本的对话上下文跟踪.
在某些场景下,仅靠对话历史不够,Agent 需要记住额外的信息(例如:用户偏好、临时标志、中间结果)。这可以通过自定义状态实现
注意:
自定义状态是 Agent 的短期记忆,仅在当前会话生命周期 内有效.
自定义状态必须继承自 AgentState 并定义为 TypedDict (在 LangChain 1.0 及之后版本)
两种实现方法:
- 通过 Middleware 定义: 推荐方式。自定义状态需要在中间件钩子或特定工具中被访问时使用
- 通过 state_schema 参数定义: 快速实现,但作用域较广, 不推荐新项目使用。自定义状态仅需在工具中使用,无需复杂中间件逻辑时的简化用法
这里介绍下通过Middleware定义的方式(推荐):
步骤:
- 定义一个继承自 AgentState 的 TypedDict
- 创建一个继承自 AgentMiddleware 的类,设置 state_schema 为该 TypedDict
从 LangChain 1.0 开始,自定义状态必须是 TypedDict 类型,不再支持Pydantic 模型或 dataclass。
- 在 create_agent 时通过 middleware 参数传入该中间件
python
# 1. 定义TypedDict
class CustomState(AgentState):
user_preferences: dict
# 2. 创建中间件(类),关联状态
class CustomMiddleware(AgentMiddleware):
state_schema = CustomState # 设置 state_schema 为该 TypedDict
tools = [] # 可选:为该中间件绑定工具
def before_invoke(self, state: CustomState, runtime) -> dict[str, Any] | None:
# 在模型调用前可以访问和修改 state
print(f"User preferences: {state.get('user_preferences')}")
return None
# 3. 创建Agent并传入中间件
agent = create_agent(
model="gpt-5-mini",
tools=[],
middleware=[CustomMiddleware], # 关键:通过middleware注入自定义状态
)
# 调用时传入额外状态
result = agent.invoke({
"messages": [{"role": "user", "content": "什么是大模型?"}],
"user_preferences": {"style": "技术性", "verbosity": "详细的"},
})
通过state_schema参数定义
步骤:
- 定义一个继承自 AgentState 的 TypedDict
- 在 create_agent 时 直接通过 state_schema 参数传入该类型
python
# 1. 定义自定义状态
class CustomState(AgentState):
user_preferences: dict
# 2. 创建Agent时传入 state_schema
agent = create_agent(
model="openai:gpt-4o-mini",
tools=[], # 工具可以访问此状态
state_schema=CustomState # 快捷定义
)
6. 人机协作(Human-in-the-loop)
Human-in-the-loop(HITL)为 AI Agent 添加人工审核与干预能力。HITL 中间件允许在 Agent 执行敏感工具调用前,暂停流程并等待人工审批. 适用于写文件、执行SQL等需要人工确认的操作
其实现依赖 LangGraph 的持久化层(checkpointer),实现执行状态的保存与恢复
当 Agent 触发中断时,人工可做出以下三种响应(由策略配置决定哪些可用)
中断策略:
- approve: 完全批准,工具按原参数执行
- edit: 允许修改工具参数后再执行
- reject: 拒绝执行,将反馈添加到对话中
注意:当多个工具调用同时中断时,需按顺序逐一决策;编辑参数时请保守修改,避免影响模型后续判断
6.1 配置
使用 HumanInTheLoopMiddleware 中间件完成人机协作,其配置选项主要包含以下两部分:
参数一: interrupt_on (必选)
这是一个字典,用于定义哪些工具需要触发人工中断 以及允许哪些决策类型
键为工具名称(字符串),值可以是以下三种形式:
- True: 中断该工具,允许全部三种决策( approve 、edit 、 reject), "工具1": True
- False: 不中断,自动批准(工具调用直接执行)
- InterruptOnConfig 对象: 精细控制, 可指定允许的决策列表和自定义描述 , "工具2": False
InterruptOnConfig 对象的属性:
allowed_decisions :列表,可选值 "approve" 、 "edit" 、 "reject" 。决定人工可用的操作类型
description :字符串或可调用函数,用于覆盖该工具的中断提示消息。若未指定,则使用全局 description_prefix 拼接而成
python
interrupt_on={
"write_file": True, # True 全决策可用
"execute_sql": {"allowed_decisions": ["approve", "reject"]}, # InterruptOnConfig对象 禁止编辑
"read_data": False, # False 自动通过
}
参数二: description_prefix (可选)
字符串。作为中断消息的全局前缀,默认会在每个中断请求的描述前加上此文本
python
description_prefix="工具执行尚待批准"
最终消息格式: description_prefix + "\n\nTool: <tool_name>\nArgs:<arguments>"
若在 InterruptOnConfig 中指定了 description ,则忽略全局前缀
关键条件:
- 必须配置 checkpointer 以支持中断恢复
- 调用时需传入 thread_id 以标识会话线程
- 策略设计:对只读工具(如 read_data )可设 False 自动放行;对写操作建议至少配置approve 和 reject
6.2 HITL一般流程:
- LLM 提议调用某工具(命中 interrupt_on 的工具)
- 中间件拦截,发出中断,等待你输入决策
- 用同一个 thread_id 再次调用 agent,传入 Command(resume={"decisions": ...}) 恢复执行(同一个thread是通过config参数确定的)
- 中间件校验决策合法性,并按决策执行对应动作(approve/edit/reject)
- LLM 收到一条 ToolMessage(真实结果或拒绝说明),再决定下一步
6.3 响应中断与恢复
触发中断后获取待审核动作
python
config = {"configurable": {"thread_id": "11"}}
# 中断
response1 = agent.invoke(
{"messages": [{"role": "user", "content": "删除数据库中data表中id=1的旧数据"}]},
config=config,
version="v2", # LangChain v1.1后必须使用v2版本获取中断信息
)
# result.interrupts 包含待审核的工具调用详情
print(response1.interrupts)
打印出来的审核工具调用详情
python
(
Interrupt(
value={
'action_requests': [ # 被中断的工具信息
{
'name': 'execute_sql',
'args': {'sql': 'DELETE FROM data WHERE id = 1;'},
'description': "工具执行尚待批准\n\nTool: execute_sql\nArgs: {'sql': 'DELETE FROM data WHERE id = 1;'}"
}
],
'review_configs': [ # 运行的操作
{'action_name': 'execute_sql', 'allowed_decisions': ['approve', 'edit']}
]
},
id='005232c15e9a9dad9d48580dbd93acaa'
),
)
提交决策继续执行
python
# approve
response2 = agent.invoke(
Command(resume={"decisions": [{"type": "approve"}]}),
config=config,
version="v2"
)
print(response2)
python
# reject: 目前不支持(因为该工具在interrupt_on没配置这个决策)
response2 = agent.invoke(
Command(
resume={
"decisions": [
{
"type": "reject",
"message": "这是错误的,因为...." # 会被构造成ToolMessage返回
}
]
}
),
config=config,
version="v2"
)
print(response2)
python
# edit
response2 = agent.invoke(
Command(
resume={
"decisions": [{
"type": "edit",
"edited_action": {
"name": "execute_sql",
"args": {'sql': 'DELETE FROM data WHERE id = 2;'}
}
}],
}
),
config=config,
version="v2"
)
print(response2.value)
7. 流模式
模式一: updates - 流式传输代理进度
在每个代理步骤(如一次LLM调用、一次工具执行)完成后,流式传输完整的状态更新。
python
# 定义工具
@tool
def get_weather_for_location(city: str) -> str:
"""获取指定城市的天气信息"""
return f"在{city}总是阳光明媚!"
agent = create_agent(
model="gpt-5-mini",
tools=[get_weather_for_location],
system_prompt="你是一位乐于助人的客服助手。",
)
# 模式1:updates
for chunk in agent.stream(
{
"messages": [{
"role": "user",
"content": "北京的天气如何?"
}]
},
stream_mode="updates",
version="v2", # v1.1. 版本以上
):
if chunk["type"] == "updates":
for step, data in chunk["data"].items():
print(f"步骤:{step}")
print(f"内容:{data}")
此处将 version="v2" (需要 LangGraph >= 1.1.)传递给 stream() 或 astream() 以获取统一的
输出格式。每个数据块都是一个具有 type、ns 和 data 键的 StreamPart 字典, 比较方便
如果用"v1"版本必须使用(mode, data)元组接收.stream才行
模式二: messages - 流式传输LLM Token
从任何调用了LLM的节点中,流式传输token级别的增量消息块,实现类似"逐字输出"效果。每块是一个元组 (token, metadata)
python
# messages
for chunk in agent.stream(
{
"messages": [{
"role": "user",
"content": "北京的天气如何?"
}]
},
stream_mode="messages",
version="v2", # v1.1. 版本以上
):
if chunk["type"] == "messages":
token, metadata = chunk["data"]
print(f"节点:{metadata['langgraph_node']}")
print(token.content_blocks)
你会看到 model 节点逐块输出工具调用的JSON参数(如 '{"' , 'city' , '":"' , '上海' ,
...)
然后 tools 节点输出工具执行结果。
最后 model 节点逐块输出最终回复文本(如 '上海' , '的' , ' 天气' ...)
模式三: custom - 流式传输自定义更新
与LangGraph用法一致,通过 get_stream_writer() 函数获取写入器,可以主动向流中推送任意自定义数据
适用场景:报告工具执行进度(如"正在查询数据库...")、中间计算结果、调试信息等
python
# 定义工具
@tool
def get_weather_for_location(city: str) -> str:
"""获取指定城市的天气信息"""
writer = get_stream_writer()
writer(f"正在查询{city}的天气")
writer(f"查询{city}的天气完成")
return f"在{city}总是阳光明媚!"
agent = create_agent(
model="gpt-5-mini",
tools=[get_weather_for_location],
system_prompt="你是一位乐于助人的客服助手。",
)
# custom 自定义
for chunk in agent.stream(
{
"messages": [{
"role": "user",
"content": "北京的天气如何?"
}]
},
stream_mode="custom",
version="v2", # v1.1. 版本以上
):
if chunk["type"] == "custom":
print(chunk["data"]) # 直接输出自定义消息
模式四: 组合模式 - 同时使用多种流模式
将模式作为列表传入,如 stream_mode="updates", "custom" 。每次迭代返回一个包含type (标识模式)和 data (对应负载)的字典
示例:同时使用 updates 和 custom 模式
python
# 组合
for chunk in agent.stream(
{
"messages": [{
"role": "user",
"content": "北京的天气如何?"
}]
},
stream_mode=["updates", "custom"],
version="v2", # v1.1. 版本以上
):
print(f"流模式:{chunk['type']}")
print(f"内容:{chunk['data']}\n")
会交替输出 type: updates (包含代理状态更新)和 type: custom (包含自定义消息)