RAG检索增强-向量库与Chunking

RAG 检索增强 · 向量库与 Chunking

风格说明 :本篇是 设计型 + 数学型 + 操作型混合 ------前半段(§1-§4)讲 RAG 全景与核心选型;中段(§5-§8)讲 chunking、embedding、向量库、re-ranking 的设计权衡;后半段(§9-§11)讲大厂题、真实事故;§12-§13 为 Staff/Architect 向量库选型与进阶范式纵深;§99 为本章冲刺满分答与 Checklist。这是当代 AI 应用的"必杀技"------没做过 RAG 的 AI 工程师面试基本通不过

前置阅读01-Transformer与Attention.md(embedding 的物理基础是 Transformer encoder);01-Prompt工程-Few-Shot-CoT与Tool-Use.md(RAG 是一种特殊的 Tool Use)。

后续展开01-电商AI辅助交易场景.md(RAG 在电商商品理解 / 客服 / 知识库的落地)。

Agent 消费检索结果 (研判、二跳、拒答)→ 02-Agent工程实践-生产落地Playbook.md §5


1. RAG 的"工程定位"

1.1 一句话定义

RAG(Retrieval-Augmented Generation,检索增强生成) :在 LLM 生成回答前,先从外部知识库检索相关文档片段,把片段拼到 prompt 里再让 LLM 生成------把"封闭考试"变成"开卷考试"

1.2 RAG vs Fine-tune vs 长上下文

维度 RAG Fine-tune 长上下文直塞
更新成本 极低(改文档) 高(重训模型) 中(重新上传)
可解释性 高(能引用来源) 低(黑盒)
延迟 中(+ 检索) 低(无检索) 中(长上下文慢)
成本 中(向量库 + 检索) 高(训练 + 部署多模型) 高(长上下文 token 多)
知识容量 TB 级文档 限于训练数据 限于 context window(128k-2M)
时效性 实时更新 训练时 freeze 实时但需手动塞
隐私 数据不出库 训练数据进模型 数据在 prompt 里
适用 企业知识库、客服、文档 QA 风格定制、特定领域微调 单次任务、小知识库

1.3 RAG 的"5 大极端挑战"

挑战 普通系统 RAG 系统
召回率 不重要 必须找到对的片段(错召回 = 错答)
chunk 切分 不存在 切大了不准,切小了失上下文
embedding 漂移 不存在 模型版本变了,所有索引重建
上下文长度 平稳 塞多了模型乱,塞少了不全
来源可追溯 不重要 必须能告诉用户答案来自哪个文档

1.4 容量估算账本

text 复制代码
某典型企业 RAG 场景:
  - 1000 万份文档 (产品手册 + FAQ + 内部 wiki)
  - 平均每文档 5 KB
  - 总文本量 50 GB

Chunk 拆分:
  - chunk_size = 500 tokens ≈ 1500 字符
  - chunks = 50 GB / 1.5 KB ≈ 33M chunks

Embedding 存储:
  - dim = 1536 (OpenAI text-embedding-3-small)
  - 单个 vector: 1536 × 4B (fp32) = 6 KB
                 1536 × 2B (fp16) = 3 KB
                 1536 × 1B (int8) = 1.5 KB
  - 总存储: 33M × 1.5 KB = 50 GB (int8) ~ 200 GB (fp32)

QPS:
  - 用户查询 1000 QPS
  - 每查询召回 top-20
  - 向量库 QPS = 1000 × 1 = 1000 (单库可扛)

带宽:
  - 单 vector 1.5 KB × top-20 × 1000 QPS = 30 MB/s

2. RAG 的"标准 6 步流水线"

2.1 完整流程

#mermaid-svg-ClokOXniJf1XP8kB{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-ClokOXniJf1XP8kB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ClokOXniJf1XP8kB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ClokOXniJf1XP8kB .error-icon{fill:#552222;}#mermaid-svg-ClokOXniJf1XP8kB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ClokOXniJf1XP8kB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ClokOXniJf1XP8kB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ClokOXniJf1XP8kB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ClokOXniJf1XP8kB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ClokOXniJf1XP8kB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ClokOXniJf1XP8kB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ClokOXniJf1XP8kB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ClokOXniJf1XP8kB .marker.cross{stroke:#333333;}#mermaid-svg-ClokOXniJf1XP8kB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ClokOXniJf1XP8kB p{margin:0;}#mermaid-svg-ClokOXniJf1XP8kB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ClokOXniJf1XP8kB .cluster-label text{fill:#333;}#mermaid-svg-ClokOXniJf1XP8kB .cluster-label span{color:#333;}#mermaid-svg-ClokOXniJf1XP8kB .cluster-label span p{background-color:transparent;}#mermaid-svg-ClokOXniJf1XP8kB .label text,#mermaid-svg-ClokOXniJf1XP8kB span{fill:#333;color:#333;}#mermaid-svg-ClokOXniJf1XP8kB .node rect,#mermaid-svg-ClokOXniJf1XP8kB .node circle,#mermaid-svg-ClokOXniJf1XP8kB .node ellipse,#mermaid-svg-ClokOXniJf1XP8kB .node polygon,#mermaid-svg-ClokOXniJf1XP8kB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ClokOXniJf1XP8kB .rough-node .label text,#mermaid-svg-ClokOXniJf1XP8kB .node .label text,#mermaid-svg-ClokOXniJf1XP8kB .image-shape .label,#mermaid-svg-ClokOXniJf1XP8kB .icon-shape .label{text-anchor:middle;}#mermaid-svg-ClokOXniJf1XP8kB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ClokOXniJf1XP8kB .rough-node .label,#mermaid-svg-ClokOXniJf1XP8kB .node .label,#mermaid-svg-ClokOXniJf1XP8kB .image-shape .label,#mermaid-svg-ClokOXniJf1XP8kB .icon-shape .label{text-align:center;}#mermaid-svg-ClokOXniJf1XP8kB .node.clickable{cursor:pointer;}#mermaid-svg-ClokOXniJf1XP8kB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ClokOXniJf1XP8kB .arrowheadPath{fill:#333333;}#mermaid-svg-ClokOXniJf1XP8kB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ClokOXniJf1XP8kB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ClokOXniJf1XP8kB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ClokOXniJf1XP8kB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ClokOXniJf1XP8kB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ClokOXniJf1XP8kB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ClokOXniJf1XP8kB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ClokOXniJf1XP8kB .cluster text{fill:#333;}#mermaid-svg-ClokOXniJf1XP8kB .cluster span{color:#333;}#mermaid-svg-ClokOXniJf1XP8kB 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-ClokOXniJf1XP8kB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ClokOXniJf1XP8kB rect.text{fill:none;stroke-width:0;}#mermaid-svg-ClokOXniJf1XP8kB .icon-shape,#mermaid-svg-ClokOXniJf1XP8kB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ClokOXniJf1XP8kB .icon-shape p,#mermaid-svg-ClokOXniJf1XP8kB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ClokOXniJf1XP8kB .icon-shape .label rect,#mermaid-svg-ClokOXniJf1XP8kB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ClokOXniJf1XP8kB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ClokOXniJf1XP8kB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ClokOXniJf1XP8kB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 离线
查询
用户问题
预处理

改写/扩展
Embedding
向量召回

top-K
Re-rank

top-N
Prompt 增强
LLM 生成
最终回答 + 引用
文档库
Chunking
Embedding
向量库

2.2 6 步详解

步骤 输入 输出 关键技术
0. 离线索引 原始文档 向量库 Chunking + Embedding + 向量库
1. 预处理 用户问题 多个查询变体 Query 改写 / HyDE / 子问题分解
2. Embedding 文本 向量 text-embedding-3, bge, jina
3. 召回 向量 + 过滤条件 top-K 候选 ANN(HNSW / IVF)+ metadata filter
4. Re-rank top-K top-N Cross-encoder / cohere rerank
5. 生成 问题 + top-N 回答 LLM + 引用结构化

2.3 离线 vs 在线分离

#mermaid-svg-CAOizxklPiuRGQIv{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-CAOizxklPiuRGQIv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CAOizxklPiuRGQIv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CAOizxklPiuRGQIv .error-icon{fill:#552222;}#mermaid-svg-CAOizxklPiuRGQIv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CAOizxklPiuRGQIv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CAOizxklPiuRGQIv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CAOizxklPiuRGQIv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CAOizxklPiuRGQIv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CAOizxklPiuRGQIv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CAOizxklPiuRGQIv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CAOizxklPiuRGQIv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CAOizxklPiuRGQIv .marker.cross{stroke:#333333;}#mermaid-svg-CAOizxklPiuRGQIv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CAOizxklPiuRGQIv p{margin:0;}#mermaid-svg-CAOizxklPiuRGQIv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CAOizxklPiuRGQIv .cluster-label text{fill:#333;}#mermaid-svg-CAOizxklPiuRGQIv .cluster-label span{color:#333;}#mermaid-svg-CAOizxklPiuRGQIv .cluster-label span p{background-color:transparent;}#mermaid-svg-CAOizxklPiuRGQIv .label text,#mermaid-svg-CAOizxklPiuRGQIv span{fill:#333;color:#333;}#mermaid-svg-CAOizxklPiuRGQIv .node rect,#mermaid-svg-CAOizxklPiuRGQIv .node circle,#mermaid-svg-CAOizxklPiuRGQIv .node ellipse,#mermaid-svg-CAOizxklPiuRGQIv .node polygon,#mermaid-svg-CAOizxklPiuRGQIv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CAOizxklPiuRGQIv .rough-node .label text,#mermaid-svg-CAOizxklPiuRGQIv .node .label text,#mermaid-svg-CAOizxklPiuRGQIv .image-shape .label,#mermaid-svg-CAOizxklPiuRGQIv .icon-shape .label{text-anchor:middle;}#mermaid-svg-CAOizxklPiuRGQIv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CAOizxklPiuRGQIv .rough-node .label,#mermaid-svg-CAOizxklPiuRGQIv .node .label,#mermaid-svg-CAOizxklPiuRGQIv .image-shape .label,#mermaid-svg-CAOizxklPiuRGQIv .icon-shape .label{text-align:center;}#mermaid-svg-CAOizxklPiuRGQIv .node.clickable{cursor:pointer;}#mermaid-svg-CAOizxklPiuRGQIv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CAOizxklPiuRGQIv .arrowheadPath{fill:#333333;}#mermaid-svg-CAOizxklPiuRGQIv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CAOizxklPiuRGQIv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CAOizxklPiuRGQIv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CAOizxklPiuRGQIv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CAOizxklPiuRGQIv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CAOizxklPiuRGQIv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CAOizxklPiuRGQIv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CAOizxklPiuRGQIv .cluster text{fill:#333;}#mermaid-svg-CAOizxklPiuRGQIv .cluster span{color:#333;}#mermaid-svg-CAOizxklPiuRGQIv 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-CAOizxklPiuRGQIv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CAOizxklPiuRGQIv rect.text{fill:none;stroke-width:0;}#mermaid-svg-CAOizxklPiuRGQIv .icon-shape,#mermaid-svg-CAOizxklPiuRGQIv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CAOizxklPiuRGQIv .icon-shape p,#mermaid-svg-CAOizxklPiuRGQIv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CAOizxklPiuRGQIv .icon-shape .label rect,#mermaid-svg-CAOizxklPiuRGQIv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CAOizxklPiuRGQIv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CAOizxklPiuRGQIv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CAOizxklPiuRGQIv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 在线(毫秒级,请求级)
离线(小时级,批量)
文档源
加载/解析
Chunking
Embedding
建索引
向量库
用户问题
Embedding
搜索 top-K
Re-rank
LLM 生成

关键设计原则

  • 离线慢 + 重:CPU 多线程、批量、ETL pipeline;
  • 在线快 + 轻:单请求 < 500ms P99;
  • 配置同步:embedding model 版本、chunk_size、index 参数双方一致。

3. Chunking 策略:切割文档的艺术

3.1 为什么 chunk_size 是 RAG 的"灵魂参数"

chunk_size 优点 缺点
小(100-200 token) 精确匹配;召回相关段落 上下文不全;多片段拼接困难
中(400-600 token) 甜区:均衡精度与上下文 仍需 overlap 防割裂
大(1000-2000 token) 上下文完整 召回准确率下降;token 浪费

实测(某企业知识库,1000 个 QA):

chunk_size 召回准确率 答案完整性 LLM token 消耗
100 0.62 0.45(信息散)
300 0.85 0.78
500 0.91 0.88
1000 0.83 0.92
2000 0.70 0.95(但 LLM 易跑偏) 极高

3.2 五大 Chunking 策略

#mermaid-svg-RfH5kPL3YBKdZuhH{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-RfH5kPL3YBKdZuhH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RfH5kPL3YBKdZuhH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RfH5kPL3YBKdZuhH .error-icon{fill:#552222;}#mermaid-svg-RfH5kPL3YBKdZuhH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RfH5kPL3YBKdZuhH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RfH5kPL3YBKdZuhH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RfH5kPL3YBKdZuhH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RfH5kPL3YBKdZuhH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RfH5kPL3YBKdZuhH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RfH5kPL3YBKdZuhH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RfH5kPL3YBKdZuhH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RfH5kPL3YBKdZuhH .marker.cross{stroke:#333333;}#mermaid-svg-RfH5kPL3YBKdZuhH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RfH5kPL3YBKdZuhH p{margin:0;}#mermaid-svg-RfH5kPL3YBKdZuhH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-RfH5kPL3YBKdZuhH .cluster-label text{fill:#333;}#mermaid-svg-RfH5kPL3YBKdZuhH .cluster-label span{color:#333;}#mermaid-svg-RfH5kPL3YBKdZuhH .cluster-label span p{background-color:transparent;}#mermaid-svg-RfH5kPL3YBKdZuhH .label text,#mermaid-svg-RfH5kPL3YBKdZuhH span{fill:#333;color:#333;}#mermaid-svg-RfH5kPL3YBKdZuhH .node rect,#mermaid-svg-RfH5kPL3YBKdZuhH .node circle,#mermaid-svg-RfH5kPL3YBKdZuhH .node ellipse,#mermaid-svg-RfH5kPL3YBKdZuhH .node polygon,#mermaid-svg-RfH5kPL3YBKdZuhH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RfH5kPL3YBKdZuhH .rough-node .label text,#mermaid-svg-RfH5kPL3YBKdZuhH .node .label text,#mermaid-svg-RfH5kPL3YBKdZuhH .image-shape .label,#mermaid-svg-RfH5kPL3YBKdZuhH .icon-shape .label{text-anchor:middle;}#mermaid-svg-RfH5kPL3YBKdZuhH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-RfH5kPL3YBKdZuhH .rough-node .label,#mermaid-svg-RfH5kPL3YBKdZuhH .node .label,#mermaid-svg-RfH5kPL3YBKdZuhH .image-shape .label,#mermaid-svg-RfH5kPL3YBKdZuhH .icon-shape .label{text-align:center;}#mermaid-svg-RfH5kPL3YBKdZuhH .node.clickable{cursor:pointer;}#mermaid-svg-RfH5kPL3YBKdZuhH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-RfH5kPL3YBKdZuhH .arrowheadPath{fill:#333333;}#mermaid-svg-RfH5kPL3YBKdZuhH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RfH5kPL3YBKdZuhH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RfH5kPL3YBKdZuhH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RfH5kPL3YBKdZuhH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RfH5kPL3YBKdZuhH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RfH5kPL3YBKdZuhH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-RfH5kPL3YBKdZuhH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RfH5kPL3YBKdZuhH .cluster text{fill:#333;}#mermaid-svg-RfH5kPL3YBKdZuhH .cluster span{color:#333;}#mermaid-svg-RfH5kPL3YBKdZuhH 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-RfH5kPL3YBKdZuhH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RfH5kPL3YBKdZuhH rect.text{fill:none;stroke-width:0;}#mermaid-svg-RfH5kPL3YBKdZuhH .icon-shape,#mermaid-svg-RfH5kPL3YBKdZuhH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RfH5kPL3YBKdZuhH .icon-shape p,#mermaid-svg-RfH5kPL3YBKdZuhH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-RfH5kPL3YBKdZuhH .icon-shape .label rect,#mermaid-svg-RfH5kPL3YBKdZuhH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RfH5kPL3YBKdZuhH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-RfH5kPL3YBKdZuhH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-RfH5kPL3YBKdZuhH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 固定长度切
简单

