1. 项目概述一个轻量、可深度定制的GraphRAG实现如果你最近在折腾RAG检索增强生成大概率听说过GraphRAG这个名字。它来自微软研究院核心思想是把文档内容构建成一个知识图谱然后基于图谱的结构比如实体、关系、社区来进行更智能的检索而不是像传统RAG那样只做简单的向量相似度匹配。理论上这能显著提升对复杂、多跳问题的回答能力。但说实话当我第一次去翻看官方的GraphRAG实现时感觉有点头大——代码库庞大依赖复杂各种抽象层叠在一起想快速理解核心逻辑或者按自己需求改点东西门槛不低。这正是nano-graphrag诞生的原因。这个项目的目标非常明确保留GraphRAG最核心、最有效的功能同时把代码写得足够简单、清晰、易修改。整个核心实现不包括测试和提示词只有大约1100行Python代码。它不是为了替代官方实现而是为开发者、研究者以及任何想亲手“摆弄”GraphRAG内部机制的人提供一个绝佳的“学习型”代码库和可快速上手的工具。你可以把它看作GraphRAG的“迷你版”或“教学版”但它功能完整支持异步类型提示齐全并且设计上高度模块化允许你轻松替换LLM、向量数据库、图谱存储等每一个组件。2. 核心设计思路极简与可插拔2.1 为什么选择“小而美”的实现路径官方GraphRAG实现为了工业级的鲁棒性和丰富的功能不可避免地引入了较高的复杂性。nano-graphrag则反其道而行之它的首要设计原则是可读性和可 hack 性。这意味着逻辑直白从文档分块、实体关系抽取、图谱构建、社区发现到最终的检索与生成整个流水线在代码中清晰可见没有过多的“魔法”和隐式行为。依赖极简核心依赖只有openai,networkx,numpy等少数几个库。像向量检索这种功能它内置了一个超轻量的nano-vectordb也可以一键切换到hnswlib或faiss。接口统一所有核心组件——LLM调用、嵌入模型、向量存储、图谱存储——都定义了简洁的抽象基类BaseLLM,BaseEmbedding,BaseVectorStorage,BaseGraphStorage。你要替换任何一个部分只需要实现对应接口的几个方法即可。这种设计带来的最大好处是学习成本低和定制自由度极高。你不仅能快速理解GraphRAG是怎么工作的还能轻易地实验自己的想法比如换用不同的社区发现算法、尝试新的关系抽取提示词或者集成一个冷门的向量数据库。2.2 GraphRAG 两种检索模式解析nano-graphrag实现了GraphRAG论文中提到的两种核心检索策略理解它们对用好这个工具至关重要全局检索Global Search工作原理当用户提出一个宏观、主题性的问题例如“这个故事的核心主题是什么”系统会先分析整个知识图谱识别出重要的“社区”即紧密连接的子图通常代表一个话题或概念。然后它会为每个重要社区生成一份“分析报告”总结该社区的核心内容。最后LLM基于这些社区报告来合成最终答案。适用场景适合总结性、概述性、需要纵观全局的查询。它利用了图谱的宏观结构信息。nano-graphrag的优化与原文逐一对所有社区进行“Map-Reduce”不同这里只选取重要性通过中心性等指标计算最高的前K个社区默认512个进行分析这在保证效果的同时大幅降低了计算和上下文长度开销。局部检索Local Search工作原理当用户提出一个具体、细节性的问题例如“主角在第三章做了什么”系统会先将问题转换为向量在向量库中找到与之最相关的文本块。然后以这些相关文本块为起点在图谱中进行“图漫步”探索并收集与这些起点相连的其他相关实体和关系形成一个局部的子图。最后LLM基于这个局部子图的信息来生成答案。适用场景适合事实性、具体、需要联系相关上下文的查询。它利用了图谱的微观连接信息。个人体会在实际使用中我发现局部检索模式往往更稳定、更可扩展尤其是对于大型文档集。因为它不需要为每次查询都重新分析整个图谱响应更快也更节省资源。项目作者也认为这是“更好、更可扩展”的模式。3. 快速上手与核心API详解3.1 环境准备与安装首先确保你的Python版本在3.9.11以上。安装方式推荐从源码安装这样方便你随时查看和修改代码git clone https://github.com/gusye1234/nano-graphrag.git cd nano-graphrag pip install -e .当然你也可以直接用pip安装稳定版pip install nano-graphrag。接下来是关键的API密钥配置。默认使用OpenAI的模型所以需要设置环境变量export OPENAI_API_KEYsk-your-openai-api-key-here如果你使用Azure OpenAI或Amazon Bedrock项目也提供了对应的配置示例只需参考项目中的.env.example.azure文件或在初始化时传入相应参数即可。甚至如果你没有任何API密钥项目还提供了使用本地模型如通过Ollama或transformers库的示例脚本真正做到开箱即试。3.2 核心工作流构建、插入与查询让我们用一个经典文学作品的例子来演示基本流程。首先准备一份文本数据比如查尔斯·狄更斯的《圣诞颂歌》curl https://raw.githubusercontent.com/gusye1234/nano-graphrag/main/tests/mock_data.txt ./book.txt然后用不到10行Python代码就能完成整个GraphRAG的构建和查询from nano_graphrag import GraphRAG, QueryParam # 初始化GraphRAG指定一个工作目录来持久化数据 graph_rag GraphRAG(working_dir./dickens_workspace) # 插入文档。这里会触发分块 - 提取实体关系 - 构建图谱 - 计算嵌入 - 索引 with open(./book.txt) as f: graph_rag.insert(f.read()) # 方式一进行全局检索适合宏观问题 answer_global graph_rag.query(What are the top themes in this story?) print(全局检索答案, answer_global) # 方式二进行局部检索推荐适合大多数具体问题 answer_local graph_rag.query(What are the top themes in this story?, paramQueryParam(modelocal)) print(局部检索答案, answer_local)关键参数解析working_dir: 这是项目的核心设计之一。所有中间数据图谱、向量索引、缓存都会保存在这个目录。下次初始化时传入相同的路径它会自动加载所有数据无需重新处理极大地节省了时间和成本。QueryParam(mode...): 用于指定检索模式可选global,local, 或naive传统向量检索。插入的细节insert方法可以接受单个字符串也可以接受字符串列表进行批量插入。它内部使用文本内容的MD5哈希作为唯一标识符因此重复插入相同内容不会产生冗余数据。需要注意的是每次插入新文档后图谱的社区结构会重新计算社区报告也会重新生成以确保知识的时效性。3.3 异步接口与进阶查询nano-graphrag全面支持异步操作对于需要高并发的应用场景非常有用。每个同步方法都有一个对应的异步版本以a开头import asyncio async def async_demo(): graph_rag GraphRAG(working_dir./async_demo) # 异步插入 await graph_rag.ainsert(一些异步文本内容...) # 异步查询 answer await graph_rag.aquery(异步查询问题, paramQueryParam(modelocal)) print(answer) asyncio.run(async_demo())有时你可能不想直接获得LLM生成的最终答案而是希望拿到检索到的原始上下文以便集成到你自己的提示工程或后续流程中。nano-graphrag提供了这个功能# 设置 only_need_contextTrue只返回检索到的上下文信息 context_info graph_rag.query( Tell me about Scrooges transformation., paramQueryParam(modelocal, only_need_contextTrue) ) print(context_info)返回的context_info会是一个结构化的文本包含了从图谱中检索到的相关实体、关系以及社区报告等原始信息。你可以自由地将这些信息填充到你自定义的提示词模板中实现更灵活的控制。4. 深度定制替换每一个核心组件nano-graphrag的真正威力在于其可插拔的架构。下面我们逐一拆解如何替换各个核心部件。4.1 自定义大语言模型LLM默认使用OpenAI的GPT-4o系列。但你可以轻松接入任何模型。关键是要实现一个符合_llm.gpt_4o_complete签名的异步函数from nano_graphrag import GraphRAG import httpx async def my_custom_llm( prompt: str, system_prompt: str None, history_messages: list [], **kwargs # 会传入 max_tokens 等参数 ) - str: # 如果启用了缓存kwargs中会包含一个 hashing_kv 对象可以在这里处理 hashing_kv kwargs.pop(hashing_kv, None) # 构建消息列表 messages [] if system_prompt: messages.append({role: system, content: system_prompt}) messages.extend(history_messages) messages.append({role: user, content: prompt}) # 调用你的LLM API这里以假设的API为例 async with httpx.AsyncClient() as client: response await client.post( https://api.your-llm.com/v1/chat/completions, json{model: your-model, messages: messages, **kwargs}, headers{Authorization: Bearer YOUR_API_KEY} ) result response.json() return result[choices][0][message][content] # 在初始化时替换LLM函数 graph_rag GraphRAG( working_dir./custom_llm, best_model_funcmy_custom_llm, # 用于核心推理的“好模型” cheap_model_funcmy_custom_llm, # 用于摘要等任务的“便宜模型” best_model_max_token_size4000, # 根据你的模型调整 cheap_model_max_async10 # 调整最大并发数 )关于JSON输出GraphRAG在实体关系抽取等环节需要LLM输出严格的JSON。对于某些开源模型其JSON输出可能不稳定。nano-graphrag提供了一个后处理接口import json from json_repair import repair_json # 需要安装 json-repair def my_json_repair_func(response: str) - dict: 修复LLM返回的不规范JSON字符串 try: return json.loads(response) except json.JSONDecodeError: # 尝试修复 repaired repair_json(response) return json.loads(repaired) graph_rag GraphRAG( working_dir./with_repair, convert_response_to_json_funcmy_json_repair_func )4.2 自定义嵌入模型默认使用OpenAI的text-embedding-3-small。替换嵌入模型需要实现一个EmbeddingFuncfrom nano_graphrag import GraphRAG from sentence_transformers import SentenceTransformer import numpy as np # 加载本地模型 model SentenceTransformer(all-MiniLM-L6-v2) wrap_embedding_func_with_attrs(embedding_dim384, max_token_size256) async def local_embedding(texts: list[str]) - np.ndarray: 使用sentence-transformers生成嵌入向量 # 注意这里需要是异步函数但sentence-transformers是同步的。 # 为了不阻塞事件循环可以在线程池中运行。 import asyncio loop asyncio.get_event_loop() embeddings await loop.run_in_executor(None, model.encode, texts, {convert_to_numpy: True}) return embeddings graph_rag GraphRAG( working_dir./local_embed, embedding_funclocal_embedding, embedding_batch_num32 # 调整批处理大小 )注意使用wrap_embedding_func_with_attrs装饰器至关重要它告诉系统你的嵌入模型的向量维度embedding_dim和最大token处理长度max_token_size这直接影响分块和向量索引的构建。4.3 自定义向量数据库与图谱存储nano-graphrag将存储抽象为三类都提供了默认实现和扩展接口。1. 向量数据库BaseVectorStorage默认内置了基于nano-vectordb的轻量实现。切换到高性能的hnswlib非常简单项目里已有示例# 参考 examples/using_hnsw_as_vectorDB.py from nano_graphrag import GraphRAG from nano_graphrag.vector_storage.hnsw_storage import HNSWStorage graph_rag GraphRAG( working_dir./hnsw_demo, vector_db_storage_clsHNSWStorage # 指定使用HNSW存储类 )同样你也可以参考示例实现MilvusStorage或FAISSStorage。2. 图谱存储BaseGraphStorage默认使用networkx在内存中维护图谱。对于需要持久化或超大规模图谱的场景可以切换到Neo4j# 首先需要安装 neo4j 驱动 pip install neo4j from nano_graphrag import GraphRAG from nano_graphrag.graph_storage.neo4j_storage import Neo4jStorage graph_rag GraphRAG( working_dir./neo4j_demo, graph_storage_clsNeo4jStorage, neo4j_uribolt://localhost:7687, # Neo4j连接信息 neo4j_userneo4j, neo4j_passwordyour_password )使用Neo4j后你可以利用其强大的图查询语言Cypher对构建的知识图谱进行更复杂的离线分析。3. 键值存储BaseKVStorage用于缓存LLM响应、存储元数据等。默认使用磁盘文件。如果你有Redis或Memcached等需求实现对应的BaseKVStorage子类即可。4.4 自定义分块方法与提示词分块Chunking默认按token数分块。你可以使用内置的按分隔符分块或完全自定义from nano_graphrag import GraphRAG from nano_graphrag._op import chunking_by_seperators # 使用内置的文本分割器按段落、句子等 graph_rag GraphRAG( working_dir./custom_chunk, chunk_funcchunking_by_seperators ) # 完全自定义分块函数 def my_chunker(text: str) - list[str]: # 实现你的分块逻辑例如按固定字符数、按章节标题等 chunks [] # ... your logic here return chunks graph_rag GraphRAG(working_dir./my_chunk, chunk_funcmy_chunker)提示词Prompts所有关键环节的提示词都定义在nano_graphrag.prompt.PROMPTS字典中。你可以直接修改它来优化效果from nano_graphrag.prompt import PROMPTS # 修改实体关系抽取的提示词 PROMPTS[entity_extraction] 你是一个信息提取专家。请从以下文本中识别实体人物、地点、组织、概念等和它们之间的关系。 ... # 修改社区报告的提示词 PROMPTS[community_report] 你是一个分析员。以下是一组相互关联的实体和关系它们构成了一个知识子图。请为这个子图生成一份简洁、全面的分析报告。 ... # 然后正常初始化 GraphRAG它会使用修改后的提示词通过调整提示词你可以让模型更适应特定领域如医疗、法律的实体和关系抽取或者让生成的报告风格更符合你的需求。5. 实战经验与避坑指南在实际使用和改造nano-graphrag的过程中我积累了一些关键经验这些在官方文档里不一定找得到。5.1 模式选择与性能权衡“Local”模式是首选对于绝大多数问答场景modelocal局部检索在速度、资源消耗和答案相关性上取得了更好的平衡。它避免了为每次查询分析整个庞大图谱的开销。“Global”模式更适合当你真的需要一份全局性的总结报告时。控制“图漫步”的深度在Local模式中QueryParam里的local_max_hops参数控制从初始相关块出发在图谱中探索的跳数。跳数越多检索到的上下文越丰富但也可能引入无关信息。对于事实性强的文档1-2跳通常足够对于概念联系紧密的文本可以尝试3跳。社区数量的影响在Global模式中global_max_consider_community参数默认512限制了用于生成报告的社区数量。如果你的文档集非常庞大产生了成千上万个社区适当调低这个值可以显著减少LLM的上下文长度和计算时间但可能会遗漏一些次要主题。需要根据文档规模和查询需求做权衡。5.2 处理大规模文档集的策略增量插入的代价虽然insert支持增量添加文档且能去重但每次插入都会触发全图的社区重新发现和报告重新生成。对于频繁更新的场景这可能会成为瓶颈。一个折中方案是定期例如每天进行一次全量重建或者考虑只对新文档涉及的局部图谱进行更新这需要更复杂的逻辑目前nano-graphrag未内置。内存与持久化默认的networkx图存储是内存式的。处理超大规模文档时例如数十万节点内存可能吃紧。这时强烈建议切换到 Neo4j 这样的外存图数据库。同样对于向量索引hnswlib或faiss也比默认的nano-vectordb更适合大规模场景。异步并发控制在初始化GraphRAG时注意best_model_max_async和embedding_func_max_async等参数。它们控制着调用LLM和嵌入模型时的最大并发数。设置过高可能导致被API限流设置过低则影响速度。需要根据你使用的服务商配额进行调整。5.3 常见问题与排查技巧实体抽取不准确或遗漏检查提示词首先检查并优化PROMPTS[entity_extraction]。确保它清晰指明了你关心的实体类型如产品名、技术术语。调整分块大小分块太大LLM可能无法处理所有信息分块太小会割裂上下文导致实体关系不完整。尝试不同的chunk_token_size默认512。升级LLM实体关系抽取非常依赖LLM的指令遵循和结构化输出能力。如果使用廉价或能力较弱的模型效果会大打折扣。确保best_model_func使用的是能力足够的模型。检索结果不相关验证嵌入模型Local检索的第一步是向量相似度搜索。如果嵌入模型不适合你的领域例如用通用模型处理专业医学文献效果会差。尝试更换为领域相关的嵌入模型。检查图谱质量如果图谱本身构建得不好实体、关系稀疏或错误后续的图漫步自然找不到相关信息。可以尝试将图谱导出项目提供可视化示例直观检查其质量。调整检索参数尝试增加local_max_hops或调整QueryParam中的top_k初始向量检索返回的文本块数。响应速度慢定位瓶颈使用简单的性能分析工具如time模块记录各个阶段插入、查询、LLM调用、嵌入计算的耗时。启用缓存nano-graphrag默认会对LLM响应进行哈希缓存。确保working_dir有写入权限缓存才能生效。考虑混合模式对于简单事实性问题可以尝试modenaive纯向量检索速度最快。对于复杂问题再用modelocal。处理非英文文本虽然项目提供了中文评测但默认提示词是英文的。处理中文或其他语言时务必翻译或重写所有相关的提示词特别是entity_extraction,community_report并确保使用的LLM和嵌入模型在该语言上有良好表现。nano-graphrag就像一个设计精良的“实验平台”它把GraphRAG的核心流程清晰地展现在你面前。通过它你不仅能快速应用这项技术更能深入理解其每一个环节的奥秘。无论是想学习图检索增强生成的原理还是需要为一个特定项目定制一个轻量且高效的RAG系统这个项目都是一个绝佳的起点。它的简洁性赋予了它巨大的灵活性剩下的就取决于你的想象力和具体需求了。