开源 AI 食谱文档

使用知识图谱增强 RAG 推理

Hugging Face's logo
加入 Hugging Face 社区

并获得增强型文档体验

开始使用

Open In Colab

使用知识图谱增强 RAG 推理

作者:Diego Carpintero

知识图谱提供了一种方法来建模和存储互联信息,其格式既可被人理解也可被机器理解。这些图由节点组成,分别表示实体及其关系。与传统数据库不同,图的固有表达能力允许更丰富的语义理解,同时提供灵活性以适应新的实体类型和关系,而无需受限于固定的模式。

通过将知识图谱与嵌入(向量搜索)结合起来,我们可以利用多跳连接信息的上下文理解来增强大语言模型的推理和可解释性。

此笔记本探讨了这种方法的实践实现,演示了如何

  • 使用合成数据集在Neo4j中构建与研究出版物相关的知识图谱,
  • 使用嵌入模型将我们数据字段的子集投影到高维向量空间中,
  • 在这些嵌入上构建向量索引以启用相似性搜索,以及
  • 通过将用户查询轻松转换为Cypher语句,并使用LangChain从我们的图中提取自然语言洞察

初始化

%pip install neo4j langchain langchain_openai langchain-community python-dotenv --quiet

设置 Neo4j 实例

我们将使用Neo4j创建我们的知识图谱,Neo4j 是一个开源数据库管理系统,专门从事图数据库技术。

为了快速轻松地设置,您可以在Neo4j Aura上启动一个免费实例。

然后,您可以使用 .env 文件将 NEO4J_URINEO4J_USERNAMENEO4J_PASSWORD 设置为环境变量

import dotenv

dotenv.load_dotenv(".env", override=True)

Langchain 提供了 Neo4jGraph 类来与 Neo4j 进行交互

import os
from langchain_community.graphs import Neo4jGraph

graph = Neo4jGraph(
    url=os.environ["NEO4J_URI"],
    username=os.environ["NEO4J_USERNAME"],
    password=os.environ["NEO4J_PASSWORD"],
)

将数据集加载到图中

以下示例建立与我们的Neo4j数据库的连接,并使用包含研究文章及其作者的合成数据填充它。

实体是

  • 研究人员
  • 文章
  • 主题

而关系是

  • 研究人员 —[PUBLISHED]—> 文章
  • 文章 —[IN_TOPIC]—> 主题
from langchain_community.graphs import Neo4jGraph

graph = Neo4jGraph()

q_load_articles = """
LOAD CSV WITH HEADERS
FROM 'https://raw.githubusercontent.com/dcarpintero/generative-ai-101/main/dataset/synthetic_articles.csv' 
AS row 
FIELDTERMINATOR ';'
MERGE (a:Article {title:row.Title})
SET a.abstract = row.Abstract,
    a.publication_date = date(row.Publication_Date)
FOREACH (researcher in split(row.Authors, ',') | 
    MERGE (p:Researcher {name:trim(researcher)})
    MERGE (p)-[:PUBLISHED]->(a))
FOREACH (topic in [row.Topic] | 
    MERGE (t:Topic {name:trim(topic)})
    MERGE (a)-[:IN_TOPIC]->(t))
"""

graph.query(q_load_articles)

让我们检查节点和关系是否已正确初始化

>>> graph.refresh_schema()
>>> print(graph.get_schema)
Node properties:
Article {title: STRING, abstract: STRING, publication_date: DATE, embedding: LIST}
Researcher {name: STRING}
Topic {name: STRING}
Relationship properties:

The relationships:
(:Article)-[:IN_TOPIC]->(:Topic)
(:Researcher)-[:PUBLISHED]->(:Article)

我们的知识图谱可以在 Neo4j 工作区中查看

构建向量索引

现在我们构建一个向量索引,以便根据其主题、标题和摘要有效地搜索相关的文章。此过程涉及使用这些字段计算每篇文章的嵌入。在查询时,系统通过使用相似性度量(例如余弦距离)查找与用户输入最相似的文章。

