在构建检索增强生成(RAG)系统时,幻觉(Hallucination)与答案质量不稳定是最常见也最棘手的挑战。本文将从“数据 -> 检索 -> 生成 -> 约束 -> 观测”五个层面,构建一个可迭代、可量化、可回溯的质量提升闭环,并提供从最小可行集到进阶策略的实践路径。

质量问题分层模型

首先,我们可以将 RAG 系统中的质量问题归纳为以下几个层面,以便系统性地进行诊断和优化。

层级 典型症状 根因类别 优先级
语料层 回答内容过时或不覆盖 内容缺失、未标准化
切块层 回答引用不聚焦,上下文割裂 语义边界错误、块过长
检索层 召回结果偏题或包含大量噪音 topK 不当、缺少重排
生成层 凭空编造事实,回答结构混乱 指令不明确、上下文冗余
约束层 答案可信度无法判断 缺乏引用验证与置信度评估
观测层 同类问题反复出现,无改进方向 缺乏监控指标与反馈分类

一、 语料与预处理

高质量的语料是 RAG 系统的基石。源头数据的质量直接决定了后续所有环节的上限。

  • 内容标准化:统一专业术语和别名(例如,将 “K8s” 统一映射为 “Kubernetes”),并移除文档中的噪声信息,如版权声明、导航栏和不必要的空段落。
  • 时效性控制:为文档增加 lastReviewed(最后审查日期)等元数据字段,在检索时可以根据时效性调整排序优先级,确保用户获取最新的信息。
  • 元数据增强:为每个数据块(Chunk)添加丰富的元数据,如 source (来源)、section (章节)、lang (语言)、version (版本) 和 hash (内容哈希值)。
  • 变更检测:通过计算文件内容的哈希值来检测变更。一旦哈希值发生变化,就触发增量嵌入(Embedding)流程,并使用 upsert 方式更新向量数据库中的旧向量。

二、 切块策略 (Chunking)

合理的切块策略能够确保检索到的上下文既完整又精确。

维度 建议 说明
长度 300-500 中文字符 / 200-400 tokens 在语义完整性与召回精度之间取得平衡。过长会引入噪音,过短则丢失上下文。
边界 沿标题、列表、段落、代码块对齐 避免句子被强行截断,从而破坏其原始语义结构。
重叠 0 或 50-80 tokens 当上下文存在跨段落依赖时,设置适度的重叠区(Overlap)可以防止关键信息丢失。
内容压缩 (可选)生成“摘要向量” 针对概念型问答,可以为长文档生成摘要并对其进行嵌入,用于二次召回。

三、 检索与重排

检索环节的目标是高效地从海量数据中找出与用户问题最相关的上下文。

  • 向量检索:作为主要召回方式,通常使用余弦相似度计算,返回 topK 个结果(建议 K=8,或根据问题类型动态设置在 5-10 之间)。
  • 语义重排 (Rerank):使用交叉编码器(Cross-encoder)模型或第三方 Rerank API 对初步召回的 Top 15-20 个结果进行二次排序,最终筛选出最相关的 Top 5。
  • 混合检索:结合向量检索的语义相似性和传统关键词检索(如 BM25)的精确匹配能力。将两路结果合并去重后,再进行重排。
  • 语义分类路由:根据用户问题的意图(如“定义类”、“步骤类”、“比较类”)设置不同的检索策略和 topK 值。
  • 过滤策略
    • 设置一个最小相似度阈值,低于该阈值的召回结果将被直接丢弃。
    • 对来自同一篇长文档的多个邻近结果进行合并,以减少上下文冗余。
// 相似度阈值过滤示例
const MIN_SIMILARITY = 0.70; // 低于此阈值的结果被认为是无关的

function filterBySimilarity(results) {
  return results.filter(r => r.score >= MIN_SIMILARITY);
}

四、 提示工程 (Prompt Engineering)

一个结构清晰、指令明确的 Prompt 是引导大模型生成高质量答案的关键。

  • 结构化模板:将 Prompt 拆分为不同部分,明确模型的角色、能力边界、拒答条件和输出格式。
System:
你是一个严谨的技术问答助手。你的任务是仅根据下面提供的上下文信息来回答用户问题。
- 禁止利用你的内部知识库进行回答。
- 如果上下文没有提供足够的信息,请直接回答 "根据已有信息,无法回答该问题"。
- 答案必须包含引用标记,如 [1] [2]。

Context:
[doc1] 这是第一份文档的内容...
[doc2] 这是第二份文档的内容...

Instruction:
请基于上方的上下文,回答用户的问题。

- 直接回答问题。
- 如果答案综合了多个来源,请在对应位置按 [1][3] 的格式标注引用。
- 在回答的末尾,以“引用来源:”为标题,列出所有引用的文档编号。

Language:
请使用与用户提问相同的语言进行回答。
  • 引用编号要求:强制模型在生成答案时附上引用编号,这为后续进行自动化事实校验提供了基础。

五、 生成约束与后处理

在模型生成答案后,增加一道校验程序,可以有效拦截不准确或伪造的信息。

机制 目标 实现要点
引用完整性校验 避免无依据的陈述 检测生成的答案中 [\d+] 格式的引用标记是否至少出现一次。
引用存在性核验 防止伪造引用来源 解析答案中的引用编号,校验其是否在本次检索返回的上下文块 ID 范围之内。
事实回查 (可选) 在高风险场景下进行二次验证 对生成的关键句子提取主谓宾三元组,再次进行向量检索比对,验证其一致性。
置信度打分 对答案的可信度进行分级 综合平均相似度 (avgSim)、最大相似度 (maxSim) 和上下文覆盖率 (coverage) 等指标计算一个综合置信度分数。
降级策略 防止误导用户 当答案的置信度过低时,不直接展示答案,而是输出一个安全模板,如“该问题可能超出知识库范围,建议您尝试其他问法”。
去重与合并 减少答案中的重复语句 基于 Jaccard 相似度或句向量余弦相似度(如阈值 > 0.85)对生成内容中的句子进行去重。

