Featured image of post 用 VPS 搭建完整 AI RAG 知识库:从向量数据库到文档解析的端到端方案

用 VPS 搭建完整 AI RAG 知识库:从向量数据库到文档解析的端到端方案

面向开发者和团队,用一台 $10/月 VPS 搭建完整的 AI RAG 知识库系统:ChromaDB/Qdrant 向量库、多格式文档解析、Embedding 模型自托管、检索质量调优和对话界面,从零到生产可用。

导语:把企业知识变成可对话的 AI 系统

你公司有多少知识躺在 PDF、Word、Notion 和 Slack 里?员工遇到问题要翻半天文档,新人上手慢,核心人员离职后经验流失。传统的关键词搜索解决不了「根据这份合同,我们的退款政策是什么」这类问题。

RAG(检索增强生成) 就是答案:把你的文档喂给 AI,让它基于你的私有知识回答,而不是靠训练数据里的过时信息。

2026 年,搭建一套完整的 RAG 系统不再需要 GPU 集群。一台 $10/月的 VPS 就能跑:文档解析流水线、向量数据库、Embedding 模型、检索调优工具和对话界面。

本文不是教你装一个 ChromaDB 容器就完事——我们会深入每个环节的最佳实践,从文档切分策略到检索质量评估,帮你搭建一个真正可用的生产级 RAG 系统。

说明:本文包含 VPS 服务商 affiliate 链接。通过链接购买,我们可能获得佣金,但不会影响你的价格。我们只推荐适合实际部署场景的海外服务。


RAG 系统架构全景

一套完整的 RAG 系统包含以下核心组件:

┌─────────────────────────────────────────────────────────┐
│                     用户对话界面                         │
│              (Open WebUI / Dify / 自建)                  │
└──────────────────────┬──────────────────────────────────┘
                       │ 用户提问
┌──────────────────────▼──────────────────────────────────┐
│                   检索层 (Retrieval)                     │
│  ┌──────────┐  ┌──────────┐  ┌──────────────────────┐   │
│  │ 查询重写  │  │ 混合检索  │  │ 重排序 (Reranker)    │   │
│  │ HyDE/    │  │ Dense+   │  │ BGE-Reranker         │   │
│  │ 扩展检索  │  │ Sparse   │  │                      │   │
│  └──────────┘  └──────────┘  └──────────────────────┘   │
┌──────────────────────┬──────────────────────────────────┐
│                   向量数据库                            │
│  ┌──────────┐  ┌──────────┐                              │
│  │ ChromaDB │  │  Qdrant   │  (二选一或并存)             │
│  └──────────┘  └──────────┘                              │
└──────────────────────┬──────────────────────────────────┘
                       │ 嵌入向量
┌──────────────────────▼──────────────────────────────────┐
│                 Embedding 模型                           │
│         (bge-large-zh / text-embedding-3-large)          │
└──────────────────────┬──────────────────────────────────┘
                       │ 原始文档
┌──────────────────────▼──────────────────────────────────┐
│                 文档解析流水线                           │
│  PDF → 文本  |  DOCX → 文本  |  HTML → 清洗文本          │
│  PPTX → 文本  |  图片 OCR  |  表格结构化提取              │
└─────────────────────────────────────────────────────────┘

关键设计决策:

  • 文档解析是 RAG 质量的上限——再好的检索模型也救不回被切碎的表格
  • 混合检索(向量 + 关键词)显著优于单一向量检索,尤其在专业术语场景
  • 重排序是性价比最高的检索优化手段,用一个小模型重新打分 Top-K 结果
  • VPS 选型取决于你的文档量和并发需求,但入门级完全够用

VPS 选型:RAG 系统需要多大配置?

RAG 系统对 VPS 的需求主要来自三个部分:向量数据库、Embedding 模型和文档解析服务。

配置参考表

