土法炼钢兴趣小组的算法知识备份

【大模型基础设施工程】17:RAG 工程全景

文章导航

分类入口
architectureai-infra
标签入口
#llm#infra#rag#retrieval#embedding#rerank#chunking#hyde#graphrag#ragas#bge#colbert

目录

一、为什么需要 RAG

大语言模型(Large Language Model,LLM)本身存在几个结构性缺陷,这些缺陷不是”再训一版基座”就能彻底解决的:

  1. 幻觉(Hallucination):模型以概率续写,内部没有”不知道就不说”的开关。对于事实性问题,哪怕只有 5% 的胡编乱造,在严肃业务(医疗、法务、财报)里就是不可接受。
  2. 知识冻结:预训练语料有截止时间,模型不可能知道昨天刚发布的政策、今天上线的 SKU、五分钟前的工单。
  3. 私有数据缺失:企业内部的合同库、代码仓库、内部 wiki、客户工单,不可能也不应该全部灌到公有大模型里。
  4. 缺乏可追溯性:纯生成的答案没有”出处”,无法审计、无法合规、无法让业务方复核。
  5. 参数化知识代价高昂:即便用 SFT、LoRA、Continual Pre-train 往模型里灌知识,更新一次就要重训一次,分钟级别的新鲜度做不到,成本也高。

检索增强生成(Retrieval-Augmented Generation,RAG) 的核心思想是把”知识”从模型参数中解耦出来,放到外部可查询的存储里。查询时先检索(Retrieve)出相关片段,再把片段作为上下文拼到 Prompt 里让模型生成(Generate)。于是:

RAG 不是 LLM 的”可选增强”,而是绝大多数 to-B 场景下 LLM 能落地的前提。本文把 RAG 看成一整套数据工程 + 检索工程 + 生成工程的系统,把离线 ETL 到在线问答的全链路铺开讲。


二、RAG 流水线总览

一个工业级 RAG 系统,离线和在线两条路径是必须分开的。

离线 ETL(Indexing Pipeline):
  原始文档 → 解析 → 清洗 → 切片(Chunking)→ Embedding → 写入向量库 / 倒排 / 图
在线查询(Query Pipeline):
  用户 Query → Query 改写/路由 → 混合检索(向量 + BM25)→ 重排(Rerank)
            → 上下文组装(Prompt 模板)→ LLM 生成 → 引用回填 → 返回

SVG:RAG 流水线全景

SVG:RAG 流水线全景

三、文档解析:把 PDF 变成可索引的结构化文本

工业 RAG 的准确率,70% 以上取决于文档解析的质量。模型再强,解析出来是乱码、表格散架、标题丢失,检索就是在沙子上盖楼。

3.1 常见文档类型与难点

类型 难点 典型工具
原生 PDF 双栏排版、页眉页脚、公式、表格 PyMuPDF、pdfplumber、Unstructured
扫描 PDF / 图片 OCR 精度、版式还原 PaddleOCR、TesserAct、OlmOCR、MinerU
HTML / Markdown 噪声(广告、导航)、嵌套结构 trafilatura、readability、BeautifulSoup
Office (docx/pptx/xlsx) 批注、嵌入对象、图片 python-docx、python-pptx、openpyxl
表格 跨页、合并单元格、无边框 Camelot、Tabula、pdfplumber、Unstructured
图像 / 图表 语义理解 GPT-4o、Qwen-VL、MiniCPM-V
代码 / 结构化 语法边界 tree-sitter

3.2 工具选型对比

工程经验:没有单一工具能打通所有文档。生产上一般分文件类型路由,再加一条 VLM 兜底链路。

3.3 清洗的隐形收益

metadata 不是可选项。它决定了后续能否按部门过滤、按时间过滤、按权限过滤

3.4 解析阶段的代码骨架

生产环境的解析 pipeline 通常长这样:按文件类型路由到不同 parser,统一输出一个 Element 抽象(标题 / 段落 / 列表 / 表格 / 图片 / 代码),再交给下游做 chunking。

