第 20 章 高级 RAG 技术与检索优化

本章目标

到第 19 章为止,KnowHub 已经完成了一套基础企业级 RAG 平台的主要链路。

用户可以:

text 复制代码
上传文档
-> 文档解析
-> 文本切片
-> Embedding 向量化
-> pgvector 存储
-> 向量检索
-> RAG 问答
-> 查看引用来源
-> 记录 qa_log

这说明系统已经"能跑"。

但 RAG 项目还有一个更难的问题:

text 复制代码
能跑,不等于答得好。

很多 RAG 系统第一版跑通后,会出现这些情况:

  • 明明知识库里有答案,却检索不到。
  • 检索到了文档,但片段不够准确。
  • TopK 返回了很多内容,但有一半无关。
  • 问答答案看起来流畅,但引用支撑不充分。
  • 用户问得很短,系统不知道该搜什么。
  • 文档更新后,旧向量和新文档不一致。
  • 模型回答了,但没有识别"资料中没有答案"。

这些问题不是简单换一个大模型就能解决。

RAG 的质量来自整条链路:

text 复制代码
文档质量
-> 切片质量
-> 向量质量
-> 召回质量
-> 排序质量
-> Prompt 约束
-> 模型生成
-> 引用校验

第 20 章要讲的就是:基础 RAG 完成后,如何继续优化检索质量和回答质量。

本章会严格区分两个层次:

  1. KnowHub 当前已经实现的基础能力。
  2. 后续可以演进的高级 RAG 能力。

当前项目已经实现了段落切片、overlap、Embedding、pgvector 余弦检索、TopK、相似度阈值、Prompt 引用约束、无结果不调用模型、qa_log 和引用记录。混合检索、rerank、查询改写、多轮对话记忆、答案后校验等内容,本章会作为高级演进方向讲解,不写成当前已经完成的代码。


20.1 为什么基础 RAG 还需要优化

先看一个很常见的现象。

你上传了一份项目文档,然后问:

text 复制代码
这个系统怎么做权限隔离?

系统可能会返回正确答案。

但如果你换一种问法:

text 复制代码
A 用户能不能看到 B 用户的数据?

系统可能就检索不到最相关的片段。

为什么?

因为用户问法和文档原文不一定一致。

文档里可能写的是:

text 复制代码
Gateway 透传 X-User-Id,下游服务基于当前用户做资源归属校验。

用户问的是:

text 复制代码
A 用户能不能看到 B 用户的数据?

这两个句子意思接近,但词面差异较大。

RAG 优化就是要解决这类问题。

它不是只追求"能返回一个答案",而是追求:

text 复制代码
更容易找到正确资料
更少召回无关资料
更稳定地拒绝无法回答的问题
更清楚地展示引用来源
更容易在文档更新后保持一致

所以第 20 章其实是在回答一个高级问题:

text 复制代码
RAG 项目从 demo 走向更可靠系统,应该优化哪些地方?

RAG 常见问题速查表

遇到 RAG 效果问题时,不要一上来就改代码。先把症状说清楚,再判断问题在检索侧、模型侧,还是数据侧。

症状 可能原因 优先排查 优化方案 对应章节
检索到 0 个结果 阈值太高、文档未索引、问题与文档无关、Embedding 模型更换后维度不一致 查看 search 页的 resultCountdocument_chunk_vector 是否有数据、检查 similarity 降低阈值、确认索引状态、重新生成向量 20.5 调优、20.6 元数据过滤
检索到了结果但答案不准 TopK 太大引入噪声、检索到的片段本身不准确、Prompt 约束不够严格 观察 search 页各片段相似度和内容是否相关 调整 TopK、增加 rerank、强化 Prompt 约束 20.5 TopK、20.8 rerank、20.10 Prompt
答案流畅但引用支撑不足 Prompt 允许模型在不参考资料时仍回答、引用约束不够严格 检查回答中的结论是否在引用的 content 中有原文对应 强制引用编号、低置信度提示、答案后校验 20.10 Prompt、20.12 答案后校验
用户短问题或模糊问题检索差 原始问题语义不够明确、缺少上下文 检查原始问题是否可以改写成更完整的表述 增加查询改写、多路召回、引导用户补充上下文 20.9 查询改写
精确编号或代码检索不到 纯向量检索对精确字符串匹配弱 检查是否应该添加关键词检索做补充 增加关键词检索,与向量结果合并去重 20.7 混合检索
检索耗时很长 pgvector 索引未创建、数据量大但未执行 ANALYZE、向量维度不一致 查看 retrievalCostTimeMs、检查 pgvector 索引状态 控制候选池规模、创建 ivfflat/hnsw 索引、执行 ANALYZE 20.8 rerank 候选池控制、第 11 章 ivfflat 索引
模型回答偶尔降级 模型服务不稳定、超时配置太短、网络抖动 查看 modelFallback 比例、model_error_message 调整 timeout、检查模型服务、优化 Prompt 长度 第 14 章 Sentinel 降级
相同问题不同时间答案不一致 模型生成有随机性、文档更新后旧向量未清理、不同用户权限看到不同数据 对比两次检索的命中片段是否一致 降低 temperature、清理旧向量、确认权限过滤条件 20.13 增量索引
多轮追问时系统不理解指代 当前是单轮问答、缺少会话历史 检查是否存在会话表和消息表、是否存在多轮对话能力 增加会话历史、历史摘要、带上下文的查询改写 20.11 多轮对话记忆
文档更新后仍回答旧内容 旧 chunk 或旧向量没有删除、索引任务没有重新执行 检查文档版本、chunk 表和向量表是否同步 引入版本号、删除旧向量、执行增量索引 20.13 增量索引
Prompt 很长但答案仍不稳定 上下文里无关内容太多、关键片段排序靠后 查看最终进入 Prompt 的 references 顺序和内容 控制 TopK、增加 rerank、截断低相关上下文 20.8 rerank、20.10 Prompt
用户觉得"答非所问" 检索命中与问题意图不一致、问题本身有歧义 对比原问题、改写问题和召回片段 增加问题澄清、查询改写、用户反馈入口 20.9 查询改写、20.14 评估

