微信扫码
添加专属顾问
❝本文经翻译并二次整理自Enhancing RAG-based application accuracy by constructing and leveraging knowledge graphs一文。LangChain已经将图构建模块的首个版本集成到了其生态之中,今天本文将展示基于知识图谱的RAG应用实战 。本系列合集,点击链接查看
图检索增强生成(Graph RAG)正逐渐流行起来,成为传统向量搜索方法的有力补充。这种方法利用图数据库的结构化特性,将数据以节点和关系的形式组织起来,从而增强检索信息的深度和上下文关联性。
图在表示和存储多样化且相互关联的信息方面具有天然优势,能够轻松捕捉不同数据类型间的复杂关系和属性。而向量数据库在处理这类结构化信息时则显得力不从心,它们更擅长通过高维向量处理非结构化数据。在 RAG 应用中,结合结构化的图数据和非结构化的文本向量搜索,可以让我们同时享受两者的优势,这也是本文将要探讨的内容。
知识图谱的确很有用,但如何构建一个呢? 构建知识图谱通常是利用图数据表示的强大功能中最困难的一步。它需要收集和整理数据,这需要对领域知识和图建模有深刻的理解。为了简化这一过程,我们开始尝试使用大型语言模型(LLM)。LLM 凭借其对语言和上下文的深刻理解,可以自动化知识图谱创建过程中的大部分工作。通过分析文本数据,这些模型能够识别实体,理解它们之间的关系,并提出如何在图结构中最佳表示这些实体。基于这些实验,我们已经将图构建模块的首个版本集成到了 LangChain 中,本文将展示其应用。
相关代码已在 GitHub 上发布。
为了跟随本文的示例,您需要搭建一个 Neo4j 实例。最简单的方法是在 Neo4j Aura 上启动一个免费实例,它提供了 Neo4j 数据库的云版本。当然,您也可以通过下载 Neo4j Desktop 应用程序来创建一个本地数据库实例。
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 密钥,因为我们将在本文中使用他们的模型。
在本次演示中,我们将使用伊丽莎白一世的维基百科页面。我们可以利用 LangChain 加载器 轻松地从维基百科获取并分割文档。
# 读取维基百科文章
raw_documents = WikipediaLoader(query="Elizabeth I").load()
# 定义分块策略
text_splitter = TokenTextSplitter(chunk_size=512, chunk_overlap=24)
documents = text_splitter.split_documents(raw_documents[:3])
现在是时候根据获取的文档来构建图谱了。为此,我们开发了一个 LLMGraphTransformer 模块,它极大地简化了在图数据库中构建和存储知识图谱的过程。
llm=ChatOpenAI(temperature=0, model_name="gpt-4-0125-preview")
llm_transformer = LLMGraphTransformer(llm=llm)
# 提取图数据
graph_documents = llm_transformer.convert_to_graph_documents(documents)
# 存储到 neo4j
graph.add_graph_documents(
graph_documents,
baseEntityLabel=True,
include_source=True
)
您可以指定知识图谱生成链使用哪种 LLM。目前,我们只支持 OpenAI 和 Mistral 的函数调用模型。不过,我们计划未来会扩展 LLM 的选择范围。在这个例子中,我们使用的是最新的 GPT-4。需要注意的是,生成的图谱质量很大程度上取决于您使用的模型。理论上,您应该选择能力最强的模型。LLM 图转换器返回的图文档可以通过 add_graph_documents 方法导入到 Neo4j。baseEntityLabel 参数为每个节点添加了一个额外的 __Entity__ 标签,以增强索引和查询性能。include_source 参数则将节点与其原始文档关联起来,便于数据追溯和理解上下文。
您可以在 Neo4j 浏览器中查看生成的图谱。
请注意,这张图片仅为了清晰展示,只展示了生成图谱的一部分。
在图谱生成之后,我们将采用一种混合检索方法,结合向量和关键字索引以及图检索技术,用于 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"
)
然后,您可以使用 similarity_search 方法来调用向量索引。
另一方面,配置图检索器虽然更为复杂,但提供了更大的灵活性。在这个例子中,我们将使用全文索引来识别相关节点,然后返回它们的直接邻域。
图检索器首先识别输入中的相关实体。为了简化,我们指导 LLM 识别人物、组织和地点。为了实现这一点,我们将使用 LCEL 配合新加入的 with_structured_output 方法。
# 从文本中提取实体
class Entities(BaseModel):
"""识别实体相关信息。"""
names: List[str] = Field(
...,
description="文本中出现的所有人物、组织或商业实体的名称",
)
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"您正在从文本中提取组织和人物实体。",
),
(
"human",
"请按照给定格式从以下输入中提取信息:{question}",
),
]
)
entity_chain = prompt | llm.with_structured_output(Entities)
让我们来实际测试一下:
entity_chain.invoke({"question": "阿梅莉亚·埃尔哈特在哪里出生?"}).names
# ['阿梅莉亚·埃尔哈特']
很好,现在我们能够在问题中识别出实体,接下来我们将使用全文索引将这些实体映射到知识图谱中。首先,我们需要定义一个全文索引,并创建一个函数来生成全文查询,这个查询允许一定程度的拼写错误,这里我们不详细展开。
graph.query(
"CREATE FULLTEXT INDEX entity IF NOT EXISTS FOR (e:__Entity__) ON EACH [e.id]")
def generate_full_text_query(input: str) -> str:
"""
为给定的输入字符串生成全文搜索查询。
该函数构建一个适用于全文搜索的查询字符串。它通过将输入字符串分割成单词,并对每个单词附加一个相似性阈值(允许2个字符变化),然后使用 AND 运算符将它们组合起来。这对于将用户问题中的实体映射到数据库值非常有用,并且能够容忍一些拼写错误。
"""
full_text_query = ""
words = [word for word in remove_lucene_chars(input).split() if word]
for word in words[:-1]:
full_text_query += f"{word}~2 AND"
full_text_query += f"{words[-1]}~2"
return full_text_query.strip()
现在,让我们整合所有步骤。
# 全文索引查询
def structured_retriever(question: str) -> str:
"""
收集问题中提到的实体的邻域信息
"""
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 模板检索相关节点的邻域信息。让我们来实际测试一下!
print(structured_retriever("伊丽莎白一世是谁?"))
# 伊丽莎白一世 - BORN_ON -> 1533年9月7日
# 伊丽莎白一世 - DIED_ON -> 1603年3月24日
# 伊丽莎白一世 - TITLE_HELD_FROM -> 英格兰和爱尔兰女王
# 伊丽莎白一世 - TITLE_HELD_UNTIL -> 1558年11月17日
# 伊丽莎白一世 - MEMBER_OF -> 都铎王朝
# 伊丽莎白一世 - CHILD_OF -> 亨利八世
# 等等...
正如我们一开始提到的,我们将结合非结构化和图检索器来创建最终的上下文,这将传递给 LLM。
def retriever(question: str):
print(f"搜索查询:{question}")
structured_data = structured_retriever(question)
unstructured_data = [el.page_content for el in vector_index.similarity_search(question)]
final_data = f"""结构化数据:
{structured_data}
非结构化数据:
{"#Document ".join(unstructured_data)}
"""
return final_data
由于我们使用的是 Python,我们可以使用 f-string 轻松地将输出合并。
我们已经成功实现了 RAG 的检索组件。接下来,我们将引入一个提示,它利用混合检索器提供的上下文来生成响应,从而完成 RAG 链的实现。
template = """根据以下上下文回答问题:
{context}
问题:{question}
"""
prompt = ChatPromptTemplate.from_template(template)
chain = (
RunnableParallel(
{
"context": _search_query | retriever,
"question": RunnablePassthrough(),
}
)
| prompt
| llm
| StrOutputParser()
)
最后,我们可以测试我们的混合 RAG 实现。
chain.invoke({"question": "伊丽莎白一世属于哪个家族?"})
# 搜索查询:伊丽莎白一世属于哪个家族?
# '伊丽莎白一世属于都铎王朝。'
我还加入了一个查询重写特性,使得 RAG 链能够适应允许后续问题的对话环境。鉴于我们使用了向量和关键字搜索方法,我们需要重写后续问题以优化搜索过程。
chain.invoke(
{
"question": "她何时出生?",
"chat_history": [("伊丽莎白一世属于哪个家族?",
"都铎王朝")],
}
)
# 搜索查询:伊丽莎白一世何时出生?
# '伊丽莎白一世出生于1533年9月7日。'
您可以看到,'她何时出生?' 首先被重写为 '伊丽莎白一世何时出生?'。然后使用重写后的查询来检索相关上下文并回答问题。
随着 LLMGraphTransformer 的引入,生成知识图谱的过程现在应该更加顺畅和易于访问,这使得任何想要通过知识图谱提供的深度和上下文来增强其基于 RAG 的应用的人更容易上手。这只是一个开始,因为我们计划进行更多的改进。
如果您对我们使用 LLM 生成图谱有任何见解、建议或问题,请随时联系我们。
相关代码已在 GitHub 上发布。
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业
2026-04-19
2026-05-07
2026-04-23
2026-06-03
2026-04-22
2026-04-23
2026-05-26
2026-05-28
2026-05-23
2026-05-22
欢迎您使用【53AI 官方网站】(以下简称“本网站”或“我们”)。本《会员服务协议》(以下简称“本协议”)是您(以下简称“会员”或“用户”)与【深圳市博思协创网络科技有限公司】之间关于注册、登录及使用本网站会员服务所订立的法律协议。
在您注册或登录前,请务必审慎阅读、充分理解各条款内容,特别是免除或限制责任的条款、知识产权条款、争议解决条款等。此类条款将以加粗形式提示您注意。 当您通过微信公众号授权、手机验证码验证或其他方式成功登录本网站时,即视为您已完全理解并同意接受本协议的全部内容。
一、 定义
本网站:指由【深圳市博思协创网络科技有限公司】运营的,域名为【53ai.com】的网站及相关移动端页面。
会员服务:指本网站向注册会员提供的知识库文章查阅、内容检索及其他相关增值服务。
知识库内容:指本网站发布的包括但不限于文字、图表、数据、研究报告、行业分析等数字化内容资源。
二、 账号注册与登录
登录方式:本网站支持以下登录方式,您可根据实际情况选择:
微信公众号授权登录:您同意将您的微信OpenID信息授权给本网站,用于创建或关联会员账号。
手机验证码登录:您需提供真实有效的手机号码,并通过短信验证码完成身份验证与登录/注册。
账号安全:您的账号仅限您本人使用,禁止赠与、借用、租用、转让或售卖。因您保管不善导致的账号被盗、密码泄露等损失,由您自行承担。
实名认证:根据相关法律法规要求,我们可能要求您在特定功能下完成实名认证。如您拒绝提供,可能无法使用部分或全部服务。
未成年人保护:若您未满18周岁,请在法定监护人的陪同下阅读本协议,并在征得监护人同意后使用本服务。
三、 服务内容与规范
知识库查阅权限:会员登录后,有权按照其会员等级对应的权限范围,在线浏览、检索本网站知识库中的相关文章及内容。
服务变更:我们有权根据业务发展需要,调整、变更或终止部分服务内容,并将以网站公告、公众号消息等方式提前通知。
禁止行为:您在使用服务时不得实施以下行为:
利用技术手段批量爬取、下载、转存知识库内容;
将知识库内容用于商业目的或未经授权地向第三方传播;
干扰本网站正常运行或侵犯其他用户合法权益;
发布违法违规信息或从事违反公序良俗的活动。
四、 知识产权声明
权利归属:本网站知识库中的排版设计、软件代码等内容的知识产权均归【公司全称】或原权利人所有,受《中华人民共和国著作权法》等法律保护。
有限许可:本网站授予会员一项非独占、不可转让、不可转授权的普通许可,仅限于个人学习、研究之目的在线查阅知识库内容。
侵权追责:未经书面许可,任何单位或个人不得以任何形式复制、转载、摘编、镜像、汇编或以其他方式使用上述内容。一经发现,我们保留追究其法律责任的权利。
五、 个人信息保护
我们重视对您个人信息的保护。关于我们如何收集、使用、存储和保护您的个人信息,请单独阅读 《隐私政策》。
您通过微信公众号授权或手机号验证所提供的信息,我们将严格按照《个人信息保护法》的规定处理,仅用于身份识别、服务提供及安全验证等必要用途。
您可以随时通过网站设置或联系客服行使查阅、更正、删除个人信息及撤回授权同意的权利。
六、 免责声明
内容准确性:知识库内容仅供参考,不构成专业建议。我们不对其完整性、准确性、时效性作任何明示或暗示的保证,您应自行判断并承担使用风险。
不可抗力:因自然灾害、政策法规变化、网络故障、第三方平台接口异常(如微信接口维护、运营商短信通道故障)等不可抗力导致的服务中断或延迟,我们不承担违约责任。
第三方链接:本网站可能包含指向第三方网站的链接,该等网站的内容和服务不受我们控制,请您自行甄别风险。
七、 违约责任
如您违反本协议约定,我们有权视情节采取警告、限制功能、暂停服务、注销账号等措施,并保留要求赔偿损失的权利。
如因您的违约行为导致我们遭受行政处罚、第三方索赔或商誉损失,您应承担全部赔偿责任(包括但不限于罚款、赔偿金、律师费、公证费等)。
八、 法律适用与争议解决
本协议的订立、执行和解释均适用中华人民共和国大陆地区法律。
因本协议产生的或与本协议有关的任何争议,双方应友好协商解决;协商不成的,任何一方均可向【公司所在地】有管辖权的人民法院提起诉讼。
九、 其他
本协议构成双方就本服务达成的完整协议,取代此前任何口头或书面约定。
本协议任一条款被认定为无效或不可执行的,不影响其他条款的效力。
我们对本协议享有最终解释权,并在法律允许的范围内保留随时修改的权利。修改后的协议一经公布即生效,继续使用服务即视为同意修订内容。