为了账号安全,请及时绑定邮箱和手机立即绑定

从零开始构建图谱RAG:Neo4j与CSV集成详解(第一部分)

大家好,欢迎回来!今天,我们将深入一个超级激动人心的项目——从头开始用CSV文件和知识图谱来打造一个GraphRAG应用。如果你觉得这挺有趣的,那你来对地方啦!

想象这样的场景:你是经营“Company Prince”的企业主。为了追踪订单、运输商、客户以及你种类繁多的产品,你完全依赖Excel表格。但随着AI技术的不断发展,你开始琢磨——如果能利用这样的技术创建一个智能客服机器人,能够立即提供你所需的所有信息,关于你的产品、发货、客户和订单会怎样?

现在,切换到另一个场景,想象你是一名AI应用程序开发者,任务是构建这个聊天机器人。你可能在想:最好的做法是什么?面对这么多可选的方法,你应该从哪里开始呢?在这篇文章中,我们将用知识图谱的方式,并一步步教你如何打造这个强大的系统。

我们首先将使用Python Pandas从CSV文件中提取数据,接着我们将深入Cypher代码,创建节点和关系,最后,这些数据将被导入到Neo4j图数据库中。完成这些步骤后,我们将在该知识图谱上构建一个检索增强生成(RAG)系统,让您以前所未有的方式查询产品、订单、客户和运输公司的数据。

本文是系列文章的第一篇,我们将在这系列文章中打下基础——专注于将数据提取并加载到Neo4j的过程。在后续的文章中,我们将逐步增加功能,直到我们拥有一个完全功能的RAG应用程序。

听起来不错,那就开始吧!

这图是代码生成的,致敬王子殿下

安装与设置

为了开始,我们需要先安装几个依赖库。

创建项目目录结构:

首先,让我们创建项目文件夹结构。我将使用VS-code来做这件事。下面是你需要运行的命令来完成初始设置。

$ mkdir northwind_graph_rag  # 创建一个名为northwind_graph_rag的目录
$ cd northwind_graph_rag
$ python3 -m venv venv  # 创建一个名为venv的虚拟环境
$ pip install pandas neo4j python-dotenv ipykernel  # 安装所需的Python库

完成这一步后,你就可以在终端输入命令来打开VScode。

运行代码 .

为了让上述命令生效,你需要在系统中正确安装或配置 code。如果没有配置好,你只需像往常一样通过用户界面打开 vscode 即可。

你还需要创建一个data文件夹来存放所有的CSV文件。你可以从Kaggle下载这些CSV文件,或者从本教程的Git仓库获取它们。

你还需要在项目根目录下创建一个名为 code 的文件夹。我们将在这里存放所有的 Jupyter 笔记本文件。我们将从一个用于数据导入的 Jupyter 笔记本文件 ingestion.ipynb 开始,并将其命名为 ingestion.ipynb

完成这一步后,你的目录结构应该像这样。

Code with Prince 制作的图片

安装依赖项

要安装依赖项,请在 code 目录中打开一个 jupyter notebook 文件,并请确保你选择了一个合适的内核,在 VScode 的右上角进行选择。你需要选择的内核是我们之前创建的虚拟环境文件夹或者刚刚创建的虚拟环境,视你的理解而定。请参见下面的图片,注意图片的右上角:

图片由Code With Prince制作

选好正确的内核后,我们现在就可以安装我们需要的软件包。

    !pip install pandas neo4j python-dotenv
    // 安装pandas, neo4j和python-dotenv库

你可以在 ingestion.ipynb 文件新建一个单元格,然后输入上面的命令。运行这个单元格就可以安装这些包了。

图片用代码和王子制作的图片

我已经把这些依赖包安装好了。

数据预处理步骤

我们将预处理我们的CSV文件,以便为知识图谱的数据导入过程做好准备。我们将合并表格并清理数据中的空值等等。请看这张图片,这里。

代码生成的图片,带有王子元素,特供王子

基于产品、供应商和产品类别

我们将首先处理产品、供应商以及产品类别这三个CSV(逗号分隔值)文件。首先,将这些CSV文件加载到pandas DataFrame中。

导入数据了