from dataclasses import dataclass, field
from typing import Literal, Optional

ElementType = Literal["title", "paragraph", "list", "table", "image", "code"]

@dataclass
class Element:
    type: ElementType
    text: str
    level: int = 0                  # 标题层级
    page: Optional[int] = None
    bbox: Optional[tuple] = None    # 版式坐标,便于引用回链
    meta: dict = field(default_factory=dict)

def parse(path: str) -> list[Element]:
    ext = path.rsplit(".", 1)[-1].lower()
    if ext == "pdf":
        try:
            return parse_pdf_mupdf(path)    # 原生 PDF 优先
        except LowQualityError:
            return parse_pdf_vlm(path)      # 扫描 / 版式复杂兜底
    if ext in ("html", "htm"):
        return parse_html(path)
    if ext == "docx":
        return parse_docx(path)
    if ext in ("png", "jpg", "jpeg", "tiff"):
        return parse_image_vlm(path)
    raise UnsupportedError(ext)

“低质量检测”常见做法:抽取文本的字符密度、乱码比例、平均行长;低于阈值就切走 VLM 兜底。这一小段工程能把整体解析可用率从 70% 拉到 95%+。

3.5 表格的特殊处理

表格最好不要被 chunk 切开。几个常用策略:


四、切片(Chunking)

4.1 为什么不能整篇塞进模型

即使长上下文模型已经普及(见 16. 长上下文工程),把整篇文档每次都丢给模型,一是贵,二是”Lost in the Middle” 真实存在,三是无法做精确引用定位。所以切片仍然是 RAG 的必修课

4.2 切片策略

经验值:中文 300–800 字 / 英文 256–512 token,overlap 10–15%,复杂手册可以放大到 1200。别死记数字,要对自己的文档跑一遍评估再拍板。

4.3 Chunking 的工程细节

几个实操要点:

4.4 一段递归切片的简化实现

SEPS = ["\n\n", "\n", "。", "!", "?", ";", ". ", " ", ""]

def split(text: str, max_len: int, overlap: int, sep_idx: int = 0) -> list[str]:
    if len(text) <= max_len:
        return [text]
    sep = SEPS[sep_idx]
    if sep == "":
        return [text[i:i+max_len] for i in range(0, len(text), max_len - overlap)]
    parts, buf = [], ""
    for seg in text.split(sep):
        cand = buf + (sep if buf else "") + seg
        if len(cand) <= max_len:
            buf = cand
        else:
            if buf:
                parts.append(buf)
            if len(seg) > max_len:
                parts.extend(split(seg, max_len, overlap, sep_idx + 1))
                buf = ""
            else:
                buf = seg
    if buf:
        parts.append(buf)
    return parts

真实生产会在这之上再叠加:token 计数、overlap 拼接、标题链头、表格豁免。LangChain / LlamaIndex 的实现基本就是这套骨架的加强版。


五、Embedding 与索引

5.1 Embedding 模型现状

模型 维度 语言 特色
OpenAI text-embedding-3-large 3072(可截断) 多语 Matryoshka,闭源 API
BAAI bge-large-zh-v1.5 / bge-m3 1024 多语 开源,中文强;M3 同时出稠密 + 稀疏 + ColBERT 多向量
E5-mistral-7b-instruct 4096 多语 大参数,MTEB 强
Qwen3-Embedding-0.6B / 4B / 8B 1024–4096 多语 阿里 2025 新出,C-MTEB SOTA
Jina jina-embeddings-v3 1024 多语 Matryoshka,长文(8K)
gte-Qwen2-7B-instruct 3584 多语 阿里,指令式 embedding
Cohere embed-v3 1024 多语 闭源,检索精度好

选型建议:

5.2 Late-Interaction:ColBERT 家族

