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

将您的 CSV 转换为图表,使用大模型

当大型语言模型(LLM)尝试用扁平格式的CSV文件生成图表时,它们在这种任务上的表现如何?

图形模型显示了曲目及其与艺人的PERFORMED_BY关系。照片由作者拍摄。

我的工作很大一部分是提高用户在使用 Neo4j 时的体验。通常,将数据导入 Neo4j 并高效建模是用户面临的关键挑战,尤其是在刚开始使用的时候。虽然初始的数据模型很重要,也需要仔细考虑,但它可以很容易地进行重构,以提高性能,随着数据量或用户数量的增加。

所以,作为一次挑战自己,我想看看大型语言模型能否帮助构建初始数据模型。至少,它能展示事物之间的关联,并让用户能快速展示一些结果给他人。

直觉上,我知道数据建模是一个迭代过程,而且某些LLM面对大量数据时容易分心,所以这正好可以利用LangGraph来循环处理数据。

让我们来看看哪些提示让它得以实现。

图模型入门

《GraphAcademy 的图数据建模基础课程》(https://graphacademy.neo4j.com/courses/modeling-fundamentals/?ref=adam) 带领您了解在图中建模数据的基础知识,简单来说,我使用了一些基本准则:

  • 名词变成标签——它们描述节点所代表的 东西
  • 动词变成关系类型——它们描述 东西 之间的连接方式。
  • 其他内容变成属性(特别是副词)——你有一个名字,而且可能开着灰色的车。

动词也可以是节点;你可能很高兴知道某个人订购了某个产品,但这种基本模型无法告诉你产品是在哪里和什么时候订购的。在这种情况下,订购就成了模型中的一个新节点。

我相信这可以提炼成一个提示,用来创建无需训练的图形数据建模方法。

迭代

几个月之前,我短暂地尝试了这个,发现我用的那个模型在处理更复杂的架构时很容易分神,结果提示信息很快达到了最大令牌数限制。

这次我打算一次试一次地来,每次处理一个键值对。这样可以避免分心,因为每次LLM只需专注于处理一个键值对。

最终的步骤采用了如下步骤:

  1. 将 CSV 文件加载到 Pandas 数据框中。
  2. 分析 CSV 中的每一列,并将其信息追加到一个基于 JSON Schema 的数据模型中。
  3. 识别并为每个实体添加缺失的唯一标识符(ID)。
  4. 检查数据模型的准确性。
  5. 生成用于导入节点和关系的 Cypher 语句。
  6. 生成支撑导入语句的唯一性约束。
  7. 创建约束并执行导入。
数据集

我在Kaggle上快速浏览了一下,发现了一个有趣的数据集。其中最吸引我的是《Spotify最热门歌曲》Spotify Most Streamed Songs

    import pandas as pd
csv_file = '/Users/adam/projects/datamodeller/数据/spotify/spotify-most-streamed-songs.csv'
df = pd.read_csv(csv_file)  
df.head()
# 代码用于读取并展示最热播放的歌曲数据

    歌曲名称 艺术家名称 艺术家数量 发行年份 发行月份 发行日期 Spotify播放列表中 Spotify排行榜中 流媒体播放次数 Apple播放列表中 … 调式 模式 动感度% 情感度% 能量度% 清脆度% 乐器演奏度% 现场演出度% 人声度% 封面图片链接  
    0 Seven (feat. Latto) (Explicit版本) Latto, Jung Kook 2 2023 7 14 553 147 141381703 43 … B大调 80 89 83 31 0 8 4 未找到  
    1 LALA Myke Towers 1 2023 3 23 1474 48 133716286 48 … 升C大调 71 61 74 7 0 10 4 https://i.scdn.co/image/ab67616d0000b2730656d5…  
    2 吸血鬼 Olivia Rodrigo 1 2023 6 30 1397 113 140003974 94 … F大调 51 32 53 17 0 31 6 https://i.scdn.co/image/ab67616d0000b273e85259…  
    3 Cruel Summer Taylor Swift 1 2019 8 23 7858 100 800840817 116 … A大调 55 58 72 11 0 11 15 https://i.scdn.co/image/ab67616d0000b273e787cf…  
    4 WHERE SHE GOES Bad Bunny 1 2023 5 18 3133 50 303236322 84 … A小调 65 23 80 14 63 11 6 https://i.scdn.co/image/ab67616d0000b273ab5c9c…

5行25列

这比较简单,但一眼就能看出来,曲目和艺术家之间应该有关联。

还有一些数据清洗的难题需要解决,特别是在artist(s)_name列中的艺术家名称以逗号分隔的情况下。

选择一个大模型(LLM)

我真的很想用本地的LLM来搞定这个任务,但很快就发现Llama 3不太够用。如果有疑问,直接用OpenAI就好:

    从 langchain_core.prompts 导入 PromptTemplate  
    从 langchain_core.pydantic_v1 导入 BaseModel, Field  
    从 typing 导入 List  
    从 langchain_core.output_parsers 导入 JsonOutputParser
from langchain_openai import ChatOpenAI  
# 初始化一个ChatOpenAI模型,使用"gpt-4o"模型
llm = ChatOpenAI(model="gpt-4o")
让我们来创建一个数据模型吧

我用简化的建模指令来创建数据建模提示。我反复调整提示,以确保稳定结果。

零样本的例子表现得相对不错,但我发现输出有些不一致。定义一个结构化的输出来存放JSON数据真的很有用。

    class JSONSchemaSpecification(BaseModel):  
      notes: str = Field(description="关于模式的任何备注或说明")  
      jsonschema: str = Field(description="描述数据模型中实体的JSON模式规范的JSON数组")

以下是少样本示例输出

JSON本身也不一致,所以我最终根据电影推荐数据集定义了一个模式。

例如的输出:

    example_output = [  
       dict(  
        title="人物",  
        type="对象",  
        description="节点",  
        properties=[  
            dict(name="name", column_name="person_name", type="字符串", description="人物的名字", examples=["汤姆·汉克斯(Tom Hanks)"]),  
            dict(name="date_of_birth", column_name="person_dob", type="日期", description="出生日期", examples=["1987-06-05"]),  
            dict(name="id", column_name="person_name, date_of_birth", type="字符串", description="ID由名字和出生日期组成,确保唯一性", examples=["tom-hanks-1987-06-05"]),  
        ],  
      ),  
       dict(  
        title="导演",  
        type="对象",  
        description="节点",  
        properties=[  
            dict(name="name", column_name="director_names", type="字符串", description="导演的名字,列中的值由逗号分隔", examples=["弗朗西斯·福特·科波拉"]),  
        ],  
      ),  
       dict(  
        title="电影",  
        type="对象",  
        description="节点",  
        properties=[  
            dict(name="title", column_name="title", type="字符串", description="标题", examples=["玩具总动员"]),  
            dict(name="released", column_name="released", type="整型", description="发行年份", examples=["1990"]),  
        ],  
      ),  
       dict(  
        title="参与",  
        type="对象",  
        description="关系",  
        properties=[  
            dict(name="_from", column_name="od", type="字符串", description="通过ID找到的人物,ID由名字和出生日期组成,确保唯一性", examples=["人物"]),  
            dict(name="_to", column_name="title", type="字符串", description="标题", examples=["电影"]),  
            dict(name="roles", type="字符串", column_name="person_roles", description="角色", examples=["伍迪"]),  
        ],  
      ),  
       dict(  
        title="导演",  
        type="对象",  
        description="关系",  
        properties=[  
            dict(name="_from", type="字符串", column_name="director_names", description="导演的名字,列中的值由逗号分隔", examples=["导演"]),  
            dict(name="_to", type="字符串", column_name="title", description="关系的终点", examples=["电影"]),  
        ],  
      ),  
    ]

我不得不偏离严格的JSON模式,并在输出中添加了 column_name 字段来辅助LLM生成导入脚本文件。提供描述的例子也有帮助,否则在 MATCH 子句中使用的属性会不一致。

链接

下面就是最后的提示:

    model_prompt = PromptTemplate.from_template("""  
    你是一位专业的图数据库专家。  
    基于现有数据源提供的信息,你的工作是设计数据模型。

你需要决定以下列在现有数据模型中的位置。考虑:

  • 列是否表示一个实体,例如人、地方或电影?如果是,则应作为节点。
  • 列是否表示两个实体之间的关系?如果是,则应作为两个节点之间的关系。
  • 列是否表示实体或关系的属性?如果是,则应作为节点或关系的属性。
  • 列是否表示一个可以用来查询相似节点的共享属性,例如类型?如果是,则应作为一个节点。
    节点说明
  • 节点标题应使用UpperCamelCase格式,例如Person、Place或Movie
    关系说明
  • 关系通常是动词,例如ACTED_IN、DIRECTED或PURCHASED
  • 关系应使用UPPER_SNAKE_CASE格式(例如:ACTED_IN)
  • 关系的良好示例是(:Person)-[:ACTED_IN]->(:Movie)或(:Person)-[:PURCHASED]->(:Product)
  • 在描述中提供字段的任何特定说明。例如,字段是否包含逗号分隔的值列表或单个值?
    属性说明
  • 属性应使用lowerPascalCase格式
  • 尽可能使用较短的名称(例如:将'person_id'和'personId'简化为'id')
  • 如果你更改了属性名称,应在描述中提及原始字段名称
  • 不要为整数或日期字段提供示例,请直接描述数据准备步骤。
  • 总是需要说明数据准备步骤,例如是否需要将字段转换为字符串,或根据分隔符拆分为多个字段。
  • 属性键应只包含字母,不应包含数字或特殊字符。
    重要!

    考虑提供的示例。是否需要进行数据准备以确保数据格式正确?
    你必须在描述中包括任何关于数据准备的信息。

    示例输出

    这是一个好的输出示例:

    {example_output}  
    新数据:键:{key}

    数据类型:{type}
    示例值:{examples}

    现有数据模型

    这是现有数据模型:

    {existing_model}  
    保留现有数据模型

    将你的更改应用于现有数据模型,但不要删除任何现有定义。
    ""

执行链路

为了迭代地更新模型,我遍历了数据框中的键,并将每个键及其数据类型,以及前五个唯一的值传递给提示:

    from json_repair 导入 dumps, loads  

    existing_model = {}  

    for i, key in enumerate(df):  
      print("\n", i, key)  
      print("----------------")  
      尝试:  
        res = try_chain(model_chain, dict(  
          existing_model=dumps(existing_model),  
          key=key,  
          type=df[key].dtype,  
          examples=dumps(df[key].unique()[:5].tolist())  
        ))  
        print(res.notes)  
        existing_model = loads(res.jsonschema)  

        print([n['title'] for n in existing_model])  
      除了 Exception as e:  
        print(e)  
        pass  

    existing_model

控制台信息

      0 track_name  
    ----------------  
    添加 'track_name' 到现有数据模型中。这代表一个音乐曲目实体。  
    ['Track']  

      1 artist(s)_name  
    ----------------  
    在现有数据模型中添加一个新字段 'artist(s)_name'。此字段表示与曲目相关的多个艺术家,并应作为新节点 'Artist' 和从 'Track' 到 'Artist' 的关系 'PERFORMED_BY' 来建模。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      2 artist_count  
    ----------------  
    将 artist_count 添加为 Track 节点的属性值。此属性值表示在曲目中表演的艺术家数量。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      3 released_year  
    ----------------  
    在现有数据模型中作为 Track 节点的属性值添加 released_year 字段。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      4 released_month  
    ----------------  
    在现有数据模型中添加 'released_month' 字段,将其视为 Track 节点的属性值。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      5 released_day  
    ----------------  
    将新的属性值 'released_day' 添加到 Track 节点,以记录曲目发布的具体日期。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      6 in_spotify_playlists  
    ----------------  
    在现有数据模型中作为 Track 节点的属性值添加新的字段 'in_spotify_playlists'。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      7 in_spotify_charts  
    ----------------  
    在现有数据模型中作为 Track 节点的属性值添加 'in_spotify_charts' 字段。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      8 streams  
    ----------------  
    在现有数据模型中添加新的字段 'streams',表示曲目的播放次数。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      9 in_apple_playlists  
    ----------------  
    在现有数据模型中添加新的字段 'in_apple_playlists'。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      10 in_apple_charts  
    ----------------  
    将 'in_apple_charts' 作为 Track 节点的属性值添加到现有数据模型中,表示曲目出现在 Apple 排行榜中的次数。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      11 in_deezer_playlists  
    ----------------  
    将 'in_deezer_playlists' 添加到现有的音乐曲目数据模型中。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      12 in_deezer_charts  
    ----------------  
    在现有 'Track' 节点中添加新的属性值 'inDeezerCharts',表示曲目出现在 Deezer 排行榜中的次数。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      13 in_shazam_charts  
    ----------------  
    将新的属性值 'in_shazam_charts' 添加到现有数据模型中。这似乎是 Track 节点的一个属性值,表示曲目出现在 Shazam 排行榜中的次数。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      14 bpm  
    ----------------  
    将 bpm 字段作为 Track 节点的属性值添加,因为它代表了曲目的特点。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      15 key  
    ----------------  
    在现有数据模型中添加 'key' 字段。'key' 表示曲目的音乐调式,这是一个可以通过查询共享属性来查找类似曲目的有趣特性。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      16 mode  
    ----------------  
    在现有数据模型中添加 'mode' 属性值。它代表曲目的音乐特性,最好作为 Track 节点的属性值捕获。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      17 danceability_%  
    ----------------  
    将 'danceability_%' 添加为现有数据模型中 Track 节点的属性值。该字段表示曲目的舞蹈性百分比值。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      18 valence_%  
    ----------------  
    将 valence 百分比值字段添加到现有数据模型中作为 Track 节点的属性值。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      19 energy_%  
    ----------------  
    将新的字段 'energy_%' 集成到现有数据模型中。此字段代表 Track 实体的属性值,并应作为 Track 节点的属性值添加。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      20 acousticness_%  
    ----------------  
    将 acousticness_% 添加为现有数据模型中 Track 节点的字段。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      21 instrumentalness_%  
    ----------------  
    将新的字段 'instrumentalness_%' 添加到现有的 Track 节点中,因为它代表了 Track 实体的属性值。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      22 liveness_%  
    ----------------  
    将新的字段 'liveness_%' 添加到现有数据模型中,作为 Track 节点的属性值。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      23 speechiness_%  
    ----------------  
    将新的字段 'speechiness_%' 添加到现有数据模型中,作为 'Track' 节点的属性值。  
    ['Track', 'Artist', 'PERFORMED_BY']  

      24 cover_url  
    ----------------  
    将新的属性值 'cover_url' 添加到现有的 'Track' 节点中。此属性值表示曲目的封面图 URL。  
    ['Track', 'Artist', 'PERFORMED_BY']

经过对提示做了一些调整来适应不同情况后,我最终得到了一个让我很满意的结果。大模型成功地确定了数据集包括TrackArtist,以及连接它们的PERFORMED_BY关系。

    [  
      {  
        "title": "Track",  
        "type": "object",  
        "description": "节点(Node)",  
        "properties": [  
          {  
            "name": "name",  
            "column_name": "track_name",  
            "type": "string",  
            "description": "曲名",  
            "examples": [  
              "Seven (feat. Latto) (Explicit Ver.)",  
              "LALA",  
              "vampire",  
              "Cruel Summer",  
              "WHERE SHE GOES",  
            ],  
          },  
          {  
            "name": "artist_count",  
            "column_name": "artist_count",  
            "type": "integer",  
            "description": "参与曲目的艺术家人数",  
            "examples": [2, 1, 3, 8, 4],  
          },  
          {  
            "name": "released_year",  
            "column_name": "released_year",  
            "type": "integer",  
            "description": "曲目发布的年份",  
            "examples": [2023, 2019, 2022, 2013, 2014],  
          },  
          {  
            "name": "released_month",  
            "column_name": "released_month",  
            "type": "integer",  
            "description": "曲目发布的月份",  
            "examples": [7, 3, 6, 8, 5],  
          },  
          {  
            "name": "released_day",  
            "column_name": "released_day",  
            "type": "integer",  
            "description": "曲目发布的具体日期",  
            "examples": [14, 23, 30, 18, 1],  
          },  
          {  
            "name": "inSpotifyPlaylists",  
            "column_name": "in_spotify_playlists",  
            "type": "integer",  
            "description": "曲目在Spotify播放列表中的数量。将值转换为整数。",  
            "examples": [553, 1474, 1397, 7858, 3133],  
          },  
          {  
            "name": "inSpotifyCharts",  
            "column_name": "in_spotify_charts",  
            "type": "integer",  
            "description": "曲目在Spotify排行榜中的排名次数。将值转换为整数。",  
            "examples": [147, 48, 113, 100, 50],  
          },  
          {  
            "name": "streams",  
            "column_name": "streams",  
            "type": "array",  
            "description": "曲目的流媒体ID列表。保持数组格式。",  
            "examples": [  
              "141381703",  
              "133716286",  
              "140003974",  
              "800840817",  
              "303236322",  
            ],  
          },  
          {  
            "name": "inApplePlaylists",  
            "column_name": "in_apple_playlists",  
            "type": "integer",  
            "description": "曲目在Apple播放列表中的数量。将值转换为整数。",  
            "examples": [43, 48, 94, 116, 84],  
          },  
          {  
            "name": "inAppleCharts",  
            "column_name": "in_apple_charts",  
            "type": "integer",  
            "description": "曲目在Apple排行榜中的排名次数。将值转换为整数。",  
            "examples": [263, 126, 207, 133, 213],  
          },  
          {  
            "name": "inDeezerPlaylists",  
            "column_name": "in_deezer_playlists",  
            "type": "array",  
            "description": "曲目在Deezer播放列表中的ID列表。保持数组格式。",  
            "examples": ["45", "58", "91", "125", "87"],  
          },  
          {  
            "name": "inDeezerCharts",  
            "column_name": "in_deezer_charts",  
            "type": "integer",  
            "description": "曲目在Deezer排行榜中的排名次数。将值转换为整数。",  
            "examples": [10, 14, 12, 15, 17],  
          },  
          {  
            "name": "inShazamCharts",  
            "column_name": "in_shazam_charts",  
            "type": "array",  
            "description": "曲目在Shazam排行榜中的ID列表。保持数组格式。",  
            "examples": ["826", "382", "949", "548", "425"],  
          },  
          {  
            "name": "bpm",  
            "column_name": "bpm",  
            "type": "integer",  
            "description": "曲目的每分钟节拍数。将值转换为整数。",  
            "examples": [125, 92, 138, 170, 144],  
          },  
          {  
            "name": "key",  
            "column_name": "key",  
            "type": "string",  
            "description": "曲目的音乐调性。将值转换为字符串。",  
            "examples": ["B", "C#", "F", "A", "D"],  
          },  
          {  
            "name": "mode",  
            "column_name": "mode",  
            "type": "string",  
            "description": "曲目的调式(如Major,Minor)。将值转换为字符串。",  
            "examples": ["Major", "Minor"],  
          },  
          {  
            "name": "danceability",  
            "column_name": "danceability_%",  
            "type": "integer",  
            "description": "曲目的可舞性百分比。将值转换为整数。",  
            "examples": [80, 71, 51, 55, 65],  
          },  
          {  
            "name": "valence",  
            "column_name": "valence_%",  
            "type": "integer",  
            "description": "曲目的情感强度百分比。将值转换为整数。",  
            "examples": [89, 61, 32, 58, 23],  
          },  
          {  
            "name": "energy",  
            "column_name": "energy_%",  
            "type": "integer",  
            "description": "曲目的能量百分比。将值转换为整数。",  
            "examples": [83, 74, 53, 72, 80],  
          },  
          {  
            "name": "acousticness",  
            "column_name": "acousticness_%",  
            "type": "integer",  
            "description": "曲目的原声度百分比。将值转换为整数。",  
            "examples": [31, 7, 17, 11, 14],  
          },  
          {  
            "name": "instrumentalness",  
            "column_name": "instrumentalness_%",  
            "type": "integer",  
            "description": "曲目的伴奏性百分比。将值转换为整数。",  
            "examples": [0, 63, 17, 2, 19],  
          },  
          {  
            "name": "liveness",  
            "column_name": "liveness_%",  
            "type": "integer",  
            "description": "曲目的现场感百分比。将值转换为整数。",  
            "examples": [8, 10, 31, 11, 28],  
          },  
          {  
            "name": "speechiness",  
            "column_name": "speechiness_%",  
            "type": "integer",  
            "description": "曲目的说唱性百分比。将值转换为整数。",  
            "examples": [4, 6, 15, 24, 3],  
          },  
          {  
            "name": "coverUrl",  
            "column_name": "cover_url",  
            "type": "string",  
            "description": "曲目的封面图片链接。如果值为'Not Found',应将其转换为空字符串。",  
            "examples": [  
              "https://i.scdn.co/image/ab67616d0000b2730656d5ce813ca3cc4b677e05",  
              "https://i.scdn.co/image/ab67616d0000b273e85259a1cae29a8d91f2093d",  
            ],  
          },  
        ],  
      },  
      {  
        "title": "Artist",  
        "type": "object",  
        "description": "节点(Node)",  
        "properties": [  
          {  
            "name": "name",  
            "column_name": "artist(s)_name",  
            "type": "string",  
            "description": "艺术家的名称。将列中的值按逗号分割。",  
            "examples": [  
              "Latto",  
              "Jung Kook",  
              "Myke Towers",  
              "Olivia Rodrigo",  
              "Taylor Swift",  
              "Bad Bunny",  
            ],  
          }  
        ],  
      },  
      {  
        "title": "PERFORMED_BY",  
        "type": "object",  
        "description": "关系(Relationship)",  
        "properties": [  
          {  
            "name": "_from",  
            "type": "string",  
            "description": "此关系开始于的节点标签",  
            "examples": ["Track"],  
          },  
          {  
            "name": "_to",  
            "type": "string",  
            "description": "此关系结束于的节点标签",  
            "examples": ["Artist"],  
          },  
        ],  
      },  
    ]
    [  
      {  
        "title": "曲目",  
        "type": "object",  
        "description": "音轨节点",  
        "properties": [  
          {  
            "name": "name",  
            "column_name": "track_name",  
            "type": "string",  
            "description": "曲目名称",  
            "examples": [  
              "Seven (feat. Latto) (Explicit Ver.)",  
              "LALA",  
              "vampire",  
              "Cruel Summer",  
              "WHERE SHE GOES",  
            ],  
          },  
          {  
            "name": "artist_count",  
            "column_name": "artist_count",  
            "type": "integer",  
            "description": "参与艺术家人数",  
            "examples": [2, 1, 3, 8, 4],  
          },  
          {  
            "name": "released_year",  
            "column_name": "released_year",  
            "type": "integer",  
            "description": "发行年",  
            "examples": [2023, 2019, 2022, 2013, 2014],  
          },  
          {  
            "name": "released_month",  
            "column_name": "released_month",  
            "type": "integer",  
            "description": "发行月",  
            "examples": [7, 3, 6, 8, 5],  
          },  
          {  
            "name": "released_day",  
            "column_name": "released_day",  
            "type": "integer",  
            "description": "发行日",  
            "examples": [14, 23, 30, 18, 1],  
          },  
          {  
            "name": "inSpotifyPlaylists",  
            "column_name": "in_spotify_playlists",  
            "type": "integer",  
            "description": "Spotify播放列表中的曲目数",  
            "examples": [553, 1474, 1397, 7858, 3133],  
          },  
          {  
            "name": "inSpotifyCharts",  
            "column_name": "in_spotify_charts",  
            "type": "integer",  
            "description": "Spotify排行榜中的排名次数",  
            "examples": [147, 48, 113, 100, 50],  
          },  
          {  
            "name": "streams",  
            "column_name": "streams",  
            "type": "array",  
            "description": "流媒体ID列表",  
            "examples": [  
              "141381703",  
              "133716286",  
              "140003974",  
              "800840817",  
              "303236322",  
            ],  
          },  
          {  
            "name": "inApplePlaylists",  
            "column_name": "in_apple_playlists",  
            "type": "integer",  
            "description": "Apple播放列表中的曲目数",  
            "examples": [43, 48, 94, 116, 84],  
          },  
          {  
            "name": "inAppleCharts",  
            "column_name": "in_apple_charts",  
            "type": "integer",  
            "description": "Apple排行榜中的排名次数",  
            "examples": [263, 126, 207, 133, 213],  
          },  
          {  
            "name": "inDeezerPlaylists",  
            "column_name": "in_deezer_playlists",  
            "type": "array",  
            "description": "Deezer播放列表中的ID列表",  
            "examples": ["45", "58", "91", "125", "87"],  
          },  
          {  
            "name": "inDeezerCharts",  
            "column_name": "in_deezer_charts",  
            "type": "integer",  
            "description": "Deezer排行榜中的排名次数",  
            "examples": [10, 14, 12, 15, 17],  
          },  
          {  
            "name": "inShazamCharts",  
            "column_name": "in_shazam_charts",  
            "type": "array",  
            "description": "Shazam排行榜中的ID列表",  
            "examples": ["826", "382", "949", "548", "425"],  
          },  
          {  
            "name": "bpm",  
            "column_name": "bpm",  
            "type": "integer",  
            "description": "节拍数",  
            "examples": [125, 92, 138, 170, 144],  
          },  
          {  
            "name": "key",  
            "column_name": "key",  
            "type": "string",  
            "description": "调式",  
            "examples": ["B", "C#", "F", "A", "D"],  
          },  
          {  
            "name": "mode",  
            "column_name": "mode",  
            "type": "string",  
            "description": "调性(例如大调、小调)",  
            "examples": ["Major", "Minor"],  
          },  
          {  
            "name": "danceability",  
            "column_name": "danceability_%",  
            "type": "integer",  
            "description": "舞曲性",  
            "examples": [80, 71, 51, 55, 65],  
          },  
          {  
            "name": "valence",  
            "column_name": "valence_%",  
            "type": "integer",  
            "description": "情感强度",  
            "examples": [89, 61, 32, 58, 23],  
          },  
          {  
            "name": "energy",  
            "column_name": "energy_%",  
            "type": "integer",  
            "description": "能量值",  
            "examples": [83, 74, 53, 72, 80],  
          },  
          {  
            "name": "acousticness",  
            "column_name": "acousticness_%",  
            "type": "integer",  
            "description": "原声性百分比",  
            "examples": [31, 7, 17, 11, 14],  
          },  
          {  
            "name": "instrumentalness",  
            "column_name": "instrumentalness_%",  
            "type": "integer",  
            "description": "器乐性百分比",  
            "examples": [0, 63, 17, 2, 19],  
          },  
          {  
            "name": "liveness",  
            "column_name": "liveness_%",  
            "type": "integer",  
            "description": "现场感百分比",  
            "examples": [8, 10, 31, 11, 28],  
          },  
          {  
            "name": "speechiness",  
            "column_name": "speechiness_%",  
            "type": "integer",  
            "description": "语言性",  
            "examples": [4, 6, 15, 24, 3],  
          },  
          {  
            "name": "coverUrl",  
            "column_name": "cover_url",  
            "type": "string",  
            "description": "封面图链接",  
            "examples": [  
              "https://i.scdn.co/image/ab67616d0000b2730656d5ce813ca3cc4b677e05",  
              "https://i.scdn.co/image/ab67616d0000b273e85259a1cae29a8d91f2093d",  
            ],  
          },  
        ],  
      },  
      {  
        "title": "Artist",  
        "type": "object",  
        "description": "艺术家节点",  
        "properties": [  
          {  
            "name": "name",  
            "column_name": "artist(s)_name",  
            "type": "string",  
            "description": "艺术家名称。将列中的值按逗号分割",  
            "examples": [  
              "Latto",  
              "Jung Kook",  
              "Myke Towers",  
              "Olivia Rodrigo",  
              "Taylor Swift",  
              "Bad Bunny",  
            ],  
          }  
        ],  
      },  
      {  
        "title": "PERFORMED_BY",  
        "type": "object",  
        "description": "关系",  
        "properties": [  
          {  
            "name": "_from",  
            "type": "string",  
            "description": "关系开始的节点标签",  
            "examples": ["Track"],  
          },  
          {  
            "name": "_to",  
            "type": "string",  
            "description": "关系结束的节点标签",  
            "examples": ["Artist"],  
          },  
        ],  
      },  
    ]
添加独特的标识符

我注意到这个架构中没有包含任何唯一标识符,这在导入关系时可能引起问题。理所当然,不同的艺术家可能会发行同名歌曲,例如这个例子所示,而且两名艺术家可能同名。

这样一来,为了在数据集中区分Tracks,创建一个标识符变得非常重要。

    # 添加主键/唯一标识符  
    uid_prompt = PromptTemplate.from_template("""  
    你是一位图数据库专家,正在检查一位同事生成的数据模型中的一个单一实体。  
    你需要确保所有导入数据库的节点都是独一无二的,确保这些节点没有重复。
## 示例A模式包含具有多个属性的演员,包括姓名和出生日期。如果有两个演员名字相同,则添加一个新的组合属性,将姓名和出生日期结合。  
如果组合值,请包括将值转换成符合slug格式的指令。将新属性命名为'id'(通常指唯一标识符)。如已确定新属性,请将其添加到属性列表中,保持其他属性不变。  
描述中应包括需要连接的字段。  
## 示例输出如下  
```  
{example_output}  
```## 当前实体模式如下  
```  
{entity}  
```  
uid_chain = uid_prompt | llm.with_structured_output(JSONSchemaSpecification)

这一步主要针对节点,因此我从模式中提取了节点,为每个节点运行了链路,然后将这些关系与更新定义进行了整合。
# 提取节点和关系类型
nodes = [n for n in existing_model if "node" in n["description"].lower()]  
rels = [n for n in existing_model if "node" not in n["description"].lower()]
# 注意:这里使用了lower()函数将描述转换为小写,以确保在判断节点和关系类型时的一致性。
生成节点的唯一ID:

with_uids = []
for entity in nodes:
res = uid_chain.invoke(dict(entity=dumps(entity)))

将响应结果解析为JSON格式

json = loads(res.jsonschema)
with_uids = with_uids + json if type(json) == list else with_uids + [json]

将解析后的ID列表添加到原始列表中
将节点和关系合并

with_uids = with_uids + rels

rels: 关系列表
调用生成唯一ID的链式方法

# 数据模型检查

为了保持理智,检查模型是否已经优化过是值得的。`模型提示` 在识别名词和动词方面表现得很出色,但在更复杂的模型中。

在一次迭代中,`*_playlists` 和 `_charts` 列被视为ID,并尝试创建 `Stream` 节点及 `IN_PLAYLIST` 关系。这可能是因为超过1,000的计数使用了带有逗号的格式(如 `1,001`)。

不错的想法,但也许有点太复杂了。这说明理解数据结构的人类角色很重要。
# 添加主键/唯一标识符  
review_prompt = PromptTemplate.from_template("""  
你是一名图数据库专家,正在审查同事生成的数据模型。  

你的任务是审查数据模型,确保它适合其目的。  
检查以下内容:  

## 检查嵌套的实体  

请记住,Neo4j无法存储对象数组或嵌套对象。  
这些必须转换成单独的节点,并通过关系进行连接。  
你必须在输出模式中包括新节点和关系的引用。  

## 检查实体中的属性  

如果有一个表示ID数组的属性,则应为该实体创建一个新节点。  
你必须在输出模式中包括新节点和关系的引用。  

# 保留说明  

确保节点、关系和属性的说明清晰简洁。  
你可以在不影响细节的情况下改进它们。  

## 当前实体模式  

{entity}  
""")  

review_chain = review_prompt | llm.with_structured_output(JSONSchemaSpecification)  

review_nodes = [n for n in with_uids if "node" in n["description"].lower() ]  
review_rels = [n for n in with_uids if "node" not in n["description"].lower() ]  

reviewed = []  

for entity in review_nodes:  
  res = review_chain.invoke(dict(entity=dumps(entity)))  
  json = loads(res.jsonschema)  

  reviewed = reviewed + json  

# 将关系重新加入  
reviewed = reviewed + review_rels  

len(reviewed) # 输出reviewed的长度  

reviewed = with_uids # 将uid信息重新赋值给reviewed

在实际情况中,我会多跑几次以逐步优化数据模型。我会设定一个上限,然后迭代到数据模型不再变化为止。

导入需要的语句

到这时,架构应该足够健壮和完整,并包含尽可能多的信息,让大型语言模型能够生成一整套导入脚本。

根据[Neo4j 数据导入建议](https://graphacademy.neo4j.com/courses/importing-fundamentals/?ref=adam),文件需要分多次处理,每次只导入一个数据库节点或关系,以避免贪婪操作和锁定。
import_prompt = PromptTemplate.from_template("""  
根据数据模型,编写一个Cypher语句,将以下数据从CSV文件导入Neo4j中。  

请勿使用LOAD CSV,因为这些数据将使用Neo4j Python Driver导入,而应使用$rows参数的UNWIND。  
你正在编写一个多步骤的导入过程,因此请专注于提到的实体。  
在导入数据时,您必须遵循以下指南:  
  • 根据描述中的说明识别主键。

  • 在识别属性时,根据描述中的说明确定属性格式。

  • 在将字段合并为ID时,使用apoc.text.slug函数将任何文本转换为缩略格式,并使用toLower将字符串转换为小写 - apoc.text.slug(toLower(row.name))

  • 如果拆分属性,则将其转换为字符串,并使用trim函数删除任何空格 - trim(toString(row.name))

  • 在将属性组合在一起时,将每个属性包装在coalesce函数中,以确保当其中一个值未设置时属性不为null - coalesce(row.id, '') + '--'+ coalesce(row.title)

  • 使用column_name字段将CSV列映射到数据模型中的属性。

  • 将CSV中的所有列名用反引号括起来 - 例如 row.column_name

  • 在合并节点时,仅根据唯一标识符进行合并。将所有其他属性使用SET进行设置。

  • 请不要使用apoc.periodic.iterate,文件将在应用程序中批量处理。
    数据模型:
    {data_model}  

    当前实体信息:

    {entity}  

    """)

这条链需要与之前的步骤有不同的输出对象。在这个情况下,cypher 成员显得尤为重要,但我还想加入一个 chain_of_thought 关键字以鼓励链式思维。

    class CypherOutputSpecification(BaseModel):  
      chain_of_thought: str = Field(description="用于生成Cypher语句的任何推理过程")  
      cypher: str = Field(description="用于导入数据的Cypher查询语句")  
      notes: Optional[str] = Field(description="关于Cypher语句的任何注释或其他说明")
import_chain = import_prompt | llm.with_structured_output(CypherOutputSpecification) # 导入链是从导入提示开始,然后通过带有Cypher结构化输出规范的llm

同样的步骤也适用于遍历每个已审核定义,以此生成Cypher。

    import_cypher = []  
    for n in reviewed:  
      print('\n\n------', n['title'])  # 打印当前项的标题
      res = import_chain.invoke(dict(  
        data_model=dumps(reviewed),  
        entity=n  
      ))  
      import_cypher.append(res.cypher)  # 将查询语句添加到列表中
      print(res.cypher)  # 输出查询语句,便于调试

控制台输出结果:

    ------ 轨道
    UNWIND $rows AS row
    MERGE (t:Track {id: apoc.text.slug(toLower(coalesce(row.`track_name`, '') + '-' + coalesce(row.`released_year`, '')))}) // COALESCE(row.`track_name`, '') // 如果 `track_name` 为空则使用空字符串替代
    SET t.name = trim(toString(row.`track_name`)),  
        t.artist_count = toInteger(row.`artist_count`),  
        t.released_year = toInteger(row.`released_year`),  
        t.released_month = toInteger(row.`released_month`),  
        t.released_day = toInteger(row.`released_day`),  
        t.inSpotifyPlaylists = toInteger(row.`in_spotify_playlists`),  
        t.inSpotifyCharts = toInteger(row.`in_spotify_charts`),  
        t.streams = row.`streams`,  
        t.inApplePlaylists = toInteger(row.`in_apple_playlists`),  
        t.inAppleCharts = toInteger(row.`in_apple_charts`),  
        t.inDeezerPlaylists = row.`in_deezer_playlists`,  
        t.inDeezerCharts = toInteger(row.`in_deezer_charts`),  
        t.inShazamCharts = row.`in_shazam_charts`,  
        t.bpm = toInteger(row.`bpm`),  
        t.key = trim(toString(row.`key`)),  
        t.mode = trim(toString(row.`mode`)),  
        t.danceability = toInteger(row.`danceability_%`),  
        t.valence = toInteger(row.`valence_%`),  
        t.energy = toInteger(row.`energy_%`),  
        t.acousticness = toInteger(row.`acousticness_%`),  
        t.instrumentalness = toInteger(row.`instrumentalness_%`),  
        t.liveness = toInteger(row.`liveness_%`),  
        t.speechiness = toInteger(row.`speechiness_%`),  
        t.coverUrl = CASE row.`cover_url` WHEN 'Not Found' THEN '' ELSE trim(toString(row.`cover_url`)) END // 封面URL 如果 `cover_url` 为 'Not Found' 则使用空字符串替代
    ------ 艺术家
    UNWIND $rows AS row
    WITH row, split(row.`artist(s)_name`, ',') AS artistNames
    UNWIND artistNames AS artistName
    MERGE (a:Artist {id: apoc.text.slug(toLower(trim(artistName)))})  
    SET a.name = trim(artistName)
    ------ 演唱
    UNWIND $rows AS row
    UNWIND split(row.`artist(s)_name`, ',') AS artist_name
    MERGE (t:Track {id: apoc.text.slug(toLower(row.`track_name`)) + '-' + trim(toString(row.`released_year`))})
    MERGE (a:Artist {id: apoc.text.slug(toLower(trim(artist_name)))})
    MERGE (t)-[:PERFORMED_BY]->(a) // 表示歌曲由该艺术家表演

这个提示的制作需要一些技术处理以实现一致的效果:

  • 有时 Cypher 中的 MERGE 语句会定义多个字段,这在最佳情况下也显得不够理想。如果任何列为空,整个导入将失败。
  • 有时结果会包括 [apoc.period.iterate](https://neo4j.com/docs/apoc/current/overview/apoc.periodic/apoc.periodic.iterate/),这个功能现在不再需要了,我想要一个我可以使用 Python 驱动程序执行的代码。
  • 我反复强调在创建关系时应该使用指定的列名。
  • 在用关系两端节点的唯一标识符时,LLM 总是不听指令,我得反复尝试才能让它按照描述中的指示去做。在这个提示和 model_prompt 之间有不少来回。
  • 如果列名包含特殊字符(例如 energy_%),就需要用反引号括起来。

将这个任务分成两个提示也会很有帮助——一个是关于节点,另一个是关于关系的。但这留待以后再说吧。

创建唯一性约束

接下来,可以以导入的脚本为基础,在数据库中创建唯一性约束。

    constraint_prompt = PromptTemplate.from_template("""  
    你是一位专业的图数据库管理员。  
    根据以下Cypher语句,编写一个Cypher语句来为MERGE语句中使用的任何属性创建唯一约束。  
    """)  
唯一约束的正确语法如下:  
CREATE CONSTRAINT movie_title_id IF NOT EXISTS FOR (m:Movie) REQUIRE m.title IS UNIQUE;Cypher:  

{cypher}

(示例Cypher代码如下)  
""")constraint_chain = constraint_prompt | llm.with_structured_output(CypherOutputSpecification)  # Cypher输出规范  
constraint_queries = []  
for statement in import_cypher:  
    res = constraint_chain.invoke(dict(cypher=statement))  
    statements = res.cypher.split(";")  
    for cypher in statements:  
        constraint_queries.append(cypher)

控制台输出信息:

    如果不存在,则创建约束 track_id_unique,应用于 (t:Track),要求 t.id 唯一

    如果不存在,则创建约束 stream_id,应用于 (s:Stream),要求 s.id 唯一

    如果不存在,则创建约束 playlist_id,应用于 (p:Playlist),要求 p.id 唯一

    如果不存在,则创建约束 chart_id,应用于 (c:Chart),要求 c.id 唯一

    如果不存在,则创建约束 track_id_unique,应用于 (t:Track),要求 t.id 唯一

    如果不存在,则创建约束 stream_id_unique,应用于 (s:Stream),要求 s.id 唯一

    如果不存在,则创建约束 track_id_unique,应用于 (t:Track),要求 t.id 唯一

    如果不存在,则创建约束 playlist_id_unique,应用于 (p:Playlist),要求 p.id 唯一

    如果不存在,则创建约束 track_id_unique,应用于 (track:Track),要求 track.id 唯一

    如果不存在,则创建约束 chart_id_unique,应用于 (chart:Chart),要求 chart.id 唯一

有时候这个提示会返回关于索引和约束的声明,因此会在分号处进行分隔处理。

运行导入操作

一切都准备好了,该执行这些Cypher语句了:

    从 os 模块导入 getenv  
    从 neo4j 模块导入 GraphDatabase
driver = GraphDatabase.driver(  
  getenv("NEO4J_URI"),  
  auth=(  
    getenv("NEO4J_USERNAME"),  
    getenv("NEO4J_PASSWORD")  
  )  
)with driver.session() as session:  # 使用driver创建一个session
  # 清空数据库:
  session.run("MATCH (n) DETACH DELETE n")  # 创建约束条件
  for q in constraint_queries:  
    if q.strip() != "":  
      session.run(q)  # 导入数据记录
  for q in import_cypher:  
    if q.strip() != "":  
      res = session.run(q, rows=rows).consume()  
      print(q)  # 打印查询语句
      print(res.counters)  # 打印结果计数器
数据集QA

这篇帖子若没有用 GraphCypherQAChain 做一些 QA 就不完整了。

    从langchain.chains导入GraphCypherQAChain模块  
    从langchain_community.graphs导入Neo4jGraph模块
    graph = Neo4jGraph(  
      url=getenv("NEO4J_URI"),  
      username=getenv("NEO4J_USERNAME"),  
      password=getenv("NEO4J_PASSWORD"),  
      enhanced_schema=True  
    )qa = GraphCypherQAChain.from_llm(  
      llm,  
      graph=graph,  
      allow_dangerous_requests=True,  
      verbose=True  
    )
最受欢迎的艺人

数据库里哪些艺术家最受欢迎?

    qa.invoke({"query": "哪些艺术家最受欢迎?"})
    > 进入新的GraphCypherQAChain链...  
    生成的Cypher语句:  
    MATCH (:Track)-[:PERFORMED_BY]->(a:Artist)  
    返回 a.name, COUNT(*) AS popularity  
    按 popularity 降序排列  
    限制为前10条
    完整信息:  
    [{'a.name': 'Bad Bunny', 'popularity': 40}, {'a.name': 'Taylor Swift', 'popularity': 38}, {'a.name': 'The Weeknd', 'popularity': 36}, {'a.name': 'SZA', 'popularity': 23}, {'a.name': 'Kendrick Lamar', 'popularity': 23}, {'a.name': 'Feid', 'popularity': 21}, {'a.name': 'Drake', 'popularity': 19}, {'a.name': 'Harry Styles', 'popularity': 17}, {'a.name': 'Peso Pluma', 'popularity': 16}, {'a.name': '21 Savage', 'popularity': 14}]> 完成的链。
    {  
      "query": "哪些问题是关于谁是最受欢迎的艺术家?",  
      "result": "Bad Bunny、Taylor Swift 和韦史密斯是目前最火的歌手。"  
    }

似乎,LLM是以一个艺术家参与的歌曲数量来判断其受欢迎程度,而不是他们的总播放量。

每分钟拍数

哪首歌BPM最高?

    qa.invoke({"请求": "哪首曲目的节奏最快?"})
    > 正在进入新的GraphCypherQAChain链...  
    生成的Cypher代码:  
    MATCH (t:Track)  
    RETURN t  
    按 t.bpm DESC 排序  
    限制为 1
完整上下文:,  
[{'t': {'id': 'seven-feat-latto-explicit-ver--2023'}}]> 链已完成。
    {  
      "query": "哪首曲子的BPM最高?",  
      "result": "这我不知道哦。"  
    }
优化Cypher生成提示

在这种情况下,Cypher 看起来是正确的,并且正确的结果包含在提示中,但 gpt-4o(注:gpt-4o)无法解释答案。看来传递到 GraphCypherQAChainCYPHER_GENERATION_PROMPT 可能需要添加更多指令来使列名更加清晰。

在Cypher语句中始终使用详细的列名,使用标签和属性的名称。例如,用‘person_name’代替‘name’。

GraphCypherQAChain,使用了自定义提示哦。

    YPHER_GENERATION_TEMPLATE = """任务:生成Cypher查询语句以查询图数据库。  
    指令:  
    仅使用提供的关系类型和模式中的属性。  
    不使用除提供的模式之外的任何关系类型或属性。  
    模式:  
    {schema}  
    注意:不要在您的响应中包含任何解释或道歉。  
    不要回答任何除构造Cypher语句外的其他问题。  
    仅包含生成的Cypher语句。  

    在Cypher语句中始终使用完整的列名,例如使用'person_name'而不是'name'。  
    结果中包含节点周围直接网络的数据以提供额外的上下文。例如,包括电影的发行年份、演员列表及其角色,或电影的导演。  
    当按属性排序时,请添加`IS NOT NULL`检查以确保仅返回具有该属性的节点。  

    示例:以下是根据特定问题生成的Cypher语句示例:  

    # 有多少人在《Top Gun》中出演?  
    MATCH (m:Movie {name:"Top Gun"})  
    RETURN COUNT { (m)<-[:ACTED_IN]-() } AS numberOfActors  

    问题:  
    {question}"""  

    CYPHER_GENERATION_PROMPT = PromptTemplate(  
        input_variables=["schema", "question"], template=CYPHER_GENERATION_TEMPLATE  
    )  

    qa = GraphCypherQAChain.from_llm(  
      llm,  
      graph=graph,  
      allow_dangerous_requests=True,  
      verbose=True,  
      cypher_prompt=CYPHER_GENERATION_PROMPT,  
    )
最多艺术家唱过的歌曲

图表擅长按类型和方向统计关系的数量。

    qa.invoke({"query": "哪些曲目由最多艺术家演唱/演奏?"})
    > 进入新的GraphCypherQAChain链...  
    生成的Cypher代码:  
    cypher  
    执行匹配:MATCH (t:Track)  
        WITH t, COUNT { (t)-[:PERFORMED_BY]->(:Artist) }  as artist_count  
    其中artist_count不为空  
    RETURN t.id AS track_id, t.name AS track_name, artist_count  
    按artist_count降序排列
    完整上下文如下:  
    [{'track_id': 'los-del-espacio-2023', 'track_name': 'Los del Espacio', 'artist_count': 8}, {'track_id': 'se-le-ve-2021', 'track_name': 'Se Le Ve', 'artist_count': 8}, {'track_id': 'we-dont-talk-about-bruno-2021', 'track_name': "We Don't Talk About Bruno", 'artist_count': 7}, {'track_id': 'cayï-ï-la-noche-feat-cruz-cafunï-ï-abhir-hathi-bejo-el-ima--2022', 'track_name': 无, 'artist_count': 6}, {'track_id': 'jhoome-jo-pathaan-2022', 'track_name': 'Jhoome Jo Pathaan', 'artist_count': 6}, {'track_id': 'besharam-rang-from-pathaan--2022', 'track_name': 无, 'artist_count': 6}, {'track_id': 'nobody-like-u-from-turning-red--2022', 'track_name': 无, 'artist_count': 6}, {'track_id': 'ultra-solo-remix-2022', 'track_name': 'ULTRA SOLO REMIX', 'artist_count': 5}, {'track_id': 'angel-pt-1-feat-jimin-of-bts-jvke-muni-long--2023', 'track_name': 无, 'artist_count': 5}, {'track_id': 'link-up-metro-boomin-dont-toliver-wizkid-feat-beam-toian-spider-verse-remix-spider-man-across-the-spider-verse--2023', 'track_name': 无, 'artist_count': 5}]> 完成。
    {  
      "query": "哪首歌曲由最多的艺术家演唱?",  
      "result": "这两首歌曲\"Los del Espacio\"和\"Se Le Ve\"由最多的艺术家演唱,每首歌曲都有8位艺术家。"  
    }

总结.

CSV分析和建模是最费时的,生成可能会花上超过五分钟。

成本本身相当低。在八小时的实验里,我差不多发送了数百个请求,只花了一美元左右。

为了达到这一点,我们遇到了不少难题。

  • 提示需要经过多次调整才能正确。这个问题可以通过微调模型或提供几个示例来克服。
  • GPT-4o 的 JSON 响应有时可能不一致。我被推荐了 [json-repair](https://github.com/mangiucugna/json_repair),这比让 LLM 自己验证 JSON 输出更好。

这种方法在LangGraph实现中可能会运行得很好,其中操作是按顺序执行的,这使得LLM能够逐步构建和优化模型。随着新模型的不断发布,它们也可能从进一步的微调中获益。

一个了解更多信息的地方

了解更多信息,可以查看Harnessing Large Language Models With Neo4j以了解更多关于使用LLM简化知识图谱的创建过程的信息。阅读Create a Neo4j GraphRAG Workflow Using LangChain and LangGraph以了解更多关于LangGraph和Neo4j的信息。想了解更多关于微调的内容,可以看看Knowledge Graphs and LLMs: Fine-Tuning vs. Retrieval-Augmented Generation

点击查看更多内容
TA 点赞

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

评论

作者其他优质文章

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

100积分直接送

付费专栏免费学

大额优惠券免费领

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

举报

0/150
提交
取消