为了从 CSV 文件中加载数据到 Pandas 数据框,我们首先导入所需的库和包,开始。

    import pandas as pd

我们导入 pandas 库并将其作为 pd 来使用。

    category_df = pd.read_csv('../data/categories.csv')  # 读取分类数据
    product_df = pd.read_csv('../data/products.csv')  # 读取产品数据
    supplier_df = pd.read_csv('../data/suppliers.csv')  # 读取供应商数据

代码生成的带有王子元素的图片

使用Pandas进行数据检查与清洗

现在我们已经加载了数据。我们可以开始检查数据,以便更好地理解并清理数据,然后使用。

为此目的,我们将用到以下代码片段如下:

    categatory_df.head() # 显示categatory_df的前几行数据

图片由代码和王子一起制作

    product_df.head() # 显示 product_df 的前几行数据

带王子元素的代码创作图片

运行 `supplier_df.head()`

图片由Code With Prince制作

在Pandas中合并DataFrame

现在我们对数据有了更清楚的了解,我们可以将数据帧合并成一个数据帧。

首先,我们首先将product_dfcategory_df结合起来,形成一个数据框。

product_category_df = pd.merge(product_df, category_df, on='categoryID')  
product_category_df.head(1)

这是由王子的代码生成的图像

我们可以检查一下合并后的数据框的结构,这应该会告诉我们数据框里有多少行和列。

    product_category_df.shape

我们也可以通过下面的代码来检查总共有多少列:

    product_category_df.columns

图片由Code With Prince制作

现在,让我们检查合并后的数据框(DataFrame)中是否有任何缺失值。为了做到这一点,你可以使用下面的代码。

    product_category_df.isna().sum() # 计算DataFrame中缺失值的数量

图片由Code With Prince用代码制作

现在,我们可以把 supplier_dfproduct_category_df 合并一下了。

    product_category_supplier_df = pd.merge(  
        product_category_df,   
        supplier_df,   
        on='supplierID',  
        how='left'  
    )  
    product_category_supplier_df.head(1)
查看合并后的数据框前1行

我们使用pd.merge来合并product_category_dfsupplier_df,并指定on='supplierID'作为合并键。然后,我们使用head(1)来查看合并后的数据框的前1行。

我不会深入讲解Pandas中数据帧合并的原理。这篇文章更侧重于数据如何进入知识图谱,以及在此基础上构建一个RAG应用。

图片由王子的代码绘制

我们可以检查 product_category_supplier_df 的结构。可以使用以下代码:

[代码]
    product_category_supplier_df.shape

我们也可以检查数据中的缺失值,方法如下:

    product_category_supplier_df.isna().sum() 计算数据框中缺失值的数量

由代码生成的图片,带有王子元素

图片由王子用代码制作

Pandas DataFrame中的空值处理

我们将从最终合并的数据框中删除任何缺失或空值。从上面的图片可以看出,regionfaxhomePage 这几列有空值。让我们把这些缺失的单元格用 Unknown 字符串来填充。这可以通过下面的代码实现:

    product_category_supplier_df["region"] = product_category_supplier_df["region"].replace({pd.NA: "未提供"})  
    product_category_supplier_df["fax"] = product_category_supplier_df["fax"].replace({pd.NA: "未提供"})  
    product_category_supplier_df["homePage"] = product_category_supplier_df["homePage"].replace({pd.NA: "未提供"})  

    product_category_supplier_df.isna().sum()

图片由“Code With Prince”生成。

如何连接到Neo4j数据库

在这部分,我们将介绍如何连接到 Neo4j。要做到这一点,我们需要首先创建一个 Neo4j Aura 账户。如果您更喜欢,也可以使用本地的 Neo4j 实例。

为了简单和方便,我们将使用Neo4j Aura实例。这将使我们所有人都能轻松跟上。

创建一个 Neo4j Aura 账号

要在Neo4j Aura上创建账户,你可以按照页面上的说明来操作。

尝试创建一个免费的 Neo4j Aura 实例吧

一旦你创建了 Neo4j Aura 账户,就可以创建一个免费的实例,这个实例可以免费供你学习和开发原型使用。

你可以通过Neo4j Aura 控制台创建免费实例。创建实例后,记得下载凭证文件。这个文件里有访问这个免费实例的凭证。

