LangGraph背景

但是如果我们不想依赖平台,而是要自己开发一个带有工作流的 AI 应用,这时最好的方法就是借助框架来完成,比如前两年比较火的 LangChain,可以做出一条后面这样的链。

DeepSeek  LangGraph 学习_DeepSeek

所以这个框架为什么叫 LangChain 也很好理解了,核心就是提供了 Chain(链)的功能。需要注意的是这条链在方向上来说是单向的,不能够向回流或者循环。

不过随着 AI 逐渐深入到业务,在落地一些应用的时候,大家就发现使用这种单向的链,有些应用搞不定,比如之前讲过的 AgenticRAG,也有人叫 GraphRAG。因为这样的应用不仅要有分支的功能,还要具备循环的功能。这时就需要用图结构来表示业务了。

DeepSeek  LangGraph 学习_DeepSeek_02

去年 LangChain 开发了一个扩展库,取名叫 LangGraph。这个库功能很强大,因为能够实现图的方式,解决 AgenticRAG 那是小菜一碟。

LangGraph编程学习

pip install langgraph

DeepSeek  LangGraph 学习_DeepSeek_03

from langgraph.graph import StateGraph, START,END

def supermarket(state):
    return {"ret": "{}买到了".format(state["ingredients"])}

if __name__ == "__main__":
    sg = StateGraph(dict)

    # 定义第一个节点
    sg.add_node("supermarket", supermarket)

    # 定义起始边
    sg.add_edge(START, "supermarket")
    # 定义结束边
    sg.add_edge("supermarket", END)

    graph = sg.compile()
    ret = graph.invoke({"ingredients": "羊排"})

    print(ret)

在代码的第 7 行,首先使用 StateGraph 方法定义了一个 Graph,可以理解为类似 Dify 创建了一个工作流。

DeepSeek  LangGraph 学习_DeepSeek_04

DeepSeek  LangGraph 学习_DeepSeek_05

from langgraph.graph import StateGraph, START,END

def supermarket(state):
    print("supermarket")
    return {"ret": "{}买到了".format(state["ingredients"])}

def recipe(state):
    print("recipe")
    return {"ret": "搜到了菜谱"}

def cooking(state):
    print("cooking")
    return {"ret": "做了一道菜"}

if __name__ == "__main__":
    sg = StateGraph(dict)

    # 定义节点
    sg.add_node("supermarket", supermarket)
    sg.add_node("recipe", recipe)
    sg.add_node("cooking", cooking)

    # 定义起始边
    sg.add_edge(START, "supermarket")

    # 定义普通边
    sg.add_edge("supermarket", "recipe")
    sg.add_edge("recipe", "cooking")

    # 定义结束边
    sg.add_edge("cooking", END)

    graph = sg.compile()
    ret = graph.invoke({"ingredients": "羊排"})

    print(ret)

DeepSeek  LangGraph 学习_DeepSeek_06

因此如果想让“羊排”在节点间流转,应该在每一步都保存一下。但是基于目前的机制还有一个问题,那就是第一个节点的输出,同样也只能在第二个节点中拿到,在第三个节点中就拿不到了。

如果我想把每一步的输出也都保存下来,那代码应该这么写:

class State(TypedDict):
    ingredients: str
    ret: list

def supermarket(state):
    print("supermarket")
    return {
        "ingredients": state["ingredients"],
        "ret": ["{}买到了".format(state["ingredients"])]
    }

def recipe(state):
    print("recipe")
    last_ret = state["ret"]
    return {
        "ingredients": state["ingredients"],
        "ret": last_ret + ["搜到了红烧{}的菜谱".format(state["ingredients"])]
    }

def cooking(state):
    print("cooking")
    last_ret = state["ret"]
    return {
        "ingredients": state["ingredients"],
        "ret": last_ret + ["做了一道红烧{}".format(state["ingredients"])]
    }

一共有两处改动。第一,State 我不直接用 dict 类型了,而是写一个 State 类,里面存放两个变量,一个是每一步都需要的 ingredients 变量,也就是“羊排”。另一个修改则是每一步的返回值 ret,由于需要存多个 ret,因此需要把它定义成列表。

DeepSeek  LangGraph 学习_DeepSeek_07

GraphRAG

GraphRAG 的核心是知识图谱 + 图搜索

Graph RAG 的第一步,就是从非结构化文本中提取信息,构建一个知识图谱。这个过程可以通过 LLM 来完成。

DeepSeek  LangGraph 学习_DeepSeek_08

DeepSeek  LangGraph 学习_DeepSeek_09

提取代码描述

像是代码这种文本,其结构特点是由一个个的函数组成。因此对于这样的文本去构建知识图谱,是非常困难的。因为我们不管怎么切割文本,也都很难保证函数的完整性。除非将函数截成一张张的图片,然后利用视觉模型进行识别,然后再入库等,这就很麻烦了。

因此我们最好是先做一下预处理,用 LLM 将每一个代码文件的描述提取出来,基于描述文本再去构建知识图谱就会简单得多。

使用的Cursor 对文件进行的描述,具体方法是,我们在 Cursor 中打开该项目,然后点击打开一个文件后,按住 Ctrl + L,就会召唤出对话模式。此时我们贴入提示词即可,提示词如下:

DeepSeek  LangGraph 学习_DeepSeek_10

依次将所有文件的描述全部提取出来,放入到一个 txt 文件中,留着待会使用 GraphRAG 构建知识图谱使用。

安装GraphRAG

DeepSeek  LangGraph 学习_DeepSeek_11

编辑配置文件settings.yaml

DeepSeek  LangGraph 学习_DeepSeek_12

将 models 里的 default_chat_model 以及 default_embedding_model 的信息从 OpenAI 改成我们自己国内的,比如 DeepSeek。当然如果你的服务器能访问 openai,那直接使用 OpenAI 也行。

其中 api_base、api_key 和 model 需要我们修改一下。这里 chat model 我用的是 deepseek-chat,embedding 用的是通义千问的 text-embedding-v1

DeepSeek  LangGraph 学习_DeepSeek_13

DeepSeek  LangGraph 学习_DeepSeek_14

DeepSeek  LangGraph 学习_DeepSeek_15

代码分析测试

DeepSeek  LangGraph 学习_DeepSeek_16

DeepSeek  LangGraph 学习_DeepSeek_17

可以看到其将所有的相关代码描述都列出来了,而且还做了总结。

继续查询另一个

graphrag query --root ./graphrag/ --method local --query "获取单个产品用到了哪个模型 "

DeepSeek  LangGraph 学习_DeepSeek_18

DeepSeek  LangGraph 学习_DeepSeek_19

列出了 Product 实体类的结构,而且还做了代码分析等,效果是非常好的,这是 LLM + 传统 RAG 达不到的效果。