系列目标:30 天从 LangChain 入门到企业级部署
今日任务:理解 Embedding 原理 → 掌握 Chroma 基础用法 → 构建“文档入库 + 语义搜索”全流程!
🔍 一、为什么需要向量数据库?
在 Day 16 中,我们把 PDF、Word 等文档转成了文本块(chunks)。
但如何快速找到与用户问题最相关的片段?
- ❌ 关键词搜索:“退货” → 找不到“如何把商品退回去?”
- ✅ 语义搜索:理解“退货” ≈ “退回去” ≈ “取消订单”
向量数据库就是为语义搜索而生:
- 将文本转为高维向量(Embedding)
- 存入数据库
- 用户提问时,也将其转为向量
- 计算相似度(如余弦距离) ,返回最相近的文档
✅ 今天,我们就用 Chroma(轻量、嵌入式、开源)搭建本地向量库!
🧱 二、技术栈说明
表格
| 组件 | 作用 |
|---|---|
OllamaEmbeddings | 使用 Ollama 提供的本地 Embedding 模型(如 nomic-embed-text) |
Chroma | 轻量级向量数据库,支持持久化、元数据过滤 |
RecursiveCharacterTextSplitter | 文本分块(来自 Day 16) |
💡 全部组件无需 API Key、无需网络、完全本地运行!
🛠️ 三、动手实践:构建端到端语义检索系统
步骤 1:安装依赖
pip install langchain-chroma chromadb
# Ollama 已安装(含 embedding 模型)
⚠️ 注意:Chroma 需要
chromadb>=0.4.0
步骤 2:准备 Embedding 模型
# day17_chroma_vectorstore.py
from langchain_ollama import OllamaEmbeddings
# 使用 Ollama 提供的开源 Embedding 模型
# 首次运行会自动下载(约 270MB)
embeddings = OllamaEmbeddings(model="nomic-embed-text")
✅
nomic-embed-text是目前最强的开源中文 Embedding 模型之一,支持 8192 token 上下文!
步骤 3:加载并分块文档(复用 Day 16)
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 加载 PDF
loader = PyPDFLoader("docs/company_policy.pdf")
docs = loader.load()
# 分块
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", "。", "!", "?", ";", " ", ""]
)
chunks = text_splitter.split_documents(docs)
print(f"📄 文档分块完成,共 {len(chunks)} 个片段")
步骤 4:创建 Chroma 向量库(内存版)
from langchain_chroma import Chroma
# 创建向量库(内存中)
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings
)
# 测试检索
query = "员工如何申请年假?"
retrieved_docs = vectorstore.similarity_search(query, k=2)
print(f"\n🔍 查询:{query}")
for i, doc in enumerate(retrieved_docs, 1):
print(f"\n【片段 {i}】(来源: {doc.metadata.get('source')}, 页码: {doc.metadata.get('page')})")
print(doc.page_content[:200] + "...")
▶️ 输出示例:
🔍 查询:员工如何申请年假?
【片段 1】(来源: company_policy.pdf, 页码: 12)
根据公司规定,员工需提前5个工作日通过HR系统提交年假申请,部门经理审批后生效...
【片段 2】(来源: company_policy.pdf, 页码: 13)
年假不可跨年度累计,未休年假将按日薪200%折算补偿...
✅ 成功实现语义匹配!即使问题中没出现“规定”“审批”等词。
💾 四、持久化:保存向量库到磁盘
避免每次重启都重新 embedding(耗时!):
# 持久化路径
persist_directory = "./chroma_db"
# 创建并保存
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory=persist_directory
)
vectorstore.persist() # 显式保存(新版本可省略)
# 后续加载(无需重新 embedding!)
loaded_vectorstore = Chroma(
persist_directory=persist_directory,
embedding_function=embeddings
)
✅ 再次启动时,直接加载已有向量,速度提升 10 倍!
🔍 五、高级用法:带元数据过滤的检索
假设文档有 category 元数据:
# 添加元数据(示例)
enriched_chunks = []
for chunk in chunks:
chunk.metadata["category"] = "hr_policy" # 或从文件名提取
enriched_chunks.append(chunk)
# 创建带元数据的向量库
vectorstore = Chroma.from_documents(enriched_chunks, embeddings)
# 只检索 HR 类政策
results = vectorstore.similarity_search(
"年假",
k=1,
filter={"category": "hr_policy"} # 过滤条件
)
💡 适用于多知识库隔离(如:技术文档 vs 客服 FAQ)
⚠️ 六、注意事项 & 最佳实践
表格
| 问题 | 建议 |
|---|---|
| 首次 embedding 慢 | 使用 SSD;或预生成向量缓存 |
| 中文检索不准 | 确保使用 nomic-embed-text 等中文优化模型 |
| 相似度分数缺失 | 用 similarity_search_with_score() 获取分数 |
| 内存占用高 | Chroma 支持 HNSW 索引(自动启用),适合百万级 |
| 生产环境扩展 | 考虑 Milvus、Weaviate、PGVector(Day 25 讲) |
💡 调试技巧:
- 打印
retrieved_docs[0].metadata查看来源- 用
query="随便问"测试召回多样性
📦 七、配套代码结构
langchain-30-days/
└── day17/
├── build_vectorstore.py # 构建并保存 Chroma 向量库
├── query_vectorstore.py # 加载并检索
└── docs/
└── company_policy.pdf # 示例文档
📝 八、今日小结
- ✅ 理解了向量数据库在 RAG 中的核心作用
- ✅ 学会了用
OllamaEmbeddings生成本地 Embedding - ✅ 掌握了 Chroma 的创建、检索、持久化三板斧
- ✅ 实现了带元数据过滤的精准检索
- ✅ 知道了本地向量库的性能边界
🎯 明日预告:Day 18 —— RAG 终极整合!用 RetrievalQA Chain 构建完整问答系统!