###本文翻译自Tomaz Bratanic的英文博客,原文链接:Enhancing the Accuracy of RAG Applications With Knowledge Graphs | by Tomaz Bratanic | Neo4j Developer Blog | Medium
###译者水平有限,如有出入请以原文为准。
正文:
GraphRAG正在蓬勃发展,并成为传统向量搜索检索方法的有力补充。这种方法利用图形数据库的结构化本质(将数据组织为节点和关系)来增强检索信息的深度和上下文性。
图形擅长以结构化方式表示和存储异类和互连的信息,可以轻松捕获不同数据类型之间的复杂关系和属性。相比之下,向量数据库经常难以处理此类结构化信息,因为它们的优势在于通过多维向量处理非结构化数据。在您的RAG应用程序中,您可以将结构化图形数据与通过非结构化文本的向量搜索相结合,以实现两全其美。这就是我们将在这篇博客文章中演示的内容。
知识图谱很棒,但如何创建一个?
构建知识图谱通常是最具挑战性的步骤。它涉及收集和结构化数据,这需要深入了解领域和图形建模。
我们一直在尝试用大语言模型(LLM)来简化这一步骤。凭借对语言和上下文的深刻理解,LLM可以自动化知识图谱创建过程的重要部分。通过分析文本数据,这些模型可以识别实体、了解它们的关系,并建议如何在图形结构中最好地表示它们。
作为这些实验的结果,我们将图形构建模块的第一个版本添加到LangChain,我们将在这篇博客文章中演示。
该代码可在Github上找到。
Neo4j 环境配置
您需要设置Neo 4j实例。请遵循这篇博客文章中的示例。最简单的方法是在Neo4j Aura上启动一个免费实例,该实例提供Neo4j数据库的云实例。或者,您还可以通过下载Neo4j Desktop并创建本地数据库实例来设置Neo4j数据库的本地实例。
os.environ["OPENAI_API_KEY"] = "sk-"
os.environ["NEO4J_URI"] = "bolt://localhost:7687"
os.environ["NEO4J_USERNAME"] = "neo4j"
os.environ["NEO4J_PASSWORD"] = "password"
graph = Neo4jGraph()
此外,您必须提供OpenAI Key,因为我们将在这篇博客文章中使用他们的模型。
数据提取
在此演示中,我们将使用伊丽莎白一世的维基百科页面。我们可以使用LangChain loaders无缝地从维基百科获取和拆分文档。
# Read the wikipedia article
raw_documents = WikipediaLoader(query="Elizabeth I").load()
# Define chunking strategy
text_splitter = TokenTextSplitter(chunk_size=512, chunk_overlap=24)
documents = text_splitter.split_documents(raw_documents[:3])
【代码解析(译者注)】
raw_documents = WikipediaLoader(query="Elizabeth I").load()
#这行代码使用 WikipediaLoader 类从维基百科加载关于 “Elizabeth I”(伊丽莎白一世)的文章内容.
#query 参数指定了要查询的主题,load() 方法执行查询并返回包含文章内容的文档对象列表。
#这里的 raw_documents 就是一个包含从维基百科获取到的文档的列表。
text_splitter = TokenTextSplitter(chunk_size=512, chunk_overlap=24)
#这行代码创建了一个 TokenTextSplitter 实例,用于定义文本分块的策略。
#chunk_size 参数设置了每个分块的最大 token 数量为 512,chunk_overlap 参数设置了相邻分块之间的重叠 token 数量为 24。
#这样做的目的是在分块时保留一定的上下文连贯性,避免在分块边界处丢失重要信息。
documents = text_splitter.split_documents(raw_documents[:3])
#这行代码使用前面定义的 text_splitter 对 raw_documents 中的前 3 篇文档进行分块处理。
#split_documents() 方法会根据设定的分块策略将文档分割成多个较小的文档块,最终得到的 documents 是一个包含分块后文档的列表。
是时候根据检索到的文档构建图表了。为此,我们实现了LLMGraphTransformer模块,该模块显著简化了在图数据库中构建和存储知识图。
llm=ChatOpenAI(temperature=0, model_name="gpt-4-0125-preview")
llm_transformer = LLMGraphTransformer(llm=llm)
# Extract graph data
graph_documents = llm_transformer.convert_to_graph_documents(documents)
# Store to neo4j
graph.add_graph_documents(
graph_documents,
baseEntityLabel=True,
include_source=True
)
【代码解析(译者注)】
llm = ChatOpenAI(temperature=0, model_name="gpt-4-0125-preview")
#初始化大语言模型
#ChatOpenAI:这是 langchain 库中的一个类,用于与 OpenAI 的聊天模型进行交互。
#temperature:温度参数,取值范围通常是 0 到 1。这里设置为 0,表示生成的文本具有最高的确定性,模型会倾向于选择概率最高的输出结果,减少随机性。
#model_name:指定要使用的 OpenAI 模型名称,"gpt - 4 - 0125 - preview" 是 OpenAI 的 GPT - 4 模型的一个特定预览版本。
llm_transformer = LLMGraphTransformer(llm=llm)
#创建图转换器,创建一个LLMGraphTransformer实例。
#LLMGraphTransformer:利用大语言模型将文本数据转换为图结构的数据。
graph_documents = llm_transformer.convert_to_graph_documents(documents)
#提取图数据
#convert_to_graph_documents:这是 LLMGraphTransformer 类的一个方法,用于将输入的 documents(前面分块处理好的文档列表)转换为图文档对象。
#graph_documents:存储转换后的图文档对象,这些对象包含了以图结构表示的信息,例如实体节点和它们之间的关系。
graph.add_graph_documents(
graph_documents,
baseEntityLabel=True,
include_source=True
)
#存储到 Neo4j 数据库
#graph:这是一个与 Neo4j 图数据库进行交互的对象,通常是通过 langchain 库中相关的类(如 Neo4jGraph)初始化得到的。
#add_graph_documents:这是 graph 对象的一个方法,用于将图文档对象添加到 Neo4j 数据库中。
#graph_documents:要添加到数据库的图文档对象列表。
#baseEntityLabel=True:一个布尔参数,指示是否为图中的实体添加基础标签_Entity_。
#include_source=True:另一个布尔参数,指示是否在存储图数据时包含数据的来源信息。
您可以定义希望知识图谱生成链使用哪个LLM。目前,我们仅支持OpenAI和Mistral的函数调用模型。然而,我们计划未来扩大LLM的选择。在本例中,我们使用最新的GPT-4。请注意,生成的图图谱的质量在很大程度上取决于您正在使用的模型。理论上,您总是希望使用最有能力的模型。LLM graph transformer 返回图形文档,可以通过add_graph_documents方法将其导入到Neo4j。baseEntityLabel参数为每个节点分配额外的__Entity__标签,从而增强索引和查询性能。select_source参数将节点链接到其原始文档,从而促进数据可追溯性和上下文理解。
您可以在Neo4j Browser中检查生成的图谱。
RAG的混合检索
生成图谱后,我们将使用混合检索方法,将向量和关键字索引与RAG应用程序的图谱检索相结合。
该图说明了从用户提出问题开始的检索过程,然后将其引导到RAG检索器。该检索器使用关键字和向量搜索来搜索非结构化文本数据,并将其与从知识图谱收集的信息相结合。由于Neo4j同时具有关键字和向量索引,因此您可以通过单个数据库系统实现所有三个检索选项。从这些来源收集的数据被输入LLM以生成并提供最终答案。
非结构化数据检索
您可以使用Neo4jVector.from_existing_graph方法向文档添加关键字和载体检索。此方法为混合搜索方法配置关键字和向量搜索索引,目标是标记为Document的节点。此外,如果文本嵌入值缺失,它还会计算文本嵌入值。
vector_index = Neo4jVector.from_existing_graph(
OpenAIEmbeddings(),
search_type="hybrid",
node_label="Document",
text_node_properties=["text"],
embedding_node_property="embedding"
)
【代码解析(译者注)】
#这段代码的主要功能是从现有的 Neo4j 图数据库中创建一个向量索引(vector_index),该索引会结合 OpenAI 的嵌入模型,支持混合搜索,用于在图数据库中存储和检索与文本相关的向量信息。
vector_index = Neo4jVector.from_existing_graph(
OpenAIEmbeddings(),
search_type="hybrid",
node_label="Document",
text_node_properties=["text"],
embedding_node_property="embedding"
)
#Neo4jVector 是一个类,它提供了与 Neo4j 图数据库交互并进行向量相关操作的功能。
#from_existing_graph 是 Neo4jVector 类的一个类方法,其作用是从现有的 Neo4j 图数据库中创建一个向量索引。
#OpenAIEmbeddings 是一个类,同样可能来自 langchain 库,用于使用 OpenAI 的嵌入模型将文本转换为向量表示。
#当你调用 OpenAIEmbeddings() 时,会创建一个该类的实例,后续在创建向量索引时,会使用这个实例将文本数据转换为向量,以便在图数据库中进行存储和检索。
#search_type 是一个参数,用于指定在向量索引中进行搜索的类型。
#"hybrid" 表示使用混合搜索方式。混合搜索通常结合了基于向量相似度的搜索和传统的基于属性的搜索,能更灵活、准确地在图数据库中查找所需的数据。
#node_label 用于指定在 Neo4j 图数据库中要操作的节点标签。
#这里指定为 "Document",意味着该向量索引会针对图数据库中具有 Document 标签的节点进行操作。
#text_node_properties 是一个列表参数,用于指定具有 Document 标签的节点中,哪些属性包含需要进行嵌入处理的文本数据。
#这里列表中只有 "text",表示会提取 Document 节点的 text 属性中的文本,使用 OpenAIEmbeddings 实例将其转换为向量。
#embedding_node_property 用于指定在图数据库的节点中,哪个属性将用于存储转换后的向量嵌入结果。
#这里指定为 "embedding",意味着经过 OpenAIEmbeddings 转换得到的向量会存储在 Document 节点的 embedding 属性中。
然后可以使用similarity_search方法调用该载体索引。
图检索
另一方面,配置图形检索更加复杂,但提供了更多的自由度。此示例将使用全文索引来识别相关节点并返回其直接邻居节点。
Graph retriever. Image by author.
图形检索器首先识别input中的相关实体。为了简单起见,我们指示LLM识别people、organizations和locations。为了实现这一目标,我们将使用LCEL和新添加的with_structural_put方法。
# Extract entities from text
class Entities(BaseModel):
"""Identifying information about entities."""
names: List[str] = Field(
...,
description="All the person, organization, or business entities that "
"appear in the text",
)
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"You are extracting organization and person entities from the text.",
),
(
"human",
"Use the given format to extract information from the following "
"input: {question}",
),
]
)
entity_chain = prompt | llm.with_structured_output(Entities)
【代码解析(译者注)】
class Entities(BaseModel):
"""Identifying information about entities."""
names: List[str] = Field(
...,
description="All the person, organization, or business entities that "
"appear in the text",
)
#定义实体类 Entities
#Entities 类继承自 BaseModel,BaseModel 通常来自 pydantic 库,用于创建数据模型,方便对数据进行验证和序列化。
#names:这是一个字符串列表类型的属性,用于存储在文本中出现的所有人物、组织或商业实体的名称。
#Field:pydantic 中的 Field 用于为属性添加额外的元数据,这里 ... 表示该属性是必需的,description 为该属性提供了详细的描述信息,说明 names 列表中存储的是文本中出现的人物、组织或商业实体的名称。
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"You are extracting organization and person entities from the text.",
),
(
"human",
"Use the given format to extract information from the following "
"input: {question}",
),
]
)
#创建聊天提示模板 prompt
#ChatPromptTemplate:这通常是 langchain 库中的一个类,用于创建聊天提示模板。
#from_messages 方法:通过传入一个消息列表来创建提示模板。消息列表中的每个元素是一个元组,元组的第一个元素表示消息的角色("system" 或 "human"),第二个元素是消息的内容。
#系统消息:"system" 角色的消息内容为 "You are extracting organization and person entities from the text.",它为大语言模型提供了任务的基本设定,告知模型要从文本中提取组织和人物实体。
#用户消息:"human" 角色的消息内容包含一个占位符 {question},这意味着在实际使用提示模板时,需要提供具体的文本内容来替换 {question},以便模型根据该文本进行实体提取。
entity_chain = prompt | llm.with_structured_output(Entities)
#创建实体提取链 entity_chain
#管道操作符 |:在 langchain 中,| 用于将不同的组件连接成一个链,使得数据可以按顺序在各个组件之间传递。
#prompt:作为链的起始部分,它负责生成包含具体文本的提示信息。
#llm 是之前初始化的大语言模型实例(如 ChatOpenAI)。
#with_structured_output(Entities) 方法用于指定大语言模型的输出应该符合 Entities 类定义的结构。
#也就是说,模型的输出会被解析并验证为 Entities 类的实例,确保输出的数据是包含 names 列表的结构化数据。
让我们来测试一下:
entity_chain.invoke({"question": "Where was Amelia Earhart born?"}).names
# ['Amelia Earhart']
【代码解析(译者注)】
entity_chain.invoke({"question": "Where was Amelia Earhart born?"}).names
#invoke 方法是 langchain 中用于触发链执行的方法。
#这里传入一个字典 {"question": "Where was Amelia Earhart born?"}
#其中 "question" 是之前在 ChatPromptTemplate 中定义的占位符
#而 "Where was Amelia Earhart born?" 是具体要处理的文本。
#entity_chain 会按照链的顺序进行处理
#首先,prompt 会根据传入的文本生成完整的提示信息,将 "Where was Amelia Earhart born?" 替换到 {question} 位置。
#然后,将生成的提示信息传递给大语言模型 llm。
#大语言模型根据系统消息(提取组织和人物实体)和用户提供的具体文本进行处理,并按照 Entities 类的结构输出结果。
#由于 entity_chain.invoke 的输出是一个 Entities 类的实例,通过 .names 可以访问该实例中的 names 属性,这个属性存储了从文本中提取的所有人物、组织或商业实体的名称。
#['Amelia Earhart']
#最终输出 ['Amelia Earhart'],这表明大语言模型从文本 "Where was Amelia Earhart born?" 中成功提取出了人物实体 "Amelia Earhart",并将其作为一个元素存储在 names 列表中返回。
太好了,现在我们可以检测问题中的实体,让我们使用full-text index将它们映射到知识图谱。首先,我们需要定义一个full-text index和一个函数,该函数将生成允许出现一些拼写错误的full-text queries,我们在这里不详细介绍。
graph.query(
"CREATE FULLTEXT INDEX entity IF NOT EXISTS FOR (e:__Entity__) ON EACH [e.id]")
def generate_full_text_query(input: str) -> str:
"""
Generate a full-text search query for a given input string.
This function constructs a query string suitable for a full-text search.
It processes the input string by splitting it into words and appending a
similarity threshold (~2 changed characters) to each word, then combines
them using the AND operator. Useful for mapping entities from user questions
to database values, and allows for some misspelings.
"""
full_text_query = ""
words = [el for el in remove_lucene_chars(input).split() if el]
for word in words[:-1]:
full_text_query += f" {word}~2 AND"
full_text_query += f" {words[-1]}~2"
return full_text_query.strip()
【代码解析(译者注)】
graph.query(
"CREATE FULLTEXT INDEX entity IF NOT EXISTS FOR (e:__Entity__) ON EACH [e.id]")
#创建全文索引
#query:是 graph 对象的一个方法,用于向图数据库发送查询语句。
#CREATE FULLTEXT INDEX entity:表示创建一个名为 entity 的全文索引。
#IF NOT EXISTS:是一个条件判断,如果该索引不存在才会执行创建操作,避免重复创建。
#FOR (e:__Entity__):指定索引作用的节点类型,这里表示针对标签为 __Entity__ 的节点。
#ON EACH [e.id]:表示对这些节点的 id 属性创建全文索引。
def generate_full_text_query(input: str) -> str:
"""
Generate a full-text search query for a given input string.
This function constructs a query string suitable for a full-text search.
It processes the input string by splitting it into words and appending a
similarity threshold (~2 changed characters) to each word, then combines
them using the AND operator. Useful for mapping entities from user questions
to database values, and allows for some misspelings.
"""
full_text_query = ""
words = [el for el in remove_lucene_chars(input).split() if el]
for word in words[:-1]:
full_text_query += f" {word}~2 AND"
full_text_query += f" {words[-1]}~2"
return full_text_query.strip()
#函数功能:该函数接收一个字符串输入,生成一个适合全文搜索的查询字符串,允许一定程度的拼写错误(每个单词允许最多 2 个字符的差异)。
#full_text_query = "":初始化一个空字符串,用于存储最终的查询字符串。
#remove_lucene_chars(input):调用 remove_lucene_chars 函数(代码中未给出该函数定义),该函数的作用应该是移除输入字符串中可能影响 Lucene 全文搜索的特殊字符。
#.split():将处理后的字符串按空格分割成单词列表。
#if el:过滤掉空字符串。
#for word in words[:-1]::遍历除最后一个单词之外的所有单词。
#full_text_query += f" {word}~2 AND":将每个单词后面添加 ~2 AND,~2 表示允许该单词与目标词有最多 2 个字符的差异,AND 是逻辑与运算符。
#full_text_query += f" {words[-1]}~2":处理最后一个单词,只添加 ~2。
#return full_text_query.strip():返回最终的查询字符串,并去除首尾的空格。
现在让我们把它们放在一起。
# Fulltext index query
def structured_retriever(question: str) -> str:
"""
Collects the neighborhood of entities mentioned
in the question
"""
result = ""
entities = entity_chain.invoke({"question": question})
for entity in entities.names:
response = graph.query(
"""CALL db.index.fulltext.queryNodes('entity', $query, {limit:2})
YIELD node,score
CALL {
MATCH (node)-[r:!MENTIONS]->(neighbor)
RETURN node.id + ' - ' + type(r) + ' -> ' + neighbor.id AS output
UNION
MATCH (node)<-[r:!MENTIONS]-(neighbor)
RETURN neighbor.id + ' - ' + type(r) + ' -> ' + node.id AS output
}
RETURN output LIMIT 50
""",
{"query": generate_full_text_query(entity)},
)
result += "\n".join([el['output'] for el in response])
return result
【代码解析(译者注)】
def structured_retriever(question: str) -> str:
"""
Collects the neighborhood of entities mentioned
in the question
"""
result = ""
#def structured_retriever(question: str) -> str::定义了一个名为 structured_retriever 的函数,它接受一个字符串类型的 question 作为输入,并返回一个字符串类型的结果。
#result = "":初始化一个空字符串 result,用于存储最终的查询结果。
entities = entity_chain.invoke({"question": question})
#entity_chain 是之前定义的一个实体提取链,它能够根据输入的文本提取其中的人物、组织等实体。
#invoke({"question": question}) 方法会将 question 传递给 entity_chain,并返回一个 Entities 类的实例,其中包含了从问题中提取的实体名称列表。
for entity in entities.names:
response = graph.query(
"""CALL db.index.fulltext.queryNodes('entity', $query, {limit:2})
YIELD node,score
CALL {
MATCH (node)-[r:!MENTIONS]->(neighbor)
RETURN node.id + ' - ' + type(r) + ' -> ' + neighbor.id AS output
UNION
MATCH (node)<-[r:!MENTIONS]-(neighbor)
RETURN neighbor.id + ' - ' + type(r) + ' -> ' + node.id AS output
}
RETURN output LIMIT 50
""",
{"query": generate_full_text_query(entity)},
)
#for entity in entities.names::遍历从问题中提取的每个实体名称。
#graph.query:这是与图数据库交互的方法,用于执行 Cypher 查询语句。
#CALL db.index.fulltext.queryNodes('entity', $query, {limit:2}):调用图数据库的全文索引查询节点的功能,'entity' 是之前创建的全文索引的名称,$query 是查询参数,{limit:2} 表示最多返回 2 个匹配的节点。
#YIELD node,score:将查询到的节点和对应的匹配得分输出。
#MATCH (node)-[r:!MENTIONS]->(neighbor):查找与匹配节点有非 MENTIONS 关系的出边邻居节点,并将节点 id、关系类型和邻居节点 id 拼接成一个字符串作为 output 返回。
#UNION:将两个 MATCH 子句的结果合并。
#MATCH (node)<-[r:!MENTIONS]-(neighbor):查找与匹配节点有非 MENTIONS 关系的入边邻居节点,并同样将相关信息拼接成字符串作为 output 返回。
#RETURN output LIMIT 50:返回最终的 output 结果,最多返回 50 条记录。
#{"query": generate_full_text_query(entity)}:这是传递给查询语句的参数,generate_full_text_query(entity) 会将实体名称转换为适合全文搜索的查询字符串。
result += "\n".join([el['output'] for el in response])
#[el['output'] for el in response]:从查询结果 response 中提取每个记录的 output 字段,形成一个列表。
#"\n".join(...):将列表中的元素用换行符连接成一个字符串,并追加到 result 中。
return result
#返回包含所有查询结果的字符串。
structural_retriever函数首先检测用户问题中的实体。接下来,它迭代检测到的实体并使用Cypher模板来检索相关节点的邻居。让我们来测试一下!
print(structured_retriever("Who is Elizabeth I?"))
# Elizabeth I - BORN_ON -> 7 September 1533
# Elizabeth I - DIED_ON -> 24 March 1603
# Elizabeth I - TITLE_HELD_FROM -> Queen Of England And Ireland
# Elizabeth I - TITLE_HELD_UNTIL -> 17 November 1558
# Elizabeth I - MEMBER_OF -> House Of Tudor
# Elizabeth I - CHILD_OF -> Henry Viii
# and more...
最终检索
正如开头提到的那样,我们将结合非结构化和图谱检索器来创建传递给LLM的最终上下文。
def retriever(question: str):
print(f"Search query: {question}")
structured_data = structured_retriever(question)
unstructured_data = [el.page_content for el in vector_index.similarity_search(question)]
final_data = f"""Structured data:
{structured_data}
Unstructured data:
{"#Document ". join(unstructured_data)}
"""
return final_data
【代码解析(译者注)】
def retriever(question: str):
print(f"Search query: {question}")
#def retriever(question: str)::定义了一个名为 retriever 的函数,它接受一个字符串类型的参数 question,表示用户提出的搜索问题。
#print(f"Search query: {question}"):打印出当前的搜索查询内容,方便调试和查看。
structured_data = structured_retriever(question)
#structured_retriever(question):调用之前定义的 structured_retriever 函数,该函数会从图数据库中提取与问题中提及的实体相关的结构化信息,例如实体的邻居节点关系等。
#structured_data 存储了这些结构化信息的字符串。
unstructured_data = [el.page_content for el in vector_index.similarity_search(question)]
#获取非结构化数据
#vector_index.similarity_search(question):使用向量索引进行相似度搜索,根据问题的向量表示,从索引中找出与问题最相似的文档。
#[el.page_content for el in ...]:这是一个列表推导式,从搜索结果中提取每个文档的 page_content 属性,将其存储在 unstructured_data 列表中,这些内容就是非结构化数据。
final_data = f"""Structured data:
{structured_data}
Unstructured data:
{"#Document ". join(unstructured_data)}
"""
#整合数据,使用格式化字符串将结构化数据和非结构化数据整合在一起。
#首先输出 Structured data: 标题,然后是 structured_data 的内容。
#接着输出 Unstructured data: 标题,将 unstructured_data 列表中的每个元素用 #Document 连接起来作为非结构化数据的展示。
return final_data
#将整合好的包含结构化数据和非结构化数据的字符串作为函数的返回值。
当我们处理Python时,我们可以简单地使用f字符串连接输出。
定义RAG链
我们已经成功实现了RAG的检索组件。接下来,我们引入一个prompt,利用集成混合检索器提供的上下文来产生响应,从而完成RAG链。
template = """Answer the question based only on the following context:
{context}
Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
chain = (
RunnableParallel(
{
"context": _search_query | retriever,
"question": RunnablePassthrough(),
}
)
| prompt
| llm
| StrOutputParser()
)
【代码解析(译者注)】
template = """Answer the question based only on the following context:
{context}
Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
#template 是一个字符串模板,它指定了生成提示的格式。其中 {context} 是用于填充检索到的相关上下文信息的占位符,{question} 是用于填充用户提出的问题的占位符。提示要求大语言模型仅根据提供的上下文来回答问题。
#ChatPromptTemplate.from_template(template) 使用 langchain 库中的 ChatPromptTemplate 类,根据定义好的模板创建一个提示模板对象 prompt。
chain = (
RunnableParallel(
{
"context": _search_query | retriever,
"question": RunnablePassthrough(),
}
)
| prompt
| llm
| StrOutputParser()
)
#RunnableParallel:这是 langchain 中的一个组件,用于并行处理多个任务。它接受一个字典作为参数,字典的键表示输出的字段名,值表示要执行的操作。
#"context": _search_query | retriever:_search_query 可能是一个用于处理搜索查询的组件(代码中未给出其定义),它会对输入进行处理,然后将处理后的结果传递给 retriever 函数。retriever 函数会根据问题检索相关的结构化和非结构化数据作为上下文。
#"question": RunnablePassthrough():RunnablePassthrough() 是一个直接传递输入的组件,它会将用户输入的问题原封不动地传递到下一步。
#prompt:将并行处理得到的 context 和 question 填充到之前定义的提示模板中,生成完整的提示信息。
#llm:这是之前初始化的大语言模型实例(如 ChatOpenAI),它会根据生成的提示信息生成回答。
#StrOutputParser():将大语言模型的输出解析为字符串类型,方便后续使用。
最后,我们可以继续测试我们的混合RAG实现。
chain.invoke({"question": "Which house did Elizabeth I belong to?"})
# Search query: Which house did Elizabeth I belong to?
# 'Elizabeth I belonged to the House of Tudor.'
我还集成了query重写功能,使RAG链能够适应允许后续问题的对话设置。鉴于我们使用向量和关键词搜索方法,我们必须重写后续问题以优化我们的搜索过程。
chain.invoke(
{
"question": "When was she born?",
"chat_history": [("Which house did Elizabeth I belong to?", "House Of Tudor")],
}
)
# Search query: When was Elizabeth I born?
# 'Elizabeth I was born on 7 September 1533.'
你可以观察到When was she born?首次改写为When was Elizabeth I born?。然后使用重写后的query来检索相关上下文并回答问题。
轻松增强RAG应用程序
随着LLMGraphTransformer的推出,生成知识图的过程现在应该更加流畅、更加易于访问,使任何希望利用知识图提供的深度和上下文增强RAG应用程序的人都更容易。这只是一个开始,因为我们计划进行很多改进。
如果您对我们使用LLM生成图表有见解、建议或疑问,请随时联系。
该代码可在Github上找到。
1818

被折叠的 条评论
为什么被折叠?



