llama_index [问题]:在RAPTOR构建完成后,是否可以添加新文档?

t1rydlwq  于 4个月前  发布在  其他
关注(0)|答案(8)|浏览(51)

问题验证

  • 我已经在文档和discord上寻找答案。

问题

我的文档树非常大,我想知道是否可以避免每次有新文档进来时重新构建整个树。我阅读了base.py中insert函数的实现,但似乎这个函数只插入高级节点,而不是添加新的叶子节点。如果我想要构建一个新的函数,将新文档作为叶子节点插入,当前代码中是否有任何特定部分可能会有所帮助?

ev7lccsx

ev7lccsx1#

嘿 @silviachen46!我是一个设计来帮助你解决bug、问题和成为贡献者的机器人。我在等待人类维护者时会协助你。
是的,在RAPTOR构建后添加新文档是可能的。RaptorRetriever 类有一个 insert 方法,允许将新文档插入索引。此方法可用于向现有索引添加新文档,从而实现RAPTOR结构的动态更新。
如果你想构建一个将新文档作为叶子节点插入的新功能,你可能会发现现有的 insert 方法作为参考很有帮助。该方法通过转换处理文档、生成嵌入、执行聚类并将带有嵌入和父ID的节点插入索引。你可以调整这个过程,以确保新文档被作为叶子节点添加。
以下是来自 insert 方法的相关代码片段:

class RaptorRetriever(BaseRetriever):
    ...
    async def insert(self, documents: List[BaseNode]) -> None:
        """Given a set of documents, this function inserts higher level of abstractions within the index.
For later retrieval

Args:
documents (List[BaseNode]): List of Documents
"""
        embed_model = self.index._embed_model
        transformations = self.index._transformations

        cur_nodes = run_transformations(documents, transformations, in_place=False)
        for level in range(self.tree_depth):
            # get the embeddings for the current documents

            if self._verbose:
                print(f"Generating embeddings for level {level}.")

            embeddings = await embed_model.aget_text_embedding_batch(
                [node.get_content(metadata_mode="embed") for node in cur_nodes]
            )
            assert len(embeddings) == len(cur_nodes)
            id_to_embedding = {
                node.id_: embedding for node, embedding in zip(cur_nodes, embeddings)
            }

            if self._verbose:
                print(f"Performing clustering for level {level}.")

            # cluster the documents
            nodes_per_cluster = get_clusters(cur_nodes, id_to_embedding)

            if self._verbose:
                print(
                    f"Generating summaries for level {level} with {len(nodes_per_cluster)} clusters."
                )
            summaries_per_cluster = await self.summary_module.generate_summaries(
                nodes_per_cluster
            )

            if self._verbose:
                print(
                    f"Level {level} created summaries/clusters: {len(nodes_per_cluster)}"
                )

            # replace the current nodes with their summaries
            new_nodes = [
                TextNode(
                    text=summary,
                    metadata={"level": level},
                    excluded_embed_metadata_keys=["level"],
                    excluded_llm_metadata_keys=["level"],
                )
                for summary in summaries_per_cluster
            ]
            # insert the nodes with their embeddings and parent_id
            nodes_with_embeddings = []
            for cluster, summary_doc in zip(nodes_per_cluster, new_nodes):
                for node in cluster:
                    node.metadata["parent_id"] = summary_doc.id_
                    node.excluded_embed_metadata_keys.append("parent_id")
                    node.excluded_llm_metadata_keys.append("parent_id")
                    node.embedding = id_to_embedding[node.id_]
                    nodes_with_embeddings.append(node)

            self.index.insert_nodes(nodes_with_embeddings)

            # set the current nodes to the new nodes
            cur_nodes = new_nodes

        self.index.insert_nodes(cur_nodes)

你可以修改这个方法,通过关注转换和嵌入过程的初始阶段,然后直接将它们插入索引,而不进行进一步的聚类或汇总,以确保新文档被作为叶子节点添加。

  • 要继续与 Dosu 交谈,请提及 @dosu。*
ztmd8pv5

ztmd8pv52#

@dosu,这行代码cur_nodes = run_transformations(documents, transformations, in_place=False)的作用是什么?它是一个SentenceSplitter(chunk_size=400, chunk_overlap=50),将文档分割成小块并将它们封装到叶子文本节点中吗?

e5nszbig

e5nszbig3#

RaptorRetriever 类的 insert 方法中,对 documents 应用了一系列转换。这些转换在 transformations 列表中定义,并应用于 documents 以生成一个新的节点列表,该列表存储在 cur_nodes 中。
是的,它可以使用类似于 SentenceSplitter(chunk_size=400, chunk_overlap=50) 的东西将文档分割成更小的部分,并将它们封装到叶子文本节点中。从提供的上下文可以看出,SentenceSplitter 是一种可以将文本分割成块的转换类型。
以下是如何将 SentenceSplitter 作为转换使用的示例:

from llama_index.core.node_parser import SentenceSplitter

# Define the SentenceSplitter transformation
sentence_splitter = SentenceSplitter(chunk_size=400, chunk_overlap=50)