总提示:遇到问题先看 qa_log 表里的 modelSuccessmodelFallbackretrievalCostTimeMsmodelCostTimeMs 四个字段。先判断问题在检索侧还是模型侧,再对照本章对应小节找方案。


20.2 当前 KnowHub 已经具备的基础能力

先不要急着讲高级技术。

我们先看当前 KnowHub 已经做到什么。

文档解析和切片

KnowHub 支持 TXT、Markdown、PDF 解析。

解析后的文本会进入 TextChunker

当前切片配置是:

text 复制代码
maxLength = 800
overlapLength = 120

也就是说,每个切片最大约 800 个字符,相邻切片之间保留一部分重叠内容。

文本归一化

TextChunker 会做一些清洗:

  • 把 Windows 换行统一成 \n
  • 把 tab 替换成空格。
  • 去掉部分控制字符。
  • 合并多余空格。
  • 合并过多空行。

这一步很基础,但很重要。

如果文本里有大量脏字符,后续切片和向量化效果都会受影响。

向量化和 pgvector

KnowHub 使用 embedding 模型把文本变成向量。

当前向量维度是:

text 复制代码
1024

pgvector 表中对应字段是:

text 复制代码
embedding vector(1024)

向量检索时,系统使用余弦距离相关计算:

sql 复制代码
1 - (embedding <=> ?::vector) AS similarity

结果按距离排序,返回最相似的切片。

TopK 和相似度阈值

当前默认配置是:

text 复制代码
topK = 5
similarityThreshold = 0.30

PgVectorUtils 还会做保护:

  • TopK 最小为 1。
  • TopK 最大为 20。
  • 相似度阈值限制在 -1 到 1。

这可以防止用户传一个特别大的 TopK,把系统一次拖垮。

Prompt 引用约束

当前 Prompt 要求模型:

text 复制代码
严格基于给定资料回答。
资料中没有答案时,回复无法确定。
答案末尾标明引用。

这说明 KnowHub 已经不是直接把问题丢给模型,而是把检索结果作为上下文交给模型。

日志和引用记录

问答结果会记录:

  • qaLogId
  • 检索耗时
  • 模型耗时
  • 总耗时
  • modelSuccess
  • modelFallback
  • 引用来源

这为后续优化提供了数据基础。

没有日志,就很难知道到底是检索差还是模型差。


20.3 RAG 效果取决于五个环节

很多人优化 RAG 时,一上来就想换模型。

但 RAG 效果不好,原因可能不在模型。

可以先用五个环节分析。

1. 文档质量

如果原文很乱,RAG 很难答好。

比如:

  • PDF 解析出来段落错乱。
  • 表格内容丢失。
  • 标题和正文混在一起。
  • 代码块被截断。
  • 文档中本来就没有答案。

这些问题不是向量数据库能解决的。

2. 切片质量

切片太短,信息不完整。

切片太长,噪声太多。

切片位置不合理,答案可能被切到两个片段里。

3. 召回质量

召回质量指的是:

text 复制代码
系统有没有把真正相关的片段找出来。

如果召回失败,模型没有资料可用。

4. 排序质量

召回到了相关内容,但最相关的片段没有排在前面,也会影响结果。

因为 Prompt 长度有限,通常只能把前几个片段交给模型。

5. 生成约束

即使检索正确,prompt 没写好,模型也可能:

  • 编造。
  • 忽略引用。
  • 回答过长。
  • 没有说明无法确定。

所以 RAG 优化不是单点优化,而是链路优化。


当前能力 vs. 高级能力

前面 19 章已经搭建了一个完整的 RAG 平台,本章不是推倒重来,而是在已有基础上继续优化。

能力维度 KnowHub 当前已实现 本章所述高级演进方向
切片 段落切片 + overlap,控制 maxLengthoverlapLength Markdown 标题层级切片、表格感知、代码块整体保留、页码来源保留
检索 单路向量检索 + TopK + 相似度阈值 混合检索、多路召回、元数据过滤、查询改写
排序 Embedding 余弦相似度排序 rerank 重排序,把更相关的片段放到 Prompt 前面
Prompt 四条基础约束:基于资料、无法确定、不编造、标明引用 六段式 Prompt、引用强制标注、低置信度提示、上下文长度控制
对话 单轮问答,每次问题独立检索 多轮对话记忆、历史摘要、指代消解、带上下文的查询改写
评估 qa_log 记录耗时、状态和引用 标准问题集、自动评估、用户反馈闭环、质量报表

