【LangGraph】我们完全能够控制大模型Agent的行为

181 阅读5分钟

【LangGraph】强大的功能人类和agent的交互(中)

在上一节我们使用 interruptagent 添加了人机交互中的断点,进行了人工监督和干预,这一节我们接着讲述手动更新图的状态,更新状态可让我们通过修改代理的操作(甚至修改过去!)来控制代理的轨迹。当您想要纠正代理的错误、探索替代路径或引导代理实现特定目标时,此功能特别有用。

一、更新检查点状态

上一节添加的 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_beforeupdate_state 手动修改状态作为人机交互工作流的一部分。中断和状态修改让我们可以控制代理的行为方式。结合持久检查点,这就说明着我们可以在任何时候暂停执行操作和恢复。

四、总结

在我们的代码中,这个更新状态和覆盖消息都是基于我们打断 agent 来的,在上面这些运行日志里也可以看到每一步包括agent在用 tools 工具搜索之前组装的内容是什么,然后我们覆盖的消息是什么,最后工具出来的结果是什么,很好的实现了我们控制 agent 的行为。 下一节我们将实现人类和agent交互的最后一步,自定义状态。