基于neo4j数据库和dify大模型框架的rag模型搭建

本文涉及的产品
实时数仓Hologres,5000CU*H 100GB 3个月
智能开放搜索 OpenSearch行业算法版,1GB 20LCU 1个月
实时计算 Flink 版,1000CU*H 3个月
简介: 基于neo4j数据库和dify大模型框架的rag模型搭建

本文主要讲述关于如何从pdf文档中提取数据并用于生成知识图谱,搭建基于知识图谱的rag模型的过程。

(其实就是知识库?有一说一因为根本没上课其实我也不知道我做的是啥,但是大概是符合课设要求的)

前置准备

neo4j数据库

neo4j用于存储从pdf文档中提取的数据。

安装方式有docker或者直接安装两种方式。

可以参考https://neo4j.com/docs/operations-manual/current/installation/linux/debian/

由于我使用的是直接安装的,因此docker安装方式就不作过多介绍了。

直接安装的配置如下:

主机: Ubuntu 22.04(其实是虚拟机)
jdk: openjdk-21-jdk
neo4j: (version)2025.04.0

由于neo4j是依赖于java运行的,因此需要java环境,如果之前没安装过java的话那很幸运,只需要一个java即可;如果之前已经安装过java,则需要对java版本的优先级进行调整(具体去问ai)。有一说一,其实不推荐安装最新版,因为大多数ai对最新版都不怎么熟悉,很容易出错,不会由于当时我已经装完了,所以只能硬着头皮往下干。(沉没成本不参与重大决策?)

安装流程

# 检验java是否安装
java --version
apt list openjdk-*jdk
sudo apt install openjdk-21-jdk
java --version
# 安装neo4j并检验
wget -O - https://debian.neo4j.com/neotechnology.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/neotechnology.gpg
echo 'deb [signed-by=/etc/apt/keyrings/neotechnology.gpg] https://debian.neo4j.com stable latest' | sudo tee -a /etc/apt/sources.list.d/neo4j.list
sudo apt-get update
apt list -a neo4j
sudo apt-get install neo4j:2025.04.0
neo4j help

第一步是安装java,第二步是将neo4j所在的库添加到apt源中然后用apt安装。

(其实可以用deb文件包然后dpkg安装,但是官方文档给的会显示不是一个合法的deb文件,肥肠奇怪,无法理解)

安装完之后将neo4j启动

sudo neo4j start

启动成功之后会显示
image-20250525202405716.png

之后自动使用守护进程运行,转入后台。

在回显中可以看到plugins目录和conf目录。先进入/var/lib/neo4j/labs中,将其中的apoc开头的jar包拷贝到plugins目录中。如果有需要,还可以去apoc的github仓库中安装额外的补全插件(插件地址为:https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/download/2025.04.0/apoc-2025.04.0-extended.jar)。

然后进入/etc/neo4j目录中,其中存在neo4j.conf文件,先对其进行修改,修改之后如下。这个修改使得在neo4j的浏览器页面中可以调用apoc函数。

image-20250525203002386.png

再作修改,使得其他主机能够访问neo4j。如果在容器中还可能用到host.docker.internal(只适用于macos和windows),当然直接安装就没有这种问题了。

# neo4j.conf
server.default_listen_address=0.0.0.0

再在neo4j.conf的同级目录中新建一个apoc.conf,内容为

apoc.import.file.enabled=true

这样neo4j就算安装完成了。

neo4j的浏览器地址为http://ip:7474/browser/,bolt端口为7687(默认是这样)

使用docker安装的话可以使用-v将docker中的目录映射到主机中,尤其记得要将plugin目录和conf目录映射出来,不然就得使用docker exec进入docker容器中再安装插件,比较麻烦。

dify框架

dify框架的安装比较简单,直接使用docker就可以完成安装,而且和neo4j不存在端口冲突。

首先从github克隆下dify的仓库,按照readme文档中的内容去做即可。

git clone https://github.com/langgenius/dify.git
cd dify
cd docker
cp .env.example .env
sudo docker compose up -d

如果没有docker compose的可以试一下docker-compose,不然就是没安装。

# 关闭dify的命令,在docker-compose.yaml目录下
sudo docker compose stop

dify的浏览器访问地址为ip,默认端口为80

搭建思路

提取数据 -> 建立数据结构 -> 存入数据库 -> 搭建检索系统 -> 输入llm -> 得到最终输出

关于提取数据、建立数据结构、存入数据库这三个部分,可将其视为一个部分,也就是将文档中的数据存入数据库。

提取数据存入数据库

提取数据

数据存储在pdf文档中,在对数据进行读取时还需要对其进行词义分析,将其分类、分段,保留它原先的意思,但是又便于检索。