每项高级能力都建立在对应基础能力之上。比如没有稳定的 qa_log,就很难做质量评估;没有基础向量检索,就谈不上 rerank;没有可靠的切片和来源记录,Prompt 再复杂也无法保证引用可信。


20.4 切片策略优化

当前 KnowHub 的切片策略比较适合教学和基础项目。

它做了段落切分、长度限制和 overlap。

但在真实知识库里,文档结构会更复杂。

固定长度切片的问题

最简单的切片方式是固定长度切片。

比如每 800 个字符切一次。

这种方式容易实现,但有问题:

text 复制代码
可能把一个完整语义切断。

例如一段话前半部分说"系统采用 gateway 鉴权",后半部分说"下游服务只信任 X-User-ID"。

如果切片正好从中间切开,检索结果可能只拿到半句话。

段落切片的优点

Knowhub 当前会先按空行拆段落,再组合成 chunk。

这比纯固定长度更好。

因为自然段通常包含一个相对完整的意思。

Overlap 的价值

Overlap 就是重叠。

它的作用是避免关键上下文在切片边界处丢失。

比如一个 chunk 末尾说:

text 复制代码
Gateway 校验 Token 后会透传

下一个 chunk 开头说:

text 复制代码
X-User-Id 给下游服务

如果没有 overlap,两句话可能分离。

有了 overlap,后一个 chunk 会带一点前文,更容易被检索出来。

后续可优化方向

后续可以支持更结构化的切片:

  • Markdown 标题层级切片。
  • 保留标题路径,比如"第 4 章 / Gateway 鉴权 / 用户透传"。
  • 表格按行或按块处理。
  • 代码块整体保留。
  • PDF 按页码和段落保留来源。
  • 超长章节先按标题拆,再按段落拆。

这些优化的目标是:

text 复制代码
让每个 chunk 尽量语义完整,并且带足够来源信息。

切片策略对比示例

假设原文是一段关于 Gateway 鉴权的技术描述:

text 复制代码
Gateway 收到请求后,首先校验 Authorization 中的 Token。Token 校验通过后,系统会解析出 userId、username 和 role。随后 Gateway 会删除外部传入的伪造用户头,并重新写入可信的 X-User-Id 和 X-User-Role。下游服务只读取 Gateway 写入的用户身份,不信任前端自己提交的 userId 参数。

如果使用固定长度切片,假设 chunkSize=150,结果可能是:

text 复制代码
chunk_0:Gateway 收到请求后,首先校验 Authorization 中的 Token。Token 校验通过后,系统会解析
chunk_1:出 userId、username 和 role。随后 Gateway 会删除外部传入的伪造用户头,并重新写入可信的 X-User-Id
chunk_2:和 X-User-Role。下游服务只读取 Gateway 写入的用户身份,不信任前端自己提交的 userId 参数。

可以看到,chunk_0chunk_1 都有语义截断。用户问"下游服务为什么不能信任前端 userId"时,系统可能只召回到半段信息。

如果按段落切片,结果是:

text 复制代码
chunk_0:Gateway 校验 Token -> 解析用户身份 -> 删除伪造请求头 -> 写入 X-User-Id 和 X-User-Role -> 下游服务读取可信身份。

这一个 chunk 就包含完整语义链路。

结论是:固定长度切片适合教学和理解原理,段落切片更适合语义检索。优化切片策略时,目标是让每个 chunk 尽量自包含,也就是不依赖上下文也能读懂。


20.5 Top K 和相似度阈值调优

KnowHub 当前支持两个重要参数:

text 复制代码
topK
similarity threshold

它们看起来简单,但对效果影响很大。

TopK 太小

如果 TopK 太小,比如只取 1 条,很容易漏掉答案。

因为最相似的片段不一定就是最完整的片段。

TopK 太大

如果 TopK 太大,比如取 20 条,会带来两个问题:

  1. Prompt 变长,模型成本增加。
  2. 无关片段更多,模型可能被干扰。

所以 TopK 不是越大越好。

当前系统限制最大 20,是合理的保护。

阈值太高

相似度阈值太高,会导致检索结果为空。

用户会看到:

text 复制代码
知识库中未检索到相关内容,无法确定。

这不一定是文档不存在,也可能是阈值设置过严。

阈值太低

阈值太低,会召回很多弱相关内容。

模型拿到这些内容后,可能生成看似合理但依据不稳的答案。

如何调参

调参时不要凭感觉。

推荐流程:

text 复制代码
准备一组真实问题
观察 search 页返回结果
记录 resultCount 和 similarity
调整 topK
调整 threshold
再观察 chat 答案和引用

第 16 章用户端里的"向量检索"Tab 就是调参工具。

先看系统找到了什么,再看系统怎么回答。


调参实例:从无结果到可用答案

看一个具体例子。

某学生上传了一份《员工报销制度》PDF,正文大约 3000 字。文档索引成功后,他提问:

text 复制代码
发票什么时候交

初始参数是:

text 复制代码
topK=3
similarityThreshold=0.50

问答接口返回:

text 复制代码
知识库中未检索到相关内容,无法确定。

第一步,先不改代码,直接调用检索接口:

text 复制代码
GET /kb/{kbId}/search?question=发票什么时候交&topK=5&similarityThreshold=0.0