规模vCPURAM存储月价区间适用场景
微型2核4GB40GB NVMe$5-8个人知识库,<1000 文档,单 Embedding 模型
小型4核8GB80GB NVMe$8-15团队知识库,<10000 文档,混合检索 + 重排序
中型4核16GB160GB NVMe$20-30企业知识库,高并发,多模型切换

推荐服务商

RackNerd 在入门级 VPS 市场性价比突出,4核8GB 年付约 $40-60(折合 $3-5/月):

👉 查看 RackNerd VPS 方案

Hostinger 的 KVM 2 计划(2核4GB)月付约 $6,KVM 4(4核8GB)约 $12,管理面板友好:

👉 查看 Hostinger VPS 方案

Vultr 提供灵活的按小时计费,4核8GB 纽约/新加坡节点约 $15/月,适合需要快速扩展的场景:

👉 查看 Vultr VPS 方案

我的建议:RAG 系统的向量数据库和 Embedding 模型对内存有一定要求。如果你的文档总量超过 5000 份或需要同时跑重排序模型,直接上 8GB RAM 起步。RackNerd 的年付方案能把成本压到极低,适合先跑起来验证。


Step 1:文档解析流水线 —— RAG 质量的基石

大多数 RAG 系统的瓶颈不在检索模型,而在文档解析质量。PDF 里的表格被切成碎片、扫描件没有 OCR、HTML 标签残留——这些问题会让后续所有环节功亏一篑。

核心工具链

# 使用 Docker Compose 部署文档解析服务
cat > ~/rag-doc-parser/docker-compose.yml << 'EOF'
version: '3.8'
services:
  parser:
    image: ghcr.io/nickhould/docling-server:latest
    ports:
      - "5001:5000"
    volumes:
      - ./uploads:/app/uploads
      - ./output:/app/output
    environment:
      - MAX_WORKERS=2
      - TIMEOUT=300

  # 可选:OCR 服务(处理扫描件 PDF)
  ocr:
    image: ghcr.io/nickhould/docling-server:latest
    command: ["--ocr"]
    ports:
      - "5002:5000"
EOF

cd ~/rag-doc-parser && docker compose up -d

文档处理策略

不同格式的文档需要不同的处理方式:

文档类型处理方式注意事项
PDF(文本型)直接提取文本 + 结构化注意分页符,避免跨页段落被切断
PDF(扫描型)OCR → 文本使用 Docling 或 Tesseract,中文需下载语言包
Word (.docx)直接解析 XML保留标题层级用于文档结构切分
PowerPoint逐页提取文本和图片备注栏内容也要提取
HTML/网页读取 + 清理标签使用 Readability 算法提取正文
Excel/CSV结构化表格处理保持行列关系,不要扁平化为纯文本
Markdown直接使用天然适合 RAG 的文档格式

文档切分策略

切分(Chunking)是 RAG 中最容易被低估的环节。切得太细会丢失上下文,切得太粗会引入噪声。

# 推荐的智能切分策略
from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=512,          # 每块 512 tokens
    chunk_overlap=64,        # 相邻块重叠 64 tokens(保留上下文)
    separators=[             # 优先在这些位置切分
        "\n\n",              # 段落之间
        "\n",                # 行之间
        ". ",                # 句子之间
        " ",                 # 单词之间
        "",                  # 强制切分
    ],
    length_function=len,
)

高级技巧:元数据增强切分

在切分时保留文档来源、章节标题、页码等元数据,检索时可以优先匹配相关章节:

# 每个 chunk 携带元数据
chunks = [
    {
        "text": "退款政策:订单签收后 7 天内可申请全额退款...",
        "metadata": {
            "source": "company-handbook.pdf",
            "section": "财务政策 > 退款流程",
            "page": 12,
            "document_type": "policy"
        }
    }
]

Step 2:向量数据库选型与部署

向量数据库是 RAG 的核心存储层,负责索引和检索文档的嵌入向量。

ChromaDB vs Qdrant 对比

