Chainlit集成LlamaIndex实现知识库高级检索(BM25全文检索器)

检索原理

BM25Retriever类是一个基于BM25算法设计的检索器,它主要用于从一组文档或节点中检索出与查询最相关的文档或节点。这个类的设计目的是为了提高文本检索的效率和准确性,尤其是在处理大量文本数据时。

BM25(Best Matching 25)算法是一种在信息检索领域广泛应用的经典算法,它是对传统的TF-IDF(Term Frequency-Inverse Document Frequency)算法的一种改进。BM25算法的核心思想是利用词频(TF)和逆文档频率(IDF)来衡量文档与查询之间的相关性,同时考虑到文档长度信息对相关性的影响。以下是对BM25算法的一些关键特性和原理的介绍:

原理

BM25算法基于这样一个假设:对于一个特定的查询项,它在相关文档中出现的频率应该高于在非相关文档中的频率。该算法通过结合词项频率(TF)和文档频率(DF)来计算文档的得分。具体来说,BM25算法包括以下几个组成部分:

  1. 词项频率(TF):词项频率是指一个词项在文档中出现的次数。BM25对传统的TF计算方法进行了调整,引入了饱和度和长度归一化,以防止长文档由于包含更多词项而获得不公平的高评分。

  2. 逆文档频率(IDF):逆文档频率是衡量词项稀有程度的一个指标。它基于整个文档集合来计算,用来降低常见词项的权重,并提升罕见词项的权重。

  3. 文档长度信息:BM25算法引入了文档长度信息,以进一步调整相关性的计算。这样可以避免因为文档长度不同而导致的相关性偏差。

计算公式

BM25算法的计算公式可以表述为:

其中:

  • ( tf_{t,d} ) 是词项 ( t ) 在文档 ( d ) 中的词频;
  • ( IDF(t) ) 是词项 ( t ) 的逆文档频率;
  • ( k_1 ) 和 ( b ) 是自由参数,用于调节计算过程中的影响;
  • ( |d| ) 是文档 ( d ) 的长度;
  • ( avgdl ) 是文档集合中所有文档长度的平均值。

改进与变种

除了标准的BM25算法之外,还有几种重要的变种,如BM25FBM25L

  • BM25F:这是BM25的一个重要扩展,可以在多个文档域上进行计算。
  • BM25L:该变种考虑了文档长度对得分的影响,通过引入文档长度规范化项来平衡不同长度的文档。

应用场景

BM25算法因其在处理词频和相关性之间非线性关系上的优势,被广泛应用于搜索引擎和相关领域。在实际应用中,如Elasticsearch和Lucene这样的全文搜索引擎,默认使用的就是Okapi BM25算法。

总结来说,BM25算法是一种强大而灵活的信息检索算法,它通过对TF-IDF模型的改进,提高了搜索结果的相关性,同时通过引入文档长度因子等改进措施,增强了算法的实用性。

该检索技术的优缺点

LlamaIndex是一个基于语言模型(LLM)的开源信息检索系统,它提供了高效的数据索引和查询功能,适用于大规模文本数据集的快速检索。其中,BM25Retriever是LlamaIndex提供的一个检索器,它基于BM25算法,这是一种广泛使用的信息检索排序函数,专门用于文档检索,尤其擅长处理长文档和短查询。下面将详细探讨BM25Retriever在LlamaIndex中的优缺点。

优点

  1. 优化的TF-IDF: BM25Retriever是基于TF-IDF(词频-逆文档频率)的改进版本,它解决了标准TF-IDF方法的一些局限性,如长文档可能得到过高评分的问题。BM25通过调整参数k1和b来考虑文档长度的影响,从而使得检索结果更加准确。

  2. 文档长度的考虑: BM25Retriever的一个关键特性是它考虑了文档长度对评分的影响。对于长文档,BM25Retriever会调整评分,确保不会因为文档较长而产生不公平的优势,这有助于提高检索结果的相关性。

  3. 灵活性: BM25Retriever可以很容易地与其他检索技术相结合,例如与向量检索混合使用,形成一种称为混合检索的技术。这种组合可以利用各自的优势,提供更加全面的检索服务。