结果发现最高相似度只有 0.18。再看文档状态,chunkCount=6,说明文档确实已经索引成功。问题不在索引,而在阈值:0.50 对这个短问题来说太高。

第二步,把阈值降到 0.15。再次检索后返回 3 条结果,最高相似度仍是 0.18,但内容已经命中:

text 复制代码
发票应在开具后 30 天内提交。

这说明检索已经找到正确片段,只是相似度绝对值偏低。短问题向量信息少,similarity 偏低是常见现象。

第三步,把问答参数调整为:

text 复制代码
topK=5
similarityThreshold=0.12

调用:

text 复制代码
POST /kb/{kbId}/chat

模型成功返回答案,并在 references 中附上报销制度对应片段。

最终这个知识库的默认参数调整为:

text 复制代码
topK=5
similarityThreshold=0.12

这个例子的经验是:对于短文档或短问题,阈值不宜设太高。应该先用 search 接口观察 similarity 分布,再决定阈值,不要凭经验直接改。


20.6 元数据过滤

当前 KnowHub 已经有基础过滤条件:

text 复制代码
userId
kbId
documentId

这保证了用户隔离和知识库范围。

但更高级的 RAG 系统通常还会加入更多元数据。

比如:

  • 文件类型。
  • 文档标签。
  • 上传时间。
  • 文档版本。
  • 业务分类。
  • 章节标题。
  • 来源部门。
  • 权限等级。

为什么元数据重要?

因为向量检索只看语义相似,有时范围太大。

比如一个企业知识库里有:

text 复制代码
Java 开发规范
财务报销制度
人事制度
产品需求文档

用户问"报销流程是什么",如果不加业务分类过滤,系统可能在很多不相关文档里搜索。

加上 category=财务制度 后,候选范围更小,召回更准确。

在 KnowHub 后续演进中,可以给文档增加:

text 复制代码
tags
category
sourceType
version
publishedAt

然后在 document_chunk_vector 中同步这些字段,检索时加入 where 条件。

这就是元数据过滤。


20.7 混合检索

当前 KnowHub 主要使用向量检索。

向量检索擅长语义相似。

比如:

text 复制代码
用户问:A 用户能看到 B 用户的数据吗?
文档写:系统通过资源归属校验实现用户隔离。

这类场景向量检索有优势。

但向量检索不擅长所有场景。

比如:

  • 精确编号。
  • 错误码。
  • 表名。
  • 方法名。
  • 配置项。
  • 产品型号。

用户问:

text 复制代码
40101 是什么错误?

关键词 "40101" 非常关键。

这时关键词检索可能比纯向量更可靠。

什么是混合检索

混合检索就是同时使用:

text 复制代码
向量检索 + 关键词检索

然后把结果合并。

常见做法是:

  1. 向量检索召回一批。
  2. 关键词检索召回一批。
  3. 合并去重。
  4. 重新排序。
  5. 取最终 TopK。

在 KnowHub 中,后续可以用两种方式演进:

  • PostgreSQL 全文检索 + pgvector。
  • 引入 ElasticSearch 或 OpenSearch 做关键词检索。

当前项目还没有实现混合检索,所以教材中要把它写成后续优化方向。


20.8 rerank 重排序

召回不是最终答案。

向量检索返回的 TopK 是基于向量相似度排序,但它不一定完全符合用户问题。

rerank 的思路是:

text 复制代码
先多召回,再精排序。

比如先用向量检索取 Top20,再用 rerank 模型判断每个片段和问题的相关性,最后取 Top5。

为什么这样做?

因为第一阶段召回要尽量不要漏掉答案。

第二阶段重排再把最相关的内容放前面。

Rerank 适合解决什么问题

  • Top K 里有无关内容。
  • 相似度高但不回答问题。
  • 片段语义接近但重点不同。
  • 需要更稳定的引用排序。

KnowHub 如何演进

后续可以增加一个 Rerank Service:

text 复制代码
输入:question + candidate chunks
输出:重新排序后的 chunks

调用位置可以放在:

text 复制代码
DocumentVectorService.search 之后
KnowledgeChatService 拼 Prompt 之前

这样既不破坏当前基础检索接口,也能逐步增强问答质量。


20.9 查询改写和多路召回

用户的问题经常不适合直接检索。

比如用户问:

text 复制代码
这个怎么配?

对系统来说,"这个"没有明确对象。

如果是多轮对话,用户上一轮可能说的是:

text 复制代码
我想部署到 Linux 虚拟机。

那"这个怎么配"其实是问 Linux 虚拟机部署配置。

查询改写

查询改写就是把用户问题改成更适合检索的问题。

比如把:

text 复制代码
这个怎么配?

改写成:

text 复制代码
KnowHub 部署到 Linux 虚拟机时,Docker Compose、环境变量和服务地址应该如何配置?

改写后的问题更容易召回正确文档。

多路召回

多路召回是同时使用多个查询。

比如:

  • 原始问题。
  • 改写问题。
  • 提取关键词。
  • 加上历史上下文的问题。

然后分别检索,最后合并去重。

这种方式能提高召回率,但成本也更高。

因为每一路可能都要做 Embedding 和检索。

所以要控制:

  • 查询数量。
  • 候选数量。
  • 去重逻辑。
  • 总耗时。