六、 监控与反馈闭环

建立一套完善的监控指标和反馈机制,是实现系统持续迭代优化的前提。

核心监控指标

指标 描述 采集方式
similarity_dist 召回 Top K 结果的相似度分数分布 在检索阶段收集
citation_count 每个回答中引用的上下文块数量 对生成内容进行解析统计
citation_match_rate 引用命中率(引用的编号是否真实存在) 对比引用编号与检索到的块 ID 集合
refusal_rate 拒答占比(模型选择不回答的比例) 对答案进行模板分类统计
followup_rate 用户追问率 分析会话日志中的连续用户提问
hallucination_flags 人工标注的幻觉数量 通过用户反馈面板或内部质检收集

建立反馈闭环

  1. 采样失败会话:定期筛选分析失败案例,例如:
    • 所有召回结果的相似度都非常低。
    • 答案中没有包含任何引用。
    • 被用户或内部人员标记为“幻觉”。
  2. 分类失败原因:为失败案例打上具体标签,如:
    • NO_RECALL:无相关内容召回
    • BAD_RERANK:重排后相关内容被过滤
    • PROMPT_FAIL:模型未遵循 Prompt 指令
    • OVERGEN:过度生成,回答冗长或偏离主题
    • NEED_CONTENT:知识库内容缺失
  3. 执行修复动作:根据失败标签,采取针对性措施:
    • NO_RECALL → 扩充语料,或适当调高召回 topK 值。
    • BAD_RERANK → 调整重排模型或其使用的特征。
    • PROMPT_FAIL → 优化或简化 Prompt 指令模板。
    • OVERGEN → 限制最大输出长度,或在 Prompt 中强化格式要求。
    • NEED_CONTENT → 定向补充缺失的文档,并触发增量嵌入。

实践路径:从 MVP 到进阶

最小可行实践集 (MVP Hardening)

对于刚起步的 RAG 项目,建议优先实施以下几项低成本、高收益的措施。

类别 措施 成本 影响
检索 动态 topK + 相似度阈值过滤 减少噪音,提升精准度
生成 结构化 Prompt + 强制引用编号 提升答案的可追溯性
约束 引用存在性校验 + 至少包含 1 条引用 显著减少低级幻觉
观测 记录相似度分布与引用统计数据 为问题诊断提供依据
反馈 手工标注 20 个典型失败会话 为系统迭代建立基础认知

进阶增强策略

当基础能力稳定后,可以探索以下高级策略来进一步提升系统质量。

  • Rerank 模型自蒸馏:使用一个高性能、高成本的大模型(如 GPT-4)对海量查询进行离线重排,将结果作为“教师”数据,来训练一个轻量级的交叉编码器模型。
  • 检索多样性:采用 MMR (Maximal Marginal Relevance) 算法或基于覆盖率的去重策略,避免召回的上下文块内容高度同质化,从而挤占宝贵的上下文窗口。
  • 语义图谱:从文档中抽取实体和关系,构建知识图谱。在问答时,可以利用图谱进行更精确的实体对齐和事实关系验证。
  • 答案自我反思链 (Self-reflection):让模型生成第一轮答案后,再引导它进行自我检查(如“请检查上述回答中的每个陈述是否都有明确的引用支持?”、“是否存在逻辑矛盾?”),并根据反思结果生成第二轮更可靠的答案。
  • 统一评分服务:构建一个集中的服务来计算所有质量指标,并将结果回写到日志系统中,用于构建监控仪表盘和设置告警。

核心代码示例

以下是构建 Prompt 和校验答案引用的简化版 TypeScript 代码示例。

构建提示

interface RetrievedBlock {
  text: string;
  // ... other metadata
}

function buildPrompt(blocks: RetrievedBlock[], question: string): string {
  const context = blocks
    .map((b, i) => `[doc${i + 1}] ${b.text}`)
    .join('\n\n');

  return `System: 你是严谨的技术问答助手...(此处省略详细指令)
Context:
${context}

Instruction: 基于上述内容回答:${question}`;
}

校验答案引用

function validateAnswer(answer: string, retrievedBlockCount: number) {
  // 使用正则表达式匹配所有 [d+] 格式的引用
  const citedIndices = new Set(
    [...answer.matchAll(/\[(\d+)\]/g)].map(match => Number(match[1]))
  );

  if (citedIndices.size === 0) {
    return { isValid: false, reason: 'No citations found' };
  }

  // 检查所有引用编号是否在有效范围内 (1 to blockCount)
  const allCitationsAreValid = [...citedIndices].every(
    n => n >= 1 && n <= retrievedBlockCount
  );

  if (!allCitationsAreValid) {
    return { isValid: false, reason: 'Invalid citation index' };
  }

  return { isValid: true, cited: [...citedIndices] };
}

总结

通过分层拆解问题、应用结构化提示、实施引用约束、建立可观测指标和打造反馈闭环,我们可以将 RAG 系统的幻觉控制从一门“玄学”调参,转变为一条清晰的“工程化运营”路径。建议从最小可行集入手,然后根据业务反馈和监控数据,按优先级迭代增强,从而持续压缩幻觉的发生概率,稳步提升答案的整体可信度。


👉 如果你需要 ChatGPT 代充 / Claude / Claude Code / 镜像 / 中转 API