好在这一步不需要我们手动完成,只需要对数据进行提取,然后交给专门的语言模型处理即可😋。

由于数据是存储在pdf文档中,可以使用pdfplumer进行读取,再使用spacy对其进行分段。

补充关于token的小知识:
在大型语言模型(LLM)中,Token 是模型处理文本数据的基本单位。Token是LLM理解、处理和生成人类语言的基础单位。通过将文本分解为Token,LLM能够将复杂的语言转换为其可以进行数学运算的数字表示形式,从而实现各种强大的自然语言处理能力。

所以说将语句进行分段是必要的。

# 核心代码
with pdfplumber.open(pdf_path) as pdf:
        chunks = []
        # 使用spacy分句
        nlp = spacy.load("zh_core_web_sm")
        for idx,page in tqdm(enumerate(pdf.pages), total=len(pdf.pages), desc="Processing PDF"):
            text = page.extract_text()
            meta = {
                "page_number": page.page_number,
                "page_width": page.width,
                "page_height": page.height
            }
            doc = nlp(text)
            sentences = [sent.text.replace('\n', ' ').replace('\r', ' ').strip() for sent in doc.sents]
            # 合并为段落(每段3句)
            for i in range(0, len(sentences), 3):
                chunk_text = "".join(sentences[i:i+3])
                chunks.append({
                    "text": chunk_text,
                    "page_number": meta["page_number"],
                    "start_pos": i,
                    "end_pos": i+3
                })

在上述代码中使用spacy中的专门处理中文的模型zh_core_web_sm对从pdf中提取出的数据进行分句,并给其标注信息,如页号等。

当然也可以使用其他的类似模型或者更好的模型,使用更好的模型得到的效果也就更好

生成向量索引

token的向量索引,也称为Token Embeddings。在token的向量索引中包含了token的词义信息,也可以快速的检索到token,在之后的检索系统中会用到。

如果不使用向量索引的话,也可以根据输入的关键词对数据库进行检索,这种就很好理解了。
其实使用向量索引也是类似这种用法,只不过是对关键词的词义也进行相关性检索,而不是单纯的仅凭关键词本身。

生成向量索引的过程自然也是不需要我们多虑的,也有专门的模型用于创建向量索引。
生成的向量索引维度为384。

model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
vectors = model.encode([chunk["text"] for chunk in chunks], batch_size=32)
for i, chunk in enumerate(chunks):
    vectors[i] = [x for x in vectors[i]]
    chunk["vector"] = vectors[i].tolist()
模型选择并不唯一,但是越好的模型生成的效果肯定是更好的

存储到数据库

接下来将其存储到数据库中,在本文中使用的是neo4j数据库,类型为nosql,主要是用于图的存储。

在存储到数据库中时需要创建不同的节点并且指出其相关关系,例如在如下代码中就创建了book节点和chunk节点两种节点,其中chunk节点属于book节点,构成了图。还添加了一些额外的meta data,便于管理和识别。

driver = GraphDatabase.driver("bolt://ip:port", auth=("neo4j", "password"))
with driver.session() as session:
    # 创建Book节点
    session.run("""
        CREATE (b:Book {
            book_id: $book_id,
            title: $title,
            author: $author
        })
    """, book_meta)

    # 创建Chunk节点及关系
    for chunk in chunks:
        session.run("""
            MATCH (b:Book {book_id: $book_id})
            CREATE (c:Chunk {
                chunk_id: $chunk_id,
                text: $text,
                page_number: $page_number,
                start_pos: $start_pos,
                end_pos: $end_pos,
                vector: $vector
            })
            CREATE (c)-[:BELONGS_TO]->(b)
        """, {
   
            "book_id": book_meta["book_id"],
            "chunk_id": f"{book_meta['book_id']}_chunk_{chunk['page_number']}_{chunk['start_pos']}",
            **chunk
        })

创建数据库索引

虽然听起来跟之前的向量索引有些相似,但是不是一个东西,之前的索引是作为节点的数据而存在,而现在要创建的数据库索引是为了提升数据库的查询效率而使用的,并且能够高效地执行语义相似度搜索,为检索系统的搭建作准备。

driver = GraphDatabase.driver("bolt://ip:port", auth=("neo4j", "password"))
with driver.session() as session:
    # 创建vector的索引
    session.run(
        """CREATE VECTOR INDEX chunk_vector_index 
        FOR (c:Chunk) ON (c.vector) OPTIONS
        { indexConfig: { `vector.dimensions`: 384,`vector.similarity_function`: 'cosine'}}"""
    )

(这里有一个坑,就是如果你使用低版本的neo4j的话,ai会使用apoc中跟vector相关的代码,就可以直接使用。但是由于版本太高了,vector函数被内置到neo4j里,apoc的函数就废弃了,这个函数还是从官方文档里搜出来的,肥肠难受😫)