当前 KnowHub 是单路检索,后续可以逐步演进到多路召回。


20.10 Prompt 优化

当前 KnowHub 的 Prompt 已经有几个关键约束:

text 复制代码
严格基于给定资料回答
资料中没有答案时回复无法确定
不要编造资料中不存在的信息
回答末尾标明引用

这是一个很好的基础 Prompt。

但在更复杂场景里,还可以继续优化。

Prompt 可以拆成几部分

一个更清晰的 Prompt 通常包含:

  1. 角色:你是什么助手。
  2. 任务:你要完成什么。
  3. 资料:可用上下文是什么。
  4. 约束:不能做什么。
  5. 输出格式:用什么格式回答。
  6. 无答案策略:资料不足时怎么回答。

控制上下文长度

Prompt 不是越长越好。

塞进太多切片,会带来:

  • 成本增加。
  • 模型注意力分散。
  • 无关内容干扰。
  • 响应变慢。

所以 Prompt 优化和检索优化要一起做。

先提升检索质量,再把更少、更准的上下文交给模型。

引用格式

当前 Prompt 要求答案末尾写"引用:引用1、引用2"。

后续可以更严格:

text 复制代码
每个关键结论后都标注引用
无法从资料证明的内容不要写
引用必须来自给定资料编号

这样更适合企业知识库。


20.11 多轮对话记忆

当前 KnowHub 的主线是单轮问答。

用户问一个问题,系统检索一次,模型回答一次。

但真实用户经常会连续追问。

比如:

text 复制代码
用户:这个系统怎么做用户隔离?
助手:系统通过 Gateway 透传 X-User-Id...
用户:那 Redis 缓存会不会绕过这个隔离?

第二个问题里的"这个隔离"依赖上一轮上下文。

如果系统只看当前问题,可能无法理解。

多轮对话需要什么

要支持多轮,需要新增:

  • 会话表。
  • 消息表。
  • 历史摘要。
  • 当前上下文窗口。
  • 多轮问题改写。

检索时不能只用当前问题,可能要把历史上下文合并进去。

但也不能把所有历史都塞进去。

否则会:

  • Prompt 太长。
  • 成本太高。
  • 历史主题污染当前问题。

所以多轮对话不是简单保存聊天记录。

它需要设计上下文压缩和主题判断。


20.12 引用可信度和答案后校验

RAG 的一个优势是有引用。

但要注意:

text 复制代码
有引用,不等于答案一定正确。

模型可能引用了资料,但答案里的某些句子仍然是推断或编造。

所以高级 RAG 系统会做答案后校验。

可以校验什么

  • 答案中的关键结论是否能在引用中找到依据。
  • 引用是否真的和问题相关。
  • 引用相似度是否过低。
  • 答案是否超出了资料范围。
  • 无答案问题是否被错误回答。

低置信度提示

如果引用相似度普遍很低,可以提示:

text 复制代码
当前答案依据较弱,请结合原文确认。

这对企业场景很重要。

因为企业知识库宁愿保守,也不要一本正经地胡说。

KnowHub 如何演进

KnowHub 已经有 qa_logqa_reference

后续可以在此基础上增加:

  • 引用平均相似度。
  • 最低相似度。
  • 答案置信度。
  • 用户反馈。
  • 人工标注结果。

这些数据可以帮助持续优化 RAG 效果。


20.13 知识库更新和增量索引

知识库不是一次上传就永远不变。

真实系统中会出现:

  • 文档更新。
  • 文档删除。
  • 文档重新上传。
  • 标签修改。
  • 文件从本地迁移到对象存储。
  • 索引任务失败后重试。

当前 KnowHub 已经有向量重建接口:

text 复制代码
POST /kb/{kbId}/documents/{documentId}/vectors/rebuild

当前向量写入逻辑是:

text 复制代码
删除旧向量
-> 遍历 chunk
-> 重新 embedding
-> upsert 到 pgvector

这是很清楚的基础方案。

后续可以演进为更细粒度的增量索引:

  • 只更新变化的 chunk。
  • 删除文档时同步删除向量。
  • 文档版本号控制。
  • 索引任务去重。
  • RabbitMQ 消息驱动索引。
  • MinIO 对象路径和文档元数据保持一致。
  • 失败任务补偿和死信队列。

这时第 9 章 RabbitMQ 和第 7 章 MinIO 的价值会更明显。

RabbitMQ 负责让索引任务异步可靠执行。

MinIO 负责让原始文件可被不同服务稳定读取。


20.14 RAG 效果怎么评估

没有评估,就不知道优化有没有用。

RAG 评估可以从几个维度看。

召回命中率

准备一批问题和标准引用,看检索结果里是否包含正确片段。

引用相关性

看返回的引用是不是和问题相关。

不能只看 answer 是否流畅。

无答案识别能力

准备一些知识库里没有答案的问题。

系统应该回答:

text 复制代码
知识库中未检索到相关内容,无法确定。

而不是强行编造。

耗时

记录:

  • 检索耗时。
  • 模型耗时。
  • 总耗时。

第 18 章已经讲过,RAG 问答耗时明显高于普通接口。

降级率

统计 modelFallback=true 的比例。

如果降级率很高,说明模型服务、网络或超时配置需要检查。

用户反馈

最终还是要看用户是否满意。

