本节给高风险工具加人工审批:读邮件可以自动执行,发邮件必须暂停等待人类决定,恢复时可以批准、拒绝或编辑工具调用。
Agent 可以调用工具,但并不是所有工具都应该自动执行。读邮件风险较低,发邮件会产生外部影响,所以更适合加人工审批。
| 工具 | 是否中断 | 原因 |
|---|---|---|
read_email | 否 | 只读取 state 中的邮件内容,不产生外部副作用 |
send_email | 是 | 会发送邮件,属于需要确认的外部动作 |
Human-in-the-loop 不是让人参与每一步,而是只在高风险动作执行前暂停。
Notebook 先定义两个工具。read_email 从 state 读邮件原文;send_email 模拟发送邮件。
@tool
def read_email(runtime: ToolRuntime) -> str:
"""Read an email from the given address."""
return runtime.state["email"]
@tool
def send_email(body: str) -> str:
"""Send an email to the given address with the given subject and body."""
return "Email sent"邮件正文被放在 EmailState.email 中,因此工具可以通过 runtime.state["email"] 读取。
HumanInTheLoopMiddleware 的关键配置是 interrupt_on。它精确声明哪些工具调用需要暂停。
from langchain.agents.middleware import HumanInTheLoopMiddleware
class EmailState(AgentState):
email: str
humanInTheLoopMiddleware = HumanInTheLoopMiddleware(
interrupt_on={
"read_email": False,
"send_email": True,
},
description_prefix="Tool execution requires approval",
)
agent = create_agent(
model=deepseek_model(),
tools=[read_email, send_email],
state_schema=EmailState,
checkpointer=InMemorySaver(),
middleware=[humanInTheLoopMiddleware],
)中断后要用同一个 thread_id 恢复执行,所以 Agent 需要 checkpointer 保存暂停时的状态。
用户要求“读邮件并立刻回复”。Agent 可以先读邮件,但当它准备调用 send_email 时会返回 __interrupt__,等待人类决策。
config = {"configurable": {"thread_id": "1"}}
message = [HumanMessage(
content="Please read my email and send a response immediately. Send the reply now in the same thread."
)]
response = agent.invoke(
{
"messages": message,
"email": "Hi Seán, I'm going to be late for our meeting tomorrow. Can we reschedule? Best, John.",
},
config=config,
)
print(response)中断发生在工具执行前。此时可以检查模型打算发送的邮件正文,再决定是否允许。
恢复执行时传入 Command(resume=...)。课程展示了三种决策:批准原工具调用、拒绝并给模型反馈、编辑工具参数后再执行。
from langgraph.types import Command
response = agent.invoke(
Command(resume={"decisions": [{"type": "approve"}]}),
config=config,
)
response = agent.invoke(
Command(resume={
"decisions": [{
"type": "reject",
"message": "No please sign off - Your merciful leader, Seán.",
}]
}),
config=config,
)
response = agent.invoke(
Command(resume={
"decisions": [{
"type": "edit",
"edited_action": {
"name": "send_email",
"args": {"body": "This is the last straw, you're fired!"},
},
}]
}),
config=config,
)| 决策 | 含义 |
|---|---|
| approve | 按模型原计划执行工具调用 |
| reject | 不执行工具,并把拒绝原因返回给 Agent |
| edit | 修改工具名称或参数后执行 |
interrupt_on 让你按工具粒度控制是否暂停。Command(resume=...) 必须使用同一个 thread_id。