易割裂
按句子边界
保语义

大小不均
按段落 / 标题
保结构

需文档有 Markdown
递归字符切
兜底

LangChain 默认
语义切
质量最高

慢/贵

策略 1:固定长度(Fixed Size)
python 复制代码
def fixed_size_chunk(text, chunk_size=500, overlap=50):
    tokens = tokenizer.encode(text)
    chunks = []
    for i in range(0, len(tokens), chunk_size - overlap):
        chunk = tokens[i:i + chunk_size]
        chunks.append(tokenizer.decode(chunk))
    return chunks

适用 :纯文本 / 简单文档;快速原型。

:可能在句子中间切断。

策略 2:按句子边界(Sentence-based)
python 复制代码
import spacy
nlp = spacy.load("zh_core_web_sm")  # 或英文模型

def sentence_chunk(text, max_tokens=500):
    doc = nlp(text)
    sentences = [s.text for s in doc.sents]
    chunks, cur, cur_size = [], [], 0
    for sent in sentences:
        size = len(tokenizer.encode(sent))
        if cur_size + size > max_tokens:
            chunks.append("".join(cur))
            cur, cur_size = [sent], size
        else:
            cur.append(sent)
            cur_size += size
    if cur:
        chunks.append("".join(cur))
    return chunks

适用 :长文章 / 散文。

:句子大小不均,可能 chunk 大小波动大。

策略 3:按段落 / 标题(Structure-based)
python 复制代码
def markdown_chunk(text, max_tokens=500):
    # 按 H1/H2 标题切
    sections = re.split(r'\n(#{1,3}\s)', text)
    chunks = []
    for sec in sections:
        if len(tokenizer.encode(sec)) > max_tokens:
            # 太大再用句子切
            chunks.extend(sentence_chunk(sec, max_tokens))
        else:
            chunks.append(sec)
    return chunks

适用 :技术文档 / Markdown / 含结构的 PDF。

优势:保留章节结构,效果最佳。

策略 4:递归字符切(LangChain 默认)
python 复制代码
from langchain.text_splitter import RecursiveCharacterTextSplitter

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

机制 :按优先级尝试 separator------优先按"\n\n"切,太大再按"\n"切,依次降级。

适用:兜底方案,覆盖各种格式。

策略 5:语义切(Semantic Chunking)
python 复制代码
def semantic_chunk(text, threshold=0.85):
    """根据相邻句子的语义距离决定切分点"""
    sentences = split_into_sentences(text)
    embeddings = [embed(s) for s in sentences]
    chunks, cur = [], [sentences[0]]
    for i in range(1, len(sentences)):
        sim = cosine_sim(embeddings[i-1], embeddings[i])
        if sim < threshold:
            # 语义跳变 → 切
            chunks.append("".join(cur))
            cur = [sentences[i]]
        else:
            cur.append(sentences[i])
    if cur:
        chunks.append("".join(cur))
    return chunks

适用 :高质量场景;不在乎慢。

:embedding 调用多次,离线时间 ×3-5。

3.3 Chunking 决策树

#mermaid-svg-0liASVbY9CmrsGPM{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-0liASVbY9CmrsGPM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0liASVbY9CmrsGPM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0liASVbY9CmrsGPM .error-icon{fill:#552222;}#mermaid-svg-0liASVbY9CmrsGPM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0liASVbY9CmrsGPM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0liASVbY9CmrsGPM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0liASVbY9CmrsGPM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0liASVbY9CmrsGPM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0liASVbY9CmrsGPM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0liASVbY9CmrsGPM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0liASVbY9CmrsGPM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0liASVbY9CmrsGPM .marker.cross{stroke:#333333;}#mermaid-svg-0liASVbY9CmrsGPM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0liASVbY9CmrsGPM p{margin:0;}#mermaid-svg-0liASVbY9CmrsGPM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0liASVbY9CmrsGPM .cluster-label text{fill:#333;}#mermaid-svg-0liASVbY9CmrsGPM .cluster-label span{color:#333;}#mermaid-svg-0liASVbY9CmrsGPM .cluster-label span p{background-color:transparent;}#mermaid-svg-0liASVbY9CmrsGPM .label text,#mermaid-svg-0liASVbY9CmrsGPM span{fill:#333;color:#333;}#mermaid-svg-0liASVbY9CmrsGPM .node rect,#mermaid-svg-0liASVbY9CmrsGPM .node circle,#mermaid-svg-0liASVbY9CmrsGPM .node ellipse,#mermaid-svg-0liASVbY9CmrsGPM .node polygon,#mermaid-svg-0liASVbY9CmrsGPM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0liASVbY9CmrsGPM .rough-node .label text,#mermaid-svg-0liASVbY9CmrsGPM .node .label text,#mermaid-svg-0liASVbY9CmrsGPM .image-shape .label,#mermaid-svg-0liASVbY9CmrsGPM .icon-shape .label{text-anchor:middle;}#mermaid-svg-0liASVbY9CmrsGPM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0liASVbY9CmrsGPM .rough-node .label,#mermaid-svg-0liASVbY9CmrsGPM .node .label,#mermaid-svg-0liASVbY9CmrsGPM .image-shape .label,#mermaid-svg-0liASVbY9CmrsGPM .icon-shape .label{text-align:center;}#mermaid-svg-0liASVbY9CmrsGPM .node.clickable{cursor:pointer;}#mermaid-svg-0liASVbY9CmrsGPM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0liASVbY9CmrsGPM .arrowheadPath{fill:#333333;}#mermaid-svg-0liASVbY9CmrsGPM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0liASVbY9CmrsGPM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0liASVbY9CmrsGPM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0liASVbY9CmrsGPM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0liASVbY9CmrsGPM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0liASVbY9CmrsGPM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0liASVbY9CmrsGPM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0liASVbY9CmrsGPM .cluster text{fill:#333;}#mermaid-svg-0liASVbY9CmrsGPM .cluster span{color:#333;}#mermaid-svg-0liASVbY9CmrsGPM 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-0liASVbY9CmrsGPM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0liASVbY9CmrsGPM rect.text{fill:none;stroke-width:0;}#mermaid-svg-0liASVbY9CmrsGPM .icon-shape,#mermaid-svg-0liASVbY9CmrsGPM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0liASVbY9CmrsGPM .icon-shape p,#mermaid-svg-0liASVbY9CmrsGPM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0liASVbY9CmrsGPM .icon-shape .label rect,#mermaid-svg-0liASVbY9CmrsGPM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0liASVbY9CmrsGPM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0liASVbY9CmrsGPM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0liASVbY9CmrsGPM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 结构化 MD/HTML
长散文
混合/未知
高质量需求
快速原型
文档类型?
策略 3 结构
策略 2 句子
策略 4 递归字符
策略 5 语义
策略 1 固定

3.4 Overlap:为什么必须有

问题:信息可能正好在 chunk 边界。

例子

text 复制代码
[chunk 1] "...公司财报显示,2025 年 Q3 营收 50 亿"  ← 在这切
[chunk 2] "元,同比增长 30%。"

用户问"2025 Q3 营收增长多少",可能只召回 chunk 1,缺"30%"信息。

解法:overlap = 10% × chunk_size(典型 50-100 token)。
#mermaid-svg-iGhSKt2nMVK6QWb0{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-iGhSKt2nMVK6QWb0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-iGhSKt2nMVK6QWb0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-iGhSKt2nMVK6QWb0 .error-icon{fill:#552222;}#mermaid-svg-iGhSKt2nMVK6QWb0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-iGhSKt2nMVK6QWb0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-iGhSKt2nMVK6QWb0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-iGhSKt2nMVK6QWb0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-iGhSKt2nMVK6QWb0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-iGhSKt2nMVK6QWb0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-iGhSKt2nMVK6QWb0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-iGhSKt2nMVK6QWb0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-iGhSKt2nMVK6QWb0 .marker.cross{stroke:#333333;}#mermaid-svg-iGhSKt2nMVK6QWb0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-iGhSKt2nMVK6QWb0 p{margin:0;}#mermaid-svg-iGhSKt2nMVK6QWb0 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-iGhSKt2nMVK6QWb0 .cluster-label text{fill:#333;}#mermaid-svg-iGhSKt2nMVK6QWb0 .cluster-label span{color:#333;}#mermaid-svg-iGhSKt2nMVK6QWb0 .cluster-label span p{background-color:transparent;}#mermaid-svg-iGhSKt2nMVK6QWb0 .label text,#mermaid-svg-iGhSKt2nMVK6QWb0 span{fill:#333;color:#333;}#mermaid-svg-iGhSKt2nMVK6QWb0 .node rect,#mermaid-svg-iGhSKt2nMVK6QWb0 .node circle,#mermaid-svg-iGhSKt2nMVK6QWb0 .node ellipse,#mermaid-svg-iGhSKt2nMVK6QWb0 .node polygon,#mermaid-svg-iGhSKt2nMVK6QWb0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-iGhSKt2nMVK6QWb0 .rough-node .label text,#mermaid-svg-iGhSKt2nMVK6QWb0 .node .label text,#mermaid-svg-iGhSKt2nMVK6QWb0 .image-shape .label,#mermaid-svg-iGhSKt2nMVK6QWb0 .icon-shape .label{text-anchor:middle;}#mermaid-svg-iGhSKt2nMVK6QWb0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-iGhSKt2nMVK6QWb0 .rough-node .label,#mermaid-svg-iGhSKt2nMVK6QWb0 .node .label,#mermaid-svg-iGhSKt2nMVK6QWb0 .image-shape .label,#mermaid-svg-iGhSKt2nMVK6QWb0 .icon-shape .label{text-align:center;}#mermaid-svg-iGhSKt2nMVK6QWb0 .node.clickable{cursor:pointer;}#mermaid-svg-iGhSKt2nMVK6QWb0 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-iGhSKt2nMVK6QWb0 .arrowheadPath{fill:#333333;}#mermaid-svg-iGhSKt2nMVK6QWb0 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-iGhSKt2nMVK6QWb0 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-iGhSKt2nMVK6QWb0 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iGhSKt2nMVK6QWb0 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-iGhSKt2nMVK6QWb0 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iGhSKt2nMVK6QWb0 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-iGhSKt2nMVK6QWb0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-iGhSKt2nMVK6QWb0 .cluster text{fill:#333;}#mermaid-svg-iGhSKt2nMVK6QWb0 .cluster span{color:#333;}#mermaid-svg-iGhSKt2nMVK6QWb0 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-iGhSKt2nMVK6QWb0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-iGhSKt2nMVK6QWb0 rect.text{fill:none;stroke-width:0;}#mermaid-svg-iGhSKt2nMVK6QWb0 .icon-shape,#mermaid-svg-iGhSKt2nMVK6QWb0 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iGhSKt2nMVK6QWb0 .icon-shape p,#mermaid-svg-iGhSKt2nMVK6QWb0 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-iGhSKt2nMVK6QWb0 .icon-shape .label rect,#mermaid-svg-iGhSKt2nMVK6QWb0 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iGhSKt2nMVK6QWb0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-iGhSKt2nMVK6QWb0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-iGhSKt2nMVK6QWb0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} chunk 1

0-500
overlap

450-500
chunk 2

450-950
overlap

900-950
chunk 3

900-1400

3.5 Chunk 元数据(关键但常被忽略)

每个 chunk 应该携带:

python 复制代码
chunk_metadata = {
    "doc_id": "doc_123",
    "doc_title": "iPhone 15 用户手册",
    "section": "电池保养",
    "page": 12,
    "chunk_index": 5,
    "total_chunks": 23,
    "doc_url": "https://...",
    "doc_updated_at": "2025-08-10",
    "tags": ["iphone", "battery"],
}

用途

  • 检索时 元数据 filter("only this product line");
  • 回答时 引用追溯("答案来自 iPhone 15 手册第 12 页");
  • 索引时间管理(LRU 淘汰旧文档)。

4. Embedding:文本到向量的转换

4.1 主流 Embedding 模型对照(2026)

模型 维度 上下文 多语言 价格 / 1M token 备注
OpenAI text-embedding-3-small 1536(可降到 256) 8191 $0.02 主流甜区
OpenAI text-embedding-3-large 3072 8191 $0.13 质量更高
Cohere embed-v3 1024 512 $0.10 re-rank 强配
BGE-M3(开源 / BAAI) 1024 8192 中英 自部署 国内首选
Jina v3 (open) 1024 8192 多语言 自部署 长上下文友好
Voyage AI 1024 16k $0.12 代码场景强

4.2 维度选择:1536 还是 256

OpenAI text-embedding-3 的 Matryoshka 技巧:训练时让前 N 维就能用------可以"截断"到 256/512/1024/1536。

维度 召回准确率 存储 适用
256 0.78 1 KB 大规模 / 成本敏感
512 0.85 2 KB 平衡
1024 0.89 4 KB 甜区
1536 0.91 6 KB 质量优先
3072 (large) 0.93 12 KB 极致质量

工程结论:1024 维是甜区;超大规模(>10亿)可考虑 256。

4.3 中英文模型选择

场景 推荐
纯英文 OpenAI text-embedding-3-small
纯中文 BGE-M3 或 Qwen-Embedding
中英文混合 BGE-M3(中英都强)或 OpenAI
跨语言搜索(多语言) Cohere embed-v3 或 BGE-M3

4.4 Embedding 版本管理

核心约束 :query 和 doc 必须用 同版本 embedding 模型------版本不一致 = 向量空间不一致 = 召回崩溃。

python 复制代码
# 必须在索引和查询都记录版本
chunk_metadata["embedding_model"] = "text-embedding-3-small@2024-01"
chunk_metadata["embedding_dim"] = 1536

# 查询时校验
def search(query):
    query_vec = embed(query, model="text-embedding-3-small@2024-01")
    # 如果文档的索引是 ada-002 → 召回结果是垃圾

版本升级流水线

  1. 用新模型 batch 重新 embed 所有 chunk(成本 = chunk 数 × $0.02/1M);
  2. 新旧索引并存 一段时间,灰度切换;
  3. 全量切换后下线旧索引。

