Appearance
RAG 体系与项目定位
看在前面
RAG(Retrieval-Augmented Generation,检索增强生成)是 LLM 应用岗面试的第一考点。这篇分两半。前半讲标准 RAG 的五段工程(切分/嵌入/向量库/检索重排/评测),按"每段的决策点 + 踩坑"写。后半是一个诚实定位——star-site ai-service 的报告解读链路(report_ocr → report_interpret)不是 RAG,是"结构化输入 + LLM",并且不应该是 RAG。把"什么时候不需要 RAG"讲清,比背五段流程更能证明真做过工程。
姊妹篇:LLM基础-Token上下文与幻觉(讲窗口与幻觉,是 RAG 存在的理由)、系统提示词工程-从财务诊断说起。
一、RAG 解决什么问题
一句话:模型参数里的知识是旧的、通用的、不可溯源的。RAG 用"先检索后生成"把私有/时效知识临时注入上下文。对比另外两条路线可以定位清楚:
| 路线 | 知识更新成本 | 可溯源 | 适用 |
|---|---|---|---|
| 提示词直塞(长上下文) | 每次调用都付全量 token 费 | 无 | 知识量小且固定(一份报告的指标) |
| 微调(参数注入) | 重训练,慢且贵 | 无 | 风格/格式习得,而非事实注入 |
| RAG(上下文注入) | 增删文档即可 | 有(引用片段) | 私有语料量大、更新频繁、要引用出处 |
RAG 不是银弹:它把"模型不知道"换成"检索没找到"这一新的失败模式,评测和运维成本都真实存在。这正是第二节"什么时候不用"的伏笔。
二、标准 RAG 五段工程
先摆全景:五段分属两条流水线,离线建索引一次、在线查询每次。
离线(写路径) 在线(读路径)
文档 → 切分 ─┐
├→ 嵌入 → 向量库 查询 → 嵌入 ─┐
元数据挂块 ──┘(含 metadata) ├→ ANN 召回 top-k
├→(可选)混合检索/BM25
├→ rerank 精排 → top-n
└→ 拼"参考资料"区进提示词 → LLM 生成2.1 切分(chunking)
文档先切块再嵌入。块太大:一个向量要概括太多主题,嵌入变糊,且塞进上下文浪费 token。块太小:语义不完整,召回回来没用。决策点三个:
- 粒度:固定长度(实现最简,语义易截断)。→ 按段落/标题的结构感知切分(Markdown 按标题层级,PDF 按版式)。→ 语义切分(用嵌入相似度找自然断点,成本高)。结构化程度越高的语料越该用结构感知切分;
- 重叠:相邻块留 10%~20% 重叠,防止关键句正好骑在边界上;
- 元数据:块上挂 source/标题/页码/时间等 metadata——检索后过滤、引用溯源全靠它,切分时不上钩后面补不回来。
经验区间(起点值,不是教条):块大小 256~1024 token、重叠 10%~20%。调优信号看两头——检索评测里"该召回没召回"的案例多为块过大(主题混杂,嵌入糊了)。"召回了但没用"多为块过小(语义残缺)。切分策略是五段里最影响最终质量的环节,却最常被一行 chunk_size=500 带过。单段展开见 RAG切分策略实战。
2.2 嵌入(embedding)
嵌入模型把文本压成定长向量,语义相近则向量距离近。选型看三点:中英双语能力(国产 bge-m3 / GLM 系嵌入对中文明显更好)。维度(维度高表达强但存储和检索贵)。指令敏感性(有的模型检索/分类要不同前缀)。度量上惯例是余弦相似度(只比方向不看模长,归一化后与内积等价)。向量库选内积还是余弦要和嵌入模型的训练目标对齐,配错会整体掉召回。
一个必须记住的纪律:入库模型和查询模型必须是同一个(或官方声明兼容的)。向量空间不同,余弦相似度就没有意义。这也是 RAG 升级时最贵的隐性成本:换嵌入模型 = 全量重建索引。
2.3 向量库选型
| 选项 | 本质 | 何时选 |
|---|---|---|
| pgvector | Postgres 插件 | 已有 PG、不超过千万级向量、不想加组件——个人/中小项目的默认答案 |
| Milvus | 专用向量数据库 | 亿级向量、需要分片/标量过滤/高 QPS |
| ES / OpenSearch | 搜索引擎加向量能力 | 已有 ES、要 BM25+向量混合检索 |
| FAISS | 进程内库,不是服务 | 实验/单机批量离线,无并发和多租户 |
选型句式(面试可直接用):"向量检索本身用 HNSW 这类 ANN 索引都够快,选型差异在运维边界。pgvector 把向量并进既有事务和备份体系,Milvus 用独立集群换规模上限。量级没到,加组件是负资产。"选型纵深见 向量检索选型-pgvector-Redis-Milvus-Qdrant。
2.4 检索与重排
- 召回:查询向量化 → ANN 取 top-k(k 取 20~50,宁滥勿缺,给重排留料)。ANN 索引主流是 HNSW(分层跳表式图索引,牺牲一点召回率换数量级的速度)。参数 m/efConstruction 控制图的密度与建图质量。面试提一句"我们知道 ANN 是近似而非精确,评测时才要盯召回率"即可;
- 重排(rerank):cross-encoder 对"查询-文档对"逐对精细打分,精度高但贵,只对 top-k 做;双塔嵌入是离线可缓存、在线快但粗。粗召回 + 精重排是标准分工,性价比最高的单点升级通常就是"加一个 rerank";
- 混合检索:关键词(BM25)补语义检索。指标名、药品名这类精确词,向量会把它和不相关的"语义相近"内容混在一起,词法匹配才是对的。融合用 RRF(倒数排名合并)简单稳健;
- metadata 过滤:时间/成员/类别这类硬条件放在向量检索之前或之中,不留给概率。过滤和排序的先后是正确性问题不是性能问题。先排序后过滤可能把该过滤的排进 top-k,把真正需要的挤出去。
2.5 评测
没有评测的 RAG 是玄学。两层:
- 检索层:金标准问题集(每个问题标注应命中的块)。看召回率@k(该找到的找到了没)、MRR(第一个正确结果排多前)、nDCG(排序质量整体好不好)。先保证该召回的召回了,再谈生成;
- 生成层:忠实度(答案是否有检索片段支撑,防"检索对了答错了"——本质是幻觉评测)与相关性。线上再加用户反馈(点踩/引用点击)回流。
- 调参纪律:一次只动一个变量(块大小/嵌入模型/top-k/重排开关),否则归因不了。金标准集宁小勿糙——50 个标注认真的问题胜过 500 个自动生成的糙问题。
三、诚实定位:ai-service 的报告链路不是 RAG
先摆链路再下结论。app/agents/report_ocr.py:视觉模型(glm-4v-flash)把体检报告照片提取成结构化 JSON(看不清用 null)。Java 侧确认修正后入库。app/agents/report_interpret.py:把某次报告的全量指标渲染成行式清单,连同系统提示词一次性送进文本模型(glm-4.5-air)解读:
python
chain = ChatPromptTemplate.from_messages([
("system", SYSTEM_PROMPT),
("human", "报告:{report_desc},共 {count} 项指标:\n{indicators}"),
]) | chat_model() | StrOutputParser()一次调用、零检索、零向量库。全程没有检索环节——不是没做,是不需要:
- 数据本来就是结构化的。RAG 五段是为"非结构化语料"设计的。文档要切分(因为没有结构)。要嵌入(因为要语义匹配)。要检索(因为语料量超过窗口)。体检报告经过 OCR 落库后是
indicator行(名称/数值/单位/参考区间/abnormalFlag)。结构化数据不存在"切分"问题,取数用 SQL 就是最优检索; - 量级塞得下。一次体检几十项指标,渲染成清单几百 token。这是上下文窗口的零头(对照姊妹篇第二节"小输入三收益");
- 不需要从海量语料找证据。解读的依据就是"这一份报告",全量即权威。检索反而引入"漏召回"这个新失败模式。
由此提炼**"不需要 RAG"的判据**(面试反问面试官会眼前一亮)。判据有三。输入数据小而全(一次装得下)。数据结构化且权威(数据库直查比语义检索准)。答案是渲染/推理而非跨语料综合(工具调用或直塞提示词即可)。
为了 RAG 而 RAG 的代价清单:向量库多一个要运维的组件、嵌入一致性约束、检索失败模式、评测集建设。每一项都是真实成本。star-site 里需要"找数据"的场景(查家庭成员、查账单)走的是另一条路。agents/assistant.py 的 ReAct 工具调用,让模型按需调 SQL/接口。实时业务数据用工具,积累的知识语料用 RAG,一次性小数据直塞提示词,三分法各归其位。
四、假想升级路径:家庭健康场景的历年对比 RAG
推演真到需要 RAG 的那天的样子。具体场景:用户第 5 年体检时问"我血压这几年趋势怎么样,跟我爸的病史比要注意什么"。输入不再是一份报告,而是5 年报告 × 全家庭多成员 × 病史/用药/生活记录。总量超窗口,且要按"当前成员、当前指标"筛选。这才是 RAG 的形状,但注意它与标准 RAG 的分叉:
- "切分"退化为"按记录切":入库单位是数据库里已有的行(报告-指标级),不是把文档切块。结构化数据的 RAG 第一步是放弃文档思维,直接把行文本化(复用
_render_indicators的行式格式)。一行天然就是一个"语义完整、大小合适"的块; - 嵌入服务于语义模糊的问法。用户说"血压"要能召回"收缩压/舒张压"。"血糖"召回"空腹血糖/糖化血红蛋白"。同义/上下位词是嵌入的主场;指标名精确匹配则交给 BM25 或词法索引,混合检索;
- metadata 硬过滤是第一道闸:member_id、年份范围、指标分组先过滤再排序。"我"的历年数据绝不能混进家人的——这是比检索精度严重得多的正确性问题。行级入库意味着 metadata 天然齐整,近似一张现成的索引表:
向量列(行文本的嵌入) | item_name | member_id | report_year | item_group | 指标数值/单位/参考区间- 评测集反而好建:结构化数据可以直接程序化生成金标准。"问某成员某指标趋势"应召回哪些行,SQL 就能算出 ground truth。比人工标注文档语料便宜一个量级;
- 生成侧复用现有资产:检索出的行拼进 human 消息的"参考资料"区。系统提示词加一句"仅基于以下资料回答,资料没有就说没有"。与现有解读链路的提示词骨架(300 字 + 免责声明)无缝衔接,改动面极小。
这条路径没立项,但它给出了从"结构化输入 + LLM"平滑长出 RAG 的边界条件。当"相关数据"开始装不下、且"相关性"需要语义判断时,才值得引入五段全套。
追问链
面试官:你们项目的 RAG 是怎么做的?
第一句先校准:报告解读链路刻意没上 RAG——报告指标是结构化的、单次全量塞得下。SQL 直查加提示词直塞是最优解;真正需要检索的实时数据走 ReAct 工具调用。然后把第四节当"如果做会怎么做"讲——证明不是不会,是判断过不该。
面试官:为什么不用微调把医学知识训进模型?
事实注入用微调是错配:知识更新要重训、不可溯源、还会灾难性遗忘。微调的正当场景是习得输出风格和格式。比如把"不超过 300 字、无 Markdown"变成肌肉记忆。事实一律走上下文。
面试官:检索回来的片段,模型不信怎么办(幻觉还在)?
三层。第一层:提示词里把片段放进明确分隔的"参考资料"区。要求"仅基于以下资料回答,资料没有就说没有"(吴恩达课程第一条的落地,见姊妹篇)。第二层:忠实度评测把"答案是否可溯源到片段"变成回归指标。第三层:引用输出(每个结论带来源)让问题至少可见。
面试官:pgvector 和 Milvus 我该怎么选?
先问量级和已有设施。量级不超过千万且已有 Postgres:pgvector(运维零新增、事务/备份复用)。亿级或高并发标量过滤:Milvus。答"量级没到不加组件"这句判断本身,比报菜名加分。
面试官:检索质量差,你的排查顺序是什么?
先分层归因再动手。看召回层:该召回的块在不在 top-k——不在,往切分和嵌入查:块糊了还是向量空间不对。看排序层:在但排太低,上 rerank 或混合检索。看生成层:召回了但答案没用上,查提示词的参考资料区约束。顺序是"检索对了没有 → 排对了没有 → 用对了没有",别上来就调 chunk_size。
面试官视角小结
- 定位句:RAG = 用检索把私有/时效知识临时注入上下文,可溯源、更新便宜。代价是把失败模式换成"检索没找到";
- 五段决策点:切分看结构感知 + 重叠 + metadata 上钩。嵌入看中文能力 + 入库查询同模型。向量库看运维边界(pgvector 默认、Milvus 上规模)。检索是 ANN 粗召回 + cross-encoder 精重排 + 混合检索补词法。评测先检索层(召回率@k)后生成层(忠实度);
- 反 RAG 判据(本文核心竞争力):数据小而全、结构化权威、答案是渲染不是综合。三判据命中就直塞提示词或走工具调用。ai-service 的 ocr→interpret 链路是教科书案例;
- 升级路径:结构化数据的 RAG 是"行级入库 + 语义召回 + metadata 硬过滤"。切分环节退化,评测可用 SQL 生成金标准;
- 联动:为什么小输入好(注意力/成本)见 LLM 基础篇。检索片段的提示词约束见 Prompt 篇。
2026-10 新增(P3 AI 栈 · 素材来自 ai-service)。