【2024构建RAG问答系统】深度解析LlamaIndex自动合并检索框架,大语言模型必知
创始人
2024-11-15 11:37:40
0

🔍 📱 嘿,Chatbot爱好者!今天我们来深入解析一个超强的文档检索和问答系统框架!🚀

🧠 你是否曾经面对海量文档感到头痛?需要快速精准回答问题却不知从何下手?别担心,这个基于LlamaIndex和TruLens的框架就是你的救星!

🔍 本文将为你揭秘:

  1. 如何构建多层次智能索引
  2. 自动合并检索的魔法
  3. 精准重排序的秘诀
  4. 性能评估的必杀技

🌟 无论你是工程师、数据科学家,还是对智能问答系统感兴趣的极客,这篇文章都能让你收获满满!

🏗️ 首先是框架整体设计

  1. 索引构建 - 文档的多重变身

    • 使用HierarchicalNodeParser进行文档分层
    • VectorStoreIndex创建向量存储
    • 索引持久化,方便随时调用
  2. 查询引擎 - 智能检索的核心

    • 基础检索 + 自动合并 = 超强理解力
    • SentenceTransformerRerank保证结果精准度
  3. 评估系统 - 性能监控利器

    • TruLens记录每一次查询
    • 可视化仪表板直观展示系统表现

💡 核心组件解析:

🌳 HierarchicalNodeParser - 文档分层的秘密武器

node_parser = HierarchicalNodeParser.from_defaults(chunk_sizes=chunk_sizes) nodes = node_parser.get_nodes_from_documents(documents) leaf_nodes = get_leaf_nodes(nodes) 

这个解析器就像一个超级智能的剪刀✂️,它能将文档切分成不同大小的片段。想象一下,你有一本厚厚的百科全书:

  • 2048字符可能对应一个章节
  • 512字符可能是一个小节
  • 128字符可能就是一两个段落

为什么要这样做?因为不同的问题需要不同粒度的信息!比如"解释相对论"可能需要整个章节,而"爱因斯坦出生年份"可能一个句子就够了。

🔄 AutoMergingRetriever - 自动合并的魔法师

retriever = AutoMergingRetriever(     base_retriever, automerging_index.storage_context, verbose=True ) 

这个检索器简直就是一个魔法师🧙‍♂️!它不仅能找到相关的文本片段,还能自动将它们合并。想象一下,你问"苹果公司的发展历程",它能自动将"乔布斯创立苹果"、"iPhone发布"、"Tim Cook接任CEO"这些片段智能地组合在一起!

📊 VectorStoreIndex - 高效检索的幕后英雄

automerging_index = VectorStoreIndex(     leaf_nodes, storage_context=storage_context, service_context=merging_context ) 

这个索引就像一个超级图书管理员👨‍💼,它将所有文本片段转化为向量,并用一种特殊的方式存储。当你问问题时,它能以光速找到最相关的片段!

🔎 SentenceTransformerRerank - 结果精炼大师

rerank = SentenceTransformerRerank(     top_n=rerank_top_n, model="BAAI/bge-reranker-base" ) 

这个组件就像一个严格的评委,它会对初步检索的结果进行二次评判,只保留最相关的部分。这就保证了回答的精准性!

🌟 为什么这个框架如此强大?

  • 多层级索引:通过创建不同层次的索引(比如两层和三层),我们可以比较哪种结构更适合我们的数据和问题类型。
auto_merging_index_0 = build_automerging_index(     documents,     llm=OpenAI(model="gpt-3.5-turbo", temperature=0.1),     embed_model="local:BAAI/bge-small-en-v1.5",     save_dir="merging_index_0",     chunk_sizes=[2048,512], )  auto_merging_index_1 = build_automerging_index(     documents,     llm=OpenAI(model="gpt-3.5-turbo", temperature=0.1),     embed_model="local:BAAI/bge-small-en-v1.5",     save_dir="merging_index_1",     chunk_sizes=[2048,512,128], ) 
  • 自动合并检索:不用担心文本片段太小,系统会自动合并相关片段!

  • 重排序机制:通过similarity_top_krerank_top_n参数,我们可以微调检索的精度和效率。

auto_merging_engine_0 = get_automerging_query_engine(     auto_merging_index_0,     similarity_top_k=12,     rerank_top_n=6, ) 
  • 集成评估:使用TruLens进行系统评估,让你的系统性能一目了然!

tru_recorder = get_prebuilt_trulens_recorder(     auto_merging_engine_0,     app_id ='app_0' )  run_evals(eval_questions, tru_recorder, auto_merging_engine_0) 