# List of transformations
transformations = [sentence_splitter]

# Apply transformations to documents
cur_nodes = run_transformations(documents, transformations, in_place=False)

这段代码将把 documents 分割成大小为 400,重叠为 50 的较小块,结果节点将存储在 cur_nodes1(https://github.com/run-llama/llama_index/blob/main/docs/docs/module_guides/loading/ingestion_pipeline/transformations.md)2(https://github.com/run-llama/llama_index/blob/main/docs/docs/understanding/loading/loading.md)3(https://github.com/run-llama/llama_index/blob/main/llama-index-packs/llama-index-packs-raptor/llama_index/packs/raptor/base.py) 中。

  • 要继续与 Dosu 对话,请提及 @dosu。*
vsmadaxz

vsmadaxz4#

@dosu 这里的in_place布尔值有什么作用?它是否意味着我们创建了原始文档的副本并在它们上面进行拆分?
如果从文本节点中检索到原始文档,我是否能够跟踪它们?如果是,请提供一个示例说明如何做到这一点?

q3qa4bjr

q3qa4bjr5#

@dosu 在这里返回的curr_nodes是一个文本节点列表,每个文本节点都包含了整个文档,但在被分割后。或者每个文本节点都包含了一个文档的一部分?

uhry853o

uhry853o6#

in_place 布尔值在 run_transformations 函数中确定转换是否应修改原始节点列表或创建副本。当 in_place 设置为 False 时,原始节点列表的副本将被创建,并将转换应用于此副本,而不修改原始列表。
是的,您将能够从每个文本节点跟踪原始文档。每个文本节点通过 relationships 属性包含对原始文档的引用,具体使用 NodeRelationship.SOURCE
以下是如何从文本节点跟踪原始文档的示例:

# Assuming you have a text node
text_node = cur_nodes[0]

# Access the original document reference
original_document_info = text_node.relationships[NodeRelationship.SOURCE]

# You can now use this information to track back to the original document
print(f"Original Document ID: {original_document_info.node_id}")

关于 cur_nodes 的结构,cur_nodes 中的每个文本节点仅包含一个文档的部分,而不是整个文档。这种分块是通过转换(如 SentenceSplitter )完成的,该转换将文档分割成更小的部分。
以下是 build_nodes_from_splits 函数中与每个文本节点如何从原始文档的部分创建相关的代码片段:

def build_nodes_from_splits(
    text_splits: List[str],
    document: BaseNode,
    ref_doc: Optional[BaseNode] = None,
    id_func: Optional[IdFuncCallable] = None,
) -> List[TextNode]:
    ref_doc = ref_doc or document
    id_func = id_func or default_id_func
    nodes: List[TextNode] = []
    relationships = {NodeRelationship.SOURCE: ref_doc.as_related_node_info()}
    for i, text_chunk in enumerate(text_splits):
        node = TextNode(
            id_=id_func(i, document),
            text=text_chunk,
            embedding=document.embedding,
            excluded_embed_metadata_keys=document.excluded_embed_metadata_keys,
            excluded_llm_metadata_keys=document.excluded_llm_metadata_keys,
            metadata_seperator=document.metadata_seperator,
            metadata_template=document.metadata_template,
            text_template=document.text_template,
            relationships=relationships,
        )
        nodes.append(node)
    return nodes

这确保每个文本节点仅包含原始文档的一部分,并为了跟踪目的保持对源文档的引用 1(https://github.com/run-llama/llama_index/blob/main/llama-index-legacy/llama_index/legacy/node_parser/node_utils.py)2(https://github.com/run-llama/llama_index/blob/main/llama-index-core/llama_index/core/node_parser/node_utils.py)3(https://github.com/run-llama/llama_index/blob/main/llama-index-packs/llama-index-packs-code-hierarchy/llama_index/packs/code_hierarchy/code_hierarchy.py)。

  • 要继续与 Dosu 交谈,请提及 @dosu。*
t8e9dugd

t8e9dugd7#

@silviachen46 目前尚未实现,主要是因为这样做比较复杂。
如果我来做的话,我可能会跟踪自从上次重新聚类以来插入了多少个文档。在达到某个阈值后,实际上重新对所有内容进行聚类。
如果低于阈值,你只需要将新文档插入到与该文档最匹配的底层聚类中(尽管不太确定如何做到这一点)。

57hvy0tb

57hvy0tb8#

@silviachen46 目前尚未实现,主要是因为实现起来比较复杂。
如果我要实现的话,我可能会跟踪自从上次重新聚类以来插入了多少个文档。在达到某个阈值后,实际上重新聚类所有内容。如果低于阈值,你只需要将新文档插入到与该文档最匹配的底层聚类中(尽管不太确定如何实现这一点)。
@logan-markewich 感谢!我一直在尝试开发这个功能。与其重新聚类,我正在考虑从叶子节点更改到父节点并一直追溯到根节点进行重新汇总,因为我的情况大约有7000多个文档,重新汇总所有这些文档会很慢且耗费资源。实现起来确实很复杂。

相关问题