典型成本(1000 万 chunk × 500 token = 50 亿 token):

  • 重新 embed = 50 亿 × 0.02/1M = **100**
  • 重建索引:几小时
  • 存储双份:临时 +100% 存储成本

4.5 Embedding 的"对称性"问题

问题:用户 query 和 doc chunk 不是同一类文本。

  • query:"iPhone 电池怎么保养?"(简短问句)
  • doc:「iPhone 电池采用锂离子技术...」(陈述长文)

直接 cosine_sim 可能不准------短问句 vs 长陈述的 embedding 分布不一致

解法 1:HyDE(Hypothetical Document Embeddings)

python 复制代码
def hyde_search(query):
    # 1. 让 LLM 想象一个"假"答案
    fake_doc = llm(f"为以下问题写一段假设性回答:{query}")
    # 2. 用假答案的 embedding 去搜
    fake_doc_vec = embed(fake_doc)
    return vector_db.search(fake_doc_vec, top_k=20)

解法 2:query 与 doc 分离 embedding

python 复制代码
# 训练时 query 和 doc 用不同投影头
query_vec = embed(query, mode="query")
doc_vec = embed(doc, mode="document")
# BGE-M3 等模型原生支持

实测:HyDE 在 FAQ / 客服场景能提升召回 5-10%。


5. 向量库:选型与对比

5.1 主流向量库对照(2026)

向量库 类别 部署 规模 备注
Pinecone 托管 SaaS Cloud only 亿级 上手快、贵
Weaviate 开源 + 托管 自部署 / 云 亿级 图谱集成强
Qdrant 开源 + 托管 自部署 / 云 亿级 Rust 高性能
Milvus / Zilliz 开源 + 托管 自部署 / 云 十亿级 国内首选
Chroma 开源 自部署 千万级 适合本地 / 原型
pgvector PG 扩展 与 PG 同部署 千万级 复用现有 PG
Elasticsearch 搜索引擎 + 向量 自部署 亿级 混合搜索强
Redis Stack KV + 向量 现有 Redis 升级 千万级 低延迟

5.2 选型决策树

#mermaid-svg-37EPrcMbYOnwi7z8{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-37EPrcMbYOnwi7z8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-37EPrcMbYOnwi7z8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-37EPrcMbYOnwi7z8 .error-icon{fill:#552222;}#mermaid-svg-37EPrcMbYOnwi7z8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-37EPrcMbYOnwi7z8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-37EPrcMbYOnwi7z8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-37EPrcMbYOnwi7z8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-37EPrcMbYOnwi7z8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-37EPrcMbYOnwi7z8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-37EPrcMbYOnwi7z8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-37EPrcMbYOnwi7z8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-37EPrcMbYOnwi7z8 .marker.cross{stroke:#333333;}#mermaid-svg-37EPrcMbYOnwi7z8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-37EPrcMbYOnwi7z8 p{margin:0;}#mermaid-svg-37EPrcMbYOnwi7z8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-37EPrcMbYOnwi7z8 .cluster-label text{fill:#333;}#mermaid-svg-37EPrcMbYOnwi7z8 .cluster-label span{color:#333;}#mermaid-svg-37EPrcMbYOnwi7z8 .cluster-label span p{background-color:transparent;}#mermaid-svg-37EPrcMbYOnwi7z8 .label text,#mermaid-svg-37EPrcMbYOnwi7z8 span{fill:#333;color:#333;}#mermaid-svg-37EPrcMbYOnwi7z8 .node rect,#mermaid-svg-37EPrcMbYOnwi7z8 .node circle,#mermaid-svg-37EPrcMbYOnwi7z8 .node ellipse,#mermaid-svg-37EPrcMbYOnwi7z8 .node polygon,#mermaid-svg-37EPrcMbYOnwi7z8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-37EPrcMbYOnwi7z8 .rough-node .label text,#mermaid-svg-37EPrcMbYOnwi7z8 .node .label text,#mermaid-svg-37EPrcMbYOnwi7z8 .image-shape .label,#mermaid-svg-37EPrcMbYOnwi7z8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-37EPrcMbYOnwi7z8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-37EPrcMbYOnwi7z8 .rough-node .label,#mermaid-svg-37EPrcMbYOnwi7z8 .node .label,#mermaid-svg-37EPrcMbYOnwi7z8 .image-shape .label,#mermaid-svg-37EPrcMbYOnwi7z8 .icon-shape .label{text-align:center;}#mermaid-svg-37EPrcMbYOnwi7z8 .node.clickable{cursor:pointer;}#mermaid-svg-37EPrcMbYOnwi7z8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-37EPrcMbYOnwi7z8 .arrowheadPath{fill:#333333;}#mermaid-svg-37EPrcMbYOnwi7z8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-37EPrcMbYOnwi7z8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-37EPrcMbYOnwi7z8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-37EPrcMbYOnwi7z8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-37EPrcMbYOnwi7z8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-37EPrcMbYOnwi7z8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-37EPrcMbYOnwi7z8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-37EPrcMbYOnwi7z8 .cluster text{fill:#333;}#mermaid-svg-37EPrcMbYOnwi7z8 .cluster span{color:#333;}#mermaid-svg-37EPrcMbYOnwi7z8 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-37EPrcMbYOnwi7z8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-37EPrcMbYOnwi7z8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-37EPrcMbYOnwi7z8 .icon-shape,#mermaid-svg-37EPrcMbYOnwi7z8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-37EPrcMbYOnwi7z8 .icon-shape p,#mermaid-svg-37EPrcMbYOnwi7z8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-37EPrcMbYOnwi7z8 .icon-shape .label rect,#mermaid-svg-37EPrcMbYOnwi7z8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-37EPrcMbYOnwi7z8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-37EPrcMbYOnwi7z8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-37EPrcMbYOnwi7z8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} <1M chunks
1M-1亿
有 PG
有 ES
有 Redis
全新
托管, 想省心
自部署, 国外
自部署, 国内
>1亿
需要向量库
规模?
Chroma / pgvector / Redis
已有基础设施?
pgvector
ES + 向量插件
Redis Stack
托管还是自部署?
Pinecone
Qdrant
Milvus
Milvus / Pinecone Pro

5.3 ANN 算法:HNSW vs IVF

向量库底层都用 ANN(Approximate Nearest Neighbor)------精确 KNN 在亿级数据下不可能(O(n) 扫描)。

#mermaid-svg-TiLnnqE3WQQyngeE{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-TiLnnqE3WQQyngeE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TiLnnqE3WQQyngeE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TiLnnqE3WQQyngeE .error-icon{fill:#552222;}#mermaid-svg-TiLnnqE3WQQyngeE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TiLnnqE3WQQyngeE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TiLnnqE3WQQyngeE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TiLnnqE3WQQyngeE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TiLnnqE3WQQyngeE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TiLnnqE3WQQyngeE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TiLnnqE3WQQyngeE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TiLnnqE3WQQyngeE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TiLnnqE3WQQyngeE .marker.cross{stroke:#333333;}#mermaid-svg-TiLnnqE3WQQyngeE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TiLnnqE3WQQyngeE p{margin:0;}#mermaid-svg-TiLnnqE3WQQyngeE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-TiLnnqE3WQQyngeE .cluster-label text{fill:#333;}#mermaid-svg-TiLnnqE3WQQyngeE .cluster-label span{color:#333;}#mermaid-svg-TiLnnqE3WQQyngeE .cluster-label span p{background-color:transparent;}#mermaid-svg-TiLnnqE3WQQyngeE .label text,#mermaid-svg-TiLnnqE3WQQyngeE span{fill:#333;color:#333;}#mermaid-svg-TiLnnqE3WQQyngeE .node rect,#mermaid-svg-TiLnnqE3WQQyngeE .node circle,#mermaid-svg-TiLnnqE3WQQyngeE .node ellipse,#mermaid-svg-TiLnnqE3WQQyngeE .node polygon,#mermaid-svg-TiLnnqE3WQQyngeE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TiLnnqE3WQQyngeE .rough-node .label text,#mermaid-svg-TiLnnqE3WQQyngeE .node .label text,#mermaid-svg-TiLnnqE3WQQyngeE .image-shape .label,#mermaid-svg-TiLnnqE3WQQyngeE .icon-shape .label{text-anchor:middle;}#mermaid-svg-TiLnnqE3WQQyngeE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TiLnnqE3WQQyngeE .rough-node .label,#mermaid-svg-TiLnnqE3WQQyngeE .node .label,#mermaid-svg-TiLnnqE3WQQyngeE .image-shape .label,#mermaid-svg-TiLnnqE3WQQyngeE .icon-shape .label{text-align:center;}#mermaid-svg-TiLnnqE3WQQyngeE .node.clickable{cursor:pointer;}#mermaid-svg-TiLnnqE3WQQyngeE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TiLnnqE3WQQyngeE .arrowheadPath{fill:#333333;}#mermaid-svg-TiLnnqE3WQQyngeE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TiLnnqE3WQQyngeE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TiLnnqE3WQQyngeE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TiLnnqE3WQQyngeE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TiLnnqE3WQQyngeE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TiLnnqE3WQQyngeE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TiLnnqE3WQQyngeE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TiLnnqE3WQQyngeE .cluster text{fill:#333;}#mermaid-svg-TiLnnqE3WQQyngeE .cluster span{color:#333;}#mermaid-svg-TiLnnqE3WQQyngeE 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-TiLnnqE3WQQyngeE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TiLnnqE3WQQyngeE rect.text{fill:none;stroke-width:0;}#mermaid-svg-TiLnnqE3WQQyngeE .icon-shape,#mermaid-svg-TiLnnqE3WQQyngeE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TiLnnqE3WQQyngeE .icon-shape p,#mermaid-svg-TiLnnqE3WQQyngeE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TiLnnqE3WQQyngeE .icon-shape .label rect,#mermaid-svg-TiLnnqE3WQQyngeE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TiLnnqE3WQQyngeE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TiLnnqE3WQQyngeE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TiLnnqE3WQQyngeE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Layer 2 (top, sparse)

少量节点, 长跳
Layer 1

中等节点
Layer 0 (base, all)

所有节点, 短边

特点

  • 多层小世界图,每层节点数指数递减;
  • 查询从顶层粗粒度找到近邻,逐层下钻精化;
  • 召回率 95%+,延迟低(毫秒级);
  • 内存占用高(图结构 + 向量都要在内存)。

典型参数

  • M(每节点边数):16-64,大 = 召回高 + 内存大;
  • ef_construction(建索引时搜索深度):100-500;
  • ef(查询时搜索深度):50-300,大 = 召回高 + 慢。
IVF(Inverted File)

#mermaid-svg-E0WjskhTDyv9HHaz{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-E0WjskhTDyv9HHaz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-E0WjskhTDyv9HHaz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-E0WjskhTDyv9HHaz .error-icon{fill:#552222;}#mermaid-svg-E0WjskhTDyv9HHaz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-E0WjskhTDyv9HHaz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-E0WjskhTDyv9HHaz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-E0WjskhTDyv9HHaz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-E0WjskhTDyv9HHaz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-E0WjskhTDyv9HHaz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-E0WjskhTDyv9HHaz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-E0WjskhTDyv9HHaz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-E0WjskhTDyv9HHaz .marker.cross{stroke:#333333;}#mermaid-svg-E0WjskhTDyv9HHaz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-E0WjskhTDyv9HHaz p{margin:0;}#mermaid-svg-E0WjskhTDyv9HHaz .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-E0WjskhTDyv9HHaz .cluster-label text{fill:#333;}#mermaid-svg-E0WjskhTDyv9HHaz .cluster-label span{color:#333;}#mermaid-svg-E0WjskhTDyv9HHaz .cluster-label span p{background-color:transparent;}#mermaid-svg-E0WjskhTDyv9HHaz .label text,#mermaid-svg-E0WjskhTDyv9HHaz span{fill:#333;color:#333;}#mermaid-svg-E0WjskhTDyv9HHaz .node rect,#mermaid-svg-E0WjskhTDyv9HHaz .node circle,#mermaid-svg-E0WjskhTDyv9HHaz .node ellipse,#mermaid-svg-E0WjskhTDyv9HHaz .node polygon,#mermaid-svg-E0WjskhTDyv9HHaz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-E0WjskhTDyv9HHaz .rough-node .label text,#mermaid-svg-E0WjskhTDyv9HHaz .node .label text,#mermaid-svg-E0WjskhTDyv9HHaz .image-shape .label,#mermaid-svg-E0WjskhTDyv9HHaz .icon-shape .label{text-anchor:middle;}#mermaid-svg-E0WjskhTDyv9HHaz .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-E0WjskhTDyv9HHaz .rough-node .label,#mermaid-svg-E0WjskhTDyv9HHaz .node .label,#mermaid-svg-E0WjskhTDyv9HHaz .image-shape .label,#mermaid-svg-E0WjskhTDyv9HHaz .icon-shape .label{text-align:center;}#mermaid-svg-E0WjskhTDyv9HHaz .node.clickable{cursor:pointer;}#mermaid-svg-E0WjskhTDyv9HHaz .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-E0WjskhTDyv9HHaz .arrowheadPath{fill:#333333;}#mermaid-svg-E0WjskhTDyv9HHaz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-E0WjskhTDyv9HHaz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-E0WjskhTDyv9HHaz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-E0WjskhTDyv9HHaz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-E0WjskhTDyv9HHaz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-E0WjskhTDyv9HHaz .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-E0WjskhTDyv9HHaz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-E0WjskhTDyv9HHaz .cluster text{fill:#333;}#mermaid-svg-E0WjskhTDyv9HHaz .cluster span{color:#333;}#mermaid-svg-E0WjskhTDyv9HHaz 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-E0WjskhTDyv9HHaz .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-E0WjskhTDyv9HHaz rect.text{fill:none;stroke-width:0;}#mermaid-svg-E0WjskhTDyv9HHaz .icon-shape,#mermaid-svg-E0WjskhTDyv9HHaz .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-E0WjskhTDyv9HHaz .icon-shape p,#mermaid-svg-E0WjskhTDyv9HHaz .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-E0WjskhTDyv9HHaz .icon-shape .label rect,#mermaid-svg-E0WjskhTDyv9HHaz .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-E0WjskhTDyv9HHaz .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-E0WjskhTDyv9HHaz .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-E0WjskhTDyv9HHaz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} query
聚类中心 1000 个
选 nprobe 个最近中心
只在这些桶里搜
top-K

特点

  • 把所有向量聚类成 N 个 cluster;
  • 查询时找最近的 nprobe 个 cluster,只在这些桶里扫描;
  • 内存友好(cluster 中心 + 桶内 ID);
  • 召回率比 HNSW 略低。

典型参数

  • nlist(cluster 数):sqrt(n_vectors) 量级;
  • nprobe(查询时 probe 几个 cluster):1-100,大 = 召回高 + 慢。
选型对照
维度 HNSW IVF
召回率(top-10) 0.95+ 0.85-0.92
延迟(百万级) 1-5ms 5-20ms
内存占用 高(图结构)
建索引时间
适合规模 < 1 亿 > 1 亿
工程现状 主流(HNSW 是默认) 超大规模才考虑

5.4 Metadata Filter:检索的第二维度

场景:用户问"iPhone 15 Pro 的售后服务"------只应该召回 iPhone 15 Pro 相关文档,不要召回 iPhone 14。

