一、场景背景与技术价值
在日常工作中,我们常常需要从大量本地文档(如 TXT、MD、PDF)中快速找到与问题相关的内容,传统的 "关键词搜索" 容易遗漏语义相关的信息,而基于向量检索的方式能实现语义级别的精准匹配。
本文将实战搭建一个轻量级本地文档向量检索系统,核心解决两个问题:
- 如何批量加载本地目录中的文档(以 TXT 为例);
- 如何将文档转为向量并实现带相似度评分的语义检索。
核心技术栈:
- LangChain 文档加载器 :
DirectoryLoader/TextLoader实现本地文档的便捷加载; - BCE 嵌入模型:本地部署的中文嵌入模型,将文本转为语义向量;
- FAISS:Facebook 开源的向量检索库,支持高效的相似度匹配并返回评分;
- HuggingFaceEmbeddings:LangChain 封装的嵌入模型调用接口,适配本地模型。
二、核心原理与流程设计
向量检索的核心逻辑是 "文本→向量→相似度计算",本次实战的完整流程为:
- 文档加载:用 LangChain 加载本地单个 / 多个 TXT 文档;
- 文本向量化:通过本地 BCE 模型将文档内容转为归一化的语义向量;
- 向量存储:用 FAISS 构建本地向量库,存储文档向量;
- 语义检索:输入查询语句,转为向量后与库中向量计算相似度,返回匹配结果 + 评分。
三、完整实战步骤与代码解析
3.1 环境准备
首先安装所需依赖,执行以下命令:
# LangChain核心依赖(含文档加载、向量库组件)
pip install langchain langchain-community langchain-huggingface
# 向量检索库
pip install faiss-cpu
# 文档加载依赖(Unstructured)
pip install unstructured
# 进度条依赖(可选,提升体验)
pip install tqdm
# HuggingFace相关依赖(加载本地嵌入模型)
pip install transformers torch sentence-transformers
同时准备本地资源:
- BCE 嵌入模型 :下载
maidalun/bce-embedding-base_v1到本地(如D:\本地模型\maidalun\bce-embedding-base_v1); - 测试文档 :在项目目录下创建
txt文件夹,放入a.txt、a1.txt、a2.txt三个测试文档(例如包含 "公鸡爱吃小米和玉米""母鸡下蛋需要安静环境""鸭子喜欢在水里觅食" 等内容)。
3.2 完整代码实现
# 1. 导入核心库
from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain_community.vectorstores import FAISS
from langchain_huggingface import HuggingFaceEmbeddings
# --------------------------
# 步骤1:加载本地文档(两种方式)
# --------------------------
# 方式1:加载单个TXT文档(指定编码避免乱码)
loader = TextLoader(".\\txt\\a.txt", encoding="utf8")
doc = loader.load()
loader1 = TextLoader(".\\txt\\a1.txt", encoding="utf8")
doc1 = loader1.load()
loader2 = TextLoader(".\\txt\\a2.txt", encoding="utf8")
doc2 = loader2.load()
# 方式2:批量加载目录下所有TXT文档(推荐,简化代码)
# loader = DirectoryLoader(
# ".\\txt", # 文档目录
# glob="**/*.txt", # 匹配所有TXT文件
# encoding="utf8", # 编码格式
# show_progress=True # 显示加载进度条(需安装tqdm)
# )
# docs = loader.load()
# print(f"批量加载文档数量:{len(docs)}")
# --------------------------
# 步骤2:初始化本地BCE嵌入模型
# --------------------------
# 本地模型路径(替换为你的实际路径)
model_name = r'D:\本地模型\maidalun\bce-embedding-base_v1'
# 嵌入模型配置:向量归一化(提升相似度计算准确性)
encode_kwargs = {'normalize_embeddings': True}
embeddings = HuggingFaceEmbeddings(
model_name=model_name,
encode_kwargs=encode_kwargs,
model_kwargs={'device': 'cpu'} # 指定运行设备(CPU/GPU)
)
# --------------------------
# 步骤3:构建FAISS向量库
# --------------------------
# 将加载的文档转为向量并存储到FAISS
# 方式1:单个文档组合(对应步骤1的方式1)
vectors = FAISS.from_documents([doc[0], doc1[0], doc2[0]], embeddings)
# 方式2:批量文档加载(对应步骤1的方式2)
# vectors = FAISS.from_documents(docs, embeddings)
# --------------------------
# 步骤4:语义检索(带相似度评分)
# --------------------------
# 普通相似度检索(仅返回文档)
# result = vectors.similarity_search("公鸡爱吃什么")
# print("普通检索结果:", result)
# 带评分的相似度检索(推荐,可判断匹配程度)
result_with_score = vectors.similarity_search_with_score("公鸡爱吃什么")
# 格式化输出检索结果
print("\n" + "="*50 + " 带相似度得分输出 " + "="*50)
for idx, (doc, score) in enumerate(result_with_score, 1):
print(f"\n【匹配结果 {idx}】")
print(f"相似度得分(越低越相似):{score:.4f}")
print(f"文本内容:{doc.page_content}")
3.3 核心代码解析
1. 文档加载的两种方式
方式 1:单个文档加载(TextLoader)
loader = TextLoader(".\\txt\\a.txt", encoding="utf8")
doc = loader.load()
TextLoader:专门用于加载纯文本文件(TXT),需指定encoding="utf8"避免中文乱码;load()方法返回一个Document对象列表,每个对象包含page_content(文本内容)和metadata(元数据,如文件路径)。
方式 2:批量目录加载(DirectoryLoader)
loader = DirectoryLoader(".\\txt", glob="**/*.txt", show_progress=True)
docs = loader.load()
glob="**/*.txt":递归匹配目录下所有 TXT 文件(**表示子目录),也可改为**/*.md匹配 Markdown 文件;show_progress=True:加载大量文档时显示进度条,提升体验(需安装tqdm);- 优势:无需逐个加载文件,适合海量本地文档场景。
2. 本地嵌入模型配置
embeddings = HuggingFaceEmbeddings(
model_name=model_name,
encode_kwargs={'normalize_embeddings': True},
model_kwargs={'device': 'cpu'}
)
normalize_embeddings=True:对生成的向量做归一化处理,使向量长度为 1,此时余弦相似度等价于点积,计算更高效且结果更稳定;model_kwargs={'device': 'cpu'}:指定模型运行在 CPU 上(若无 GPU),有 GPU 可改为'cuda'提升速度;- 核心价值:无需调用第三方 API,完全离线完成文本向量化,数据更安全。
3. FAISS 向量检索核心方法
(1)普通检索:similarity_search
result = vectors.similarity_search("公鸡爱吃什么")
- 返回与查询语句语义最相似的文档列表(默认返回 4 个);
- 优点:简单易用;缺点:无相似度评分,无法判断匹配程度。
(2)带评分检索:similarity_search_with_score
result_with_score = vectors.similarity_search_with_score("公鸡爱吃什么")
- 返回元组列表
(文档对象, 相似度得分); - 得分说明:FAISS 默认使用 L2 距离(欧式距离),得分越低表示语义越相似(得分 0 表示完全匹配);
- 核心价值:可根据得分筛选结果(如只保留得分 < 1.0 的文档),提升检索精准度。
四、运行结果与关键说明
4.1 预期运行结果
假设测试文档内容:
a.txt:公鸡爱吃小米、玉米和虫子,每天需要充足的谷物投喂;a1.txt:母鸡下蛋期间需要补充钙,喜欢在安静的鸡舍觅食;a2.txt:鸭子喜欢在水里找小鱼吃,和公鸡的食性差异很大。
运行代码后输出:
================================================== 带相似度得分输出 ==================================================
【匹配结果 1】
相似度得分(越低越相似):0.1250
文本内容:公鸡爱吃小米、玉米和虫子,每天需要充足的谷物投喂;
【匹配结果 2】
相似度得分(越低越相似):0.8925
文本内容:母鸡下蛋期间需要补充钙,喜欢在安静的鸡舍觅食;
【匹配结果 3】
相似度得分(越低越相似):1.5680
文本内容:鸭子喜欢在水里找小鱼吃,和公鸡的食性差异很大。
4.2 关键说明
- 得分解读:L2 距离得分 <0.5 表示高度相似,0.5~1.0 表示中度相似,>1.0 表示低相似;
- 乱码问题 :加载文档时必须指定
encoding="utf8"(或对应文档的编码,如gbk),否则中文会出现乱码; - 模型路径 :
model_name必须指向本地 BCE 模型的根目录(包含config.json、pytorch_model.bin等文件)。
五、扩展方向
- 支持更多文档格式 :替换
TextLoader为PyPDFLoader(PDF)、DocxLoader(Word)、MarkdownLoader(MD),实现多格式文档加载; - 文本分割优化 :结合
RecursiveCharacterTextSplitter对长文档分割,避免单篇文档过长导致嵌入效果下降; - 检索参数调优 :通过
similarity_search_with_score("查询语句", k=2)指定返回结果数量(k 值); - 结合大模型生成:将检索结果作为上下文,调用本地 / 云端大模型生成回答,升级为完整的 RAG 系统;
- 向量库持久化 :通过
vectors.save_local("faiss_index")保存向量库,下次使用时通过FAISS.load_local加载,无需重复向量化。
六、核心总结
- LangChain 提供了
TextLoader/DirectoryLoader等开箱即用的文档加载工具,能快速处理本地单 / 多文档加载,解决了传统爬虫 / 文件读取的繁琐问题; - 本地 BCE 嵌入模型 + FAISS 的组合,实现了完全离线的语义检索,数据不落地第三方,兼顾效率与安全性;
similarity_search_with_score是 FAISS 的核心实用方法,通过相似度得分可精准筛选匹配结果,是构建高质量 RAG 系统的基础。
该系统是本地知识库的核心基础,可直接适配企业内部文档检索、个人知识库管理等场景,只需替换文档目录和检索语句,即可快速落地使用。
可以运行的代码如下:
from langchain_community.document_loaders import DirectoryLoader
from langchain_community.vectorstores import FAISS
#loader = DirectoryLoader("../", glob="**/*.txt")
#默认情况下不会显示进度条。要显示进度条,请安装 tqdm 库(例如 pip install tqdm),并将 show_progress 参数设置为 True。
#loader = DirectoryLoader("../", glob="**/*.md", show_progress=True)
#docs = loader.load()
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.document_loaders import TextLoader
loader = TextLoader(".\\txt\\a.txt", encoding="utf8")
doc = loader.load()
loader1 = TextLoader(".\\txt\\a1.txt", encoding="utf8")
doc1 = loader1.load()
loader2 = TextLoader(".\\txt\\a2.txt", encoding="utf8")
doc2 = loader2.load()
model_name = r'D:\本地模型\maidalun\bce-embedding-base_v1'
# D:\大模型\RAG_Project\maidalun\bce-embedding-base_v1
# 生成的嵌入向量将被归一化,有助于向量比较
encode_kwargs = {'normalize_embeddings': True}
embeddings = HuggingFaceEmbeddings(
model_name=model_name
)
vectors = FAISS.from_documents([doc[0], doc1[0],doc2[0]], embeddings)
#result = vectors.similarity_search("公鸡爱吃什么")
#print(result)
result_with_score = vectors.similarity_search_with_score("公鸡爱吃什么")
print("\n" + "="*50 + " 带相似度得分输出 " + "="*50)
for idx, (doc, score) in enumerate(result_with_score, 1):
print(f"\n【匹配结果 {idx}】")
print(f"相似度得分(越低越相似):{score:.4f}")
print(f"文本内容:{doc.page_content}")