缺点

  1. 中文支持问题: 默认情况下,BM25Retriever的tokenizer可能不支持中文处理,这意味着在处理中文文本时需要额外的步骤或定制的解决方案,如使用jieba分词器来处理中文文本。

  2. 计算资源需求: 尽管BM25Retriever在文档检索方面表现优秀,但它需要进行大量的计算来确定文档与查询的相关性得分,尤其是在大型数据集中,这可能会导致较高的计算资源需求。

  3. 参数调优: BM25算法依赖于一些参数(如k1, b等),这些参数可能需要根据具体的应用场景进行调优才能达到最佳性能。如果不正确地设置这些参数,可能会影响检索结果的质量。

综上所述,BM25Retriever在LlamaIndex中提供了一种强大的检索机制,尤其适用于需要精确排序结果的应用场景。然而,在使用时需要注意其对于特定语言的支持情况以及可能产生的计算开销等问题。在实际应用中,根据具体的业务需求选择合适的检索器,并且合理地调整参数以优化性能是非常重要的。

LlamaIndex官方地址 https://docs.llamaindex.ai/en/stable/

快速上手

创建一个文件,例如"chainlit_chat"

bash 复制代码
mkdir chainlit_chat

进入 chainlit_chat文件夹下,执行命令创建python 虚拟环境空间(需要提前安装好python sdkChainlit 需要python>=3.8。,具体操作,由于文章长度问题就不在叙述,自行百度),命令如下:

bash 复制代码
python -m venv .venv
  • 这一步是避免python第三方库冲突,省事版可以跳过
  • .venv是创建的虚拟空间文件夹可以自定义

接下来激活你创建虚拟空间,命令如下:

bash 复制代码
#linux or mac
source .venv/bin/activate
#windows
.venv\Scripts\activate

在项目根目录下创建requirements.txt,内容如下:

bash 复制代码
chainlit
llama-index-core
llama-index-llms-dashscope
llama-index-embeddings-dashscope
llama-index-retrievers-bm25~=0.3.0

执行以下命令安装依赖:

bash 复制代码
pip install -r .\requirements.txt
  • 安装后,项目根目录下会多出.chainlit.files文件夹和chainlit.md文件

代码创建

只使用通义千问的DashScope模型服务灵积的接口

在项目根目录下创建.env环境变量,配置如下:

bash 复制代码
DASHSCOPE_API_KEY="sk-api_key"
  • DASHSCOPE_API_KEY 是阿里dashscope的服务的APIkey,代码中使用DashScope的sdk实现,所以不需要配置base_url。默认就是阿里的base_url。
  • 阿里模型接口地址 https://dashscope.console.aliyun.com/model

在项目根目录下创建app.py文件,代码如下:

bash 复制代码
import os
import time

import chainlit as cl
from llama_index.core.query_engine import RetrieverQueryEngine
from llama_index.retrievers.bm25 import BM25Retriever
from llama_index.core import (
    Settings,
    VectorStoreIndex,
    SimpleDirectoryReader, load_index_from_storage, StorageContext,
)
from llama_index.core.node_parser import SentenceSplitter
from llama_index.embeddings.dashscope import DashScopeEmbedding, DashScopeTextEmbeddingModels, \
    DashScopeTextEmbeddingType
from llama_index.llms.dashscope import DashScope, DashScopeGenerationModels

Settings.llm = DashScope(
    model_name=DashScopeGenerationModels.QWEN_TURBO, api_key=os.environ["DASHSCOPE_API_KEY"], max_tokens=512
)
Settings.embed_model = DashScopeEmbedding(
    model_name=DashScopeTextEmbeddingModels.TEXT_EMBEDDING_V2,
    text_type=DashScopeTextEmbeddingType.TEXT_TYPE_DOCUMENT,
)
Settings.node_parser = SentenceSplitter(chunk_size=512, chunk_overlap=20)
Settings.num_output = 512
Settings.context_window = 6000


