大家好,我是阿龙。上一章我们搞定了环境配置和DeepSeek API,今天终于进入RAG系统的核心基石:文档处理与向量化。

这一章有个铁律:垃圾进,垃圾出。如果你的文档切分得像“碎纸机里的残骸”,那检索系统就只能给你“驴唇不对马嘴”的片段,大模型再牛也白搭。

别慌,阿龙今天手把手带你优雅地处理文档,从切分到向量化,每一步都讲透,让你直接打出“组合拳”!


1.1 文档加载与切分(Chunking)—— 切出“黄金语义块”

为什么必须切分?

大模型有上下文窗口限制(比如DeepSeek是64K),但更关键的是:过长的文本会稀释关键信息。想象一下,把一本《十万个为什么》直接塞给模型,它还能精准回答“为什么天是蓝的”吗?

切分的目标是:保持语义完整,同时块大小适中。比如客服知识库,每个段落本身就是独立的“知识点”,切分时就该以段落为边界。

代码实战:模拟加载客服文档

我们先模拟一段客服知识库文本(实际项目里你会用 pypdf 读取PDF,或者用 python-docx 读Word)。

python

from langchain_text_splitters import RecursiveCharacterTextSplitter
import os

# 模拟读取客服知识库文档
def load_documents():
    text = """
    【退换货政策】
    我们的产品支持7天无理由退换货。用户需保证商品包装完好,不影响二次销售。
    退款将在收到退回商品后的3个工作日内原路返还。

    【发货说明】
    每日下午4点前的订单当天发货,4点后的订单次日发货。
    合作快递包括顺丰和中通,不支持指定快递。

    【会员权益】
    注册会员可享受全场98折优惠。
    金牌会员(年消费满1000元)享受全场9折及免费上门取件退货服务。
    """
    return text

切分器配置:让“剪刀”更智能

LangChain的 RecursiveCharacterTextSplitter 是“递归字符文本分割器”,它会按优先级尝试用不同的分隔符切割,尽量保证块边界在自然断点(比如段落、句号)。

python

def split_text(text):
    # chunk_size: 每个块的最大字符数
    # chunk_overlap: 块之间的重叠字符数,防止语义被切断(比如句子被切分后,上下文丢失)
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=100,
        chunk_overlap=20,
        separators=["\n\n", "\n", ".", "!", "?"]  # 按优先级:段落、换行、句号、感叹号、问号
    )
    chunks = splitter.split_text(text)
    print(f"文档已切分为 {len(chunks)} 个片段")
    return chunks

参数详解(面试/项目复盘必问)

  • chunk_size:块的最大字符数(不是token数)。如果按字符切分,中文一般一个字算一个字符,但英文单词长度不同。如果追求精准,可以用 TokenTextSplitter(基于token计数)。
  • chunk_overlap:重叠大小。比如一句话被切分到两个块里,重叠部分能让后一个块保留前文线索。
  • separators:分隔符列表,切分器会按顺序尝试,直到块大小符合要求。这里我加了中文常用的“。”和“!”,避免把一句话切成两半。

运行一下

python

if __name__ == "__main__":
    raw_text = load_documents()
    chunks = split_text(raw_text)
    for i, chunk in enumerate(chunks):
        print(f"块 {i}: {chunk}\n{'-'*40}")

输出结果(示例):

(rag_fresh) fulongmin@fulongdeMacBook-Pro rag % python /Users/fulongmin/Desktop/zm/rag/knowledge_base.py                                             

文档已切分为 3 个片段
块 0: 【退换货政策】
    我们的产品支持7天无理由退换货。用户需保证商品包装完好,不影响二次销售。
    退款将在收到退回商品后的3个工作日内原路返还。
----------------------------------------
块 1: 【发货说明】
    每日下午4点前的订单当天发货,4点后的订单次日发货。
    合作快递包括顺丰和中通,不支持指定快递。
----------------------------------------
块 2: 【会员权益】
    注册会员可享受全场98折优惠。
    金牌会员(年消费满1000元)享受全场9折及免费上门取件退货服务。
