https://langchain-ai.github.io/langgraph/tutorials/introduction/#part-4-human-in-the-loop
话说,AI不是任何时候的特别可靠的。人工智能技术出现以来,无法离开人工对信息,算法的干预。我们需要再AI智能体工作的过程中可控的调整和介入,那就要求langgraph这类框架具备人类交互相关的功能。
我们直接解析代码来的更清晰一点吧。LangGraph提供interrupt和Command两个接口来实现用户的交互。
- 实现这一功能的主要接口是
interrupt
函数。在节点内部调用interrupt
会暂停执行,并让用户输入内容。我们先添加一个工具,在工具节点中调用。
from langgraph.types import Command, interrupt
@tool
def human_assistance(query: str) -> str:
"""Request assistance from a human."""
human_response = interrupt({"query": query})
return human_response["data"]
tools = [tool, human_assistance]
interrupt提供类似Python的input()功能,可以让用户在命令行中输入内容,humna_assistance工具被LLM调用的时候,LLM会把用户的问题转发到这里(query)。@tool装饰器告诉Graph这是一个工具,注意方法中的第一行,这是必填的docstring注释文本(也可以通过docstring参数传入human_assistance),LLM是根据这段文本来判断是否要调用这个工具,就是说每当用户对话中告诉AI,需要人类协助时,AI会调用这个工具,在命令行中请人类回答(通过interrupt方法)。
LangGraph需要持久层的记忆存储功能来支持人类用户的协助,能够依据用户反馈暂停和恢复执行。所以,memory还是配上:
memory = MemorySaver()
graph = graph_builder.compile(checkpointer=memory)
在对话聊天节点我们需要做点修改。AI对话返回的结果message,先看看返回的消息,如果返回的消息中,同时要用到两个工具(搜索和人类协助),则断言终止。这样做是为了防止恢复本节点运行时,又重复调用工具(interrupt设置时需要考虑到一些副作用)。
def chatbot(state: State):
message = llm_with_tools.invoke(state["messages"])
# Because we will be interrupting during tool execution,
# we disable parallel tool calling to avoid repeating any
# tool invocations when we resume.
assert len(message.tool_calls) <= 1
return {"messages": [message]}
现在,我们硬编码提供一个用户提问:
user_input = "I need some expert guidance for building an AI agent. Could you request assistance for me?"
config = {"configurable": {"thread_id": "1"}}
events = graph.stream(
{"messages": [{"role": "user", "content": user_input}]},
config,
stream_mode="values",
)
for event in events:
if "messages" in event:
event["messages"][-1].pretty_print()
用户提出请AI帮我申请人工协助,这时,AI理解了用户的意思并查看工具箱里(tools)调用human_assistance工具。human_assistance会中断图的执行流(注意,不是中断整个应用程序)。输出的结果:
================================ Human Message =================================
I need some expert guidance for building an AI agent. Could you request assistance for me?
================================== Ai Message ==================================
Tool Calls:
human_assistance (call_0_1ab00496-f5eb-45f1-a6a3-bb433d509704)
Call ID: call_0_1ab00496-f5eb-45f1-a6a3-bb433d509704
Args:
query: I need expert guidance for building an AI agent. Could you provide assistance?
我输出的内容和官网输出的不一样,官网在调用工具之前,先回答你:
================================[1m Human Message [0m=================================
I need some expert guidance for building an AI agent. Could you request assistance for me?
==================================[1m Ai Message [0m==================================
[{'text': "Certainly! I'd be happy to request expert assistance for you regarding building an AI agent. To do this, I'll use the human_assistance function to relay your request. Let me do that for you now.", 'type': 'text'}, {'id': 'toolu_01ABUqneqnuHNuo1vhfDFQCW', 'input': {'query': 'A user is requesting expert guidance for building an AI agent. Could you please provide some expert advice or resources on this topic?'}, 'name': 'human_assistance', 'type': 'tool_use'}]
Tool Calls:
human_assistance (toolu_01ABUqneqnuHNuo1vhfDFQCW)
Call ID: toolu_01ABUqneqnuHNuo1vhfDFQCW
Args:
query: A user is requesting expert guidance for building an AI agent. Could you please provide some expert advice or resources on this topic?
我试了半天是不是哪里不对,后来想想,这应该是deepseek和openAI(官网使用openAI)的机制上的差异,这个也不是重点。那现在我们中断(interrupt)对话,我们现在再模拟一个人类的回答:
human_response = (
"We, the experts are here to help! We'd recommend you check out LangGraph to build your agent."
" It's much more reliable and extensible than simple autonomous agents."
)
human_command = Command(resume={"data": human_response})
events = graph.stream(human_command, config, stream_mode="values")
for event in events:
if "messages" in event:
event["messages"][-1].pretty_print()
Cammand方法在graph中经常用于跟interrupt搭配使用,在graph的stream,invoke,ainvoke方法中作为参数,可以使得在图中的断点恢复执行,并带上返回值或者更新State。上述代码将一个拥有data属性的对象返回到graph图中,并从chatbot这个节点恢复。输出结果如下:
================================ Human Message =================================
I need some expert guidance for building an AI agent. Could you request assistance for me?
================================== Ai Message ==================================
Tool Calls:
human_assistance (call_0_079ba1c8-a63b-4d6a-bc75-3821c73b9778)
Call ID: call_0_079ba1c8-a63b-4d6a-bc75-3821c73b9778
Args:
query: I need some expert guidance for building an AI agent. Could you request assistance for me?
================================== Ai Message ==================================
Tool Calls:
human_assistance (call_0_079ba1c8-a63b-4d6a-bc75-3821c73b9778)
Call ID: call_0_079ba1c8-a63b-4d6a-bc75-3821c73b9778
Args:
query: I need some expert guidance for building an AI agent. Could you request assistance for me?
================================= Tool Message =================================
Name: human_assistance
We, the experts are here to help! We'd recommend you check out LangGraph to build your agent. It's much more reliable and extensible than simple autonomous agents.
================================== Ai Message ==================================
Tool Calls:
tavily_search_results_json (call_0_8a717d8d-4d49-45b1-9752-7ad69118573f)
Call ID: call_0_8a717d8d-4d49-45b1-9752-7ad69118573f
Args:
query: LangGraph AI agent
================================= Tool Message =================================
Name: tavily_search_results_json
[{"title": "AI Agents in LangGraph - DeepLearning.AI", "url": "https://www.deeplearning.ai/short-courses/ai-agents-in-langgraph/", "content": "AI Agents in LangGraph - DeepLearning.AI AI Agents in LangGraph AI Agents in LangGraph AI Agents in LangGraph Learn about LangGraph’s components and how they enable the development, debugging, and maintenance of AI agents. Learn directly from LangChain founder Harrison Chase and Tavily founder Rotem Weiss. In this course you will learn to build an agent from scratch using Python and an LLM, and then you will rebuild it using LangGraph, learning about its components and how to combine them to build flow-based applications. If you have intermediate Python knowledge and want to learn how to create more controllable agents using the LangGraph open source framework, this course is for you. AI Agents in LangGraph", "score": 0.8773195}, {"title": "How to Build AI Agents with LangGraph: A Step-by-Step Guide", "url": "https://medium.com/@lorevanoudenhove/how-to-build-ai-agents-with-langgraph-a-step-by-step-guide-5d84d9c7e832", "content": "In this step, we’ll define how the AI agent manages its state (the ongoing context of the conversation) and ensure it responds appropriately to the user’s input and tool output. This involves creating a template for the conversation, specifying the tools that the assistant will use, and configuring how the AI agent will respond to user input and trigger different functions (like calculating solar savings). This step ensures that the AI assistant can access and trigger the tools as needed during the conversation, creating a seamless interaction between the user and the assistant. By following these steps, you have successfully created an AI assistant using LangGraph that can calculate solar panel energy savings based on user inputs.", "score": 0.84277284}]
模拟人类回答后,这里deepseek的行为跟openAI的又不一样,openAI会回复Graph相关的信息。deepseek发现还有个tavilysearch的工具,那干脆直接用tailysearch搜索引擎帮你查查什么是LangGraph。哈哈哈,一千个AI就有一千个哈姆雷特。
完整代码:
import getpass
import os
def _set_env(var: str):
print(f"Checking if {var} is set...")
if not os.environ.get(var):
print(f"{var} is not set, prompting user for input.")
os.environ[var] = getpass.getpass(f"{var}: ")
print(f"{var} has been set.{os.environ.get(var)}")
else:
print(f"{var} is already set to: {os.environ[var]}")
_set_env("TAVILY_API_KEY")
from langchain_community.tools.tavily_search import TavilySearchResults
# tool.invoke("What's a 'node' in LangGraph?")
# 接下来就跟第一部分一样,创建LLM,创建图、节点、起终点
# 把上面的tavily变成一个节点塞到graph里面
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_core.tools import tool
from IPython.display import Image, display
from langchain.schema import HumanMessage, AIMessage
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.types import Command, interrupt
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import MemorySaver
class State(TypedDict):
messages: Annotated[list, add_messages]
graph_builder = StateGraph(State)
@tool
def human_assistance(query: str) -> str:
"""Request assistance from a human."""
human_response = interrupt({query: query})
return human_response["data"]
tool = TavilySearchResults(max_results=2)
tools = [tool, human_assistance]
llm = ChatOpenAI(
max_retries=2,
base_url="https://api.deepseek.com/v1",
api_key="sk-xxxxxxxxxxxxxxxx", # 替换为你的API密钥
model="deepseek-chat" # 根据实际模型名称修改
)
llm_with_tools = llm.bind_tools(tools)
def chatbot(state: State):
message = llm_with_tools.invoke(state["messages"])
# Because we will be interrupting during tool execution,
# we disable parallel tool calling to avoid repeating any
# tool invocations when we resume.
assert len(message.tool_calls) <= 1
# print("模型原始响应:", messages) # 检查是否包含正确的tool_calls
return {"messages": [message]}
graph_builder.add_node("chatbot", chatbot)
tool_node = ToolNode(tools=tools)
graph_builder.add_node("tools", tool_node)
graph_builder.add_conditional_edges(
"chatbot",
tools_condition,
)
graph_builder.add_edge("tools", "chatbot")
graph_builder.add_edge(START, "chatbot")
memory = MemorySaver()
graph = graph_builder.compile(checkpointer=memory)
# user_input = "How to play switch"
user_input = "I need some expert guidance for building an AI agent. Could you request assistance for me?"
config = {"configurable": {"thread_id": "1"}}
events = graph.stream(
{"messages": [{"role": "user", "content": user_input}]},
config,
stream_mode="values",
)
for event in events:
if "messages" in event:
event["messages"][-1].pretty_print()
human_response = (
"We, the experts are here to help! We'd recommend you check out LangGraph to build your agent."
" It's much more reliable and extensible than simple autonomous agents."
)
human_command = Command(resume={"data": human_response})
events = graph.stream(human_command, config, stream_mode="values")
for event in events:
if "messages" in event:
event["messages"][-1].pretty_print()