人在回路中¶
请改用 interrupt
函数。
从 LangGraph 0.2.57 版本开始,设置断点的推荐方法是使用 interrupt
函数,因为它简化了人在回路中的模式。
请参阅修订后的人在回路中指南,获取使用 interrupt
函数的最新版本。
人在回路中(或“在回路中”)通过几种常见的用户交互模式增强了智能体的能力。
常见的交互模式包括
(1) 批准
- 我们可以中断智能体,向用户展示当前状态,并允许用户接受某个操作。
(2) 编辑
- 我们可以中断智能体,向用户展示当前状态,并允许用户编辑智能体状态。
(3) 输入
- 我们可以明确地创建一个图节点来收集人类输入,并将该输入直接传递给智能体状态。
这些交互模式的用例包括
(1) 审查工具调用
- 我们可以中断智能体来审查和编辑工具调用的结果。
(2) 时光旅行
- 我们可以手动重放和/或分支智能体的历史操作。
持久化¶
所有这些交互模式都由 LangGraph 内置的持久化层实现,该层会在每个步骤写入图状态的检查点。持久化允许图停止,以便人类可以审查和/或编辑图的当前状态,然后根据人类的输入继续执行。
断点¶
在图流程的特定位置添加断点是实现人在回路中的一种方式。在这种情况下,开发者知道工作流程中在哪里需要人类输入,只需在该特定图节点之前或之后放置一个断点即可。
在这里,我们使用检查点和断点编译图,断点设置在我们想要中断之前的节点 step_for_human_in_the_loop
。然后我们执行上述交互模式之一,如果人类编辑了图状态,这将创建一个新的检查点。新的检查点保存到 thread
中,我们可以通过传入 None
作为输入来从那里恢复图的执行。
# Compile our graph with a checkpointer and a breakpoint before "step_for_human_in_the_loop"
graph = builder.compile(checkpointer=checkpointer, interrupt_before=["step_for_human_in_the_loop"])
# Run the graph up to the breakpoint
thread_config = {"configurable": {"thread_id": "1"}}
for event in graph.stream(inputs, thread_config, stream_mode="values"):
print(event)
# Perform some action that requires human in the loop
# Continue the graph execution from the current checkpoint
for event in graph.stream(None, thread_config, stream_mode="values"):
print(event)
动态断点¶
或者,开发者可以定义触发断点必须满足的某个条件。当开发者想要在特定条件下暂停图时,动态断点的概念就很有用。这使用 NodeInterrupt
,它是一种特殊的异常,可以根据某些条件在节点内部引发。例如,我们可以定义一个动态断点,当 input
长度超过 5 个字符时触发。
def my_node(state: State) -> State:
if len(state['input']) > 5:
raise NodeInterrupt(f"Received input that is longer than 5 characters: {state['input']}")
return state
假设我们运行图时输入触发了动态断点,然后尝试通过简单地为输入传入 None
来恢复图的执行。
# Attempt to continue the graph execution with no change to state after we hit the dynamic breakpoint
for event in graph.stream(None, thread_config, stream_mode="values"):
print(event)
图将再次中断,因为该节点将使用相同的图状态重新运行。我们需要更改图状态,以便不再满足触发动态断点的条件。因此,我们可以简单地将图状态编辑为一个满足我们动态断点条件(< 5 个字符)的输入,并重新运行该节点。
# Update the state to pass the dynamic breakpoint
graph.update_state(config=thread_config, values={"input": "foo"})
for event in graph.stream(None, thread_config, stream_mode="values"):
print(event)
或者,如果我们想保留当前输入并跳过执行检查的节点(my_node
)怎么办?为此,我们只需使用 as_node="my_node"
执行图更新,并为值传入 None
。这将不会更新图状态,但会将更新作为 my_node
运行,从而有效地跳过该节点并绕过动态断点。
# This update will skip the node `my_node` altogether
graph.update_state(config=thread_config, values=None, as_node="my_node")
for event in graph.stream(None, thread_config, stream_mode="values"):
print(event)
有关如何执行此操作的详细说明,请参阅我们的指南!
交互模式¶
批准¶
有时我们想批准智能体执行中的某些步骤。
我们可以在想要批准的步骤之前的断点处中断智能体。
这通常推荐用于敏感操作(例如,使用外部 API 或写入数据库)。
通过持久化,我们可以向用户展示当前的智能体状态以及下一步,以便进行审查和批准。
如果获得批准,图将从保存到 thread
中的最后一个已保存检查点恢复执行。
# Compile our graph with a checkpointer and a breakpoint before the step to approve
graph = builder.compile(checkpointer=checkpointer, interrupt_before=["node_2"])
# Run the graph up to the breakpoint
for event in graph.stream(inputs, thread, stream_mode="values"):
print(event)
# ... Get human approval ...
# If approved, continue the graph execution from the last saved checkpoint
for event in graph.stream(None, thread, stream_mode="values"):
print(event)
有关如何执行此操作的详细说明,请参阅我们的指南!
编辑¶
有时我们想审查和编辑智能体的状态。
与批准一样,我们可以在想要检查的步骤之前的断点处中断智能体。
我们可以向用户展示当前状态,并允许用户编辑智能体状态。
例如,这可以用来纠正智能体犯的错误(例如,请参阅下面的工具调用部分)。
我们可以通过分支保存到 thread
中的当前检查点来编辑图状态。
然后我们可以像之前一样从我们分支的检查点继续运行图。
# Compile our graph with a checkpointer and a breakpoint before the step to review
graph = builder.compile(checkpointer=checkpointer, interrupt_before=["node_2"])
# Run the graph up to the breakpoint
for event in graph.stream(inputs, thread, stream_mode="values"):
print(event)
# Review the state, decide to edit it, and create a forked checkpoint with the new state
graph.update_state(thread, {"state": "new state"})
# Continue the graph execution from the forked checkpoint
for event in graph.stream(None, thread, stream_mode="values"):
print(event)
有关如何执行此操作的详细说明,请参阅此指南!
输入¶
有时我们想在图的特定步骤中明确地获取人类输入。
我们可以为此创建一个指定的图节点(例如,在我们示例图中的 human_input
)。
与批准和编辑一样,我们可以在此节点之前的断点处中断智能体。
然后我们可以执行包含人类输入的状体更新,就像我们编辑状态时一样。
但是,我们添加了一件事
我们可以在状态更新时使用 as_node=human_input
来指定状态更新应被视为一个节点。
这很微妙,但很重要
在编辑时,用户决定是否编辑图状态。
在输入时,我们在图的结构中明确定义一个节点来收集人类输入!
然后包含人类输入的状体更新将作为这个节点运行。
# Compile our graph with a checkpointer and a breakpoint before the step to to collect human input
graph = builder.compile(checkpointer=checkpointer, interrupt_before=["human_input"])
# Run the graph up to the breakpoint
for event in graph.stream(inputs, thread, stream_mode="values"):
print(event)
# Update the state with the user input as if it was the human_input node
graph.update_state(thread, {"user_input": user_input}, as_node="human_input")
# Continue the graph execution from the checkpoint created by the human_input node
for event in graph.stream(None, thread, stream_mode="values"):
print(event)
有关如何执行此操作的详细说明,请参阅此指南!
用例¶
审查工具调用¶
一些用户交互模式结合了上述想法。
例如,许多智能体使用工具调用来做出决策。
工具调用带来了挑战,因为智能体必须做到两点正确
(1) 要调用的工具名称
(2) 传递给工具的参数
即使工具调用正确,我们可能也想应用自由裁量权
(3) 工具调用可能是敏感操作,我们希望批准
考虑到这些要点,我们可以结合上述想法来创建一个对工具调用进行人在回路审查的流程。
# Compile our graph with a checkpointer and a breakpoint before the step to to review the tool call from the LLM
graph = builder.compile(checkpointer=checkpointer, interrupt_before=["human_review"])
# Run the graph up to the breakpoint
for event in graph.stream(inputs, thread, stream_mode="values"):
print(event)
# Review the tool call and update it, if needed, as the human_review node
graph.update_state(thread, {"tool_call": "updated tool call"}, as_node="human_review")
# Otherwise, approve the tool call and proceed with the graph execution with no edits
# Continue the graph execution from either:
# (1) the forked checkpoint created by human_review or
# (2) the checkpoint saved when the tool call was originally made (no edits in human_review)
for event in graph.stream(None, thread, stream_mode="values"):
print(event)
有关如何执行此操作的详细说明,请参阅此指南!
时光旅行¶
与智能体协作时,我们经常想仔细检查它们的决策过程
(1) 即使它们达到了预期的最终结果,导致该结果的推理过程通常也很重要,需要检查。
(2) 当智能体犯错误时,了解原因通常很有价值。
(3) 在上述任一情况下,手动探索其他决策路径都很有用。
总的来说,我们将这些调试概念称为时光旅行
,它们由重放
和分支
组成。
重放¶
有时我们只想简单地重放智能体的历史操作。
上面,我们展示了从图的当前状态(或检查点)执行智能体的例子。
我们只需在带有 thread
的输入中传入 None
。
thread = {"configurable": {"thread_id": "1"}}
for event in graph.stream(None, thread, stream_mode="values"):
print(event)
现在,我们可以修改此操作,通过传入检查点 ID 来从特定检查点重放历史操作。
要获取特定检查点 ID,我们可以轻松地获取线程中的所有检查点并筛选出我们想要的那个。
每个检查点都有一个唯一的 ID,我们可以使用它来从特定检查点重放。
假设通过审查检查点,我们想从检查点 xxx
重放。
运行图时,我们只需传入检查点 ID。
config = {'configurable': {'thread_id': '1', 'checkpoint_id': 'xxx'}}
for event in graph.stream(None, config, stream_mode="values"):
print(event)
重要的是,图知道哪些检查点之前已经执行过。
因此,它会重放任何之前执行过的节点,而不是重新执行它们。
有关重放的相关背景信息,请参阅此附加概念指南。
有关如何执行时光旅行的详细说明,请参阅此指南!
分支¶
有时我们想分支智能体的历史操作,并探索图中的不同路径。
如上所述,编辑
正是我们对图的当前状态执行此操作的方式!
但是,如果我们想分支图的历史状态怎么办?
例如,假设我们想编辑某个检查点 xxx
。
在更新图状态时,我们传入这个 checkpoint_id
。
config = {"configurable": {"thread_id": "1", "checkpoint_id": "xxx"}}
graph.update_state(config, {"state": "updated state"}, )
这会创建一个新的分支检查点 xxx-fork
,然后我们可以从它运行图。
config = {'configurable': {'thread_id': '1', 'checkpoint_id': 'xxx-fork'}}
for event in graph.stream(None, config, stream_mode="values"):
print(event)
有关分支的相关背景信息,请参阅此附加概念指南。
有关如何执行时光旅行的详细说明,请参阅此指南!