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

大模型智能应用机器学习

关注我~第一时间学习如何更好地使用AI。

重要的不是我们是否会被AI替代,

而是我们要比被替代的人更懂AI。

前期导览:

从零开始学LangGraph(0):如何搭建基本的AI应用开发学习环境

从零开始学LangGraph(1):Chat Model --- 如何通过代码与模型对话(上)

从零开始学LangGraph(2):Message和Template —— 如何通过代码与模型对话(下)

从零开始学LangGraph(3):Tools --- 如何实现大模型与外部系统对接

从零开始学LangGraph(4):用Excel表格来理解LangGraph的基本工作原理

大家好,上一期我用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单元格内容。

picture.image

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

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

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

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

  
from typing import TypedDict  
  
class 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 的介绍(从零开始学LangGraph(3):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 random  
  
def 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,EDND  
  
  
graph = 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 StateGraph  
graph = 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,EDND  
  
graph.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,display  
display(Image(app.get\_graph().draw\_mermaid\_png()))

代码输出结果如下:

picture.image

从形式上来看,我们的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已经成功运行了~~

picture.image

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

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

—— END——

往期精华:

1.COZE教程

零基础搞定!萌新的 Coze 开源版保姆级本地部署指南

AI工作流编排手把手指南之一:Coze智能体的创建与基本设置

AI工作流编排手把手指南之二:Coze智能体的插件添加与调用

AI工作流编排手把手指南之三:Coze智能体的工作流

Agent | 工作流编排指南4:萌新友好的Coze选择器节点原理及配置教程

Agent | 工作流编排指南5:长文扩写自由 — Coze循环节点用法详解

Coze工作流编排指南6:聊天陪伴类智能体基本工作流详解-快来和玛奇玛小姐姐谈心吧~

PPT自由!Coze工作流 X iSlide插件-小白也能看懂的节点参数配置原理详解

2.MCP探索

Excel-MCP应用 | 自动提取图片数据到Excel的极简工作流手把手教程

markitdown-mcp联动Obsidian-mcp | 一个极简知识管理工作流

【15合1神器】不会代码也能做高级图表!这个MCP工具让我工作效率翻了不止三倍!

【效率翻倍】Obsidian自动待办清单实现:MCP联动Prompt保姆级教程(萌新3分钟上手)

萌新靠MCP实现RPA、爬虫自由?playwright-mcp实操案例分享!

高德、彩云MCP全体验:让Cherry Studio化身私人小助理的喂饭版指南!

3.Prompt设计

干货分享 | Prompt设计心法 - 如何3步做到清晰表达需求?

打工人看了流泪的Prompt设计原理,如何用老板思维让AI一次听懂需求?

不会Prompt还敢说自己会用DeepSeek?别怕!10分钟让你成为提示大神!

0
0
0
0
关于作者
关于作者

文章

0

获赞

0

收藏

0

相关资源
火山引擎大规模机器学习平台架构设计与应用实践
围绕数据加速、模型分布式训练框架建设、大规模异构集群调度、模型开发过程标准化等AI工程化实践,全面分享如何以开发者的极致体验为核心,进行机器学习平台的设计与实现。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论