结合prompt分析NodeRAG的build过程

之前介绍了NodeRAG的节点类型和安装过程。

linux环境conda安装NodeRAG示例-CSDN博客

这里尝试从prompt代码角度分析NodeRAG如何将文档转化为节点、关系。

1 整体处理流程

NodeRAG定义了如下所示状态及处理流程。

define the state to pipeline mapping

self.state_pipeline_map = {

State.DOCUMENT_PIPELINE: document_pipline,

State.TEXT_PIPELINE: text_pipline,

State.GRAPH_PIPELINE: Graph_pipeline,

State.ATTRIBUTE_PIPELINE: Attribution_generation_pipeline,

State.EMBEDDING_PIPELINE: Embedding_pipeline,

State.SUMMARY_PIPELINE: SummaryGeneration,

State.INSERT_TEXT: Insert_text,

State.HNSW_PIPELINE: HNSW_pipeline

}

状态转化序列如下所示,依次是INIT、DOCUMENT、TEXT、GRAPH、ATTRIBUTE、EMBEDDING、SUMMARY、INSERT、HNSW和FINISHED,涵盖文档划分、图谱化、特征提取、向量化、内容摘要等。

define the state sequence

self.state_sequence = [

State.INIT,

State.DOCUMENT_PIPELINE,

State.TEXT_PIPELINE,

State.GRAPH_PIPELINE,

State.ATTRIBUTE_PIPELINE,

State.EMBEDDING_PIPELINE,

State.SUMMARY_PIPELINE,

State.INSERT_TEXT,

State.HNSW_PIPELINE,

State.FINISHED

]

https://github.com/Terry-Xu-666/NodeRAG/blob/main/NodeRAG/build/Node.py

这里重点关注文档切分、语义单元提取(摘要、实体、关系)、关联关系构建过程。

2 文档初步切分

State.DOCUMENT_PIPELINE: document_pipline环节

NodeRAG文档切分代码,输入是文件读出的字符串,块大小chunk_size对应token数,实际切分end边界按token数计算。

复制代码
from typing import List
from .token_utils import get_token_counter

class SemanticTextSplitter:
    def __init__(self, chunk_size: int = 1048, model_name: str = "gpt-4o-mini"):
        """
        Initialize the text splitter with chunk size and model name parameters.
        
        Args:
            chunk_size (int): Maximum number of tokens per chunk
            model_name (str): Model name for token counting
        """
        self.chunk_size = chunk_size
        self.token_counter = get_token_counter(model_name)

    def split(self, text: str) -> List[str]:
        """
        Split text into chunks based on both token count and semantic boundaries.
        """
        chunks = []
        start = 0
        text_len = len(text)
        
        while start < text_len:
            # add 4 times of chunk_size string to the start position
            end = start + self.chunk_size * 4  # assume each token is 4 characters
            if end > text_len:
                end = text_len
                
            # get the current text fragment
            current_chunk = text[start:end]
            
            # if the token count of the current fragment exceeds the limit, need to find the split point
            while self.token_counter(current_chunk) > self.chunk_size and start < end:
                # find semantic boundary in the current range
                boundaries = ['\n\n', '\n', '。', '.', '!', '!', '?', '?', ';', ';']
                semantic_end = end
                
                for boundary in boundaries:
                    boundary_pos = current_chunk.rfind(boundary)
                    if boundary_pos != -1:
                        semantic_end = start + boundary_pos + len(boundary)
                        break
                
                # if found semantic boundary, use it; otherwise, force truncation by character
                if semantic_end < end:
                    end = semantic_end
                else:
                    # 没找到合适的语义边界,往回数token直到满足大小限制
                    end = start + int(len(current_chunk) // 1.2)
                
                current_chunk = text[start:end]
            
            # 添加处理好的文本块
            chunk = current_chunk.strip()
            if chunk:
                chunks.append(chunk)
            
            # 移动到下一个起始位置
            start = end

        return chunks

NodeRAG/NodeRAG/utils/text_spliter.py at main · Terry-Xu-666/NodeRAG · GitHub

3 语义单元提取

State.TEXT_PIPELINE: text_pipline环节

将文本切分为块后,进一步从块中提取语义单元,每个单元包含对特定事件或活动的详细描述哦

1)为每个语义单元提供总结,同时保留与原始上下文相关的所有关键细节。

2)直接从每个语义单元的原始文本中提取所有实体,而不是从改写的总结中提取。

