Skip to content

rag

3 posts with the tag “rag”

RAG:知识库分段

在构建知识库时,分段(Chunking)是一个至关重要的步骤,尤其是在与大型语言模型(LLM)结合使用,例如在检索增强生成(RAG)系统中。分段的目的是将长文本分割成较小的、可管理的块,以便:

  1. 适应LLM的上下文窗口限制: LLM有输入Token数量的限制,过长的文本需要被切分。
  2. 提高检索相关性: 较小的、语义连贯的块更容易在向量搜索中被精确匹配到。
  3. 减少计算成本: 处理更小的块可以降低嵌入(embedding)和检索的计算量。
  4. 降低“幻觉”风险: 提供更精确、上下文相关的知识块有助于LLM生成更准确的回答。

以下是常见的知识库分段方式,以及它们各自的优缺点和适用场景:

1. 基于固定大小的分段 (Fixed-Size Chunking)

Section titled “1. 基于固定大小的分段 (Fixed-Size Chunking)”

这是最简单、最直接的分段方式。

  • 方式: 将文档按照固定的字符数或Token数进行切分,通常会设置一个重叠量(overlap)。
    • 示例: 每500个字符切成一个块,重叠100个字符。
  • 优点:
    • 实现简单,处理速度快。
    • 适用于任何文本类型。
  • 缺点:
    • 破坏语义完整性: 可能会在一个句子的中间或段落的中间进行切分,导致上下文缺失或语义中断。
    • 检索到的块可能不包含完整的概念,降低RAG的效果。
  • 适用场景:
    • 对语义完整性要求不高的简单文本。
    • 初步处理或需要快速处理大量文档时。

2. 基于标点符号或结构的分段 (Delimiter-Based / Structural Chunking)

Section titled “2. 基于标点符号或结构的分段 (Delimiter-Based / Structural Chunking)”

这种方式尝试在文本的自然断点处进行切分,以保留更多的语义完整性。

  • 方式:
    • 句子级分段: 将文档按句子切分(例如,遇到句号、问号、叹号等)。
    • 段落级分段: 将文档按段落切分(例如,遇到连续的换行符)。
    • 标题/章节级分段: 根据文档的标题、副标题、章节等结构信息进行切分。这通常需要解析文档的格式(如Markdown、HTML、PDF结构等)。
  • 优点:
    • 语义完整性较好: 尽量保持句子或段落的完整性,减少语义中断。
    • 实现相对简单,处理效率较高。
  • 缺点:
    • 无法处理长句子/长段落: 如果一个句子或段落太长,仍然可能超出LLM的上下文限制。
    • 依赖文档结构: 对于结构不明确或混杂的文档,效果可能不佳。
  • 适用场景:
    • 大多数散文体文档(文章、报告、博客)。
    • 有清晰标题和章节结构的文档。

3. 递归字符文本拆分器 (Recursive Character Text Splitter)

Section titled “3. 递归字符文本拆分器 (Recursive Character Text Splitter)”

这是 LangChain 等框架中推荐的通用文本分段策略,结合了固定大小和结构化切分的思想。

  • 方式: 尝试按一系列分隔符(如 "\n\n""\n"" """)递归地切分文本。它会优先使用更高级别(通常表示更大语义单元)的分隔符进行切分,如果切分后的块仍然过大,则继续使用下一个分隔符进行切分,直到所有块都小于指定大小,并通常带有重叠。
  • 优点:
    • 兼顾语义和长度: 尽可能保持语义完整性的同时,确保块大小符合要求。
    • 通用性强,适用于多种文本类型。
    • 提供重叠机制,有助于保留上下文信息。
  • 缺点:
    • 对于高度非结构化或语义关联性复杂的文本,可能仍有局限。
  • 适用场景:
    • 目前最常用且推荐的通用分段策略,适用于绝大多数知识库场景。

4. 父子分段 (Parent-Child Chunking / Small-to-Large Chunking)

Section titled “4. 父子分段 (Parent-Child Chunking / Small-to-Large Chunking)”