python 复制代码
results = vector_db.search(
    query_vec,
    top_k=20,
    filter={
        "product": "iphone_15_pro",
        "doc_type": "support",
        "language": "zh",
    }
)

关键设计

  • 索引时把 metadata 一起入库;
  • 查询时 filter(向量库内部优化,不是先 ANN 再过滤);
  • filter 必须 可索引字段(如 categorical),不能是任意 text。

5.5 索引参数调优

HNSW 调优表(参考):

参数 影响 推荐起点
M 召回 + 内存 16
ef_construction 索引质量 + 建索引时间 200
ef_search 查询召回 + 延迟 100
python 复制代码
# Qdrant 示例
client.create_collection(
    collection_name="docs",
    vectors_config={
        "size": 1024,
        "distance": "Cosine",
    },
    hnsw_config={
        "m": 16,
        "ef_construct": 200,
    },
)

# 查询时调 ef_search
client.search(
    collection_name="docs",
    query_vector=q,
    limit=20,
    search_params={"hnsw_ef": 128},
)

5.6 容量与性能账本

单 Qdrant 节点(32 vCPU + 128GB RAM)

向量数 维度 内存占用 QPS 延迟 P99
1M 1024 4GB 10000 2ms
10M 1024 40GB 5000 5ms
50M 1024 200GB(多节点) 2000 10ms
100M 1024 400GB(多节点) 1500 15ms

多副本扩展:QPS ∝ 副本数(读写分离的话);存储 × 副本数。


6. 混合检索(Hybrid Search)

6.1 为什么纯向量召回不够

问题

  • 向量召回擅长 语义相似 ,但 关键词精确匹配 不行;
  • 用户问 "ABC-123 型号的故障",doc 里也有 "ABC-123"------纯向量可能召回 "型号 ABC-456 故障"(语义相似但不对)。

解法:BM25(关键词)+ 向量 双路召回,最后融合。

6.2 Hybrid 架构

#mermaid-svg-HYvOiBBOratt0fii{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-HYvOiBBOratt0fii .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HYvOiBBOratt0fii .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HYvOiBBOratt0fii .error-icon{fill:#552222;}#mermaid-svg-HYvOiBBOratt0fii .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HYvOiBBOratt0fii .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HYvOiBBOratt0fii .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HYvOiBBOratt0fii .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HYvOiBBOratt0fii .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HYvOiBBOratt0fii .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HYvOiBBOratt0fii .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HYvOiBBOratt0fii .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HYvOiBBOratt0fii .marker.cross{stroke:#333333;}#mermaid-svg-HYvOiBBOratt0fii svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HYvOiBBOratt0fii p{margin:0;}#mermaid-svg-HYvOiBBOratt0fii .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HYvOiBBOratt0fii .cluster-label text{fill:#333;}#mermaid-svg-HYvOiBBOratt0fii .cluster-label span{color:#333;}#mermaid-svg-HYvOiBBOratt0fii .cluster-label span p{background-color:transparent;}#mermaid-svg-HYvOiBBOratt0fii .label text,#mermaid-svg-HYvOiBBOratt0fii span{fill:#333;color:#333;}#mermaid-svg-HYvOiBBOratt0fii .node rect,#mermaid-svg-HYvOiBBOratt0fii .node circle,#mermaid-svg-HYvOiBBOratt0fii .node ellipse,#mermaid-svg-HYvOiBBOratt0fii .node polygon,#mermaid-svg-HYvOiBBOratt0fii .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HYvOiBBOratt0fii .rough-node .label text,#mermaid-svg-HYvOiBBOratt0fii .node .label text,#mermaid-svg-HYvOiBBOratt0fii .image-shape .label,#mermaid-svg-HYvOiBBOratt0fii .icon-shape .label{text-anchor:middle;}#mermaid-svg-HYvOiBBOratt0fii .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HYvOiBBOratt0fii .rough-node .label,#mermaid-svg-HYvOiBBOratt0fii .node .label,#mermaid-svg-HYvOiBBOratt0fii .image-shape .label,#mermaid-svg-HYvOiBBOratt0fii .icon-shape .label{text-align:center;}#mermaid-svg-HYvOiBBOratt0fii .node.clickable{cursor:pointer;}#mermaid-svg-HYvOiBBOratt0fii .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HYvOiBBOratt0fii .arrowheadPath{fill:#333333;}#mermaid-svg-HYvOiBBOratt0fii .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HYvOiBBOratt0fii .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HYvOiBBOratt0fii .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HYvOiBBOratt0fii .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HYvOiBBOratt0fii .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HYvOiBBOratt0fii .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HYvOiBBOratt0fii .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HYvOiBBOratt0fii .cluster text{fill:#333;}#mermaid-svg-HYvOiBBOratt0fii .cluster span{color:#333;}#mermaid-svg-HYvOiBBOratt0fii 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-HYvOiBBOratt0fii .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HYvOiBBOratt0fii rect.text{fill:none;stroke-width:0;}#mermaid-svg-HYvOiBBOratt0fii .icon-shape,#mermaid-svg-HYvOiBBOratt0fii .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HYvOiBBOratt0fii .icon-shape p,#mermaid-svg-HYvOiBBOratt0fii .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HYvOiBBOratt0fii .icon-shape .label rect,#mermaid-svg-HYvOiBBOratt0fii .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HYvOiBBOratt0fii .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HYvOiBBOratt0fii .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HYvOiBBOratt0fii :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户问题
向量检索

top-50
BM25 检索

top-50
RRF 融合
top-20 候选
Re-rank
top-5 最终

6.3 RRF(Reciprocal Rank Fusion)

最简单也最有效的融合算法

python 复制代码
def rrf_fusion(results_lists, k=60):
    """
    results_lists: [[doc_id_1, doc_id_2, ...], ...]
    每个 list 是某路召回的 top-N
    """
    scores = defaultdict(float)
    for results in results_lists:
        for rank, doc_id in enumerate(results, start=1):
            scores[doc_id] += 1.0 / (k + rank)
    sorted_docs = sorted(scores.items(), key=lambda x: -x[1])
    return [doc_id for doc_id, _ in sorted_docs]

优点

  • 不需要归一化两路 score(向量 cosine 和 BM25 score 量级不同);
  • 简单 + 鲁棒;
  • 默认 k=60 已经很好。

6.4 加权融合

python 复制代码
def weighted_fusion(vec_results, bm25_results, alpha=0.7):
    """alpha = 向量权重, 1-alpha = BM25 权重"""
    scores = defaultdict(float)
    for doc_id, score in vec_results:
        scores[doc_id] += alpha * normalize(score)
    for doc_id, score in bm25_results:
        scores[doc_id] += (1 - alpha) * normalize(score)
    return sorted(scores.items(), key=lambda x: -x[1])

调优:alpha = 0.5-0.8 是甜区,根据场景实测。

6.5 Hybrid 的真实收益

场景 纯向量 纯 BM25 Hybrid(RRF)
FAQ(短问句) 0.83 0.78 0.91
长文档 QA 0.86 0.62 0.89
代码搜索 0.78 0.85(关键词命中率高) 0.90
多语言 0.81 0.45(BM25 不支持跨语言) 0.83(向量主导)

工程结论 :除多语言场景外,Hybrid 几乎在所有场景都比单路好------应该是默认选项


7. Re-rank:精排提升 top-N 质量

7.1 为什么需要 Re-rank

Pipeline

  • 召回(ANN):从 33M chunks 召回 top-50 → 准确率不高但快;
  • Re-rank(cross-encoder):对 top-50 精排到 top-5 → 准确率高但慢。

为什么 ANN 不能精排

  • ANN 用 bi-encoder(query 和 doc 各自 embed)------ 没有 query-doc 交互;
  • Cross-encoder 让 query 和 doc 拼起来过 BERT 类模型------能捕捉细粒度匹配。

渲染错误: Mermaid 渲染失败: Parse error on line 9: ...ery] --> Concat\[CLS query SEP doc] -----------------------^ Expecting 'SUBROUTINEEND', 'TAGEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'SQE'

7.2 主流 Re-rank 模型

模型 类别 备注
Cohere Rerank v3 API 高质量,易接入,$2/1M tokens
BGE-Reranker-v2-m3 开源 中英文都强
Jina Reranker v2 开源 / API 长文档强
mxbai-rerank-large 开源 性能与 Cohere 相当

7.3 Re-rank 工作流

python 复制代码
def search_with_rerank(query):
    # 1. 召回 top-50
    vec_results = vector_db.search(embed(query), top_k=50)

    # 2. Re-rank top-50 → top-5
    docs = [r.content for r in vec_results]
    rerank_results = cohere.rerank(
        query=query,
        documents=docs,
        top_n=5,
        model="rerank-v3",
    )

    # 3. 返回精排后的 top-5
    return [vec_results[r.index] for r in rerank_results]

7.4 Re-rank 的真实收益

实测(FAQ 系统,1000 query):

阶段 准确率(top-5) 延迟
纯向量 top-50 0.78 5ms
+ RRF Hybrid 0.86 15ms
+ Re-rank 0.93 100ms

工程结论

  • 不在乎延迟 → Re-rank 必上(+15% 准确率);
  • 实时场景(< 100ms 总延迟)→ Re-rank 可选或跳过;
  • 异步 batch 场景 → Re-rank 必上。

7.5 Re-rank 的成本

Cohere Rerank:$2/1M token。

  • 单 query:50 docs × 500 token = 25k token = 0.05 / 1k queries → **50 / 1M queries**。
  • 比 GPT-4 调用便宜,但比向量召回贵一个数量级。

自部署开源 reranker(BGE-Reranker-v2-m3):

  • 单 GPU(A100)QPS ≈ 100;
  • 月成本:$2000(A100 instance)。
  • 多于 100 万 query/月时自部署划算

8. RAG 的"高阶模式"

8.1 多步检索(Multi-hop / Iterative RAG)

场景:用户问题需要多份文档拼接答案。

例子

"对比 iPhone 15 和 14 的电池续航差异。"

直接 RAG:召回 5 个 chunk → 可能只召回 iPhone 15 或 14 一个的。

多步 RAG

text 复制代码
Step 1: 召回 "iPhone 15 电池续航" → 拿到 chunk_a
Step 2: 召回 "iPhone 14 电池续航" → 拿到 chunk_b
Step 3: 给 LLM 同时塞 chunk_a + chunk_b → 对比

实现:LLM 先分解子问题 → 各自检索 → 汇总。

python 复制代码
def multi_hop_rag(query):
    # 1. 分解
    sub_queries = llm(f"分解以下问题为子问题:{query}")
    # 2. 各自检索
    contexts = []
    for sub_q in sub_queries:
        results = vector_db.search(embed(sub_q), top_k=5)
        contexts.extend(results)
    # 3. 整合
    return llm(f"基于以下信息回答:\n{contexts}\n\n问题:{query}")

8.2 Self-RAG(带评估的 RAG)

核心:让 LLM 自己评判检索到的 chunk 是否相关,不相关就重新检索。

python 复制代码
def self_rag(query, max_iter=3):
    for i in range(max_iter):
        results = vector_db.search(embed(query), top_k=10)
        # LLM 评估
        scores = llm(f"评估以下文档与问题的相关性:\n{results}\n问题:{query}")
        relevant = [r for r, s in zip(results, scores) if s > 0.7]
        if relevant:
            return llm(f"基于:{relevant}\n回答:{query}")
        # 不相关 → 改写 query
        query = llm(f"以下检索不到,请改写查询:{query}")
    return "无法找到相关信息"

8.3 Graph RAG(图检索)

场景:知识库有强关系结构(如组织架构、产品依赖图)。

思路

  • 把文档实体抽取成图节点 + 关系;
  • 检索时除了语义召回,也走图遍历("这个产品的上下游")。

实现:Microsoft GraphRAG(开源)、Neo4j + 向量集成。

8.4 Agentic RAG(带 Agent 决策的 RAG)

结合:让 Agent 决定 "用哪个工具"------RAG 只是工具之一。

python 复制代码
agent_tools = [
    {"name": "rag_search", "description": "搜索内部文档"},
    {"name": "sql_query", "description": "查数据库"},
    {"name": "web_search", "description": "搜索网络最新信息"},
]
# Agent 自己选择用哪个 tool

详见 01-Agent框架-LangChain-LangGraph与AutoGen.md


9. 真实面试现场题(5 道带公司风格标记)

9.1 🟦 字节:抖音搜索的 RAG(短视频内容问答)

(1) 标准答案 :抖音搜索的 RAG 不是文本 RAG------是 多模态 RAG(视频帧 + 字幕 + 评论)。难点:多模态 embedding + 海量内容(10 亿视频)+ 时效性。

(2) 原理 walk

text 复制代码
特点:
  - 10 亿短视频,每天新增 1 亿
  - 单视频 = 视频帧 + 字幕 + 标题 + 评论
  - 用户查询多样(关键词、自然语言、表情)

架构:
  离线索引:
    视频帧抽取(每 5s 一帧)
    OCR + ASR → 字幕文本
    CLIP embedding(图文统一空间)
    分布式向量库(10 亿级)

  在线检索:
    query → CLIP embedding
    向量库召回 top-100 视频
    Re-rank: 综合相关性 + 用户兴趣 + 热度
    返回 top-10

  时效性:
    新视频上传后 5 min 内可被搜索
    增量索引 (Kafka → 索引服务)

(3) 权衡与量化

  • 10 亿视频 × CLIP 1024 维 = 4TB 向量存储(int8 量化 = 1TB);
  • 单 query 延迟 < 200ms(P99);
  • 召回准确率 0.85+。

(4) 落地清单

  • 监控:召回延迟、准确率、新视频可搜延迟;
  • 配置:top_k、re-rank 模型版本、热度权重;
  • 回滚:索引版本回滚(CLIP 升级时)。

(5) 追问

  • 追问 1:CLIP embedding 怎么选?为什么不用 OpenAI?

    CLIP 是图文对训练,图文统一空间------用户上传图也能搜视频,文本也能搜。OpenAI text embedding 是纯文本,不能跨模态。字节自训 ChineseCLIP(中文场景)+ 海外 OpenCLIP------质量与 OpenAI text 在文本上对比,CLIP 在 cross-modal 上 +20%。

  • 追问 2:10 亿向量怎么 ANN?

    分布式 Milvus 或自研:1) 分片 :按 video_id hash 分到 100 个分片;2) 每分片用 IVF (HNSW 在 10 亿级别内存吃不消);3) 多副本 :每分片 2 副本保 HA;4) GPU 加速 :FAISS-GPU 做 IVF 查询。实测:100 节点集群可扛 10 万 QPS。

  • 追问 3:怎么防"召回到不该看的视频"?

    3 层风控:1) 下架视频 hash 黑名单 :违规视频 video_id 入 redis 黑名单,召回后过滤;2) 用户偏好过滤 :用户屏蔽的作者 / 类目;3) 实时审核 :召回 top-100 实时跑分类器(暴力 / 色情 / 政治),有问题不展示。SLA:误召率 < 0.01%。


9.2 🟧 阿里:淘宝商品理解 RAG(千万 SKU 商品搜索)

(1) 标准答案 :淘宝商品 RAG 的核心是 商品文本(标题 / 描述 / 评论)的 embedding + 属性 filter。难点:千万 SKU + 用户意图理解 + 个性化排序。

