使用知识图谱增强 RAG 推理
知识图谱提供了一种方法来建模和存储互联信息,其格式既可被人理解也可被机器理解。这些图由节点和边组成,分别表示实体及其关系。与传统数据库不同,图的固有表达能力允许更丰富的语义理解,同时提供灵活性以适应新的实体类型和关系,而无需受限于固定的模式。
通过将知识图谱与嵌入(向量搜索)结合起来,我们可以利用多跳连接和信息的上下文理解来增强大语言模型的推理和可解释性。
此笔记本探讨了这种方法的实践实现,演示了如何
- 使用合成数据集在Neo4j中构建与研究出版物相关的知识图谱,
- 使用嵌入模型将我们数据字段的子集投影到高维向量空间中,
- 在这些嵌入上构建向量索引以启用相似性搜索,以及
- 通过将用户查询轻松转换为Cypher语句,并使用LangChain从我们的图中提取自然语言洞察
初始化
%pip install neo4j langchain langchain_openai langchain-community python-dotenv --quiet
设置 Neo4j 实例
我们将使用Neo4j创建我们的知识图谱,Neo4j 是一个开源数据库管理系统,专门从事图数据库技术。
为了快速轻松地设置,您可以在Neo4j Aura上启动一个免费实例。
然后,您可以使用 .env
文件将 NEO4J_URI
、NEO4J_USERNAME
和 NEO4J_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?"})
[1m> Entering new GraphCypherQAChain chain...[0m Generated Cypher: [32;1m[1;3mcypher MATCH (r:Researcher {name: "Emily Chen"})-[:PUBLISHED]->(a:Article) RETURN COUNT(a) AS numberOfArticles [0m Full Context: [32;1m[1;3m[{'numberOfArticles': 7}][0m [1m> Finished chain.[0m
“是否有任何一对研究人员共同发表了三篇以上文章?”
在这个示例中,查询“是否有任何一对研究人员共同发表了三篇以上文章?”导致 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?"}
... )
[1m> Entering new GraphCypherQAChain chain...[0m Generated Cypher: [32;1m[1;3mcypher 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 [0m Full Context: [32;1m[1;3m[{'r1.name': 'David Johnson', 'r2.name': 'Emily Chen', 'sharedArticles': 4}, {'r1.name': 'Robert Taylor', 'r2.name': 'Emily Chen', 'sharedArticles': 4}, {'r1.name': 'Emily Chen', 'r2.name': 'David Johnson', 'sharedArticles': 4}, {'r1.name': 'Emily Chen', 'r2.name': 'Robert Taylor', 'sharedArticles': 4}][0m [1m> Finished chain.[0m
“哪位研究人员与最多同行合作?”
让我们找出与最多同行合作的研究人员。我们的查询“哪位研究人员与最多同行合作?”现在导致 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?"})
[1m> Entering new GraphCypherQAChain chain...[0m Generated Cypher: [32;1m[1;3mcypher 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 [0m Full Context: [32;1m[1;3m[{'researcher': 'David Johnson', 'collaborators': 6}][0m [1m> Finished chain.[0m
< > 在 GitHub 上更新