传统 bi-encoder 把句子压成一个向量,损失细粒度信息。ColBERT(Contextualized Late Interaction over BERT)给每个 token 都存一个向量,查询时算 query token × doc token 的 MaxSim 之和。

5.3 索引:向量 + 倒排 + 图

工程上三者往往同时存在:向量负责语义召回,倒排负责关键词和过滤,图负责多跳推理。

5.4 Embedding 服务化

自建 Embedding 服务推荐:

几个坑:


六、检索:向量 + BM25 混合

6.1 为什么单独向量不够

向量召回在”语义相似”上强,但在以下场景会翻车:

BM25 在这些场景稳如老狗,而在长句语义匹配上弱于向量。两者互补

6.2 融合方法

6.3 SVG:混合检索融合

SVG:混合检索融合

七、重排(Rerank)

7.1 为什么需要 Rerank

Embedding 是 bi-encoder:query 与 doc 独立编码,靠点积/余弦算相似度。速度快,但没有 query-doc 的 token 级交互。Rerank 用 cross-encoder:把 [CLS] query [SEP] doc [SEP] 一起进 BERT,直接输出相关性分。精度明显高,但 O(N) 次前向,只能在 Top-K 上做。

7.2 主流 Rerank 模型

7.3 ColBERT 作为”准 Rerank”

ColBERT 的 late interaction 在召回和重排之间,常见用法:向量召回 Top-200 → ColBERT rescore → Top-50 → cross-encoder → Top-5。三级漏斗精度与延迟平衡最佳,但工程复杂度高。

7.4 Rerank 的工程坑


八、Query 改写与路由

用户 Query 往往”短、含糊、带代词、预设了上下文”,直接拿去检索召回很差。Query 改写是 RAG 效果的另一个核心放大器。

8.1 HyDE(Hypothetical Document Embeddings)

让 LLM 先”假装回答”这个问题,把假回答做 embedding 去检索。直觉:假答案和真答案在 embedding 空间接近,比原始问题更接近目标文档。适合问答型 Query,对事实型 Query 特别有效。

8.2 Multi-Query

让 LLM 把一个问题改写成 3–5 个不同表述,分别检索后合并(RRF)。鲁棒性高,成本可控。

8.3 Subquery 分解

复杂问题(“对比 A 和 B 在 2023/2024 的营收增速”)拆成多个子问题并行检索。Agentic RAG 的基础。

8.4 RAG-Fusion

Multi-Query + RRF 的工程化封装:N 个改写 × 检索 → RRF 融合。

8.5 Query 路由

ROUTE_PROMPT = """根据用户问题选择知识库,只输出名字:
候选:[product_manual, legal_contract, codebase, none]
问题:{q}"""

8.6 一段 HyDE 的极简实现

HYDE_PROMPT = "请以百科风格给出一段 150 字左右的可能答案,用于检索相关文档:\n问题:{q}"

def hyde_retrieve(q: str, k: int = 20):
    hypo = llm.complete(HYDE_PROMPT.format(q=q))
    hits_hyde = vector_store.search(embed(hypo), k=k)
    hits_raw  = vector_store.search(embed(q), k=k)
    return rrf_merge([hits_hyde, hits_raw], k=60)[:k]

经验:HyDE 对”事实问答 / 知识问答”提升明显( 常见 +5–15 个百分点),对”短关键词 / 精确名词”反而可能变差,生产里常与原 Query 并行 + RRF 合并,而不是替换。


九、上下文组装与引用回填

9.1 Prompt 模板

你是企业知识助手。请仅依据以下参考资料回答问题;若资料不足以回答,直接说"不知道"。
每条论断后用 [序号] 标注来源。

<参考资料>
[1] {title_1} (来源: {source_1})
{chunk_1}
[2] {title_2} (来源: {source_2})
{chunk_2}
...
</参考资料>

问题:{query}
回答:

几个细节:

9.2 引用回填

生成完后,正则抽 \[\d+\],映射到 chunk metadata,返给前端超链接 + 高亮片段。合规和用户信任都靠这一环。


