中间件与人工介入(Human-in-the-Loop)
本文介绍 LangGraph 两个核心生产模式:让 Agent 在执行敏感操作前等待人类审批,以及通过中间件在 Agent 运行的关键节点插入自定义逻辑。
一、Human-in-the-Loop:让 Agent 暂停等待审批
为什么需要它?
Agent 可以自动发邮件、删数据库、下订单……但这些操作一旦执行就难以撤销。Human-in-the-Loop 让你在 Agent 执行敏感操作前强制暂停,等人类确认。
核心原理
用户输入 → Agent 执行 → 遇到 interrupt() → 暂停 → 人类审批 → Command(resume) → 继续执行
第一步:在工具里加 interrupt()
from langgraph.types import interrupt
from langchain_core.tools import tool
@tool
def send_email(to: str, subject: str, body: str) -> str:
"""Send an email to a recipient."""
# 暂停,等待人类审批
approval = interrupt({
"action": "send_email",
"to": to,
"subject": subject,
"body": body,
"message": "Do you want to send this email?"
})
if approval.get("approved"):
return f"Email sent to {to} with subject '{subject}'"
else:
return "Email cancelled by user"
interrupt()传入的字典是展示给人类看的信息,内容自定义,没有固定格式。
第二步:创建带 checkpointer 的 Agent
from langchain.agents import create_agent
from langgraph.checkpoint.memory import MemorySaver
# 中断必须有 checkpointer,否则无法保存暂停状态
agent = create_agent(
model=model,
tools=[send_email],
system_prompt="You are a helpful email assistant.",
checkpointer=MemorySaver()
)
第三步:触发中断
import uuid
from langchain.messages import HumanMessage
config = {"configurable": {"thread_id": str(uuid.uuid4())}}
result = agent.invoke(
{"messages": [HumanMessage(content="Send an email to alice@example.com with subject 'Meeting Tomorrow' and body 'Let\\'s meet at 3pm.'")]},
config=config
)
# 检查是否触发了中断
if "__interrupt__" in result:
info = result["__interrupt__"][0].value
print(f"⏸️ Agent 已暂停")
print(f" 收件人: {info['to']}")
print(f" 主题: {info['subject']}")
print(f" 正文: {info['body']}")
print(f" 提示: {info['message']}")
输出结构:
{
"messages": [...],
"__interrupt__": [Interrupt(value={
"action": "send_email",
"to": "alice@example.com",
"subject": "Meeting Tomorrow",
"body": "Let's meet at 3pm.",
"message": "Do you want to send this email?"
})]
}
第四步:人类做出决定,恢复执行
from langgraph.types import Command
# 同意
result = agent.invoke(
Command(resume={"approved": True}),
config=config # 必须用同一个 thread_id!
)
# 拒绝
result = agent.invoke(
Command(resume={"approved": False}),
config=config
)
thread_id是关键:LangGraph 通过它找到暂停的 Checkpoint,从中断处继续执行。
二、进阶模式:中断 + 编辑
除了简单的同意/拒绝,还可以让人类修改内容后再执行:
@tool
def send_email_v2(to: str, subject: str, body: str) -> str:
"""Send an email with edit support."""
response = interrupt({
"action": "send_email",
"to": to, "subject": subject, "body": body,
"message": "Review this email. You can approve, reject, or edit it."
})
if response["type"] == "approve":
return f"Email sent to {to}"
elif response["type"] == "reject":
return "Email cancelled"
elif response["type"] == "edit":
# 人类改了哪个字段就用哪个,没改的保持原值
to = response.get("to", to)
subject = response.get("subject", subject)
body = response.get("body", body)
return f"Email sent with edits: To={to}, Subject={subject}"
人类携带修改内容恢复执行:
result = agent_v2.invoke(
Command(resume={
"type": "edit",
"subject": "URGENT: Meeting Today at 2pm", # 只改主题
"body": "This is the updated body."
}),
config=config
)
三、Middleware(中间件)
什么是中间件?
中间件让你在 Agent 运行的每个关键节点前后插入自定义代码,而不改变 Agent 本身的逻辑。
类比:快递中转站 —— 包裹(请求)在发件人和收件人之间经过中转,中转站可以做检查、加急标记等操作。
Agent 循环
用户输入
→ [before_model] ← 调用模型前
→ [wrap_model_call] ← 包装模型调用本身
→ 模型执行
→ [after_model] ← 调用模型后
→ 工具执行
→ 循环...
两种钩子风格
| Node-style | Wrap-style | |
|---|---|---|
| 比喻 | 监控摄像头 | 安保人员 |
| 能否阻止执行 | ❌ | ✅ |
| 典型用途 | 日志、状态更新、验证 | 重试、缓存、模型替换 |
| 代表钩子 | before_model, after_model |
wrap_model_call, wrap_tool_call |
示例 1:动态系统提示(Node-style)
根据用户角色动态切换 system prompt:
from langchain.agents.middleware import dynamic_prompt, ModelRequest
from typing import TypedDict
class Context(TypedDict):
user_role: str
@dynamic_prompt
def dynamic_prompt_middleware(request: ModelRequest) -> str:
user_role = request.runtime.context.get("user_role", "general")
if user_role == "expert":
return "你是技术专家助手,提供详细的技术回答和代码示例。"
elif user_role == "beginner":
return "你是入门助手,用简单易懂的语言解释概念,避免专业术语。"
else:
return "你是通用助手。"
# 创建带中间件的 Agent
agent = create_agent(
model=model,
tools=[explain_concept],
middleware=[dynamic_prompt_middleware],
context_schema=Context
)
# 专家模式调用
result = agent.invoke(
{"messages": [HumanMessage(content="解释异步编程")]},
context={"user_role": "expert"}
)
# 入门模式调用
result = agent.invoke(
{"messages": [HumanMessage(content="解释异步编程")]},
context={"user_role": "beginner"}
)
示例 2:请求日志记录(Wrap-style)
在每个步骤打印调试信息:
from langchain.agents.middleware import AgentMiddleware, AgentState, ModelRequest, ModelResponse
from typing import Any, Callable
class RequestLoggerMiddleware(AgentMiddleware):
"""记录所有模型请求,便于调试。"""
def before_model(self, state: AgentState, runtime) -> dict[str, Any] | None:
message_count = len(state.get("messages", []))
print(f"[调用前] 当前消息数: {message_count}")
return None # 返回 None 表示不修改 state
def wrap_model_call(
self,
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse]
) -> ModelResponse:
print(f"[模型调用] 可用工具数: {len(request.tools) if request.tools else 0}")
response = handler(request) # 实际调用模型
return response
def after_model(self, state: AgentState, runtime) -> dict[str, Any] | None:
last_message = state["messages"][-1]
if hasattr(last_message, 'tool_calls') and last_message.tool_calls:
print(f"[调用后] 模型请求调用 {len(last_message.tool_calls)} 个工具")
else:
print(f"[调用后] 模型直接返回最终答案")
return None
# 使用
agent = create_agent(
model=model,
tools=[explain_concept],
middleware=[RequestLoggerMiddleware()]
)
四、中间件 + Human-in-the-Loop 组合
两者配合使用,构建生产级的安全 Agent:
# 危险工具:需要人工确认
@tool
def delete_database(database_name: str) -> str:
"""Delete a database."""
response = interrupt({
"action": "delete_database",
"database_name": database_name,
"warning": "这将永久删除数据库!",
"message": "你确定吗?"
})
if response.get("confirmed"):
return f"数据库 '{database_name}' 已删除"
return "已取消删除"
# 安全中间件:检测危险操作并记录
class SafetyMiddleware(AgentMiddleware):
def after_model(self, state: AgentState, runtime) -> dict[str, Any] | None:
last_message = state["messages"][-1]
if hasattr(last_message, 'tool_calls'):
for tool_call in last_message.tool_calls:
if "delete" in tool_call["name"].lower():
print(f"⚠️ [安全警告] 检测到危险操作: {tool_call['name']}")
print(f" 参数: {tool_call['args']}")
return None
# 生产级 Agent:同时具备安全日志 + 人工审批
production_agent = create_agent(
model=model,
tools=[delete_database],
middleware=[SafetyMiddleware()],
checkpointer=MemorySaver()
)
执行流程:
用户请求删除数据库
→ SafetyMiddleware 检测到危险操作,打印警告日志
→ 工具执行,触发 interrupt() 暂停
→ 人类看到警告,决定是否确认
→ Command(resume={"confirmed": True/False})
→ 继续或取消执行
总结
| 模式 | 用途 | 关键 API |
|---|---|---|
| Human-in-the-Loop | 敏感操作前等待人类审批 | interrupt(), Command(resume=...) |
| Node-style 中间件 | 日志、验证、状态注入 | before_model, after_model |
| Wrap-style 中间件 | 拦截、重试、模型替换 | wrap_model_call, wrap_tool_call |
| 组合使用 | 生产级安全 Agent | 以上全部 |
使用场景:
- 发邮件/删数据等不可逆操作 → Human-in-the-Loop
- 调试 Agent 执行过程 → 日志中间件
- 根据用户身份切换行为 → 动态 prompt 中间件
- 生产环境敏感操作 → 两者组合

浙公网安备 33010602011771号