3)从第2步中提取的实体中列出语义单元内的所有关系,其中关系类型可以是描述性句子。使用格式"ENTITY_A,RELATION_TYPE,ENTITY_B",请确保字符串中包含三个元素,分别表示两个实体和关系类型。

因为文本被切分为一个个独立语义单元,NodeRAG有可能解决了RAG语义切分问题,

示例中text为文本切分后的块。

text_decomposition_prompt_Chinese = """

目标:给定一个文本,将该文本被划分为多个语义单元,每个单元包含对特定事件或活动的详细描述。

执行以下任务:

1.为每个语义单元提供总结,同时保留与原始上下文相关的所有关键细节。

2.直接从每个语义单元的原始文本中提取所有实体,而不是从改写的总结中提取。

3.从第2步中提取的实体中列出语义单元内的所有关系,其中关系类型可以是描述性句子。使用格式"ENTITY_A,RELATION_TYPE,ENTITY_B",请确保字符串中包含三个元素,分别表示两个实体和关系类型。

要求:

时间实体:根据文本中提到的日期或时间的具体部分来表示时间实体,不填补缺失部分。

每个语义单元应以一个字典表示,包含三个键:semantic_unit(每个语义单元的概括性总结)、entities(直接从每个语义单元的原始文本中提取的实体列表,实体名格式为大写)、relationships(描述性句子形式的提取关系字符串三元组列表)。所有这些字典应存储在一个列表中,以便管理和访问。

示例:

文本:2024年9月,艾米莉·罗伯茨博士前往巴黎参加国际可再生能源会议。在她的访问期间,她与几家欧洲公司探讨了合作并介绍了她在提高太阳能板效率方面的最新研究。与此同时,在世界的另一边,她的同事约翰·米勒博士在亚马逊雨林进行实地工作。他记录了几种新物种,并观察了森林砍伐对当地野生动物的影响。两位学者的工作在各自的领域内至关重要,对环境保护工作做出了重大贡献。

输出:

{{ "semantic_unit": "2024年9月,艾米莉·罗伯茨博士参加了在巴黎举行的国际可再生能源会议,她在会上介绍了她关于太阳能板效率提高的研究并探讨了与欧洲公司的合作。", "entities": \["艾米莉·罗伯茨博士", "2024-09", "巴黎", "国际可再生能源会议", "欧洲公司", "太阳能板效率"\], "relationships": \[ "艾米莉·罗伯茨博士, 参加了, 国际可再生能源会议", "艾米莉·罗伯茨博士, 探讨了合作, 欧洲公司", "艾米莉·罗伯茨博士, 介绍了研究, 太阳能板效率"

}},

{{

"semantic_unit": "约翰·米勒博士在亚马逊雨林进行实地工作,记录了几种新物种并观察了森林砍伐对当地野生动物的影响。",

"entities": ["约翰·米勒博士", "亚马逊雨林", "新物种", "森林砍伐", "当地野生动物"],

"relationships": [

"约翰·米勒博士, 在, 亚马逊雨林进行实地工作",

"约翰·米勒博士, 记录了, 新物种",

"约翰·米勒博士, 观察了, 森林砍伐对当地野生动物的影响"

]

}},

{{

"semantic_unit": "艾米莉·罗伯茨博士和约翰·米勒博士的工作在各自的领域内至关重要,对环境保护工作做出了重大贡献。",

"entities": ["艾米莉·罗伯茨博士", "约翰·米勒博士", "环境保护"],

"relationships": [

"艾米莉·罗伯茨博士, 贡献于, 环境保护",

"约翰·米勒博士, 贡献于, 环境保护"

]

}}

]

##########

实际数据:

##########

文本:{text}

"""

NodeRAG/NodeRAG/utils/prompt/text_decomposition.py at main · Terry-Xu-666/NodeRAG · GitHub

4 实体关系重建

State.GRAPH_PIPELINE: Graph_pipeline环节

之前抽取的关系relationship,格式有可能是错误的,需要重新按照'实体A,关系类型,实体B'重构。

prompt示例如下。

relationship_reconstraction_prompt_Chinese = """

你将获得一个包含实体之间关系的元组字符串。这些关系的格式是错误的,需要被重新构建。正确的格式应为:'实体A,关系类型,实体B',每个元组应包含三个元素:两个实体和一个关系类型。你的任务是将每个关系重新构建为以下格式:{{'source': '实体A', 'relation': '关系类型', 'target': '实体B'}}。请确保输出遵循此结构,准确映射提供的实体和关系。

错误的关系元组:{relationship}

"""

