LangGraph学习——快速入门
构建langgraph聊天机器人的基本流程
创建一个 StateGraph
首先创建一个 StateGraph。一个 StateGraph
对象将我们的聊天机器人结构定义为“状态机”。我们将添加 节点
来表示 LLM 和聊天机器人可以调用的函数,并添加 边
来指定机器人应如何在这些函数之间进行转换。
1 | from typing import Annotated |
在 langgraph 中,状态会在图的各个节点之间传递。当一个节点产生新的消息时,它会更新 State 中的 messages 字段。
我们的图现在可以处理两个关键任务
- 每个
节点都可以接收当前状态作为输入,并输出状态的更新。 - 对
消息的更新将追加到现有列表而不是覆盖它,这得益于与Annotated语法一起使用的预构建add_messages函数。
langgraph中每个消息对象通常包含以下关键属性:
- role : 一个字符串,标识消息的发送者(例如 ‘human’ , ‘ai’ , ‘system’ )。
- content : 消息的具体内容,通常是字符串,但也可以是更复杂的结构(例如,用于多模态输入)。
- id : 一个可选的唯一标识符。
Annotated 的作用 : 通过使用 Annotated[list, add_messages] ,你改变了这个默认行为。 add_messages 函数(由 langgraph 提供或由你自定义)的逻辑是 追加 而不是覆盖。所以,当一个新节点返回消息时, langgraph 会调用 add_messages 函数,将新消息 追加 到现有 messages 列表的末尾。
定义一个聊天模型
两种方法:
1.使用 init_chat_model(通用高层封装)
这是一个通用的辅助函数,旨在提供一个统一的接口来初始化来自 不同提供商
的聊天模型。
init_chat_model — 🦜🔗 LangChain 文档 — init_chat_model — 🦜🔗 LangChain documentation
2.使用如ChatOpenAI (特定于提供商的类) 这是一个专门为 OpenAI API 设计的类,提供了对 OpenAI 模型所有功能的完全访问。
1 | from langchain.chat_models import init_chat_model |
添加一个节点
现在我们可以将聊天模型集成到一个简单的节点中
1 | #定义节点chatbot |
chatbot 节点函数如何将当前 状态
作为输入,并返回一个包含更新的 消息
列表的字典,键为“messages”。这是所有 LangGraph 节点函数的基本模式。
我们 状态 中的 add_messages 函数会将 LLM
的响应消息追加到状态中已有的消息之后。
添加一个 入口 点
添加一个 入口
点,以告诉图每次运行时从何处开始工作
1 | graph_builder.add_edge(START, "chatbot") |
编译图
在运行图之前,我们需要对其进行编译。我们可以通过在图构建器上调用
compile() 来完成。这将创建一个
CompiledGraph,我们可以在我们的状态上调用它。
1 | graph = graph_builder.compile() |
可视化图
您可以使用 get_graph 方法和其中一个“绘图”方法(例如
draw_ascii 或 draw_png)来可视化图。这些
draw 方法都需要额外的依赖项。
1 | from IPython.display import Image, display |
运行聊天机器人
运行聊天机器人
1 | def stream_graph_updates(user_input: str): |
graph.stream() 是 LangGraph 的核心功能之一。它会执行整个图(Graph),但不是一次性返回最终结果,而是像视频流一样,一步一步地返回中间过程的更新。这使得您可以实时看到模型生成内容的每一个部分。
添加网页搜索工具
获取Tavily api
Tavily 的搜索 API 是一款专为 AI 代理 (LLM) 构建的搜索引擎,能够快速提供实时、准确和基于事实的结果。
每月 1,000 次免费搜索
Tavily Search | 🦜️🔗 LangChain 框架
添加工具
1 | from langchain_tavily import TavilySearch |
定义图
在LLM上添加bind_tools。这让LLM知道如果它想使用搜索引擎,应使用正确的JSON格式。
定义聊天模型llm(代码同上)
将tools整合到StateGraph中
1 | from typing import Annotated |
创建一个运行工具的函数
现在,创建一个函数来运行被调用的工具。通过将工具添加到一个名为BasicToolNode的新节点来完成,该节点检查状态中的最新消息,如果消息包含tool_calls,则调用工具。它依赖于LLM的tool_calling支持,该支持在Anthropic、OpenAI、Google
Gemini以及许多其他LLM提供商中可用。
1 | # 定义 __call__ 方法,让这个类的实例可以像函数一样被调用 |
call 是 Python 中一个非常特殊的“魔术方法”(magic method)。它的作用是 让一个类的实例(对象)能够像函数一样被调用 。
这在 langgraph 中是一种常见且核心的设计模式。它的含义是:
- 节点即函数 : BasicToolNode 的实例(比如 tool_node )本身就代表了图中的一个可执行节点。
- 执行逻辑 :当 langgraph 的状态机运行到这个 tool_node 节点时,它会直接“调用”这个节点对象,并把当前的状态( inputs 字典)传递给它
可以使用LangGraph预构建的ToolNode。
1 | from langgraph.prebuilt import ToolNode |
定义conditional_edges
添加了工具节点后,现在您可以定义conditional_edges。
边(Edges)将控制流从一个节点路由到下一个节点。条件边(Conditional
edges)从单个节点开始,通常包含“if”语句,根据当前图状态路由到不同的节点。这些函数接收当前的图state并返回一个字符串或字符串列表,指示接下来要调用哪个(或哪些)节点。
接下来,定义一个名为route_tools的路由函数,它检查聊天机器人输出中的tool_calls。通过调用add_conditional_edges将此函数提供给图,这会告诉图,无论何时chatbot节点完成,都要检查此函数以确定下一步去哪里。
如果存在工具调用,条件将路由到tools;如果不存在,则路由到END。由于条件可以返回END,因此这次您不需要明确设置finish_point。
1 | def route_tools( |
可以使用预构建的tools_condition代替route_tools以使其更简洁。
tools_condition函数在聊天机器人需要使用工具时返回 “tools”,如果可以不使用响应则返回 “END”。
1 | from langgraph.prebuilt import tools_condition |
可视化图
如上
向机器人提问
现在您可以向聊天机器人提出超出其训练数据范围的问题。
如上
添加记忆功能
LangGraph
通过持久性检查点解决了这个问题。如果您在编译图时提供一个checkpointer,并在调用图时提供一个thread_id,LangGraph
会在每一步之后自动保存状态。当您使用相同的thread_id再次调用图时,图会加载其保存的状态,允许聊天机器人从上次中断的地方继续。
我们稍后会看到,检查点比简单的聊天记忆功能强大得多——它允许您随时保存和恢复复杂状态,用于错误恢复、人工干预工作流、时间旅行交互等。但首先,让我们添加检查点以实现多轮对话。
创建 MemorySaver
检查点
1 | from langgraph.checkpoint.memory import MemorySaver |
这是一个内存中的检查点,方便本教程使用。然而,在生产应用程序中,您可能会将其更改为使用
SqliteSaver 或 PostgresSaver
并连接数据库。
编译图
使用提供的检查点编译图,图在遍历每个节点时将对 State
进行检查点。
1 | graph = graph_builder.compile(checkpointer=memory) |
与您的聊天机器人互动
选择一个线程作为此对话的键。
thread_id决定对话窗口
1
config = {"configurable": {"thread_id": "1"}}
调用您的聊天机器人
1
2
3
4
5
6
7
8
9
10user_input = "我是谁"
# The config is the **second positional argument** to stream() or invoke()!
events = graph.stream(
{"messages": [{"role": "user", "content": user_input}]},
config,
stream_mode="values",
)
for event in events:
event["messages"][-1].pretty_print()
添加人工干预
代理可能不可靠,并且可能需要人工输入才能成功完成任务。同样,对于某些操作,您可能需要在运行前要求人工批准,以确保一切按预期运行。
LangGraph 的持久化层支持人工干预工作流,允许根据用户反馈暂停和恢复执行。此功能的主要接口是interrupt函数。在节点内调用interrupt将暂停执行。通过传入Command,可以恢复执行并接收来自人工的新输入。interrupt在功能上类似于
Python 的内置input(),但有一些注意事项。
添加human_assistance工具
将human_assistance工具添加到聊天机器人。此工具使用interrupt从人工接收信息。
1 | # 添加人工干预功能 |
简单来说, 调用哪个工具,以及何时调用,完全是由大语言模型(LLM)根据你给它的指令(Prompt)来决定的。
1 | tool = TavilySearch(max_results=2) |
定义chatbot
1 | def chatbot(state: State): |
中断安全性的断言 ( assert ) : assert len(message.tool_calls) <= 1 是在实现人工干预时一个非常重要的 安全措施 。
- 问题 : 现代 LLM 支持并行工具调用(一次请求执行多个工具)。但如果其中一个工具是 human_assistance 并触发了中断,整个图会暂停。当人工操作完成后,图会从中断点恢复。此时,如果不对工具调用数量做限制,LangGraph 可能会重新尝试执行所有在中断前请求的工具,导致已经执行过的工具被再次调用。
- 解决方案 : 这个断言强制要求 LLM 在每一步最多只能请求调用一个工具。这样就保证了当中断发生并恢复后,不会有重复执行工具的风险,确保了流程的稳定性和可预测性。
编译图
1 | memory = MemorySaver() |
调用聊天机器人并中断
1 | user_input = "我需要一些关于构建 AI 代理的专家指导。你能帮我请求协助吗?" |
聊天机器人生成了一个工具调用,但随后执行被中断。如果您检查图状态,您会看到它停止在工具节点
1 | snapshot = graph.get_state(config) |
恢复执行
要恢复执行,请传入一个包含工具所需数据的Command对象。此数据的格式可以根据需要进行自定义。对于本示例,请使用一个带有键"data"的字典(由human_assistance决定)
1 | human_response = ( |
1.工具的定义 ( human_assistance function):
- 当 LLM 调用 human_assistance 工具时,这个函数被执行。
- 函数内部, interrupt() 被调用,导致图暂停,并等待人工输入。
- 在图恢复后, interrupt() 函数会返回一个值,这个值就是您通过 Command(resume=…) 注入的内容,也就是 {“data”: human_response} 。
- 因此, human_assistance 函数中的 human_response 变量实际上就等于 {“data”: human_response} 。
- 最后, return human_response[“data”] 从这个字典中提取出 “data” 键对应的值 ,并将其作为 human_assistance 工具的最终返回结果。
2.恢复指令 ( Command(resume=…) ):
- 当您构建 Command(resume={“data”: human_response}) 时,您正在创建一个符合 human_assistance 函数期望的结构。
- 您将人工回复包装在一个字典里,并使用 “data” 作为键。
- 这个结构被传递回 interrupt() ,然后被 human_assistance 函数接收和解析。
因为 human_assistance 函数的 return 语句期望从返回的字典中访问 “data” 键,所以我们在恢复执行时必须提供一个具有相同结构的字典。这是为了确保数据能够正确地在中断和恢复的过程中传递。
在 LangGraph 中,Command
是一个用于控制图执行流程、更新状态、实现人机交互的核心类。它支持以下四个参数:
| 参数名 | 类型 | 说明 |
|---|---|---|
update |
dict |
用于更新图的状态(state)。例如:Command(update={"foo": "bar"})。 |
resume |
Any |
与 interrupt()
配合使用,用于恢复被中断的图执行,并传递用户输入。 |
goto |
str 或 Send 或
List[str|Send] |
控制下一步要执行的节点,支持跳转到指定节点、多个节点序列,或使用
Send 对象。 |
graph |
str |
可选,指定命令作用的图。默认是当前图,也可以设为
Command.PARENT 表示父图。 |
自定义状态
在本教程中,您将向状态添加额外字段,以定义复杂行为,而无需依赖消息列表。聊天机器人将使用其搜索工具查找特定信息,并将其转发给人工进行审查。
向状态添加键
通过向状态添加 name 和 birthday
键,更新聊天机器人以研究实体的生日
1 | class State(TypedDict): |
将此信息添加到状态中,可以使其轻松被其他图节点(例如存储或处理信息的下游节点)以及图的持久层访问。
在工具内部更新状态
现在,在 human_assistance
工具内部填充状态键。这允许人工在信息存储到状态之前对其进行审查。使用 Command
从工具内部发出状态更新。
1 | # 从 langchain_core.messages 导入 ToolMessage,用于创建工具调用的响应消息 |
图的其余部分保持不变。
提示聊天机器人调用人工审查
提示聊天机器人查找 LangGraph
库的“生日”,并在其获取所需信息后,指示聊天机器人使用
human_assistance 工具。通过在工具参数中设置
name 和
birthday,您将强制聊天机器人为这些字段生成提议。
1 | user_input = ( |
我们再次在 human_assistance 工具中触发了
interrupt。
添加人工协助
聊天机器人未能识别正确的日期,因此为其提供信息
1 | human_command = Command( |
请注意,这些字段现在已反映在状态中
1 | snapshot = graph.get_state(config) |
这使得下游节点(例如,进一步处理或存储信息的节点)可以轻松访问它们。
时间功能(从之前的某个状态开始)
在典型的聊天机器人工作流程中,用户与机器人进行一次或多次交互以完成任务。记忆和人工干预功能可以为图状态启用检查点并控制未来的响应。
如果您希望用户能够从之前的响应开始并探索不同的结果,该怎么办?或者,如果您希望用户能够回溯聊天机器人的工作以纠正错误或尝试不同的策略,这在自主软件工程师等应用程序中很常见,那又该怎么办?
您可以使用 LangGraph 内置的时光旅行功能创建这些类型的体验。
回溯您的图
通过使用图的get_state_history方法获取检查点来回溯您的图。然后,您可以从之前的这个时间点恢复执行。
1 | # 初始化一个变量 to_replay 为 None,它将用于存储我们想要“时间旅行”回去的特定状态。 |
图的每一步都会保存检查点。这跨越了调用,因此您可以回溯整个线程的历史。
从特定时间点加载状态
从to_replay状态恢复。从这一点恢复将接下来调用action节点。
1 | print(to_replay.next) |
检查点的to_replay.config包含一个checkpoint_id时间戳。提供此checkpoint_id值会告诉
LangGraph 的检查点器从该时间点加载状态。
1 | for event in graph.stream(None, to_replay.config, stream_mode="values"): |
运行本地服务器
安装 LangGraph CLI
1 | # Python >= 3.11 is required. |
创建 LangGraph 应用 🌱
从 new-langgraph-project-python
模板 或 new-langgraph-project-js
模板
创建一个新应用。此模板展示了一个单节点应用程序,您可以根据自己的逻辑进行扩展。
1 | langgraph new . --template new-langgraph-project-python |
使用
langgraph new而不指定模板,系统将显示一个交互式菜单,您可以从中选择可用的模板列表。
使用uv安装依赖项
uv 是一个用 Rust 编写的极速 Python 包和项目管理器 。它旨在解决传统 Python 包管理工具(如 pip 、 poetry 等)在速度和效率方面的痛点,提供更快的安装、依赖解析和环境管理。
1 | pip install uv#安装uv |
运行uv sync会根据
pyproject.toml的依赖创建虚拟环境并安装依赖
pyproject.toml文件是 Python 项目中用于统一配置项目元数据、构建系统、依赖管理和各种工具设置的标准化文件。它通常用于替代旧的 requirements.txt 文件,提供更现代和集中的项目配置方式。
创建一个 .env 文件
您将在新 LangGraph 应用的根目录下找到一个 .env.example
文件。在新 LangGraph 应用的根目录下创建一个 .env 文件,并将
.env.example 文件的内容复制到其中,填入所需的 API
密钥。
添加环境变量如LANGSMITH_API_KEY,OPENAI_API_KEY等
启动 LangGraph 服务器
在本地启动 LangGraph API 服务器
1 | langgraph dev |
示例输出
1 | > Ready! |
LangGraph 服务器(如您通过 langgraph dev 命令启动的服务器)的主要作用是提供一个运行环境和接口,用于开发、测试、部署和管理基于 LangGraph 构建的 AI 代理和应用程序。具体来说,它有以下几个主要用途:
API 接口暴露 :它将您用 LangGraph 定义的复杂代理逻辑(即图结构)通过标准的 RESTful API 接口暴露出来。这意味着其他应用程序、前端界面或者其他服务可以通过 HTTP 请求与您的 LangGraph 代理进行交互,而无需直接集成 LangGraph 的 Python 代码。
简化部署 :通过将 LangGraph 应用程序打包成一个可运行的服务,您可以更容易地将其部署到云服务器、容器(如 Docker)或其他生产环境中。这使得 LangGraph 代理可以作为一个独立的微服务运行,方便扩展和管理。
开发和调试便利 :
- 实时预览和调试 :服务器通常会提供一个 Studio UI(如您在 http://127.0.0.1:2024/studio 看到的),让开发者能够可视化地查看代理的图结构、执行流程、状态变化和中间步骤,这对于理解和调试复杂的代理行为至关重要。
- API 文档 :自动生成的 API 文档(如 http://127.0.0.1:2024/docs )提供了所有可用接口的详细说明和交互式测试功能,极大地加速了开发和集成过程。
状态管理和持久化 :LangGraph 代理通常涉及复杂的状态管理。服务器可以负责处理这些状态的持久化,确保代理在多次交互之间能够记住上下文和历史信息。
在 LangGraph Studio 中测试您的应用程序
LangGraph
Studio 是一个专门的 UI,您可以连接到 LangGraph API
服务器,以便在本地可视化、交互和调试您的应用程序。通过访问
langgraph dev 命令输出中提供的 URL,在 LangGraph Studio
中测试您的图。
1 | > - LangGraph Studio Web UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024 |
参考资料
官方教程,但是英文https://academy.langchain.com/collections