《从零搭建RAG系统第3天:文档加载+文本向量化+向量存入Milvus》

0 阅读15分钟

大家好,我是老赵,一名程序员老兵,平时主要从事企业级应用开发,最近打算从零学习、并落地一套完整的RAG检索增强生成系统。不搞虚头理论,全程边学边做,把遇到的问题、踩过的坑、能直接跑通的代码,全都真实记录下来。

前两天我们完成了核心准备工作:Day1搭建Miniconda的rag-env开发环境;Day2用Docker部署Milvus向量数据库,搞定了镜像拉取的坑。

今天正式进入RAG的核心流程——文档加载与文本向量化:把本地文档(新手先用TXT,后续再更PDF/Word)加载到系统,用bge-small-zh模型将文本转化为向量,最后把向量存入Milvus,为后续的检索匹配打下基础。

全程不搞理论,只记实战操作、真实踩坑和可复制代码,和前两天一致,边做边更,适合想从零落地RAG的开发者。后续会持续更新,最终整理成完整教程+源码+部署手册,涵盖RAG全流程。

一、今日目标

  1. 准备测试文档(TXT格式,贴合新手操作);
  2. 基于LangChain实现本地文档加载(解决文档编码、路径报错问题);
  3. 用bge-small-zh模型实现文本向量化(本地可跑,无需联网调用API);
  4. 将向量化后的文本向量,存入Day2部署的Milvus向量数据库;
  5. 验证向量存入成功,完成RAG核心数据链路闭环;
  6. 记录2个新手必踩坑(文档加载失败、向量存入Milvus报错)及解决方案。

二、前置准备(必做,衔接前两天环境)

  1. 环境前提:
    • 激活Miniconda的rag-env环境(终端前缀显示(rag-env)),未激活则执行:conda activate rag-env
    • 确保Docker的Milvus容器正常运行,执行docker ps,查看milvus-standalone的状态,为“Up”即可(若未启动,执行docker start milvus-standalone);
    • 依赖前提:Day1已安装langchain==0.2.14 langchain-core==0.2.35 langchain-community==0.2.12 pymilvus==2.6.9 sentence-transformers==5.2.2。
  2. 文档准备(新手首选TXT,避免复杂格式踩坑):
    • 创建一个本地TXT文档,命名为test_rag.txt
    • 文档内容:任意输入一段中文文本(300-500字即可,比如复制一段技术文档、文章摘要),示例:
          RAG 检索增强生成(Retrieval Augmented Generation),已经成为当下最火热的LLM应用方案和打开方式了。
      理解起来不难,就是通过自有垂域数据库检索相关信息,然后合并成为提示模板,给大模型润色生成回答。
      每当将大模型应用于实际业务场景时发现,通用的基础大模型基本无法满足实际业务需求,主要有以下几方面原因:
      知识的局限性:大模型自身的知识完全源于训练数据,而现有的主流大模型(deepseek、文心一言、通义千问…)的训练集基本都是构建于网络公开的数据,对于一些实时性的、非公开的或私域的数据是没有。
      幻觉问题:所有的深度学习模型的底层原理都是基于数学概率,模型输出实质上是一系列数值运算,大模型也不例外,所以它经常会一本正经地胡说八道,尤其是在大模型自身不具备某一方面的知识或不擅长的任务场景。
      数据安全性:对于企业来说,数据安全至关重要,没有企业愿意承担数据泄露的风险,尤其是大公司,没有人将私域数据上传第三方平台进行训练会推理。这也导致完全依赖通用大模型自身能力的应用方案不得不在数据安全和效果方面进行取舍。
      而RAG就是解决上述问题的有效方案。
      
    • 记录文档路径(关键,避免加载失败):比如Windows路径D:\rag_doc\test_rag.txt

三、实战步骤(可直接复制代码,全程无冗余)

步骤1:安装文档加载所需依赖(补充依赖)