十、高级 RAG 范式

10.1 Self-RAG

训练阶段给模型插入 reflection token([Retrieve] / [IsRelevant] / [IsSupported] / [IsUseful])。推理时模型自己决定是否检索、检索到的是否相关、生成是否被支持。优点:动态控制检索;缺点:需要专门微调的模型。

10.2 CRAG(Corrective RAG)

对检索结果跑一个轻量检索评估器,把文档分成 correct / ambiguous / incorrect:

工程上易落地,加一个评估模型即可。

10.3 Adaptive RAG

按问题难度路由:简单事实(模型直接答)→ 单跳检索 → 多跳检索 / Agentic。节省成本与延迟。

10.4 GraphRAG

Microsoft Research 2024 年提出的范式。离线阶段:

  1. LLM 从文档中抽实体与关系构图;
  2. Leiden 算法做社区发现;
  3. LLM 对每个社区生成社区摘要

查询阶段分 Local(实体邻域)和 Global(先按社区摘要聚合再回答)。擅长全局型问题(“这批文档里主要讨论了哪些主题”),在分析型、综述型任务上显著强于向量 RAG。代价:构图时 LLM 调用量大,成本高。下一篇 18. 向量库与图 RAG 会展开。

10.5 Agentic RAG

把 RAG 放到 Agent 循环里:规划 → 子查询 → 检索 → 反思 → 再查 → 综合。LangGraph、LlamaIndex Agent、AutoGen 都是常用框架。适合多跳问答、跨知识库比较、带工具调用的场景。代价是延迟和成本。

10.6 Long-context RAG vs 小 chunk RAG

在 Kimi、Gemini 1.5、GPT-4.1、Claude 这类 100 万 token 上下文模型出现后,出现了新的取舍:

现实世界的答案几乎总是混合:大部分问题走小 chunk RAG;少数需要全局视角的走长上下文或 GraphRAG。

10.7 一段 GraphRAG 风格的伪代码

展示”实体抽取 → 图构建 → 社区摘要 → 查询”的核心骨架,实际实现见 Microsoft 官方 graphrag 仓库:

# 1) 抽取实体与关系
EXTRACT_PROMPT = """从文本抽取实体(name, type, description)与关系(src, dst, description, strength)。
输出 JSON:{"entities": [...], "relations": [...]}
文本:{chunk}"""

entities, relations = [], []
for ch in chunks:
    out = llm.json(EXTRACT_PROMPT.format(chunk=ch))
    entities.extend(out["entities"]); relations.extend(out["relations"])

# 2) 实体消歧:同名 / 同义聚合(Embedding + 规则)
entities = dedupe_by_embedding(entities, threshold=0.86)

# 3) 建图 + 社区发现
G = build_graph(entities, relations)
communities = leiden(G, resolution=[1.0, 2.0, 4.0])   # 多层

# 4) 社区摘要
SUMMARY_PROMPT = "基于以下实体 + 关系写一段 200 字摘要:\n{sub}"
for c in communities:
    c.summary = llm.complete(SUMMARY_PROMPT.format(sub=c.dump()))

# 5) 查询:Global = map-reduce 社区摘要;Local = 实体邻域
def query(q):
    if is_global(q):
        partials = [llm.answer(q, ctx=c.summary) for c in top_communities(q, k=20)]
        return llm.reduce(q, partials)
    else:
        ents = match_entities(q, G)
        ctx  = neighbors(G, ents, hops=2) + related_chunks(ents)
        return llm.answer(q, ctx=ctx)

工程代价要算清楚:假设 10 万 chunk,每个 chunk 抽取大约消耗 1.5k token 输入 + 0.5k token 输出,全量构图约 2 亿 token,按 DeepSeek-V3 / GPT-4o-mini 批价算就是百元到千元级一次,不是随便重建的东西。所以生产上 GraphRAG 通常离线 T+1 全量 + 日常增量 upsert

10.8 CRAG 的轻量落地