(2) 原理 walk

text 复制代码
特点:
  - 1 千万 SKU
  - 每 SKU 富文本:标题 + 描述 + 评论(数百条)+ 属性
  - 用户查询从 "iPhone" 到 "便宜耐用的笔记本"

数据模型:
  SKU embedding = avg(标题 emb, 描述 emb, top10 评论 emb)
  属性结构化(品牌、价格、品类)→ filter
  评分(销量、好评率、店铺等级)→ rank 信号

架构:
  离线:
    SKU 数据 ETL → 文本拼接 → embedding
    属性结构化入 ES filter index

  在线:
    query → embedding
    向量召回 top-200 SKU(按品类分桶)
    属性 filter(价格区间、品牌)
    Re-rank: LTR 模型综合(相关性 + 个性化 + 业务规则)
    返回 top-50

(3) 权衡与量化

  • 1000 万 SKU × 1024 维 = 40GB 向量存储;
  • 单 query 延迟 < 100ms(P99);
  • CTR vs 旧搜索 + 15%。

(4) 落地清单

  • 监控:召回率、CTR、转化率;
  • 配置:品类分桶、LTR 权重、业务规则;
  • 回滚:SKU 增量索引可暂停。

(5) 追问

  • 追问 1:怎么处理"评论数据"对召回的影响?

    评论数据是双刃剑:1) 优势 :评论描述真实用户场景,比卖家描述更准;2) 风险 :差评进入 embedding 可能让"垃圾产品"被召回。解法:1) 只用 top10 好评 + top3 差评(差评说明真实问题);2) 评论按时间衰减(最近 30 天权重 ×2);3) 评论数 < 10 的 SKU 不用评论,只用卖家描述。

  • 追问 2:1000 万 SKU 用 HNSW 还是 IVF?

    HNSW。1 千万 × 1024 维 × 2B(fp16)= 20GB------单机内存装得下,HNSW 召回更高。如果是 1 亿 SKU 才考虑 IVF。调优:M=32,ef_construct=200,ef_search=100,实测召回 0.92+,延迟 5ms。

  • 追问 3:怎么实现"个性化"?

    3 层:1) 召回层个性化 :用户历史购买的品类权重 ×1.5;2) filter 层 :用户偏好品牌优先;3) Re-rank 层 :LTR 模型输入 user_emb + item_emb + 上下文,学排序。冷启动:新用户用全局热度排序,3 次行为后切到个性化。


9.3 🟪 蚂蚁:内部知识库 RAG(金融文档 QA)

(1) 标准答案 :蚂蚁内部知识库的 RAG 必须 高精度 + 强引用追溯------金融场景错答可能引发资损。

(2) 原理 walk

text 复制代码
特点:
  - 几千万份内部文档(产品说明、合规手册、风控规则)
  - 强引用追溯(必须能定位到具体段落)
  - 极高精度要求(合规失误代价大)

数据模型:
  Doc 结构化:
    doc_id, title, source, version,
    review_status, classification (公开/内部/敏感)
  Chunk 结构化:
    chunk_id, doc_id, page, paragraph_id,
    embedding, text

权限模型:
  user → role → doc_classification
  检索时 metadata filter (classification IN user.allowed)

回答:
  必须给 [doc_id, page, paragraph] 引用
  用户可点击跳转原文档

(3) 权衡与量化

  • 召回准确率 0.95+(金融红线);
  • 单 query 延迟 < 500ms(异步可接受);
  • 100% 必须有引用来源。

(4) 落地清单

  • 监控:召回准确率、引用准确率、用户点击原文档比例;
  • 配置:权限规则、敏感词过滤;
  • 回滚:模型版本(Chinese-BGE 升级时)。

(5) 追问

  • 追问 1:怎么保证"引用准确"?

    3 层保证:1) chunk 携带 metadata (doc_id, page, paragraph_id);2) LLM 生成时 prompt 强制引用格式 ("必须用 doc_id, page 标注");3) 后处理校验 :引用的 page 必须真实存在该 chunk,否则报错。实测:错引用率 < 0.5%。

  • 追问 2:合规 / 敏感文档怎么管?

    4 层防护:1) classification 字段 :每文档标级(公开/内部/敏感/绝密);2) 检索 filter :用户权限决定可见范围;3) 检索日志 :所有查询入审计日志;4) 离线 review :敏感文档每月审核(标签更新、过期处理)。架构上:绝密文档单独向量库,物理隔离。

  • 追问 3:模型版本升级怎么平滑切换?

    双索引并存:1) 建新索引 :新 embedding 模型重新跑全量 chunk → 新向量库;2) 灰度切换 :10% 流量到新版 → 100%;3) 指标对比 :召回 / 用户满意度;4) 回滚 :旧索引保留 30 天。成本:双存储 30 天 = 临时 +100% 存储成本,可接受。


9.4 🔵 Google: How would you build a RAG system for Bard / Gemini?

(1) Standard Answer : A production RAG for a search-integrated LLM (Bard) requires freshness (real-time web), scale (trillions of pages), and multi-source fusion (search + structured knowledge graph + user history).

(2) Walk-through:

text 复制代码
Architecture:
  Online retrieval (per query):
    Layer 1: Google Search API (top-50 web results)
    Layer 2: Knowledge Graph lookup (entities, structured)
    Layer 3: User context (search history, location)
  
  Embedding:
    Use multilingual embedding (mBert / mT5)
    Cache common queries (10-100k QPS hit rate)
  
  Re-rank:
    Cross-encoder for top-50 → top-5
    Combine with freshness + authority score
  
  Generation:
    Stuff top-5 snippets into Gemini context
    Generate with citation
    Verifier: cross-check citations exist in retrieved docs

(3) Trade-offs:

  • Freshness vs accuracy: real-time but Google Search results may not be high-quality;
  • Latency: total < 3 sec (search 1s + rerank 200ms + LLM 1.5s);
  • Cost: high, but offset by ad revenue.

(4) Operations:

  • Metrics: citation accuracy, freshness lag, user click-through;
  • Config: which sources to combine, weighting;
  • Rollback: disable specific sources (e.g., low-quality blogs).

(5) Follow-ups:

  • Q1: How do you handle conflicting information across sources?

    Multi-source strategy: 1) Source authority weighting : Wikipedia > random blog; 2) Recency : more recent sources weighted higher; 3) Quorum : if 5/10 sources agree, use that; minority opinions flagged; 4) Surface disagreements : when sources contradict, LLM should say "Sources disagree: X says A, Y says B". Real impact: reduces "confidently wrong" responses by 40%.

  • Q2: How do you keep the knowledge graph fresh?

    KG update pipeline: 1) Stream from Wikipedia / Wikidata changes : hourly delta; 2) Web crawl new entities : process via NER + entity linking; 3) Manual curation for high-stakes entities (politicians, celebrities); 4) Versioning : every KG snapshot is timestamped, so "answer as of today" is reproducible. Lag target: < 24 hours for non-trending, < 1 hour for trending.

  • Q3: How do you handle multilingual queries?

    Cross-lingual approach: 1) mBert / LaBSE embeddings : same semantic space across 100+ languages; 2) Query translation as fallback : if cross-lingual recall is weak, translate query to top 3 languages and search each; 3) Source-aware : prefer same-language sources but include other-language if highly relevant; 4) Citation in original + translated : show both. Result: 80%+ accuracy on cross-lingual queries.


9.5 🟢 AWS: Building a customer support RAG on Bedrock

(1) Standard Answer : Use Amazon Bedrock + Amazon OpenSearch + Lambda. Bedrock for embedding + LLM, OpenSearch for vector + keyword hybrid, Lambda for orchestration.

(2) Walk-through:

text 复制代码
Architecture:
  Ingestion Pipeline (Lambda + Step Functions):
    S3 → Trigger Lambda → Chunk + Embed (Bedrock) → OpenSearch
  
  Query Pipeline (API Gateway + Lambda):
    User query → Lambda
    Lambda → Bedrock (embed)
    Lambda → OpenSearch (hybrid search: vector + BM25)
    Lambda → Bedrock Rerank
    Lambda → Bedrock LLM (with context)
    Return answer + citations
  
  Caching (ElastiCache):
    Common queries cached for 1 hour
    Hit rate: 30-40% (FAQ-heavy)

(3) Trade-offs:

  • Bedrock vs self-hosted: Bedrock is convenient but ~2x cost;
  • OpenSearch vs Pinecone: OpenSearch has hybrid (vector + BM25) natively;
  • Lambda cold start: use Provisioned Concurrency for query pipeline.

(4) Operations:

  • Metrics: CloudWatch (latency, errors, costs);
  • Config: chunk_size, top_k, rerank model;
  • Rollback: Lambda version pinning + traffic shift.

(5) Follow-ups:

  • Q1: Bedrock vs self-hosted Claude / Llama, which to choose?

    Decision matrix: 1) Bedrock : convenience, instant scale, no infra; cost = 2x self-hosted at scale; 2) Self-hosted on SageMaker : cheap at high volume, but needs ML ops; 3) Bedrock Custom Model : middle ground, train on Bedrock infra. Rule: < 10M tokens/month → Bedrock; > 100M tokens/month → SageMaker; in-between → measure.

  • Q2: How do you handle hot documents (FAQ that 30% of users ask)?

    Cache strategy: 1) Query-level cache (ElastiCache) : hash of normalized query → cached answer, TTL 1h; 2) Embedding cache : same query → same embedding, reuse; 3) Pre-compute answers : for top 1000 FAQ, pre-generate answers offline, hit memcache first; 4) Cache invalidation: when source doc updates → bust cache for related FAQs.

  • Q3: How do you protect against PII leakage in RAG?

    Layered defense: 1) Pre-ingestion : scan docs with Amazon Macie / Comprehend PII detector, redact before indexing; 2) Query-time : log all queries, alert on PII patterns; 3) Output filtering : post-process LLM output, mask any detected PII; 4) Access control : per-user knowledge base, RBAC at retrieval level. Compliance: GDPR / HIPAA depending on data; document data lineage.


10. 真实事故复盘(电商交易场景)

10.1 案例:商品搜索 RAG 召回率从 0.91 跌到 0.45

S(Situation)
  • 业务:电商商品 RAG 搜索,1000 万 SKU;
  • 基线:召回准确率(top-5)= 0.91;
  • 架构:OpenAI text-embedding-3-small + Qdrant + Cohere Rerank。
T(Trigger)
  • 2025-10-20 09:00:业务方反馈"用户说搜不到商品,CTR 暴跌 40%";
  • 09:30:on-call 拉数据 → 召回 top-5 准确率 0.45(从 0.91);
  • 10:00:紧急排查。
A(Approach)

第 1 步:复现

python 复制代码
test_queries = ["iPhone 15 Pro 256GB", "无线耳机降噪", ...]
for q in test_queries:
    results = rag.search(q)
    print(f"{q}: {[r.title for r in results[:5]]}")
# 召回的几乎都是"语义相似但完全不对"的商品

第 2 步:定位

python 复制代码
# 检查向量库版本
print(vector_db.metadata())
# embedding_model: "text-embedding-3-small@2024-01"
# index_built_at: "2025-04-10"
# total_vectors: 9_823_412

# 检查 query embedding 用的版本
print(embed_client.model)
# "text-embedding-3-small"  ← 当前版本

# OpenAI API 调用日志
# 2025-10-20 02:30: model="text-embedding-3-small" updated
# 新版本和老版本向量空间发生轻微偏移!

根因

  • OpenAI 在 2025-10-20 02:30 静默更新了 text-embedding-3-small 模型;
  • 我们的索引(doc embedding)是 2025-04 的版本;
  • query embedding 用的是新版本------向量空间不一致
  • 召回准确率从 0.91 跌到 0.45。
R(Resolution)

止血(30 分钟)

  • 立即切回 OpenAI 旧版本(用 stable version SDK);
  • 同时全量重建索引(1000 万 SKU × 0.02/1M = 200)。

根因修复(24 小时)

  1. embedding 模型 freeze :用 text-embedding-3-small-001(带版本号);
  2. CI 检测:每天定时跑一组 ground truth 检查 embedding 一致性;
  3. 应急 SOP:embedding API 变更 → 切回旧版本 → 异步重建索引;
  4. 架构改进:考虑切自部署 embedding(BGE-M3)避免 vendor lock-in。
M(Metrics)
指标 平时 事故时 修复后
召回准确率(top-5) 0.91 0.45 0.92
CTR 12% 7% 12%
转化率 3.2% 1.8% 3.3%
业务损失 0 ~50万元(4 小时低 CTR) 0
P(Postmortem)

短期

  • 锁定 embedding 模型版本(pinned version)
  • CI 添加 embedding consistency check
  • 应急 SOP 文档

中期

  • 评估自部署 embedding(BGE-M3)
  • 双模型并行 A/B(万一 vendor 改了能切)
  • 召回质量大盘(实时监控)

长期

  • 多 vendor 抽象层(统一 embedding interface)
  • 索引版本管理(每个 embedding 版本一份索引)

核心教训

"Vendor 模型版本是不可控的 。OpenAI 不通知就改了 text-embedding-3-small,造成召回崩溃。生产系统必须:1) pin 版本;2) 自动一致性检测;3) 关键场景考虑自部署。这是 RAG 系统的红线。"


11. 90 秒口述脚本(背诵版)

"RAG = 检索增强生成,把封闭考试变成开卷。6 步流水线 :离线索引(chunking + embedding + 入向量库)+ 在线检索(query embedding + ANN 召回 + re-rank + LLM 生成)。Chunking 是灵魂参数,500 token + 10% overlap 是甜区,复杂文档用递归切或语义切。Embedding 必须 pin 版本(OpenAI 静默更新害死人),中文用 BGE-M3。向量库 :HNSW 是默认,超大规模才用 IVF,pgvector 复用现有 PG。Hybrid Search (BM25 + 向量 + RRF 融合)几乎是必选,准确率 +5-10%。Re-rank (Cohere / BGE)能再 +7-10%。最大坑 = embedding 版本漂移和 chunk 边界割裂。生产红线:1) pin embedding 版本;2) 引用来源;3) metadata filter 权限;4) 双索引升级。"


§12 向量数据库选型深度(Staff / Architect)

定位 :§5 讲「够用选型」;本节给 Architect 白板级 对比------七库横评、ANN 数学、Embedding/Rerank 矩阵、三层架构、Spring AI 多库编排、10M SKU 容量账本、2 道 Staff 题。

12.1 七库横评:容量 · 延迟 · QPS · 持久化 · 过滤 · 运维成本

维度 Milvus Qdrant Weaviate pgvector Pinecone LanceDB Redis Vector
容量上限 十亿级(分布式) 亿级(集群) 亿级 千万级(单 PG 实例) 亿级(托管) 十亿级(列存 + 磁盘 ANN) <100 万(内存为主)
P99 延迟 5--15ms(HNSW,集群) 1--5ms(Rust,单机优) 5--20ms 10--50ms(与 PG 负载共享) 3--10ms(托管优化) 10--30ms(DiskANN 偏吞吐) <2ms(纯内存)
读 QPS 5k--20k/节点 10k--50k/节点 3k--10k 500--3k(受 PG 连接与 SQL 影响) 按套餐 10k+ 高吞吐扫描场景 50k+(小集合)
持久化 对象存储 + WAL 本地盘 + 快照 本地 / 云 PG WAL + 备份链 全托管 列存文件(S3 友好) AOF/RDB(易丢热数据需 RDB)
Metadata Filter 强(标量 + JSON) (payload 索引) 强(GraphQL) SQL WHERE(最灵活) 强(namespace + metadata) 中(列谓词) Tag / Hash 字段
运维成本 高(K8s、etcd、MinIO) 中(单二进制友好) 中高 (复用 DBA) 最低(SaaS) 低(嵌入式 / 无服务) 最低(已有 Redis)