创建一个.env文件(一个环境变量文件)

一旦我们获得了连接到 Neo4j 实例的凭证,然后,在项目的根目录下创建一个名为 .env 的文件,并按照以下格式添加凭证:

NEO4J_URI=xxxxxxxxxxx  # Neo4J URI 地址
NEO4J_USERNAME=xxxxxxxxxx  # Neo4J 用户名
NEO4J_PASSWORD=xxxxxxxxxxxxxxx  # Neo4J 密码
AURA_INSTANCEID=xxxxxxxxxxxxxxxxx  # AURA 实例ID
AURA_INSTANCENAME=xxxxxxxxxxxxxx  # AURA 实例名称

图片由代码制作,王子专用

加载环境变量设置

一旦这个文件准备好了,我们就可以加载环境变量并在代码中使用。为此,我们将使用之前安装的 python-dotenv 库。

    from dotenv import load_dotenv  # 加载环境变量
    import os  

    %load_ext dotenv  # 加载dotenv扩展
    %dotenv  # 设置当前工作目录的.env文件中的环境变量

这段代码会从 .env 文件中加载环境变量到操作系统的环境变量中。这样可以将敏感信息保留在代码内部,而不是暴露在外。

我们可以用标准库里的 os 模块来访问环境变量。以下是如何做的:

NEO4J_URI = os.getenv("NEO4J_URI")  # Neo4j URI
NEO4J_USER = os.getenv("NEO4J_USERNAME")  # Neo4j 用户名
NEO4J_PASSWORD = os.getenv("NEO4J_PASSWORD")  # Neo4j 密码
# 每一行都从环境变量中获取与 Neo4j 相关的变量
连接至 Neo4j 的免费实例

在我们可以开始将数据导入Neo4j实例之前,第一步应该是建立与它的连接。假设您已经完成了必要的设置,让我们来连接一个免费的Neo4j实例吧。

以下是一段用于建立连接的Python代码:

from neo4j import GraphDatabase  # 从neo4j导入GraphDatabase
# 设置认证信息,包括Neo4J的用户名和密码
AUTH = (NEO4J_USER, NEO4J_PASSWORD)
使用 GraphDatabase.driver(NEO4J_URI, auth=AUTH) 创建一个驱动程序,并将其赋值给变量 driver。
然后,调用 driver 的 verify_connectivity 方法来验证连接。

图片由Code With Prince制作

如何将数据输入Neo4j?

现在我们已经成功连接到了数据库,准备将数据插入到Neo4j的知识图数据库中。为此,我们将要编写一些Cypher代码。下面我将用到的代码片段。这段代码很容易理解。我将不解释这段Cypher代码。

这是密码代码:

    def insert_data(tx, row):  
        tx.run('''  
                CREATE (product:Product {  
                    productID: $productID,  
                    productName: $productName,  
                    supplierID: $supplierID,  
                    categoryID: $categoryID,  
                    quantityPerUnit: $quantityPerUnit,  
                    unitPrice: $unitPrice,  
                    unitsInStock: $unitsInStock,  
                    unitsOnOrder: $unitsOnOrder,  
                    reorderLevel: $reorderLevel,  
                    discontinued: $discontinued  
                })  
                MERGE (category:Category {  
                    categoryID: $categoryID,  
                    categoryName: $categoryName,  
                    description: $description,  
                    picture: $picture  
                })  
                MERGE (supplier:Supplier {  
                    supplierID: $supplierID,  
                    companyName: $companyName,  
                    contactName: $contactName,  
                    contactTitle: $contactTitle,  
                    address: $address,  
                    city: $city,  
                    region: $region,  
                    postalCode: $postalCode,  
                    country: $country,  
                    phone: $phone,  
                    fax: $fax,  
                    homePage: $homePage  
                })  
                CREATE (product)-[:PART_OF]->(category)  
                CREATE (product)-[:SUPPLIED_BY]->(supplier)  
                ''', row)
数据导入代码:

为了把数据插入知识图谱数据库,我们需要运行以下代码。

    with driver.session() as session:  # 将驱动器的会话作为session对象
        for _, row in product_category_supplier_df.iterrows():  # 遍历product_category_supplier_df数据框的每一行
            session.write_transaction(insert_data, row.to_dict())  # 使用session对象执行事务,插入数据字典
