人机协作¶
要在代理中审查、编辑和批准工具调用,您可以使用 LangGraph 内置的人机协作 (HIL) 功能,特别是 interrupt()
原语。
LangGraph 允许您无限期地暂停执行——几分钟、几小时甚至几天——直到收到人工输入。
这之所以可能,是因为代理状态已检查点到数据库中,这使得系统能够持久化执行上下文并在以后恢复工作流程,从中断处继续。
要深入了解人机协作概念,请参阅概念指南。
审查工具调用¶
向工具添加人工审批步骤
- 在工具中使用
interrupt()
暂停执行。 - 使用
Command(resume=...)
根据人工输入继续。
API 参考:InMemorySaver | interrupt | create_react_agent
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import interrupt
from langgraph.prebuilt import create_react_agent
# An example of a sensitive tool that requires human review / approval
def book_hotel(hotel_name: str):
"""Book a hotel"""
response = interrupt( # (1)!
f"Trying to call `book_hotel` with args {{'hotel_name': {hotel_name}}}. "
"Please approve or suggest edits."
)
if response["type"] == "accept":
pass
elif response["type"] == "edit":
hotel_name = response["args"]["hotel_name"]
else:
raise ValueError(f"Unknown response type: {response['type']}")
return f"Successfully booked a stay at {hotel_name}."
checkpointer = InMemorySaver() # (2)!
agent = create_react_agent(
model="anthropic:claude-3-5-sonnet-latest",
tools=[book_hotel],
checkpointer=checkpointer, # (3)!
)
interrupt
函数在特定节点暂停代理图。在这种情况下,我们在工具函数开始时调用interrupt()
,这会在执行工具的节点暂停图。interrupt()
内部的信息(例如,工具调用)可以呈现给人工,并且图可以根据用户输入(工具调用批准、编辑或反馈)恢复。InMemorySaver
用于在工具调用循环的每一步存储代理状态。这支持短期内存和人机协作功能。在此示例中,我们使用InMemorySaver
将代理状态存储在内存中。在生产应用程序中,代理状态将存储在数据库中。- 使用
checkpointer
初始化代理。
使用 stream()
方法运行代理,传入 config
对象以指定线程 ID。这允许代理在未来的调用中恢复相同的对话。
config = {
"configurable": {
"thread_id": "1"
}
}
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "book a stay at McKittrick hotel"}]},
config
):
print(chunk)
print("\n")
您会看到代理一直运行到它到达
interrupt()
调用,此时它会暂停并等待人工输入。
使用 Command(resume=...)
根据人工输入恢复代理。
API 参考:Command
from langgraph.types import Command
for chunk in agent.stream(
Command(resume={"type": "accept"}), # (1)!
# Command(resume={"type": "edit", "args": {"hotel_name": "McKittrick Hotel"}}),
config
):
print(chunk)
print("\n")
interrupt
函数与Command
对象结合使用,以人工提供的值恢复图。
与 Agent Inbox 结合使用¶
您可以创建一个包装器,为任何工具添加中断。
以下示例提供了一个与 Agent Inbox UI 和 Agent Chat UI 兼容的参考实现。
为任何工具添加人机协作的包装器
from typing import Callable
from langchain_core.tools import BaseTool, tool as create_tool
from langchain_core.runnables import RunnableConfig
from langgraph.types import interrupt
from langgraph.prebuilt.interrupt import HumanInterruptConfig, HumanInterrupt
def add_human_in_the_loop(
tool: Callable | BaseTool,
*,
interrupt_config: HumanInterruptConfig = None,
) -> BaseTool:
"""Wrap a tool to support human-in-the-loop review."""
if not isinstance(tool, BaseTool):
tool = create_tool(tool)
if interrupt_config is None:
interrupt_config = {
"allow_accept": True,
"allow_edit": True,
"allow_respond": True,
}
@create_tool( # (1)!
tool.name,
description=tool.description,
args_schema=tool.args_schema
)
def call_tool_with_interrupt(config: RunnableConfig, **tool_input):
request: HumanInterrupt = {
"action_request": {
"action": tool.name,
"args": tool_input
},
"config": interrupt_config,
"description": "Please review the tool call"
}
response = interrupt([request])[0] # (2)!
# approve the tool call
if response["type"] == "accept":
tool_response = tool.invoke(tool_input, config)
# update tool call args
elif response["type"] == "edit":
tool_input = response["args"]["args"]
tool_response = tool.invoke(tool_input, config)
# respond to the LLM with user feedback
elif response["type"] == "response":
user_feedback = response["args"]
tool_response = user_feedback
else:
raise ValueError(f"Unsupported interrupt response type: {response['type']}")
return tool_response
return call_tool_with_interrupt
- 此包装器创建一个新工具,该工具在执行被包装工具之前调用
interrupt()
。 interrupt()
使用 Agent Inbox UI 期望的特殊输入和输出格式HumanInterrupt
对象的列表被发送到AgentInbox
以向最终用户呈现中断信息- 恢复值由
AgentInbox
以列表形式提供(即,Command(resume=[...])
)
您可以使用 add_human_in_the_loop
包装器将 interrupt()
添加到任何工具,而无需将其添加在工具内部
API 参考:InMemorySaver | create_react_agent
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.prebuilt import create_react_agent
checkpointer = InMemorySaver()
def book_hotel(hotel_name: str):
"""Book a hotel"""
return f"Successfully booked a stay at {hotel_name}."
agent = create_react_agent(
model="anthropic:claude-3-5-sonnet-latest",
tools=[
add_human_in_the_loop(book_hotel), # (1)!
],
checkpointer=checkpointer,
)
config = {"configurable": {"thread_id": "1"}}
# Run the agent
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "book a stay at McKittrick hotel"}]},
config
):
print(chunk)
print("\n")
add_human_in_the_loop
包装器用于将interrupt()
添加到工具中。这允许代理暂停执行并等待人工输入,然后继续进行工具调用。
您会看到代理一直运行到它到达
interrupt()
调用,此时它会暂停并等待人工输入。
使用 Command(resume=...)
根据人工输入恢复代理。
API 参考:Command
from langgraph.types import Command
for chunk in agent.stream(
Command(resume=[{"type": "accept"}]),
# Command(resume=[{"type": "edit", "args": {"args": {"hotel_name": "McKittrick Hotel"}}}]),
config
):
print(chunk)
print("\n")