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

所以这个框架为什么叫 LangChain 也很好理解了,核心就是提供了 Chain(链)的功能。需要注意的是这条链在方向上来说是单向的,不能够向回流或者循环。
不过随着 AI 逐渐深入到业务,在落地一些应用的时候,大家就发现使用这种单向的链,有些应用搞不定,比如之前讲过的 AgenticRAG,也有人叫 GraphRAG。因为这样的应用不仅要有分支的功能,还要具备循环的功能。这时就需要用图结构来表示业务了。

去年 LangChain 开发了一个扩展库,取名叫 LangGraph。这个库功能很强大,因为能够实现图的方式,解决 AgenticRAG 那是小菜一碟。
LangGraph编程学习
pip install langgraph
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 创建了一个工作流。


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)
因此如果想让“羊排”在节点间流转,应该在每一步都保存一下。但是基于目前的机制还有一个问题,那就是第一个节点的输出,同样也只能在第二个节点中拿到,在第三个节点中就拿不到了。
如果我想把每一步的输出也都保存下来,那代码应该这么写:
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,因此需要把它定义成列表。

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


提取代码描述
像是代码这种文本,其结构特点是由一个个的函数组成。因此对于这样的文本去构建知识图谱,是非常困难的。因为我们不管怎么切割文本,也都很难保证函数的完整性。除非将函数截成一张张的图片,然后利用视觉模型进行识别,然后再入库等,这就很麻烦了。
因此我们最好是先做一下预处理,用 LLM 将每一个代码文件的描述提取出来,基于描述文本再去构建知识图谱就会简单得多。
使用的Cursor 对文件进行的描述,具体方法是,我们在 Cursor 中打开该项目,然后点击打开一个文件后,按住 Ctrl + L,就会召唤出对话模式。此时我们贴入提示词即可,提示词如下:

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

编辑配置文件settings.yaml

将 models 里的 default_chat_model 以及 default_embedding_model 的信息从 OpenAI 改成我们自己国内的,比如 DeepSeek。当然如果你的服务器能访问 openai,那直接使用 OpenAI 也行。
其中 api_base、api_key 和 model 需要我们修改一下。这里 chat model 我用的是 deepseek-chat,embedding 用的是通义千问的 text-embedding-v1



代码分析测试


可以看到其将所有的相关代码描述都列出来了,而且还做了总结。
继续查询另一个
graphrag query --root ./graphrag/ --method local --query "获取单个产品用到了哪个模型 "

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
