product_category_supplier_df:代表产品类别和供应商的数据框

上述代码只是遍历每一行,并调用 insert_data 函数,将其转换为字典格式后传入。

执行这段代码后,我们已将产品节点、供应商节点和产品类别节点插入了Neo4j。

图片由代码制作,主题是王子

完成这一点之后,我们可以接着编写一些cipher查询来查询Neo4j中的数据。下面有一些你可以使用的非常基础的查询。

MATCH (n:Product) RETURN n  
MATCH (n: 类别) RETURN n  
MATCH (n: 供应商) RETURN n  
MATCH (n) RETURN n
录入订单、订单详情、货运公司、员工资料和客户信息

让我们回到最初的图表。到目前为止,我们已经完成了我们需要做的左边。接下来,我们接着看右边的部分。请看下面的图:

请看:
请看

图片由代码和王子共同创作

因为我们基本上要做相同的事情,我简单看一下代码。

    # 将数据读取到Pandas DataFrame中  
    orders_df = pd.read_csv('../data/orders.csv')  
    order_details_df = pd.read_csv('../data/order_details.csv')  
    customer_df = pd.read_csv('../data/customers.csv')  
    shipper_df = pd.read_csv('../data/shippers.csv')  
    employee_df = pd.read_csv('../data/employees.csv')  

    # 将订单和订单详情表合并  
    orders_order_details_df = pd.merge(  
        orders_df,   
        order_details_df,   
        on='orderID',   
        how='left'  
    )  

    # 将结果表与客户表合并  
    orders_order_details_customer_df = pd.merge(orders_order_details_df, customer_df, on='customerID', how='left')  

    # 将结果表与承运商表合并  
    orders_order_details_customer_shipper_df = pd.merge(  
        orders_order_details_customer_df,   
        shipper_df,   
        left_on='shipVia',   
        right_on="shipperID",   
        how='left'  
    )  

    # 最后将数据与员工表合并  
    orders_order_details_customer_shipper_employee_df = pd.merge(  
        orders_order_details_customer_shipper_df,   
        employee_df,   
        left_on='employeeID',   
        right_on='employeeID',   
        how='left'  
    )  

    # 将NaN替换为“Unknown”  
    orders_order_details_customer_shipper_employee_df.replace({pd.NA: "Unknown"}, inplace=True)  

    # 将“reportsTo”列转换为整数  
    orders_order_details_customer_shipper_employee_df["reportsTo"] = orders_order_details_customer_shipper_employee_df["reportsTo"].astype('Int64')  

    # 首先创建副总裁节点,因为有些节点指向它。   
    vice_president = orders_order_details_customer_shipper_employee_df[orders_order_details_customer_shipper_employee_df["title"] == "副总裁"]

我首先创建了副总裁节点,因为有些节点依赖于它。我们有一些员工节点向副总裁报告,这种关系是一种报告关系。因此,副总裁节点需要在其他节点之前存在,因为副总裁并不向其他员工报告,而是其他员工向他/她报告。

我们现在可以写一些Cypher代码来把数据插入到Neo4j数据库。

    def 创建员工(tx, row):  
        tx.run("""  
            MERGE (e:人员 {  
                员工ID: $employeeID,  
                姓: $lastName,  
                名: $firstName,  
                职位: $title,  
                尊称: $titleOfCourtesy,  
                出生日期: $birthDate,  
                受雇日期: $hireDate,  
                通讯地址: $address_y,  
                城市: $city_y,  
                省份: $region_y,  
                邮政编码: $postalCode_y,  
                国家: $country_y,  
                家庭电话: $homePhone,  
                分机号: $extension,  
                照片: $photo,  
                备注: $notes,  
                照片路径: $photoPath  
        })  
        """, row)

写一段代码把数据加到数据库里

    with driver.session() as session:  
        for _, row in vice_president.iterrows():  
            # 将行数据转换为字典并调用create_manager函数
            session.write_transaction(create_manager, row.to_dict())