特性ChromaDBQdrant
安装复杂度极简,单容器中等,支持集群
查询性能适合中小规模大规模高性能
过滤检索支持支持(更强大)
混合检索插件支持原生支持(稀疏 + 密集)
中文优化依赖 Embedding 模型依赖 Embedding 模型
内存占用~500MB~300MB
适合场景个人/小团队知识库企业级/高并发场景

ChromaDB 部署(推荐入门)

mkdir -p ~/rag-chroma && cd ~/rag-chroma

cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
  chroma:
    image: chromadb/chroma:latest
    ports:
      - "8000:8000"
    volumes:
      - chroma_data:/chroma/chroma
    environment:
      - ANONYMIZED_TELEMETRY=false
      - CHROMA_SERVER_AUTH_CREDENTIALS=admin:secure-password
      - CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER=chromadb.auth.simple_authn.SimpleAuthServerCredentialsProvider
      - CHROMA_SERVER_AUTH_PROVIDER=chromadb.auth.simple_authn.SimpleAuthServerAuthenticationProvider

volumes:
  chroma_data:
EOF

docker compose up -d

Qdrant 部署(推荐生产)

mkdir -p ~/rag-qdrant && cd ~/rag-qdrant

cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
  qdrant:
    image: qdrant/qdrant:latest
    ports:
      - "6333:6333"   # REST API
      - "6334:6334"   # gRPC
    volumes:
      - qdrant_data:/qdrant/storage
    environment:
      - QDRANT__SERVICE__API_KEY=your-secret-key

  # Qdrant 面板(可选)
  qdrant_dashboard:
    image: ghcr.io/qdrant/dashboard:latest
    ports:
      - "8080:80"
    environment:
      - QDRANT__API_KEY=your-secret-key

volumes:
  qdrant_data:
EOF

docker compose up -d

集合(Collection)配置

无论选择哪个向量数据库,合理的集合配置对检索质量影响巨大:

from qdrant_client import QdrantClient
from qdrant_client.models import (
    Distance,
    VectorParams,
    PayloadSchemaType,
)

client = QdrantClient(url="http://localhost:6333", api_key="your-secret-key")

client.create_collection(
    collection_name="knowledge_base",
    vectors_config=VectorParams(
        size=1024,              # Embedding 维度(bge-large 是 1024)
        distance=Distance.COSINE,  # 余弦相似度
    ),
)

# 为常用过滤字段创建_payload_ 索引
client.create_payload_index(
    collection_name="knowledge_base",
    field_name="document_type",
    field_schema=PayloadSchemaType.KEYWORD,
)

client.create_payload_index(
    collection_name="knowledge_base",
    field_name="source",
    field_schema=PayloadSchemaType.KEYWORD,
)

Step 3:Embedding 模型部署

Embedding 模型的质量直接决定了检索的准确度。对于中文知识库,推荐使用专门针对中文优化的模型。

模型选型

模型语言维度推荐场景
bge-large-zh-v1.5中文1024中文知识库首选,综合性能最佳
bge-m3多语言1024中英混合文档,支持稠密+稀疏+多粒度
text-embedding-3-large (OpenAI)多语言3072预算充足时,质量最高
m3e-base中文768轻量级中文场景
bge-small-zh-v1.5中文512资源受限时的快速方案

自托管 Embedding 服务

# 使用 FastAPI + Sentence Transformers 部署 Embedding API
mkdir -p ~/rag-embedding && cd ~/rag-embedding

cat > requirements.txt << 'EOF'
fastapi==0.115.0
uvicorn==0.30.6
sentence-transformers==3.2.0
pydantic==2.9.0
EOF

cat > main.py << 'PYEOF'
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from sentence_transformers import SentenceTransformer
from typing import List
import numpy as np

app = FastAPI(title="RAG Embedding Service")

# 加载模型(首次启动会下载,约 1.3GB)
model = SentenceTransformer("BAAI/bge-large-zh-v1.5")

class EmbedRequest(BaseModel):
    texts: List[str]
    normalize: bool = True