----------------------------------------
(rag_fresh) fulongmin@fulongdeMacBook-Pro rag % 

注意:实际开发中,chunk_size需要根据你的模型和文档特点调整。太小会丢失上下文,太大则检索精度下降。一般经验值:中文场景 200~500 字符,英文 100~300 token。


4.2 向量化存储(Embedding)—— 把文字变成“数学语言”

计算机不懂文字,只懂数字。所以我们需要把切分好的文本块转换成向量(Embedding),然后存入向量数据库。

选择Embedding模型

我们用 sentence-transformers 的 all-MiniLM-L6-v2 模型,它轻量、效果好,适合入门。首次运行会自动下载模型(约80MB)。

如果没有自动下载,可以执行下面的命令:

pip install chromadb

初始化ChromaDB(持久化存储)

ChromaDB是一个轻量级向量数据库,支持本地持久化,非常友好。

python

import chromadb
from chromadb.utils import embedding_functions

# 初始化客户端,指定持久化路径(之后重启程序也能加载数据)
chroma_client = chromadb.PersistentClient(path="./chroma_db")

# 创建Embedding函数
emb_fn = embedding_functions.SentenceTransformerEmbeddingFunction(
    model_name="all-MiniLM-L6-v2"
)

def store_vectors(chunks):
    # 获取或创建集合(Collection相当于数据库中的表)
    collection = chroma_client.get_or_create_collection(
        name="customer_service_kb",
        embedding_function=emb_fn
    )

    # 生成唯一的ID
    ids = [f"id_{i}" for i in range(len(chunks))]

    # 添加元数据(比如来源、时间戳),方便后续过滤
    metadatas = [{"source": "policy_doc"} for _ in chunks]

    # 添加向量数据
    collection.add(
        documents=chunks,   # 原始文本
        metadatas=metadatas,  # 元数据
        ids=ids             # 唯一ID
    )
    print(f"成功存储 {len(chunks)} 条向量数据")
    return collection

整合流程

把前面的函数串起来:

python

if __name__ == "__main__":
    # 1. 加载文档
    raw_text = load_documents()
    # 2. 切分文档
    chunks = split_text(raw_text)
    # 3. 存储向量
    collection = store_vectors(chunks)

运行后,你会在项目目录下看到一个 chroma_db 文件夹,里面就是持久化的向量数据。


常见问题与技巧

Q1: 切分时如何保留Markdown/HTML结构?

  • 可以用 MarkdownHeaderTextSplitter 或 HTMLHeaderTextSplitter,按标题层级切分,并保留上下文信息。

Q2: 中文句号切分不准确?

  • RecursiveCharacterTextSplitter 的分隔符列表里加上中文标点,比如 。!?,同时注意编码。如果文本中有英文句点,可能导致误切,可以优先用段落切分。

Q3: 向量化模型怎么选?

  • 英文场景:all-MiniLM-L6-v2(快)、BAAI/bge-large-en(准)。中文场景:uer/sbert-base-chinese-nli、moka-ai/m3e-base。如果想省钱,也可以用DeepSeek的Embedding API(后面会讲)。

Q4: ChromaDB的数据怎么查询?

  • 用 collection.query(query_texts=["你的问题"], n_results=3) 就能检索最相似的3个文本块。后面会专门讲检索。

Q5: 文档更新后怎么增量添加?

  • ChromaDB支持 add 相同ID会覆盖,或者你可以先 delete 再 add。也可以维护版本号在metadata里。

总结与预告

今天你学会了:

  • 如何用 RecursiveCharacterTextSplitter 智能切分文档。
  • 如何用 sentence-transformers 和 ChromaDB 将文本向量化并持久化。
  • 源码及依赖下载地址:https://pan.baidu.com/s/1CNntkxr226Xeo_jmnrCCkg?pwd=ek4t

下一章,我们将进入检索与生成环节:

  • 根据用户问题检索相关文档块。
  • 把检索结果和问题一起提交给DeepSeek API,生成最终答案。
  • 构建一个完整的客服问答机器人原型!

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