不想做 Self-RAG 那种重训,CRAG 是性价比最高的”纠偏”方案:只加一个检索评估器(可以是一个小模型或 LLM few-shot)和一个Web 搜索兜底。示意:

EVAL_PROMPT = """判断这段资料对于回答问题的相关性,输出 correct/ambiguous/incorrect 之一。
问题:{q}
资料:{doc}"""

def crag(q: str):
    docs = hybrid_retrieve(q, k=10)
    labeled = [(d, llm.classify(EVAL_PROMPT.format(q=q, doc=d.text))) for d in docs]
    good = [d for d, s in labeled if s == "correct"]
    if good:
        return generate(q, good)
    # 走 Web / 其他兜底知识库
    web_docs = web_search(rewrite(q), k=5)
    return generate(q, good + web_docs)

实操经验:先把 evaluator 做成 cache 友好(以 (q_hash, doc_hash) 为 key),避免一个问题评估几十次;评估模型用 1.5B–7B 的小模型就够,别用大模型烧钱。

10.9 Agentic RAG 的规划骨架

PLAN_PROMPT = """把复杂问题拆成 1-5 个可独立检索的子问题,JSON 数组输出。
问题:{q}"""

def agentic_rag(q: str, max_steps: int = 3):
    subs = llm.json(PLAN_PROMPT.format(q=q))
    notes = []
    for sq in subs:
        hits = hybrid_retrieve(sq, k=5)
        hits = rerank(sq, hits, top_n=3)
        notes.append({"sub": sq, "evidence": hits})
    # 反思:信息是否足够?
    if llm.yes_no(f"下列笔记是否足够回答『{q}』?\n{notes}") == "no" and max_steps > 0:
        follow_up = llm.complete(f"还缺什么信息?给一个新子问题:{notes}")
        return agentic_rag(q + " " + follow_up, max_steps - 1)
    return llm.complete(f"综合下列笔记回答:{q}\n笔记:{notes}")

Agentic RAG 强在多跳问答跨库综合,代价是延迟膨胀 3–5 倍、Token 消耗 5–10 倍。产品侧常见做法:普通问题走直连 RAG,用户明确点”深度模式”或路由识别为”综合型”问题时才切到 Agentic。


十一、评估:没有评估就没有 RAG 工程

11.1 两层评估

11.2 评估数据

11.3 事实核查

11.4 一段最小 RAGAS 评估

from datasets import Dataset
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision, context_recall

samples = [{
    "question":     "员工差旅报销上限?",
    "answer":       "员工国内出差单日住宿上限 600 元 [1]。",
    "contexts":     ["... 差旅管理办法第 4.2 条:国内一线城市住宿单日上限 600 元 ..."],
    "ground_truth": "国内一线城市住宿单日上限 600 元。",
}]

ds = Dataset.from_list(samples)
res = evaluate(ds, metrics=[faithfulness, answer_relevancy, context_precision, context_recall])
print(res)

评估要像 CI 一样跑:每次升级 embedding、换模型、改 Prompt 都要过一遍回归集,否则就是在盲飞。生产上一般会有两个数据集:小而精的人工标注集(几百条,天天跑)和大而杂的线上采样集(几千条,每周跑一次)。


十二、架构:离线 ETL 与在线服务

12.1 离线 ETL

源系统 (OSS/S3/Confluence/GitLab)
  → 采集 (事件或定时)
  → 解析 + 清洗 (Spark/Ray/Prefect)
  → Chunking + Embedding (GPU 批推,vLLM/TEI)
  → 写入 (Milvus + ES + MySQL metadata)
  → 索引校验 (Recall 回归)

几点工程经验:

12.2 在线服务

API Gateway → Query Service
  → Router (intent / KB 选择)
  → Query Rewriter (HyDE / Multi-Query)
  → Retriever (Milvus + ES 并行 → RRF)
  → Reranker (bge-reranker-v2)
  → Prompt Builder
  → LLM Gateway (vLLM / Bedrock / DashScope)
  → Citation Postprocessor
  → 返回 + 记录观测