🚀 实战小贴士:

  1. 根据你的文档类型调整chunk_sizes。长文档可能需要更多层级。
  2. 实验不同的similarity_top_krerank_top_n值,找到最佳平衡点。
  3. 使用TruLens仪表板比较不同配置的性能,持续优化你的系统。

🎉 总结:
这个框架不仅强大,而且高度可定制。无论你是处理科技文档、法律合同还是文学作品,它都能帮你构建一个智能、高效的问答系统。

为什么要创建不同层次索引?

  1. 创建不同层次索引的原因:

     

    a) 性能优化:不同的文档类型和查询需求可能需要不同的索引结构。
    b) 平衡精度和效率:层次越多,索引越精细,但计算开销也越大。
    c) 适应性研究:了解哪种索引结构最适合特定的文档集和查询类型。

  2. 原理:

     

    a) 粒度控制:不同层次提供不同粒度的文本块。
    b) 上下文保留:多层结构有助于在检索时保留更多上下文信息。
    c) 检索灵活性:可以根据查询复杂度选择合适层次的文本块。

  3. 举例说明:

假设我们有一本关于世界历史的教科书。

两层索引 [2048, 512]:

  • 第一层(2048字符)可能对应一个小节。
  • 第二层(512字符)可能对应一个段落。

三层索引 [2048, 512, 128]:

  • 第一层(2048字符)对应一个小节。
  • 第二层(512字符)对应一个段落。
  • 第三层(128字符)可能对应1-2个句子。

查询示例:

  1. "简述第二次世界大战的主要原因"

    • 两层索引可能足够,因为这是一个宽泛的问题,可能需要整个段落的信息。
  2. "希特勒在哪一年入侵波兰?"

    • 三层索引可能更有效,因为这个具体的事实可能只需要1-2个句子就能回答。

通过比较不同层次索引的性能,我们可以找到最适合这本历史教科书的索引结构。

现在,解释这行代码:

auto_merging_engine_0 = get_automerging_query_engine(     auto_merging_index_0,      similarity_top_k=12,      rerank_top_n=6 ) 

这行代码创建了一个查询引擎,具体解释如下:

  1. auto_merging_index_0:这是之前创建的自动合并索引。

  2. similarity_top_k=12:这个参数指定初始检索时要返回的最相似文本块的数量。在这里,系统会首先检索12个最相似的文本块。

  3. rerank_top_n=6:这个参数指定在重新排序后要保留的top相关文本块的数量。系统会对初始检索的12个文本块进行重新排序,然后只保留最相关的6个。

  4. get_automerging_query_engine:这个函数创建了一个查询引擎,它使用自动合并检索器和重排序后处理器。

工作流程:

  1. 当收到查询时,引擎首先检索12个最相似的文本块。
  2. 然后,它可能会合并一些相关的文本块(这是AutoMergingRetriever的功能)。
  3. 接着,它会对这些文本块进行重新排序。
  4. 最后,它只保留排序后的前6个文本块用于生成回答。

这种方法的优点是它在广泛搜索(12个初始结果)和精确答案(6个最终结果)之间取得了平衡,有助于提高回答的相关性和准确性。

节点解析器Nodeparser详解

节点解析器(Node Parser)的概念、作用和工作原理:

  1. 节点解析器的概念:
    节点解析器是一个工具,用于将大型文档分割成更小、更易管理的部分,这些部分被称为"节点"。在LlamaIndex中,节点是文档的基本单位,包含文本内容和元数据。

  2. 节点解析器的作用:

    • 文档分割: 将大文档切分成小块,便于后续处理和检索。
    • 层次化结构: 创建文档的层次结构,保留文档的原始结构和上下文关系。
    • 元数据添加: 为每个节点添加相关的元数据,如位置信息、标题等。
  3. HierarchicalNodeParser的工作原理:

    • 多层次分割: 按照指定的chunk_sizes列表,从大到小依次分割文档。
    • 创建层次结构: 较大的块成为父节点,较小的块成为子节点,形成树状结构。
    • 保留上下文: 每个子节点都知道其父节点,有助于在检索时提供更多上下文
  4. node_parser = HierarchicalNodeParser.from_defaults(     chunk_sizes=[2048, 512, 128] ) nodes = node_parser.get_nodes_from_documents([document]) 
    • 创建了一个三层的解析器,chunk_sizes分别是2048, 512, 和128个字符。
    • 首先,文档被分割成2048字符的大块。
    • 然后,这些大块被进一步分割成512字符的中等块。
    • 最后,中等块被分割成128字符的小块。
    • 这样形成了一个三层的树状结构。
  5. 为什么这样做很有用:

    • 灵活检索: 可以根据查询的具体需求,选择合适大小的文本块进行检索。
    • 上下文保留: 即使检索到很小的文本块,也可以通过其父节点快速获取更多上下文。
    • 提高准确性: 多层次的结构有助于在不同粒度上匹配查询,提高检索的准确性。

