从零开始学LangGraph:手把手教你搭建简易Graph

84 阅读10分钟

从零开始学LangGraph:手把手教你搭建简易Graph

【AI大模型教程】

大家好,上一期我用Excel表为例大概解释了一下LangGraph的工作原理,今天我们就来着手搭建一个简单的Graph,目的是通过实践来加深对State、Node、Edge这些概念的理解。本期内容较多,但是绝对干货,希望喜欢的朋友能一键三连!

关于State的一个补充

首先需要给大家解释一下,从严格意义上来讲,LangGraph中的State是由SchemaReducer共同组成的。

在State中,Schema是必须自定义的,因为它决定了Agent所需的业务字段、各业务字段要求的数据类型,而Reducer作为一种状态(字段值)更新的控制机制,如果用户不手动设置也没事,因为会被默认为“覆盖”。

出于循序渐进,尽快让大家上手的考虑,我所表述的“定义State”,主要指的是对Schema的定义,即对所有节点、边的输入模式的定义。后续我会在适当的位置再引入Reducer。

好了,下面我就正式进入Graph的搭建教程!

一、定义State

从表格到字典

正如上期所述,我们用LangGraph创建一个AI Agent,就像是设计一个Excel表格一样,是为了通过对相关数据进行处理以获得一个结果。

为了获得这个结果,在Excel中,我们需要对表格的表头字段进行定义,而在LangGraph中,我们则是对Agent的State进行定义。

同样想想Excel表格,每个表头字段填写在第一行,而第二行则代表了各个字段对应的值。如下图所示,“”职业“”对应A2单元格内容、“等级”对应B2单元格内容、“血量”对应C2单元格内容。

这种成对出现的数据形式,在编程中往往是用一种叫做字典(Dict) 的数据结构来储存的,其中,表头三个字段,被称为,而第二行单元格的内容,被称为键的。换言之,Dict是一种使用键值对来储存数据的结构。

而在LangGraph中,State其实也是一种字典,只不过为了保证相关运行数据类型的准确性以提升Agent性能,LangGraph中一般是用TypedDict来定义State。(也有其他方式,比如pydantic的数据模型)

考虑到本系列教程的定位,我们不会太纠结一些编程理论方面的东西,比如你不需要完全搞明白什么是Dict、TypedDict,你只需要知道TypedDict允许你对State所涉及的数据类型(是字符串还是整数、布尔值等)进行限定就行了。如果你真的很有兴趣,可以直接找个AI去问。

下面先给出定义State的代码如下,大家可以拿它跟前面Excel表格图片的内容比较一下。

from typing import TypedDictclass AppState(TypedDict):    char_class: str    char_level:int    char_HP:int

代码解读

from typing import TypedDict

第一行很好理解,从typing中引入TypedDict。

class AppState(TypedDict):

接着,我们定义一个名叫AppState的类(class),这个类就是我们所需要的State了。它的名字是你自己任意取得,想叫啥都可以,只要保证可读性即可。

同时,括号中填入TypedDict,意味着我们将这个类的类型定义为Type Dictionary,这样我们就可以对构成State的键值对的数据类型进行限制。

char_class: str    char_level:int    char_HP:int

接下来的三行缩进代码,分别为三个键值对,这就是State的主体了。这三个键值对的键(即冒号左边部分),与前面图中Excel表格内列示的三个表头字段,即职业、等级和血量,一一对应。

而冒号右边的内容,代表着我们对能填入这个键的数据的类型的限制,即:

  • • 职业:必须是字符串
  • • 等级:必须是整数
  • • 血量:必须是整数

至此,我们就创建了一个简单的State,是不是很简单。

二、创建Node函数

定义好了State,接着就需要创建Node函数。在LangGraph中,Node和一些特殊的Edge,本质上都是函数,需要先定义出来,然后再加入到Graph中。我们先来学习如何创建Node函数。

而在此之前,我建议大家回顾一下我前面对Tools的介绍,你就会发现很多内容都是相通的。换言之,我们仍然可以通过三个步骤来创建Node函数。

1.定义函数

从前面的State不难看出,我要做的Graph是一个与游戏角色相关的东西。由于我们现在做的是一个Hello World Graph,不想搞得太复杂,所以这里我打算搞两个Node,一个用来处理角色HP的变动(比如是否成功闪避攻击),一个用来展示角色当前状态。

先以第一个Node的函数为例,我们首先需要定义它:

def dodge_check_node(state: AppState) -> AppState:

这段代码定义了一个名为dodge_check_node的函数,这个函数有一个参数state,然后通过类型提示,指定state期望接收的数据类型,以及该函数应输出的数据类型。这个数据类型,就是我们前面定义好的类型为Typed Dictionary的state。

由于本例中我们只定义了一个state,即AppState,所以没啥好说的,输入输出都是它。但LangGraph其实允许我们创建多个不同State,比如公有私有、输入输出,允许我们进行非常灵活的state操作,这个后面再讲。

2.利用函数文档字符串进行Node描述

def dodge_check_node(state: AppState) -> AppState:    """检查闪避结果并处理HP变化    生成1-6的随机整数,如果大于3则闪避成功,否则HP减少特定数值    """

