• 如何避免大语言模型绕过知识库乱答的情况?LlamaIndex 原理与应用简介


    本文首发于博客 LLM 应用开发实践

    随着 LangChain + LLM 方案快速普及,知识问答类应用的开发变得容易,但是面对回答准确度要求较高的场景,则暴露出一些局限性,比如向量查询方式得到的内容不匹配,LLM 对意图识别不准。所以 LlamaIndex(也称为 GPT Index)由社区顺势推出,是一个开发者友好的接口,它将外部数据连接到 LLM,提供了一系列工具来简化流程,包括可以与各种现有数据源和格式(如 api、pdf、文档和 SQL 等)集成的数据连接器,极大的改善了上述问题。本文译自 LlamaIndex: How to Use Index Correctly,翻译内容并非原版无对照翻译,有所增减。

    LlamaIndex索引

    列表索引

    它对于综合跨多个数据源的信息的答案非常有用

    列表索引是一种简单的数据结构,其中节点按顺序存储。在索引构建期间,文档文本被分块、转换为节点并存储在列表中。

    在查询期间,如果没有指定其他查询参数,LlamaIndex只是将列表中的所有node加载到Response Synthesis模块中。

    列表索引提供了许多查询列表索引的方法,从基于嵌入的查询中获取前k个邻居,或者添加一个关键字过滤器,如下所示:

    img

    LlamaIndex为列表索引提供Embedding支持。除了每个节点存储文本之外,每个节点还可以选择存储Embedding。在查询期间,我们可以在调用LLM合成答案之前,使用Embeddings对节点进行最大相似度检索。

    索引构建过程中,LlamaIndex不会生成Embedding,而是在查询时生成,这种设计避免了在索引构建期间为所有文本块生成Embeddings,这可能会导致大量数据的开销。

    向量存储索引

    它是最常见且易于使用的,允许对大型数据语料库回答查询

    默认情况下,GPTVectorStoreIndex 使用内存中的 SimpleVectorStore作为默认存储上下文的一部分初始化,基于向量存储的索引在索引构建期间生成Embeddings

    img

    树状索引

    它对总结一组文件很有用

    树状索引是树结构索引,其中每个节点是子节点的摘要。在索引构建期间,树以自下而上的方式构建,直到我们最终得到一组根节点。树状索引从一组节点(成为该树中的叶节点)构建层次树。

    查询树状索引涉及从根节点向下遍历到叶节点。默认情况下(child_branch_factor=1 ),查询在给定父节点的情况下选择一个子节点。如果child_branch_factor=2,则查询在每个级别选择两个子节点。

    img

    为了在查询期间构建树状索引,我们需要将retriver_moderesponse_mode添加到查询引擎,并将GPTTreeIndex中的build_tree参数设置为False

    index_light = GPTTreeIndex.from_documents(documents, build_tree=False)
    query_engine = index_light.as_query_engine(
        retriever_mode="all_leaf",
        response_mode='tree_summarize',
    )
    query_engine.query("What is net operating income?")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    关键词表索引

    他对于将查询路由到不同的数据源非常有用

    关键字表索引从每个Node提取关键字,并构建从每个关键字到该关键字对应的Node的映射。

    在查询时,从查询中提取相关关键字,并将其与预提取的Node关键字进行匹配,获取相应的Node。提取的节点被传递到响应合成模块。

    img

    • GPTKeywordTableIndex- 使用LLM从每个文档中提取关键字,这意味着它确实需要在构建期间调用LLM

    • GPTSimpleKeywordTableIndex- 使用regex关键字提取器从每个文档中提取关键字,在构建期间不会调用LLM

    可组合性图索引

    它对于构建知识图谱很有用

    使用LlamaIndex,您可以通过在现有索引之上构建索引来创建复合索引。该特性使您能够有效地索引完整的文档层次结构,并为GPT提供量身定制的知识。

    通过利用可组合性,可以在多个级别定义索引,例如为单个文档定义低级索引,为文档组定义高级索引。考虑下面的例子:

    • 可以为每个文档中的文本创建树索引。
    • 生成一个列表索引,涵盖所有的树索引为整个文档集合。

    通过一个场景来演示可组合性图索引的能力:

    • 从多个文档创建树索引
    • 从树索引生成摘要
    • 接下来在3个树索引的顶部创建一个列表索引的图,因为列表索引适合于合成跨多个数据源组合信息的答案
    • 最后查询图
    from llama_index.indices.composability import ComposableGraph
    
    from langchain.chat_models import ChatOpenAI
    from llama_index import LLMPredictor
    
    # setting up vector indicies for each year
    service_context = ServiceContext.from_defaults(chunk_size_limit=512)
    index_set = {}
    for year in years:
        storage_context = StorageContext.from_defaults()
        cur_index = GPTVectorStoreIndex.from_documents(
            documents=doc_set[year],
            service_context=service_context,
            storage_context=storage_context
        )
        index_set[year] = cur_index
        # store index in the local env, so you don't need to do it over again
        storage_context.persist(f'./storage_index/apple-10k/{year}')
    
    # define an LLMPredictor set number of output tokens
    llm_predictor = LLMPredictor(llm=ChatOpenAI(temperature=0, max_tokens=512, model_name='gpt-3.5-turbo'))
    service_context = ServiceContext.from_defaults(llm_predictor=llm_predictor)
    storage_context = StorageContext.from_defaults()\
    
    ## define a list index over the vector indicies 
    ## allow us to synthesize information across  each index
    index_summary = [index_set[year].as_query_engine().query("Summary this document in 100 words").response for year in years]
    graph = ComposableGraph.from_indices(
        GPTListIndex,
        [index_set[y] for y in years],
        index_summaries=index_summary,
        service_context=service_context,
        storage_context=storage_context
    )
    
    root_id = graph.root_id
    
    #save to disk
    storage_context.persist(f'./storage_index/apple-10k/root')
    
    ## querying graph
    custom_query_engines = {
        index_set[year].index_id: index_set[year].as_query_engine() for year in years
    }
    
    query_engine = graph.as_query_engine(
        custom_query_engines=custom_query_engines
    )
    
    response = query_engine.query("Outline the financial statement of Q2 2023")
    response.response
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    Pandas索引和SQL索引

    它对结构化数据很有用

    from llama_index.indices.struct_store import GPTPandasIndex
    import pandas as pd
    
    df = pd.read_csv("titanic_train.csv")
    
    index = GPTPandasIndex(df=df)
    
    query_engine = index.as_query_engine(
        verbose=True
    )
    response = query_engine.query(
        "What is the correlation between survival and age?",
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    文档摘要索引

    这是一个全新的LlamaIndex数据结构,它是为了问答而制作的。

    通常,大多数用户以以下方式开发基于LLM的QA系统:

    1. 获取源文档并将其分成文本块。
    2. 然后将文本块存储在矢量数据库中。
    3. 在查询期间,通过使用相似度和/或关键字过滤器进行Embedding来检索文本块。
    4. 执行整合后的响应。

    然而,这种方法存在一些影响检索性能的局限性:

    1. 文本块没有完整的全局上下文,这通常限制了问答过程的有效性。
    2. 需要仔细调优top-k /相似性分数阈值,因为过小的值可能会导致错过相关上下文,而过大的值可能会增加不相关上下文的成本和延迟。
    3. Embeddings可能并不总是为一个问题选择最合适的上下文,因为这个过程本质上是分别决定文本和上下文的。

    为了增强检索结果,一些开发人员添加了关键字过滤器。然而,这种方法有其自身的挑战,例如通过手工或使用NLP关键字提取/主题标记模型为每个文档确定适当的关键字,以及从查询中推断正确的关键字。

    img

    这就是 LlamaIndex 引入文档摘要索引的原因,它可以为每份文档提取非结构化文本摘要并编制索引,从而提高检索性能,超越现有方法。该索引比单一文本块包含更多信息,比关键字标签具有更多语义。它还允许灵活的检索,包括基于 LLM 和嵌入的方法。在构建期间,该索引接收文档并使用 LLM 从每个文档中提取摘要。在查询时,它会根据摘要使用以下方法检索相关文档:

    • 基于 LLM 的检索:获取文档摘要集合并请求 LLM 识别相关文档+相关性得分
    • 基于嵌入的检索:利用摘要嵌入相似性来检索相关文档,并对检索结果的数量施加顶k限制。

    文档摘要索引的检索类为任何选定的文档检索所有节点,而不是在节点级返回相关块。

    知识图谱索引

    它通过在一组文档中提取知识三元组(主语、谓语、宾语)来建立索引。
    在查询时,它既可以只使用知识图谱作为上下文进行查询,也可以利用每个实体的底层文本作为上下文进行查询。通过利用底层文本,我们可以针对文档内容提出更复杂的查询。

    索引成本和索引时间

    • 索引的成本是一个需要考虑的重要因素,在处理海量数据集时,这一点尤为重要。
    • 索引速度,整个解决方案的运行准备时间。索引时间各不相同,但都是一次性的,通常 40 页的 PDF 文件大约需要 5 秒钟。试想一下,如果一个庞大的数据集超过 10 万页,可能需要几天时间,可以利用异步方法来缩短索引时间。

    更多内容在公号

    在这里插入图片描述

  • 相关阅读:
    【Leetcode】152.乘积最大子数组
    电动车企的2023:抱团活下去
    Android Handler之同步屏障机制
    【微服务】SpringBoot监听器机制以及在Nacos中的应用
    多队列网卡与虚拟化
    C++ 命名空间-----namespace(超详细解析,小白必看系列)
    【算法与数据结构】235、LeetCode二叉搜索树的最近公共祖先
    必备技能——Excel如何设置打印剧中
    【附源码】Python计算机毕业设计天润律师事务所管理系统
    GFS分布式文件系统
  • 原文地址:https://blog.csdn.net/weixin_43829633/article/details/133810166