通过使用HierarchicalNodeParser,我们可以更有效地组织和检索大型文档中的信息,既保留了文档的整体结构,又提供了灵活的检索粒度。


完整代码

 import os  from llama_index import (     ServiceContext,     StorageContext,     VectorStoreIndex,     load_index_from_storage, ) from llama_index.node_parser import HierarchicalNodeParser from llama_index.node_parser import get_leaf_nodes from llama_index import StorageContext, load_index_from_storage from llama_index.retrievers import AutoMergingRetriever from llama_index.indices.postprocessor import SentenceTransformerRerank from llama_index.query_engine import RetrieverQueryEngine   def build_automerging_index(     documents,     llm,     embed_model="local:BAAI/bge-small-en-v1.5",     save_dir="merging_index",     chunk_sizes=None, ):     chunk_sizes = chunk_sizes or [2048, 512, 128]     node_parser = HierarchicalNodeParser.from_defaults(chunk_sizes=chunk_sizes)     nodes = node_parser.get_nodes_from_documents(documents)     leaf_nodes = get_leaf_nodes(nodes)     merging_context = ServiceContext.from_defaults(         llm=llm,         embed_model=embed_model,     )     storage_context = StorageContext.from_defaults()     storage_context.docstore.add_documents(nodes)      if not os.path.exists(save_dir):         automerging_index = VectorStoreIndex(             leaf_nodes, storage_context=storage_context, service_context=merging_context         )         automerging_index.storage_context.persist(persist_dir=save_dir)     else:         automerging_index = load_index_from_storage(             StorageContext.from_defaults(persist_dir=save_dir),             service_context=merging_context,         )     return automerging_index   def get_automerging_query_engine(     automerging_index,     similarity_top_k=12,     rerank_top_n=6, ):     base_retriever = automerging_index.as_retriever(similarity_top_k=similarity_top_k)     retriever = AutoMergingRetriever(         base_retriever, automerging_index.storage_context, verbose=True     )     rerank = SentenceTransformerRerank(         top_n=rerank_top_n, model="BAAI/bge-reranker-base"     )     auto_merging_engine = RetrieverQueryEngine.from_args(         retriever, node_postprocessors=[rerank]     )     return auto_merging_engine import os ​ from llama_index import (     ServiceContext,     StorageContext,     VectorStoreIndex,     load_index_from_storage, ) from llama_index.node_parser import HierarchicalNodeParser from llama_index.node_parser import get_leaf_nodes from llama_index import StorageContext, load_index_from_storage from llama_index.retrievers import AutoMergingRetriever from llama_index.indices.postprocessor import SentenceTransformerRerank from llama_index.query_engine import RetrieverQueryEngine ​ ​ def build_automerging_index(     documents,     llm,     embed_model="local:BAAI/bge-small-en-v1.5",     save_dir="merging_index",     chunk_sizes=None, ):     chunk_sizes = chunk_sizes or [2048, 512, 128]     node_parser = HierarchicalNodeParser.from_defaults(chunk_sizes=chunk_sizes)     nodes = node_parser.get_nodes_from_documents(documents)     leaf_nodes = get_leaf_nodes(nodes)     merging_context = ServiceContext.from_defaults(         llm=llm,         embed_model=embed_model,     )     storage_context = StorageContext.from_defaults()     storage_context.docstore.add_documents(nodes) ​     if not os.path.exists(save_dir):         automerging_index = VectorStoreIndex(             leaf_nodes, storage_context=storage_context, service_context=merging_context         )         automerging_index.storage_context.persist(persist_dir=save_dir)     else:         automerging_index = load_index_from_storage(             StorageContext.from_defaults(persist_dir=save_dir),             service_context=merging_context,         )     return automerging_index ​ ​ def get_automerging_query_engine(     automerging_index,     similarity_top_k=12,     rerank_top_n=6, ):     base_retriever = automerging_index.as_retriever(similarity_top_k=similarity_top_k)     retriever = AutoMergingRetriever(         base_retriever, automerging_index.storage_context, verbose=True     )     rerank = SentenceTransformerRerank(         top_n=rerank_top_n, model="BAAI/bge-reranker-base"     )     auto_merging_engine = RetrieverQueryEngine.from_args(         retriever, node_postprocessors=[rerank]     )     return auto_merging_engine from llama_index.llms import OpenAI ​ index = build_automerging_index(     [document],     llm=OpenAI(model="gpt-3.5-turbo", temperature=0.1),     save_dir="./merging_index", ) ​ query_engine = get_automerging_query_engine(index, similarity_top_k=6) TruLens Evaluation from trulens_eval import Tru ​ Tru().reset_database() Two layers auto_merging_index_0 = build_automerging_index(     documents,     llm=OpenAI(model="gpt-3.5-turbo", temperature=0.1),     embed_model="local:BAAI/bge-small-en-v1.5",     save_dir="merging_index_0",     chunk_sizes=[2048,512], ) auto_merging_engine_0 = get_automerging_query_engine(     auto_merging_index_0,     similarity_top_k=12,     rerank_top_n=6, ) from utils import get_prebuilt_trulens_recorder ​ tru_recorder = get_prebuilt_trulens_recorder(     auto_merging_engine_0,     app_id ='app_0' ) eval_questions = [] with open('generated_questions.text', 'r') as file:     for line in file:         # Remove newline character and convert to integer         item = line.strip()         eval_questions.append(item) def run_evals(eval_questions, tru_recorder, query_engine):     for question in eval_questions:         with tru_recorder as recording:             response = query_engine.query(question) run_evals(eval_questions, tru_recorder, auto_merging_engine_0) from trulens_eval import Tru ​ Tru().get_leaderboard(app_ids=[]) Tru().run_dashboard() Three layers auto_merging_index_1 = build_automerging_index(     documents,     llm=OpenAI(model="gpt-3.5-turbo", temperature=0.1),     embed_model="local:BAAI/bge-small-en-v1.5",     save_dir="merging_index_1",     chunk_sizes=[2048,512,128], ) auto_merging_engine_1 = get_automerging_query_engine(     auto_merging_index_1,     similarity_top_k=12,     rerank_top_n=6, ) ​ tru_recorder = get_prebuilt_trulens_recorder(     auto_merging_engine_1,     app_id ='app_1' ) run_evals(eval_questions, tru_recorder, auto_merging_engine_1) from trulens_eval import Tru ​ Tru().get_leaderboard(app_ids=[]) Tru().run_dashboard() 

