2026-04-22 · architecture / ai-infra
【大模型基础设施工程】18:向量库与图 RAG
从 HNSW、IVF-PQ、DiskANN 到 Milvus、Qdrant、pgvector;从稠密稀疏混合到 Microsoft GraphRAG 的工程实操。
























大语言模型(Large Language Model,LLM)本身存在几个结构性缺陷,这些缺陷不是”再训一版基座”就能彻底解决的:
检索增强生成(Retrieval-Augmented Generation,RAG) 的核心思想是把”知识”从模型参数中解耦出来,放到外部可查询的存储里。查询时先检索(Retrieve)出相关片段,再把片段作为上下文拼到 Prompt 里让模型生成(Generate)。于是:
RAG 不是 LLM 的”可选增强”,而是绝大多数 to-B 场景下 LLM 能落地的前提。本文把 RAG 看成一整套数据工程 + 检索工程 + 生成工程的系统,把离线 ETL 到在线问答的全链路铺开讲。
一个工业级 RAG 系统,离线和在线两条路径是必须分开的。
离线 ETL(Indexing Pipeline):
原始文档 → 解析 → 清洗 → 切片(Chunking)→ Embedding → 写入向量库 / 倒排 / 图
在线查询(Query Pipeline):
用户 Query → Query 改写/路由 → 混合检索(向量 + BM25)→ 重排(Rerank)
→ 上下文组装(Prompt 模板)→ LLM 生成 → 引用回填 → 返回
工业 RAG 的准确率,70% 以上取决于文档解析的质量。模型再强,解析出来是乱码、表格散架、标题丢失,检索就是在沙子上盖楼。
| 类型 | 难点 | 典型工具 |
|---|---|---|
| 原生 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 |
Element
列表(Title / NarrativeText / Table / List),方便按类型做
chunking。工程经验:没有单一工具能打通所有文档。生产上一般分文件类型路由,再加一条 VLM 兜底链路。
#、##)写入
metadata;doc_id / page / section / source_url。metadata 不是可选项。它决定了后续能否按部门过滤、按时间过滤、按权限过滤。
生产环境的解析 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%+。
表格最好不要被 chunk 切开。几个常用策略:
即使长上下文模型已经普及(见 16. 长上下文工程),把整篇文档每次都丢给模型,一是贵,二是”Lost in the Middle” 真实存在,三是无法做精确引用定位。所以切片仍然是 RAG 的必修课。
["\n\n", "\n", "。", "!", "?", " ", ""]
优先级递归切,尽量保持语义单元。LangChain 的
RecursiveCharacterTextSplitter
是工业默认。AutoMergingRetriever / LangChain 的
ParentDocumentRetriever 都是这个思路。经验值:中文 300–800 字 / 英文 256–512 token,overlap 10–15%,复杂手册可以放大到 1200。别死记数字,要对自己的文档跑一遍评估再拍板。
几个实操要点:
产品手册 > 第 3 章 > 3.2 节),帮助
Embedding 和 LLM 理解位置。hash(doc_id + section_path + offset) 生成稳定
id,方便增量更新。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 的实现基本就是这套骨架的加强版。
| 模型 | 维度 | 语言 | 特色 |
|---|---|---|---|
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 | 多语 | 闭源,检索精度好 |
选型建议:
bge-small-zh /
m3e-small,几百 MB 内存就够。传统 bi-encoder 把句子压成一个向量,损失细粒度信息。ColBERT(Contextualized Late Interaction over BERT)给每个 token 都存一个向量,查询时算 query token × doc token 的 MaxSim 之和。
工程上三者往往同时存在:向量负责语义召回,倒排负责关键词和过滤,图负责多跳推理。
自建 Embedding 服务推荐:
embedding
API。几个坑:
"query: ..." 或自定义
instruction),否则精度大幅下降。向量召回在”语义相似”上强,但在以下场景会翻车:
ORA-00942、CVE-2024-38063
这种 token,embedding 经常分不清。BM25 在这些场景稳如老狗,而在长句语义匹配上弱于向量。两者互补。
score = Σ 1 / (k + rank_i),k
一般取 60。不需要分数归一化,鲁棒,工业默认。α * vec + (1-α) * bm25,需要先做
min-max 或 z-score 归一化。Embedding 是 bi-encoder:query 与 doc
独立编码,靠点积/余弦算相似度。速度快,但没有
query-doc 的 token 级交互。Rerank 用
cross-encoder:把
[CLS] query [SEP] doc [SEP] 一起进
BERT,直接输出相关性分。精度明显高,但 O(N)
次前向,只能在 Top-K 上做。
ColBERT 的 late interaction 在召回和重排之间,常见用法:向量召回 Top-200 → ColBERT rescore → Top-50 → cross-encoder → Top-5。三级漏斗精度与延迟平衡最佳,但工程复杂度高。
top_k 定在 30–80。用户 Query 往往”短、含糊、带代词、预设了上下文”,直接拿去检索召回很差。Query 改写是 RAG 效果的另一个核心放大器。
让 LLM 先”假装回答”这个问题,把假回答做 embedding 去检索。直觉:假答案和真答案在 embedding 空间接近,比原始问题更接近目标文档。适合问答型 Query,对事实型 Query 特别有效。
让 LLM 把一个问题改写成 3–5 个不同表述,分别检索后合并(RRF)。鲁棒性高,成本可控。
复杂问题(“对比 A 和 B 在 2023/2024 的营收增速”)拆成多个子问题并行检索。Agentic RAG 的基础。
Multi-Query + RRF 的工程化封装:N 个改写 × 检索 → RRF 融合。
ROUTE_PROMPT = """根据用户问题选择知识库,只输出名字:
候选:[product_manual, legal_contract, codebase, none]
问题:{q}"""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 对”事实问答 / 知识问答”提升明显(Recall@10 常见 +5–15 个百分点),对”短关键词 / 精确名词”反而可能变差,生产里常与原 Query 并行 + RRF 合并,而不是替换。
你是企业知识助手。请仅依据以下参考资料回答问题;若资料不足以回答,直接说"不知道"。
每条论断后用 [序号] 标注来源。
<参考资料>
[1] {title_1} (来源: {source_1})
{chunk_1}
[2] {title_2} (来源: {source_2})
{chunk_2}
...
</参考资料>
问题:{query}
回答:
几个细节:
[i],方便引用;后处理把
[i] 映射回 URL / 文件 + 页码。生成完后,正则抽 \[\d+\],映射到 chunk
metadata,返给前端超链接 +
高亮片段。合规和用户信任都靠这一环。
训练阶段给模型插入 reflection
token([Retrieve] / [IsRelevant] /
[IsSupported] /
[IsUseful])。推理时模型自己决定是否检索、检索到的是否相关、生成是否被支持。优点:动态控制检索;缺点:需要专门微调的模型。
对检索结果跑一个轻量检索评估器,把文档分成 correct / ambiguous / incorrect:
工程上易落地,加一个评估模型即可。
按问题难度路由:简单事实(模型直接答)→ 单跳检索 → 多跳检索 / Agentic。节省成本与延迟。
Microsoft Research 2024 年提出的范式。离线阶段:
查询阶段分 Local(实体邻域)和 Global(先按社区摘要聚合再回答)。擅长全局型问题(“这批文档里主要讨论了哪些主题”),在分析型、综述型任务上显著强于向量 RAG。代价:构图时 LLM 调用量大,成本高。下一篇 18. 向量库与图 RAG 会展开。
把 RAG 放到 Agent 循环里:规划 → 子查询 → 检索 → 反思 → 再查 → 综合。LangGraph、LlamaIndex Agent、AutoGen 都是常用框架。适合多跳问答、跨知识库比较、带工具调用的场景。代价是延迟和成本。
在 Kimi、Gemini 1.5、GPT-4.1、Claude 这类 100 万 token 上下文模型出现后,出现了新的取舍:
现实世界的答案几乎总是混合:大部分问题走小 chunk RAG;少数需要全局视角的走长上下文或 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。
不想做 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
的小模型就够,别用大模型烧钱。
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。
Recall@K:Top-K 内命中的比例。MRR(Mean Reciprocal
Rank):正例排名的倒数。nDCG@K:考虑多级相关性和排序质量。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 都要过一遍回归集,否则就是在盲飞。生产上一般会有两个数据集:小而精的人工标注集(几百条,天天跑)和大而杂的线上采样集(几千条,每周跑一次)。
源系统 (OSS/S3/Confluence/GitLab)
→ 采集 (事件或定时)
→ 解析 + 清洗 (Spark/Ray/Prefect)
→ Chunking + Embedding (GPU 批推,vLLM/TEI)
→ 写入 (Milvus + ES + MySQL metadata)
→ 索引校验 (Recall 回归)
几点工程经验:
(doc_id, version)
为主键,重跑不产生脏数据。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 用流式返回。
doc_id
upsert,删除用 tombstone,定期 compact。valid_from / valid_to,查询时 filter
掉过期文档。RAG 作为企业入口,可观测性和权限必须一开始就做进架构:
query / 改写后 query / 召回 ids / rerank 分数 / prompt / 答案 / 引用 / 延迟分解 / 成本。结合
LangSmith、Langfuse、Arize Phoenix 或自建 ClickHouse
看板。tenant_id / dept / acl_tags,检索时强制注入
filter。选型建议:POC 用 Dify/FastGPT/Coze 快跑;生产自研用 LlamaIndex / LangChain 组装,关键组件(解析、Embedding、Rerank)独立可替换。
| 维度 | 托管云平台(百炼 / Bedrock KB) | 开源低代码(Dify / FastGPT / RagFlow) | 自研(LlamaIndex/LangChain + 自管组件) |
|---|---|---|---|
| 上线速度 | 最快(天级) | 快(周级) | 慢(月级) |
| 可定制性 | 低 | 中 | 高 |
| 数据主权 | 看部署形态 | 私有化友好 | 完全自主 |
| 评估与观测 | 厂商自带 | 基础功能 | 需自建 |
| 长期成本 | 流量越大越贵 | 基础设施成本 | 人力 + 基础设施 |
| 合规 | 依赖厂商 | 较灵活 | 最灵活 |
| 适用规模 | 小 / 中 | 中 | 大 / 复杂 |
经验法则:POC 快速验证 → 中期用开源平台沉淀 → 核心业务再走自研。跳级容易摔。
一个企业知识库(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 生成往往是大头,所以小模型分诊 + 大模型兜底是常见降本手法。
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("员工差旅报销的额度上限是多少?"))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]上面两段是单机玩具,生产里至少要把向量库和 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:
按阶段排查故障的简表:
| 现象 | 最可能的环节 | 排查动作 |
|---|---|---|
| 答非所问 | Retrieval 召回差 | 看 Recall@K / 尝试 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
把当前热点继续串成多页阅读,而不是停在单篇消费。
2026-04-22 · architecture / ai-infra
从 HNSW、IVF-PQ、DiskANN 到 Milvus、Qdrant、pgvector;从稠密稀疏混合到 Microsoft GraphRAG 的工程实操。
2026-04-22 · architecture / ai-infra
面向工程师的大模型基础设施开篇地图,覆盖 2022 到 2026 的工程分水岭、五层工程栈、训练与推理的工程差异、中国与全球行业版图以及成本曲线。
2026-04-22 · architecture / ai-infra
面向 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 全链路。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。