延迟预算(典型中文企业问答):

阶段 耗时
Query 改写 150–400 ms
向量召回 20–80 ms
BM25 10–30 ms
Rerank (Top50) 100–300 ms
LLM 首字 (TTFT) 300–1000 ms
全程流式总计 1.5–4 s

首字延迟是用户体感关键,能并行的都并行(改写 / 召回 / Rerank),LLM 用流式返回。

12.3 增量更新

12.4 可观测与安全要点

RAG 作为企业入口,可观测性权限必须一开始就做进架构:


十三、国内外生态

13.1 国内托管平台

13.2 国外托管平台

13.3 开源 RAG 平台

选型建议:POC 用 Dify/FastGPT/Coze 快跑;生产自研用 LlamaIndex / LangChain 组装,关键组件(解析、Embedding、Rerank)独立可替换。

13.4 选型对比表

维度 托管云平台(百炼 / Bedrock KB) 开源低代码(Dify / FastGPT / RagFlow) 自研(LlamaIndex/LangChain + 自管组件)
上线速度 最快(天级) 快(周级) 慢(月级)
可定制性
数据主权 看部署形态 私有化友好 完全自主
评估与观测 厂商自带 基础功能 需自建
长期成本 流量越大越贵 基础设施成本 人力 + 基础设施
合规 依赖厂商 较灵活 最灵活
适用规模 小 / 中 大 / 复杂

经验法则:POC 快速验证 → 中期用开源平台沉淀 → 核心业务再走自研。跳级容易摔。

13.5 成本粗估

一个企业知识库(100 万 chunk、每天 10 万次查询)的典型月成本量级:

项目 单价参考 月成本量级
Embedding 重建 1 亿 token / 月 自建 bge-m3:GPU 摊销 百元 ~ 千元
向量库(Milvus) 100M × 1024 dim 1 台 r6i.2xlarge × 3 副本 数千元
BM25(ES) 100M 文档 3 × hot node 数千元
Rerank 10 万查询 × Top50 1 张 A10 千元
LLM 生成 10 万查询 × 2k token Qwen-Plus / GPT-4o-mini 数千元 ~ 数万元
观测 + 存储 —— —— 千元

LLM 生成往往是大头,所以小模型分诊 + 大模型兜底是常见降本手法。


十四、代码示例:最小 RAG

14.1 LangChain 版本

from langchain_community.document_loaders import PyMuPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_community.retrievers import BM25Retriever
from langchain.retrievers import EnsembleRetriever
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

docs = PyMuPDFLoader("handbook.pdf").load()
splitter = RecursiveCharacterTextSplitter(
    chunk_size=600, chunk_overlap=80,
    separators=["\n\n", "\n", "。", "!", "?", " ", ""],
)
chunks = splitter.split_documents(docs)

emb = HuggingFaceEmbeddings(model_name="BAAI/bge-m3")
vs = FAISS.from_documents(chunks, emb)
dense = vs.as_retriever(search_kwargs={"k": 20})
bm25 = BM25Retriever.from_documents(chunks); bm25.k = 20
hybrid = EnsembleRetriever(retrievers=[dense, bm25], weights=[0.6, 0.4])

prompt = ChatPromptTemplate.from_template("""仅依据参考资料回答,不知则说不知道。
每条论断用 [i] 标引用。

参考资料:
{context}

问题:{question}
回答:""")

def fmt(docs):
    return "\n\n".join(f"[{i+1}] {d.metadata.get('source','')}\n{d.page_content}"
                       for i, d in enumerate(docs))

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
chain = ({"context": hybrid | fmt, "question": RunnablePassthrough()}
         | prompt | llm | StrOutputParser())

print(chain.invoke("员工差旅报销的额度上限是多少?"))

14.2 LlamaIndex 版本