🔬 核心原理解析

  1. 多层次索引:
    想象你在整理一本百科全书。你可能会先分章节(2048字符),再分小节(512字符),最后到段落(128字符)。这就是多层次索引的原理!它让系统能够根据问题的具体情况,选择最合适的"阅读范围"。

  2. 自动合并检索:
    这就像是一个超级阅读理解高手。它不仅能找到相关的段落,还能自动将多个段落组合起来,形成一个完整的答案。再也不用担心答非所问啦!

  3. 重排序机制:
    这相当于给检索结果做最后一道筛选。通过精确的语义理解,它能将最相关的信息排在最前面,确保回答的质量。

💡 使用小贴士

  1. 根据你的文档类型调整chunk_sizes。科技文档可能需要更细的划分,而文学作品可能需要保留更多上下文。

  2. 多尝试不同的similarity_top_k和rerank_top_n值。这就像调整显微镜的焦距,找到最清晰的图像!

  3. 充分利用TruLens仪表板。它就像你的AI助手的"体检报告",告诉你哪里表现出色,哪里需要改进。

相关内容

热门资讯

Django函数视图和类视图 函数视图1.全局环境的urls.py引入映入应用的urls,避免后期开发路由过多而导致...
C#中WebView2调用与交... 简要说明:此控件实际上是 [WebView2 COM API] (htt...
AI大模型需要什么样的数据? 数据将是未来AI大模型竞争的关键要素人工智能发展的突破得益于高质量数据的发展。例如,大...
基于SpringBoot+Vu... 基于SpringBoot+Vue的多媒体信息共享平台(带1w+文档)基于Spring...
2024年人工智能顶级会议投稿... 数据挖掘是信息科学领域的重要分支,致力于挖掘和分析庞大数据集中的有价值模式与规律。它融...
终于来了,Runway gen... 最近有好几个学员私信我们,让我出一期Runway完整的使用教程,刚好11月Runway对外发布运动涂...
人工智能搜索引擎 Perple... Perplexity AI是一款革命性的人工智能搜索引擎,结合传统搜索索引与大型语言模...
AI大模型探索之路-实战篇14... 系列篇章💥AI大模型探索之路-实战篇4:深入DB-GPT数据应用开发框...
k8s核心知识总结 写在前面时间一下子到了7月份尾;整个7月份都乱糟糟的,不管怎么样...
C# 组合Cancellati... 前言在异步编程中,经常需要使用CancellationToken来取消任务的执行。 但...