搭建检索系统

所有对数据的前置准备已经完成,接下来要搭建的是检索系统,用于从数据库中检索出跟用户输入最相关的词条。

首先,要将用户输入的内容也转为向量索引,才好进行比较。

然后使用neo4j中内置的vector比较函数,将相关的数据从neo4j中取出,按照相关性进行排序。

为了提高相关性的准确度,这里可以使用reranker模型进行重排序,得到真正相关性更高的结果。

然后再根据得到相关内容,回到文档中原来的位置,提取其上下文,提高答案的正确率。

因此,检索系统分为四个部分。

输入转换 -> 向量检索 -> 重排序 -> 提取上下文

后两个步骤是可选的,在我写的代码中设置了选项,在使用时可选也可不选。只是不选的话得到的答案比较没人样,或者直接得不到答案而已。

听起来比较简单,其实做起来也不是很难(毕竟大部分代码都不是我写的)。

不过由于后续要使用dify框架,所以这里将检索系统封装为了一个http的api,便于后续的调用,使用的是fastapi。(主要是我感觉dify里面的代码执行应该是不允许搞这么复杂的,当然我也没仔细研究过)

输入llm

这个部分也比较简单,选择一个自己喜欢的模型,然后设置好prompt和参数就好了。

prompt可参考:

你是一个专业书籍问答助手,请严格基于用户提供的<上下文>回答问题。
若问题与上下文无关或超出知识范围,必须回答"未找到相关信息"。
回答必须满足以下要求:
1. 答案需完整覆盖问题核心,避免冗余
2. 必须使用中文口语化表达,禁用Markdown格式
3. 若上下文存在矛盾信息,需指出矛盾点

