在这篇文章中,我将尝试分享我在那个医疗行业项目中学习到的关于知识图谱的知识,并解释如何使用知识图谱在结构化数据集上进行问答和检索增强生成(RAG)。我将介绍三种关键方法,说明如何与图数据库进行自然语言交互,为此,我将以Neo4j数据库为例进行展示。
CypherQAChain:一种直接将自然语言问题转换为Cypher查询的简单方法。我将演示这种方法如何简化图查询,并提供快速、准确的答案。
高级查询:对于更复杂的问题,我们将探索一种结合实体提取和数据库值映射的技术。这种方法非常适合处理需要更深入分析的细微查询。
基于RAG的方法:为了提高相关性和检索效果,我们将向量索引与知识图谱集成。通过这种方式,我们确保能够处理作为节点属性存储的非结构化数据(如文本描述)的问答任务。
为了解释这些方法,我将使用一个包含电影、演员、导演、类型、IMDB评分和上映日期信息的电影数据集。通过这个数据集,你将看到如何使用知识图谱和大语言模型(LLM)高效地与结构化数据集进行交互,以及如何在作为节点属性存储的非结构化数据上进行RAG。
知识图谱和图数据库的基本概述
在开始使用知识图谱构建聊天系统之前,理解图数据库(GraphDB)的基础概念以及它们在处理复杂、相互关联的数据场景中的优势是至关重要的。
什么是图数据库(GraphDB)?
图数据库(GraphDB)是一种设计用于以实体及其关系的网络形式存储和管理数据的数据库类型。与传统的关系型数据库使用行和列不同,图数据库使用以下主要组件来存储数据:
这种结构使得图数据库(GraphDB)非常适合处理相互关联的数据,尤其是在关系对于理解整体情况至关重要的场景中,比如社交媒体平台。在这种情况下使用图数据库具有以下优点:
关系的自然表示
在传统的关系型数据库中,我们将用户存储在一个表中,并使用外键在另一个表中存储他们的关系(如“关注”或“与…为好友”)。例如:
虽然这种方法可行,但随着网络规模的扩大,查询这些关系会变得复杂且缓慢。
在图数据库中,这些关系被直接建模为节点(用户)之间的边。例如:
这种方法使我们能够直接遍历关系,比如查找“共同好友”或“建议的联系人”,而无需复杂的表连接。
节点和关系的语义丰富性和属性
图数据库允许边携带关于关系的详细信息,从而将它们转变为知识图谱。例如,我们可以定义不仅仅是简单的“关注”边:
这种语义丰富性为数据增添了有意义的上下文。例如:
此外,图数据库中的每个节点和边不仅具有节点和它们之间的关系,而且每个节点和边还携带属性,如下图所示,从而使它们成为知识图谱。
动态更新
图数据库是为动态更新而构建的,这意味着当新数据到来时,如果它符合现有节点和边的关系,则会被放置在相应位置;否则,会形成新的节点和关系,从而动态更新数据库。
以不断变化的社交媒体网站为例,新的友谊形成,帖子被分享,评论被添加。
例如,当用户A关注用户B时,图数据库会通过在它们各自的节点之间创建一条新边来即时更新。这种动态特性有助于使网络保持最新信息。
遍历效率
图数据库针对遍历算法进行了优化,例如:
例如,在社交媒体平台上,遍历对于以下任务至关重要:
可解释性和可追溯性
对此,我想说图数据库使得追踪用户或实体如何连接变得容易。例如:
这种透明度对于调试、用户洞察以及建立对推荐算法的信任非常重要。
LLM 和 GraphDB 如何在问答应用程序中交互?
为了使用知识图谱构建智能高效的聊天机器人系统,我们首先需要理解LLM和GraphDB实际上是如何协同工作的。
如上文所述,图数据库(如Neo4j)在存储和导航相互关联的数据方面非常高效。它们使用节点(实体)、边(实体之间的关系)和属性(节点和边的属性)的结构来表示和管理复杂的关系。
而LLM则是将类似人类的、非结构化的输入转化为有意义、结构化的查询背后的“大脑”。例如,如果用户问聊天机器人“谁出演了电影《盗梦空间》?”,正是LLM将这种自然语言查询转换为精确的查询语言,如Cypher(Neo4j所用):
MATCH (m:Movie {title: "Inception"})-[:ACTED_IN]-(p:Person) (m:Movie {title: "Inception"})-[:ACTED_IN]-(p:Person)
RETURN p.name;
然后,大型语言模型(LLM)解释图数据库(GraphDB)检索到的结果,并向用户提供对话式的回应,例如:
“The cast of Inception includes Leonardo DiCaprio, Ellen Page, and Joseph Gordon-Levitt.”
随着编码和推理能力的最新进展,现代LLM已成为以下方面的卓越工具:
但它们的作用远不止于此。除了查询数据外,LLM在处理非结构化数据以创建知识图谱方面也发挥着重要作用。
LLM用于将非结构化数据转换为图谱
在GraphDB生态系统中,LLM最令人兴奋的应用之一是其能够将PDF、文档等形式的非结构化信息转换为结构化的图谱数据。这涉及识别文本中的实体(节点)、关系(边)以及与之相关的属性,然后将它们表示为图谱。
示例:从文本中提取图谱数据
考虑一份文本文档:
“莱昂纳多·迪卡普里奥出演了由克里斯托弗·诺兰执导的《盗梦空间》。”
使用像LLMGraphTransformer这样的工具,可以将文本转换为图谱,如下所示:
节点:
- Leonardo DiCaprio人物)
- Inception(电影)
- Christopher Nolan(人物)
关系:
- Leonardo DiCaprio→ ACTED_IN→ → →Inception
- Christopher NolanDIRECTEDInception
请注意,图谱构建过程是非确定性的,因为LLMGraphTransformer或类似工具以LLM为基础。因此,每次执行获得的结果可能略有不同,并且获得的图谱质量高度依赖于所使用的LLM类型。
LLMGraphTransformer使用LLM来:
图谱形成的有效性取决于所选的LLM模型,因为它影响提取的图谱数据的准确性和粒度。在未来关于非结构化数据的图谱问答/检索增强生成(RAG)的博客中,我将详细讨论这个话题。
方法1:使用CypherChainQA实际实现问答
现在我们已经基本了解了GraphDB与LLM如何交互,接下来让我们使用Langchain框架中的CypherChainQA开始我们的第一个基于GraphDB的问答聊天机器人阶段。
Neo4j设置:
在我们的实现中,我们将使用Neo4j,这是一个强大的图数据库管理系统,以其处理和查询图数据的效率而闻名。Neo4j提供了几个特性,使其成为聊天机器人开发的优秀选择:
此外,Neo4j与LangChain框架的良好集成使其非常适合构建基于图谱的聊天机器人系统。Neo4j与LangChain的集成使我们能够:
让我们设置Neo4j AuraDB(免费实例)
前往Neo4j Aura并登录或注册(你可以免费创建一个实例用于学习目的)。
创建一个新的数据库实例(设置可能需要一些时间)。
实例准备好后,当系统提示时,记下并下载连接凭据.txt文件:
使用上述记下的凭据,通过以下代码连接到Neo4j数据库:
from kaggle_secrets import UserSecretsClient
from langchain_community.graphs import Neo4jGraph
user_secrets = UserSecretsClient()
groq_api_key = user_secrets.get_secret("groq_api_key")
hf_api_key = user_secrets.get_secret("hf_api_key")
NEO4J_PASSWORD = user_secrets.get_secret("NEO4J_PASSWORD")
NEO4J_URI = user_secrets.get_secret("NEO4J_URI")
NEO4J_USERNAME = user_secrets.get_secret("NEO4J_USERNAME")
graph = Neo4jGraph(
url=NEO4J_URI,
username=NEO4J_USERNAME,
password=NEO4J_PASSWORD)
print("Connected to Neo4j!")
数据预处理和创建图谱
现在我们已经与数据库建立了连接,接下来我们可以导入数据集,对其进行一些预处理,并更新Neo4j数据库。
步骤1️⃣:我们首先加载一个示例电影数据集。该数据集包含有关电影的关键信息,如标题、导演、演员、类型和评分。让我们读取数据并快速浏览一下:
df = pd.read_csv("https://raw.githubusercontent.com/tomasonjo/blog-datasets/main/movies/movies_small.csv", nrows=20)"https://raw.githubusercontent.com/tomasonjo/blog-datasets/main/movies/movies_small.csv", nrows=20)
display(df.head(3))
print(df.shape)
print("="*40)
print(df.columns)
print("="*40)
print(df["title"][:20].values)
步骤2️⃣:为了使数据集对于检索增强生成(RAG)任务的查询和检索更有意义,我为每部电影创建了一个详细描述列。这些描述列结合了多个字段,如标题、导演、演员、类型、IMDb评分和上映日期。
此外,我还创建了一些额外的虚拟列。
details = df['title'][0]'title'][0]
details
df['movie_detail'] = df.apply(lambda row: f"Movie {row['title']} is directed by {row['director']}\
having actors named {row['actors'].replace('|',' , ')} and it is movie of the \
genre in {row['genres'].replace('|',' , ')}. It has a rating of {row['imdbRating']} \
and it is released in {row['released']}",axis=1)
df.head()
# Add custom data for locations and similar movies
location = ["United States", "United States", "United States", "United States", "United States",
"United States", "United States", "United States", "United States", "United Kingdom",
"United States", "United States", "United States", "United States", "Malta",
"United States", "United Kingdom", "United States", "United States", "United States"]
similar_movie = ["Finding Nemo", "Jumanji: Welcome to the Jungle", "The Bucket List", "The Best Man Holiday",
"Cheaper by the Dozen", "The Departed", "Notting Hill", "The Adventures of Huck Finn",
"Die Hard", "Mission Impossible", "Dave", "Dead and Loving It: Young Frankenstein",
"Spirit: Stallion of the Cimarron", "JFK",
"Pirates of the Caribbean: The Curse of the Black Pearl", "Goodfellas",
"Pride and Prejudice", "Pulp Fiction", "The Mask", "Speed"]
df['location'] = location
df['similar_movie'] = similar_movie
# Save the enriched dataset for graph construction
df.to_csv("movie.csv", sep=",", index=False)
步骤3️⃣:构建Neo4j图谱
现在,我们将使用处理过的数据集更新Neo4j数据库。图谱将包括电影、导演、演员、类型、地点和相似电影的节点。同时,也会相应地创建这些实体之间的关系。
# creating below input value just to make sure not to execute this code again for creating graph accidentaly
value = input("Do You realy want to execute this cell and create the GraphDB again ? y/n")
movie_csv_path = 'movie.csv'
if value == 'y':
graph.query("""
LOAD CSV WITH HEADERS FROM
'https://raw.githubusercontent.com/manindersingh120996/RAG-Related-Projects/refs/heads/main/movie%20(1).csv'
AS row
MERGE (m:Movie {id:row.movieId})
SET m.released = row.released,
m.title = row.title,
m.movie_detail = row.movie_detail,
m.imdbRating = toFloat(row.imdbRating)
FOREACH (director in split(row.director, '|') |
MERGE (p:Person {name:trim(director)})
MERGE (p)-[:DIRECTED]->(m))
FOREACH (actor in split(row.actors, '|') |
MERGE (p:Person {name:trim(actor)})
MERGE (p)-[:ACTED_IN] ->(m))
FOREACH (genre in split(row.genres, '|') |
MERGE (g:Genre {name:trim(genre)})
MERGE (m)-[:IN_GENRE]->(g))
MERGE (l:location {name:trim(row.location)})
MERGE (m)-[:WAS_TAKEN_IN]->(l)
MERGE (s:SimilarMovies {name:trim(row.similar_movie)})
MERGE (m)-[:IS_SIMILAR_TO]->(s)
""")
为了创建图谱,我们使用Cypher查询语言,并为此遍历数据集的每一行,创建:
我们可以看到创建的图形数据库的架构如下:
graph.refresh_schema().refresh_schema()
print(graph.schema)
GraphCypherQAChain
现在我们已经设置好了Neo4j数据库,并用所需的节点和关系对其进行了结构化,接下来我们可以利用GraphCypherQAChain。这个链提供了一种简单高效的机制来与数据库进行交互,它接收一个问题,将其转换为Cypher查询,执行查询,并使用结果来回答原始问题。
为了确保Cypher查询既准确又高效,至关重要的是使用一个足够成熟的语言模型(LLM)来理解和生成Cypher语法。选择功能不足的语言模型可能会导致:
为了获得最佳结果,建议使用为此类目的训练过的高级语言模型(LLM)。
例如,在我使用LLaMA 3.1(8B)进行测试时,未能达到预期的结果,但当我切换到LLaMA 3.3(70B)后,我的系统能够回答更复杂的查询。
GraphCypherQAChain的实现如下:
from langchain.chains import GraphCypherQAChain
# this we can call as the simple agent because here
# we are running only two lines
# but it is not that powerfull as we will see for question 2 and question 3
# where question becomes more complicated
"""
One way to improve this process is by using a more powerful language model
capable of generating accurate Cypher queries. For instance,
I initially used LLaMA 3.1 (8B), which struggled to answer two specific questions.
However, upgrading to LLaMA 3.3 (70B) successfully resolved the 1st and 3rd questions.
"""
cypher_execution_chain = GraphCypherQAChain.from_llm(graph=graph, # here it retrives the graph Schema
llm=llm_model,
verbose=True,
allow_dangerous_requests=True)
response = cypher_execution_chain.invoke({"query": q_one})
print(response)
print("\nLLM response:", response["result"])
尽管对于简单问题它能表现良好,但一旦问题变得复杂,它就无法回答了,如下所示:
q_two = "What are the most common genres for movies released in 1995?"= "What are the most common genres for movies released in 1995?"
response = cypher_execution_chain.invoke({"query": q_two})
print(response)
print("\nLLM response:", response["result"])
GraphCypherQAChain在处理更复杂的问题时显得力不从心,比如那些包含多个条件或间接关系的问题。这表明我们需要一个更强大、更智能的解决方案来处理这种情况。
方法2:高级查询:
如我们上面所观察到的,当用户问题变得更复杂时,比如涉及多个条件或间接关系的问题,CypherQAChain就难以提供答案。这是因为,虽然CypherQAChain理解数据库架构,但它并不知道数据库中的实际数据。例如,它可能识别出“标题”这样的属性,但不知道存在哪些电影标题。这可能导致查询无法返回有用的结果。为了解决这个问题,我添加了一个映射层,将用户输入与数据库中的实际值进行匹配,并使用这些值来创建更精确的查询,从而使系统更加可靠。
它的工作流程包括以下步骤:
第一步:检测用户输入中的实体:对于所有用户输入的查询,它会提取出实体,如可能的人
from typing import List
# from langchain.chains.openai_functions import create_structured_output_chain
from langchain.chains import create_structured_output_runnable
from langchain_core.prompts import ChatPromptTemplate
# from langchain_core.pydantic_v1 import BaseModel, Field
from pydantic import BaseModel, Field, field_validator
class Entities(BaseModel):
"""Identifying information about entities and
extract person, movies, and years entitites from the text."""
names: List[str] = Field(
...,
description="All the person , year or movies appearing in the text",
)
entity_extractor_model = llm_model.with_structured_output(Entities)
第二步:将实体映射到数据库值
一旦识别出实体,我们将它们与数据库中的实际值进行匹配。为此,我使用了LangChain文档中的一个通用查询,来检索图数据库中关于节点(如电影或人物)及其关系(例如,ACTED_IN,IN_GENRE)的详细信息。这个查询提供了节点及其上下文的清晰、易读的摘要。
该过程涉及三个关键步骤:
match_query = """"""
MATCH (m:Movie|Person)
WHERE m.title CONTAINS $value OR m.name CONTAINS $value OR m.released CONTAINS $value
MATCH (m)-[r:ACTED_IN|IN_GENRE]-(t)
WITH m, type(r) as type, collect(coalesce(t.name, t.title)) as names
WITH m, type+": "+reduce(s="", n IN names | s + n + ", ") as types
WITH m, collect(types) as contexts
WITH m, "type:" + labels(m)[0] + "\ntitle: "+ coalesce(m.title, m.name)
+ "\nyear: "+coalesce(m.released,"") +"\n" +
reduce(s="", c in contexts | s + substring(c, 0, size(c)-2) +"\n") as result
RETURN result
"""
def map_to_database(values)->str:
"""
Maps the values to entities in the database and returns the mapping information.
Args:
values (list): A list of values to map to entities in the database.
Returns:
str: A string containing the mapping information of each value to entities in the
"""
result = ""
for entity in values.names:
response = graph.query(match_query, {"value": entity})
# print(response)
try:
for values in response:
result += f"{entity} maps to {values['result']} in database\n\n" # Query the database to find the mapping for the entity
except IndexError:
pass
return result
为了提高映射的效率,我们使用了一个利用CONTAINS子句的Cypher查询来查找匹配项。为了更灵活(处理拼写错误),还可以应用模糊搜索或全文索引等技术。
第三步:生成上下文感知的Cypher查询
利用提取的信息和数据库架构,我们创建一个准确且针对用户问题定制的Cypher查询。这个自定义的Cypher查询确保它考虑了数据库中的所有相关属性、关系和数据值。
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
# Generate Cypher statement based on natural language input
cypher_template = """Based on the Neo4j graph schema below, write a Cypher query that would answer the user's question:
{schema}
Entities in the question map to the following database values:
{entities_list}
Question: {question}
Note: Do not include any explanations or apologies in your responses.
Do not wrap the response in any backticks or anything else.
Respond with a Cypher statement only!
Cypher query:"""
cypher_prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"Given an input question, convert it to a Cypher query. No pre-amble.",
),
("human", cypher_template),
]
)
# cypher_prompt.invoke({'schema'})
chain = cypher_prompt | llm_model.bind(stop=["\nCypherResult:"]) | StrOutputParser()
第四步:基于数据库结果生成答案
现在我们已经有了一条能够生成目标Cypher查询的链条,接下来需要执行这个Cypher查询,并将数据库结果返回给语言模型(LLM),以生成最终答案。
from langchain.chains.graph_qa.cypher_utils import CypherQueryCorrector, Schema
# Cypher validation tool for relationship directions
corrector_schema = [
Schema(el["start"], el["type"], el["end"])
for el in graph.structured_schema.get("relationships")
]
cypher_validation = CypherQueryCorrector(corrector_schema)
# Generate natural language response based on database results
response_template = """Based on the the question, Cypher query, and Cypher response, write a natural language response:
Question: {question}
Cypher query: {query}
Cypher Response: {response}"""
response_prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"Given an input question and Cypher response, convert it to a natural"
" language answer.Give the answer in Structured format such a Visually appealing to Read to user."
"No pre-amble.",
),
("human", response_template),
]
)
chain_2 = response_prompt | llm_model | StrOutputParser()
将上述所有代码合并到一个单一的流程中,将为我们获取问题2的答案,而这是上面方法1未能做到的,结果如下:
def questions_asked(question_asked:str):
question = question_asked
entities = entity_extractor_model.invoke(question)
entities_list = map_to_database(entities)
schema = graph.get_schema
chain = cypher_prompt | llm_model.bind(stop=["\nCypherResult:"]) | StrOutputParser()
cypher_query = chain.invoke({'schema': schema,
'entities_list': entities_list,
'question': question})
print(f"Cypher Query Generated : {cypher_query}")
print("="*40)
graph.query(cypher_query)
final_answer = chain_2.invoke({'response': graph.query(cypher_query),
'query': cypher_query,
'question': question})
print(f'Final Answer for Question : "{question}" --> \n {final_answer}')
questions_asked(q_two)
我们发现,这种方法成功回答了CypherQAChain无法回答的问题。关键的改进在于提取和映射相关实体到数据库,为语言模型(LLM)提供了更好的数据上下文。这种额外的上下文使LLM能够生成更精确的Cypher查询,仅检索最相关的信息。
在我的实验中,这种方法在所有测试案例中都表现完美,尽管有可能我遗漏了一些边界情况。
到目前为止,我们一直专注于Cypher查询生成、数据检索和答案生成。接下来,我们将深入探讨如何使用Neo4j图数据库构建基本的检索增强生成(RAG)管道。
方法3:使用图数据库(Neo4j)的RAG
到目前为止,我们一直专注于结构化文本数据,但如果非结构化文本也是数据集的一部分呢?这正是像Neo4j这样的知识图谱真正擅长的地方。它们可以在一个地方存储结构化和非结构化数据,以及RAG任务所需的非结构化数据的嵌入。这简化了为语言模型提供检索增强生成(RAG)所需的上下文。
GraphRAG是一种使用知识图谱来增强上下文检索的RAG方法。在本节中,我将演示如何使用LangChain和Neo4j实现GraphRAG。我们将使用预处理期间创建的附加列movie_details(通过合并所有相关列)来构建RAG的向量索引。
当在GraphRAG中仅使用单个属性来检索所有相关信息时,建议使用我们有信心包含足够所需信息的属性,以满足预期问题。
第一步:对于GraphRAG,第一步是创建所选字段的向量嵌入,如下所示:
我们将为电影详情列创建向量嵌入,并将这些嵌入存储在一个新列中,称为movie_detail_embeddings。然后,这些嵌入将用于填充图数据库中电影详情的向量索引,从而在RAG任务期间实现高效检索。
import torch
device = 'cuda' if torch.cuda.is_available() else 'cpu'
embeddings = HuggingFaceEmbeddings(model_name="jinaai/jina-embeddings-v3",
encode_kwargs = {'normalize_embeddings': True},
model_kwargs = {'device': device,'trust_remote_code':'True',})
def embed_text(text:str)->List:
"""
Embeds the given text using the specified model.
Parameters:
text (str): The text to be embedded.
Returns:
List: A list containing the embedding of the text.
"""
response = embeddings.embed_query(text)
return response
embedding_list = [embed_text(i) for i in df["movie_detail"]]
embedding_list
print("Number of vectors:", len(embedding_list))
print("Embedding dimension:", len(embedding_list[0]))
embedding_list[0][:5]
df["movie_detail_embeddings"] = embedding_list
df.head(3)
第二步:在Neo4j数据库中创建空的向量索引,稍后我们将用第一步中创建的嵌入来填充它。
我们将设置一个与嵌入模型相同维度的空向量索引,并指定用于检索的相似度函数,如下所示。
# here it will create the vector index in the graphDB
# for now it will be empty
# creating below input value just to make sure not to execute this code again for creating Vector index again
value = input("Do You realy want to execute this cell and create the Vector Index again ? y/n")
if value == 'y':
graph.query("""
CREATE VECTOR INDEX movie_detail_embeddings IF NOT EXISTS // Create a vector index named 'movie_tagline_embeddings' if it doesn't already exist
FOR (m:Movie) ON (m.movie_detail_embeddings) // Index the 'taglineEmbedding' property of Movie nodes
OPTIONS { indexConfig: { // Set options for the index
`vector.dimensions`: 1024, // Specify the dimensionality of the vector space (1024 dimensions)
`vector.similarity_function`: 'cosine' // Specify the similarity function to be cosine similarity
}}"""
)
graph.query("""
SHOW VECTOR INDEXES // Retrieves information about all the vector indexes in the Database
""")
第三步:用第一步中创建的向量嵌入填充向量索引
# Populating the Vector Indexes in the Database with Vector Embedding Values
# Creating the input value below as a safeguard to prevent accidental re-execution of this code,
# which could recreate the graph unintentionally.
value = input("Do You realy want to execute this cell and populate the Vector index again ? y/n")
if value == 'y':
for index, row in df.iterrows():
movie_id = row['movieId']
embedding = row['movie_detail_embeddings']
graph.query(f"MATCH (m:Movie {{id: '{movie_id}'}}) SET m.movie_detail_embeddings = {embedding}")
现在,我们已经创建并更新了带有所需向量索引和嵌入的图数据库,接下来我们可以使用以下代码对输入查询执行向量搜索:
result = graph.query(""""""
with $question_embedding as question_embedding
CALL db.index.vector.queryNodes(
'movie_detail_embeddings',
$top_k,
question_embedding
) YIELD node AS movie, score
RETURN movie.title, movie.movie_detail, score
""",
params={
"question_embedding":question_embeddings,
"top_k":3
})
import pprint
pprint.pprint(result)
prompt = f"# Question:\n{question}\n\n# Graph DB search results:\n{result}"f"# Question:\n{question}\n\n# Graph DB search results:\n{result}"
messages = [
{"role": "system", "content": str(
"You will be given the user question along with the search result of that question over a Neo4j graph database. Give the user the proper answer."
)},
{"role": "user", "content": prompt}
]
response = llm_model.invoke(messages)
print(response.content)
23
上述完整代码可以编译成以下单一管道:
def rag_pipeline(question:str):
question_embeddings = embed_text(question)
result = graph.query("""
with $question_embedding as question_embedding
CALL db.index.vector.queryNodes(
'movie_detail_embeddings',
$top_k,
question_embedding
) YIELD node AS movie, score
RETURN movie.title, movie.tagline, score
""",
params={
"question_embedding":question_embeddings,
"top_k":5
})
prompt = f"# Question:\n{question}\n\n# Graph DB search results:\n{result}"
messages = [
{"role": "system", "content": str(
"You will be given the user question along with the search result of that question over a Neo4j graph database. Give the user the proper answer."
)},
{"role": "user", "content": prompt}
]
response = llm_model.invoke(messages)
return response
answer = rag_pipeline("What all are the movies about the Adventures ?")
现在我们已经探索了图数据库上的检索增强生成(RAG),重要的是要注意,虽然RAG和查询生成各自都很强大,但它们在检索相关内容时都有局限性。为了克服这些挑战并构建一个更可靠的系统,可以采用混合方法:
这种混合方法既利用了结构化数据,也利用了非结构化数据,创建了一个强大的系统,能够有效处理甚至复杂的查询。
结论
在这篇文章中,我分享了关于知识图谱的知识,以及它们如何与大型语言模型(LLM)协同工作,以及如何使用它们为结构化数据构建聊天机器人。我介绍了简单的方法,如CypherQAChain,以及更先进的方法,如处理非结构化数据的GraphRAG。