Hualin Luan Cloud Native · Quant Trading · AI Engineering
返回文章

Article

RAG质量评测与安全防护:从Rule-Based Evaluation到发布门禁

以个人知识库RAG问答为例,设计检索、引用、回答三层评估,六层安全防护,固定Evaluation Set和发布前审计门禁。

Meta

Published

2026/5/29

Category

guide

Reading Time

约 16 分钟阅读

引言:RAG能回答,不等于可以公开

前两篇文章讨论了RAG系统的架构和检索实现。但公开/chat入口真正打开前,还要回答另一个问题:系统答错时,你能不能知道错在检索、引用还是生成?

一个典型失败场景是”如何联系”。如果系统召回了过时页面,回答可能很流畅,却把用户导向错误入口。这个问题不能只靠”回答看起来不错”发现。它需要检索、引用、回答三层都留下可诊断证据。

本文给出一套面向个人知识库RAG的质量门禁:三层质量评估、固定Evaluation Set、六层安全防护、Closeout Gate和Reverse Audit。它的目标不是证明系统已经完美,而是让每次发布都有可复核证据。

适用边界:本文适合公开内容问答、站内助手和文档搜索增强回答。不覆盖内部知识库权限隔离、PII合规处理、企业审计报表或多租户访问控制。

先看质量、安全和发布证据如何汇到同一个门禁:

RAG质量评测与安全门禁示意图,展示三层质量评估、六层安全防护,以及固定Evaluation Set、Debug Report、Closeout Gate和Reverse Audit之间的关系。

图1:质量评估、安全门禁与发布闭环。它把“答得对不对”“该不该让请求继续往下游走”“这一版能不能发布”拆成三条并行但最终汇合的证据链。

三层质量评估体系

为什么需要分层评估?

传统RAG评测往往只有一个维度:“回答是否准确”。公开入口需要更细粒度的诊断能力。

示例故障:系统对”如何解决429错误”的回答流畅准确,但引用了旧版本文档。虽然回答内容本身可读,来源却已经过时。如果只测回答文本,这个问题会被掩盖。

分层评估的价值在于定位故障环节:是检索出了问题、引用出了问题,还是回答生成本身出了问题?

Layer 1: 检索质量(Retrieval Quality)

评估目标:系统是否找回了正确的外部知识

核心指标

1. 预期来源召回率

interface RetrievalTest {
  query: string;
  expectedSources: string[];      // 预期应召回的来源URL
  requiredFacts: string[];         // 回答需要覆盖的关键事实
  minRecallRate: number;           // 最低召回率阈值
}

// 测试示例
const retrievalTests: RetrievalTest[] = [
  {
    query: "AI-TDD如何约束AI输出?",
    expectedSources: [
      "https://hlluan.com/blog/ai-tdd-framework/"
    ],
    requiredFacts: [
      "Manifest",
      "六个心智模型",
      "准入门禁或交付门禁"
    ],
    minRecallRate: 0.8  // 80%以上预期来源被召回
  }
];

2. 低相关性过滤

interface RelevanceFilter {
  // 向量检索分数阈值
  minVectorScore: 0.35;
  // 关键词检索强信号判断
  hasStrongKeyword: boolean;
  // 精排分数阈值
  minRerankScore: 0.3;
}

// 过滤逻辑
if (!hasStrongKeyword && !hasStrongRerank && maxVectorScore < minVectorScore) {
  return [];  // 过滤掉低相关性结果
}

3. 当前页Scope验证

interface ScopeTest {
  query: string;
  intent: "current-page-summary";
  currentUrl: string;
  // 验证:返回的来源应限定在当前页
  validate(sources: Source[]): boolean {
    return sources.every(s => s.url === currentUrl);
  }
}

Layer 2: 引用质量(Citation Quality)

评估目标:系统引用的来源是否可信、准确、无重复

核心指标

1. 来源去重验证

interface DeduplicationRule {
  // 同一页面的不同变体应被识别为同一来源
  aliases: [
    { url: "https://hlluan.com/blog/post", title: "文章标题" },
    { url: "https://www.hlluan.com/blog/post/", title: "文章标题" },
    { url: "https://hlluan.com/blog/post?utm_source=test", title: "文章标题" },
  ];
  // 验证:去重后只剩1个来源
  validate(sources: Source[]): boolean {
    return uniqueBy(sources, s => normalizeUrl(s.url)).length === 1;
  }
}

