首页 > 教程攻略 > ai资讯 >利用知识图谱提高 RAG 应用的准确性

利用知识图谱提高 RAG 应用的准确性

来源:互联网 时间:2026-07-29 15:43:18

在 RAG 应用中使用 Neo4j 和 LangChain 构建和检索知识图谱信息的实用指南

利用知识图谱提高 RAG 应用的准确性

图片由DALL-E创作

GraphRAG 最近热度很高,正在成为传统向量搜索的有力补充。这种方法利用图数据库的形态,将数据组织为节点和关系,从而大幅增强搜索信息的深度和上下文。原本在向量数据库中不太好搞的结构化信息,放到图里反而游刃有余——毕竟图天然擅长表示和存储互连数据,轻松捕获不同数据类型之间的复杂关系和属性。而在 RAG 应用里,把结构化的图形数据和非结构化的向量搜索结合起来,就能取长补短,实现更好的效果。这正是今天要演示的内容。

知识图谱好是好,可怎么创建呢?这一步往往最棘手:需要收集数据、构建结构,还得对领域和图形建模有深刻理解。好消息是,大语言模型在语言理解和上下文把握上的能力,正好可以用来简化知识图谱的创建流程。通过分析文本数据,这些模型能识别实体、理清关系,并建议如何在图结构中最佳地表示。

作为这些实验的成果,LangChain 新增了第一个版本的图构建模块,下面就来演示它的用法。

代码可在GitHub上获取。

Neo4j环境设置

需要先设置一个 Neo4j 实例。最简单的方式是在 Neo4j Aura 上启动一个免费实例,获得云端数据库;也可以下载 Neo4j 桌面应用,在本地创建数据库。

Shell
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 的加载器从维基百科获取文档并分割。

Shell
# 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])

是时候根据文档构建图了。这里用到一个 LLMGraphTransformer 模块,能大大简化在图数据库中构建和存储知识图谱的过程。

Shell
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 来生成知识图谱。目前只支持 OpenAI 和 Mistral 的函数调用模型,但后续会扩展更多选项。这里用最新的 GPT-4。注意,生成的图质量跟模型能力直接相关,理论上应该选最强的。 LLMGraphTransformer 返回图文档,通过 add_graph_documents 导入 Neo4j。参数 baseEntityLabel=True 会为每个节点额外添加 __Entity__ 标签,增强索引和查询性能;include_source=True 则将节点链接到原始文档,方便追溯和上下文理解。

在 Neo4j 浏览器里可以查看生成的图(下图仅为局部)。

生成的部分图

请注意,此图只是生成图的一部分。

RAG 混合检索

图生成之后,采用混合搜索方法:将向量搜索、关键字搜索与图搜索结合起来,用于 RAG 应用。

结合(向量+关键字)和图混合搜索方法。图片由作者提供

上图展示了搜索流程:用户提出问题后,RAG 搜索器同时进行关键字搜索和向量搜索来查找非结构化文本,同时从知识图谱中提取结构化信息。Neo4j 本身就支持关键字索引和向量索引,所以一个数据库系统就能搞定三种搜索。收集到的数据输入 LLM 生成最终答案。

非结构化数据检索器

使用 Neo4jVector.from_existing_graph 方法为文档添加关键字和向量支持。这个方法会配置混合搜索索引,目标节点标签为 Document。如果文本嵌入值不存在,还会自动计算。

Shell
vector_index = Neo4jVector.from_existing_graph(
OpenAIEmbeddings(),
search_type="hybrid",
node_label="Document",
text_node_properties=["text"],
embedding_node_property="embedding"
)

然后就可以用 similarity_search 方法调用向量索引。

图检索器

配置图检索要复杂一些,但自由度更高。这里用全文索引来识别相关节点,并返回它们的直接邻居。

图检索器。图片由作者提供

图检索器首先识别输入中的相关实体。简单起见,让 LLM 识别人员、组织和地点。使用 LCEL 和新加入的 with_structured_output 方法来实现。

Shell
# 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)

测试一下:

Shell
entity_chain.invoke({"question": "Where was Amelia Earhart born?"}).names
# ['Amelia Earhart']

能检测到问题中的实体了,接下来用全文索引把它们映射到知识图谱。先定义一个全文索引和一个生成允许拼写错误的全文查询的函数。

Shell
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()

把它们组合起来。

Shell
# 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

structured_retriever 函数先检测用户问题中的实体,然后遍历实体,用 Cypher 模板检索相关节点的邻域。测试一下:

Shell
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 的最终上下文。

Shell
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

在 Python 里用 f-string 拼接输出就行。

定义 RAG 链

检索组件已经完成,接着构建提示模板,利用混合检索器提供的上下文生成回答,最后组装成 RAG 链。

Shell
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()
)

测试一下混合 RAG 的效果:

Shell
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.'

还集成了查询重写功能,使得 RAG 链能适应对话场景中的后续问题。因为使用了向量和关键字搜索,后续问题需要重写才能优化搜索。

Shell
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?”,然后用重写后的查询去检索上下文并给出答案。

轻松增强 RAG 应用程序

借助 LLMGraphTransformer 模块,知识图谱的生成过程变得流畅且容易上手,让更多开发者能利用图谱的深度和上下文来增强 RAG 应用。这只是一个开始,后续还有不少改进计划。

代码可在GitHub上获取。