8年Java开发者AI转型第二周:向量数据库与Embedding实战 (Day 11-12)
系列:8年Java开发者AI转型第二周
上一篇:RAG系统深入 + 向量数据库实战(Day 8-10)
本期内容:PgVector 替代 ChromaDB、PostgreSQL 扩展向量检索、生产级 RAG 系统
一、背景
上一篇我们搭起了一个能工作的 RAG 系统,用的是 ChromaDB —— 轻量、本地、零配置,非常适合原型开发。
但原型和生产之间隔着一条巨大的鸿沟:
| 维度 | ChromaDB | PostgreSQL + pgvector |
|---|---|---|
| 部署 | 本地文件,单机 | 分布式集群,高可用 |
| 并发 | 单进程读写 | 连接池,多并发 |
| 事务 | 无 ACID | 完整事务支持 |
| 权限 | 无 | RBAC 角色控制 |
| 运维 | 手动备份 | 快照、流复制、 PITR |
打个 Java 类比:
- ChromaDB =
ArrayList<HashMap>,开发快,但扛不住量 - PgVector = MySQL + MyBatis,企业级标配,能水平扩展
本期目标:把 RAG 系统的向量数据库从 ChromaDB 迁移到 PgVector,让系统更接近生产环境。
二、PgVector 前置知识
2.1 什么是 pgvector
pgvector 是 PostgreSQL 的一个扩展,给 Postgres 增加了向量数据类型和向量相似度搜索能力。
安装只需要三步:
-- 1. 安装扩展(在任意数据库中执行)
CREATE EXTENSION vector;
-- 2. 创建一张表存储向量
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
content TEXT,
embedding vector(1536) -- 1536 是向量维度,取决于 Embedding 模型
);
-- 3. 创建索引加速检索
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops);
Java 类比:
CREATE EXTENSION vector≈ 在 Maven 里加一个依赖vector(1536)≈ 定义一个float[1536]字段ivfflat索引 ≈ 给数据库字段加 B-Tree 索引
2.2 向量相似度算法
pgvector 支持四种相似度度量:
| 算法 | 符号 | 说明 | Java 类比 |
|---|---|---|---|
| 余弦相似度 | <=> | 角度差,不看长度 | Vector3D.angleBetween() |
| 内积 | <#> | 长度×角度 | dotProduct() |
| 欧氏距离 | <-> | 直线距离 | Math.hypot(dx, dy, dz) |
| 曼哈顿距离 | <+> | 网格距离 | 不太常用 |
生产推荐:余弦相似度(<=>),和 ChromaDB 的 cosine 一致。
2.3 索引类型
pgvector 有两种索引:
- ivfflat:适合中小数据集(百万级以下),建索引快
- hnsw:适合大数据集(千万级以上),查询更快但建索引慢
-- ivfflat 索引(快速上手)
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
-- hnsw 索引(高性能)
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
经验值:
lists = 向量数 / 10(ivfflat),m = 16, ef_construction = 64(hnsw)。
三、从 ChromaDB 到 PgVector 的迁移
3.1 架构对比
ChromaDB 架构:
文档 → Embedding → ChromaDB (本地文件) → 余弦相似度检索
PgVector 架构:
文档 → Embedding → PostgreSQL (vector 列) → IVFFlat/HNSW 索引检索
差别不大,核心变化只是存储层。
3.2 LangChain 集成
LangChain 提供了 langchain-postgres 包,让 PgVector 的使用方式和 ChromaDB 几乎一样:
from langchain_postgres import PGVector
from langchain_postgres.vectorstores import PGVector as PGVectorStore
# ChromaDB 写法
vectorstore = Chroma(
persist_directory="./chroma_db",
embedding_function=embeddings,
)
# PgVector 写法
# PgVector 连接信息
PG_CONNECTION = (
"postgresql://root:rootPassword@localhost:5432/rag_db"
)
COLLECTION_NAME = "kkfile_documents"
vectorstore = PGVectorStore(
collection_name=COLLECTION_NAME,
embeddings=embeddings,
connection=PG_CONNECTION,
use_jsonb=True,
)
看到没有?API 几乎一模一样。这就是 LangChain 的价值——屏蔽底层存储差异。
Java 类比:就像 JPA 的 EntityManager,底层从 H2 换成 MySQL,业务代码几乎不用改。
3.3 完整迁移代码
把上一篇的 RAGqa.py 改成 PgVector 版本:
# step07_pgvector.py — PgVector 完整 RAG 系统
import os
import json
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma # 旧版
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from BigModelEmbedding import BigModelEmbeddingFunc
from langchain_postgres.vectorstores import PGVector as PGVectorStore
# ── 配置 ──
with open("config_bigmodel.json", "r", encoding="utf-8") as f:
config = json.load(f)
EMBEDDING_KEY = config["embedding"]["api_key"]
DEEPSEEK_KEY = config["deepseek"]["api_key"]
DEEPSEEK_URL = config["deepseek"]["base_url"]
DEEPSEEK_MODEL = config["deepseek"]["model"]
# PgVector 连接
PG_CONNECTION = "postgresql://root:rootPassword@localhost:5432/rag_db"
COLLECTION_NAME = "kkfile_documents"
# ── 1. 加载文档 ──
doc_path = "doc/kkfile.md"
docs = TextLoader(doc_path, encoding="utf-8").load()
print(f"加载了 {len(docs)} 篇文档")
# ── 2. 文本分割 ──
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_documents(docs)
print(f"分成 {len(chunks)} 个块")
# ── 3. 向量化 + 存储到 PgVector ──
embeddings = BigModelEmbeddingFunc(api_key=EMBEDDING_KEY)
vectorstore = PGVectorStore(
collection_name=COLLECTION_NAME,
embeddings=embeddings,
connection=PG_CONNECTION,
use_jsonb=True,
)
# 检查是否已有数据
count = vectorstore.collection_metadata.get("item_count", 0)
if count > 0:
print(f"加载已有 PgVector 集合,共 {count} 条记录")
else:
vectorstore.add_documents(chunks)
print(f"新建 PgVector 集合,存入 {len(chunks)} 个分块")
# ── 4. 构建 RAG 管道 ──
retriever = vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 5},
)
prompt = PromptTemplate.from_template("""请严格依据下面参考资料回答用户问题,给出回答的依据文件,不要编造内容:
参考资料:{context}
用户问题:{input}
""")
llm = ChatOpenAI(
model=DEEPSEEK_MODEL,
temperature=0.7,
api_key=DEEPSEEK_KEY,
base_url=DEEPSEEK_URL,
)
rag_chain = (
{"context": retriever, "input": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
# ── 5. 问答测试 ──
print("========== RAG问答系统 (PgVector版) ==========")
questions = [
"kkFileView支持哪些文档格式?",
"kkFileView的官网地址是什么?",
"v5.0.0版本有什么新功能?",
]
for q in questions:
print(f"\n Q: {q}")
result = rag_chain.invoke(q)
print(f" A: {result[:200]}...")
# ── 6. 元数据过滤查询 ──
print("\n[6] 元数据过滤查询...")
retriever_filtered = vectorstore.as_retriever(
search_type="similarity",
search_kwargs={
"k": 3,
"filter": {"source_file": "doc/kkfile.md"},
},
)
filtered_result = retriever_filtered.invoke("kkFileView支持哪些文档格式?")
print(f" 过滤后检索到 {len(filtered_result)} 条相关文档")
for i, doc in enumerate(filtered_result[:2]):
print(f" [{i+1}] 来源: {doc.metadata.get('source_file', 'N/A')}")
print(f" 内容: {doc.page_content[:80]}...")
print("\n" + "=" * 60)
print("Step 07 完成 — 这就是 RAG 系统!")
print("=" * 60)
3.4 运行效果
============================================================
Step 07 — PgVector 完整 RAG 系统
============================================================
[1] 加载文档...
加载了 1 篇文档
[2] 文本分割 (chunk_size=500, overlap=50)...
分成 40 个块
[3] 向量化 + 存储到 PgVector...
新建 PgVector 集合,存入 40 个分块
[4] 构建 RAG 管道...
[5] 问答测试...
Q: kkFileView支持哪些文档格式?
A: 根据参考资料,kkFileView 支持以下文档格式:
* **Office 办公文档**:doc, docx, xls, xlsx, xlsm, ppt, pptx, csv, tsv, dotm, xlt, xltm, dot, dotx, xlam, xla, pages, pptm
* **国产 WPS Office 办公文档**:wps, dps, et, ett, wpt
*...
Q: kkFileView的官网地址是什么?
A: 根据参考资料,kkFileView 的官网地址是 **https://kkview.cn**。
**依据文件:**
- 文档中明确列出:“15. 官网建设:[https://kkview.cn](https://kkview.cn/)”(来自 `2020年05月20日` 更新记录)。
- 在“官网及文档”部分也直接给出:“地址:[https://kkview.cn](https://kkview...
Q: v5.0.0版本有什么新功能?
A: 根据参考资料中 v5.0.0 版本的发布记录,该版本仅列出了优化内容,**未明确提及任何“新功能”**。具体优化项如下:
1. xlsx 前端解析优化 - 提升Excel文件前端渲染性能
2. 图片解析优化 - 改进图片处理机制
3. tif 解析优化 - 增强TIF格式支持
4. svg 解析优化 - 优化SVG矢量图渲染
5. json 解析优化 - 改进JSON文件处理 ...
[6] 元数据过滤查询...
过滤后检索到 0 条相关文档
============================================================
Step 07 完成 — 这就是 RAG 系统!
============================================================
四、PgVector 的高级功能
4.1 元数据过滤
ChromaDB 支持 where 过滤,PgVector 通过 filter 参数实现,语法更灵活:
# ChromaDB 写法
retriever = vectorstore.as_retriever(
search_kwargs={"k": 5, "where": {"source": "note1"}}
)
# PgVector 写法(支持 $and, $or, $gte 等操作符)
retriever = vectorstore.as_retriever(
search_kwargs={
"k": 5,
"filter": {
"$and": [
{"source_file": {"$like": "%.pdf"}},
{"file_type": ".pdf"},
]
},
}
)
Java 类比:这就像 MyBatis 的 <if> 动态 SQL,filter 就是 WHERE 子句。
4.2 混合检索
PgVector 支持向量检索 + 全文检索的混合模式:
-- 在 PostgreSQL 中,用 tsvector 做全文检索
ALTER TABLE documents ADD COLUMN content_tsvector tsvector;
UPDATE documents
SET content_tsvector = to_tsvector('chinese', content);
CREATE INDEX idx_content_search ON documents USING gin(content_tsvector);
-- 混合查询:向量相似度 + 关键词匹配
SELECT *,
0.7 * (1 - (embedding <=> target_embedding)) +
0.3 * ts_rank(content_tsvector, plainto_tsquery('chinese', '关键词'))
AS hybrid_score
FROM documents
ORDER BY hybrid_score DESC
LIMIT 5;
中文全文检索需要用
zhparser或pg_jieba分词插件,这是 ChromaDB 做不到的。
4.3 批量操作
# 批量添加(和 ChromaDB 一样高效)
vectorstore.add_documents(new_chunks)
# 批量更新
vectorstore.update(collection_name="kkfile_documents", ...)
# 批量删除
vectorstore.delete(where={"source_file": "old_doc.md"})
五、ChromaDB vs PgVector 对比
5.1 功能对比
| 功能 | ChromaDB | PgVector |
|---|---|---|
| 向量存储 | ✅ | ✅ |
| 余弦相似度 | ✅ | ✅ |
| HNSW 索引 | ✅ | ✅ |
| 元数据过滤 | ✅ where | ✅ filter(更强大) |
| 全文检索 | ❌ | ✅(配合 tsvector) |
| 事务支持 | ❌ | ✅(ACID) |
| 多租户隔离 | 手动 | ✅(Schema/Row Level Security) |
| 连接池 | ❌ | ✅(PgBouncer) |
| 运维 | 本地文件 | 标准 PostgreSQL 运维体系 |
5.2 选型建议
原型开发 / 个人项目 / 小规模 (< 10万条)
→ ChromaDB(零配置,上手快)
生产环境 / 企业级 / 大规模 (> 10万条)
→ PgVector(已有 PostgreSQL 的话)
需要全文检索 + 向量检索混合
→ PgVector(PostgreSQL 原生支持)
需要多租户隔离
→ PgVector(Schema 级隔离)
5.3 Java 类比总结
| 概念 | ChromaDB | PgVector |
|---|---|---|
| 数据库实例 | PersistentClient(path=...) | postgres://localhost:5432/rag_db |
| 集合/表 | Collection | TABLE documents |
| 插入 | coll.add(documents=[...]) | INSERT INTO documents (content, embedding) |
| 查询 | coll.query(query_texts=[...]) | SELECT * FROM documents ORDER BY embedding <=> ? LIMIT 5 |
| 过滤 | where={"source": "note1"} | WHERE source_file LIKE '%.pdf' |
| 索引 | 自动创建 | CREATE INDEX ... USING ivfflat |
六、知识地图回顾
经过前五期的学习,你已经掌握了 RAG 的全套基础技能:
| 期数 | 知识点 | 类比(Java) |
|---|---|---|
| Day 1-2 | Python 速学 + 环境搭建 | 学会新语言 |
| Day 3-5 | API 调用 + Prompt 工程 + 参数调优 | 学会跟 AI 对话 |
| Day 5-7 | Embedding + ChromaDB + LangChain RAG | 学会让 AI 读你的文档 |
| Day 8-10 | 多格式解析 + 分割策略 + 元数据 | 让 AI 读懂各种格式 |
| Day 11-12 | PgVector 向量数据库实战 | 从原型到生产 |
七、RAG 全流程一图流
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ kkfile.md │───▶│ TextSplitter │───▶│ Chunks │
│ (原始文档) │ │ (切分成小块) │ │ (500字/块) │
└─────────────┘ └──────────────┘ └──────┬──────┘
│
┌─────────▼─────────┐
│ BigModelEmbedding │
│ (文本→向量) │
└─────────┬─────────┘
│
┌─────────▼─────────┐
│ PostgreSQL │
│ + pgvector 扩展 │
│ (向量持久化存储) │
└─────────┬─────────┘
│
┌─────────────────▼─────────────────┐
│ 用户提问 │
│ "kkFileView官网地址?" │
└─────────────────┬─────────────────┘
│
┌─────────▼─────────┐
│ Retriever │
│ (PgVector 向量检索)│
└─────────┬─────────┘
│
┌─────────▼─────────┐
│ PromptTemplate │
│ (组装上下文+问题) │
└─────────┬─────────┘
│
┌─────────▼─────────┐
│ ChatOpenAI │
│ (DeepSeek生成) │
└─────────┬─────────┘
│
┌─────────▼─────────┐
│ 精准回答 │
│ "官网地址是..." │
└───────────────────┘
八、下一步
下期我们将深入 RAG 系统的进阶优化:
- 混合检索:向量检索 + BM25 关键词检索,兼顾语义和精确匹配
- 重排序(Reranking):Cohere Rerank / BGE Reranker,召回 Top-10 后精排 Top-2
- 查询改写(Query Rewriting):让用户的问题更适合检索
- 引用溯源:让 AI 回答标注来源,增强可信度
本文代码基于 LangChain 1.x + DeepSeek API + PgVector,完整项目见:ai-learning