2. 来源安全边界

// 禁止的来源URL模式
const forbiddenUrlPatterns = [
  /^http:\/\//,                    // 非HTTPS
  /localhost/,                     // 本地开发
  /127\.0\.0\.1/,                  // 回环地址
  /file:\/\//,                     // 本地文件
  /workers\.dev/,                 // Workers开发域名
];

// 验证函数
function validateSourceUrl(url: string): boolean {
  return !forbiddenUrlPatterns.some(pattern => pattern.test(url));
}

3. 来源新鲜度验证

// 优先使用D1中的新鲜metadata
async function enrichSourceFromD1(
  source: RetrievedChunk,
  env: Env
): Promise<Source> {
  const row = await env.RAG_DB.prepare(`
    SELECT title, url, updated_at
    FROM rag_chunks
    WHERE chunk_id = ?
  `).bind(source.id).first<{
    title: string;
    url: string;
    updated_at: string;
  }>();

  return {
    ...source,
    title: row?.title ?? source.title,  // 使用D1新鲜标题
    url: row?.url ?? source.url,        // 使用D1新鲜URL
  };
}

Layer 3: 回答质量(Answer Quality)

评估目标:生成的回答是否完整、准确、符合预期语言

核心指标

1. 必需事实覆盖

interface AnswerTest {
  query: string;
  answer: string;
  requiredFacts: string[];

  // 验证:所有必需事实都出现在回答中
  validate(): boolean {
    return requiredFacts.every(fact =>
      answer.toLowerCase().includes(fact.toLowerCase())
    );
  }
}

2. 语言行为验证

// 文本语言推断
function inferTextLocale(text: string): "zh-CN" | "en" {
  // 有英文字母且无中文字符 → 英文
  const hasEnglish = /[a-z]/i.test(text);
  const hasChinese = /\p{Script=Han}/u.test(text);

  return (hasEnglish && !hasChinese) ? "en" : "zh-CN";
}

// 验证:回答语言与问题语言一致
test("Language matching", () => {
  expect(inferTextLocale("How does AI-TDD govern AI output?")).toBe("en");
  expect(inferTextLocale("AI-TDD如何约束AI输出?")).toBe("zh-CN");
});

3. Fallback策略验证

// 当证据不足时,系统应明确说明而非编造
interface FallbackTest {
  query: string;
  evidenceSufficient: false;

  // 验证回答
  validate(answer: string): boolean {
    // 应包含"未找到充分依据"等明确说明
    const hasExplicitLabel = /未找到|知识库.*不足|insufficient evidence/i.test(answer);
    // 证据不足时不应继续给出来源编号
    const hasCitationMarker = /\[source-\d+\]/i.test(answer);

    return hasExplicitLabel && !hasCitationMarker;
  }
}

Evaluation Set设计

什么是Evaluation Set?

Evaluation Set是一组预定义的测试用例,用于持续评估RAG系统的质量。不同于随机的用户查询,Evaluation Set覆盖高价值场景,降低关键入口被遗漏的概率。

九类高价值问题

基于我的个人知识库内容,我定义了九类高价值问题:

类别代表问题预期来源验收重点
1. 关于作者Hualin是谁?有什么技术背景?/about不编造履历,引用作者页
2. 合作联系如何联系进行合作?/contact/about联系入口新鲜,来源去重
3. 网站主题网站覆盖哪些技术主题?/topics能列出站内topic,不扩展到站外
4. 量化交易Micang Trader系列讲什么?/blog/series-quant-trading/*、series index能区分架构、测试、性能、AI工程化
5. AI工程AI-TDD如何约束AI输出?AI-TDD文章或AI工程专题回答Manifest、六心智模型、门禁时不夸大标准化程度
6. Java或PythonJava GC如何选型?Python GIL为什么重要?Java/Python系列文章能命中系列内容和关键概念
7. 云原生/微服务微服务治理如何从CF演进到云原生?microservices-governance系列能按演进脉络回答
8. 当前页总结总结当前页面当前URL对应document来源限定在当前页
9. 低相关性Fallback站内不存在的主题无强证据明确说明证据不足,不编造引用

这九类不是越多越好,而是覆盖个人知识库最容易出错的入口:作者身份、联系入口、主题导航、核心专题、当前页总结和无答案处理。

下面是一条可执行样例。正式评估集应为每一类至少准备一条中文和一条英文问题。category最好直接复用稳定的意图或评估ID,而不是临时自然语言字符串。

case_id: "topics-001"
category: "site-topics-overview"
question: "网站覆盖哪些技术主题?"
expectedEvidence:
  sourcePatterns: ["/topics"]
  requiredSourceIds: ["topics"]
requiredFacts:
  - "AI工程化"
  - "量化交易"
  - "云原生"
fallbackPolicy:
  allowGeneralKnowledge: false
  requireLabel: true
expectedLanguage: "zh-CN"
citationExpectations:
  requireDeduplication: true
  maxSources: 4

Evaluation Case Schema

每个测试用例包含以下字段:

interface EvaluationCase {
  id: string;                    // 稳定标识
  category: string;              // 问题类别(9类之一)
  question: string;              // 测试问题
  expectedEvidence: {            // 预期证据
    sourcePatterns: string[];    // 来源URL模式
    requiredSourceIds: string[]; // 必需来源ID
  };
  requiredFacts: string[];       // 回答需要覆盖的关键事实
  fallbackPolicy: {              // Fallback策略
    allowGeneralKnowledge: boolean;
    requireLabel: boolean;
  };
  expectedLanguage: string;      // 预期回答语言
  citationExpectations: {        // 引用预期
    requireDeduplication: boolean;
    maxSources: number;
  };
}

Rule-Based Evaluation

为什么选择Rule-Based而非LLM-as-Judge?

在首阶段质量门禁中,我坚持确定性规则而非LLM评判:

  1. 可解释性:规则失败可以直接定位到具体规则ID
  2. 可复现性:相同输入在同一规则版本下会得到相同结果
  3. 成本可控:不需要为每次门禁调用LLM API
  4. 速度更可控:规则检查通常可以在本地或边缘侧快速完成

LLM-as-Judge仅用于:人工抽查、趋势分析、细粒度质量研究,不作为首阶段门禁。

Evaluation Debug Report

每个失败的测试用例生成Debug Report:

{
  "query": "如何联系进行合作?",
  "category": "contact-collaboration",
  "retrievalCandidates": [
    { "id": "chunk-001", "score": 0.82, "source": "vector" },
    { "id": "chunk-015", "score": 0.75, "source": "keyword" }
  ],
  "selectedCitations": [
    { "url": "https://hlluan.com/contact/", "title": "Contact | Hualin Luan" },
    { "url": "https://www.hlluan.com/contact", "title": "Contact | Hualin Luan" }
  ],
  "deduplicationResult": { "before": 2, "after": 1 },
  "fallbackDecision": "sufficient",
  "languageDecision": "zh-CN",
  "failedRuleIds": [
    "CQ-003"   // 来源去重:同一页面变体未被折叠
  ]
}

固定评估用例 Debug Report 安全边界

  • ✅ 允许:固定评估集的query、category、source identifiers、rule IDs
  • ❌ 禁止:embedding values、private Markdown、API keys、raw user data

六层安全防护架构

Layer 1: Kill Switch(紧急制动)

作用:一键关闭系统,停止当前请求继续进入昂贵或高风险调用链

if (env.RAG_CHAT_ENABLED !== "true") {
  return errorResponse("rag_chat_disabled", 503, request, env);
}

使用场景

  • 上游API异常(如OpenAI服务中断)
  • 预算超限
  • 安全事件
  • 紧急维护

关键特性:在进入检索、模型或其他高成本分支之前先检查,尽量把拒绝成本压到最低

Layer 2: Origin校验(来源控制)

作用:只允许特定域名的请求

const allowedOrigins = env.ALLOWED_ORIGINS
  .split(",")
  .map(origin => origin.trim())
  .filter(Boolean);
const origin = request.headers.get("Origin");

if (!origin || !allowedOrigins.includes(origin)) {
  return errorResponse("origin_not_allowed", 403, request, env);
}

控制目标:降低API被未授权站点直接调用的风险。Origin校验不是身份认证,不能替代服务端鉴权、限流和预算门禁。

Layer 3: Rate Limit(限流)

作用:限制单用户或单IP的异常高频请求

interface RateLimitConfig {
  perMinute: 3;
  perHour: 20;
  perDay: 50;
}

// 多维度限流
async function checkRateLimit(
  env: Env,
  sessionId: string,
  ipHash: string
): Promise<RateLimitResult> {
  const sessionCheck = await checkSessionLimit(env, sessionId);
  const ipCheck = await checkIpLimit(env, ipHash);

  return {
    allowed: sessionCheck.allowed && ipCheck.allowed,
    retryAfter: Math.max(sessionCheck.retryAfter, ipCheck.retryAfter)
  };
}

Layer 4: Daily Budget(日预算)

作用:在调用高成本上游前限制每日请求预算

const dailyLimit = parseInt(env.RAG_GLOBAL_DAILY_LIMIT ?? "500");
const todayCount = await getTodayRequestCount(env);

if (todayCount >= dailyLimit) {
  return errorResponse("budget_exhausted", 429, request, env);
}

关键决策:在调用LLM、rerank或其他高成本上游之前先检查预算,避免把预算控制放到调用之后

Layer 5: Circuit Breaker(熔断)

作用:降低上游连续失败引发级联故障的概率

class CircuitBreaker {
  private failureCount = 0;
  private lastFailureTime: number | null = null;
  private readonly threshold = 10;      // 10次失败
  private readonly timeout = 5 * 60 * 1000;  // 5分钟熔断窗口

  recordFailure(): void {
    this.failureCount++;
    this.lastFailureTime = Date.now();
  }

  isOpen(): boolean {
    if (this.failureCount < this.threshold) return false;
    if (!this.lastFailureTime) return false;

    const elapsed = Date.now() - this.lastFailureTime;
    if (elapsed < this.timeout) return true;

    this.failureCount = 0;
    this.lastFailureTime = null;
    return false;
  }
}

在Workers这类边缘运行时中,类内存状态只能作为说明性伪代码,不能作为跨请求、跨实例的权威熔断状态。落地实现应把失败计数和窗口状态写入KV或D1,并接受KV最终一致性带来的短暂误差。

Layer 6: Input Validation(输入校验)

作用:在进入检索和模型调用前裁剪明显危险或过大的输入

interface ValidationRules {
  message: {
    minLength: 1;
    maxLength: 1000;
    forbiddenPatterns: [/<script>/i, /javascript:/i];
  };
  pageContext: {
    maxLength: 3000;
    allowedFields: ["title", "url", "content"];
  };
  history: {
    maxLength: 12;
    itemMaxLength: 1000;
  };
}

隐私保护设计

Telemetry脱敏原则

允许记录的字段(脱敏后):

interface TelemetryLog {
  requestId: string;           // 匿名ID
  sessionHash: string;         // HMAC后的session,不可逆
  ipHash: string;              // HMAC后的IP前缀,不保存原始IP
  status: "success" | "error"; // 状态码
  latencyMs: number;          // 响应时间
  retrievedCount: number;     // 检索数量
  indexEpoch: string;         // 索引版本
}

禁止记录的字段

// ❌ 禁止记录
{
  message: "用户原始问题",           // 隐私
  pageContext: "原始页面内容",       // 隐私
  answer: "模型回答全文",            // 隐私
  rawIP: "192.168.1.1",              // 隐私
  embedding: [0.1, 0.2, ...],        // 敏感
}

固定评估集替代方案

  • 第一版不做真实用户问题聚合
  • 质量分析依赖固定评估问题集,并覆盖中英文与核心入口场景
  • 避免隐私合规复杂度

治理与审计

本节使用三个治理术语。它们不是读者必须采用的产品名,而是发布前质量控制的角色划分。

术语含义在RAG发布中的作用
Traceability Matrix需求、任务、证据、命令和结果的追踪表让需求完成状态有可检查证据,而不是只靠口头声明
Closeout Gate交付前需要通过的命令和证据门禁阻断评估集、构建、安全测试失败的版本
Reverse Audit从交付证据反查需求覆盖发现”做了很多事但没覆盖关键需求”的问题

Requirement Traceability Matrix

每个关键需求都应有追踪链:

Trace Row:
  id: TRACE-001
  covers: [MUST-001, MUST-002, NEG-001]  # 覆盖的需求ID
  taskRefs: [TASK-001]                   # 关联任务
  evidenceRefs: [EVD-001, EVD-002]     # 证据引用
  contractValidationCommandRefs: [CMD-CONTRACT-001]
  deliveryEvidenceCommandRefs: [CMD-DELIVERY-001]
  sequenceViewRefs: [SEQ-001]
  artifactRefs: [ART-001]
  status: PASS                           # PENDING / PASS / FAIL

Closeout Gate

交付前建议通过的门禁

requiredCommands:
  - CMD-DELIVERY-001: audit Evaluation Set
  - CMD-DELIVERY-002: run answer-quality evaluation
  - CMD-DELIVERY-003: E2E gate
  - CMD-DELIVERY-004: build + export + audit
  - CMD-DELIVERY-005: debug safety audit

blockingConditions:
  - Evaluation Set missing required categories
  - answer-quality runner missing deterministic rule results
  - current-page summary drifts
  - low-relevance displays unrelated sources
  - debug report exposes sensitive fields
  - LLM-as-Judge required for first-stage pass/fail

Reverse Audit

交付前执行反向审计

node scripts/reverse_audit_contract.js \
  path/to/confirmed-rag-requirement-contract.md

审计内容

  • implementationConfirmation block完整性
  • requirement ID交叉引用一致性
  • trace rows覆盖完整性
  • evidence gates可执行性

Launch Gate检查清单

在公开入口启用前,建议把以下检查作为发布模板。它不是”本文系统已经全部完成”的声明,而是上线前应收集的证据清单:

基础设施检查

  • Worker部署成功,/health返回ok
  • Vectorize索引创建成功,维度=1024
  • D1数据库创建成功,migrations已执行
  • KV namespace创建成功

数据检查

  • baseline verify通过
  • /admin/stats返回正确indexEpoch
  • /admin/manifest 返回的 manifest 数量与 corpus 一致
  • corpusHash匹配

安全检查

  • RAG_CHAT_ENABLED=false验证返回503
  • 非法Origin返回403 origin_not_allowed
  • rate limit阈值测试通过
  • daily budget计数正常

质量检查

  • 固定评估集覆盖9类问题,并记录每类中英文用例是否通过
  • smoke chat来源引用正确
  • hybrid/rerank开关可独立控制
  • 当前页总结scope验证通过

发布决策

  • 所有TRACE状态=PASS
  • 所有EVD证据已收集
  • Closeout Gate通过,并保存命令输出或审计报告
  • 用户确认上线

总结

RAG质量保障不是单一环节的工作,而是贯穿设计、开发、测试和发布的证据链。

关键实践回顾

质量评估

  • 三层评估体系(检索/引用/回答)定位故障环节
  • 九类高价值问题覆盖核心场景
  • Rule-Based Evaluation提供确定性门禁

安全防护

  • 六层防护架构(Kill Switch→Input Validation)
  • 隐私保护通过Telemetry脱敏实现
  • 固定评估集替代用户问题聚合

治理审计

  • Traceability Matrix追踪需求闭环
  • Closeout Gate阻断质量债务进入发布
  • Reverse Audit复核交付证据覆盖

持续改进

当前机制

  • Rule-Based Evaluation作为首阶段门禁
  • 固定评估集降低核心入口回归风险
  • Debug Report支持故障诊断

未来演进

  • LLM-as-Judge用于人工抽查和趋势分析
  • 用户反馈闭环(需补充隐私设计)
  • A/B测试框架支持算法迭代

读者下一步

  1. 先写9类固定评估问题,每类至少覆盖中文和英文各一条。
  2. 给每条问题写expectedEvidencerequiredFacts和fallback策略。
  3. RAG_CHAT_ENABLED=false状态下验证安全闸门,不要边公开边补门禁。
  4. 发布前保存Evaluation Set、answer-quality报告、来源去重报告和debug安全审计结果。

参考资料

系列文章

Reading path

继续沿这条专题路径阅读

按推荐顺序继续阅读 AI 工程化实践 相关内容,而不是只看同专题的随机文章。

查看完整专题路径 →

Next step

继续深入这个专题

如果这篇内容对你有帮助,下一步可以回到专题页继续系统阅读,或者订阅后续更新。

返回专题页 订阅 RSS 更新

RSS Subscribe

订阅更新

通过 RSS 阅读器订阅获取最新文章推送,无需频繁访问网站。

推荐使用 FollowFeedlyInoreader 等 RSS 阅读器

评论与讨论

使用 GitHub 账号登录参与讨论,评论将同步至 GitHub Discussions

正在加载评论...