from langchain_community.vectorstores import Neo4jVector
from langchain_openai import OpenAIEmbeddings

vector_index = Neo4jVector.from_existing_graph(
    OpenAIEmbeddings(),
    url=os.environ["NEO4J_URI"],
    username=os.environ["NEO4J_USERNAME"],
    password=os.environ["NEO4J_PASSWORD"],
    index_name="articles",
    node_label="Article",
    text_node_properties=["topic", "title", "abstract"],
    embedding_node_property="embedding",
)

注意:要访问 OpenAI 嵌入模型,您需要创建一个 OpenAI 帐户,获取 API 密钥,并将OPENAI_API_KEY设置为环境变量。您可能还会发现尝试其他嵌入模型集成很有用。

基于相似度的问答

Langchain RetrievalQA使用上述向量索引作为检索器创建一个问答 (QA) 链。

from langchain.chains import RetrievalQA
from langchain_openai import ChatOpenAI

vector_qa = RetrievalQA.from_chain_type(llm=ChatOpenAI(), chain_type="stuff", retriever=vector_index.as_retriever())

让我们问“哪些文章讨论了人工智能可能如何影响我们的日常生活?”。

>>> r = vector_qa.invoke(
...     {
...         "query": "which articles discuss how AI might affect our daily life? include the article titles and abstracts."
...     }
... )
>>> print(r["result"])
The articles that discuss how AI might affect our daily life are:

1. **The Impact of AI on Employment: A Comprehensive Study**
   *Abstract:* This study analyzes the potential effects of AI on various job sectors and suggests policy recommendations to mitigate negative impacts.

2. **The Societal Implications of Advanced AI: A Multidisciplinary Analysis**
   *Abstract:* Our study brings together experts from various fields to analyze the potential long-term impacts of advanced AI on society, economy, and culture.

These two articles would provide insights into how AI could potentially impact our daily lives from different perspectives.

遍历知识图以进行推理

知识图谱非常适合在实体之间建立连接,从而能够提取模式并发现新的见解。

本节演示如何实现此过程并将结果使用自然语言查询集成到 LLM 管道中。

使用 LangChain 的图-Cypher-链

为了构建表达性和高效的查询,Neo4j用户使用Cypher,这是一种受 SQL 启发的声明式查询语言。LangChain提供了包装器GraphCypherQAChain,这是一个抽象层,允许使用自然语言查询图数据库,从而更轻松地将基于图的数据检索集成到 LLM 管道中。

在实践中,GraphCypherQAChain

  • 从用户输入(自然语言)生成 Cypher 语句(针对 Neo4j 等图数据库的查询),应用上下文学习(提示工程),
  • 针对图数据库执行所述语句,以及
  • 提供结果作为上下文,以便将 LLM 响应建立在准确、最新的信息基础上

注意:此实现涉及执行模型生成的图查询,这会带来固有的风险,例如意外访问或修改数据库中的敏感数据。为了降低这些风险,请确保您的数据库连接权限尽可能受限,以满足您的链/代理的特定需求。虽然此方法降低了风险,但并不能完全消除风险。

from langchain.chains import GraphCypherQAChain
from langchain_openai import ChatOpenAI

graph.refresh_schema()

cypher_chain = GraphCypherQAChain.from_llm(
    cypher_llm=ChatOpenAI(temperature=0, model_name="gpt-4o"),
    qa_llm=ChatOpenAI(temperature=0, model_name="gpt-4o"),
    graph=graph,
    verbose=True,
)

使用自然语言的查询示例

请注意以下示例中如何将 cypher 查询执行的结果作为上下文提供给 LLM

“Emily Chen 发表了多少篇文章?”

在这个示例中,我们的问题“Emily Chen 发表了多少篇文章?”将被翻译成 Cypher 查询