可以设计:

text 复制代码
有帮助 / 没帮助
答案错误
引用不相关
缺少信息

这些反馈可以反过来优化切片、检索和 Prompt。


利用 qa_log 收集基础评估指标

前面讲评估维度,这里给出几条可以直接在 MySQL 中执行的 SQL。

前提是 qa_logqa_reference 表已经创建,详见第 12 章和第 15 章。

查询一:最近 7 天降级率统计

sql 复制代码
SELECT
  COUNT(*) AS total,
  SUM(model_fallback = 1) AS fallback_count,
  ROUND(SUM(model_fallback = 1) / COUNT(*) * 100, 1) AS fallback_pct
FROM qa_log
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY);

这条 SQL 用来判断模型服务是否稳定。如果 fallback_pct 明显升高,优先检查模型服务、网络和第 14 章的 timeout 配置。

查询二:最近 7 天成功问答的平均耗时分布

sql 复制代码
SELECT
  AVG(retrieval_cost_time_ms) AS avg_retrieval_ms,
  AVG(model_cost_time_ms) AS avg_model_ms,
  AVG(cost_time_ms) AS avg_total_ms
FROM qa_log
WHERE model_success = 1
  AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY);

这条 SQL 用来判断慢在哪里。avg_retrieval_ms 高,优先看 Embedding 和 pgvector;avg_model_ms 高,优先看 Prompt 长度和模型服务。

查询三:最近 7 天无检索结果率

sql 复制代码
SELECT
  COUNT(*) AS total,
  SUM(model_called = 0) AS no_result_count,
  ROUND(SUM(model_called = 0) / COUNT(*) * 100, 1) AS no_result_pct
FROM qa_log
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY);

这条 SQL 用来观察召回质量。如果 no_result_pct 很高,说明切片、阈值、TopK 或用户问题表达需要优化。

查询四:高频被引用的文档片段 Top10

sql 复制代码
SELECT
  c.document_name,
  c.chunk_index,
  COUNT(*) AS cited_count
FROM qa_reference r
JOIN document_chunk c ON r.chunk_id = c.id
GROUP BY c.document_name, c.chunk_index
ORDER BY cited_count DESC
LIMIT 10;

这条 SQL 可以帮助管理员发现哪些文档片段最常被引用。高频片段可能是核心制度、核心接口说明,也可能是因为某些文档写得更容易被检索命中。

优化要有前后对比。比如调低阈值之后,再看 no_result_pct 是否下降;缩短 Prompt 之后,再看 avg_model_ms 是否下降。这样才能判断优化是否有效,而不是凭感觉说"好像好了一点"。


20.15 KnowHub 的高级演进路线

不要一口气做所有高级功能。

更合理的是分阶段演进。

第一阶段:调参和切片优化

先基于现有能力做:

  • 调整 maxLength
  • 调整 overlapLength
  • 调整 TopK。
  • 调整相似度阈值。
  • 优化 Markdown 标题切片。
  • 优化 PDF 解析后的段落清洗。

这是成本最低的一阶段。

第二阶段:元数据过滤

增加:

  • 文档标签。
  • 文档分类。
  • 文档来源。
  • 文档版本。

检索时增加过滤条件。

这可以明显减少无关召回。

第三阶段:混合检索和 rerank

加入关键词检索和 reranker。

流程变成:

text 复制代码
向量召回 + 关键词召回
-> 合并去重
-> rerank
-> 最终 TopK
-> Prompt

这是提升检索质量的重要阶段。

第四阶段:查询改写和多轮对话

当用户问题更复杂时,引入:

  • 查询改写。
  • 多路召回。
  • 会话历史。
  • 历史摘要。

这会让系统更像真正的知识库助手。

第五阶段:评估体系

最后补:

  • 标准问题集。
  • 标准引用。
  • 自动评估脚本。
  • 用户反馈闭环。
  • 问答质量报表。

没有评估体系,高级优化很容易变成凭感觉调参。


20.16 本章和前面章节的关系

第 20 章不是推翻前面的基础架构,而是在基础架构上继续演进。

可以这样对应:

text 复制代码
第 8 章:文档解析与切片 -> 切片策略优化
第 10 章:Embedding -> 向量质量和查询改写
第 11 章:pgvector -> TopK、阈值、索引和召回优化
第 12 章:RAG 问答 -> Prompt、引用和答案质量
第 13 章:Redis -> 热点结果、状态和未来缓存优化
第 14 章:Sentinel -> 高成本问答保护
第 15 章:日志排障 -> qa_log 质量分析
第 16 章:用户端 -> 检索调参和问答反馈入口
第 17 章:管理端 -> 全局日志和质量观察
第 18 章:测试验证 -> 优化前后效果对比
第 19 章:部署交付 -> 高级能力上线基础
第 20 章:高级 RAG 优化 -> 让系统从能用走向更好用

基础 RAG 解决"有没有"。

高级优化解决"好不好"。


本章小结

这一章我们讲了高级 RAG 技术与检索优化。

KnowHub 当前已经具备基础 RAG 能力:文本解析、归一化、切片、overlap、Embedding、pgvector 余弦检索、TopK、相似度阈值、Prompt 引用约束、无结果不调用模型、qa_log 和引用记录。本章在这个基础上进一步讲解了切片策略优化、TopK 和阈值调优、元数据过滤、混合检索、rerank、多路召回、查询改写、多轮对话记忆、引用可信度、答案后校验、增量索引和 RAG 效果评估。