选型口诀

  • <100 万、会话/热缓存 → Redis Vector;
  • <1000 万、要 SQL 联表/事务 → pgvector;
  • >1000 万 UGC、要高 QPS ANN → Milvus / Qdrant;
  • 原型 / 离线分析 / 湖仓一体 → LanceDB;
  • 零运维、预算足 → Pinecone;
  • 知识图谱 + 向量一体 → Weaviate。

12.2 ANN 索引数学:HNSW · IVF-PQ · DiskANN

HNSW(默认首选,内存内)

图结构:多层小世界图;Layer (l) 节点数约 (N / 2^l)。查询从顶层贪心下钻到 L0 精排。

关键参数

参数 含义 调大效果 推荐起点
M 每节点双向边数 召回↑、内存↑、建索引慢 16(高精度 32--64)
efConstruction 建索引时候选池大小 图质量↑、建索引时间↑ 200
efSearch 查询时候选池大小 召回↑、延迟↑ 64--128(生产);压测可到 256

何时用 :向量 能放进内存 、规模 <1 亿 、要 毫秒级 P99 ------电商商品向量、FAQ、客服知识库 默认 HNSW

text 复制代码
召回率 ≈ f(efSearch / M)  (经验:efSearch ≥ 2×topK 且 ≥ 64)
内存 ≈ N × dim × bytes + N × M × pointer_overhead
IVF-PQ(倒排 + 乘积量化)

两阶段

  1. IVF :K-means 聚成 nlist 个桶;查询只 probe nprobe 个桶 → 复杂度从 O(N) 降到 O(N/nlist × nprobe)。
  2. PQ :把 dim 切成 m 个子空间,每子空间 256 个质心 → 向量压成 m 字节码本,距离用 查表 + 累加 近似。
参数 含义 调大效果
nlist 桶数 通常 √N ~ N/1000
nprobe 查询探桶数 召回↑、延迟↑
m(PQ) 子量化器数 必须整除 dim;m 大压缩率低、精度高
nbits 每子空间码本位数 通常 8

何时用>1 亿向量 、内存放不下全量 float、可接受 召回 0.85--0.92 ;常与 OPQ 预旋转 联用。Milvus IVF_PQ、Faiss IndexIVFPQ 是标准组合。

DiskANN(磁盘友好图索引)

思路 :图索引主体放 SSD,热路径缓存;适合 十亿级、单机/少节点、内存只够 cache 一部分图。

何时用 :LanceDB、Azure DiskANN、Milvus DiskANN 模式;离线批检索、召回优先于极致延迟 的分析型 RAG;在线客服 一般不首选(P99 难压到 5ms 内)。

选型对照(Architect 白板)
场景 索引 理由
在线客服 / 商品搜索 <50M HNSW 延迟与召回最佳平衡
全量 UGC 评论 5 亿+ IVF-PQDiskANN 内存/成本
开发机原型 <100k HNSW 或 暴力 Flat 简单
多模态 2k 维 HNSW + 降维 或 PQ 否则内存爆炸

#mermaid-svg-eFg48fPU1myUWGFj{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-eFg48fPU1myUWGFj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eFg48fPU1myUWGFj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eFg48fPU1myUWGFj .error-icon{fill:#552222;}#mermaid-svg-eFg48fPU1myUWGFj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eFg48fPU1myUWGFj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eFg48fPU1myUWGFj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eFg48fPU1myUWGFj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eFg48fPU1myUWGFj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eFg48fPU1myUWGFj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eFg48fPU1myUWGFj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eFg48fPU1myUWGFj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eFg48fPU1myUWGFj .marker.cross{stroke:#333333;}#mermaid-svg-eFg48fPU1myUWGFj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eFg48fPU1myUWGFj p{margin:0;}#mermaid-svg-eFg48fPU1myUWGFj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-eFg48fPU1myUWGFj .cluster-label text{fill:#333;}#mermaid-svg-eFg48fPU1myUWGFj .cluster-label span{color:#333;}#mermaid-svg-eFg48fPU1myUWGFj .cluster-label span p{background-color:transparent;}#mermaid-svg-eFg48fPU1myUWGFj .label text,#mermaid-svg-eFg48fPU1myUWGFj span{fill:#333;color:#333;}#mermaid-svg-eFg48fPU1myUWGFj .node rect,#mermaid-svg-eFg48fPU1myUWGFj .node circle,#mermaid-svg-eFg48fPU1myUWGFj .node ellipse,#mermaid-svg-eFg48fPU1myUWGFj .node polygon,#mermaid-svg-eFg48fPU1myUWGFj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eFg48fPU1myUWGFj .rough-node .label text,#mermaid-svg-eFg48fPU1myUWGFj .node .label text,#mermaid-svg-eFg48fPU1myUWGFj .image-shape .label,#mermaid-svg-eFg48fPU1myUWGFj .icon-shape .label{text-anchor:middle;}#mermaid-svg-eFg48fPU1myUWGFj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eFg48fPU1myUWGFj .rough-node .label,#mermaid-svg-eFg48fPU1myUWGFj .node .label,#mermaid-svg-eFg48fPU1myUWGFj .image-shape .label,#mermaid-svg-eFg48fPU1myUWGFj .icon-shape .label{text-align:center;}#mermaid-svg-eFg48fPU1myUWGFj .node.clickable{cursor:pointer;}#mermaid-svg-eFg48fPU1myUWGFj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eFg48fPU1myUWGFj .arrowheadPath{fill:#333333;}#mermaid-svg-eFg48fPU1myUWGFj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eFg48fPU1myUWGFj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eFg48fPU1myUWGFj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eFg48fPU1myUWGFj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eFg48fPU1myUWGFj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eFg48fPU1myUWGFj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eFg48fPU1myUWGFj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eFg48fPU1myUWGFj .cluster text{fill:#333;}#mermaid-svg-eFg48fPU1myUWGFj .cluster span{color:#333;}#mermaid-svg-eFg48fPU1myUWGFj 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-eFg48fPU1myUWGFj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eFg48fPU1myUWGFj rect.text{fill:none;stroke-width:0;}#mermaid-svg-eFg48fPU1myUWGFj .icon-shape,#mermaid-svg-eFg48fPU1myUWGFj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eFg48fPU1myUWGFj .icon-shape p,#mermaid-svg-eFg48fPU1myUWGFj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eFg48fPU1myUWGFj .icon-shape .label rect,#mermaid-svg-eFg48fPU1myUWGFj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eFg48fPU1myUWGFj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eFg48fPU1myUWGFj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eFg48fPU1myUWGFj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

<10ms 在线
离线/分析
向量规模 N
全量能进内存?
HNSW

调 M / efSearch
延迟 SLA
IVF-PQ

nprobe 调优
DiskANN / Lance

12.3 Embedding 模型矩阵(2026 生产向)

模型 维度 多语言 相对成本 典型场景
BGE-M3 1024(dense)+ sparse 中英日韩等 自部署 GPU 低 国内电商、混合检索(dense+sparse)
E5-large-v2 1024 英为主,多语可用 自部署低 开源栈、query/doc 双塔
text-embedding-3-small 1536(可降维) 多语 API $0.02/1M tok 快速上线、需 pin 版本
text-embedding-3-large 3072 多语 API 更高 高精度检索、小库
Cohere embed-v3 1024 多语强 API 中等 跨境、多语客服
Voyage-3 1024 多语 API 中等 长文档、代码检索

工程红线(与 §10 事故呼应):

  1. 索引与查询必须用同一模型 + 同一版本
  2. 换模型 = 全量重建 + 双写灰度;
  3. 中文场景 BGE-M3 / Cohere 通常优于纯英文 E5;
  4. text-embedding-3-* 支持 dimensions 截断------索引与查询截断长度必须一致。

12.4 Reranker 层:从 ANN 粗召回到精排

类型 代表 延迟 效果 何时上
Cross-Encoder bge-reranker-v2-m3 50--200ms / 20 对 +7--15% nDCG 候选 ≤50,质量敏感
API Rerank Cohere rerank-v3 按调用计费 多语强、免运维 跨境、无 GPU
轻量 CE ms-marco-MiniLM 更快 略低于 BGE 延迟预算紧

标准栈 :ANN top-50 → Rerank top-20 → top-5 进 LLM ;Rerank 输入总长建议 <8k tokens(截断 chunk 摘要而非全文)。

python 复制代码
# 伪代码:两阶段检索
candidates = milvus.search(q_vec, top_k=50, filter={"category": "phone"})
pairs = [(query, c.text) for c in candidates]
scores = reranker.predict(pairs)  # bge-reranker-v2-m3
final = sorted(zip(candidates, scores), key=lambda x: -x[1])[:5]

12.5 三层向量架构(电商典型)

层级 存储 数据 规模 SLA
L1 Redis Vector 会话上下文、热 SKU、购物车相似 <100k P99 <2ms
L2 pgvector 商品目录、FAQ、运营规则(要 SQL 联表) <10M P99 <30ms
L3 Milvus UGC 评价、问答、图片多模态 >10M P99 <15ms,水平扩展

#mermaid-svg-0xbEU7PHIQeleFwI{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-0xbEU7PHIQeleFwI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0xbEU7PHIQeleFwI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0xbEU7PHIQeleFwI .error-icon{fill:#552222;}#mermaid-svg-0xbEU7PHIQeleFwI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0xbEU7PHIQeleFwI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0xbEU7PHIQeleFwI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0xbEU7PHIQeleFwI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0xbEU7PHIQeleFwI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0xbEU7PHIQeleFwI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0xbEU7PHIQeleFwI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0xbEU7PHIQeleFwI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0xbEU7PHIQeleFwI .marker.cross{stroke:#333333;}#mermaid-svg-0xbEU7PHIQeleFwI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0xbEU7PHIQeleFwI p{margin:0;}#mermaid-svg-0xbEU7PHIQeleFwI .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0xbEU7PHIQeleFwI .cluster-label text{fill:#333;}#mermaid-svg-0xbEU7PHIQeleFwI .cluster-label span{color:#333;}#mermaid-svg-0xbEU7PHIQeleFwI .cluster-label span p{background-color:transparent;}#mermaid-svg-0xbEU7PHIQeleFwI .label text,#mermaid-svg-0xbEU7PHIQeleFwI span{fill:#333;color:#333;}#mermaid-svg-0xbEU7PHIQeleFwI .node rect,#mermaid-svg-0xbEU7PHIQeleFwI .node circle,#mermaid-svg-0xbEU7PHIQeleFwI .node ellipse,#mermaid-svg-0xbEU7PHIQeleFwI .node polygon,#mermaid-svg-0xbEU7PHIQeleFwI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0xbEU7PHIQeleFwI .rough-node .label text,#mermaid-svg-0xbEU7PHIQeleFwI .node .label text,#mermaid-svg-0xbEU7PHIQeleFwI .image-shape .label,#mermaid-svg-0xbEU7PHIQeleFwI .icon-shape .label{text-anchor:middle;}#mermaid-svg-0xbEU7PHIQeleFwI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0xbEU7PHIQeleFwI .rough-node .label,#mermaid-svg-0xbEU7PHIQeleFwI .node .label,#mermaid-svg-0xbEU7PHIQeleFwI .image-shape .label,#mermaid-svg-0xbEU7PHIQeleFwI .icon-shape .label{text-align:center;}#mermaid-svg-0xbEU7PHIQeleFwI .node.clickable{cursor:pointer;}#mermaid-svg-0xbEU7PHIQeleFwI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0xbEU7PHIQeleFwI .arrowheadPath{fill:#333333;}#mermaid-svg-0xbEU7PHIQeleFwI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0xbEU7PHIQeleFwI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0xbEU7PHIQeleFwI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0xbEU7PHIQeleFwI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0xbEU7PHIQeleFwI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0xbEU7PHIQeleFwI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0xbEU7PHIQeleFwI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0xbEU7PHIQeleFwI .cluster text{fill:#333;}#mermaid-svg-0xbEU7PHIQeleFwI .cluster span{color:#333;}#mermaid-svg-0xbEU7PHIQeleFwI 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-0xbEU7PHIQeleFwI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0xbEU7PHIQeleFwI rect.text{fill:none;stroke-width:0;}#mermaid-svg-0xbEU7PHIQeleFwI .icon-shape,#mermaid-svg-0xbEU7PHIQeleFwI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0xbEU7PHIQeleFwI .icon-shape p,#mermaid-svg-0xbEU7PHIQeleFwI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0xbEU7PHIQeleFwI .icon-shape .label rect,#mermaid-svg-0xbEU7PHIQeleFwI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0xbEU7PHIQeleFwI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0xbEU7PHIQeleFwI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0xbEU7PHIQeleFwI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户 Query
Router / 意图分类
L1 Redis Vector

会话 & 热缓存
L2 pgvector

目录 RAG + SQL 过滤
L3 Milvus

UGC 海量
RRF 融合
Reranker
LLM 生成 + 引用

路由规则示例

  • intent=session_recall → 只查 L1;
  • intent=product_spec → L2(sku_id metadata 强过滤);
  • intent=review_qa → L3;
  • intent=compare → L2 + L3 并行,RRF 合并。

12.6 Spring AI 多 VectorStore 编排(@Qualifier)

java 复制代码
@Configuration
public class VectorStoreConfig {

    @Bean
    @Qualifier("catalogPgVector")
    public VectorStore catalogPgVector(JdbcTemplate jdbc, EmbeddingModel embedding) {
        return PgVectorStore.builder(jdbc, embedding)
            .dimensions(1024)
            .distanceType(PgVectorStore.PgDistanceType.COSINE_DISTANCE)
            .indexType(PgVectorStore.PgIndexType.HNSW)
            .build();
    }

    @Bean
    @Qualifier("ugcMilvus")
    public VectorStore ugcMilvus(EmbeddingModel embedding) {
        return MilvusVectorStore.builder()
            .embeddingModel(embedding)
            .collectionName("ugc_reviews")
            .databaseName("ecom")
            .build();
    }

    @Bean
    @Qualifier("sessionRedis")
    public VectorStore sessionRedis(RedisConnectionFactory factory, EmbeddingModel embedding) {
        return RedisVectorStore.builder(factory, embedding)
            .indexName("session_ctx")
            .prefix("sess:")
            .initializeSchema(true)
            .build();
    }
}

@Service
public class TieredRagService {

    private final VectorStore catalogPgVector;
    private final VectorStore ugcMilvus;
    private final VectorStore sessionRedis;

    public TieredRagService(
            @Qualifier("catalogPgVector") VectorStore catalogPgVector,
            @Qualifier("ugcMilvus") VectorStore ugcMilvus,
            @Qualifier("sessionRedis") VectorStore sessionRedis) {
        this.catalogPgVector = catalogPgVector;
        this.ugcMilvus = ugcMilvus;
        this.sessionRedis = sessionRedis;
    }

