到目前为止,我们只有一个简单的状态:messages。紧靠这个消息状态,其实已经可以做很多事了。但是你想做些更复杂的功能,可以往状态中添加其他字段(作为前端,这是非常熟悉的状态管理机制)。
现在,我们将展示一个新的场景,在这个场景中,聊天机器人正在使用其搜索工具查找特定信息,并将这些信息转发给人类进行审查。
继续我们前一节的代码,改改状态这个类:
class State(TypedDict):
messages: Annotated[list, add_messages]
name: str
birthday: str
将姓名和生日添加到状态中,其他图节点(例如存储或处理信息的下游节点)以及图的持久层都可以轻松访问它。
在这里,我们将在human_assistance工具进行人类交互相关的操作。首先,AI会带着入参进来工具,询问人类,参数中的name和birthday是不是确实是LangGraph的诞生时间,这里使用interpt接收人类用户的回复,判断时候更新State。这里State已经有三个字段了,包括LangGraph的名和发布日期,以及回复给AI的结果。我们将再次使用Command,这次是从我们的工具内部发出状态更新。在此处阅读有关Command的用例。
@tool
def human_assistance(name: str, birthday: str, tool_call_id: Annotated[str, InjectedToolCallId]) -> str:
"""Request assistance from a human."""
human_response = interrupt({
"question": "Is this correct?",
"name": name,
"birthday": birthday,
})
# 更新状态State
print("human_assistance工具节点接收到的消息是:" + human_response.get("correct", ""))
if human_response.get("correct", "").lower().startswith("y"):
verified_name = name
verified_birthday = birthday
response = "Human confirmed the information is correct. Please provide a summary."
else:
verified_name = human_response.get("name", name)
verified_birthday = human_response.get("birthday", birthday)
response = f"Human provided corrected data: {human_response}. Please update and summarize."
state_update = {
"name": verified_name,
"birthday": verified_birthday,
"messages": [
ToolMessage(response, tool_call_id=tool_call_id)
],
}
return Command(update=state_update)
工具做好了,我们开始模拟用户提问。user_input输入问题,关于LangGraph的发布时间,并要求AI调用人类协助工具。
user_input = (
"Can you look up when LangGraph was released? "
"When you have the answer, use the human_assistance tool for review."
)
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 Message =================================
Can you look up when LangGraph was released? When you have the answer, use the human_assistance tool for review.
chatbot节点消息: content='' additional_kwargs={'tool_calls': [{'id': 'call_0_200adfca-e0a6-4ccb-8963-92dd53e43c35', 'function': {'arguments': '{"query":"LangGraph release date"}', 'name': 'tavily_search_results_json'}, 'type': 'function', 'index': 0}], 'refusal': None} response_metadata={'token_usage': {'completion_tokens': 25, 'prompt_tokens': 268, 'total_tokens': 293, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 256}, 'prompt_cache_hit_tokens': 256, 'prompt_cache_miss_tokens': 12}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_3a5770e1b4_prod0225', 'finish_reason': 'tool_calls', 'logprobs': None} id='run-0ea27cfd-3d94-4206-b465-4d0177cbe208-0' tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'LangGraph release date'}, 'id': 'call_0_200adfca-e0a6-4ccb-8963-92dd53e43c35', 'type': 'tool_call'}] usage_metadata={'input_tokens': 268, 'output_tokens': 25, 'total_tokens': 293, 'input_token_details': {'cache_read': 256}, 'output_token_details': {}}
================================== Ai Message ==================================
Tool Calls:
tavily_search_results_json (call_0_200adfca-e0a6-4ccb-8963-92dd53e43c35)
Call ID: call_0_200adfca-e0a6-4ccb-8963-92dd53e43c35
Args:
query: LangGraph release date
================================= Tool Message =================================
Name: tavily_search_results_json
[{"title": "langgraph · PyPI", "url": "https://pypi.org/project/langgraph/", "content": "LangGraph is a library for building stateful, multi-actor applications with LLMs, used to create agent and multi-agent workflows. The simplest way to create a tool-calling agent in LangGraph is to use create_react_agent: # Define the tools for the agent to use # Define the tools for the agent to use # This means that after `tools` is called, `agent` node is called next. workflow.add_edge(\"tools\", 'agent') Normal edge: after the tools are invoked, the graph should always return to the agent to decide what to do next LangGraph adds the input message to the internal state, then passes the state to the entrypoint node, \"agent\". langgraph-0.2.70-py3-none-any.whl (149.7 kB view details)Uploaded Feb 6, 2025 Python 3", "score": 0.68571854}, {"title": "January 2024 - LangChain - Changelog", "url": "https://changelog.langchain.com/?date=2024-01-01", "content": "LangChain - Changelog LangChain LangSmith LangGraph Blog Case Studies LangChain Academy Community Experts Changelog LangChain LangSmith LangGraph LangChain LangSmith LangGraph LangChain Changelog This splits up the previous langchain package into three different... LangChain 🚀 OpenGPTs ----------- After OpenAI’s DevDay, we launched a project inspired by GPTs and the Assistants API. LangChain 📊 LangChain Benchmarks for Python ---------------------------------- LangChain benchmarks is a Python package with associated datasets to facilitate experimentation and benchmarking of different cognitive architectures. LangSmith SaaS ✍️ Data Annotation Queues in LangSmith -------------------------------------- We've launched our newest feature, data annotation queues, in LangSmith (our SaaS platform for managing your LangChain applications). Subscribe By clicking subscribe, you accept our privacy policy and terms and conditions.", "score": 0.33141232}]
chatbot节点消息: content='' additional_kwargs={'tool_calls': [{'id': 'call_0_59e1b6d7-32a3-43a1-a800-ea48e408858b', 'function': {'arguments': '{"name": "LangGraph", "birthday": "2025-02-06"}', 'name': 'human_assistance'}, 'type': 'function', 'index': 0}], 'refusal': None} response_metadata={'token_usage': {'completion_tokens': 32, 'prompt_tokens': 743, 'total_tokens': 775, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 704}, 'prompt_cache_hit_tokens': 704, 'prompt_cache_miss_tokens': 39}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_3a5770e1b4_prod0225', 'finish_reason': 'tool_calls', 'logprobs': None} id='run-087d1d8a-151d-4e7d-a32c-2ae3a9ba9f4f-0' tool_calls=[{'name': 'human_assistance', 'args': {'name': 'LangGraph', 'birthday': '2025-02-06'}, 'id': 'call_0_59e1b6d7-32a3-43a1-a800-ea48e408858b', 'type': 'tool_call'}] usage_metadata={'input_tokens': 743, 'output_tokens': 32, 'total_tokens': 775, 'input_token_details': {'cache_read': 704}, 'output_token_details': {}}
================================== Ai Message ==================================
Tool Calls:
human_assistance (call_0_59e1b6d7-32a3-43a1-a800-ea48e408858b)
Call ID: call_0_59e1b6d7-32a3-43a1-a800-ea48e408858b
Args:
name: LangGraph
birthday: 2025-02-06
上述结果可以看出,AI使用tavily搜索工具帮我们搜索了相关网页,AI分析网页结果后得出LangGraph的发布日期是2025-02-06,这显然是不对的。
接下来,我们模拟人类用户输入:
# 这里模拟人类输入
human_command = Command(
resume={
"name": "LangGraph",
"birthday": "Jan 17, 2024",
},
)
events = graph.stream(human_command, config, stream_mode="values")
for event in events:
if "messages" in event:
event["messages"][-1].pretty_print()
human_command模拟了一个人类的输入,提供一个resume对象,包含name和birthday两个属性。AI接受Command后,以这两个字段作为参数返回给human_assistance工具,在工具中经过判断后,继续执行后续的节点。输出结果如下:
================================== Ai Message ==================================
Tool Calls:
human_assistance (call_0_59e1b6d7-32a3-43a1-a800-ea48e408858b)
Call ID: call_0_59e1b6d7-32a3-43a1-a800-ea48e408858b
Args:
name: LangGraph
birthday: 2025-02-06
human_assistance工具节点接收到的消息是:
================================= Tool Message =================================
Name: human_assistance
Human provided corrected data: {'name': 'LangGraph', 'birthday': 'Jan 17, 2024'}. Please update and summarize.
chatbot节点消息: content='The corrected release date for LangGraph is **January 17, 2024**. This update has been reviewed and confirmed by a human.' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 28, 'prompt_tokens': 812, 'total_tokens': 840, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 704}, 'prompt_cache_hit_tokens': 704, 'prompt_cache_miss_tokens': 108}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_3a5770e1b4_prod0225', 'finish_reason': 'stop', 'logprobs': None} id='run-ec0a59b2-cddc-4303-920b-d6d526a51a71-0' usage_metadata={'input_tokens': 812, 'output_tokens': 28, 'total_tokens': 840, 'input_token_details': {'cache_read': 704}, 'output_token_details': {}}
================================== Ai Message ==================================
The corrected release date for LangGraph is **January 17, 2024**. This update has been reviewed and confirmed by a human.
Process finished with exit code 0
这是一次人类和AI合作的精彩之旅,部分由AI判断,部分由人工判断,从而修改图的State状态,在未来庞大的工程中,状态应该是随着业务流不断变化的(在一个thread_id内)。完整代码:
import getpass
import os
from os import stat_result
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]}")
# tvly-XXXXXXXXXXXXXXXXXXXXXXXXXX
_set_env("TAVILY_API_KEY")
from langchain_community.tools.tavily_search import TavilySearchResults
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 InjectedToolCallId, tool
from langchain_core.messages import ToolMessage
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]
name: str
birthday: str
graph_builder = StateGraph(State)
@tool
def human_assistance(name: str, birthday: str, tool_call_id: Annotated[str, InjectedToolCallId]) -> str:
"""Request assistance from a human."""
human_response = interrupt({
"question": "Is this correct?",
"name": name,
"birthday": birthday,
})
# 更新状态State
print("human_assistance工具节点接收到的消息是:" + human_response.get("correct", ""))
if human_response.get("correct", "").lower().startswith("y"):
verified_name = name
verified_birthday = birthday
response = "Human confirmed the information is correct. Please provide a summary."
else:
verified_name = human_response.get("name", name)
verified_birthday = human_response.get("birthday", birthday)
response = f"Human provided corrected data: {human_response}. Please update and summarize."
state_update = {
"name": verified_name,
"birthday": verified_birthday,
"messages": [
ToolMessage(response, tool_call_id=tool_call_id)
],
}
return Command(update=state_update)
tool = TavilySearchResults(max_results=2)
tools = [tool, human_assistance]
llm = ChatOpenAI(
max_retries=2,
base_url="https://api.deepseek.com/v1",
api_key="sk-XXXXXXXXXXXXXXXXXXXXXXXXX", # 替换为你的API密钥
model="deepseek-chat" # 根据实际模型名称修改
)
llm_with_tools = llm.bind_tools(tools)
def chatbot(state: State):
message = llm_with_tools.invoke(state["messages"])
print("chatbot节点消息:", message) # 检查是否包含正确的tool_calls
# 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]}
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 = (
"Can you look up when LangGraph was released? "
"When you have the answer, use the human_assistance tool for review."
)
config = {"configurable": {"thread_id": "1"}}
initial_state = {
"messages": [{"role": "user", "content": user_input}],
"name": "", # 初始化为空字符串
"birthday": ""
}
events = graph.stream(
initial_state,
config,
stream_mode="values",
)
for event in events:
if "messages" in event:
event["messages"][-1].pretty_print()
# 这里模拟人类输入
human_command = Command(
resume={
"name": "LangGraph",
"birthday": "Jan 17, 2024",
},
)
events = graph.stream(human_command, config, stream_mode="values")
for event in events:
if "messages" in event:
event["messages"][-1].pretty_print()
除了人工协助图的状态外,也可以在代码中读取和修改代码:
graph.update_state(config, {"name": "LangGraph (library)"})
snapshot = graph.get_state(config)
{k: v for k, v in snapshot.values.items() if k in ("name", "birthday")}
1979

被折叠的 条评论
为什么被折叠?