from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.core.node_parser import SentenceSplitter
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.llms.openai import OpenAI
from llama_index.core.postprocessor import SentenceTransformerRerank

Settings.embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-m3")
Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)
Settings.node_parser = SentenceSplitter(chunk_size=600, chunk_overlap=80)

docs = SimpleDirectoryReader("./data").load_data()
index = VectorStoreIndex.from_documents(docs)

reranker = SentenceTransformerRerank(model="BAAI/bge-reranker-v2-m3", top_n=5)
qe = index.as_query_engine(similarity_top_k=20, node_postprocessors=[reranker])
resp = qe.query("员工差旅报销的额度上限是多少?")
print(resp); print("---"); [print(n.metadata, n.score) for n in resp.source_nodes]

14.3 用 Milvus + TEI 的生产雏形

上面两段是单机玩具,生产里至少要把向量库和 Embedding 服务拆出来:

import requests
from pymilvus import MilvusClient, DataType

TEI = "http://tei:8080"                       # HuggingFace TEI
client = MilvusClient(uri="http://milvus:19530")

def embed(texts: list[str]) -> list[list[float]]:
    r = requests.post(f"{TEI}/embed", json={"inputs": texts}, timeout=30)
    return r.json()

# 建集合(一次性)
schema = MilvusClient.create_schema(auto_id=False, enable_dynamic_field=True)
schema.add_field("id", DataType.VARCHAR, is_primary=True, max_length=64)
schema.add_field("vec", DataType.FLOAT_VECTOR, dim=1024)
schema.add_field("text", DataType.VARCHAR, max_length=4096)
schema.add_field("doc_id", DataType.VARCHAR, max_length=64)
schema.add_field("acl", DataType.VARCHAR, max_length=128)
client.create_collection("kb_v1", schema=schema)
client.create_index("kb_v1", [{"field_name": "vec", "index_type": "HNSW",
                                "metric_type": "COSINE", "params": {"M": 16, "efConstruction": 200}}])

# 写入(批量)
def upsert(chunks):
    vecs = embed([c["text"] for c in chunks])
    rows = [{**c, "vec": v} for c, v in zip(chunks, vecs)]
    client.upsert("kb_v1", rows)

# 查询(带 ACL 过滤)
def search(q, user_acl, k=20):
    vec = embed([q])[0]
    return client.search("kb_v1", data=[vec], limit=k,
                         filter=f'acl in {user_acl}',
                         output_fields=["text", "doc_id"])[0]

这个雏形已经具备:向量库独立、Embedding 服务独立、ACL filter、批量写入、HNSW 索引。再加上 Elasticsearch 的 BM25 并行调用与 bge-reranker 服务,就是一套小规模生产可用的骨架。

两段代码都可以在半小时内跑通,把公司 PDF 塞进去就能问。但生产要做的事是后面那 20 倍的工程:解析、清洗、路由、评估、可观测、权限。


十五、生产 Checklist 与反模式

Checklist

按阶段排查故障的简表

现象 最可能的环节 排查动作
答非所问 Retrieval 召回差 / 尝试 HyDE / 换 Embedding
召回对但答错 Prompt 或 LLM 检查上下文顺序 / 压缩 / 换更强模型
专有名词查不到 缺 BM25 加混合检索 / 同义词词典
表格数据错乱 解析 / chunking 表格独立 chunk / 用 Markdown 表
幻觉多 Prompt / 拒答阈值 强化”不知道就说不知道” / rerank 分数阈值
延迟高 Rerank 或 LLM 压 top_k / 减 token / 并行化 / 流式
新文档不生效 增量链路 看 ETL 作业与索引版本
权限泄漏 metadata filter 未生效 审计日志回放

常见反模式


十六、小结

RAG 不是一个”向量库 + Prompt 拼接”就能解决的问题。一条工业级 RAG 流水线至少包括:多源文档解析、结构化清洗、语义切片、高质量 Embedding、混合检索、Cross-Encoder 重排、Query 改写、上下文组装、引用回填、离线 + 在线评估、增量更新、可观测与安全十几个环节。每一环都有独立的模型、工具与评估方法。

