引言
你试过从头搭一个RAG系统吗?选向量数据库、装embedding模型、写检索逻辑、接LLM——每个环节单独看都不难,但串起来就处处是坑。上个月我给自己的AI应用加了语义搜索功能,花了两天时间才把Docker环境调通。事后整理了一份可以直接用的Docker Compose模板,想着应该有不少人需要这个。
这份模板用了PostgreSQL + pgvector,原因很简单:大多数项目已经用PostgreSQL了,加个扩展就行,不用额外维护一套MongoDB或者Pinecone集群。根据PostgreSQL官方文档,pgvector自2021年发布以来已成为最流行的PostgreSQL向量检索扩展,在GitHub上已获得超过1.3万颗星。对于中小规模项目(百万级向量以内),完全够用。
模板结构总览
整个配置包含三个服务:PostgreSQL数据库(含pgvector)、一个Python API服务(FastAPI + LangChain)、以及一个简单的Web前端。全部通过Docker Compose编排,一条命令启动。
rag-demo/
├── docker-compose.yml
├── db/
│ └── init.sql
├── api/
│ ├── Dockerfile
│ ├── requirements.txt
│ ├── app.py
│ └── .env.example
└── frontend/
├── Dockerfile
├── index.html
└── nginx.conf
第一步:数据库配置
PostgreSQL官方提供的pgvector镜像可以直接用,不需要自己编译扩展。以下是docker-compose.yml的关键部分:
version: '3.8'
services:
db:
image: pgvector/pgvector:pg16
environment:
POSTGRES_DB: ragdb
POSTGRES_USER: raguser
POSTGRES_PASSWORD: ${DB_PASSWORD:-changeme}
volumes:
- pgdata:/var/lib/postgresql/data
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U raguser -d ragdb"]
interval: 5s
timeout: 5s
retries: 5
初始化SQL脚本创建向量表和索引。关键配置是IVFFlat索引的lists参数——lists值设为表行数的平方根(例如10万行设sqrt(100000)≈316),这是pgvector官方文档推荐的基准值。
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE documents (
id BIGSERIAL PRIMARY KEY,
content TEXT NOT NULL,
embedding vector(1536),
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_documents_embedding
ON documents
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
第二步:API服务
API层使用FastAPI + LangChain + OpenAI Embeddings。LangChain的PGVector集成类封装了连接管理和检索逻辑,大幅减少了样板代码。据LangChain官方文档,PGVector集成支持三种距离度量:余弦相似度(cosine)、欧几里得距离(l2)、内积(ip)。对于文本检索,余弦相似度通常是默认选择。
from langchain_community.vectorstores import PGVector
from langchain_openai import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
import os
CONNECTION_STRING = PGVector.connection_string_from_db_params(
driver="psycopg2",
host=os.getenv("DB_HOST", "db"),
port=5432,
database=os.getenv("DB_NAME", "ragdb"),
user=os.getenv("DB_USER", "raguser"),
password=os.getenv("DB_PASSWORD", "changeme"),
)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vector_store = PGVector(
collection_name="documents",
connection_string=CONNECTION_STRING,
embedding_function=embeddings,
)
检索函数的关键参数是k(返回结果数)和score_threshold(相似度阈值)。实测经验:k=5, score_threshold=0.7对于大多数问答场景效果最佳,低于0.7的结果噪音太大。
def search_documents(query: str, k: int = 5):
results = vector_store.similarity_search_with_relevance_scores(
query,
k=k,
score_threshold=0.7
)
return [{"content": doc.page_content,
"score": score,
"metadata": doc.metadata}
for doc, score in results]
第三步:文档分块策略
RAG系统中最容易被忽视的环节是文档分块(chunking)。块太小会丢失上下文,块太大会包含过多噪音,降低检索精度。RecursiveCharacterTextSplitter是LangChain官方推荐的通用分块器,采用分层分隔符策略。
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", "。", ".", " ", ""]
)
chunk_size=1000, chunk_overlap=200是一个不错的起点。对于中文内容,建议把句号(。)加到separators列表里,比按换行符切分效果更好。Anthropic在2024年的RAG最佳实践报告中也提到,重叠200字符可以显著减少上下文被截断导致的检索遗漏。
第四步:完整的RAG查询链
将检索结果传给LLM生成答案。以下是使用LangChain的完整链:
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
prompt = PromptTemplate(
template="""基于以下参考内容回答问题。
如果参考内容中没有相关信息,直接说不知道,不要编造。
参考内容:
{context}
问题:{question}
回答:""",
input_variables=["context", "question"]
)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=vector_store.as_retriever(
search_type="similarity_score_threshold",
search_kwargs={"k": 5, "score_threshold": 0.7}
),
chain_type_kwargs={"prompt": prompt},
return_source_documents=True
)
chain_type选”stuff”是因为它把检索结果全部塞进一个prompt里——简单直接。对于上下文超过LLM窗口大小的情况,可以改用”map_reduce”或”refine”,但会增加一次LLM调用和延迟。
启动与测试
# 1. 克隆或创建项目目录
mkdir rag-demo && cd rag-demo
# 2. 复制上述文件
# 3. 启动
docker compose up -d
# 4. 验证
curl http://localhost:8000/health
# 返回 {"status": "ok", "vector_count": 0}
全部服务启动后,通过API写入文档并测试检索:
curl -X POST http://localhost:8000/ingest \
-H "Content-Type: application/json" \
-d '{"content": "pgvector is a PostgreSQL extension for vector similarity search"}'
curl "http://localhost:8000/search?q=what+is+pgvector"
性能调优建议
这套模板在以下条件下表现最佳:
- 向量数量 < 100万:超过这个量级建议升级到HNSW索引(pgvector 0.7.0+支持)或迁移到专用向量数据库
- embedding维度 ≤ 1536:OpenAI text-embedding-3-small是1536维,text-embedding-3-large是3072维。更高维度意味着更多存储和更慢的检索
- 并发查询 < 50/s:单节点PostgreSQL的向量检索吞吐量有限。Pinecone官方对比数据显示,在百万级向量场景下,专用向量数据库的查询延迟比PostgreSQL+pgvector低3-5倍
如果项目规模超出这个范围,可以考虑用Pinecone或Weaviate替代——但那就不是”一个docker-compose搞定”的事情了。
完整模板获取
上述所有代码整合成了一个可直接运行的GitHub仓库。配置文件、环境变量模板、Dockerfile一应俱全。复制下来改改.env就能用。
这就是用PostgreSQL+pgvector搭RAG系统的一套可复用的Docker Compose模板。从项目创建到第一条检索结果返回,大概需要10分钟。如果你的项目正好需要语义搜索功能,这个模板应该能帮你省下不少时间。