这是一种更高级的策略,旨在平衡检索精度和上下文完整性。

  • 方式:
    • 小块检索: 创建较小的、语义独立的子块用于向量搜索(例如,单个句子或短段落)。
    • 大块提供上下文: 为每个小块关联一个更大的“父块”(可能是原始文档的整个段落、章节,甚至整个文档),当小块被检索到时,将对应的父块提供给LLM作为上下文。
  • 优点:
    • 高检索精度: 小块有助于精确匹配用户查询。
    • 丰富上下文: 大块确保LLM获得足够的背景信息,生成更准确、连贯的回答。
    • 有效解决“金句问题”:当查询只需要文档中的某一句时,直接提供该句及其上下文,而不是整个大文档。
  • 缺点:
    • 实现复杂度较高。
    • 存储和处理成本增加(需要存储两套块:子块的嵌入和父块的文本)。
  • 适用场景:
    • 需要高精度检索和丰富上下文的复杂文档(如研究论文、法律文件、技术手册)。
    • 对RAG系统性能要求较高的场景。

这是一种利用大模型或嵌入模型的能力来智能切分的策略。

  • 方式:
    • 将文本切分成句子或小段落。
    • 对每个小单元生成嵌入向量。
    • 计算相邻单元之间嵌入向量的相似度(例如,余弦相似度)。
    • 在相似度低于某个阈值的地方(表示语义发生了较大转变)进行切分。
    • 有时会结合LLM来判断最佳切分点。
  • 优点:
    • 高度保留语义完整性: 确保每个块都包含一个相对完整的、连贯的概念或主题。
    • 更智能的切分: 不依赖于固定的规则,而是根据文本的实际含义进行调整。
  • 缺点:
    • 实现复杂,计算成本较高(需要生成大量嵌入并进行相似度计算)。
    • 依赖于嵌入模型和可能的LLM的性能。
  • 适用场景:
    • 对语义连贯性和RAG系统效果要求极高的场景。
    • 处理复杂、多主题或非结构化文本时效果更佳。

没有“最好”的方式,只有“最适合”的方式。选择哪种分段策略取决于以下几个因素:

  • 您的数据类型和结构: 文档是结构化的(如手册、报告)还是非结构化的(如聊天记录、邮件)?
  • LLM的上下文窗口大小: 您的LLM能处理多长的文本?
  • 性能要求: 您对检索的精度和生成答案的质量有什么要求?
  • 计算资源和成本: 您愿意投入多少计算资源进行分段和存储?
  • 实现复杂度: 您有多少时间和精力投入到分段策略的实现上?

一般推荐:

  • 通用场景(推荐首选): 递归字符文本拆分器 (Recursive Character Text Splitter) 是一个非常好的起点。它在简单性和效果之间取得了很好的平衡,并且易于在 LangChain 等框架中实现。
  • 追求更高精度和上下文完整性: 考虑 父子分段。它能够有效解决“金句问题”,并为LLM提供更丰富的上下文。
  • 处理复杂语义或对效果有极致要求: 探索 语义分段。虽然计算成本高,但它在保留语义连贯性方面表现最佳。
  • 对于极度简单的文本或初步测试: 固定大小或基于标点符号的分段可以作为快速解决方案。

在实际操作中,通常会尝试不同的分段策略和参数(如块大小、重叠量),并通过**评估RAG系统的性能(例如,检索召回率、答案的准确性和连贯性)**来找到最适合您知识库的最佳分段方案。

使用 LlamaIndex 和 Milvus 检索增强生成 (RAG)

这里将要介绍使用本地部署的LLM,如何使用LlamaIndex构建RAG系统。

rag

主要流程:

workflow

  • RAG 是一种基于检索增强生成(Retrieval-Augmented Generation)的技术,它通过检索相关文档来增强生成模型的输出。RAG可以用于各种任务,包括问答、摘要、翻译等。

  • LlamaIndex 是一个简单、灵活的数据框架,用于将自定义数据源连接到大型语言模型(LLMs)。它支持多种数据源,包括文本文件、数据库、API等,并且可以轻松地扩展到新的数据源。它还提供了丰富的API,使得构建RAG系统变得非常简单。

  • Ollama 是一个开源的LLM部署工具,允许用户在本地电脑上运行各种大型语言模型(LLMs)。它的主要特点包括:

    1. 本地化运行 - 让用户可以在自己的电脑上离线运行AI模型,而不需要连接到云服务
    2. 支持多种模型 - 包括Llama 2、Mistral、Vicuna等多种开源语言模型
    3. 简单易用 - 提供了简单的命令行界面和API接口
    4. 资源效率 - 针对桌面环境进行了优化,减少了资源消耗
    5. 隐私保护 - 因为模型在本地运行,所以数据不会发送到外部服务器
  • Milvus 是一个开源的向量数据库,可以用于存储和检索高维向量数据。它支持多种向量存储引擎,包括FAISS、Pinecone、Qdrant等,并且可以轻松地扩展到新的存储引擎。它还提供了丰富的API,使得构建RAG系统变得非常简单。