class EmbedResponse(BaseModel):
    embeddings: List[List[float]]
    model: str = "bge-large-zh-v1.5"

@app.post("/embed", response_model=EmbedResponse)
def embed_texts(request: EmbedRequest):
    try:
        embeddings = model.encode(
            request.texts,
            normalize_embeddings=request.normalize,
            batch_size=32,
        )
        return EmbedResponse(embeddings=embeddings.tolist())
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/health")
def health():
    return {"status": "ok", "model": "bge-large-zh-v1.5"}
PYEOF

# 启动服务
pip install -r requirements.txt
uvicorn main:app --host 0.0.0.0 --port 8001 --workers 2
# 测试 Embedding 服务
curl http://localhost:8001/embed \
  -H "Content-Type: application/json" \
  -d '{"texts": ["什么是退款政策?", "公司年假规定"], "normalize": true}'

Docker 化部署

cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
  embedding:
    build: .
    ports:
      - "8001:8001"
    volumes:
      - ~/.cache/huggingface:/root/.cache/huggingface  # 缓存模型文件
    deploy:
      resources:
        limits:
          memory: 4G
EOF

cat > Dockerfile << 'EOF'
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 预下载模型,避免首次请求超时
RUN python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('BAAI/bge-large-zh-v1.5')"
COPY main.py .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8001", "--workers", "2"]
EOF

Step 4:构建完整的 RAG 检索管线

文档解析 → Embedding → 存入向量库 → 检索 → 重排序 → 生成回答,这是一条完整的 RAG 管线。

使用 LangChain 编排管线

mkdir -p ~/rag-pipeline && cd ~/rag-pipeline

cat > requirements.txt << 'EOF'
langchain==0.3.0
langchain-community==0.3.0
langchain-core==0.3.0
langchain-text-splitters==0.3.0
chromadb==0.5.0
openai==1.40.0
tiktoken==0.7.0
EOF

cat > rag_pipeline.py << 'PYEOF'
"""
完整的 RAG 检索管线
"""
import os
from langchain_community.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_core.documents import Document
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.retrievers import EnsembleRetriever

# 配置
CHROMA_PATH = "./chroma_db"
EMBEDDING_URL = "http://embedding-service:8001"  # 自托管 Embedding
CHAT_MODEL = "gpt-4o-mini"  # 或使用自托管的 Qwen2.5-7B

class RAGPipeline:
    def __init__(self):
        # 1. 初始化 Embedding(优先使用自托管)
        self.embeddings = OpenAIEmbeddings(
            openai_api_key="self-hosted",
            base_url=EMBEDDING_URL,
            model="bge-large-zh-v1.5",
            dimensions=1024,
        )

        # 2. 文档切分器
        self.splitter = RecursiveCharacterTextSplitter(
            chunk_size=512,
            chunk_overlap=64,
            separators=["\n\n", "\n", ". ", " ", ""],
        )

        # 3. 向量数据库
        self.vectorstore = Chroma(
            persist_directory=CHROMA_PATH,
            embedding_function=self.embeddings,
        )

    def ingest_documents(self, texts: List[str], metadata: List[dict]):
        """导入文档到向量库"""
        documents = [
            Document(page_content=text, metadata=meta)
            for text, meta in zip(texts, metadata)
        ]

        chunks = self.splitter.split_documents(documents)
        self.vectorstore.add_documents(chunks)
        print(f"✅ 已导入 {len(chunks)} 个文档块")

    def retrieve(self, query: str, top_k: int = 5) -> list:
        """
        检索相关文档块
        使用混合检索:向量检索 + 关键词检索
        """
        # 向量检索
        vector_retriever = self.vectorstore.as_retriever(
            search_type="similarity",
            search_kwargs={"k": top_k},
        )

        # 相似度检索(考虑相关性)
        mmr_retriever = self.vectorstore.as_retriever(
            search_type="mmr",
            search_kwargs={"k": top_k, "fetch_k": 20},
        )

        # 混合检索
        ensemble = EnsembleRetriever(
            retrievers=[vector_retriever, mmr_retriever],
            weights=[0.7, 0.3],
        )

        return ensemble.invoke(query)

    def qa_chain(self, query: str, temperature: float = 0.1) -> str:
        """生成基于检索结果的回答"""
        docs = self.retrieve(query)

        # 构建上下文
        context = "\n\n".join([doc.page_content for doc in docs])

        # 调用 LLM 生成回答
        llm = ChatOpenAI(
            model=CHAT_MODEL,
            temperature=temperature,
            openai_api_key=os.getenv("OPENAI_API_KEY", "sk-placeholder"),
            base_url=os.getenv("OPENAI_API_BASE", "https://api.openai.com/v1"),
        )

        prompt = f"""基于以下参考资料回答问题。如果资料中没有相关信息,请如实说明。

参考资料:
{context}

问题:{query}

请给出详细、准确的回答,并在回答末尾标注引用的文档来源。"""

        response = llm.invoke(prompt)
        return response.content