Day1安装的依赖可满足向量化和Milvus连接,但文档加载需要额外依赖(处理TXT编码、路径解析),执行命令:

# 清华源安装,适配rag-env环境,避免版本冲突
pip install python-dotenv chardet

说明:chardet用于自动识别文档编码(解决中文乱码、加载失败),python-dotenv用于后续配置管理(提前铺垫)。

步骤2:基于LangChain加载本地TXT文档

创建Python文件(命名为doc_load_vector.py,方便后续复用),复制以下代码,替换自己的文档路径即可:

# 导入文档加载所需模块(LangChain内置)
from langchain.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

# 1. 加载本地TXT文档(替换为你的文档路径)
loader = TextLoader(r".\test_data\test_rag.txt", encoding="utf-8")
# 加载文档内容
documents = loader.load()

# 2. 拆分文档(关键:避免文本过长,影响向量化和后续检索效果)
# 拆分规则:按字符递归拆分,_chunk_size=200(每段200字),chunk_overlap=20(两段重叠20字,保证上下文连贯)
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=200,
    chunk_overlap=20,
    length_function=len,
)
# 执行拆分,得到拆分后的文档片段
split_docs = text_splitter.split_documents(documents)
# 打印拆分结果(验证加载和拆分成功)
print("文档加载成功,拆分后片段数:", len(split_docs))
print("\n拆分后的第一个片段:")
for i, doc in enumerate(split_docs):
    print(f"----文档:{i + 1}----")
    print(doc.page_content)

执行Python文件(终端执行,确保在rag-env环境中):

python doc_load_vector.py

✅ 若打印出“文档加载成功,拆分后片段数”和具体文本片段,说明文档加载、拆分成功。

步骤3:用bge-small-zh模型实现文本向量化

在上述doc_load_vector.py文件中,继续添加以下代码(向量化拆分后的文档片段):

# 导入向量化模型模块
from sentence_transformers import SentenceTransformer
# 初始化bge-small-zh模型(轻量中文模型,本地可跑,无需联网调用API)
# 第一次运行会自动下载模型(约300MB,国内网络可正常下载,若慢可改用清华源镜像)
embedding_model = SentenceTransformer('BAAI/bge-small-zh')
# 3. 文本向量化:将拆分后的文档片段转化为向量(维度:512维,bge-small-zh默认输出)
# 提取文档片段的文本内容
doc_texts = [doc.page_content for doc in split_docs]
# 执行向量化(生成向量列表,每个向量对应一个文档片段)
doc_embeddings = embedding_model.encode(doc_texts)
# 打印向量化结果(验证向量化成功)
print("\n文本向量化成功,向量数量:", len(doc_embeddings))
print("每个向量维度:", len(doc_embeddings[0]))  # 输出512,即为成功
print("第一个文档片段的向量(前10位):", doc_embeddings[0][:10])

再次执行Python文件,查看输出结果,若打印出向量数量、维度(384),说明向量化成功。

步骤4:将向量存入Milvus向量数据库(核心闭环)

继续在doc_load_vector.py文件中添加代码,连接Milvus,创建集合(类似数据库的表),存入向量:

# 导入Milvus相关模块(Day1已安装pymilvus)
from pymilvus import connections, Collection, CollectionSchema, FieldSchema, DataType, utility
# 4. 连接Milvus向量数据库(衔接Day2的部署,参数和Day2验证时一致)
connections.connect(
    alias="default",
    host="localhost",
    port="19530"
)
# 5. 在Milvus中创建集合(类似数据库的表,用于存储文档向量和对应文本)
# 集合名称:rag_test_collection(可自定义,建议语义化)
collection_name = "rag_test_collection"
# 定义集合的字段(3个核心字段:主键ID、文档文本、文档向量)
fields = [
    # 主键ID:唯一标识,自增
    FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
    # 文档文本:存储拆分后的文档片段(方便后续检索后展示原文)
    FieldSchema(name="doc_text", dtype=DataType.VARCHAR, max_length=600),  # 最大长度600,可根据需求调整
    # 文档向量:存储向量化后的向量,维度512(和bge-small-zh模型输出一致)
    FieldSchema(name="doc_embedding", dtype=DataType.FLOAT_VECTOR, dim=512)
]