这里使用Ollama本地部署模型,生产环境可以考虑使用云服务,或者[vLLM](https://github.com/vllm-project/vllm)进行部署。

linux 环境下:

Terminal window
curl -fsSL https://ollama.com/install.sh | sh

Mac 和 Windows 环境下, 可以在官网下载

Milvus 在 Milvus 资源库中提供了 Docker Compose 配置文件。要使用 Docker Compose 安装 Milvus,只需运行

Terminal window
Download the configuration file
wget https://github.com/milvus-io/milvus/releases/download/v2.5.9/milvus-standalone-docker-compose.yml -O docker-compose.yml
Start Milvus
sudo docker compose up -d
Creating milvus-etcd ... done
Creating milvus-minio ... done
Creating milvus-standalone ... done

使用以下命令检查容器是否启动并运行:

Terminal window
docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
milvus-etcd quay.io/coreos/etcd:v3.5.18 "etcd -advertise-cli…" etcd 20 minutes ago Up 20 minutes (healthy) 2379-2380/tcp
milvus-minio minio/minio:RELEASE.2023-03-20T20-16-18Z "/usr/bin/docker-ent…" minio 20 minutes ago Up 20 minutes (healthy) 0.0.0.0:9000-9001->9000-9001/tcp, :::9000-9001->9000-9001/tcp
milvus-standalone milvusdb/milvus:v2.5.6 "/tini -- milvus run…" standalone 20 minutes ago Up 20 minutes (healthy) 0.0.0.0:9091->9091/tcp, :::9091->9091/tcp, 0.0.0.0:19530->19530/tcp, :::19530->19530/tcp

uv 使用 Rust 开发的 Python 包和项目管理器。

uv

mac OS 和 linux :

Terminal window
curl -LsSf https://astral.sh/uv/install.sh | sh

uv 管理项目依赖和环境,支持lockfiles, workspaces 等, 类似于 rye 和 poetry。

Terminal window
uv init
uv venv --python=3.10 # 创建项目虚拟环境
source .venv/bin/activate # 激活项目虚拟环境

代码中需要依赖 pymiluvs , llamaindexollma,使用下面的命令安装依赖:

Terminal window
uv add pymilvus
Terminal window
uv add llama-index-vector-stores-milvus
Terminal window
uv add llama-index
Terminal window
uv add llama-index-llms-ollama
Terminal window
uv add llama-index-embeddings-ollama

这里使用的数据是,保存在Github上Paul Graham的一篇文章。

Terminal window
>mkdir -p 'data/paul_graham/'
>wget 'https://raw.githubusercontent.com/run-llama/llama_index/main/docs/docs/examples/data/paul_graham/paul_graham_essay.txt' -O 'data/paul_graham/paul_graham_essay.txt'
from llama_index.core import SimpleDirectoryReader
# load documents
documents = SimpleDirectoryReader(
input_files=["./data/paul_graham_essay.txt"]
).load_data()
print("Document ID:", documents[0].doc_id)

out:

Document ID: 41314907-75fa-4bba-b6d9-6eb36e6add24

这里使用的是的嵌入模型是:modelscope.cn/Embedding-GGUF/gte-Qwen2-1.5B-instruct-Q4_K_M-GGUF:latest , 数据保存到 Milvus 中:

from llama_index.embeddings.ollama import OllamaEmbedding
from llama_index.embeddings.ollama import OllamaEmbedding
settings.embed_model = OllamaEmbedding(
model_name="modelscope.cn/Embedding-GGUF/gte-Qwen2-1.5B-instruct-Q4_K_M-GGUF:latest",
base_url="http://127.0.0.1:11434",
)

连接上本地部署的 Milvus 服务:

from llama_index.vector_stores.milvus import MilvusVectorStore
vector_stores = MilvusVectorStore(
collection_name="t_doc",
dim=1536,
uri="http://127.0.0.1:19530",
overwrite=False,
similarity_metric="COSINE",
)
storage_context = StorageContext.from_defaults(vector_store=vector_stores)
index = VectorStoreIndex.from_documents(
docs,
storage_context=storage_context,
show_progress=True,
)

现在我们有了文档,可以创建索引并插入文档。

其中:

  • collection_name: Milvus 集合名称
  • dim: 嵌入向量维度
  • uri: Milvus 服务地址
  • overwrite: 是否覆盖已存在的集合
  • similarity_metric: 相似度度量方法

现在我们已经将文档存储到了索引中,可以针对索引提出问题。

使用的同样的嵌入模型, 从 Milvus 中加载向量,然后创建索引,最后查询数据。

embed_model = OllamaEmbedding(
base_url="http://127.0.0.1:11434",
model_name="modelscope.cn/Embedding-GGUF/gte-Qwen2-1.5B-instruct-Q4_K_M-GGUF:latest",
)
Settings.llm = Ollama(
base_url="http://127.0.0.1:11434",
model="qwen2.5:7b-instruct-q4_K_M",
request_timeout=30,
)
vector_stores = MilvusVectorStore(
collection_name="t_doc",
dim=1536,
uri="http://127.0.0.1:19530",
overwrite=False,
similarity_metric="COSINE",
)
index = VectorStoreIndex.from_vector_store(
vector_store=vector_stores,
embed_model=embed_model,
)
query_engine = index.as_query_engine()
response = query_engine.query("What is AI?")
print(response)
❯ uv run main.py
2025-04-19 15:00:40,435 [DEBUG][_create_connection]: Created new connection using: 3d9484d069f84709bd3befbc643cc588 (async_milvus_client.py:600)
INFO:httpx:HTTP Request: POST http://127.0.0.1:11434/api/embeddings "HTTP/1.1 200 OK"
INFO:httpx:HTTP Request: POST http://127.0.0.1:11434/api/chat "HTTP/1.1 200 OK"
AI, or Artificial Intelligence, in the early 1980s as described, involved creating programs that could understand natural language to a certain extent. The speaker believed at first that AI was about teaching programs like SHRDLU more words and expanding their formal representations of concepts. However, he later realized that this approach had significant limitations because there was an unbridgeable gap between the subset of natural language these early programs could handle and true human-like understanding. He came to see AI as a field with potential but one that was fundamentally flawed in its initial approaches, particularly those relying on explicit data structures to represent concepts without achieving actual intelligence.

在博客原文中的相关SHRDLU:

Terminal window
rg SHRDLU
data/paul_graham/paul_graham_essay.txt
25:AI was in the air in the mid 1980s, but there were two things especially that made me want to work on it: a novel by Heinlein called The Moon is a Harsh Mistress, which featured an intelligent computer called Mike, and a PBS documentary that showed Terry Winograd using SHRDLU. I haven't tried rereading The Moon is a Harsh Mistress, so I don't know how well it has aged, but when I read it I was drawn entirely into its world. It seemed only a matter of time before we'd have Mike, and when I saw Winograd using SHRDLU, it seemed like that time would be a few years at most. All you had to do was teach SHRDLU more words.
29:For my undergraduate thesis, I reverse-engineered SHRDLU. My God did I love working on that program. It was a pleasing bit of code, but what made it even more exciting was my belief — hard to imagine now, but not unique in 1985 — that it was already climbing the lower slopes of intelligence.
33:I applied to 3 grad schools: MIT and Yale, which were renowned for AI at the time, and Harvard, which I'd visited because Rich Draves went there, and was also home to Bill Woods, who'd invented the type of parser I used in my SHRDLU clone. Only Harvard accepted me, so that was where I went.
37:What these programs really showed was that there's a subset of natural language that's a formal language. But a very proper subset. It was clear that there was an unbridgeable gap between what they could do and actually understanding natural language. It was not, in fact, simply a matter of teaching SHRDLU more words. That whole way of doing AI, with explicit data structures representing concepts, was not going to work. Its brokenness did, as so often happens, generate a lot of opportunities to write papers about various band-aids that could be applied to it, but it was never going to get us Mike.

当使用 QueryEngine 时,LlamaIndex 内部会使用 Prompt 来指导 LLM 如何利用检索到的信息来回答用户的查询。

自定义流程中: 可以获取检索结果,然后自己构建 Prompt 调用 LLM 进行进一步处理。

使用 Retriever 获取 Top-K,然后自定义 Prompt 进行 LLM 验证

Section titled “使用 Retriever 获取 Top-K,然后自定义 Prompt 进行 LLM 验证”

这种方法更灵活,控制力更强,专注于“识别关联”而非“生成答案”。

  1. 使用 Retriever 获取节点:
retriever = index.as_retriever(similarity_top_k=5) # 获取最相似的 5 个
question_text = "What is AI"
retrieved_nodes = retriever.retrieve(question_text)
  1. 构建自定义 Prompt 调用 LLM:

目标: 让 LLM 从检索到的 retrieved_nodes 中选出答案。 Prompt 示例:

from llama_index.llms.ollama import Ollama
llm = Ollama(
base_url="http://127.0.0.1:11434",
model="qwen2.5:7b-instruct-q4_K_M",
request_timeout=30,
)
retriever = index.as_retriever(similarity_top_k=5) # 获取最相似的 5 个
question_text = "What is AI"
retrieved_nodes = retriever.retrieve(question_text)
context_str = "\n\n".join([f"{node.text}" for node in retrieved_nodes])
prompt_template = f"""
"{question_text}"
Context:
---
{context_str}
---
"""
response = Settings.llm.complete(prompt_template)
print(response.text)

RAG vs 模型微调

RAG (Retrieval-Augmented Generation),中文可以理解为检索增强生成,是一种通过从外部知识库检索信息来增强大型语言模型(LLM)能力的AI框架。


简单来说,传统的 LLM 是基于其训练数据来生成文本的,而 RAG 则在此基础上增加了一个步骤:当用户提出问题或指令时,RAG会先从外部的信息源(比如网页、数据库、文档等)中搜索相关的知识,然后将这些检索到的信息与用户的问题一起提供给 LLM,让 LLM 在生成答案时能够参考这些外部信息


RAG的主要步骤可以概括为:


  1. 检索 (Retrieval): 接收用户的查询,并从外部知识库中找到与之相关的信息。这通常涉及到信息检索技术,例如关键词搜索、向量搜索等。
  2. 增强 (Augmentation): 将检索到的相关信息与用户的原始查询结合起来,形成一个更丰富的输入(通常称为提示,Prompt),提供给 LLM。
  3. 生成 (Generation): LLM 接收到增强后的输入,并基于这些信息生成最终的回答或文本。

使用RAG的好处包括:


  1. 获取最新的信息: LLM的训练数据通常是静态的,而RAG可以实时地从外部获取最新的信息,从而生成更准确、及时的回答。
  2. 减少“幻觉”: 通过引用外部的可靠信息来源,RAG可以降低LLM生成不真实或不相关信息的风险。
  3. 提高透明度: 在某些情况下,RAG可以提供生成答案所依据的外部信息来源,帮助用户验证答案的可靠性。
  4. 降低计算成本: 相比于重新训练整个LLM来使其掌握新知识,RAG的成本通常更低。
  5. 适应特定领域: RAG可以连接到特定的知识库,例如企业内部文档或专业领域的数据库,从而使LLM在这些特定领域内提供更专业的回答。

RAG的一些应用场景包括:


  1. 智能客服: 能够根据最新的产品信息、FAQ等知识库回答用户的问题。
  2. 问答系统: 能够从大量的文档或数据中检索并生成针对特定问题的答案。
  3. 内容创作: 能够基于最新的研究报告或新闻资讯生成文章或报告。

总而言之,RAG通过将LLM的生成能力与信息检索系统的知识获取能力相结合,使得AI能够生成更准确、更可靠、更具上下文相关性的文本。

模型微调 (Fine-tuning) 则是指在预训练好的大型语言模型的基础上,使用一个较小但更 específico 的数据集进行额外的训练,以使模型更好地适应特定的任务或领域。这个过程会调整模型内部的权重和参数,使其学习到目标任务的特定模式和知识。


模型微调相较于RAG,在难度和不利方面主要体现在以下几点:


难度:


  1. 数据准备和标注: 模型微调通常需要一个高质量、与目标任务高度相关的标注数据集。收集、清洗和标注这些数据可能非常耗时、昂贵且困难,尤其是在某些专业领域。RAG主要依赖于构建和维护一个可搜索的知识库,对数据的标注要求通常较低。
  2. 专业知识: 微调需要对自然语言处理(NLP)、深度学习以及模型架构有一定的理解,才能选择合适的微调策略、调整超参数并评估模型性能。RAG的实现则更侧重于信息检索系统的构建和与LLM的集成。
  3. 实验和调优: 微调过程可能需要多次实验才能找到最佳的模型配置和超参数,以避免过拟合或欠拟合,并获得理想的性能。RAG的调优主要集中在检索策略和生成提示的优化上。
  4. 计算资源: 对大型LLM进行微调需要大量的计算资源(GPU/TPU)和时间,尤其是在处理大型数据集时。RAG在推理阶段的计算成本主要在于检索和生成答案,通常低于重新训练整个模型。

不利的地方:


  1. 知识更新困难: 微调后的模型所学习的知识是基于微调数据集的静态快照。如果需要模型掌握新的信息,必须重新进行微调,这既耗时又耗费资源。RAG可以通过更新外部知识库来轻松地引入和利用最新的信息。
  2. 泛化能力下降风险: 如果微调数据集过小或不够多样化,微调后的模型可能会过拟合于特定任务,导致在未见过的数据上的泛化能力下降。RAG通过利用外部知识库,模型仍然可以依赖其预训练的通用知识。
  3. 透明度和可解释性较低: 微调改变了模型内部的权重和参数,使得模型学习到的知识和决策过程更加难以解释和追溯。RAG在生成答案时可以提供检索到的外部证据,提高了透明度和可信度。
  4. 模型偏移 (Model Drift): 随着时间的推移,如果目标任务或数据的分布发生变化,微调后的模型性能可能会逐渐下降,需要定期维护和重新微调。RAG可以通过更新知识库来适应变化,但检索系统的有效性也可能受到数据质量的影响。
  5. 难以处理开放领域或不断演变的信息: 由于微调依赖于静态数据集,它不太适合需要处理不断变化或涉及广泛领域知识的任务。RAG通过实时检索外部信息,更适合这类场景。
特征RAG (检索增强生成)模型微调 (Fine-tuning)
核心思想实时检索外部知识并融入生成过程,增强模型对新知识的利用通过额外的训练调整模型内部参数,使其适应特定任务或领域
知识来源外部知识库 (实时或近实时)微调数据集 (静态)
更新知识相对容易,更新外部知识库即可需要重新进行微调,成本较高
适用场景需要处理最新信息、知识密集型、特定领域但知识 постоянно 更新的场景需要模型学习特定风格、术语、或执行特定任务,且知识相对稳定的场景
数据需求构建高质量的外部知识库和高效的检索系统需要高质量、标注良好的微调数据集 (通常比预训练数据小)
计算成本检索过程有计算成本,但通常低于重新训练整个模型微调过程需要一定的计算资源,尤其对于大型模型
灵活性较高,可以灵活更换和扩展知识库一旦微调完成,模型的知识和能力就相对固定
透明度可以提供检索到的证据,提高回答的可信度模型内部知识难以追溯,透明度较低
减少“幻觉”通过引用外部知识,有助于减少模型生成不实信息可以通过高质量的微调数据减少“幻觉”,但并非完全消除
实现难度需要构建信息检索系统和融合机制需要准备微调数据、选择合适的微调策略和超参数

总结来说:


  1. 选择 RAG 的情况: 当你需要模型能够回答关于最新信息、特定领域知识(这些知识可能会频繁更新)的问题,并且希望答案有外部证据支持时。RAG不需要改变模型本身,而是通过外部信息来增强其能力。
  2. 选择模型微调的情况: 当你需要模型学习特定的语言风格、理解特定领域的术语、或者在某个特定任务上获得更好的性能,并且你有相关的标注数据可以用来训练模型时。微调会改变模型内部的知识和行为方式。

在实践中,RAG和模型微调有时也可以结合使用,以充分发挥各自的优势。例如,可以先对模型进行微调,使其具备更好的领域理解能力,然后再使用RAG来增强其获取最新信息和生成更准确答案的能力。