【LangGraph】强大的功能人类和agent的交互(下)
前面我们通过中断和状态更新实现对 agent
的控制,模拟人机交互过程,到现在为止,我们依赖的是 LanGraph
一个简单的状态(当然它只是一个消息列表messages
!)。我们可以使用这个简单的状态做很多事情,如果我们想在不依赖消息列表的情况下去定义复杂的行为,则可以向状态添加其他字段。我现在使用新的节点来扩展我们的 agent
。
一、创建人类节点
在之前我们的例子中,每当调用工具时,图表总是会中断。我们现在希望我们的 agent
可以选择性依赖人类。
为了实现这一步,我们要创建一个类人类的节点,graph
将始终运行到此节点之前停止。只有当 LLM
调用“人类”工具时,我们才会执行此节点。我们将在图表状态中创建一个 ask_human
标志,如果 LLM
调用此工具,我们将修改这个标志。
from dotenv import load_dotenv
load_dotenv()
from typing import Annotated
from langchain_openai import ChatOpenAI
from langchain_community.tools.tavily_search import TavilySearchResults
from typing_extensions import TypedDict
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition
from pydantic import BaseModel
class State(TypedDict):
messages: Annotated[list, add_messages]
ask_human: bool
其他没变,增加了一个pydantic的引入,还有在我们的状态结构里面添加了一个 ask_human
的表示,可能很多同学会疑惑,Pydantic
是什么? 我这解释一下,Pydantic
是一个 Python
数据验证和设置管理库,它可以进行数据验证,数据转换,和类型检查,后面我们就要用到。
接下来,定义一个模式来向模型展示,让其决定是否需要请求帮助。
class RequestAssistance(BaseModel):
request: str
然后我们开始定义 agent
的节点。这里的主要目的修改是 ask_human
,如果我们看到 agent
调用了该RequestAssistance
标志,则修改这个标志。
tool = TavilySearchResults(max_results=2)
tools = [tool]
llm = ChatOpenAI(model="gpt-4o-mini")
llm_with_tools = llm.bind_tools(tools + [RequestAssistance])
def chatbot(state: State):
response = llm_with_tools.invoke(state["messages"])
ask_human = False
if (
response.tool_calls
and response.tool_calls[0]["name"] == RequestAssistance.__name__
):
ask_human = True
return {"messages": [response], "ask_human": ask_human}
初始化图,增加chatbot节点和tool节点,
graph_builder = StateGraph(State)
graph_builder.add_node("chatbot", chatbot)
graph_builder.add_node("tools", ToolNode(tools=[tool]))
前面的准备工作就准备完毕,接下来,创建 human
节点。这个节点的作用是在我们的 graph
作为一个占位符,并且它触发 interrupt
(中断)。如果 human
节点在期间没有手动更新状态interrupt
,它会输入一条 tool
消息,以便 LLM
知道我们是请求过了但未响应。此节点还会将 ask_human
设置成 false
,以便 graph
知道除非再次发出请求,否则不要重新访问该节点。
from langchain_core.messages import AIMessage, ToolMessage
def create_response(response: str, ai_message: AIMessage):
return ToolMessage(
content=response,
tool_call_id=ai_message.tool_calls[0]["id"],
)
def human_node(state: State):
new_messages = []
if not isinstance(state["messages"][-1], ToolMessage):
new_messages.append(
create_response("No response from human.", state["messages"][-1])
)
return {
"messages": new_messages,
"ask_human": False,
}
graph_builder.add_node("human", human_node)
二、定义条件边逻辑
如果设置了ask_human = false
标志,select_next_node
将导向到该节点。否则,它让预构建函数选择下一个节点。
这里回忆一下,tools_condition函数只是检查响应消息中是否
chatbot有任何响应。如果是,它将导向到该节点。否则,它将结束
graph`。
def select_next_node(state: State):
if state["ask_human"]:
return "human"
# Otherwise, we can route as before
return tools_condition(state)
graph_builder.add_conditional_edges(
"chatbot",
select_next_node,
{"human": "human", "tools": "tools", END: END},
)
最后,我们添加简单的边并编译图形。这些边指示图形每当a执行完成时 a -> b。
# The rest is the same
graph_builder.add_edge("tools", "chatbot")
graph_builder.add_edge("human", "chatbot")
graph_builder.add_edge(START, "chatbot")
memory = MemorySaver()
graph = graph_builder.compile(
checkpointer=memory,
# We interrupt before 'human' here instead.
interrupt_before=["human"],
)
我们通过执行下面方法看下当前我们构造的图形:
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
可以看到
chatbot
可以向人类请求帮助(chatbot->select->human),然后可以调用搜索引擎工具(chatbot->select->action),或直接响应(chatbot->select-> end)。一旦做出操作或请求,图表将转换回节点chatbot以继续操作。
我们下面就来试一下这个graph的效果。
user_input = "需要一些专家指导来构建这个AI代理。你能帮我请求帮助吗?"
config = {"configurable": {"thread_id": "1"}}
# The config is the **second positional argument** to stream() or invoke()!
events = graph.stream(
{"messages": [("user", user_input)]}, config, stream_mode="values"
)
for event in events:
if "messages" in event:
event["messages"][-1].pretty_print()
graph
状态在调用 human
节点之前中断了。我们可以充当此场景中的“专家”人士,我们添加新的信息输入得到 ToolMessage
来手动更新状态。
下面,通过以下方式响应 agent
的请求:
1.ToolMessage
使用我们的响应创建一个,这将被传回 chatbot
。
- 调用
update_state
以手动更新图形状态。
ai_message = snapshot.values["messages"][-1]
human_response = (
"专家在此提供帮助!我们建议您探索使用LangGraph来构建您的AI代理。"
"与简单的自主代理相比,LangGraph提供了更高的可靠性和可扩展性。"
)
tool_message = create_response(human_response, ai_message)
graph.update_state(config, {"messages": [tool_message]})
我们这里检查状态来确认我们自己的输入已被添加。
graph.get_state(config).values["messages"]
开始恢复graph,参数传None
events = graph.stream(None, config, stream_mode="values")
for event in events:
if "messages" in event:
event["messages"][-1].pretty_print()
这里便可以看到我们充当的专家建议已经被agent采纳,并返回我们充当的建议之后的内容。
这里需要注意一点的是,agent已将更新后的状态纳入其最终响应中。由于所有内容都经过了检查,因此循环中的“专家”人员可以随时执行更新,而不会影响图表的执行。
三、总结
我们已经向助手 graph
添加了一个额外节点,让 agent
自行决定是否需要中断执行。您通过使用 State
里面的 ask_human
字段更新 graph
并在编译 graph
时修改中断逻辑来实现此目的。这样,我们就可以动态地将人自己纳入循环中,同时每次执行图时都保持完整内存。完整的实现了人类与 agent
的交互,我们提供建议,agent
采纳。