微信扫码
与创始人交个朋友
我要投稿
今年,微软开源了自主研发的Microsoft GraghRag,犹如一枚重磅炸弹,给市场带来了非常大的反响。开源后不到半年,Github上就获得了18k的star,增长迅猛。
在这篇文章中,风叔就详细拆解一下微软GraphRag的原理,并结合源代码,观察实际运行效果。
微软GraphRAG的原理
微软GraphRag的原理并不复杂,下面的一张图即可解释清楚。首先,我们仍然需要从多个原始知识文档构建知识图谱,这一部分与上篇文章介绍的GraphRAG并无太大区别;然后利用社区检测算法(比如leiden算法),把知识图谱划分成多个社区/聚簇,并利用LLM生成这些社区的自然语言摘要;在回答总结性问题时,先在社区执行查询获得中间答案,最后汇总生成全局性的答案。
可以看到,相比以往RAG结合知识图谱的方式,微软GraphRAG最大的不同就是引入了“社区”这个概念。社区,即围绕某个主题的一组紧密相关的实体与关系信息,比如“三国演义”这个社区,就会关联到众多的诸侯国、主公、将领、谋士、事件等实体及其关系。
从实现流程来看,微软GraphRAG与传统RAG仍然一致,包括索引阶段和查询阶段两大环节,查询阶段相对简单,最复杂的是索引阶段。
索引阶段的主要过程如下所示:
1. 文本块拆分: 将原始文档拆分成多个文本块,这个过程与经典RAG的处理过程一样。
2. 实体与关系提取: 对文本块借助LLM分析,提取实体与关系,这个过程与普通的Graph RAG也类似。
3. 生成实体与关系摘要: 为提取的实体与关系生成简单的描述性信息,这是区别普通Graph RAG的一个步骤。这些描述性信息,会作为一个属性存放在实体/关系的Graph节点中。这种摘要的好处是可以借助嵌入向量更准确的对这些实体与关系进行检索,带来的负面影响是由于需要为大量的实体与关系生成描述信息,将会产生很多的LLM调用,这可能带来过长的耗时与昂贵的大模型API开销。
4. 检测与识别社区: 借助社区检测算法,在Graph中识别出多个社区。
5. 生成社区摘要: 借助LLM生成每个社区的摘要信息,用来了解数据集的全局主题结构和语义,这也是微软GraphRAG的核心价值所在,也是回答全局性问题的关键。
我们再来看一下检索查询阶段,微软GraphRAG有两种查询模式,local模式和global模式。local(本地)模式通常用于针对具体事实的提问;global(全局)模式则是为了支持全局型的查询任务,即建立在高层语义理解基础之上的概要性问题。
上图是local模式,其查询的主要方法是结合相关的知识图谱结构化信息与原始文档的非结构化数据,构建用于增强生成的上下文,并借助LLM获得响应,因此非常适合回答关于特定事实的问题(比如某个实体的信息与关系等)。其主要过程如下所示:
上图是global模式,其查询的架构相对简单,采用了分布式计算中的Map-Reduce架构,可以简单概括为:
微软GraphRAG实战
现在我们用一个文档来构建基于微软GraphRAG的demo应用,这里重点关注索引阶段,准备一个白话文三国演义片段的文本文件用于测试。
第一步,微软GraphRag安装和熟悉
使用pip安装graphrag库,建议在虚拟环境下进行。初始化,这里使用msgraphrag作为应用目录:
python -m graphrag.index --init --root ./msgraphrag
初始化以后会在指定的根目录下生成基本的文件与目录结构,重要的包括:
尽管可以使用这里初始化的默认提示模板,但强烈建议通过GraphRAG提供的命令来创建自适应提示模板:GraphRAG会提取输入数据的信息,并借助大模型来分析与生成更具有针对性的提示模板。
第二步,创建索引
现在运行下面的命令来创建自适应的提示模板(更多参数请参考官方文档):
python -m graphrag.prompt_tune --language Chinese
执行成功后我们来观察两个prompts目录下新的提示模板文件内容
这里从最后输出的信息可以了解到其基本的处理过程:从拆分输入文本开始,到提取实体与关系、生成摘要信息、构建内存中的Graph结构、从Graph识别社区、创建社区报告、创建Graph中的文本块节点和文档节点等。
第三步,将索引集成到neo4j
在完成索引后,默认情况下,GraphRAG会把构建整个知识图谱所需要的数据持久化到output目录下,并以parquet格式文件的方式存放。
由于parquet文件可以很简单的通过pandas库读取成DataFrame表,所以在了解其结构后,就可以通过Cypher语句导入成Neo4j图数据库中的节点与关系。
这里使用其提供的笔记本文件在本地运行,即可成功地把上面构建的知识图谱数据全部导入到Neo4j数据库。
第四步 检索查询
我们使用上文介绍的local模式来进行检索,首先创建一个检索相关实体的向量索引。
由于需要根据用户问题从知识图谱所有节点中检索出最相关的实体(导入时设置的标签为__Entity__),这需要利用到实体节点的一个属性:description_embedding,即节点描述信息的嵌入向量。
因此我们需要在description_embedding上创建一个向量索引,并基于此索引来检索相关实体即可。使用如下Cypher语句在Neo4j创建这个索引:
CREATE VECTOR INDEX entity_index IF NOT EXISTS FOR (e:__Entity__) ON e.description_embeddingOPTIONS {indexConfig: { `vector.dimensions`: 1536, `vector.similarity_function`: 'cosine'}}
然后提取更多相关信息。在检索出的多个实体基础上,进一步检索其关联信息,包括关联的文本块、社区报告、内部关系、外部关系等,并将这些信息组装成上下文,用于后续的生成。
这里可以利用Neo4j的Vector组件的一个输入参数retrieval_query来实现这个过程。该参数作用是自定义一个Cypher代码片段,该片段会添加到到默认的Neo4j向量检索语句后一起执行,并最终要求返回text、score、metadata三个字段,以用于Langchain构建检索的返回对象。
现在我们定义一个后续处理的代码片段,在检索出的node基础上(这里就是关联的实体),进一步检索其他关联信息。
lc_retrieval_query = """
//接收向量检索输出的node,在此基础上进一步检索
WITH collect(node) as nodes
//查找最相关的文本块,输出text属性
WITH
collect {
UNWIND nodes as n
MATCH (n)<-[:HAS_ENTITY]->(c:__Chunk__)
WITH c, count(distinct n) as freq
RETURN c.text AS chunkText
ORDER BY freq DESC
LIMIT $topChunks
} AS text_mapping,
//查找最相关的社区,输出summary摘要(如果没有weight,用cypher设定)
collect {
UNWIND nodes as n
MATCH (n)-[:IN_COMMUNITY]->(c:__Community__)
WITH c, c.rank as rank, c.weight AS weight
RETURN c.summary
ORDER BY rank, weight DESC
LIMIT $topCommunities
} AS report_mapping,
//查找最相关的其他实体(nodes外部),输出描述
collect {
UNWIND nodes as n
MATCH (n)-[r:RELATED]-(m)
WHERE NOT m IN nodes
RETURN r.description AS descriptionText
ORDER BY r.rank, r.weight DESC
LIMIT $topOutsideRels
} as outsideRels,
//查找最相关的其他实体(nodes内部),输出描述
collect {
UNWIND nodes as n
MATCH (n)-[r:RELATED]-(m)
WHERE m IN nodes
RETURN r.description AS descriptionText
ORDER BY r.rank, r.weight DESC
LIMIT $topInsideRels
} as insideRels,
//输出这些实体本身的描述
collect {
UNWIND nodes as n
RETURN n.description AS descriptionText
} as entities
//返回text,score,metadata三个字段
RETURN {Chunks: text_mapping, Reports: report_mapping,
Relationships: outsideRels + insideRels,
Entities: entities} AS text, 1.0 AS score, {source:''} AS metadata
"""
这里的Cypher语句虽然较长,但其实并不复杂,就是对向量检索输出的node搜集后,检索更多相关信息(社区报告、其他实体、关系等),最后合并输出,注意这里必须输出text,score,metadata三个属性,这是Langchain构建输出对象的需要。
剩下的工作就很简单了,把这个片段用retrieval_query参数传入即可。
#创建neo4j向量存储对象,注意传入retrieval_query参数
lc_vector = Neo4jVector.from_existing_index(
text_embedder,
url=NEO4J_URI,
username=NEO4J_USERNAME,
password=NEO4J_PASSWORD,
index_name='entity_index',
retrieval_query=lc_retrieval_query,
)
#chain,并调用获得响应。此处可参考Langchain文档学习
chain = RetrievalQAWithSourcesChain.from_chain_type(
llm,
chain_type="stuff",
retriever=lc_vector.as_retriever(search_kwargs={"params":{
"topChunks": topChunks,
"topCommunities": topCommunities,
"topOutsideRels": topOutsideRels,
"topInsideRels": topInsideRels,
}})
)
response = chain.invoke(
{"question": "周瑜和孙权有什么关系?"},
return_only_outputs=True,
)
print(response['answer'])
如果运行顺利,将可以看到类似的输出:
周瑜是孙策的结拜兄弟,在孙策去世后,辅佐孙权在赤壁之战中击破曹操。
当然在实际使用中,我们还可以根据自身的需要,进一步优化检索召回策略,比如参考风叔在《RAG实战篇系列》中介绍的查询重写、查询扩展、多级索引、Rerank模型等方式。
除了local模式的查询外,global模式也可自定义实现。如果说local模式的关键在于如何召回相关上下文,global模式的关键则在于map与reduce过程的提示模板,感兴趣的朋友可参考微软GraphRAG源代码中的提示模板自行实现。
53AI,企业落地应用大模型首选服务商
产品:大模型应用平台+智能体定制开发+落地咨询服务
承诺:先做场景POC验证,看到效果再签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2024-11-22
RAG技术在实际应用中的挑战与解决方案
2024-11-22
从普通RAG到RAPTOR,10个最新的RAG框架
2024-11-22
如何使用 RAG 提高 LLM 成绩
2024-11-21
提升RAG性能的全攻略:优化检索增强生成系统的策略大揭秘 | 深度好文
2024-11-20
FastGraphRAG 如何做到高达 20%优化检索增强生成(RAG)性能优化
2024-11-20
为裸奔的大模型穿上"防护服":企业AI安全护栏设计指南
2024-11-20
RAG-Fusion技术在产品咨询中的实践与分析
2024-11-19
构建高性能RAG:文本分割核心技术详解
2024-07-18
2024-05-05
2024-07-09
2024-07-09
2024-05-19
2024-06-20
2024-07-07
2024-07-07
2024-07-08
2024-07-09
2024-11-06
2024-11-06
2024-11-05
2024-11-04
2024-10-27
2024-10-25
2024-10-21
2024-10-21