🌟 LangChain 30 天保姆级教程 · Day 17|向量存储入门!用 Chroma 构建本地向量数据库,实现语义检索!

3 阅读4分钟

系列目标:30 天从 LangChain 入门到企业级部署
今日任务:理解 Embedding 原理 → 掌握 Chroma 基础用法 → 构建“文档入库 + 语义搜索”全流程!


🔍 一、为什么需要向量数据库?

在 Day 16 中,我们把 PDF、Word 等文档转成了文本块(chunks)。
但如何快速找到与用户问题最相关的片段

  • ❌ 关键词搜索:“退货” → 找不到“如何把商品退回去?”
  • ✅ 语义搜索:理解“退货” ≈ “退回去” ≈ “取消订单”

向量数据库就是为语义搜索而生

  1. 将文本转为高维向量(Embedding)
  2. 存入数据库
  3. 用户提问时,也将其转为向量
  4. 计算相似度(如余弦距离) ,返回最相近的文档

✅ 今天,我们就用 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 构建完整问答系统!