# 使用示例
if __name__ == "__main__":
    pipeline = RAGPipeline()

    # 导入文档
    pipeline.ingest_documents(
        texts=["公司的退款政策是签收后 7 天内可申请全额退款..."],
        metadata=[{"source": "handbook.pdf", "document_type": "policy"}],
    )

    # 检索并回答
    answer = pipeline.qa_chain("我们的退款政策是什么?")
    print(answer)
PYEOF

检索质量评估

不要盲目相信检索效果。建立评估闭环:

def evaluate_retrieval(query: str, expected_docs: List[str]) -> dict:
    """
    评估检索质量
    - Precision@K: 检索结果中有多少是相关的
    - Recall@K: 所有相关文档中有多少被检索到了
    - MRR: 第一个相关文档的排名倒数平均
    """
    retrieved = pipeline.retrieve(query, top_k=5)

    relevant_count = sum(1 for doc in retrieved if doc.metadata.get("relevant"))
    precision = relevant_count / len(retrieved) if retrieved else 0

    return {
        "precision_at_5": precision,
        "retrieved_count": len(retrieved),
        "documents": [doc.metadata for doc in retrieved],
    }

Step 5:前端对话界面

有了后端管线,需要一个友好的界面让用户与知识库对话。

方案 A:Open WebUI(推荐)

Open WebUI 是一个功能丰富的开源 Web 界面,支持对话历史、多轮对话、文件上传:

mkdir -p ~/rag-webui && cd ~/rag-webui

cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    ports:
      - "3000:8080"
    environment:
      - OLLAMA_BASE_URL=http://embedding:11434
      - WEBUI_SECRET_KEY=your-secret-key
      - ENABLE_RAG_HYBRID_SEARCH=true
      - RAG_TOP_K=5
      - RAG_CHUNK_SIZE=512
      - RAG_CHUNK_OVERLAP=64
    volumes:
      - webui_data:/app/backend/data

volumes:
  webui_data:
EOF

docker compose up -d

访问 http://your-vps-ip:3000 即可使用。

方案 B:Dify(适合团队)

Dify 提供更完整的 AI 应用开发平台,内置 RAG 引擎:

# Dify 部署需要 4GB+ RAM
curl -o dify-env.txt https://raw.githubusercontent.com/langgenius/dify/main/docker/.env
# 修改配置指向你的自托管 Embedding 服务和向量数据库
sed -i 's/EMBEDDING_API_KEY=.*/EMBEDDING_API_KEY=your-key/' dify-env.txt
sed -i 's/VECTOR_STORE=.*/VECTOR_STORE=qdrant/' dify-env.txt
sed -i 's/QDRANT_URI=.*/QDRANT_URI=http://qdrant:6333/' dify-env.txt

docker compose -f docker/Compose.yaml up -d

方案 C:自建轻量界面

如果你只需要最简单的问答界面:

<!-- simple-rag.html - 直接部署到 Nginx -->
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <title>AI 知识库</title>
    <style>
      body {
        font-family: -apple-system, sans-serif;
        max-width: 800px;
        margin: 0 auto;
        padding: 20px;
      }
      .chat {
        border: 1px solid #ddd;
        border-radius: 8px;
        padding: 16px;
        min-height: 400px;
      }
      .message {
        margin: 8px 0;
        padding: 8px 12px;
        border-radius: 4px;
      }
      .user {
        background: #e3f2fd;
        text-align: right;
      }
      .ai {
        background: #f5f5f5;
      }
      input {
        width: 100%;
        padding: 12px;
        border: 1px solid #ddd;
        border-radius: 4px;
        box-sizing: border-box;
      }
      button {
        margin-top: 8px;
        padding: 12px 24px;
        background: #1976d2;
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
      }
    </style>
  </head>
  <body>
    <h1>📚 AI 知识库</h1>
    <div class="chat" id="chat"></div>
    <input
      type="text"
      id="query"
      placeholder="输入你的问题..."
      onkeypress="if(event.key==='Enter')ask()"
    />
    <button onclick="ask()">提问</button>

    <script>
      async function ask() {
        const query = document.getElementById("query").value;
        if (!query) return;

        // 显示用户问题
        addMessage(query, "user");
        document.getElementById("query").value = "";

        // 调用 RAG API
        const resp = await fetch("/api/rag/query", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ query }),
        });
        const data = await resp.json();
        addMessage(data.answer, "ai");
      }

      function addMessage(text, role) {
        const chat = document.getElementById("chat");
        const div = document.createElement("div");
        div.className = `message ${role}`;
        div.textContent = text;
        chat.appendChild(div);
        chat.scrollTop = chat.scrollHeight;
      }
    </script>
  </body>
</html>

配合 Nginx 反向代理即可部署。


成本对比:自托管 vs 云服务

方案月成本数据隐私定制性维护成本
自建 RAG(VPS)$8-15/月✅ 完全私有极高
ChatGPT Custom Instructions$20/月❌ 数据上云
Dify Cloud 版$29/月起⚠️ 服务商可控
Pinecone + GPT API$10-50/月❌ 数据分散

自建 RAG 的核心优势

  1. 数据不出域——合同、财务报表、客户数据完全留在你的 VPS
  2. 成本可预测——不管调用多少次,VPS 费用固定
  3. 模型可替换——今天用 bge-large-zh,明天换 bge-m3,随时切换
  4. 管线可定制——文档解析、切分策略、检索逻辑完全掌控

进阶优化:让 RAG 真正好用

1. 查询重写(Query Rewriting)

用户的原始问题往往不够精确。在检索前先改写查询:

def rewrite_query(original_query: str) -> List[str]:
    """
    将用户问题改写为多个角度的查询
    例如:"退款政策" → ["如何申请退款", "退款条件是什么", "退款流程"]
    """
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.5)
    prompt = f"""
    用户的问题是:"{original_query}"

    请将其改写为 3 个不同角度但意图相同的问题,用于文档检索。
    只输出问题列表,每行一个问题,不要其他内容。
    """
    response = llm.invoke(prompt)
    return [q.strip() for q in response.content.strip().split("\n")]

2. HyDE(假设性文档嵌入)

生成一个假设性的答案,用这个答案去做向量检索,通常比直接用问题检索更准确:

def hyde_retrieve(query: str) -> List[Document]:
    """
    HyDE: Hypothetical Document Embeddings
    先生成一个假设性文档,再用它做检索
    """
    llm = ChatOpenAI(model="gpt-4o-mini")
    prompt = f"""
    假设你有一份关于以下问题的完美文档,请生成这个文档的关键段落。
    问题:{query}

    只输出文档内容,不要解释。
    """
    hypothetical_doc = llm.invoke(prompt).content

    # 用假设性文档做检索
    return vectorstore.similarity_search(hypothetical_doc, k=5)

3. 重排序(Reranking)

先用向量检索取 Top-50,再用重排序模型精选 Top-5:

# 部署 BGE Reranker
docker run -d --name reranker \
  -p 8002:8000 \
  -e MODEL_PATH="BAAI/bge-reranker-v2-m3" \
  bge-reranker:latest
# 检索 + 重排序管线
def retrieve_with_rerank(query: str, top_k: int = 5) -> List[Document]:
    # 第一步:向量检索召回 Top-50
    candidates = vectorstore.similarity_search(query, k=50)

    # 第二步:用重排序模型精选 Top-5
    passages = [{"query": query, "passage": doc.page_content} for doc in candidates]
    scores = reranker_service.rank(passages)

    # 按分数排序,取 Top-K
    ranked_indices = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)
    return [candidates[i] for i in ranked_indices[:top_k]]

4. 文档时效性管理

知识库中的文档会过期,需要定期更新:

def manage_document_lifecycle():
    """
    文档生命周期管理
    - 新文档:解析 → Embed → 入库
    - 更新文档:删除旧版本 → 重新入库
    - 过期文档:标记为 deprecated,降低检索权重
    """
    # 检查文档最后更新时间
    # 超过 90 天的文档标记为待审核
    # 超过 180 天的文档归档
    pass

常见问题

Q:4核8GB 的 VPS 能跑多大的知识库?

对于 ChromaDB + bge-large-zh,8GB RAM 可以支撑约 5-10 万条文档块的向量索引。如果文档总量更大,建议切换到 Qdrant 并使用磁盘索引(DiskANN),可以将内存占用降低 60% 以上。

Q:需要 GPU 吗?

不需要。bge-large-zh 的 Embedding 推理在 CPU 上完全够用,单条文本约 50ms。如果你要同时处理大量文档的批量 Embedding,可以考虑 GPU 加速,但对日常检索来说 CPU 已经绰绰有余。

Q:中英文混合文档怎么处理?

推荐 bge-m3 模型,它原生支持多语言混合检索,不需要为中文和英文分别部署不同的 Embedding 模型。如果使用 bge-large-zh,建议对英文文档额外部署一个 English embedding 模型(如 text-embedding-3-small)。

Q:如何保证回答的准确性?

  1. 设置置信度阈值——检索结果的相关度低于阈值时,回答"我不知道"
  2. 引用来源——在每个回答中标注引用的文档片段
  3. 人工审核——对高频问题建立人工审核的知识条目
  4. 定期评估——用固定测试集评估检索和回答质量

Q:向量数据库的数据怎么备份?

# ChromaDB 备份
tar czf chroma-backup-$(date +%Y%m%d).tar.gz ./chroma_db/

# Qdrant 备份(在线热备)
curl -X POST http://localhost:6333/collections/knowledge_base/backup
# 备份文件存储在 ./backups/ 目录

# 建议:每日自动备份到对象存储(S3 兼容)
aws s3 cp ./backups/ s3://rag-backups/ --recursive

总结:用 $10/月搭建企业级 RAG 知识库

RAG 系统的核心价值不在于某个单一组件,而在于整条管线的协同:

  • 文档解析质量决定了信息的完整性
  • 向量数据库决定了检索效率
  • Embedding 模型决定了语义理解的准确度
  • 检索策略(混合检索 + 重排序)决定了最终召回质量
  • 前端界面决定了用户体验

一台 $8-15/月的 VPS(RackNerd / Hostinger / Vultr)完全可以承载这套系统的全部组件。数据完全私有,成本可控,模型随时可换。

快速起步建议

  1. 买一台 RackNerd 4核8GB VPS(年付约 $40-60)
  2. 用 Docker Compose 部署 ChromaDB + Embedding 服务 + Open WebUI
  3. 导入第一批文档,调整切分参数
  4. 用 HyDE + 重排序优化检索质量
  5. 建立定期更新和备份机制

👉 在 RackNerd 上搭建你的 RAG 知识库 👉 试试 Hostinger VPS(易用面板) 👉 Vultr 按小时计费,灵活扩展