2024–2026 年 RAG 领域的几个确定趋势:

另外几个值得关注的方向:

下一篇我们聚焦”存储层”:向量库的工程细节,以及图 RAG 的落地路径。


上一篇16. 长上下文工程 下一篇18. 向量库与图 RAG

参考资料

  1. Lewis, P. et al. Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks. NeurIPS 2020.
  2. Gao, Y. et al. Retrieval-Augmented Generation for Large Language Models: A Survey. arXiv:2312.10997, 2024.
  3. Asai, A. et al. Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection. ICLR 2024.
  4. Yan, S. et al. Corrective Retrieval Augmented Generation (CRAG). arXiv:2401.15884, 2024.
  5. Edge, D. et al. From Local to Global: A Graph RAG Approach to Query-Focused Summarization. Microsoft Research, 2024.
  6. Khattab, O., Zaharia, M. ColBERT: Efficient and Effective Passage Search via Contextualized Late Interaction over BERT. SIGIR 2020.
  7. Chen, J. et al. BGE-M3: Multi-Lingual, Multi-Functional, Multi-Granularity Text Embeddings. 2024.
  8. Wang, L. et al. Text Embeddings by Weakly-Supervised Contrastive Pre-training (E5). 2022.
  9. ES-FAQ. RAGAS: Automated Evaluation of Retrieval Augmented Generation. 2023.
  10. MTEB / C-MTEB Leaderboard, HuggingFace Spaces.
  11. LlamaIndex, LangChain, Haystack, Dify, RagFlow, FastGPT 官方文档。
  12. Liu, N. F. et al. Lost in the Middle: How Language Models Use Long Contexts. TACL 2024.
  13. Muennighoff, N. et al. MTEB: Massive Text Embedding Benchmark. EACL 2023.
  14. Xiao, S. et al. C-Pack: Packed Resources For General Chinese Embeddings. SIGIR 2024.
  15. Santhanam, K. et al. ColBERTv2: Effective and Efficient Retrieval via Lightweight Late Interaction. NAACL 2022.
  16. Shao, R. et al. Retrieval-Augmented Generation for AI-Generated Content: A Survey. arXiv:2402.19473, 2024.
  17. Jin, Z. et al. LongRAG: Enhancing Retrieval-Augmented Generation with Long-context LLMs. arXiv:2406.15319, 2024.
  18. Sarthi, P. et al. RAPTOR: Recursive Abstractive Processing for Tree-Organized Retrieval. ICLR 2024.
  19. Qwen Team. Qwen3-Embedding Technical Report. 2025.
  20. Wang, S. et al. Searching for Best Practices in Retrieval-Augmented Generation. EMNLP 2024.
  21. Jiang, Z. et al. Active Retrieval Augmented Generation (FLARE). EMNLP 2023.
  22. Gao, L. et al. Precise Zero-Shot Dense Retrieval without Relevance Labels (HyDE). ACL 2023.
  23. Trivedi, H. et al. Interleaving Retrieval with Chain-of-Thought Reasoning (IRCoT). ACL 2023.

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-22 · architecture / ai-infra

【大模型基础设施工程】23:LLM 可观测性

面向 LLM、RAG 与 Agent 系统的可观测性工程实战;覆盖 Metrics、Logs、Traces、Token 成本、幻觉评估、Langfuse / LangSmith / Phoenix / OpenLLMetry 与 OpenTelemetry GenAI 语义约定。

2026-04-22 · architecture / ai-infra

大模型基础设施工程

面向中国工程团队的大模型基础设施系列。从 GPU/CUDA/互联、训练框架与 3D 并行、vLLM/SGLang 推理引擎、量化与推测解码、RAG/Agent 到服务化、网关、可观测性与安全合规,覆盖 LLMOps 全链路。


By .