接下来是一段文档字符串(docstring)。跟创建Tools的时候一样,我们需要它来对函数进行描述,以帮助模型或Agent理解使用这个函数的用途。当然,因为我们目前还没引入LLM,所以写文档字符串只是为了跟大家说明有这个事。

3.编写函数体,设定Node的业务逻辑

接下来就是完成函数体的内容,写明Node的具体业务逻辑,比如我这里写了一个非常简单的闪避判定与血量扣减逻辑。因为我们需要用随机数,所以前面有一个对random库的引入。

import randomdef dodge_check_node(state: AppState) -> AppState:    """检查闪避结果并处理HP变化    生成1-6的随机整数,如果大于3则闪避成功,否则HP减少特定数值    """    dice_roll = random.randint(1, 6)        if dice_roll > 3:        print("你成功闪避了伤害")    else:        state['char_HP'] = state['char_HP'] - dice_roll        print(f"闪避失败!受到{dice_roll}点伤害")            return state

关于这段代码,有两个要点需要注意,首先:

state['char_HP'] = state['char_HP'] - dice_roll * 2

这里展示了LangGraph中使用State的基本方式,因为state本质是Dict,所以我们可以通过每个键值对的键去访问、修改相关的值。然后:

return state

函数结尾的return state,确保了Node函数的处理结果(如代码中对HP的值的修改),将保存到我们的state(已被指定为AppState)中。

下面给出第二个Node函数的代码,内容不再赘述:

def display_character_status(state: AppState) -> AppState:    """展示角色当前的各状态值"""        print("\n" + "="*30)    print("     角色状态信息")    print("="*30)    print(f"职业: {state['char_class']}")    print(f"等级: {state['char_level']}")    print(f"生命值: {state['char_HP']}")    print("="*30)        return state

三、创建并编译Graph

到此为止,我们就可以创建Graph了。有的朋友可能会问,Edge怎么没讲?因为如前所述,只有一些特殊的Edge才是函数,需要专门写代码,比如Conditional Edge,这个我后面再讲,对于普通的Edge,直接add就行,大家一看就懂。

创建Graph的代码非常简单,主要包括4个步骤,我先贴出整体的代码,然后逐项给大家解释:

from langgraph.graph import StateGraph,START,EDNDgraph = StateGraph(AppState)graph.add_node("dodge_check",dodge_check_node)graph.add_node("character_status",display_character_status)graph.add_edge(START, "dodge_check")graph.add_edge( "dodge_check", "character_status")graph.add_edge("character_status",END)app = graph.compile()

1.激活StateGraph

from langgraph.graph import StateGraphgraph = StateGraph(AppState)

首先,从LangGraph官方库中导入StateGraph,我们需要向StateGraph传入我们前面定义好的state,从而实例化一个Graph。

2.增加节点

graph.add_node("dodge_check",dodge_check_node)graph.add_node("character_status",display_character_status)

然后,一个一个的把前面写好的Node函数,以Node的形式加入到Graph里。方法就是使用add_node函数,传入两个参数,首先是字符串形式的节点名称,这个也是任意取的,然后就是前面已经定义好的节点对应的函数,代表了节点的动作。

3.连接节点

from langgraph.graph import START,EDNDgraph.add_edge(START, "dodge_check")graph.add_edge( "dodge_check", "character_status")graph.add_edge("character_status",END)

然后,就是把这些节点,根据刚才取好的名字都连接起来。

这里可以引入现成STARTEND节点,也可以使用set_entry_point()set_fiish_point()这种函数来指定起始点,两种方式都可以

graph.set_entry_point("dodge_check")graph.add_edge( "dodge_check", "character_status")graph.set_finish_point("character_status")

4.编译Graph

app = graph.compile()

这是简单但最重要的一步,我们需要使用cpmpile方法来编译我们的Graph,这样我们才能使用它。

Graph的可视化

在使用Graph之前,我们可以将它可视化一下,来看看我们的成果。我直接提供一段代码供大家使用:

from IPython.display import Image,displaydisplay(Image(app.get_graph().draw_mermaid_png()))

代码输出结果如下:

从形式上来看,我们的Graph已经成功地构建起来了。当然,这个Graph非常简陋,除了首尾之外,只有两个直接连接的Node。但显而易见的,只要掌握了方法原理,复杂的Graph我们也能轻松拿捏。

下面我们来跑一下这个Graph看看效果。

使用Graph

使用Graph的方法也非常简单,就是调用invoke,并按state的定义向其传入参数字典即可。

先回顾下我们定义好的state:

class AppState(TypedDict):    char_class: str    char_level:int    char_HP:int

因此,我们可以向invoke传入一个字典,它的信息代表着一个等级为5,血量为10的法师。

result = app.invoke({"char_class":"法师","char_level":5,"char_HP":10})

可以看到,我们的Graph已经成功运行了~~

如图所示,第一部分关于受到伤害的信息,来自于第一个Node,第二部分的角色状态信息,则来自第二个Node。可以看到,我们一开始设定的state中的10点HP,已经因为在第一个Node中受到了2点伤害,被更新为了8点。

好了,以上就是本期的主要内容,后续我会在这个Graph的基础上,逐渐引入更加复杂的要素,带着大家构建出真正的AI Agent。希望喜欢的朋友不要忘了一键三连,祝大家玩的开心!