{
  {#context#}}

问题:
{
  {#sys.query#}}

请按以下格式回答:
【答案摘要】
简明总结核心答案(1-2句话)

【详细解释】
分点说明推导过程和依据

【相关延伸】
推荐2-3个关联知识点(可选)

实际上llm部分是在dify框架中完成的,所以要先启动dify,如何启动在前置准备章节中已经有详细描述,但是还有一个点没提到,那就是需要注册一个模型api平台的账号,这里我选择的是硅基流动,其实用deepseek或者豆包之类的平台也是一样的,只要它们有提供api的接口就可以。

硅基流动注册

注册只需要手机号验证就可以了(ps:我本来想用github登的,但是登完还是要手机号😓)

如果没有邀请码可以填我的:f1EqQpK5

双方都会获得免费的2000w tokens,我当时注册也是随便搜了一个邀请码就填上了。

注册成功之后在左侧的工具栏中有一个api密钥,新建一个,密钥描述随便写(当然填dify也可以)。

dify初始化

第一次进入dify会要求输入管理员账户,如果是自己用的话随便输一个也没事,有别人要用的话还是设置得复杂一点。

登录成功之后点击右上角的头像>设置>模型供应商,可以看到有很多家,选择硅基流动,输入之前新建的api密钥即可。

这就算初始化完成了。

dify_chatflow搭建

接下来根据之前的思路,也就是数据->检索->llm的流程搭建dify工作流,注意已经将检索系统封装为http的api并且在本地启动,并且数据也已经输入到数据库中。

image-20250525213001882.png

工作流的流程很简单,看看其内部构造

http请求

image-20250525213225134.png

封装了search接口,query是用户在对话框的输入,expand_window是上下文的窗口大小,默认开启了上下文检索但是没有开启重排序,参数类型为json。

代码执行

image-20250525213359658.png

使用http请求的返回值作为输入,解析json,并且提取其中的text的值,返回值为list。

llm

image-20250525213522940.png

prompt在上文中已给出,使用的是千问8B的模型(因为不要钱),真好,也可以自己在主机上跑一个开源的大模型,但是我指定不行,等下笔记本烧了😓。

最后一个选直接回复即可,以llm的输出作为输入。

对于小段文本的测试效果还不错,但是没有使用指标进行度量。其实是可以对性能进行评估的,但是我比较懒,课设的话没必要纠结太多(

而且如果要人工评估的话就要打标签,要用ai评估的话好像也比较麻烦,还得跑很多轮。

dify_workflow搭建

和chatflow差不多,只是开始时需要输入,结束时需要输出。
image-20250525221425847.png

改进方案

可以改进的方向有很多

1、在分句和创建向量时使用更好的模型

2、额外安装一个索引数据库,在neo4j中只存放索引的索引,可以有效提高查询的效率。

3、额外安装一个缓存数据库,如redis,也可以有效提高查询效率。

4、搭建检索系统时可以进行加权,对不同的权值进行调整

5、使用付费的高级llm和更加高级、专业的prompt

6、优化数据结构,例如在搭建的过程中我其实想给出每段文章的页码,但是这样的话要对数据结构进行修改,所以就没有做了

源代码

https://github.com/zx2023qj/rag_model

目录
相关文章
|
23天前
|
人工智能 自然语言处理 IDE
模型微调不再被代码难住!PAI和Qwen3-Coder加速AI开发新体验
通义千问 AI 编程大模型 Qwen3-Coder 正式开源,阿里云人工智能平台 PAI 支持云上一键部署 Qwen3-Coder 模型,并可在交互式建模环境中使用 Qwen3-Coder 模型。
296 109
|
5天前
|
存储 人工智能 自然语言处理
RAG:增强大模型知识库的新范式
RAG:增强大模型知识库的新范式
252 99
|
6天前
|
机器学习/深度学习 算法 数据可视化
从零开始训练推理模型:GRPO+Unsloth改造Qwen实战指南
推理型大语言模型兴起,通过先思考再作答提升性能。本文介绍GRPO等强化学习算法,详解其原理并动手用Qwen2.5-3B训练推理模型,展示训练前后效果对比,揭示思维链生成的实现路径。
80 1
从零开始训练推理模型:GRPO+Unsloth改造Qwen实战指南
|
10天前
|
人工智能 Java 开发者
阿里出手!Java 开发者狂喜!开源 AI Agent 框架 JManus 来了,初次见面就心动~
JManus是阿里开源的Java版OpenManus,基于Spring AI Alibaba框架,助力Java开发者便捷应用AI技术。支持多Agent框架、网页配置、MCP协议及PLAN-ACT模式,可集成多模型,适配阿里云百炼平台与本地ollama。提供Docker与源码部署方式,具备无限上下文处理能力,适用于复杂AI场景。当前仍在完善模型配置等功能,欢迎参与开源共建。
349 1
阿里出手!Java 开发者狂喜!开源 AI Agent 框架 JManus 来了,初次见面就心动~
|
23天前
|
机器学习/深度学习 人工智能 JSON
微软rStar2-Agent:新的GRPO-RoC算法让14B模型在复杂推理时超越了前沿大模型
Microsoft Research最新推出的rStar2-Agent在AIME24数学基准测试中以80.6%的准确率超越超大规模模型DeepSeek-R1,展现“思考更聪明”而非“更长”的AI推理新方向。
97 8
微软rStar2-Agent:新的GRPO-RoC算法让14B模型在复杂推理时超越了前沿大模型
|
6天前
|
存储 人工智能 监控
如何用RAG增强的动态能力与大模型结合打造企业AI产品?
客户的问题往往涉及最新的政策变化、复杂的业务规则,数据量越来越多,而大模型对这些私有知识和上下文信息的理解总是差强人意。
29 2
|
7天前
|
敏捷开发 人工智能 自动驾驶
AI大模型入门第四篇:借助RAG实现精准用例自动生成!
测试开发是否总被用例维护、漏测风险和文档滞后困扰?RAG技术让AI实时解读最新需求,自动生成精准测试用例,动态对齐线上数据,节省70%维护成本,助你告别手工“填坑”,高效应对需求变化。
|
10天前
|
机器学习/深度学习 人工智能 搜索推荐
解锁RAG高阶密码:自适应、多模态、个性化技术深度剖析
别让你的AI系统还停留在'只会查字典'的阶段!本文用轻松幽默的方式揭秘高级RAG技术如何让AI变得更聪明:自适应检索像读心术一样精准,多模态RAG让AI能'看图识字',个性化RAG则让AI记住你的每一个小习惯。想打造真正智能的AI应用?这三项技能缺一不可!
|
17天前
通义千问Image模型使用指南
该表格展示了多个设计场景,包括模型选择、复制粘贴提示词、一键生图等步骤。每个步骤配有详细描述及示意图,呈现了不同主题如商业海报、IP主视觉、品牌包装、街拍风格等的设计构思与实现方式。
|
18天前
|
机器学习/深度学习 人工智能 测试技术
探索 Qwen2.5-Max 的智能:大规模 MoE 模型的飞跃
Qwen2.5-Max 是通义实验室推出的最先进MoE模型,在推理、知识和代码任务中表现卓越,已在超20万亿token上预训练,并通过SFT和RLHF优化。在多项基准测试中领先,如Arena-Hard达89.4,MMLU-Pro为76.1,性能超越DeepSeek V3、GPT-4o等模型,成为当前最佳开源模型。可通过Qwen Chat和API使用,适用于开发者、研究者及AI爱好者探索前沿AI能力。
151 2