# 定义集合 schema
schema = CollectionSchema(fields=fields, description="RAG测试集合:存储文档片段和对应向量")
# 检查集合是否已存在,若存在则删除(避免重复创建,新手可直接执行)
if utility.has_collection(collection_name):
    utility.drop_collection(collection_name)
# 创建集合
collection = Collection(name=collection_name, schema=schema)
# 6. 准备存入Milvus的数据(格式:列表,对应集合的3个字段,id自增无需传入)
milvus_data = [
    doc_texts,  # 对应doc_text字段
    doc_embeddings.tolist()  # 对应doc_embedding字段,转化为列表格式(Milvus要求)
]
# 7. 将数据插入Milvus集合
collection.insert(milvus_data)
# 8. 创建索引(关键:后续检索需要索引,否则检索速度极慢)
index_params = {
    "index_type": "IVF_FLAT",  # 基础索引类型,新手首选,简单易操作
    "metric_type": "L2",       # 距离度量方式,L2为欧氏距离(适合向量匹配)
    "params": {"nlist": 128}   # 索引参数,默认128即可
}
# 为向量字段创建索引
collection.create_index(field_name="doc_embedding", index_params=index_params)
# 加载集合到内存(检索前必须加载)
collection.load()
# 打印存入结果(验证向量存入成功)
print("\n向量存入Milvus成功!")
print("Milvus集合名称:", collection_name)
print("存入的向量数量:", collection.num_entities)  # 和文档片段数、向量数量一致,即为成功
# 关闭Milvus连接(可选,后续开发可保持连接)
connections.disconnect("default")

第三次执行Python文件,若打印出“向量存入Milvus成功”,且存入的向量数量和文档片段数一致,说明整个流程(加载→向量化→存入Milvus)闭环成功!

四、今天踩的2个核心坑(新手必踩,真实实录)

坑1:文档加载失败(报错“FileNotFoundError”/中文乱码)

问题触发操作

执行文档加载代码时,报错“FileNotFoundError: [Errno 2] No such file or directory: 'xxx.txt'”,或加载后文本出现乱码(如“��”)。

问题现象

  1. 终端报错,提示文件路径不存在,无法加载文档;
  2. 文档加载成功,但打印的文本片段出现大量乱码,无法正常识别中文;

原因

  1. 文档路径错误(最常见):路径拼写错误、Windows路径未用双反斜杠(\)、路径中包含中文特殊字符;
  2. 文档编码问题:TXT文档编码不是UTF-8(比如GBK编码),LangChain默认用UTF-8加载,导致乱码;

最终可用解决方案(按优先级执行)

  1. 核对并修改文档路径(关键中的关键):     - Windows路径:必须用双反斜杠,比如D:\\rag_doc\\test_rag.txt(单反斜杠会被识别为转义字符),或者使用raw字符串,如: r"D:\rag_doc\test_rag.txt";
  2. 解决中文乱码:
# 方法1:指定编码为utf-8(优先,本文代码已添加)
loader = TextLoader(r".\test_data\test_rag.txt", encoding="utf-8")
# 方法2:若utf-8失败,改用gbk编码(适合Windows默认保存的TXT)
loader = TextLoader("D:\\rag_doc\\test_rag.txt", encoding="gbk")
# 方法3:自动识别编码(chardet包,本文已安装)
import chardet
with open("D:\\rag_doc\\test_rag.txt", "rb") as f:
    encoding = chardet.detect(f.read())["encoding"]