我们现在就可以开始编写代码,把数据加密后存进数据库。

    def insert_data(tx, row):  
        tx.run("""  
        CREATE (o:Order {  
            orderID: $orderID,  
            orderDate: $orderDate,  
            requiredDate: $requiredDate,  
            shippedDate: $shippedDate,  
            shipVia: $shipVia,  
            freight: $freight,  
            shipName: $shipName,  
            shipAddress: $shipAddress,  
            shipCity: $shipCity,  
            shipRegion: $shipRegion,  
            shipPostalCode: $shipPostalCode,  
            shipCountry: $shipCountry  
        })  
        WITH o  
        MATCH (p:Product { productID: $productID })  
        WITH p, o  
        MERGE (c:Customer {  
            customerID: $customerID,  
            companyName: $companyName_x,  
            contactName: $contactName,  
            contactTitle: $contactTitle,  
            address: $address_x,  
            city: $city_x,  
            region: $region_x,  
            postalCode: $postalCode_x,  
            country: $country_x,  
            phone: $phone_x,  
            fax: $fax  
        })  
        WITH c, p, o  
        MERGE (s:Shipper {  
            shipperID: $shipperID,  
            companyName: $companyName_y,  
            phone: $phone_y  
        })  
        WITH s, c, p, o  
        MERGE (e:Employee {  
            employeeID: $employeeID,  
            lastName: $lastName,  
            firstName: $firstName,  
            title: $title,  
            titleOfCourtesy: $titleOfCourtesy,  
            birthDate: $birthDate,  
            hireDate: $hireDate,  
            address: $address_y,  
            city: $city_y,  
            region: $region_y,  
            postalCode: $postalCode_y,  
            country: $country_y,  
            homePhone: $homePhone,  
            extension: $extension,  
            photo: $photo,  
            notes: $notes,  
            photoPath: $photoPath  
        })  
        WITH e, s, c, p, o  
        MATCH (m:Employee { employeeID: $reportsTo }) // 假设 reportsTo 是经理的员工编号  
        WITH m, e, s, c, p, o  
        MERGE (e)-[:向...汇报]->(m)  
        MERGE (o)-[:包含]->(p)  
        MERGE (o)-[:订购者]->(c)  
        MERGE (o)-[:承运方]->(s)  
        MERGE (o)-[:处理者]->(e)  
        """, parameters=row)

为了简单起见,我将插入前250条记录。

    with driver.session() as session:  
        # 遍历前250行数据
        for _, row in orders_order_details_customer_shipper_employee_df[:250].iterrows():  
            # 将数据插入到数据库中
            session.write_transaction(insert_data, row.to_dict())

运行成功后,我们可以使用Neo4j控制台来查看知识图谱。

由代码和王子制作的图片

图片由代码生成,王子专属

图片由代码和王子共同创作

构建 ETL 管道的下一篇文章

在接下来的文章里,我们将讲解如何为这个项目搭建ETL管道。

构建 Neo4j 图谱RAG 应用的 ETL 管道 | 第二部分 在上一篇文章中,我们探讨了如何创建一个基本的数据摄取管道,该管道允许我们手动输入数据……ai.gopubby.com
推荐的 YouTube视频

我也做像这样的文章的YouTube视频教程,对于喜欢视频胜过阅读的朋友们,这视频就是给你的。

总结

恭喜你终于完成了这个教程!我们已经成功地从一堆CSV文件建了一个基础知识图,向数据驱动的强大应用程序迈出了第一步。

在下一篇文章中,我们将更深入地研究,在这个知识图谱上叠加一个对话式的AI接口。想象一下能够实时向你的数据提问并得到详细回答——这听起来是不是很酷?这就是接下来我们要构建的功能,我们将让用户能够与我们创建的数据进行动态对话。

继续关注我们,准备好迎接进一步提升你的AI技巧!

注:这里将“AI技能”调整为“AI技巧”,使表达更口语化。

你还可以在我的这些其他平台上找到我:

1、YouTube(点击访问)
2、推特(点击访问)
3、领英(点击访问)
4、迪斯科德(点击访问)

再见,下次见!祝你编程愉快,世界依然在转。

GitHub - Princekrampah/neo4j_graphrag_tutorial参与 Princekrampah/neo4j_graphrag_tutorial 项目的贡献,通过 GitHub 创建一个帐户来贡献
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消