LangChain + RAG 实战(二):文档切片与向量化
·
创作者: Yardon | GitHub: github.com/YardonYan | 版本: v1.0 |
RAG 的核心流程
RAG(Retrieval-Augmented Generation)是最实用的 LLM 应用模式:
1. 用户提问 → "React 的 useEffect 怎么用?"
2. 从知识库检索相关文档片段
3. 把片段 + 问题一起发给 LLM
4. LLM 基于检索到的内容生成答案
为什么要检索?因为 LLM 有两个致命问题:
- 知识截止日期:训练数据固定在某个时间点
- 幻觉:遇到不知道的问题会"编造"答案
RAG 通过注入外部知识解决了这两个问题。
文档处理流程:
原始文档 → 加载 → 切分 → Embedding → 向量数据库 → 检索 → 生成答案
文档加载器:从各种格式读取
# PDF 加载
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader("document.pdf")
pages = loader.load()
# Markdown/Text 加载
from langchain_community.document_loaders import TextLoader
loader = TextLoader("notes.md")
docs = loader.load()
# 网页加载
from langchain_community.document_loaders import WebBaseLoader
loader = WebBaseLoader("https://docs.langchain.com")
docs = loader.load()
文本切分:大小与重叠的策略
LLM 的上下文窗口有限,不能把整本书扔进去。切分是最关键的一步——切得太碎语义断裂,切得太长检索精度下降。
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # 每块最大 1000 字符
chunk_overlap=200, # 块之间重叠 200 字符
separators=["\n\n", "\n", "。", ",", " ", ""], # 优先在自然边界切
)
chunks = splitter.split_documents(documents)
print(f"切分为 {len(chunks)} 个文档块")
重叠是必需的。比如一个概念跨了两块:
块1: "...在 React 中,useEffect 是一个非常重要的 Hook..."
块1 结束 ←
块2: "...useEffect 来处理副作用,如 API 调用、事件监听等..."
如果不重叠,“useEffect” 这个概念可能在第二个块里失去了上下文——“Effect” 到底指什么?200 个字符的重叠保证了关键信息不会被"一刀两断"。
经验值:
| 场景 | chunk_size | chunk_overlap |
|---|---|---|
| 技术文档 | 500-800 | 100 |
| 长文章/论文 | 1000-1500 | 200 |
| 代码库 | 按函数/类切 | 按 import 块重叠 |
Embedding:把文字变成向量
计算机不理解文字,只理解数字。Embedding 就是把文字翻译成 1536 维(或更多)的数字向量——语义越接近,向量的空间距离越近。
"苹果很好吃" → [0.12, -0.34, 0.56, ..., 0.78] (1536个数字)
"橙子很甜" → [0.14, -0.31, 0.58, ..., 0.79] (向量很近)
"汽车很快" → [-0.45, 0.67, -0.23, ..., -0.11] (向量很远)
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# 单个文本
vector = embeddings.embed_query("什么是 React Hooks?")
print(len(vector)) # 1536
# 批量(文档块)
chunk_vectors = embeddings.embed_documents(
[chunk.page_content for chunk in chunks]
)
向量数据库:存什么,怎么存
向量数据库专门存储"向量 + 文本"的配对:
存储结构:
{
"id": "chunk_001",
"vector": [0.12, -0.34, 0.56, ...], # 1536维向量
"metadata": {
"source": "react-guide.pdf",
"page": 15,
"chunk_index": 3
},
"text": "useEffect 是 React 中处理副作用的 Hook..."
}
检索时,把用户问题也 Embedding 化,在向量空间里找最近的 Top K 个块。
Chroma 快速上手
Chroma 是入门级的向量数据库,轻量到可以嵌入 Python 进程运行:
pip install chromadb
from langchain_community.vectorstores import Chroma
# 构建向量数据库
vectordb = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db", # 持久化到磁盘
)
# 检索
retriever = vectordb.as_retriever(search_kwargs={"k": 3})
docs = retriever.invoke("React 的 useEffect 怎么用?")
for doc in docs:
print(doc.page_content[:100])
检索:找到最相关的片段
# 相似度搜索
docs = vectordb.similarity_search("useEffect 的依赖数组")
# 带分数
docs_with_scores = vectordb.similarity_search_with_score("useEffect", k=5)
for doc, score in docs_with_scores:
print(f"相关度: {score:.2f} | {doc.page_content[:80]}")
# MMR 多样化检索(避免搜出 5 条几乎一样的内容)
docs = vectordb.max_marginal_relevance_search(
"useEffect", k=5, fetch_k=20, lambda_mult=0.5
)
本章小结
| 步骤 | 工具 | 要点 |
|---|---|---|
| 加载 | PyPDFLoader、TextLoader | 支持多种格式 |
| 切分 | RecursiveCharacterTextSplitter | chunk_size + overlap 决定检索质量 |
| 向量化 | OpenAIEmbeddings | 1536 维表征语义距离 |
| 存储 | Chroma | 轻量级向量数据库 |
| 检索 | similarity_search / MMR | Top-K 或多样化检索 |
下一章:构建第一个 RAG 链——把切分、向量化、检索、生成串成一条完整的管道。
📌 创作者: Yardon | 🏠 个人网站: GlimmerAI.top
📖 本章是「LangChain + RAG 实战」系列的第 2 章。
🌟 欢迎大家来观看!
更多推荐
所有评论(0)