loader = TextLoader("D:\\rag_doc\\test_rag.txt", encoding=encoding)`

坑2:向量存入Milvus失败(报错“dimension mismatch”/“collection not found”)

问题触发操作

执行向量存入代码时,报错“dimension mismatch”(维度不匹配),或“varchar field doc_text exceeds max length”(数据超长)。

问题现象

  1. 报错“dimension mismatch: expected 384, got 512”(向量维度和Milvus集合定义的维度不一致);
  2. 报错“length of varchar field doc_text exceeds max length, row number: 1, length: 564, max length: 500: invalid parameter”(doc_text列的数据长度超过其定义允许的最大值);

原因

  1. 向量维度不匹配(最常见):Milvus集合定义的向量维度为384,但向量化模型生成的向量是512维的;
  2. doc_text列定义的长度是500,而分块数据中有的数据长度会超过500

最终可用解决方案(按优先级执行)

  1. 把Milvus集合定义中向量列的dim定义为512;
  2. 把doc_text列的最大长度改为600;

五、完整验证

执行完所有代码后,我们额外验证一次:从Milvus中查询存入的向量和文本,确认存入的数据正确。

# 从Milvus中查询前3条数据(验证存入的数据正确)
query_result = collection.query(expr="id > 0",  # 查询条件:id大于0(所有数据)
                                limit=3,  # 查询3条
                                output_fields=["id", "doc_text", "doc_embedding"]  # 输出的字段
                                )
# 打印查询结果
print("\n从Milvus查询到的数据:")
for res in query_result:
    print(f"id: {res['id']}")
    print(f"文档文本: {res['doc_text']}")
    print(f"向量(前10位): {res['doc_embedding'][:10]}\n")

执行代码后,若能正常打印出Milvus中的id、文档文本和向量,说明全流程(文档加载→向量化→存入Milvus)完全成功,RAG的核心数据链路已打通。

六、今日核心代码汇总(新手可直接复制保存)

将上述所有代码整合,完整的doc_load_vector.py文件代码(替换自己的文档路径即可):

# 导入文档加载所需模块(LangChain内置)
from langchain.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

# 1. 加载本地TXT文档(替换为你的文档路径)
loader = TextLoader(r".\test_data\test_rag.txt", encoding="utf-8")
# 加载文档内容
documents = loader.load()

# 2. 拆分文档(关键:避免文本过长,影响向量化和后续检索效果)
# 拆分规则:按字符递归拆分,_chunk_size=200(每段200字),chunk_overlap=20(两段重叠20字,保证上下文连贯)
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=200,
    chunk_overlap=20,
    length_function=len,
)
# 执行拆分,得到拆分后的文档片段
split_docs = text_splitter.split_documents(documents)
# 打印拆分结果(验证加载和拆分成功)
print("文档加载成功,拆分后片段数:", len(split_docs))
print("\n拆分后的第一个片段:")
for i, doc in enumerate(split_docs):
    print(f"----文档:{i + 1}----")
    print(doc.page_content)

# 导入向量化模型模块
from sentence_transformers import SentenceTransformer

# 初始化bge-small-zh模型(轻量中文模型,本地可跑,无需联网调用API)
# 第一次运行会自动下载模型(约300MB,国内网络可正常下载,若慢可改用清华源镜像)
embedding_model = SentenceTransformer('BAAI/bge-small-zh')
# 3. 文本向量化:将拆分后的文档片段转化为向量(维度:512维,bge-small-zh默认输出)
# 提取文档片段的文本内容
doc_texts = [doc.page_content for doc in split_docs]
# 执行向量化(生成向量列表,每个向量对应一个文档片段)
doc_embeddings = embedding_model.encode(doc_texts)
# 打印向量化结果(验证向量化成功)
print("\n文本向量化成功,向量数量:", len(doc_embeddings))
print("每个向量维度:", len(doc_embeddings[0]))  # 输出512,即为成功
print("第一个文档片段的向量(前10位):", doc_embeddings[0][:10])

# 导入Milvus相关模块(Day1已安装pymilvus)
from pymilvus import connections, Collection, CollectionSchema, FieldSchema, DataType, utility

# 4. 连接Milvus向量数据库(衔接Day2的部署,参数和Day2验证时一致)
connections.connect(
    alias="default",
    host="localhost",
    port="19530"
)
# 5. 在Milvus中创建集合(类似数据库的表,用于存储文档向量和对应文本)
# 集合名称:rag_test_collection(可自定义,建议语义化)
collection_name = "rag_test_collection"
# 定义集合的字段(3个核心字段:主键ID、文档文本、文档向量)
fields = [
    # 主键ID:唯一标识,自增
    FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
    # 文档文本:存储拆分后的文档片段(方便后续检索后展示原文)
    FieldSchema(name="doc_text", dtype=DataType.VARCHAR, max_length=600),  # 最大长度600,可根据需求调整
    # 文档向量:存储向量化后的向量,维度512(和bge-small-zh模型输出一致)
    FieldSchema(name="doc_embedding", dtype=DataType.FLOAT_VECTOR, dim=512)
]

# 定义集合 schema
schema = CollectionSchema(fields=fields, description="RAG测试集合:存储文档片段和对应向量")
# 检查集合是否已存在,若存在则删除(避免重复创建,新手可直接执行)
if utility.has_collection(collection_name):
    utility.drop_collection(collection_name)
# 创建集合
collection = Collection(name=collection_name, schema=schema)
# 6. 准备存入Milvus的数据(格式:列表,对应集合的3个字段,id自增无需传入)
milvus_data = [
    doc_texts,  # 对应doc_text字段
    doc_embeddings.tolist()  # 对应doc_embedding字段,转化为列表格式(Milvus要求)
]
# 7. 将数据插入Milvus集合
collection.insert(milvus_data)
# 8. 创建索引(关键:后续检索需要索引,否则检索速度极慢)
index_params = {
    "index_type": "IVF_FLAT",  # 基础索引类型,新手首选,简单易操作
    "metric_type": "L2",  # 距离度量方式,L2为欧氏距离(适合向量匹配)
    "params": {"nlist": 128}  # 索引参数,默认128即可
}
# 为向量字段创建索引
collection.create_index(field_name="doc_embedding", index_params=index_params)
# 加载集合到内存(检索前必须加载)
collection.load()
# 打印存入结果(验证向量存入成功)
print("\n向量存入Milvus成功!")
print("Milvus集合名称:", collection_name)
print("存入的向量数量:", collection.num_entities)  # 和文档片段数、向量数量一致,即为成功

# 从Milvus中查询前3条数据(验证存入的数据正确)
query_result = collection.query(expr="id > 0",  # 查询条件:id大于0(所有数据)
                                limit=3,  # 查询3条
                                output_fields=["id", "doc_text", "doc_embedding"]  # 输出的字段
                                )
# 打印查询结果
print("\n从Milvus查询到的数据:")
for res in query_result:
    print(f"id: {res['id']}")
    print(f"文档文本: {res['doc_text']}")
    print(f"向量(前10位): {res['doc_embedding'][:10]}\n")
    
# 关闭Milvus连接(可选,后续开发可保持连接)
connections.disconnect("default")

七、明日计划

基于今天完成的“文档向量存入Milvus”,实现RAG的核心检索功能——用户输入问题,将问题向量化,然后从Milvus中检索出与问题最相关的文档片段,完成“检索”环节;同时记录检索过程中的匹配度优化、检索失败等坑,继续完善RAG全流程。

我是老赵,一名程序员老兵,全程真实记录RAG从零搭建全过程。本系列会持续更新,最后整理成完整视频教程+源码+部署手册。

关注 老赵全栈实战,不迷路,一起从0落地RAG系统,避开所有新手坑。