@cl.cache
def get_vector_store_index():
    storage_dir = "./storage_bm25_512"
    if os.path.exists(storage_dir):
        storage_context = StorageContext.from_defaults(persist_dir=storage_dir)
        index = load_index_from_storage(storage_context)
    else:
        documents = SimpleDirectoryReader("./data_file").load_data(show_progress=True)
        node_parser = SentenceSplitter.from_defaults(chunk_size=512, chunk_overlap=20)
        nodes = node_parser.get_nodes_from_documents(documents)
        print(f"nodes: {len(nodes)}")
        index = VectorStoreIndex(nodes=nodes)
        index.storage_context.persist(persist_dir=storage_dir)
    return index


vector_store_index = get_vector_store_index()


@cl.on_chat_start
async def start():
    await cl.Message(
        author="Assistant", content="你好! 我是泰山AI智能助手. 有什么可以帮助你的吗?"
    ).send()


@cl.on_message
async def main(message: cl.Message):
    start_time = time.time()
    retriever = BM25Retriever.from_defaults(docstore=vector_store_index.docstore, similarity_top_k=5)
    query_engine = RetrieverQueryEngine.from_args(
        retriever, streaming=True
    )
    msg = cl.Message(content="", author="Assistant")
    res = await query_engine.aquery(message.content)
    async for token in res.response_gen:
        await msg.stream_token(token)
    print(f"代码执行时间: {time.time() - start_time} 秒")
    source_names = []
    for idx, node_with_score in enumerate(res.source_nodes):
        node = node_with_score.node
        source_name = f"source_{idx}"
        source_names.append(source_name)
        msg.elements.append(
            cl.Text(content=node.get_text(), name=source_name, display="side")
        )
    await msg.stream_token(f"\n\n **数据来源**: {', '.join(source_names)}")
    await msg.send()
  • 代码中的persist_dir=storage_dir 不设置的默认是 ./storage.
  • 代码中chunk_size是将长文档分割的文本块的大小,chunk_overlap 是和上下文本块的重合文本的大小。
  • 代码中 node_parser = HierarchicalNodeParser.from_defaults( node_parser_ids=node_parser_ids, node_parser_map=node_parser_map ) 可以简写为 node_parser = HierarchicalNodeParser.from_defaults() 会按照 [2048,512,128]三种层次分割,经过我测试不使用默认的效果会更好
  • similarity_top_k=5 返回5条最相关的数据

代码解读

这段代码是一个使用ChainLit框架构建的聊天机器人应用,它集成了向量数据库索引和检索功能,以从文档中检索信息并回答用户的问题。下面是代码的逐部分解释:

  1. 导入模块

    • ostime 是标准库模块,分别用于操作系统相关功能和计时功能。
    • chainlit as cl 用于创建交互式的Web应用程序。
    • llama_index 是一个用于构建索引、检索器和查询引擎的库,帮助处理文本数据并进行问答系统开发。
  2. 设置环境变量

    • 设置了DashScope的LLM(Language Model)和Embedding模型,并指定了使用的模型类型以及API密钥位置。这些设置是根据环境变量DASHSCOPE_API_KEY来获取的。
  3. 向量存储索引函数 (get_vector_store_index)

    • 这个函数首先检查是否存在一个持久化的存储目录。如果存在,则加载已有的索引;如果不存在,则读取指定目录下的文档,将其分割成节点,创建向量存储索引,并将其持久化到指定目录。
  4. 聊天开始时的事件处理器 (start)

    • 当聊天会话开始时,发送一条消息给用户作为问候。
  5. 接收消息时的事件处理器 (main)

    • 当收到用户的输入消息时,使用BM25检索器来检索最相关的文档,并通过RetrieverQueryEngine生成答案。
    • 使用异步生成器流式传输响应给用户,并记录每个源文档的信息,以便显示数据来源。

这段代码展示了一个基于文档检索的问答系统的实现方式,利用了现代语言模型的能力来生成高质量的回答。注意,在实际部署时需要确保环境变量DASHSCOPE_API_KEY已经正确设置,并且指定的数据文件夹路径是正确的。此外,还需确保所有依赖项已安装,并且与ChainLit框架兼容。

在项目根目录下创建data_file文件夹

