HelloAI
L4 第 3 篇 🐣 难度 🕒 17 分钟

RAG 从 0 到 1:让 LLM 基于你的数据回答

企业 AI 应用 90% 都是 RAG。这一篇带你搭一个能跑的 RAG 系统——从分块到部署。

阿莱
2026/7/5

L0-04 我们讲过:RAG 是 LLM 幻觉的最实用解药

RAG 可视化 我们看过整个流程。 这一篇打开每一步——从零搭建一个能跑的 RAG 系统

你可以照着这一篇用 100 行 Python 跑出来。

为什么需要 RAG

裸 LLM 的两大问题:

  1. 知识有截止日期——训练数据到某个时间为止,之后的事不知道
  2. 不知道你的私有数据——你公司文档、你客户邮件、你 wiki

RAG 是怎么解决的?

检索(Retrieval) → 增强(Augmented) → 生成(Generation)

把 LLM 当作”会读材料、会写人话”的实习生。 你不让它凭记忆答,你给它材料让它现读现答

整个流程(5 步)

1. 索引阶段(离线一次性)
   你的文档 → 分块 → 向量化 → 存到向量库

2. 查询阶段(每次用户提问)
   用户问题 → 向量化 → 在向量库里查相似 → 取 top-K → 塞进 prompt → LLM 回答

一步一步搭

Step 1:准备文档

假设你有一个企业 Wiki:

docs/
├── product_intro.md
├── pricing.md
├── faq.md
├── tutorial.md
└── ...

读进 Python:

import os

documents = []
for filename in os.listdir('docs/'):
    with open(f'docs/{filename}', 'r', encoding='utf-8') as f:
        documents.append({
            'source': filename,
            'text': f.read()
        })

Step 2:分块(Chunking)

为什么不直接整篇塞 LLM?

  • LLM 上下文有限(GPT-4 是 128k token)
  • 每次塞太多内容浪费钱
  • “找精确的小段” 比 “塞一整篇” 检索准

分块策略(按从简到难):

A. 固定长度分块

def fixed_chunk(text, chunk_size=500, overlap=50):
    chunks = []
    for i in range(0, len(text), chunk_size - overlap):
        chunks.append(text[i:i + chunk_size])
    return chunks

简单粗暴,但可能把一个完整段落切碎。

B. 按段落分块

def paragraph_chunk(text):
    return [p.strip() for p in text.split('\n\n') if p.strip()]

更智能,但段落长度不均匀。

C. 递归分块(实战首选)

LangChain 的 RecursiveCharacterTextSplitter:先按段落切,太长就按句子,再太长按字符。

from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,
    separators=["\n\n", "\n", "", ".", " ", ""]
)
chunks = splitter.split_text(text)

D. 语义分块(高级)

用 embedding 模型判断”哪几句话语义连贯,放一起”。效果最好,工程最复杂

一个 RAG 系统效果好不好,70% 在 chunking 策略。这是最容易被忽视的工程细节。

Step 3:向量化(Embedding)

把每个 chunk 转成向量。

用 OpenAI Embeddings

from openai import OpenAI
client = OpenAI()

def embed(text):
    resp = client.embeddings.create(
        model="text-embedding-3-small",
        input=text
    )
    return resp.data[0].embedding   # 1536 维向量

vectors = [embed(chunk) for chunk in chunks]

用开源(不花钱)

from sentence_transformers import SentenceTransformer
model = SentenceTransformer('BAAI/bge-large-zh-v1.5')   # 中文最强之一
vectors = model.encode(chunks)

主流 embedding 模型对比(2026)

模型维度优势用途
OpenAI text-embedding-31536/3072通用、英文最强通用 RAG
Cohere embed-multilingual-v31024多语言跨语言 RAG
BGE (BAAI)1024开源中文最强中文 RAG
Voyage AI1024长文本好复杂场景

选 embedding 模型比选 LLM 重要——一个 RAG 系统的检索精度首先取决于 embedding 模型。

Step 4:存到向量库

不能每次查询都用 numpy 计算几万次余弦相似度——慢。需要专门的向量数据库

选择

数据库类型何时用
FAISS(Facebook 开源)本地库原型、单机
ChromaDB本地/远程中小型
Pinecone云服务生产、托管
Weaviate自托管/云复杂查询
Qdrant(Rust 写的)自托管/云性能首选
Milvus自托管超大规模
pgvector(PostgreSQL 扩展)嵌入现有 PG不想加新依赖

用 ChromaDB 起步

import chromadb

client = chromadb.Client()
collection = client.create_collection("my_docs")

# 添加
collection.add(
    documents=chunks,           # 原始文本
    embeddings=vectors,         # 向量(也可让 Chroma 自己生成)
    metadatas=[{'source': doc['source']} for doc in documents for _ in chunks_of(doc)],
    ids=[f"chunk_{i}" for i in range(len(chunks))]
)

Step 5:检索

用户提问时:

def retrieve(query, k=5):
    query_vec = embed(query)
    results = collection.query(
        query_embeddings=[query_vec],
        n_results=k
    )
    return results['documents'][0]   # 返回 top-k chunk

Step 6:(可选但强烈推荐)Rerank

检索回来的 top-K 不一定都好用——可能 top-1 比 top-5 差。

加一个 rerank(重排) 模型——用更精确的模型对 top-N 打分,挑出真正最相关的 top-K:

from sentence_transformers import CrossEncoder
reranker = CrossEncoder('BAAI/bge-reranker-large')

def rerank(query, candidates, top_k=3):
    pairs = [(query, c) for c in candidates]
    scores = reranker.predict(pairs)
    ranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)
    return [c for c, _ in ranked[:top_k]]

经典做法:retrieve top-20 → rerank → top-3。这一步显著提高 RAG 准确率

Step 7:组装 prompt + 调 LLM

def rag_answer(query):
    # 1. 检索
    candidates = retrieve(query, k=20)
    # 2. 重排
    top_chunks = rerank(query, candidates, top_k=3)

    # 3. 拼 prompt
    context = "\n\n".join([f"[{i+1}] {chunk}" for i, chunk in enumerate(top_chunks)])
    prompt = f"""请基于下面的资料回答用户问题。
如果资料里没有相关信息,请明确说"我不知道"。

【资料】
{context}

【用户问题】
{query}

【你的回答】"""

    # 4. 调 LLM
    resp = client.chat.completions.create(
        model="claude-haiku-4-5",
        messages=[{"role": "user", "content": prompt}]
    )
    return resp.choices[0].message.content

完整 RAG,约 100 行代码

真实部署里的”坑”

理论 RAG 很简单,工程 RAG 都是细节:

1. 复杂查询拆解

用户问”对比一下我们 A 产品和 B 产品的性价比”—— 单纯检索”A 产品” 或 “B 产品” 都不够。 需要先用 LLM 拆解查询,分别检索,再综合。

2. 元数据过滤

“我们 2024 年的政策是什么”—— 不应该把 2023、2022 的检索结果混进来。 向量库要支持按元数据(年份、部门)过滤。

向量搜索擅长”语义相似”,但对精确关键词(产品 SKU、人名、专有名词)不擅长。 解决:同时跑 BM25(词项匹配)和向量搜索,融合结果。

4. Chunk 太小或太大

太小(100 字)——上下文丢失。 太大(2000 字)——稀释了关键信息。 经验值:300-800 字 + 50 字 overlap。

5. 引用与可溯源

LLM 答完,要给出”答案来自哪个 chunk”——让用户能溯源。

# 在 prompt 里要求模型引用
"请在每个论断后标注 [1] [2] 等来源编号"

6. 性能优化

  • 缓存:相同问题的检索结果缓存
  • 异步:检索和 prompt 拼装并行
  • 流式:LLM 边生成边返回

7. 评估

怎么知道 RAG 系统好不好?

  • 召回率:相关 chunk 是否被检索到(需要标注 ground truth)
  • 生成质量:人工评 / LLM 评(用 GPT-4 做评委)
  • 响应时间:从问到答多久
  • 成本:每次查询多少美元

建议用 RAGAS 库(开源 RAG 评估框架)来做。

生态工具

不想从 0 写?用框架:

工具特点
LangChain最流行、生态最大、但代码冗长
LlamaIndex专为 RAG 设计、API 优雅
Haystack(Deepset)老牌、企业级
Verba(Weaviate 出)UI 现成
Dify低代码、可视化

起步推荐:自己用 LangChain / LlamaIndex 写一遍,理解每一步。生产推荐:根据团队和场景选——没有一招通吃的。

真实业务里的 RAG

企业 AI 应用 90% 都是 RAG

  • 客服机器人:检索 FAQ + 历史工单
  • 法律助手:检索法条 + 判例
  • 医疗咨询:检索医学指南 + 病历
  • 公司 AI 知识库:员工问”我们公司怎么做出差报销”
  • AI 写作助手:检索品牌指南 + 历史文章

一个项目从”概念验证”到”上线”,技术上 90% 在调 RAG——分块策略、embedding 选型、rerank、prompt 优化、评估。

💡 RAG 是 LLM 应用的"事实标准"

2026 年:

  • 几乎所有企业 AI 项目都基于 RAG
  • 几乎所有 AI 创业公司都在做某种 “X 行业的 RAG”
  • 大公司在用更复杂的变体(GraphRAG、Agentic RAG)

学会 RAG = 学会 LLM 应用工程

下一篇:《LoRA 微调入门》 —— RAG 让 LLM “知道” 你的数据;微调让 LLM “成为” 你想要的样子。

📬

读到这里说明你认真在学 🎯

订阅每周精选 —— 下一篇新文章 / 新可视化第一时间送到邮箱。

💬

讨论区

· 用 GitHub 账号登录评论
⚠️ Giscus 评论未配置 —— 在 src/components/Comments.astro 顶部填入 仓库 ID 和分类 ID(见组件注释里的配置步骤)。