本章最重要的原则是:不要把高级名词堆在系统上,而要先找问题。检索不到,就看切片和召回;召回不准,就看阈值、TopK、元数据和 rerank;答案不可信,就看 Prompt、引用和后校验;系统变慢,就看 Embedding、pgvector、模型调用和缓存。优化必须围绕真实问题展开。

下一章,我们会进入生产环境演进、性能优化与面试表达,把前面所有章节沉淀成项目复盘、工程边界、性能优化和对外表达方式。

本章关键术语速查表

这些术语不需要都背下来,遇到对应问题时回来查即可。面试和答辩中能准确使用其中 5 到 8 个术语,已经能体现 RAG 领域的工程理解力。

术语 一句话解释
Embedding 把文本变成数字向量,让系统能计算语义相似度,详见第 10 章。
chunk / 切片 把长文档拆成多个短片段,每个片段独立向量化和检索,详见第 8 章。
TopK 召回多少个最相似片段,太小漏答案,太大引入噪声,详见本章 20.5。
相似度阈值 只保留相似度超过该值的片段,太高无结果,太低召回弱相关,详见本章 20.5。
overlap 相邻切片之间的重叠部分,防止关键信息刚好落在边界处,详见本章 20.4。
元数据过滤 通过文件类型、标签、分类等字段缩小检索范围,减少无关召回,详见本章 20.6。
混合检索 同时使用向量检索和关键词检索,互补两种检索方式的短板,详见本章 20.7。
rerank / 重排序 召回一批候选后,用更精细的模型重新排顺序,把最相关的片段放前面,详见本章 20.8。
查询改写 把用户输入的模糊或短问题改成更完整、更适合检索的表达,详见本章 20.9。
多路召回 用多个不同查询分别检索,合并结果,提高命中率,详见本章 20.9。
多轮对话 系统记住前几轮对话,理解用户当前问题中的指代,比如"这个""它",详见本章 20.11。
幻觉 大模型在没有资料依据的情况下编造听起来合理但不准确的回答,详见本章 20.12。
置信度 系统对答案可靠程度的评估,引用相似度越低,置信度越低,详见本章 20.12。
增量索引 只重新索引发生变化的文档或 chunk,而非全量重建,详见本章 20.13。

思考题

  1. 为什么基础 RAG 能跑通后,仍然可能回答不好?
  2. RAG 效果主要受哪些环节影响?
  3. KnowHub 当前 TextChunkermaxLengthoverlapLength 分别解决什么问题?
  4. TopK 太小和太大会分别带来什么问题?
  5. 相似度阈值太高和太低会分别造成什么后果?
  6. 为什么元数据过滤能提高检索质量?
  7. 向量检索和关键词检索分别擅长什么场景?
  8. rerank 为什么通常放在初次召回之后?
  9. 查询改写解决的是什么问题?
  10. 多轮对话为什么不能简单把所有历史消息都塞进 Prompt?
  11. 为什么有引用不等于答案一定正确?
  12. 如果让你给 KnowHub 设计下一阶段 RAG 优化路线,你会先做哪三件事?

思考题参考答案

1. 为什么基础 RAG 能跑通后,仍然可能回答不好?

因为"能跑"只代表链路通了,不代表链路质量高。基础 RAG 只是完成了「上传文档 -> 解析 -> 切片 -> 向量化 -> 检索 -> 问答」这条主链路,但检索是否命中、切片是否语义完整、Prompt 约束是否严格、引用是否可信,这些质量维度都没有被保证。用户问法和文档原文词面不一致时,纯向量检索可能召回不到最相关的片段;切片把完整语义切断时,模型只能拿到半句话;Prompt 约束不严时,模型可能编造或忽略引用。所以基础 RAG 跑通后,仍可能检索不到、答不准、引用支撑不足。

2. RAG 效果主要受哪些环节影响?

主要受五个环节影响:文档质量、切片质量、召回质量、排序质量、生成约束。文档本身很乱,后续环节再优化也难答好;切片太短或太长都会影响信息完整性;召回失败则模型没有资料可用;排序不好会让最相关的片段排不到前面;Prompt 约束不严,模型可能编造或忽略引用。所以 RAG 优化是链路优化,不是单点优化。

3. KnowHub 当前 TextChunkermaxLengthoverlapLength 分别解决什么问题?

maxLength 控制每个切片的最大长度,解决切片过长导致噪声太多、向量化效果变差的问题;overlapLength 控制相邻切片之间的重叠长度,解决关键上下文刚好落在切片边界处而丢失的问题。两者配合,让每个切片既不过长,又能保留边界处的上下文。

4. TopK 太小和太大会分别带来什么问题?

TopK 太小,比如只取 1 条,容易漏掉答案,因为最相似的片段不一定就是最完整的片段;TopK 太大,比如取 20 条,会带来两个问题:一是 Prompt 变长、模型成本增加,二是无关片段更多、模型可能被干扰。所以 TopK 不是越大越好,当前系统限制最大 20 是合理的保护。

5. 相似度阈值太高和太低会分别造成什么后果?