    public List<Document> retrieve(String query, RagRoute route) {
        var request = SearchRequest.builder().query(query).topK(20).build();
        return switch (route) {
            case SESSION -> sessionRedis.similaritySearch(request);
            case CATALOG -> catalogPgVector.similaritySearch(
                SearchRequest.from(request).filterExpression("active == true").build());
            case UGC -> ugcMilvus.similaritySearch(request);
            case HYBRID -> rrfMerge(
                catalogPgVector.similaritySearch(request),
                ugcMilvus.similaritySearch(request));
        };
    }
}

注意 :Spring AI 的 QuestionAnswerAdvisor 默认绑定 单个 VectorStore;三层架构需 自定义 RetrievalAugmentor 或 Service 层路由(见 §13.9)。

12.7 容量估算示例:1000 万 SKU(BGE-M3 · HNSW)

假设

  • 每 SKU 1 个主 chunk(标题+卖点+规格摘要)≈ 400 tokens;
  • Embedding:BGE-M3 dense 1024 维 float32
  • 索引:HNSWM=16efConstruction=200
text 复制代码
向量存储:
  10M × 1024 × 4B ≈ 40 GB(纯向量)
  HNSW 图开销 ≈ 1.2--1.5× → 48--60 GB 内存/working set

原文 chunk(可选存对象存储):
  10M × 400 tok × ~1.5B/tok ≈ 6 GB 文本

QPS 估算(单 Milvus 查询节点 32C128G):
  efSearch=128, topK=20 → 约 3000--5000 QPS
  业务 1000 QPS → 2 副本 + 1 写入节点足够

索引构建:
  10M 向量 batch embed(GPU 8×A10)≈ 2--4 小时
  HNSW build ≈ 1--2 小时(与 efConstruction 正相关)

成本粗算(自建 K8s):
  3 节点 Milvus + MinIO ≈ ¥8k--15k/月
  vs Pinecone pod 级 ≈ $2k--5k/月(随维度与 QPS 跳档)

扩容触发器 :单集合 >50M 向量 或内存 >80% → 分片(按 category_id)或降维 / IVF-PQ。

12.8 Staff 面试题(STAR 简答)

Q1:1000 万商品向量库怎么选 Milvus 还是 pgvector?
STAR 内容
S 某跨境电商平台,1000 万 SKU 语义搜索,峰值 800 QPS,要求 P99<20ms,需按类目+库存 metadata 过滤。
T 在 2 周内确定向量底座并完成灰度。
A 用 pgvector 做 <500 万已上架+有库存 的子集联表 SQL;全量 1000 万放 Milvus 分片category_id partition)。ANN 用 HNSW,efSearch=128。离线用 BGE-M3 建索引,在线 Hybrid(BM25+向量)+ bge-reranker top-50→5。
R 灰度后 nDCG@10 +12%,P99 14ms;pgvector 承担运营后台「SQL+向量」联合查询,Milvus 扛 C 端主流量。
Q2:embedding 模型升级怎么做零停机?
STAR 内容
S text-embedding-3-small 迁到 BGE-M3,避免 §10 类「向量空间漂移」事故。
T 零停机、可回滚。
A 双索引并行:index_v1(旧)+ index_v2(新);查询层 shadow read 对比 nDCG;流量 5%→50%→100% 切 v2;ground truth 集每日跑一致性门禁;旧索引保留 14 天。
R 切换窗口零 P0;召回率 +6%,API 成本降 40%(自部署)。

§13 进阶 RAG 范式(Staff / Architect)

定位 :§8 已介绍 Multi-hop / Self-RAG / GraphRAG 概念;本节补齐 2024--2026 论文级范式、选型决策树、事故叙事与 Spring AI 落地差异。

13.1 GraphRAG(Microsoft)

机制依据

机制

  1. 从文档抽 实体 + 关系 建图;
  2. 社区检测 (Leiden)→ 每个社区 LLM 生成 Community Summary
  3. 查询时:全局问题走 社区摘要检索 ;局部问题走 实体子图 + 原文 chunk

何时用 GraphRAG

适合 不适合
多跳关系(「A 部门谁负责 B 产品的合规」) 简单 FAQ(单价、退货政策)
私有知识库 无清晰标题结构 已有优质 BM25 + 结构化 catalog
需要 全局主题(「这份年报三大风险」) 延迟预算 <2s 的在线客服

成本 :离线索引 LLM 调用量 10--50× 于 flat RAG ;适合 离线报告、Research Assistant,不适合高 QPS C 端。

13.2 HybridRAG:向量 + BM25 + 知识图谱

#mermaid-svg-xjLcsNc2AT9LQTlT{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-xjLcsNc2AT9LQTlT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xjLcsNc2AT9LQTlT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xjLcsNc2AT9LQTlT .error-icon{fill:#552222;}#mermaid-svg-xjLcsNc2AT9LQTlT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xjLcsNc2AT9LQTlT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xjLcsNc2AT9LQTlT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xjLcsNc2AT9LQTlT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xjLcsNc2AT9LQTlT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xjLcsNc2AT9LQTlT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xjLcsNc2AT9LQTlT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xjLcsNc2AT9LQTlT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xjLcsNc2AT9LQTlT .marker.cross{stroke:#333333;}#mermaid-svg-xjLcsNc2AT9LQTlT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xjLcsNc2AT9LQTlT p{margin:0;}#mermaid-svg-xjLcsNc2AT9LQTlT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-xjLcsNc2AT9LQTlT .cluster-label text{fill:#333;}#mermaid-svg-xjLcsNc2AT9LQTlT .cluster-label span{color:#333;}#mermaid-svg-xjLcsNc2AT9LQTlT .cluster-label span p{background-color:transparent;}#mermaid-svg-xjLcsNc2AT9LQTlT .label text,#mermaid-svg-xjLcsNc2AT9LQTlT span{fill:#333;color:#333;}#mermaid-svg-xjLcsNc2AT9LQTlT .node rect,#mermaid-svg-xjLcsNc2AT9LQTlT .node circle,#mermaid-svg-xjLcsNc2AT9LQTlT .node ellipse,#mermaid-svg-xjLcsNc2AT9LQTlT .node polygon,#mermaid-svg-xjLcsNc2AT9LQTlT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xjLcsNc2AT9LQTlT .rough-node .label text,#mermaid-svg-xjLcsNc2AT9LQTlT .node .label text,#mermaid-svg-xjLcsNc2AT9LQTlT .image-shape .label,#mermaid-svg-xjLcsNc2AT9LQTlT .icon-shape .label{text-anchor:middle;}#mermaid-svg-xjLcsNc2AT9LQTlT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xjLcsNc2AT9LQTlT .rough-node .label,#mermaid-svg-xjLcsNc2AT9LQTlT .node .label,#mermaid-svg-xjLcsNc2AT9LQTlT .image-shape .label,#mermaid-svg-xjLcsNc2AT9LQTlT .icon-shape .label{text-align:center;}#mermaid-svg-xjLcsNc2AT9LQTlT .node.clickable{cursor:pointer;}#mermaid-svg-xjLcsNc2AT9LQTlT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xjLcsNc2AT9LQTlT .arrowheadPath{fill:#333333;}#mermaid-svg-xjLcsNc2AT9LQTlT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xjLcsNc2AT9LQTlT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xjLcsNc2AT9LQTlT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xjLcsNc2AT9LQTlT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xjLcsNc2AT9LQTlT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xjLcsNc2AT9LQTlT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xjLcsNc2AT9LQTlT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xjLcsNc2AT9LQTlT .cluster text{fill:#333;}#mermaid-svg-xjLcsNc2AT9LQTlT .cluster span{color:#333;}#mermaid-svg-xjLcsNc2AT9LQTlT 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-xjLcsNc2AT9LQTlT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xjLcsNc2AT9LQTlT rect.text{fill:none;stroke-width:0;}#mermaid-svg-xjLcsNc2AT9LQTlT .icon-shape,#mermaid-svg-xjLcsNc2AT9LQTlT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xjLcsNc2AT9LQTlT .icon-shape p,#mermaid-svg-xjLcsNc2AT9LQTlT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xjLcsNc2AT9LQTlT .icon-shape .label rect,#mermaid-svg-xjLcsNc2AT9LQTlT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xjLcsNc2AT9LQTlT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xjLcsNc2AT9LQTlT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xjLcsNc2AT9LQTlT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Query
Query Rewrite
Dense ANN
BM25 / ES
Graph 遍历

1-2 hop
RRF / 加权融合
Reranker
LLM

工程要点 :三路 并行 ;图谱路只取 高置信边 (避免噪声爆炸);融合后 统一 dedupe(同 doc_id 只保留最高分)。

13.3 Self-RAG:retrieve / generate / critique tokens

机制依据L3 Self-RAG (arXiv:2310.11511) · 官方实现见作者仓库(论文为准定义 critique token 语义)

论文要点(Self-RAG, 2023):模型生成特殊 token:

  • [Retrieval]:是否需要检索;
  • [IsRel]:chunk 是否相关;
  • [IsSup]:生成是否被 chunk 支持;
  • [IsUse]:回答是否有用。

落地简化 :不必微调专用模型------用 强模型 + JSON 结构化 critique 循环 1--2 次(见 §8.2)。适合 高价值低 QPS(医疗、法务),不适合 1000 QPS 客服。

13.4 CRAG:Corrective RAG + Web Fallback

流程

  1. 检索 top-K;
  2. Retriever Evaluator 打分(相关度分布、熵);
  3. 分三档:Correct → 直接生成;Ambiguous → 查询改写再检;IncorrectWeb 搜索 / 更大模型 兜底。

价值 :减少「硬答幻觉」;风险 :Web 引入不可控来源 → 必须 引用过滤 + 域名白名单

13.5 RAPTOR:层次树检索

建树:递归聚类 chunk → 父节点 = 子簇摘要 → 形成树。

查询 :自上而下(先匹配高层摘要,再下钻叶子)或 折叠树 一次 embedding 多粒度。

适合100+ 页手册、法规、白皮书 ;不适合 已切得很好的 500-token chunk 商品库

13.6 LongRAG:长文档处理

策略 做法 代价
父文档检索 小块检索,返回 整节/整页 上下文 token 多
滑动窗口 + 合并 命中相邻 chunk 合并 实现简单
LongContext LLM 检索 top-3 整章塞进 128k 贵、慢
RAPTOR / 层次摘要 先粗后细 离线成本高

13.7 Agentic RAG

定义 :由 Agent 规划 何时检索、检索什么、是否多跳、是否调用 SQL/Web------RAG 降级为 Tool 之一。

text 复制代码
User → Agent Planner
         ├─ tool: vector_search(collection, query, filter)
         ├─ tool: sql_inventory(sku)
         ├─ tool: web_search (CRAG fallback)
         └─ synthesize with citations

固定流水线 RAG 对比:灵活度↑、延迟与成本方差↑、可观测性要求更高(每步 tool trace)。

13.8 HyDE · Query Rewrite · RRF(工程三板斧)

技术 作用 典型提升
HyDE LLM 先生成 假想答案文档 再 embed 检索 FAQ/口语 +5--10% 召回
Query Rewrite 多查询扩展 / 消歧 / 翻译 长尾 +8--15%
RRF 多路排名融合,无需分数校准 Hybrid 标配
python 复制代码
def rrf(rank_lists, k=60):
    scores = {}
    for ranks in rank_lists:
        for rank, doc_id in enumerate(ranks, start=1):
            scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank)
    return sorted(scores, key=scores.get, reverse=True)

13.9 范式选型决策树

#mermaid-svg-Cll0DV1Zd5bYLqXn{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-Cll0DV1Zd5bYLqXn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Cll0DV1Zd5bYLqXn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Cll0DV1Zd5bYLqXn .error-icon{fill:#552222;}#mermaid-svg-Cll0DV1Zd5bYLqXn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Cll0DV1Zd5bYLqXn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Cll0DV1Zd5bYLqXn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Cll0DV1Zd5bYLqXn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Cll0DV1Zd5bYLqXn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Cll0DV1Zd5bYLqXn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Cll0DV1Zd5bYLqXn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Cll0DV1Zd5bYLqXn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Cll0DV1Zd5bYLqXn .marker.cross{stroke:#333333;}#mermaid-svg-Cll0DV1Zd5bYLqXn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Cll0DV1Zd5bYLqXn p{margin:0;}#mermaid-svg-Cll0DV1Zd5bYLqXn .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Cll0DV1Zd5bYLqXn .cluster-label text{fill:#333;}#mermaid-svg-Cll0DV1Zd5bYLqXn .cluster-label span{color:#333;}#mermaid-svg-Cll0DV1Zd5bYLqXn .cluster-label span p{background-color:transparent;}#mermaid-svg-Cll0DV1Zd5bYLqXn .label text,#mermaid-svg-Cll0DV1Zd5bYLqXn span{fill:#333;color:#333;}#mermaid-svg-Cll0DV1Zd5bYLqXn .node rect,#mermaid-svg-Cll0DV1Zd5bYLqXn .node circle,#mermaid-svg-Cll0DV1Zd5bYLqXn .node ellipse,#mermaid-svg-Cll0DV1Zd5bYLqXn .node polygon,#mermaid-svg-Cll0DV1Zd5bYLqXn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Cll0DV1Zd5bYLqXn .rough-node .label text,#mermaid-svg-Cll0DV1Zd5bYLqXn .node .label text,#mermaid-svg-Cll0DV1Zd5bYLqXn .image-shape .label,#mermaid-svg-Cll0DV1Zd5bYLqXn .icon-shape .label{text-anchor:middle;}#mermaid-svg-Cll0DV1Zd5bYLqXn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Cll0DV1Zd5bYLqXn .rough-node .label,#mermaid-svg-Cll0DV1Zd5bYLqXn .node .label,#mermaid-svg-Cll0DV1Zd5bYLqXn .image-shape .label,#mermaid-svg-Cll0DV1Zd5bYLqXn .icon-shape .label{text-align:center;}#mermaid-svg-Cll0DV1Zd5bYLqXn .node.clickable{cursor:pointer;}#mermaid-svg-Cll0DV1Zd5bYLqXn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Cll0DV1Zd5bYLqXn .arrowheadPath{fill:#333333;}#mermaid-svg-Cll0DV1Zd5bYLqXn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Cll0DV1Zd5bYLqXn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Cll0DV1Zd5bYLqXn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Cll0DV1Zd5bYLqXn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Cll0DV1Zd5bYLqXn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Cll0DV1Zd5bYLqXn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Cll0DV1Zd5bYLqXn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Cll0DV1Zd5bYLqXn .cluster text{fill:#333;}#mermaid-svg-Cll0DV1Zd5bYLqXn .cluster span{color:#333;}#mermaid-svg-Cll0DV1Zd5bYLqXn 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-Cll0DV1Zd5bYLqXn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Cll0DV1Zd5bYLqXn rect.text{fill:none;stroke-width:0;}#mermaid-svg-Cll0DV1Zd5bYLqXn .icon-shape,#mermaid-svg-Cll0DV1Zd5bYLqXn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Cll0DV1Zd5bYLqXn .icon-shape p,#mermaid-svg-Cll0DV1Zd5bYLqXn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Cll0DV1Zd5bYLqXn .icon-shape .label rect,#mermaid-svg-Cll0DV1Zd5bYLqXn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Cll0DV1Zd5bYLqXn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Cll0DV1Zd5bYLqXn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Cll0DV1Zd5bYLqXn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是







