AI AgentTechnical Deep Dive

RAG 入门:从能回答到答得有依据

发布时间2026/02/15
分类AI Agent
预计阅读10 分钟
作者吴长龙
*

RAG 不是“给模型塞一堆文档”的同义词,而是一套围绕检索、上下文拼装和回答约束展开的系统设计。

01.RAG 真正解决的不是“模型不够聪明”

很多团队第一次做知识库问答时,会先尝试两种办法:

  • 把大段文档直接贴进 prompt
  • 指望模型“自己知道公司内部知识”

这两条路很快都会碰到天花板。

原因通常不是模型不够强,而是系统没有解决三个基础问题:

  • 文档太长,没法每次都完整塞进上下文
  • 知识会更新,模型参数里不可能实时同步
  • 即使模型会回答,也未必能指出答案来自哪里

RAG 的价值就在这里。它不是让模型“学会更多”,而是让模型在回答前,先从外部知识源里拿到与当前问题最相关的上下文。

所以更准确地说,RAG 解决的是“基于私有或最新知识进行有依据回答”的问题。

02.一个最小 RAG 系统长什么样

不管你选什么框架,最小 RAG 系统都绕不开下面四层:

1. 内容层

内容层决定你到底拿什么给模型用。常见来源包括:

  • Markdown 文档
  • 产品说明书
  • FAQ
  • 工单记录
  • 内部知识库页面

这一步最容易被低估。内容源本身如果结构混乱、重复严重、术语不统一,后面再怎么调模型都只是救火。

2. 索引层

索引层负责把原始内容变成“可检索”的形式,通常包括:

  • 切块
  • 提取元数据
  • 生成 embedding
  • 存储到向量库或混合检索系统

索引不是一次性工作。只要内容持续更新,索引就必须有同步机制。

3. 检索层

检索层的职责不是“找最多内容”,而是“找最相关且可用的内容”。

这里常见的动作有:

  • 向量召回
  • 关键词召回
  • 元数据过滤
  • 重排

如果检索质量差,后面的模型只能在错误上下文上“认真胡说”。

4. 生成层

模型的工作不是重新发明知识,而是:

  • 阅读检索到的片段
  • 组织答案
  • 在信息不足时明确说明
  • 必要时附上引用

这一步要靠 prompt 约束回答范围,不能把所有正确性都寄托在模型自觉上。

03.切块为什么比很多人想象中更重要

RAG 入门最常见的做法,是把文档按固定字数切成块。这个方法可以工作,但效果经常一般,因为它没有考虑内容边界。

比如一篇故障排查文档里,可能包含:

  • 背景
  • 配置项说明
  • 错误码解释
  • 恢复步骤

如果简单按 500 字切块,就很容易把“错误码定义”切在上一块,把“恢复步骤”切在下一块。用户问的是“出现 502 怎么恢复”,结果召回时只拿到一半信息。

更靠谱的原则是:

  • 优先按自然结构切块,例如标题、段落、列表、代码块
  • 让每个 chunk 尽量表达一个完整语义单元
  • 给 chunk 保留来源、标题、标签等元数据

下面是一个更贴近实际的切块骨架:

python snippetpython
from dataclasses import dataclass


@dataclass
class Chunk:
    id: str
    title: str
    content: str
    source: str
    tags: list[str]


def split_by_sections(markdown: str, source: str) -> list[Chunk]:
    sections = []
    current_title = "未命名小节"
    current_lines: list[str] = []
    index = 0

    for line in markdown.splitlines():
        if line.startswith("## "):
            if current_lines:
                sections.append(
                    Chunk(
                        id=f"{source}-{index}",
                        title=current_title,
                        content="\n".join(current_lines).strip(),
                        source=source,
                        tags=[],
                    )
                )
                index += 1
                current_lines = []
            current_title = line.removeprefix("## ").strip()
            continue

        current_lines.append(line)

    if current_lines:
        sections.append(
            Chunk(
                id=f"{source}-{index}",
                title=current_title,
                content="\n".join(current_lines).strip(),
                source=source,
                tags=[],
            )
        )

    return [chunk for chunk in sections if chunk.content]