MATCH (r:Researcher {name: "Emily Chen"})-[:PUBLISHED]->(a:Article)
RETURN COUNT(a) AS numberOfArticles

它匹配标记为Author且名称为“Emily Chen”的节点,并遍历到Article节点的PUBLISHED关系。然后计算连接到“Emily Chen”的Article节点的数量

>>> # the answer should be '7'
>>> cypher_chain.invoke({"query": "How many articles has published Emily Chen?"})
> Entering new GraphCypherQAChain chain...
Generated Cypher:
cypher
MATCH (r:Researcher {name: "Emily Chen"})-[:PUBLISHED]->(a:Article)
RETURN COUNT(a) AS numberOfArticles

Full Context:
[{'numberOfArticles': 7}]

> Finished chain.

“是否有任何一对研究人员共同发表了三篇以上文章?”

在这个示例中,查询“是否有任何一对研究人员共同发表了三篇以上文章?”导致 Cypher 查询

MATCH (r1:Researcher)-[:PUBLISHED]->(a:Article)<-[:PUBLISHED]-(r2:Researcher)
WHERE r1 <> r2
WITH r1, r2, COUNT(a) AS sharedArticles
WHERE sharedArticles > 3
RETURN r1.name, r2.name, sharedArticles

它导致从Researcher节点遍历到PUBLISHED关系以查找连接的Article节点,然后反向遍历以查找Researchers对。

>>> # the answer should be David Johnson & Emily Chen, Robert Taylor & Emily Chen
>>> cypher_chain.invoke(
...     {"query": "are there any pair of researchers who have published more than three articles together?"}
... )
> Entering new GraphCypherQAChain chain...
Generated Cypher:
cypher
MATCH (r1:Researcher)-[:PUBLISHED]->(a:Article)<-[:PUBLISHED]-(r2:Researcher)
WHERE r1 <> r2
WITH r1, r2, COUNT(a) AS sharedArticles
WHERE sharedArticles > 3
RETURN r1.name, r2.name, sharedArticles

Full Context:
[&#123;'r1.name': 'David Johnson', 'r2.name': 'Emily Chen', 'sharedArticles': 4}, &#123;'r1.name': 'Robert Taylor', 'r2.name': 'Emily Chen', 'sharedArticles': 4}, &#123;'r1.name': 'Emily Chen', 'r2.name': 'David Johnson', 'sharedArticles': 4}, &#123;'r1.name': 'Emily Chen', 'r2.name': 'Robert Taylor', 'sharedArticles': 4}]

> Finished chain.

“哪位研究人员与最多同行合作?”

让我们找出与最多同行合作的研究人员。我们的查询“哪位研究人员与最多同行合作?”现在导致 Cypher

MATCH (r:Researcher)-[:PUBLISHED]->(:Article)<-[:PUBLISHED]-(peer:Researcher)
WITH r, COUNT(DISTINCT peer) AS peerCount
RETURN r.name AS researcher, peerCount
ORDER BY peerCount DESC
LIMIT 1

这里,我们需要从所有Researcher节点开始,遍历它们的PUBLISHED关系,以查找连接的Article节点。对于每个Article节点,Neo4j 然后回溯查找其他也发表了同一篇文章的Researcher节点(同行)。

>>> # the answer should be 'David Johnson'
>>> cypher_chain.invoke({"query": "Which researcher has collaborated with the most peers?"})
> Entering new GraphCypherQAChain chain...
Generated Cypher:
cypher
MATCH (r1:Researcher)-[:PUBLISHED]->(:Article)<-[:PUBLISHED]-(r2:Researcher)
WHERE r1 <> r2
WITH r1, COUNT(DISTINCT r2) AS collaborators
RETURN r1.name AS researcher, collaborators
ORDER BY collaborators DESC
LIMIT 1

Full Context:
[&#123;'researcher': 'David Johnson', 'collaborators': 6}]

> Finished chain.

< > 在 GitHub 上更新