https://github.com/Terry-Xu-666/NodeRAG/blob/main/NodeRAG/utils/prompt/relationship_reconstraction.py

除此之外,Graph环节还包括基本图结构的重建,具体参考以下链接。

https://github.com/Terry-Xu-666/NodeRAG/blob/main/NodeRAG/build/pipeline/graph_pipeline.py

5 关联特征总结

State.ATTRIBUTE_PIPELINE: Attribution_generation_pipeline环节

生成所给实体的简明总结,涵盖其基本属性和重要相关关系。目的是生成属性节点,为重要实体提供详细描述。entity是实体,semantic_units是于entity相关联的语义单元描述,relationships是实体与语义单元关联的相关关系。

attribute_generation_prompt_Chinese = """

生成所给实体的简明总结,涵盖其基本属性和重要相关关系。该总结应像小说中的人物简介或产品描述一样,提供引人入胜且精准的概览。确保输出只包含该实体的总结,不包含任何额外的解释或元数据。字数不得超过2000字,但如果输入材料有限,可以少于2000字。重点在于通过流畅的叙述提炼出最重要的见解,突出实体的核心特征及重要关系。

实体: {entity}

相关语义单元: {semantic_units}

相关关系: {relationships}

"""

如果一个完整语义描述横跨多个节点,这种方式能有效涵盖这种情况。

6 社区摘要总结

State.SUMMARY_PIPELINE: SummaryGeneration环节

使用社区聚类算法找到内容相关的节点,并将这些节点内容组织成一个完整文本。从文本数据中提取不同类别的高层次信息,例如概念、主题、相关理论、潜在影响和关键见解。每条信息应含一个简洁的标题和相应的描述,以反映该聚类文本中的独特视角。基于每条信息构建高级元素节点,包含从社区分析中提取的深层洞察。

community_summary_Chinese = """你将收到来自同一聚类的一组文本数据。你的任务是从文本数据中提取不同类别的高层次信息,例如概念、主题、相关理论、潜在影响和关键见解。每条信息应包含一个简洁的标题和相应的描述,以反映该聚类文本中的独特视角。

请不要试图包含所有可能的信息;相反,选择在该聚类中最具重要性和多样性的元素。避免冗余信息------如果有高度相似的内容,请将它们合并为一个综合条目。确保提取的高层次信息反映文本中的多维度内容,提供全面的概览。

聚类文本数据:

{content}

"""

7 query实体提取

将用户问题query分解为一个 list,其中每一项是句子的主要实体(如关键名词或对象)。

目的是利用提取实体,在图中搜索相关节点。

decompos_query_Chinese = '''

请将以下问题分解为一个 list,其中每一项是句子的主要实体(如关键名词或对象)。如果你对用户的意图或相关领域知识有充分把握,也可以包含密切相关的术语。如果不确定,请仅从问题中提取实体。请尽量减少囊括常见的名词,请将这些元素整合在一个单一的 list 中输出。

问题:{query}

'''

reference


NodeRAG

https://github.com/Terry-Xu-666/NodeRAG

linux环境conda安装NodeRAG示例

https://blog.csdn.net/liliang199/article/details/151101894

相关推荐
东方不败之鸭梨的测试笔记3 小时前
LangChain: Models, Prompts 模型和提示词
人工智能·python·langchain
初恋叫萱萱3 小时前
AI驱动开发实战:基于飞算JavaAI的在线考试系统设计与实现
人工智能
yzx9910133 小时前
图像去雾:从暗通道先验到可学习融合——一份可跑的 PyTorch 教程
人工智能·pytorch·学习
siriuuus3 小时前
Linux 磁盘扩容及分区相关操作实践
linux·运维·服务器
博大世界3 小时前
解剖智驾“大脑”:一文读懂自动驾驶系统软件架构
人工智能·机器学习·自动驾驶
大熊猫侯佩3 小时前
苹果 AI 探秘:代号 “AFM” —— “温柔的反叛者”
人工智能·sft·ai 大模型·apple 本地大模型·foundationmodel·苹果智能·applebot
黑色的山岗在沉睡3 小时前
LeetCode 189. 轮转数组
java·算法·leetcode
会飞的小蛮猪3 小时前
Jenkins运维之路(权限分配&忘记admin密码)
java·运维·经验分享·jenkins·prometheus