这个例子不复杂,但它传达了一个重要原则:切块首先是内容建模问题,其次才是向量化问题。

04.一个适合个人项目的最小实现

如果你只是做个人站点搜索、知识问答助手或内部文档答疑,完全没必要一开始就上复杂架构。下面这个组合已经足够起步:

  • 内容源:Markdown 或数据库正文
  • 切块:按标题和段落切块
  • embedding:统一模型生成向量
  • 检索:向量召回 + 元数据过滤
  • 生成:要求模型只基于命中片段回答

示意代码如下:

python snippetpython
from openai import OpenAI

client = OpenAI()


def embed_texts(texts: list[str]) -> list[list[float]]:
    response = client.embeddings.create(
        model="text-embedding-3-small",
        input=texts,
    )
    return [item.embedding for item in response.data]


def build_prompt(question: str, contexts: list[str]) -> str:
    context_block = "\n\n---\n\n".join(contexts)
    return f"""
你是文档问答助手,只能依据提供的上下文回答。
如果上下文不足,请明确说“当前资料不足以回答这个问题”。

上下文:
{context_block}

问题:{question}
"""


def answer_question(question: str, retrieve) -> str:
    retrieved_docs = retrieve(question, top_k=4)
    contexts = [doc["content"] for doc in retrieved_docs]

    response = client.responses.create(
        model="gpt-4.1-mini",
        input=build_prompt(question, contexts),
    )
    return response.output_text

真正关键的不是这几十行代码,而是 retrieve() 做得怎么样。一个实际可用的 retrieve(),通常至少要支持:

  • 过滤内容范围,例如产品线、语言、版本号
  • 去掉明显重复的 chunk
  • 把命中结果按相关性重新排序
  • 把来源信息一起返回,便于回答时引用

05.RAG 常见失败,不一定是模型的问题

失败一:召回到了“相关词”,没召回到“相关答案”

用户问“如何回滚发布失败的任务”,召回结果却全是“发布系统介绍”。这通常说明:

  • 切块太粗
  • 元数据不够
  • 只做向量召回,没有做关键词补充

对这类问题,往往要先修检索,而不是先换更贵的模型。

失败二:上下文太多,模型抓不到重点

很多人觉得“多给一点文档总没坏处”,结果恰恰相反。相关内容和无关内容混在一起,会让回答变散,甚至忽略关键条件。

一个实用原则是:上下文要追求“刚好够用”,而不是“越多越安全”。

失败三:知识本身质量不高

如果源文档里版本混杂、规则冲突、标题乱写,RAG 只能把这些问题更稳定地暴露出来。

所以做 RAG 之前,值得先问一句:现在的问题到底是“模型不会答”,还是“文档本来就不好用”。

06.什么时候该做 RAG,什么时候先别做

适合做 RAG:

  • 有明确的私有知识源
  • 回答必须基于最新资料
  • 需要可追溯出处
  • 问题相对聚焦,能通过检索缩小范围

先别做 RAG:

  • 内容量很小,直接整理成 FAQ 更简单
  • 用户问题高度开放,检索很难界定上下文
  • 资料本身质量差,尚未完成清洗
  • 还没想清楚怎么评估回答是否更好

如果只是几篇静态文档,先把信息架构整理清楚,比急着上向量库更划算。

07.做 RAG 时至少要盯住的三个指标

RAG 不是“能跑起来”就结束。最起码要观察:

  • 命中率:检索结果里是否真的包含答案
  • 回答可用率:用户是否能直接拿答案做事
  • 引用准确率:引用的来源是否和答案一致

这三项里,第一项是根。检索没命中,后面再怎么调 prompt 都只是补救。

08.总结

RAG 的核心不是“向量数据库”这几个字,而是三件事:

  • 让知识可被稳定检索
  • 让模型只基于相关上下文回答
  • 让整个系统能被持续更新和评估

如果你从内容结构、切块策略和检索质量开始搭,而不是一上来就迷信模型参数,RAG 项目通常会走得更稳,也更接近真实工程落地。