将你的文件放到data_file文件夹下。
llama_index 库支持多种文件格式的加载,以便从中提取文本内容用于索引构建和后续的信息检索或问答任务。以下是一些常见的文件格式支持:

  1. 文本文件 (.txt):简单的纯文本文件。
  2. PDF 文件 (.pdf):便携文档格式,广泛用于书籍、报告等文档。
  3. Microsoft Word 文档 (.doc, .docx):Word 文档格式。
  4. CSV 文件 (.csv):逗号分隔值文件,常用于表格数据。
  5. HTML 文件 (.html, .htm):超文本标记语言文件。
  6. Markdown 文件 (.md, .markdown):轻量级标记语言。
  7. JSON 文件 (.json):JavaScript 对象表示法,常用于数据交换。
  8. EPUB 文件 (.epub):电子书格式。
  9. PPTX 文件 (.pptx):PowerPoint 演示文稿。

除了上述文件格式外,llama_index 可能还支持其他一些格式,具体取决于其内部依赖库的支持情况。例如,它可能通过第三方库支持解析像 .xls, .xlsx 这样的 Excel 文件。

为了加载这些不同类型的文件,llama_index 提供了多个不同的读取器(readers),如 SimpleDirectoryReader 可以用来加载一个目录中的多个文件,而针对特定文件格式(如 PDF 或 Word 文档),则有专门的读取器类。

例如,如果你有一个包含多种文件格式的目录,你可以使用 SimpleDirectoryReader 来加载它们。如果你只处理一种类型的文件,比如 PDF 文件,你可以选择使用更具体的读取器,比如 PDFReader

运行应用程序

要启动 Chainlit 应用程序,请打开终端并导航到包含的目录app.py。然后运行以下命令:

bash 复制代码
 chainlit run app.py -w   
  • -w标志告知 Chainlit 启用自动重新加载,因此您无需在每次更改应用程序时重新启动服务器。您的聊天机器人 UI 现在应该可以通过http://localhost:8000访问。
  • 自定义端口可以追加--port 80

启动后界面如下:



BM25Retriever索引器还可以与向量检索器等其他索引器,利用QueryFusionRetriever类将其融合查询。

后续会出更多关于LlamaIndex高级检查的技术文章教程,感兴趣的朋友可以持续关注我的动态!!!

相关文章推荐

《Chainlit快速实现AI对话应用的界面定制化教程》
《Chainlit接入FastGpt接口快速实现自定义用户聊天界面》
《使用 Xinference 部署本地模型》
《Fastgpt接入Whisper本地模型实现语音输入》
《Fastgpt部署和接入使用重排模型bge-reranker》
《Fastgpt部署接入 M3E和chatglm2-m3e文本向量模型》
《Fastgpt 无法启动或启动后无法正常使用的讨论(启动失败、用户未注册等问题这里)》
《vllm推理服务兼容openai服务API》
《vLLM模型推理引擎参数大全》
《解决vllm推理框架内在开启多显卡时报错问题》
《Ollama 在本地快速部署大型语言模型,可进行定制并创建属于您自己的模型》

相关推荐
网易独家音乐人Mike Zhou3 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
安静读书3 小时前
Python解析视频FPS(帧率)、分辨率信息
python·opencv·音视频
小二·4 小时前
java基础面试题笔记(基础篇)
java·笔记·python
小喵要摸鱼6 小时前
Python 神经网络项目常用语法
python
一念之坤7 小时前
零基础学Python之数据结构 -- 01篇
数据结构·python
wxl7812278 小时前
如何使用本地大模型做数据分析
python·数据挖掘·数据分析·代码解释器
NoneCoder8 小时前
Python入门(12)--数据处理
开发语言·python
LKID体9 小时前
Python操作neo4j库py2neo使用(一)
python·oracle·neo4j
小尤笔记9 小时前
利用Python编写简单登录系统
开发语言·python·数据分析·python基础
FreedomLeo19 小时前
Python数据分析NumPy和pandas(四十、Python 中的建模库statsmodels 和 scikit-learn)
python·机器学习·数据分析·scikit-learn·statsmodels·numpy和pandas