高 QPS
低 QPS 复杂任务
新 RAG 需求
知识是否强关系/多跳?
GraphRAG / HybridRAG+KG
单 chunk 能否回答?
Flat + Hybrid BM25

  • Rerank
    文档是否超长整册?
    RAPTOR / LongRAG
    检索质量不稳定?
    CRAG / Self-RAG critique
    QPS 与延迟?
    Agentic RAG

13.10 STAR-M-P 迷你事故:GraphRAG 误用于简单 FAQ

维度 内容
S 内部 HR FAQ(800 条短问答),团队套用 Microsoft GraphRAG 做「前沿试点」。
T 2 周内上线员工问答 Bot。
A(失误) 全量建图 + 社区摘要;离线索引 6 小时×$800 LLM 成本 ;在线 P95 8s ;简单问「年假几天」返 社区摘要拼接 而非原 FAQ 句。
M 采纳率 12% (目标 40%);工单 +30%
R 改回 flat chunk + pgvector + BM25 Hybrid ;P95 1.2s ;采纳率 38%
P 规范:FAQ<5k 条且无关系推理 → 禁止 GraphRAG ;GraphRAG 仅进入 Research/合规多跳 白名单。

教训范式越新 ≠ 越适合;简单结构化 FAQ 用 GraphRAG = 过度工程 + 延迟与成本失控。

13.11 Spring AI:QuestionAnswerAdvisor vs 自定义 Agentic RAG

方式 适用 限制
QuestionAnswerAdvisor VectorStore、固定 topK、标准 QA 无多库路由、无 critique 循环
RetrievalAugmentor + 自定义 DocumentRetriever 多库、RRF、metadata 需自己写融合逻辑
Spring AI + @Tool Agent Agentic RAG、CRAG Web 兜底 延迟难控,要 maxSteps + 超时
java 复制代码
// 标准 QA(单库)
var advisor = QuestionAnswerAdvisor.builder(vectorStore)
    .searchRequest(SearchRequest.builder().topK(5).build())
    .build();
ChatClient.builder(chatModel).defaultAdvisors(advisor).build();

// Agentic:用 ChatClient + tools,不用 Advisor 包死检索
ChatClient.builder(chatModel)
    .defaultTools(new VectorSearchTool(tieredRagService), new WebSearchTool())
    .build();

14. 关联文件 + 一句话速记

14.1 关联文件

  • 01-Transformer与Attention.md · embedding 的物理基础(encoder 输出 + pooling)。
  • 01-Prompt工程-Few-Shot-CoT与Tool-Use.md · RAG 是 Tool Use 的最常见实例化。
  • 01-Agent框架-LangChain-LangGraph与AutoGen.md · Agentic RAG 的 Agent 决策层。
  • 01-电商AI辅助交易场景.md · 电商场景的 RAG 落地(商品理解、客服)。

14.2 一句话速记

RAG = Chunking + Embedding + 向量库 + Re-rank + LLM 。Chunking 甜区 500 token,embedding pin 版本,向量库 HNSW 默认,Hybrid + Re-rank 必加,引用追溯不可少。最大风险是 embedding 版本漂移和 chunk 割裂 ,CI 检测 + 自部署兜底是关键。Staff 加:三层向量架构 + 范式选型树 + §99 冲刺


§99 本章冲刺 · 满分答与 Checklist

定位 :考前 30 分钟 过 R01--R12 + 下方 30 项 Checklist;与 98-面试高频题满分答与Checklist.md 全模块题表互补,本篇偏 RAG 工程与向量库

满分答结构(本章统一)

步骤 内容 占比
结论 选型或原则一句话 ~15%
原理 Chunk / ANN / Hybrid 因果 ~25%
工程 容量、版本、三层库、延迟 ~35%
验证 nDCG、A/B、shadow index ~15%
风险 漂移、割裂、幻觉、成本 ~10%

R01 · RAG 和 Fine-tune 怎么选?

知识频繁变、要引用来源 → RAG风格/格式/领域语感 → SFT单次小包知识 → 长上下文 。电商商品与政策以 RAG + metadata 过滤 为主;客服语气可 RAG + 轻量 SFT。风险:RAG 召回错 = 答错;Fine-tune 知识过期。


R02 · Chunk size 怎么定?

:起点 500 token + 10% overlap ;技术文档用 标题递归切 ;对话日志用 语义切 。验证:扫 256/512/1024 看 nDCG@5 + 引用完整率 。过大 → 噪声多;过小 → 丢上下文。表格/代码单独 chunk 并打 type metadata。


R03 · Embedding 模型怎么选?版本怎么管?

:中文 BGE-M3 ;跨境 Cohere v3 / text-embedding-3 ;要可控 自部署 E5/BGE 。必须 pin 版本 、双索引灰度切换;每日 ground truth 一致性探针 (§10 事故)。query/doc 模式分离的模型(BGE)检索时要用对 mode


R04 · HNSW 三个参数含义?

M 控制图连边(召回/内存);efConstruction 建索引质量;efSearch 查询召回/延迟。在线默认 M=16, efSearch=64--128;压测不够再升 efSearch。亿级放不下内存 → IVF-PQ 或 DiskANN


R05 · 为什么要 Hybrid Search?

:向量擅 语义 ,BM25 擅 SKU/型号/专有名词精确匹配 。双路 top-50 → RRF 融合 → Rerank → top-5。工程上 ES/pg 全文 + Milvus 向量并行。实测 nDCG 常 +5--10%


R06 · Rerank 值得上吗?

:候选 ≤50 时值得;bge-reranker-v2-m3Cohere rerank 可再 +7--15% 。延迟 +50--200ms,放 异步或仅高质量通道。高 QPS 全链路 Rerank 要 GPU 池化。


R07 · 七库怎么快速选型?

Redis <100k 热数据pgvector <10M 要 SQLMilvus/Qdrant >10M 在线 ANNPinecone 零运维LanceDB 湖仓/离线Weaviate 图+向量 。看 容量、P99、QPS、filter、运维成本 五元组(§12.1 表)。


R08 · 三层向量架构是什么?

L1 Redis 会话/热缓存;L2 pgvector 目录与可 SQL 过滤知识;L3 Milvus 海量 UGC。Router 按意图分发,RRF 合并 后 Rerank。Spring AI 用 多 Bean @Qualifier(§12.6)。


R09 · GraphRAG 什么时候用?

多跳关系、全局主题、非结构化大部头 时用;简单 FAQ、低延迟客服 禁用。成本高、索引慢;§13.10 事故:800 条 FAQ 误用 GraphRAG 采纳率暴跌。默认 flat + Hybrid


R10 · Self-RAG / CRAG 解决什么?

Self-RAG :检索结果 critique 后再生成,减无关上下文幻觉。CRAG :检索差时 改写再检或 Web 兜底 。适合低 QPS 高价值场景;要 白名单 Web + 引用 。高 QPS 用轻量 置信度阈值 + 拒答 代替全 Self-RAG。


R11 · 如何估算 1000 万向量内存?

N × dim × 4B + HNSW 1.2--1.5× ;10M×1024×4B≈40GB 向量,总 working set ~60GB 。QPS 看 efSearch 与副本数;写入要 batch embed + 异步建索引。详见 §12.7。


R12 · RAG 生产红线有哪些?

:① embedding 版本 pin + CI 探针 ;② 回答必须带引用 ;③ metadata 权限过滤 (租户/类目);④ Hybrid + Rerank 默认开启;⑤ 索引双版本灰度 ;⑥ 拒答/低置信 策略;⑦ 成本监控 (embed QPS、LLM token)。最大坑:静默换 embeddingchunk 边界断句


30 项生产 Checklist(分组)

A. 索引(Indexing)
  • chunk 策略文档化(size / overlap / 递归规则)
  • 表格、代码、列表 独立 chunk 类型
  • 文档版本号写入 metadata(doc_version
  • 增量更新管道(CDC / 定时 diff)
  • 删除/下架 同步删向量(tombstone)
  • 双索引灰度(v1/v2)可一键回滚
  • 建索引批大小与失败重试监控
  • 全量重建 SLO 与进度条(避免半成品上线)
B. Embedding
  • 模型版本 pinned(含 API model id)
  • query/doc 同一模型同一空间
  • 每日 ground truth 余弦分布漂移 告警
  • 自部署 GPU 副本与熔断
  • 多语言分路由(中/英 embed 分离可选)
  • embed 成本仪表盘($/1M tok)
C. 检索(Retrieval)
  • Hybrid:BM25 + 向量 至少一路
  • RRF 或加权融合参数固化
  • metadata filter(租户、库存、语言)
  • ANN 参数:efSearch / nprobe 压测记录
  • Rerank top-50→5 延迟预算
  • HyDE / Query Rewrite A/B 开关
  • 多库路由(L1/L2/L3)集成测试
  • 召回 空结果低分桶 监控告警
D. 生产(Production)
  • 生成 强制引用(doc_id + span)
  • 低置信 拒答 文案与转人工
  • PII / 权限 检索前过滤
  • 端到端 trace(retrieve → rerank → gen)
  • nDCG / 采纳率 / 幻觉率 分桶看板
  • 峰值 降级:关 Rerank → 关 HyDE → 仅 BM25
  • 事故 SOP:embedding 变更、索引损坏、召回暴跌
  • §11 90 秒脚本 流利背诵

v2.4 增补 · ColBERT / SPLADE / Late Interaction / LightRAG

深度专章见 26-Embedding与Reranker-选型与微调;本节给 Staff 选型口径§13 GraphRAG 互补

14.1 检索范式对照

范式 机制 延迟 适用
Bi-encoder query/doc 各 embed 一次,ANN 默认生产(BGE/E5)
Cross-encoder query+doc 拼接过 Transformer 高(仅 rerank top-K) 精排 50→5
ColBERT / Late Interaction token 级 向量,MaxSim 聚合 中(索引大) 法律/长文档/高召回
SPLADE 稀疏扩展词项 + 倒排 关键词+语义混合
LightRAG 轻量图索引 + 实体关系 中高 比 GraphRAG 省算,企业 KB

14.2 ColBERT(Staff 必答)

text 复制代码
Bi-encoder:  sim(q, d) = cos(E(q), E(d))           # 一个向量/侧
ColBERT:     sim(q, d) = Σ_i max_j E(q_i)·E(d_j)   # token 级 MaxSim
  • 优点 :比 bi-encoder 召回更准 (细粒度匹配);比 cross-encoder 可 ANN 预筛
  • 成本 :索引体积 ≈ doc_tokens × dim;百万长文档要 分层(先 bi-encoder 召回 200 再 ColBERT rerank)。
  • 面试金句 :ColBERT 是 「可检索的 cross-encoder」 ;生产常见 RAGatouille / Vespa colbert

14.3 SPLADE 与 Hybrid

  • SPLADE :学习扩展稀疏词权重 → 兼容 ES BM25 倒排 + 语义扩展。
  • 工程BM25 ∪ SPLADE ∪ dense 三路 RRF;法务/政策库 SPLADE+BM25 往往优于纯向量。

14.4 LightRAG vs GraphRAG(§13 延伸)

机制依据L2 HKUDS/LightRAG · 论文/说明见仓库 README(与 §13 GraphRAG 对照选型)

维度 GraphRAG (§13) LightRAG
构图 社区摘要 + 全局/局部查询 实体-关系双级索引
成本 高(社区检测+多轮 LLM) 较低,增量友好
适用 复杂组织知识、研报 中等规模企业 Wiki

选型 :QPS>100 的客服 FAQ → Hybrid+rerank ;复杂合规图谱 → GraphRAG;折中 → LightRAG + 权限 metadata。

14.5 口述题(3 道)

L01 · 何时上 ColBERT?

:长文档、专业术语密集、bi-encoder 召回 nDCG 瓶颈;接受 索引 3--10× 膨胀。先 双塔召回 200 ,ColBERT 精排 20

L02 · SPLADE 和 BM25 区别?

:BM25 手工 IDF;SPLADE 神经网络学扩展词 (含同义/子词)。可落 ES learned sparse 字段。

L03 · LightRAG 何时替代 GraphRAG?

:预算/延迟不够跑社区摘要;实体关系相对稳定;要 增量更新 文档。仍要 引用+权限过滤

14.6 Checklist

  • 默认栈:Hybrid + cross-encoder rerank
  • 长文档/法律:评估 ColBERT 或 ColPali(见 10
  • 政策库:SPLADE 或 BM25 权重调优
  • 图谱 RAG:GraphRAG vs LightRAG 用 QPS 与构建成本决策
  • 26 embedding 版本 pin 联动

🧭 章节导航

# 文件 风格
00 00-README.md 索引
01 01-Transformer与Attention.md 机制
02 01-Prompt工程-Few-Shot-CoT与Tool-Use.md 操作
03 本篇 · RAG 检索增强-向量库与 Chunking 设计
26 03-Embedding与Reranker-选型与微调.md 机制+操作
04 01-Agent框架-LangChain-LangGraph与AutoGen.md 设计
05 01-AI辅助开发-Cursor-Copilot与Claude-Code.md 操作
06 02-评估-Eval-Hallucination与质量度量.md 机制
07 03-部署-模型Serving-Caching与Cost.md 操作
08 01-电商AI辅助交易场景.md 设计

官方文档与源码(一级依据)

AI Engineering · 正文机制应来自下方 官方文档(L1)官方源码仓库(L2)

禁止用教程站/博客充当机制依据。本章 QPS/延迟/STAR 为面试示意。

写作规范:docs/official-sources-registry.md §0

L1 · 官方文档

L2 · 官方源码

L3 · 论文 / 开放规范

相关推荐
Jing_jing_X3 小时前
从 Prompt 对话到 OpenClaw:Agent 是怎么一步步发展出来的?
ai·prompt·个人开发·ai应用开发
城事漫游Molly3 小时前
AI与质性研究的融合(三):AI赋能质性数据分析——从编码到理论构建的新范式
大数据·人工智能·机器学习·prompt·ai for science·智能体·定性研究
麦哲思科技任甲林4 小时前
白话skills之二:Prompt和Skills的区别是什么?
prompt·sop·skills
人工智能培训5 小时前
数字孪生赋能建筑行业 解锁工程全周期智慧管理
大数据·人工智能·机器学习·prompt·agent
-停泊1 天前
Skill和Prompt有何不同
prompt·agent·skill
贺国亚1 天前
01-Prompt工程-Few-Shot-CoT与Tool-Use
prompt
z小猫不吃鱼1 天前
12 Prompt Engineering 入门:提示词为什么会影响模型行为?
人工智能·gpt·自然语言处理·prompt
csdn_aspnet2 天前
Gemini赋能安全工程师,自动写PoC脚本,探索Gemini在网络安全领域辅助漏洞验证与POC生成的实战路径
安全·web安全·prompt·poc·gemini·工程师
不爱吃糖の糖糖2 天前
RAG 06:RAG 多路召回与检索优化策略详解
人工智能·embedding