阈值太高会导致检索结果为空,用户看到「知识库中未检索到相关内容,无法确定」,这不一定是文档不存在,也可能是阈值设置过严;阈值太低会召回很多弱相关内容,模型拿到这些内容后,可能生成看似合理但依据不稳的答案。调参时应先用 search 接口观察 similarity 分布,再决定阈值。

6. 为什么元数据过滤能提高检索质量?

因为向量检索只看语义相似,有时检索范围太大。比如企业知识库里有 Java 开发规范、财务报销制度、人事制度、产品需求文档,用户问「报销流程是什么」时,如果不加业务分类过滤,系统可能在很多不相关文档里搜索。加上 category=财务制度 后,候选范围更小,召回更准确,也能减少无关内容对模型的干扰。

7. 向量检索和关键词检索分别擅长什么场景?

向量检索擅长语义相似场景,比如用户问「A 用户能不能看到 B 用户的数据」,文档写「系统通过资源归属校验实现用户隔离」,两者词面差异大但语义接近,向量检索能命中;关键词检索擅长精确匹配场景,比如精确编号、错误码、表名、方法名、配置项、产品型号,用户问「40101 是什么错误」时,关键词「40101」非常关键,关键词检索比纯向量更可靠。

8. rerank 为什么通常放在初次召回之后?

因为第一阶段召回的目标是「尽量不要漏掉答案」,所以要尽量多召回,比如取 Top20;但向量相似度排序不一定完全符合用户问题,TopK 里可能混入无关内容。rerank 放在初次召回之后,用更精细的模型对候选片段重新排序,把最相关的内容放到 Prompt 前面,既保证不遗漏,又提升排序质量。

9. 查询改写解决的是什么问题?

查询改写解决的是「用户问题不适合直接检索」的问题。用户的问题经常很短或很模糊,比如「这个怎么配?」,对系统来说「这个」没有明确对象。查询改写把这类问题改写成更完整、更适合检索的表达,比如「KnowHub 部署到 Linux 虚拟机时,Docker Compose、环境变量和服务地址应该如何配置?」,改写后的问题更容易召回正确文档。

10. 多轮对话为什么不能简单把所有历史消息都塞进 Prompt?

因为把所有历史都塞进去会带来三个问题:一是 Prompt 太长,模型成本增加;二是模型注意力分散,无关历史会干扰当前问题;三是历史主题污染当前问题,比如用户上一轮在聊部署,这一轮问「那 Redis 呢」,如果塞入大量无关历史,模型可能理解偏。所以多轮对话需要设计上下文压缩和主题判断,只保留与当前问题相关的历史。

11. 为什么有引用不等于答案一定正确?

因为模型可能引用了资料,但答案里的某些句子仍然是推断或编造。引用只能说明「模型参考了某段资料」,不能证明「答案中的每个结论都能在引用中找到依据」。所以高级 RAG 系统会做答案后校验,检查关键结论是否能在引用中找到原文对应、引用是否真的和问题相关、引用相似度是否过低、答案是否超出资料范围。

12. 如果让你给 KnowHub 设计下一阶段 RAG 优化路线,你会先做哪三件事?

我会先做这三件事:

  1. 先建立评估基线 :基于现有 qa_logqa_reference,准备一组标准问题集,统计召回命中率、无答案识别率、降级率和平均耗时,作为优化前后的对比基准。没有评估,后续优化很容易变成凭感觉调参。
  2. 调参和切片优化:这是成本最低的一步。先用 search 接口观察真实问题的 similarity 分布,调整 TopK 和相似度阈值;同时优化切片策略,让每个 chunk 尽量语义自包含,减少边界截断。
  3. 引入元数据过滤:给文档增加标签、分类、来源、版本等字段,在检索时加入过滤条件,缩小候选范围,减少无关召回。这一步实现成本低,但对检索质量的提升非常明显。

做完这三件事后,再根据评估结果决定是否进入混合检索、rerank、查询改写等更高级的演进方向。

相关推荐
维克兜率天1 小时前
【维克】正则化回归:从Lasso到Ridge:防止过拟合的数学魔法
人工智能·数据挖掘·回归
AI的探索之旅1 小时前
BOM管理的AI自动化:从原理图到采购清单,我搭了一套全自动工具链
运维·人工智能·自动化
念啊啊啊啊丶1 小时前
【工业异常异常检测】2026-ICML-CoGeoAD:基于多视图注意力机制的分层颜色几何融合零样本3D异常检测
人工智能·深度学习·神经网络·机器学习·计算机视觉
szxinmai主板定制专家1 小时前
ZYNQ高速数据采集卡设计:1M采样率实现IEPE传感器信号采集
人工智能·数据采集·zynq·rk3588+fpga·rk3576+fpga·振动采集
阳明山水1 小时前
销量预测2026下半年:新架构、新基准与新共识
人工智能·深度学习·算法·机器学习·架构
天国梦1 小时前
智习室英语学习工具选型指南:2026年品牌合作筛选方法与落地评估
大数据·人工智能
bamb001 小时前
一个项目带你入门AI应用开发06
python
xcLeigh2 小时前
中英文提示词对比:中文提示词与英文提示词的选择策略
人工智能·chatgpt
qyz_hr2 小时前
拆解AI落地路径,人力资源服务如何跨过规模化交付门槛?
人工智能