想让大模型回答你私有文档里的问题,直接把全文塞进去既不现实也很贵。RAG(检索增强生成)的做法是:先把文档建成可检索的知识库,提问时只把最相关的片段喂给模型。这篇教程用 Python 从零搭一个能真正跑起来的本地 RAG。

整体架构

RAG 分离线建库和在线问答两个阶段:

前置准备

创建虚拟环境并安装依赖:

python -m venv venv
# Windows
venv\Scripts\activate
# macOS / Linux
source venv/bin/activate

pip install chromadb sentence-transformers openai
  • chromadb:轻量的本地向量数据库
  • sentence-transformers:本地免费的 Embedding 模型
  • openai:调用大模型生成最终答案

步骤一:准备文档

真实项目里可用 langchain 的加载器读取 PDF、Markdown。这里先用一个示例列表说明:

documents = [
"Hexo 是一个基于 Node.js 的静态博客生成器。",
"Butterfly 是一款流行的 Hexo 主题,支持丰富的配置。",
"RAG 通过检索外部知识库来增强大模型的回答。",
]

步骤二:切分成 chunk

长文档要切成小段分别向量化。段太长检索不准,太短语义不全:

def split_docs(docs, chunk_size=100, overlap=20):
chunks = []
for doc in docs:
# 这里按字符切,实际可按段落或句子
for i in range(0, len(doc), chunk_size - overlap):
chunks.append(doc[i:i + chunk_size])
return chunks

chunks = split_docs(documents)

overlap 是相邻 chunk 的重叠字数,避免句子被切断而丢失语义。

步骤三:向量化并入库

用 Embedding 模型把每个 chunk 转成向量,存进 Chroma:

import chromadb
from sentence_transformers import SentenceTransformer

model = SentenceTransformer("all-MiniLM-L6-v2")
client = chromadb.Client()
collection = client.create_collection("knowledge")

embeddings = model.encode(chunks).tolist()
collection.add(
documents=chunks,
embeddings=embeddings,
ids=[f"chunk_{i}" for i in range(len(chunks))],
)

步骤四:检索相关片段

把问题同样向量化,在库里找出最相似的 top-k:

def retrieve(question, k=2):
q_emb = model.encode([question]).tolist()
result = collection.query(query_embeddings=q_emb, n_results=k)
return result["documents"][0]

步骤五:拼接提示词并生成

把检索到的片段和问题组装成提示词,交给大模型作答:

import os
from openai import OpenAI

llm = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

def ask(question):
contexts = retrieve(question)
context_text = "\n".join(contexts)
prompt = f"""请仅根据以下资料回答问题,资料中没有的信息请回答"不知道"。

资料:
{context_text}

问题:{question}
"""
resp = llm.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
)
return resp.choices[0].message.content

print(ask("Butterfly 是什么?"))

运行后,模型会基于检索到的资料回答,而不是凭空编造。

在线问答的完整流程

优化方向

跑通之后,可以从这几处继续提升效果:

  • 切分策略:按句子或段落切,比按字符更合理
  • 混合检索:叠加 BM25 关键词检索,弥补向量对专有名词的弱项
  • 重排序:检索后用 rerank 模型对 top-k 精排
  • 更强的 Embedding:中文场景换用 bge、m3e 等中文模型

小结

一个最小可用的 RAG 只需五步:切分、向量化、入库、检索、生成。本地方案用 Chroma + sentence-transformers 就能零成本跑通,非常适合快速验证。理解了这条链路,日后迁移到 Milvus、Elasticsearch 等生产级组件,就只是规模问题。