知识图谱提升RAG应用准确性实战指南

2026-06-20阅读 0热度 0
ai 人工智能

将Neo4j与LangChain结合,在RAG应用中高效构建与检索知识图谱的实战教程

利用知识图谱提高 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上获取。

免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

相关阅读

更多
欢迎回来 登录或注册后,可保存提示词和历史记录
登录后可同步收藏、历史记录和常用模板
注册即表示同意服务条款与隐私政策