一、文档加载:读取 PDF 内容至上下文
通过 PyPDFLoader 加载目标 PDF 文件,将文档内容载入程序上下文,为后续处理奠定基础。加载后可查看文档页数及首页核心信息,快速验证加载效果。
python
from langchain_community.document_loaders import PyPDFLoader
# 定义 PDF 文件路径(请根据实际场景调整)
file_path = "./static/26考研考点排查表.pdf"
# 初始化 PDF 加载器
loader = PyPDFLoader(file_path)
# 加载文档内容至上下文
docs = loader.load()
# 验证加载结果:输出文档页数、首页前200字符内容及元数据
print(f"文档总页数:{len(docs)}")
print(f"首页内容预览(前200字符):\n{docs[0].page_content[:200]}\n")
print(f"首页元数据:{docs[0].metadata}")
二、文档分片:合理切割文本,保留上下文关联
为适配向量模型的输入长度限制,同时避免割裂关键信息,采用递归字符分割策略:将文档切割为 1000 字符/块的文本片段,且相邻块保留 200 字符重叠。这种重叠设计能有效降低重要表述与关联上下文被拆分的风险,保障后续检索的准确性。
python
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 初始化文本分割器,配置分片参数
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # 单个文本块的最大字符数
chunk_overlap=200, # 相邻文本块的重叠字符数
add_start_index=True # 为每个块添加原始文档中的起始索引,便于溯源
)
# 对加载的文档进行分片处理
all_splits = text_splitter.split_documents(docs)
# 预览分片结果(仅展示前3块,避免大量文本输出)
print("=== 文本分片结果预览(前3个块) ===")
for i, chunk in enumerate(all_splits[:3]):
print(f"块 #{i+1} | 字符长度: {len(chunk.page_content)} | 内容预览: {chunk.page_content[:50]}{'...' if len(chunk.page_content) > 50 else ''}")
print(f"\n文档分片完成,总块数:{len(all_splits)}")
三、向量转换:将文本片段转为数值向量
文本属于非结构化数据,无法直接用于相似度匹配。通过 Hugging Face 提供的预训练嵌入模型,将分割后的文本块转换为固定长度的数值向量,实现非结构化文本的结构化表征。此处选用 sentence-transformers/all-mpnet-base-v2 模型,兼顾向量表征效果与通用性。
python
from langchain_huggingface import HuggingFaceEmbeddings
# 初始化嵌入模型(首次执行会自动从 Hugging Face Hub 下载至本地缓存,默认路径:~/.cache/huggingface/hub)
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2")
# 测试向量生成:对前2个文本块进行嵌入转换
vector_1 = embeddings.embed_query(all_splits[0].page_content)
vector_2 = embeddings.embed_query(all_splits[1].page_content)
# 验证向量一致性:同一模型生成的向量长度必须相同
assert len(vector_1) == len(vector_2)
print(f"向量生成完成,单个向量长度:{len(vector_1)}\n")
print(f"第一个向量预览(前10个元素):{vector_1[:10]}")
四、向量存储:将向量与文本关联存入数据库
将生成的文本向量与其对应的原始文本块关联存储,便于后续快速检索。此处选用轻量级向量数据库 Chroma,支持本地持久化存储,无需复杂的服务部署,适合快速验证和小规模应用场景。
ini
from langchain_chroma import Chroma
# 初始化 Chroma 向量数据库
vector_store = Chroma(
collection_name="example_collection", # 向量集合名称,便于区分不同文档
embedding_function=embeddings, # 关联嵌入模型
persist_directory="./chroma_langchain_db" # 本地持久化存储路径(可选,删除则仅存于内存)
)
# 将所有文本块及其向量存入数据库,并返回各块的唯一ID
ids = vector_store.add_documents(documents=all_splits)
print(f"向量存储完成,共存入 {len(ids)} 个文本块的向量数据")
五、多样检索:基于向量相似度的灵活查询
向量数据库的核心价值在于通过向量相似度匹配,快速定位与查询需求相关的文本内容。以下提供 4 种常用检索方式,适配不同业务场景(如同步/异步、是否需要相似度分数等)。
1. 基础文本检索:根据查询字符串返回相似文档
直接输入查询文本(如"数一"),数据库自动将其转为向量,再匹配相似度最高的文档并返回。
bash
# 输入查询文本,检索相似文档
results = vector_store.similarity_search("数一")
# 输出相似度最高的文档内容
print("=== 基础文本检索结果(相似度最高) ===")
print(results[0])
2. 异步文本检索:非阻塞查询,支持并行任务
采用异步方式执行检索,不阻塞主线程,可在检索过程中并行处理其他任务,提升程序执行效率,适合高并发场景。
python
# 异步检索(需在异步函数中执行,或使用异步环境)
results = await vector_store.asimilarity_search("数一")
# 输出相似度最高的文档内容
print("=== 异步文本检索结果(相似度最高) ===")
print(results[0])
3. 带相似度分数的检索:量化匹配程度
检索时返回相似文档及对应的相似度分数(该分数为距离度量,值越小表示相似度越高),可通过分数筛选符合阈值的结果,提升检索精准度。
python
# 检索相似文档并返回相似度分数
results = vector_store.similarity_search_with_score("数一")
# 提取并输出Top1结果的分数和文档内容
doc, score = results[0]
print("=== 带相似度分数的检索结果 ===")
print(f"相似度分数(值越小越相似):{score:.4f}\n")
print(f"匹配文档内容:\n{doc}")
4. 向量直接检索:基于预生成向量的精准匹配
若已提前生成查询文本的向量,可直接传入向量进行检索,跳过数据库内部的向量转换步骤,进一步提升检索速度。
ini
# 先手动生成查询文本的向量
query_embedding = embeddings.embed_query("数一")
# 直接传入向量进行相似检索
results = vector_store.similarity_search_by_vector(query_embedding)
# 输出相似度最高的文档内容
print("=== 向量直接检索结果(相似度最高) ===")
print(results[0])
代码仓库
gitee:gitee.com/o_insist/la...
github:github.com/o-insist/la...
✨ 学习之路,循序渐进,持续更新中...