【LangGraph】强大的功能人类和agent的交互(中)
在上一节我们使用 interrupt
为 agent
添加了人机交互中的断点,进行了人工监督和干预,这一节我们接着讲述手动更新图的状态,更新状态可让我们通过修改代理的操作(甚至修改过去!)来控制代理的轨迹。当您想要纠正代理的错误、探索替代路径或引导代理实现特定目标时,此功能特别有用。
一、更新检查点状态
上一节添加的 interrupt_after
让图标被打断:
graph = graph_builder.compile(
checkpointer=memory,
interrupt_before=["tools"],
# Note: can also interrupt **after** actions, if desired.
# interrupt_after=["tools"]
)
在上一节 LLM
刚刚请求使用搜索引擎工具,我们的图表就被打断了。如果我们按照之前的方式继续,该工具将被调用来搜索网络。但是,如果我们认为 agent
不需要使用该工具怎么办?
我们下面来看下怎么来更新图的状态来让 agent
继续:
from langchain_core.messages import AIMessage, ToolMessage
# 提供新的响应消息
answer = (
"LangGraph is a goo tools."
)
new_messages = [
# LLM 的 API 期望一些 ToolMessage 来匹配其工具调用。
ToolMessage(content=answer, tool_call_id=existing_message.tool_calls[0]["id"]),
# 然后把答放入 LLM
AIMessage(content=answer),
]
new_messages[-1].pretty_print()
更新图状态
graph.update_state(
config,
# 这是待更新的值。我们State中的消息是“仅追加”的,这意味着这将作为追加内容。
# 这是当前存在的消息状态
{"messages": new_messages},
)
print(f"graph.get_state(config).values:{graph.get_state(config).values["messages"]}")
现在图表已经更新完成,因为我们已经提供了最终的响应消息!新消息已追加到状态中已有的消息中,
前面我们定义的
States
类里面的 messages
用预建 add_messages
函数进行了注释。这指示图表始终将值追加到现有列表末尾,而不是直接覆盖列表。这里应用了相同的逻辑,因此我们传递给的消息 update_state
以相同的方式追加!
因为 update_state
函数的运行方式就像它是图中的一个节点一样!默认情况下,更新操作使用最后执行的节点,我们可以在下面来手动指定它。让我们添加一个更新状态的操作并告诉图将其视为来自“chatbot”,就是大模型回调方法节点。
简单点就是说使用了 as_node="chatbot"
,表示这个更新操作是以 chatbot
节点的身份执行的。这意味着系统会继续处理,就像 chatbot 节点刚刚运行了一样。当以后我们需要模拟某个特定节点(如 chatbot
)的行为,并向对话历史中添加特定消息时使用。
graph.update_state(
config,
{"messages": [AIMessage(content="I'm an AI expert!")]},
as_node="chatbot",
)
我们这里只是告诉图表更新 as_node="chatbot"
。如果我们按照下图并从chatbot节点开始,我们自然会到达边缘tools_condition
,因为我们的更新消息缺少工具调用然后执行 __end__
。下面我们展示一下当前图的各个节点情况:
from IPython.display import Image, display
try:
display(Image(graph.get_graph().draw_mermaid_png()))
except Exception:
# This requires some extra dependencies and is optional
pass
接下来我们检查一下当前更新后的状态:
snapshot = graph.get_state(config)
print(snapshot.values["messages"][-3:])
print(snapshot.next)
可以看到状态已经更新成功。由于我们继续向状态追加
AI
消息。当 chatbot
角色使用不包含的 AIMessage
进行响应 tool_calls
,因此图表知道它已进入完成状态(next
为空)。
二、覆盖现有消息
沃恩最开始定义的 State
对象里面的 add_messages
我们用来注释上述图表的函数控制 State
更新 messages
。此函数查看新 messages
列表中的任何消息 ID
。如果 ID
与现有状态中的消息匹配,add_messages
则用新内容覆盖现有消息。
举个例子,让我们更新工具调用,以确保我们从搜索引擎获得良好的结果!首先,启动一个新线程:
user_input = "I'm learning LangGraph. Could you do some research on it for me?"
config = {"configurable": {"thread_id": "2"}} # we'll use thread_id = 2 here
events = graph.stream(
{"messages": [("user", user_input)]}, config, stream_mode="values"
)
for event in events:
if "messages" in event:
event["messages"][-1].pretty_print()
接下来,让我们更新代理的工具调用:
from langchain_core.messages import AIMessage
snapshot = graph.get_state(config)
existing_message = snapshot.values["messages"][-1]
print("原始 Message ID", existing_message.id)
print(existing_message.tool_calls[0])
new_tool_call = existing_message.tool_calls[0].copy()
new_tool_call["args"]["query"] = "“LangGraph 人机交互工作流"
new_message = AIMessage(
content=existing_message.content,
tool_calls=[new_tool_call],
# ID 是 LangGraph 知道替换状态中的消息而不是追加此消息的关键。通过为消息指定一个唯一的 ID,LangGraph 可以识别出这是对现有消息的更新,而不是一条全新的消息。因此,它会用这条新消息替换掉具有相同 ID 的旧消息,而不是将其追加到消息列表的末尾。这种机制在需要修改或更新对话历史中的特定消息时非常有用,例如纠正错误或更新 AI 的响应
id=existing_message.id,
)
print("新的tool_calls")
print(new_message.tool_calls[0])
print("新的Message ID", new_message.id)
graph.update_state(config, {"messages": [new_message]})
print(f"{graph.get_state(config).values["messages"][-1].tool_calls}")
在这里我们便修改了
AI
的工具调用以搜索“LangGraph 人机交互工作流”,而不是简单的“LangGraph”。
然后我们用 None
参数来来恢复图并接收图返回的值:
events = graph.stream(None, config, stream_mode="values")
for event in events:
if "messages" in event:
event["messages"][-1].pretty_print()
这里因为我们还设置了检查点,我们修改图的操作也会存在检查点内存中,所以我们可以看一下
agent
能否回忆起:
events = graph.stream(
{
"messages": (
"user",
"Remember what I'm learning about?",
)
},
config,
stream_mode="values",
)
for event in events:
if "messages" in event:
event["messages"][-1].pretty_print()
我们已使用
interrupt_before
和 update_state
手动修改状态作为人机交互工作流的一部分。中断和状态修改让我们可以控制代理的行为方式。结合持久检查点,这就说明着我们可以在任何时候暂停执行操作和恢复。
四、总结
在我们的代码中,这个更新状态和覆盖消息都是基于我们打断 agent
来的,在上面这些运行日志里也可以看到每一步包括agent在用 tools
工具搜索之前组装的内容是什么,然后我们覆盖的消息是什么,最后工具出来的结果是什么,很好的实现了我们控制 agent
的行为。
下一节我们将实现人类和agent交互的最后一步,自定义状态。