👋 我是 Yulia,一线 AI 应用开发工程师:4 年工程经验,在职做大模型应用落地。
只写一件事------把最新 AI 技术讲成人话 + 给你能直接上手用的代码 (真实可验证,不搬运不注水)。
有用就点个 关注,持续更新大模型应用的前沿实战。
📚 本文是《后端开发从入门到靠谱》系列第 11 篇。第十课把"RAG 检索"列为 AI 后端服务层的关键模块,这一篇把它完整落地 ------从文档到能回答问题,并重点讲清项目中如何设计一个好用的 RAG。整篇由浅入深,篇幅较长,建议收藏慢慢看。
大模型很强,但它有个致命问题:它只知道"训练时"见过的知识,不知道你的私有数据。你问它"我们公司最新的产品报价是多少",它要么编一个(幻觉),要么说不知道。
RAG(Retrieval-Augmented Generation,检索增强生成) 就是解决这个的:先检索你的私有知识库,把相关内容"喂"给大模型,再让它基于这些内容回答。这篇带你从 0 到 1 落地一个 RAG,并讲透怎么把它设计得好用。
读完你会拿到:
- 一个直觉模型:RAG 是"开卷考试"------先翻书(检索),再答题(生成)
- RAG 五步完整链路:加载 → 切分 → 向量化 → 检索 → 生成(每步讲底层原理)
- 切分策略设计:chunk 大小/重叠怎么选、为什么切分是"好 RAG"的第一步
- 检索设计:纯向量检索的局限 → 混合检索 + 重排序(项目里提升准确率的关键)
- prompt 设计:怎么让大模型"只基于检索内容回答",杜绝幻觉
- 如何设计一个好的 RAG:六个设计决策 + 评估方法(本篇灵魂)
- 完整实战:把 RAG 接进第十课的 FastAPI 架构
- 常见坑与避坑指南
先看全景图,RAG 的完整链路:
#mermaid-svg-l3pHpl4pBmBC9VuE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-l3pHpl4pBmBC9VuE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-l3pHpl4pBmBC9VuE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-l3pHpl4pBmBC9VuE .error-icon{fill:#552222;}#mermaid-svg-l3pHpl4pBmBC9VuE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-l3pHpl4pBmBC9VuE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-l3pHpl4pBmBC9VuE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-l3pHpl4pBmBC9VuE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-l3pHpl4pBmBC9VuE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-l3pHpl4pBmBC9VuE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-l3pHpl4pBmBC9VuE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-l3pHpl4pBmBC9VuE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-l3pHpl4pBmBC9VuE .marker.cross{stroke:#333333;}#mermaid-svg-l3pHpl4pBmBC9VuE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-l3pHpl4pBmBC9VuE p{margin:0;}#mermaid-svg-l3pHpl4pBmBC9VuE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-l3pHpl4pBmBC9VuE .cluster-label text{fill:#333;}#mermaid-svg-l3pHpl4pBmBC9VuE .cluster-label span{color:#333;}#mermaid-svg-l3pHpl4pBmBC9VuE .cluster-label span p{background-color:transparent;}#mermaid-svg-l3pHpl4pBmBC9VuE .label text,#mermaid-svg-l3pHpl4pBmBC9VuE span{fill:#333;color:#333;}#mermaid-svg-l3pHpl4pBmBC9VuE .node rect,#mermaid-svg-l3pHpl4pBmBC9VuE .node circle,#mermaid-svg-l3pHpl4pBmBC9VuE .node ellipse,#mermaid-svg-l3pHpl4pBmBC9VuE .node polygon,#mermaid-svg-l3pHpl4pBmBC9VuE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-l3pHpl4pBmBC9VuE .rough-node .label text,#mermaid-svg-l3pHpl4pBmBC9VuE .node .label text,#mermaid-svg-l3pHpl4pBmBC9VuE .image-shape .label,#mermaid-svg-l3pHpl4pBmBC9VuE .icon-shape .label{text-anchor:middle;}#mermaid-svg-l3pHpl4pBmBC9VuE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-l3pHpl4pBmBC9VuE .rough-node .label,#mermaid-svg-l3pHpl4pBmBC9VuE .node .label,#mermaid-svg-l3pHpl4pBmBC9VuE .image-shape .label,#mermaid-svg-l3pHpl4pBmBC9VuE .icon-shape .label{text-align:center;}#mermaid-svg-l3pHpl4pBmBC9VuE .node.clickable{cursor:pointer;}#mermaid-svg-l3pHpl4pBmBC9VuE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-l3pHpl4pBmBC9VuE .arrowheadPath{fill:#333333;}#mermaid-svg-l3pHpl4pBmBC9VuE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-l3pHpl4pBmBC9VuE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-l3pHpl4pBmBC9VuE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-l3pHpl4pBmBC9VuE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-l3pHpl4pBmBC9VuE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-l3pHpl4pBmBC9VuE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-l3pHpl4pBmBC9VuE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-l3pHpl4pBmBC9VuE .cluster text{fill:#333;}#mermaid-svg-l3pHpl4pBmBC9VuE .cluster span{color:#333;}#mermaid-svg-l3pHpl4pBmBC9VuE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-l3pHpl4pBmBC9VuE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-l3pHpl4pBmBC9VuE rect.text{fill:none;stroke-width:0;}#mermaid-svg-l3pHpl4pBmBC9VuE .icon-shape,#mermaid-svg-l3pHpl4pBmBC9VuE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-l3pHpl4pBmBC9VuE .icon-shape p,#mermaid-svg-l3pHpl4pBmBC9VuE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-l3pHpl4pBmBC9VuE .icon-shape .label rect,#mermaid-svg-l3pHpl4pBmBC9VuE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-l3pHpl4pBmBC9VuE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-l3pHpl4pBmBC9VuE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-l3pHpl4pBmBC9VuE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-l3pHpl4pBmBC9VuE .blue>*{fill:#4E79A7!important;color:#fff!important;stroke:none!important;}#mermaid-svg-l3pHpl4pBmBC9VuE .blue span{fill:#4E79A7!important;color:#fff!important;stroke:none!important;}#mermaid-svg-l3pHpl4pBmBC9VuE .blue tspan{fill:#fff!important;}#mermaid-svg-l3pHpl4pBmBC9VuE .orange>*{fill:#F28E2B!important;color:#fff!important;stroke:none!important;}#mermaid-svg-l3pHpl4pBmBC9VuE .orange span{fill:#F28E2B!important;color:#fff!important;stroke:none!important;}#mermaid-svg-l3pHpl4pBmBC9VuE .orange tspan{fill:#fff!important;}#mermaid-svg-l3pHpl4pBmBC9VuE .green>*{fill:#59A14F!important;color:#fff!important;stroke:none!important;}#mermaid-svg-l3pHpl4pBmBC9VuE .green span{fill:#59A14F!important;color:#fff!important;stroke:none!important;}#mermaid-svg-l3pHpl4pBmBC9VuE .green tspan{fill:#fff!important;}#mermaid-svg-l3pHpl4pBmBC9VuE .red>*{fill:#E15759!important;color:#fff!important;stroke:none!important;}#mermaid-svg-l3pHpl4pBmBC9VuE .red span{fill:#E15759!important;color:#fff!important;stroke:none!important;}#mermaid-svg-l3pHpl4pBmBC9VuE .red tspan{fill:#fff!important;}#mermaid-svg-l3pHpl4pBmBC9VuE .cyan>*{fill:#76B7B2!important;color:#fff!important;stroke:none!important;}#mermaid-svg-l3pHpl4pBmBC9VuE .cyan span{fill:#76B7B2!important;color:#fff!important;stroke:none!important;}#mermaid-svg-l3pHpl4pBmBC9VuE .cyan tspan{fill:#fff!important;} 文档
切分 chunk
向量化 embedding
向量库
用户问题
向量化
检索 相似度
拼 prompt
大模型生成
一、RAG 是什么?为什么需要它
先把概念讲透,后面的设计才有根基。
1.1 大模型的三个先天缺陷
- 知识截止:大模型的知识是"训练那一刻"的快照,训练后发生的事(新产品、新政策)它不知道;
- 不知道私有数据:你公司的内部文档、你的个人笔记,它从没见过;
- 会幻觉 :不知道的时候,它不会老实说"不知道",而是一本正经地编造。
1.2 RAG 的本质:开卷考试
RAG 用一个生活化类比秒懂:普通大模型是"闭卷考试",RAG 是"开卷考试"。
- 闭卷:大模型只能靠记忆回答,记不住就瞎编;
- 开卷:先检索 你的资料(翻书),把相关内容拿过来,再生成答案(照着书答题)。
"开卷"两个字就是 RAG 的全部精髓------给大模型补充它没有的知识。
#mermaid-svg-zG44wIDIhEt3yJ4F{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zG44wIDIhEt3yJ4F .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zG44wIDIhEt3yJ4F .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zG44wIDIhEt3yJ4F .error-icon{fill:#552222;}#mermaid-svg-zG44wIDIhEt3yJ4F .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zG44wIDIhEt3yJ4F .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zG44wIDIhEt3yJ4F .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zG44wIDIhEt3yJ4F .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zG44wIDIhEt3yJ4F .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zG44wIDIhEt3yJ4F .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zG44wIDIhEt3yJ4F .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zG44wIDIhEt3yJ4F .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zG44wIDIhEt3yJ4F .marker.cross{stroke:#333333;}#mermaid-svg-zG44wIDIhEt3yJ4F svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zG44wIDIhEt3yJ4F p{margin:0;}#mermaid-svg-zG44wIDIhEt3yJ4F .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-zG44wIDIhEt3yJ4F .cluster-label text{fill:#333;}#mermaid-svg-zG44wIDIhEt3yJ4F .cluster-label span{color:#333;}#mermaid-svg-zG44wIDIhEt3yJ4F .cluster-label span p{background-color:transparent;}#mermaid-svg-zG44wIDIhEt3yJ4F .label text,#mermaid-svg-zG44wIDIhEt3yJ4F span{fill:#333;color:#333;}#mermaid-svg-zG44wIDIhEt3yJ4F .node rect,#mermaid-svg-zG44wIDIhEt3yJ4F .node circle,#mermaid-svg-zG44wIDIhEt3yJ4F .node ellipse,#mermaid-svg-zG44wIDIhEt3yJ4F .node polygon,#mermaid-svg-zG44wIDIhEt3yJ4F .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zG44wIDIhEt3yJ4F .rough-node .label text,#mermaid-svg-zG44wIDIhEt3yJ4F .node .label text,#mermaid-svg-zG44wIDIhEt3yJ4F .image-shape .label,#mermaid-svg-zG44wIDIhEt3yJ4F .icon-shape .label{text-anchor:middle;}#mermaid-svg-zG44wIDIhEt3yJ4F .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-zG44wIDIhEt3yJ4F .rough-node .label,#mermaid-svg-zG44wIDIhEt3yJ4F .node .label,#mermaid-svg-zG44wIDIhEt3yJ4F .image-shape .label,#mermaid-svg-zG44wIDIhEt3yJ4F .icon-shape .label{text-align:center;}#mermaid-svg-zG44wIDIhEt3yJ4F .node.clickable{cursor:pointer;}#mermaid-svg-zG44wIDIhEt3yJ4F .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-zG44wIDIhEt3yJ4F .arrowheadPath{fill:#333333;}#mermaid-svg-zG44wIDIhEt3yJ4F .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-zG44wIDIhEt3yJ4F .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-zG44wIDIhEt3yJ4F .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zG44wIDIhEt3yJ4F .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-zG44wIDIhEt3yJ4F .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zG44wIDIhEt3yJ4F .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-zG44wIDIhEt3yJ4F .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zG44wIDIhEt3yJ4F .cluster text{fill:#333;}#mermaid-svg-zG44wIDIhEt3yJ4F .cluster span{color:#333;}#mermaid-svg-zG44wIDIhEt3yJ4F div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-zG44wIDIhEt3yJ4F .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zG44wIDIhEt3yJ4F rect.text{fill:none;stroke-width:0;}#mermaid-svg-zG44wIDIhEt3yJ4F .icon-shape,#mermaid-svg-zG44wIDIhEt3yJ4F .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zG44wIDIhEt3yJ4F .icon-shape p,#mermaid-svg-zG44wIDIhEt3yJ4F .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-zG44wIDIhEt3yJ4F .icon-shape .label rect,#mermaid-svg-zG44wIDIhEt3yJ4F .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zG44wIDIhEt3yJ4F .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zG44wIDIhEt3yJ4F .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zG44wIDIhEt3yJ4F :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-zG44wIDIhEt3yJ4F .blue>*{fill:#4E79A7!important;color:#fff!important;stroke:none!important;}#mermaid-svg-zG44wIDIhEt3yJ4F .blue span{fill:#4E79A7!important;color:#fff!important;stroke:none!important;}#mermaid-svg-zG44wIDIhEt3yJ4F .blue tspan{fill:#fff!important;}#mermaid-svg-zG44wIDIhEt3yJ4F .orange>*{fill:#F28E2B!important;color:#fff!important;stroke:none!important;}#mermaid-svg-zG44wIDIhEt3yJ4F .orange span{fill:#F28E2B!important;color:#fff!important;stroke:none!important;}#mermaid-svg-zG44wIDIhEt3yJ4F .orange tspan{fill:#fff!important;}#mermaid-svg-zG44wIDIhEt3yJ4F .green>*{fill:#59A14F!important;color:#fff!important;stroke:none!important;}#mermaid-svg-zG44wIDIhEt3yJ4F .green span{fill:#59A14F!important;color:#fff!important;stroke:none!important;}#mermaid-svg-zG44wIDIhEt3yJ4F .green tspan{fill:#fff!important;} 闭卷 只靠记忆
记不住就瞎编
开卷 RAG
先检索资料
照着资料答
二、RAG 的完整五步链路
一个 RAG 系统,本质上就是五步。先记住这五步的"骨架",后面每步单独展开:
- 文档加载:把 PDF / Word / 网页 / 数据库里的文档读进来,变成纯文本;
- 切分(Chunking):把长文档切成一小块一小块(chunk),因为大模型一次只能"看"有限的内容;
- 向量化(Embedding):把每个 chunk 用模型转成一串数字(向量),语义相近的文本,向量也相近;
- 检索(Retrieval):用户提问也转成向量,去向量库里找"最相似"的若干 chunk;
- 生成(Generation):把检索到的 chunk 拼进 prompt,让大模型基于它们生成答案。
其中第 2 步(切分)和第 4 步(检索)是"好 RAG"和"烂 RAG"的分水岭,也是本文重点。
三、第一步:文档加载与切分(chunking 设计)
3.1 为什么要切分?
大模型的上下文长度有限(比如 8K、128K token),一份 100 页的 PDF 直接塞不进去,必须切小块。但切分不是"随便切",它直接决定检索质量。
3.2 切分的三个核心参数
python
def split_text(text: str, chunk_size: int = 500, overlap: int = 50):
"""把长文本切成若干 chunk,overlap 让相邻 chunk 有重叠"""
chunks = []
start = 0
while start < len(text):
end = start + chunk_size
chunks.append(text[start:end])
start = end - overlap # 回退 overlap,保证上下文不断
return chunks
- chunk_size(块大小) :一个 chunk 有多少字/token。太大 → 检索时混入无关内容,答案被"污染";太小→ 一个完整意思被切断,模型"断章取义"。
- overlap(重叠):相邻 chunk 之间重复的部分。因为一句话可能被切到两个 chunk 的边界,overlap 保证边界处的意思不丢。
- 切分方式 :固定长度切 vs 按语义切(按段落、标题、句子边界切)。
3.3 切分是"好 RAG"的第一个设计决策
#mermaid-svg-uwmsDVxyyHUjDXiE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uwmsDVxyyHUjDXiE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uwmsDVxyyHUjDXiE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uwmsDVxyyHUjDXiE .error-icon{fill:#552222;}#mermaid-svg-uwmsDVxyyHUjDXiE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uwmsDVxyyHUjDXiE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uwmsDVxyyHUjDXiE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uwmsDVxyyHUjDXiE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uwmsDVxyyHUjDXiE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uwmsDVxyyHUjDXiE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uwmsDVxyyHUjDXiE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uwmsDVxyyHUjDXiE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uwmsDVxyyHUjDXiE .marker.cross{stroke:#333333;}#mermaid-svg-uwmsDVxyyHUjDXiE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uwmsDVxyyHUjDXiE p{margin:0;}#mermaid-svg-uwmsDVxyyHUjDXiE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-uwmsDVxyyHUjDXiE .cluster-label text{fill:#333;}#mermaid-svg-uwmsDVxyyHUjDXiE .cluster-label span{color:#333;}#mermaid-svg-uwmsDVxyyHUjDXiE .cluster-label span p{background-color:transparent;}#mermaid-svg-uwmsDVxyyHUjDXiE .label text,#mermaid-svg-uwmsDVxyyHUjDXiE span{fill:#333;color:#333;}#mermaid-svg-uwmsDVxyyHUjDXiE .node rect,#mermaid-svg-uwmsDVxyyHUjDXiE .node circle,#mermaid-svg-uwmsDVxyyHUjDXiE .node ellipse,#mermaid-svg-uwmsDVxyyHUjDXiE .node polygon,#mermaid-svg-uwmsDVxyyHUjDXiE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-uwmsDVxyyHUjDXiE .rough-node .label text,#mermaid-svg-uwmsDVxyyHUjDXiE .node .label text,#mermaid-svg-uwmsDVxyyHUjDXiE .image-shape .label,#mermaid-svg-uwmsDVxyyHUjDXiE .icon-shape .label{text-anchor:middle;}#mermaid-svg-uwmsDVxyyHUjDXiE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-uwmsDVxyyHUjDXiE .rough-node .label,#mermaid-svg-uwmsDVxyyHUjDXiE .node .label,#mermaid-svg-uwmsDVxyyHUjDXiE .image-shape .label,#mermaid-svg-uwmsDVxyyHUjDXiE .icon-shape .label{text-align:center;}#mermaid-svg-uwmsDVxyyHUjDXiE .node.clickable{cursor:pointer;}#mermaid-svg-uwmsDVxyyHUjDXiE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-uwmsDVxyyHUjDXiE .arrowheadPath{fill:#333333;}#mermaid-svg-uwmsDVxyyHUjDXiE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-uwmsDVxyyHUjDXiE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-uwmsDVxyyHUjDXiE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uwmsDVxyyHUjDXiE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-uwmsDVxyyHUjDXiE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uwmsDVxyyHUjDXiE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-uwmsDVxyyHUjDXiE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-uwmsDVxyyHUjDXiE .cluster text{fill:#333;}#mermaid-svg-uwmsDVxyyHUjDXiE .cluster span{color:#333;}#mermaid-svg-uwmsDVxyyHUjDXiE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-uwmsDVxyyHUjDXiE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-uwmsDVxyyHUjDXiE rect.text{fill:none;stroke-width:0;}#mermaid-svg-uwmsDVxyyHUjDXiE .icon-shape,#mermaid-svg-uwmsDVxyyHUjDXiE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uwmsDVxyyHUjDXiE .icon-shape p,#mermaid-svg-uwmsDVxyyHUjDXiE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-uwmsDVxyyHUjDXiE .icon-shape .label rect,#mermaid-svg-uwmsDVxyyHUjDXiE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uwmsDVxyyHUjDXiE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-uwmsDVxyyHUjDXiE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-uwmsDVxyyHUjDXiE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-uwmsDVxyyHUjDXiE .blue>*{fill:#4E79A7!important;color:#fff!important;stroke:none!important;}#mermaid-svg-uwmsDVxyyHUjDXiE .blue span{fill:#4E79A7!important;color:#fff!important;stroke:none!important;}#mermaid-svg-uwmsDVxyyHUjDXiE .blue tspan{fill:#fff!important;}#mermaid-svg-uwmsDVxyyHUjDXiE .orange>*{fill:#F28E2B!important;color:#fff!important;stroke:none!important;}#mermaid-svg-uwmsDVxyyHUjDXiE .orange span{fill:#F28E2B!important;color:#fff!important;stroke:none!important;}#mermaid-svg-uwmsDVxyyHUjDXiE .orange tspan{fill:#fff!important;}#mermaid-svg-uwmsDVxyyHUjDXiE .red>*{fill:#E15759!important;color:#fff!important;stroke:none!important;}#mermaid-svg-uwmsDVxyyHUjDXiE .red span{fill:#E15759!important;color:#fff!important;stroke:none!important;}#mermaid-svg-uwmsDVxyyHUjDXiE .red tspan{fill:#fff!important;} 切太大
混入无关内容
切太小
意思被切断
语义切分
按段落标题切
设计原则 :优先按语义边界 切(段落、标题、列表),而不是机械按字符数切。中文场景下,一个 chunk 通常在 200~500 字左右是常见起点,再根据实际效果调。
进阶做法:用父文档检索(parent-document retrieval)------检索时用小 chunk(更精准),但生成时把大 chunk 的父文档一起喂给模型(上下文更完整)。这是生产 RAG 常用的技巧。
四、第二步:向量化(embedding)
4.1 什么是向量化
大模型不认识"文本",只认识数字。Embedding(向量化)就是把一段文字变成一串固定长度的数字(向量) ,规则是:语义相近的文字,向量在空间里也相近。
python
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("BAAI/bge-small-zh-v1.5") # 中文 embedding 模型
chunk = "FastAPI 是一个现代 Python Web 框架"
vector = model.encode(chunk) # 变成 512 维的浮点数向量
print(vector.shape) # (512,)
这段代码把一个句子变成了 512 个数字。神奇的地方在于:"FastAPI 是 Web 框架" 和 "FastAPI 用于开发后端接口" 两句话虽然字面不同,但向量很近 ;而 "今天天气不错" 的向量离它们很远。这就是"语义检索"的基础。
4.2 embedding 模型怎么选
| 因素 | 说明 |
|---|---|
| 语言 | 中文场景选中文模型(bge-small-zh / bge-large-zh) |
| 维度 | 512 维(快、省)vs 1024 维(更准、占空间) |
| 领域 | 通用模型 vs 领域微调模型(医疗/法律等垂直领域) |
选 embedding 模型的本质是在"准确度"和"成本/速度"之间权衡------维度越高越准,但存储和计算越贵。
五、第三步:向量库与检索(混合检索 + 重排序)
这是"好 RAG"的核心,也是本文最深的部分。
5.1 向量检索:找"最相似"的 chunk
检索就是:把用户问题也向量化,然后在向量库里找和它最相似的 top-k 个 chunk 。相似度通常用余弦相似度(两个向量的夹角越小越相似)。
python
import numpy as np
def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
def retrieve(query_vec, chunks, top_k=5):
scores = [cosine_similarity(query_vec, c) for c in chunks]
top_idx = np.argsort(scores)[-top_k:] # 取相似度最高的 top_k 个
return [chunks[i] for i in top_idx]
生产上不会用 numpy 手写(慢、占内存),而是用专门的向量库 FAISS 或 Chroma,它们能高效处理百万级向量。
5.2 纯向量检索的局限:为什么需要混合检索
纯向量检索有个反直觉的缺陷:它对"语义相似"很敏感,但对"精确关键词"不敏感。比如用户搜"错误码 -32602 是什么",向量检索可能找不到文档里精确写着"-32602"的段落(因为它是生僻数字),反而返回一堆"错误处理"的泛泛内容。
所以生产级 RAG 常用混合检索(Hybrid Search):
#mermaid-svg-zYT49816diCm1Xo1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zYT49816diCm1Xo1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zYT49816diCm1Xo1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zYT49816diCm1Xo1 .error-icon{fill:#552222;}#mermaid-svg-zYT49816diCm1Xo1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zYT49816diCm1Xo1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zYT49816diCm1Xo1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zYT49816diCm1Xo1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zYT49816diCm1Xo1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zYT49816diCm1Xo1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zYT49816diCm1Xo1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zYT49816diCm1Xo1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zYT49816diCm1Xo1 .marker.cross{stroke:#333333;}#mermaid-svg-zYT49816diCm1Xo1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zYT49816diCm1Xo1 p{margin:0;}#mermaid-svg-zYT49816diCm1Xo1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-zYT49816diCm1Xo1 .cluster-label text{fill:#333;}#mermaid-svg-zYT49816diCm1Xo1 .cluster-label span{color:#333;}#mermaid-svg-zYT49816diCm1Xo1 .cluster-label span p{background-color:transparent;}#mermaid-svg-zYT49816diCm1Xo1 .label text,#mermaid-svg-zYT49816diCm1Xo1 span{fill:#333;color:#333;}#mermaid-svg-zYT49816diCm1Xo1 .node rect,#mermaid-svg-zYT49816diCm1Xo1 .node circle,#mermaid-svg-zYT49816diCm1Xo1 .node ellipse,#mermaid-svg-zYT49816diCm1Xo1 .node polygon,#mermaid-svg-zYT49816diCm1Xo1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zYT49816diCm1Xo1 .rough-node .label text,#mermaid-svg-zYT49816diCm1Xo1 .node .label text,#mermaid-svg-zYT49816diCm1Xo1 .image-shape .label,#mermaid-svg-zYT49816diCm1Xo1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-zYT49816diCm1Xo1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-zYT49816diCm1Xo1 .rough-node .label,#mermaid-svg-zYT49816diCm1Xo1 .node .label,#mermaid-svg-zYT49816diCm1Xo1 .image-shape .label,#mermaid-svg-zYT49816diCm1Xo1 .icon-shape .label{text-align:center;}#mermaid-svg-zYT49816diCm1Xo1 .node.clickable{cursor:pointer;}#mermaid-svg-zYT49816diCm1Xo1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-zYT49816diCm1Xo1 .arrowheadPath{fill:#333333;}#mermaid-svg-zYT49816diCm1Xo1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-zYT49816diCm1Xo1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-zYT49816diCm1Xo1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zYT49816diCm1Xo1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-zYT49816diCm1Xo1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zYT49816diCm1Xo1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-zYT49816diCm1Xo1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zYT49816diCm1Xo1 .cluster text{fill:#333;}#mermaid-svg-zYT49816diCm1Xo1 .cluster span{color:#333;}#mermaid-svg-zYT49816diCm1Xo1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-zYT49816diCm1Xo1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zYT49816diCm1Xo1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-zYT49816diCm1Xo1 .icon-shape,#mermaid-svg-zYT49816diCm1Xo1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zYT49816diCm1Xo1 .icon-shape p,#mermaid-svg-zYT49816diCm1Xo1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-zYT49816diCm1Xo1 .icon-shape .label rect,#mermaid-svg-zYT49816diCm1Xo1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zYT49816diCm1Xo1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zYT49816diCm1Xo1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zYT49816diCm1Xo1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-zYT49816diCm1Xo1 .blue>*{fill:#4E79A7!important;color:#fff!important;stroke:none!important;}#mermaid-svg-zYT49816diCm1Xo1 .blue span{fill:#4E79A7!important;color:#fff!important;stroke:none!important;}#mermaid-svg-zYT49816diCm1Xo1 .blue tspan{fill:#fff!important;}#mermaid-svg-zYT49816diCm1Xo1 .orange>*{fill:#F28E2B!important;color:#fff!important;stroke:none!important;}#mermaid-svg-zYT49816diCm1Xo1 .orange span{fill:#F28E2B!important;color:#fff!important;stroke:none!important;}#mermaid-svg-zYT49816diCm1Xo1 .orange tspan{fill:#fff!important;}#mermaid-svg-zYT49816diCm1Xo1 .green>*{fill:#59A14F!important;color:#fff!important;stroke:none!important;}#mermaid-svg-zYT49816diCm1Xo1 .green span{fill:#59A14F!important;color:#fff!important;stroke:none!important;}#mermaid-svg-zYT49816diCm1Xo1 .green tspan{fill:#fff!important;}#mermaid-svg-zYT49816diCm1Xo1 .red>*{fill:#E15759!important;color:#fff!important;stroke:none!important;}#mermaid-svg-zYT49816diCm1Xo1 .red span{fill:#E15759!important;color:#fff!important;stroke:none!important;}#mermaid-svg-zYT49816diCm1Xo1 .red tspan{fill:#fff!important;} 用户问题
BM25 关键词检索
向量 语义检索
RRF 融合排名
重排序 rerank
最终 top-k
- BM25:关键词检索,精确匹配字面词("查字典");
- 向量检索:语义检索,找意思相近("问懂行的人");
- RRF 融合:把两套排名合并成一个(用"排名"而非"分数"融合,因为两者分数不可直接比);
- 重排序(Rerank):再用一个专门的排序模型,对候选做更精细的排序,挑出真正最相关的。
为什么这套流程是好 RAG 的关键:单一检索方式总有盲区,混合检索 + 重排序能互补,把"该找到的"都找出来、且排在最前面。
5.3 元数据过滤:缩小范围、精准命中
检索之前,先按元数据过滤,能大幅提升准确率和性能。比如:
- 按时间:只检索"最近一年的文档";
- 按分类:只检索"产品文档",不碰"人事制度";
- 按权限:只检索"当前用户有权限看"的内容(这还涉及安全隔离)。
python
# 先按元数据过滤,再做向量检索
results = vector_db.query(
query_embeddings=[query_vec],
where={"category": "产品文档", "year": 2025}, # 元数据过滤
n_results=top_k,
)
元数据过滤是"从能用到好用"的关键一步,也是很多人做 RAG 时忽略的地方。
六、第四步:生成(prompt 设计,杜绝幻觉)
检索到相关内容后,最后一步是把它们拼进 prompt,让大模型生成答案。prompt 怎么写,直接决定答案准不准、会不会瞎编。
一个好的 RAG prompt 必须包含:
python
def build_prompt(question, retrieved_chunks):
context = "\n\n".join([f"[来源{i}] {c}" for i, c in enumerate(retrieved_chunks)])
prompt = f"""请基于以下资料回答用户的问题。
资料:
{context}
规则:
1. 只根据上面的资料回答,不要使用你自己的知识;
2. 如果资料里没有答案,直接说"资料中没有相关信息",不要编造;
3. 回答时标注引用的来源编号,如 [来源1]。
用户问题:{question}
"""
return prompt
三个设计要点:
- "只根据资料回答":强制大模型把检索内容当唯一依据,防止它"脱稿"瞎编(幻觉);
- "不知道就说不知道":给模型一个"安全出口",资料里没有就老实承认,而不是硬编;
- "标注来源":让答案可追溯,用户能看到依据,也方便排查检索质量问题。
这是 RAG 和"直接问大模型"最大的区别:RAG 的答案是有出处的、可控的。一个"好 RAG"的标志,就是答案几乎都能对上来源。
七、如何设计一个好的 RAG(本篇灵魂)
把前面所有点收拢,一个好的 RAG 系统,要在六个维度上做设计决策:
#mermaid-svg-UBc9lDXRnZyS6v9g{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UBc9lDXRnZyS6v9g .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UBc9lDXRnZyS6v9g .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UBc9lDXRnZyS6v9g .error-icon{fill:#552222;}#mermaid-svg-UBc9lDXRnZyS6v9g .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UBc9lDXRnZyS6v9g .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UBc9lDXRnZyS6v9g .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UBc9lDXRnZyS6v9g .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UBc9lDXRnZyS6v9g .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UBc9lDXRnZyS6v9g .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UBc9lDXRnZyS6v9g .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UBc9lDXRnZyS6v9g .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UBc9lDXRnZyS6v9g .marker.cross{stroke:#333333;}#mermaid-svg-UBc9lDXRnZyS6v9g svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UBc9lDXRnZyS6v9g p{margin:0;}#mermaid-svg-UBc9lDXRnZyS6v9g .edge{stroke-width:3;}#mermaid-svg-UBc9lDXRnZyS6v9g .section--1 rect,#mermaid-svg-UBc9lDXRnZyS6v9g .section--1 path,#mermaid-svg-UBc9lDXRnZyS6v9g .section--1 circle,#mermaid-svg-UBc9lDXRnZyS6v9g .section--1 polygon,#mermaid-svg-UBc9lDXRnZyS6v9g .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .section--1 text{fill:#ffffff;}#mermaid-svg-UBc9lDXRnZyS6v9g .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .edge-depth--1{stroke-width:17;}#mermaid-svg-UBc9lDXRnZyS6v9g .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled circle,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:lightgray;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:#efefef;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-0 rect,#mermaid-svg-UBc9lDXRnZyS6v9g .section-0 path,#mermaid-svg-UBc9lDXRnZyS6v9g .section-0 circle,#mermaid-svg-UBc9lDXRnZyS6v9g .section-0 polygon,#mermaid-svg-UBc9lDXRnZyS6v9g .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-UBc9lDXRnZyS6v9g .section-0 text{fill:black;}#mermaid-svg-UBc9lDXRnZyS6v9g .node-icon-0{font-size:40px;color:black;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-UBc9lDXRnZyS6v9g .edge-depth-0{stroke-width:14;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled circle,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:lightgray;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:#efefef;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-1 rect,#mermaid-svg-UBc9lDXRnZyS6v9g .section-1 path,#mermaid-svg-UBc9lDXRnZyS6v9g .section-1 circle,#mermaid-svg-UBc9lDXRnZyS6v9g .section-1 polygon,#mermaid-svg-UBc9lDXRnZyS6v9g .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .section-1 text{fill:black;}#mermaid-svg-UBc9lDXRnZyS6v9g .node-icon-1{font-size:40px;color:black;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .edge-depth-1{stroke-width:11;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled circle,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:lightgray;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:#efefef;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-2 rect,#mermaid-svg-UBc9lDXRnZyS6v9g .section-2 path,#mermaid-svg-UBc9lDXRnZyS6v9g .section-2 circle,#mermaid-svg-UBc9lDXRnZyS6v9g .section-2 polygon,#mermaid-svg-UBc9lDXRnZyS6v9g .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .section-2 text{fill:#ffffff;}#mermaid-svg-UBc9lDXRnZyS6v9g .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .edge-depth-2{stroke-width:8;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled circle,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:lightgray;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:#efefef;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-3 rect,#mermaid-svg-UBc9lDXRnZyS6v9g .section-3 path,#mermaid-svg-UBc9lDXRnZyS6v9g .section-3 circle,#mermaid-svg-UBc9lDXRnZyS6v9g .section-3 polygon,#mermaid-svg-UBc9lDXRnZyS6v9g .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .section-3 text{fill:black;}#mermaid-svg-UBc9lDXRnZyS6v9g .node-icon-3{font-size:40px;color:black;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .edge-depth-3{stroke-width:5;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled circle,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:lightgray;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:#efefef;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-4 rect,#mermaid-svg-UBc9lDXRnZyS6v9g .section-4 path,#mermaid-svg-UBc9lDXRnZyS6v9g .section-4 circle,#mermaid-svg-UBc9lDXRnZyS6v9g .section-4 polygon,#mermaid-svg-UBc9lDXRnZyS6v9g .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .section-4 text{fill:black;}#mermaid-svg-UBc9lDXRnZyS6v9g .node-icon-4{font-size:40px;color:black;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .edge-depth-4{stroke-width:2;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled circle,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:lightgray;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:#efefef;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-5 rect,#mermaid-svg-UBc9lDXRnZyS6v9g .section-5 path,#mermaid-svg-UBc9lDXRnZyS6v9g .section-5 circle,#mermaid-svg-UBc9lDXRnZyS6v9g .section-5 polygon,#mermaid-svg-UBc9lDXRnZyS6v9g .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .section-5 text{fill:black;}#mermaid-svg-UBc9lDXRnZyS6v9g .node-icon-5{font-size:40px;color:black;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .edge-depth-5{stroke-width:-1;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled circle,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:lightgray;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:#efefef;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-6 rect,#mermaid-svg-UBc9lDXRnZyS6v9g .section-6 path,#mermaid-svg-UBc9lDXRnZyS6v9g .section-6 circle,#mermaid-svg-UBc9lDXRnZyS6v9g .section-6 polygon,#mermaid-svg-UBc9lDXRnZyS6v9g .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .section-6 text{fill:black;}#mermaid-svg-UBc9lDXRnZyS6v9g .node-icon-6{font-size:40px;color:black;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .edge-depth-6{stroke-width:-4;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled circle,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:lightgray;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:#efefef;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-7 rect,#mermaid-svg-UBc9lDXRnZyS6v9g .section-7 path,#mermaid-svg-UBc9lDXRnZyS6v9g .section-7 circle,#mermaid-svg-UBc9lDXRnZyS6v9g .section-7 polygon,#mermaid-svg-UBc9lDXRnZyS6v9g .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .section-7 text{fill:black;}#mermaid-svg-UBc9lDXRnZyS6v9g .node-icon-7{font-size:40px;color:black;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .edge-depth-7{stroke-width:-7;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled circle,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:lightgray;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:#efefef;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-8 rect,#mermaid-svg-UBc9lDXRnZyS6v9g .section-8 path,#mermaid-svg-UBc9lDXRnZyS6v9g .section-8 circle,#mermaid-svg-UBc9lDXRnZyS6v9g .section-8 polygon,#mermaid-svg-UBc9lDXRnZyS6v9g .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .section-8 text{fill:black;}#mermaid-svg-UBc9lDXRnZyS6v9g .node-icon-8{font-size:40px;color:black;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .edge-depth-8{stroke-width:-10;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled circle,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:lightgray;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:#efefef;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-9 rect,#mermaid-svg-UBc9lDXRnZyS6v9g .section-9 path,#mermaid-svg-UBc9lDXRnZyS6v9g .section-9 circle,#mermaid-svg-UBc9lDXRnZyS6v9g .section-9 polygon,#mermaid-svg-UBc9lDXRnZyS6v9g .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .section-9 text{fill:black;}#mermaid-svg-UBc9lDXRnZyS6v9g .node-icon-9{font-size:40px;color:black;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .edge-depth-9{stroke-width:-13;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled circle,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:lightgray;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:#efefef;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-10 rect,#mermaid-svg-UBc9lDXRnZyS6v9g .section-10 path,#mermaid-svg-UBc9lDXRnZyS6v9g .section-10 circle,#mermaid-svg-UBc9lDXRnZyS6v9g .section-10 polygon,#mermaid-svg-UBc9lDXRnZyS6v9g .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .section-10 text{fill:black;}#mermaid-svg-UBc9lDXRnZyS6v9g .node-icon-10{font-size:40px;color:black;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .edge-depth-10{stroke-width:-16;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled circle,#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:lightgray;}#mermaid-svg-UBc9lDXRnZyS6v9g .disabled text{fill:#efefef;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-root rect,#mermaid-svg-UBc9lDXRnZyS6v9g .section-root path,#mermaid-svg-UBc9lDXRnZyS6v9g .section-root circle,#mermaid-svg-UBc9lDXRnZyS6v9g .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-UBc9lDXRnZyS6v9g .section-root text{fill:#ffffff;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-root span{color:#ffffff;}#mermaid-svg-UBc9lDXRnZyS6v9g .section-2 span{color:#ffffff;}#mermaid-svg-UBc9lDXRnZyS6v9g .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-UBc9lDXRnZyS6v9g .edge{fill:none;}#mermaid-svg-UBc9lDXRnZyS6v9g .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-UBc9lDXRnZyS6v9g :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 好 RAG 设计
切分
语义切分
chunk 大小
父文档检索
检索
混合检索
重排序
元数据过滤
生成
只据资料答
标注来源
评估
召回率
准确率
测试集
工程化
缓存
流式输出
成本
模型分级
缓存命中
7.1 六个设计决策清单
| 维度 | 好 RAG 的做法 | 常见错误 |
|---|---|---|
| 切分 | 语义切分 + 合理 chunk 大小 + overlap | 固定长度乱切,或 chunk 过大过小 |
| 检索 | 混合检索(BM25+向量)+ 重排序 + 元数据过滤 | 只用纯向量检索 |
| 生成 | 只据资料答、标注来源、不知道就说 | 让模型自由发挥,导致幻觉 |
| 评估 | 建测试集,量化召回率/准确率,迭代 | 不做评估,凭感觉调 |
| 工程化 | 缓存、流式输出、异步 | 每次重复检索、同步阻塞 |
| 成本 | 模型分级、缓存命中 | 所有问题都用最贵模型 |
7.2 评估:好 RAG 是"测"出来的,不是"感觉"出来的
没有评估的 RAG 优化都是玄学。要建一个测试集:准备一批"问题 + 正确答案(或正确来源文档)",然后量化两个指标:
- 召回率(Recall):该检索到的相关文档,有没有被检索到?------衡量"检索"好不好;
- 准确率/正确率:最终答案对不对?------衡量"生成"好不好。
python
# 简化版评估:看正确文档有没有出现在检索结果里
def recall_at_k(queries, golden_docs, retriever, k=5):
hits = 0
for q, gold in zip(queries, golden_docs):
retrieved = retriever(q, k) # 检索 top-k
if gold in retrieved:
hits += 1
return hits / len(queries) # 命中率
优化的正确姿势 :先测出基线(当前 recall 是多少)→ 改一个设计(比如加混合检索)→ 再测 → 变好了就保留,变差了就回退。用数据说话,而不是拍脑袋。
八、完整实战:把 RAG 接进 FastAPI
把前面所有步骤串起来,接进第十课的 FastAPI 架构:
python
from fastapi import FastAPI
from pydantic import BaseModel
from sentence_transformers import SentenceTransformer
import chromadb
app = FastAPI()
# ---- 初始化(启动时执行一次)----
embed_model = SentenceTransformer("BAAI/bge-small-zh-v1.5")
client = chromadb.Client()
collection = client.create_collection("knowledge_base")
# ---- 建库:文档 → 切分 → 向量化 → 存入向量库 ----
def build_index(documents: list[str]):
for i, doc in enumerate(documents):
chunks = split_text(doc, chunk_size=500, overlap=50) # 切分
for chunk in chunks:
vec = embed_model.encode(chunk).tolist() # 向量化
collection.add(ids=[f"{i}_{len(chunk)}"], embeddings=[vec],
documents=[chunk])
# ---- 检索:问题 → 向量化 → 混合检索 → 重排序 ----
def retrieve(question: str, top_k: int = 5):
q_vec = embed_model.encode(question).tolist()
return collection.query(query_embeddings=[q_vec], n_results=top_k)["documents"][0]
# ---- 生成:拼 prompt → 调大模型 ----
@app.post("/ask")
async def ask(q: Question):
chunks = retrieve(q.text) # 检索
prompt = build_prompt(q.text, chunks) # 拼 prompt
answer = await call_llm_stream(prompt) # 流式生成
return answer
这张图把 RAG 和第十课的架构对上了 :RAG 的"检索 + 生成"就是服务层的两个能力,向量库就是数据层的新成员。整条链路是:问题 → 向量化 → 检索 → 拼 prompt → 大模型 → 流式返回。
九、常见坑与避坑指南
| 坑 | 原因 | 解决方案 |
|---|---|---|
| 答案瞎编 | prompt 没约束"只据资料答" | 加"只据资料答 + 不知道就说 + 标来源" |
| 检索不准 | 只用纯向量检索 | 混合检索(BM25+向量)+ 重排序 |
| 切分后答案断章取义 | chunk 切太小 / 没 overlap | 语义切分 + 合理 chunk 大小 + overlap |
| 重复检索浪费钱 | 相同问题反复调模型 | 加缓存,命中直接返回 |
| 文档更新后查不到新内容 | 向量库没同步更新 | 文档变更时重建/增量更新索引 |
| 检索到无关分类的内容 | 没做元数据过滤 | 按分类/时间/权限先过滤 |
总结:一张图带走
这课的核心,一句话串起来:
- RAG = 开卷考试:先检索私有资料(翻书),再生成答案(答题),给大模型补上它不知道的知识。
- 五步链路:加载 → 切分 → 向量化 → 检索 → 生成。
- 切分设计:语义切分 + 合理 chunk 大小 + overlap,避免"混入无关"或"断章取义"。
- 检索设计(好 RAG 的关键):混合检索(BM25 关键词 + 向量语义)+ RRF 融合 + 重排序 + 元数据过滤。
- 生成设计:prompt 强制"只据资料答 + 标注来源 + 不知道就说",杜绝幻觉。
- 好 RAG 是测出来的 :建测试集,量化召回率/准确率,用数据迭代,不拍脑袋。

三个立即可用的行动:
- 拿一份你手头的文档,先按本文的切分函数跑一遍,感受不同
chunk_size对切分结果的影响; - 用
sentence-transformers+chromadb把"切分 → 向量化 → 检索"三步跑通,打印检索结果看看准不准; - 准备 10 个"问题 + 正确答案",用第七节的
recall_at_k测出你的基线,然后试着加混合检索,看 recall 有没有提升。
如果这篇对你有帮助,点个赞、收藏一下 ,下一篇我们讲 Agent 开发------让大模型不仅会"查资料回答",还能"调工具、做决策"。
关注我,我会持续更新 AI 应用开发的前沿实战,陪你从入门到能独立设计生产级 RAG。
你在做 RAG 时,检索不准的问题是怎么解决的?是加了混合检索,还是靠调 chunk 大小?评论区聊聊你的实战经验,我挨个回。
(本文 RAG 方案基于主流开源实践,embedding 模型、向量库、重排序模型可按项目替换,具体版本以官方文档为准,有变化评论区提醒我。)