智能体面试准备(十九):RAG 评估体系——检索指标、RAGAS、忠实度与故障归因

上一篇《Function Calling 全链路》讲的是智能体怎么正确地调工具,这一篇回到检索这条主线。A 系列第十二篇讲过 RAG 怎么搭,但面试真正卡人的往往不是"你会不会搭",而是"你的 RAG 到底好不好,你怎么知道"。RAG 是一个多阶段级联系统,任何一环坏了最终都表现为"答得不对",如果没有分段归因能力,调优就变成了盲目试参数。今天这篇把 RAG 评估体系一次讲透:从检索侧的经典 IR 指标,到生成侧的忠实度与相关性,再到 RAGAS 这类自动化框架的原理与坑,最后给一套可落地的分层归因方案。结尾照例是面试速答与高频追问清单。


一、先想清楚:RAG 为什么必须分段评估

1.1 级联系统的误差传播

RAG 不是一个模型,是一条流水线。每一环的错误都会向下传播且不可逆。

  用户问题
     │
     ▼
  [1] 查询理解/改写 ──── 改写跑偏 → 后面全错
     │
     ▼
  [2] 检索(召回)  ──────── 没召回到 → 生成再强也答不出
     │
     ▼
  [3] 重排(rerank) ─────── 排序差 → 关键片段被截断丢弃
     │
     ▼
  [4] 上下文组装 ───────── 截断/顺序不当 → 中间迷失
     │
     ▼
  [5] 生成 ─────────────── 有材料却编造 → 忠实度问题
     │
     ▼
  最终答案

关键洞察:端到端只看一个"回答正确率",等于把五个环节的问题混成一锅粥。 假设某系统端到端准确率 60%,这 40% 的错误里到底是"没检索到"还是"检索到了但没用好"?两者的修复动作完全不同——前者要改 embedding 模型或分块策略,后者要改 prompt 或换生成模型。分不清就只能瞎调。

这就是面试的第一个高分点:RAG 评估的首要目标不是给出一个分数,而是提供归因能力。

1.2 评估的二维框架

                   检索质量(有没有给对材料)
                          高          低
                    ┌──────────┬──────────┐
              高    │  理想     │ 幻觉/编造 │
  生成质量          │  区       │ (材料不足) │
 (用没用好材料)    ├──────────┼──────────┤
              低    │ 有材料    │  彻底失败  │
                    │ 不会用    │           │
                    └──────────┴──────────┘

四个象限对应四种完全不同的优化动作:
- 有材料不会用:改 prompt、加引用要求、换更强的生成模型、缩短上下文
- 材料不足导致编造:改分块、换 embedding、加 query 改写、扩大 top-k
- 彻底失败:先修检索,检索不修好,改生成没有意义
- 理想区:关注延迟和成本


二、检索侧评估:经典 IR 指标

2.1 需要的标注数据

检索评估需要 (query, 相关文档集合) 的标注。这是最贵的一步,但有低成本做法(后面 2.5 节讲)。

2.2 核心指标全解

设某 query 的真实相关文档集为 R,检索返回的前 k 个为 Retrieved@k。

Recall@k(召回率)—— RAG 里最重要的检索指标

  Recall@k = |R ∩ Retrieved@k| / |R|

为什么最重要?因为召回是 RAG 的上限。没召回到的文档,生成阶段无论如何也变不出来。实践中通常关注 Recall@20 或 Recall@50(重排前的候选集),只要召回够高,排序问题可以靠 reranker 补救。

Precision@k(精确率)

  Precision@k = |R ∩ Retrieved@k| / k

在 RAG 里 precision 的重要性低于 recall,但不是不重要——噪声文档会稀释注意力、挤占上下文、诱发幻觉。

MRR(Mean Reciprocal Rank)—— 只关心第一个正确答案排多前

  MRR = (1/|Q|) × Σ_q  1 / rank_q(第一个相关文档的位置)

  第 1 位命中 → 1.0    第 2 位 → 0.5    第 5 位 → 0.2

适合"只需要一个正确答案"的场景(如 FAQ 匹配)。

NDCG@k —— 考虑分级相关性和位置折扣,最全面

  DCG@k  = Σ_{i=1..k}  rel_i / log2(i + 1)
  IDCG@k = 理想排序下的 DCG
  NDCG@k = DCG@k / IDCG@k

rel_i 可以是 0/1,也可以是 0-3 的分级相关度。NDCG 是学术界和搜索工业界的黄金标准。

Hit Rate@k(命中率)—— 最粗但最直观

  Hit@k = 前 k 个里至少有一个相关文档的 query 占比

2.3 一份可直接用的实现

import numpy as np
from typing import Sequence


def recall_at_k(retrieved: Sequence[str], relevant: set, k: int) -> float:
    if not relevant:
        return 0.0
    return len(set(retrieved[:k]) & relevant) / len(relevant)


def precision_at_k(retrieved: Sequence[str], relevant: set, k: int) -> float:
    if k == 0:
        return 0.0
    return len(set(retrieved[:k]) & relevant) / k


def mrr(retrieved: Sequence[str], relevant: set) -> float:
    for i, d in enumerate(retrieved, start=1):
        if d in relevant:
            return 1.0 / i
    return 0.0


def ndcg_at_k(retrieved: Sequence[str], rel_map: dict, k: int) -> float:
    """rel_map: {doc_id: 相关度分级},未出现的按 0 计。"""
    gains = [rel_map.get(d, 0) for d in retrieved[:k]]
    dcg = sum(g / np.log2(i + 2) for i, g in enumerate(gains))
    ideal = sorted(rel_map.values(), reverse=True)[:k]
    idcg = sum(g / np.log2(i + 2) for i, g in enumerate(ideal))
    return dcg / idcg if idcg > 0 else 0.0


def eval_retrieval(dataset, retriever, ks=(1, 5, 10, 20)):
    """dataset: [{'query':..., 'relevant': set(...), 'rel_map': {...}}, ...]"""
    agg = {f"recall@{k}": [] for k in ks}
    agg.update({f"ndcg@{k}": [] for k in ks})
    agg["mrr"] = []
    for item in dataset:
        docs = retriever(item["query"], top_k=max(ks))
        for k in ks:
            agg[f"recall@{k}"].append(recall_at_k(docs, item["relevant"], k))
            agg[f"ndcg@{k}"].append(ndcg_at_k(docs, item.get("rel_map", {d: 1 for d in item["relevant"]}), k))
        agg["mrr"].append(mrr(docs, item["relevant"]))
    return {m: round(float(np.mean(v)), 4) for m, v in agg.items()}

2.4 分块策略对指标的影响

一个容易忽略但面试很爱问的点:chunk 粒度直接决定指标的含义。

  chunk = 整篇文档(2000 字)
     召回容易(语义覆盖广),但塞进上下文浪费 token,噪声高

  chunk = 段落(300 字)
     精度高、token 省,但一个答案跨段落时容易只召回一半

  chunk = 句子(50 字)
     检索精准但上下文缺失,模型看不懂片段在说什么

主流解法是 small-to-big / 父子块:用小块做检索(精准匹配),命中后返回其父块(完整语境)。评估时要注意:指标要在"最终喂给模型的单元"上算,而不是在检索单元上算,否则会高估。

2.5 低成本构造标注集:反向生成法

没有标注怎么办?用 LLM 从文档反向生成问题。

GEN_Q_PROMPT = """阅读下面的文档片段,提出 2 个只能依据该片段回答的具体问题。
要求:
- 问题必须能被该片段完整回答,不依赖外部知识
- 问题要像真实用户提问,不要出现"根据上文""该片段"等字样
- 避免过于宽泛的问题

文档片段:
{chunk}

输出 JSON 数组:["问题1", "问题2"]"""


def build_eval_set(chunks: list[dict], llm, sample_n: int = 300):
    """每个 chunk 生成问题,天然形成 (query -> 该 chunk 为正例) 的标注。"""
    import json, random
    dataset = []
    for c in random.sample(chunks, min(sample_n, len(chunks))):
        try:
            qs = json.loads(llm(GEN_Q_PROMPT.format(chunk=c["text"])))
        except Exception:
            continue
        for q in qs:
            dataset.append({"query": q, "relevant": {c["id"]}, "source_chunk": c["text"]})
    return dataset

这个方法的已知缺陷必须主动说出来(面试加分):
1. 生成的问题往往和 chunk 用词高度重合,导致高估向量检索效果(词面匹配就能中)。缓解办法是要求 LLM 做同义改写、或者用另一个模型改写问题。
2. 只有单一正例,实际上可能有多个 chunk 都能回答,会低估 precision。
3. 分布与真实用户提问不同——真实用户会打错字、问得模糊、跨文档提问。所以这套集合适合做相对比较(A 方案 vs B 方案),不适合当绝对值汇报。


三、生成侧评估:忠实度与相关性

检索到了材料,生成阶段的问题是"有没有老老实实用材料回答"。

3.1 四个核心维度

  ┌──────────────┬────────────────────────────────────┐
  │ Faithfulness │ 答案的每个论断是否都能由检索材料支撑   │
  │ 忠实度        │ 低 → 幻觉、编造                     │
  ├──────────────┼────────────────────────────────────┤
  │ Answer        │ 答案是否切题、直接回应了用户问题      │
  │ Relevance     │ 低 → 答非所问、大段无关铺陈          │
  ├──────────────┼────────────────────────────────────┤
  │ Context       │ 检索到的材料中有多少是真正有用的      │
  │ Precision     │ 低 → 噪声多,浪费 token 且干扰生成    │
  ├──────────────┼────────────────────────────────────┤
  │ Context       │ 回答所需的信息是否都被检索到了        │
  │ Recall        │ 低 → 材料不全,模型只能残缺作答       │
  └──────────────┴────────────────────────────────────┘

注意 Faithfulness 和 Answer Relevance 是正交的:一个回答可以完全忠于材料但答非所问(材料讲的不是用户想问的),也可以切题但编造。必须分开测。

3.2 Faithfulness 的计算:论断分解法

这是 RAGAS 的核心思路,也是面试要能讲清楚的算法。

  Step 1  把答案拆成若干原子论断 (atomic claims)
          "北京是中国首都,人口约2100万" → ["北京是中国首都", "北京人口约2100万"]

  Step 2  对每个论断,判断能否由 context 推出(NLI 二分类)
          论断1: context 支持 → 1
          论断2: context 未提及 → 0

  Step 3  Faithfulness = 被支持的论断数 / 总论断数 = 1/2 = 0.5
CLAIM_SPLIT_PROMPT = """把下面这段答案拆解为若干条独立的原子论断。
要求:每条论断只包含一个可独立验证的事实,代词要还原为具体实体。

答案:
{answer}

输出 JSON 数组,每个元素是一条论断字符串。"""

NLI_PROMPT = """判断"论断"是否能由"参考材料"直接推出。

参考材料:
{context}

论断:
{claim}

规则:
- 材料明确支持该论断 → supported
- 材料明确反驳该论断 → contradicted
- 材料未提及、或需要外部知识才能判断 → not_mentioned

只输出一个词:supported / contradicted / not_mentioned"""


def faithfulness(answer: str, contexts: list[str], llm) -> dict:
    import json
    try:
        claims = json.loads(llm(CLAIM_SPLIT_PROMPT.format(answer=answer)))
    except Exception:
        return {"score": None, "error": "claim_split_failed"}
    if not claims:
        return {"score": 1.0, "claims": []}

    ctx = "\n\n".join(contexts)
    results = []
    for c in claims:
        verdict = llm(NLI_PROMPT.format(context=ctx, claim=c)).strip().lower()
        results.append({"claim": c, "verdict": verdict})

    supported = sum(r["verdict"] == "supported" for r in results)
    return {
        "score": supported / len(claims),
        "claims": results,
        # 矛盾比未提及更严重,单独统计便于定位
        "contradicted": sum(r["verdict"] == "contradicted" for r in results),
    }

工程要点:
- 要区分 "contradicted"(与材料矛盾)和 "not_mentioned"(材料没提)。前者是严重错误,后者可能只是模型补充了常识(比如"北京是中国的城市")。只用二分类会把两者混淆,导致分数虚低。
- 论断拆解质量决定整个指标的质量。拆得太细(把"2100万"和"人口"拆开)会产生大量无意义论断;拆得太粗则漏检。
- 这个方法每个答案要调用 1 + N 次 LLM(N 为论断数),成本不低,要做采样评估而非全量。

3.3 Answer Relevance:反向生成问题法

思路很巧:如果答案切题,那么从答案反推出的问题应该和原问题很像。

def answer_relevance(question: str, answer: str, llm, embed, n: int = 3) -> float:
    """从答案反向生成 n 个问题,算与原问题的平均余弦相似度。"""
    import numpy as np
    prompt = f"根据下面的答案,反推出用户最可能问的问题。生成 {n} 个不同表述。\n答案:{answer}\n输出JSON数组。"
    import json
    try:
        gen_qs = json.loads(llm(prompt))
    except Exception:
        return 0.0

    q_vec = np.array(embed(question))
    sims = []
    for gq in gen_qs:
        g_vec = np.array(embed(gq))
        cos = float(q_vec @ g_vec / (np.linalg.norm(q_vec) * np.linalg.norm(g_vec) + 1e-9))
        sims.append(cos)
    return float(np.mean(sims)) if sims else 0.0

局限也要知道:这个指标对"答非所问"敏感,但对"答错了但很切题"不敏感——它测的是相关性不是正确性。

3.4 Context Precision:加权命中位置

  Context Precision@K = Σ_k (Precision@k × v_k) / (相关 chunk 总数)
  其中 v_k = 1 如果第 k 个 chunk 相关,否则 0

  它奖励"相关 chunk 排在前面",因为靠前的 chunk 更容易被模型利用

这个指标把 IR 的排序质量和 LLM 的注意力分布结合起来了——呼应第十三篇讲过的"中间迷失(lost in the middle)"现象。


四、RAGAS 框架:能用,但要知道它的坑

RAGAS 是目前最流行的 RAG 自动评估库,把上面几个指标封装好了。

# 典型用法
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision, context_recall
from datasets import Dataset

data = Dataset.from_dict({
    "question": questions,
    "answer": answers,
    "contexts": contexts_list,        # list[list[str]]
    "ground_truth": ground_truths,    # context_recall 需要
})

result = evaluate(data, metrics=[faithfulness, answer_relevancy,
                                 context_precision, context_recall])
print(result)

四个必须知道的坑

坑一:分数不可跨项目比较。 RAGAS 分数严重依赖裁判模型、prompt 版本和库版本。你的 faithfulness 0.85 和别人的 0.85 不是一回事。只能用于同一套配置下的纵向对比(这版 vs 上版)。

坑二:成本和延迟被低估。 faithfulness 一条要跑 1+N 次 LLM 调用,1000 条样本可能就是上万次调用。必须做采样(200~500 条足够)和并发控制,否则一次评估跑几小时、花几百块。

坑三:中文场景要换 prompt。 默认 prompt 是英文的,对中文的论断拆解质量明显下降(尤其是长句拆分和代词还原)。必须自己重写中文版 prompt 并做人工校准。

坑四:指标之间会打架。 增大 top-k 会提升 context recall,但降低 context precision;强化"必须基于材料回答"的 prompt 会提升 faithfulness,但可能降低 answer relevance(模型变得过于保守、频繁拒答)。不能只优化单一指标,要看组合。

       top_k 增大的效果
  context_recall  ↑↑
  context_precision ↓↓
  faithfulness    ↓ (噪声诱发编造)
  延迟/成本        ↑↑
  → 存在最优点,通常在 top_k = 5~10 区间,要实测

五、故障归因:一套可落地的分层诊断

这是本文最实用的部分,也是面试的压轴回答。

5.1 归因决策树

                    答案错了
                       │
        ┌──────────────┴──────────────┐
        │                             │
  context_recall 低?              context_recall 高
  (材料里没有答案)                (材料里有答案)
        │                             │
        ▼                             ▼
   检索侧问题                    ┌────┴────┐
        │                       │         │
   ┌────┼────┬────────┐    faithfulness  faithfulness
   │    │    │        │      低           高但答案错
   ▼    ▼    ▼        ▼       │            │
 分块  向量  query   索引      ▼            ▼
 太碎  模型  没改写   缺失   生成在编造    材料本身有误
 或太  不匹配                (改prompt/  (数据源问题,
  粗   领域                   换模型/     要治理知识库)
                              加引用约束)

5.2 实现:一次评估打全套标签

from dataclasses import dataclass, asdict

@dataclass
class RagDiagnosis:
    qid: str
    ctx_recall: float        # 材料是否包含答案所需信息
    ctx_precision: float     # 材料噪声程度
    faithfulness: float      # 答案是否忠于材料
    ans_relevance: float     # 答案是否切题
    correct: bool | None     # 端到端是否正确(有 ground truth 时)
    bucket: str = ""         # 归因结论

    def diagnose(self, th=0.6):
        if self.ctx_recall < th:
            self.bucket = "retrieval_miss"       # 检索没给到材料
        elif self.faithfulness < th:
            self.bucket = "generation_hallucination"  # 有材料却编造
        elif self.ans_relevance < th:
            self.bucket = "off_topic"            # 忠实但答非所问
        elif self.correct is False:
            self.bucket = "source_error"         # 材料本身错误
        else:
            self.bucket = "ok"
        return self


def aggregate(diags: list[RagDiagnosis]) -> dict:
    from collections import Counter
    c = Counter(d.diagnose().bucket for d in diags)
    total = max(len(diags), 1)
    return {k: round(v / total, 4) for k, v in c.most_common()}


# 输出示例:
# {'ok': 0.62, 'retrieval_miss': 0.24, 'generation_hallucination': 0.09,
#  'off_topic': 0.03, 'source_error': 0.02}
# → 结论清晰:主要矛盾是检索召回,优先投入改分块和 embedding

有了这张分布表,优化方向就不再靠猜。"我们的 RAG 准确率 62%,其中 24% 的错误来自检索未召回,9% 来自生成幻觉,所以这个季度重点做检索优化" —— 这句话在面试里的分量远超任何指标罗列。

5.3 检索侧的针对性优化清单

一旦定位到 retrieval_miss 占比高,按性价比排序的动作是:

优先级动作典型收益成本
1加 BM25 做混合检索(RRF 融合)Recall +5~15 点极低,半天
2加 reranker(bge-reranker / cohere)NDCG +10 点低,延迟 +50ms
3调分块策略(父子块 / 重叠窗口)Recall +3~10 点中,要重建索引
4Query 改写 / HyDE / 多查询Recall +5~10 点中,延迟 +1 次 LLM
5领域微调 embeddingRecall +10~20 点高,要标注数据
6知识库治理(去重、补全、更新)视情况,可能最大最高,但常被忽视

混合检索的 RRF 融合值得给一段代码,因为它是性价比最高的一招:

def rrf_fuse(rank_lists: list[list[str]], k: int = 60, top_n: int = 20):
    """Reciprocal Rank Fusion:把多路召回结果融合。
    k=60 是论文推荐值,作用是压制头部排名的过度影响。"""
    scores = {}
    for lst in rank_lists:
        for rank, doc in enumerate(lst, start=1):
            scores[doc] = scores.get(doc, 0.0) + 1.0 / (k + rank)
    return [d for d, _ in sorted(scores.items(), key=lambda x: -x[1])[:top_n]]


# 用法:向量召回 + BM25 召回融合
fused = rrf_fuse([vector_search(q, 50), bm25_search(q, 50)], top_n=20)

RRF 的好处是不需要归一化分数——向量相似度和 BM25 分数量纲完全不同,直接加权融合要调参,而 RRF 只用排名,鲁棒性极强。


六、线上评估:离线指标之外

离线评测集再好也是静态的,线上还需要无标注的质量监控。

6.1 可用的无标注信号

  显式信号   点赞/点踩、"这个回答有帮助吗"
             覆盖率低(<5% 用户会点),但准确
  ─────────────────────────────────────────
  隐式信号   引用点击率     用户点开了引用来源 → 回答可信
             重问率        同一 session 内换个说法再问 → 上次没答好
             会话轮次      异常增多 → 反复澄清,体验差
             复制率        用户复制了答案 → 大概率有用
             人工转接率    客服场景的黄金指标
  ─────────────────────────────────────────
  自动信号   无引用回答占比  答案里没有任何 citation → 高幻觉风险
             检索置信度低   top1 相似度 < 阈值 → 该走兜底话术
             答案长度异常   过短(可能拒答)/过长(可能跑偏)

6.2 一个实用的线上守卫

def rag_guardrail(query, contexts, scores, answer) -> dict:
    """线上实时护栏:低置信直接兜底,不要硬答。"""
    flags = []
    if not contexts or max(scores, default=0) < 0.35:
        flags.append("low_retrieval_confidence")
    if "[" not in answer and "引用" not in answer:
        flags.append("no_citation")
    if len(answer) < 20:
        flags.append("too_short")

    action = "pass"
    if "low_retrieval_confidence" in flags:
        action = "fallback"      # 回复"未找到相关资料"并引导转人工
    elif "no_citation" in flags:
        action = "regenerate"    # 强制要求带引用重新生成一次

    return {"flags": flags, "action": action}

这个思路的价值在于:RAG 系统最该避免的不是"答不出",而是"自信地答错"。宁可承认查不到,也不要编。这一点在金融、医疗、法务场景是硬要求。


七、面试速答

Q:你怎么评估一个 RAG 系统?
A:分检索侧、生成侧、端到端三层,核心目的不是拿一个分数而是能归因。检索侧用 Recall@k、NDCG@k、MRR,其中 Recall 最关键因为它决定系统上限——没召回到的内容生成阶段变不出来。生成侧用 Faithfulness(是否忠于材料)、Answer Relevance(是否切题)、Context Precision(材料噪声度)。端到端再看正确率和业务指标。最后把每条样本打上归因标签,输出一张"错误来源分布表",比如 24% 检索未召回、9% 生成幻觉,这样优化方向就明确了。

Q:Faithfulness 怎么算?
A:论断分解法。先用 LLM 把答案拆成若干原子论断,每条只含一个可独立验证的事实,代词要还原;然后对每条论断做 NLI 判断,看能否由检索材料推出;最后用"被支持的论断数除以总论断数"作为分数。工程上有两个要点:一是必须区分"与材料矛盾"和"材料未提及",前者是严重错误后者可能只是补充常识,混在一起会让分数虚低;二是每个答案要调用 1+N 次 LLM,成本高,实践中做 200 到 500 条采样评估而非全量。

Q:只看端到端准确率有什么问题?
A:RAG 是五级级联流水线——查询改写、检索、重排、上下文组装、生成,任何一环出错都表现为最终答案不对。只看端到端等于把五个环节的问题混成一锅粥。比如准确率 60%,剩下 40% 的错误里,"没检索到"要改 embedding 和分块策略,"检索到了但编造"要改 prompt 或换生成模型,两者的修复动作完全相反。没有分段归因,调优就变成盲目试参数。

Q:没有标注数据怎么评估检索?
A:用反向生成法,让 LLM 读每个 chunk 生成只能依据该片段回答的问题,天然形成 query 到正例 chunk 的映射。但必须清楚三个缺陷:生成的问题和原文用词高度重合,会高估向量检索效果,缓解办法是加一轮同义改写;只标了单一正例,实际可能有多个 chunk 都能回答,会低估 precision;分布和真实用户不同,用户会打错字、问得模糊、跨文档提问。所以这套集合适合做 A 方案 vs B 方案的相对比较,不适合当绝对值汇报。真正可信的还是从线上日志采样人工标注几百条。

Q:RAGAS 好用吗?有什么坑?
A:能用,是快速起步的好选择,但四个坑要知道。第一,分数不可跨项目比较,它严重依赖裁判模型、prompt 版本和库版本,只能做同配置下的纵向对比。第二,成本被低估,faithfulness 一条要 1+N 次调用,1000 条样本上万次调用,必须采样和控并发。第三,默认 prompt 是英文的,中文场景论断拆解质量明显下降,必须重写中文 prompt 并人工校准。第四,指标之间会打架,增大 top-k 提升 context recall 但降低 precision,强化"必须基于材料"的约束提升 faithfulness 但可能让模型过度保守频繁拒答,所以要看指标组合而不是单点优化。

Q:检索召回率低,你会怎么优化?
A:按性价比排序。第一步加 BM25 做混合检索用 RRF 融合,半天工作量通常能提 5 到 15 个点,而且 RRF 只用排名不用归一化分数,鲁棒性很好。第二步加 reranker,延迟只增加几十毫秒,NDCG 能提 10 个点。第三步调分块策略,用父子块做 small-to-big,小块检索大块喂给模型。第四步做 query 改写或 HyDE,代价是多一次 LLM 调用。第五步领域微调 embedding,收益最大但需要标注数据。最后是知识库治理——去重、补全、更新,这一步最容易被忽视但很多时候是根因,材料里压根没有答案,改多少检索算法都没用。

Q:线上没有标注怎么监控 RAG 质量?
A:靠隐式信号加自动护栏。隐式信号包括引用点击率、重问率、会话轮次、答案复制率、人工转接率,其中重问率和转接率最灵敏。自动信号包括无引用回答占比、检索 top1 相似度低于阈值的比例、答案长度异常。更重要的是做实时护栏:检索置信度过低时不要硬答,直接走"未找到相关资料"的兜底话术并引导转人工。RAG 系统最该避免的不是答不出,而是自信地答错,这在金融医疗法务场景是硬要求。

Q:Context Precision 和 Precision@k 有什么区别?
A:Precision@k 是纯 IR 指标,只看前 k 个里有多少相关,不考虑顺序。Context Precision 引入了位置加权,相关 chunk 排得越靠前分数越高。这个设计是有依据的——大模型存在"中间迷失"现象,上下文开头和结尾的信息利用率明显高于中间,所以同样是召回到了,排在第 1 位和排在第 15 位对生成质量的实际贡献差别很大。Context Precision 把 IR 的排序质量和 LLM 的注意力分布特性结合起来了。


八、高频追问清单

  1. Recall 和 Precision 在 RAG 里为什么不对等重要?什么场景下 Precision 会变得更关键?
  2. 父子块(small-to-big)策略下,检索指标应该在哪个粒度上计算?
  3. 如果 faithfulness 很高但用户还是投诉答错,可能是什么原因?
  4. 多跳问题(需要综合两篇文档)的检索评估该怎么设计?
  5. RRF 里的 k=60 是怎么来的?改成 10 或 200 会有什么变化?
  6. 如何评估 query 改写模块本身的收益?怎么做消融?
  7. 知识库更新后,历史评测集还有效吗?怎么做版本管理?
  8. Agentic RAG(多轮检索、自主决定检索时机)的评估比单轮难在哪?
  9. 如果 LLM 裁判和人工标注只有 55% 一致率,这个自动指标还能用吗?
  10. RAG 系统的 P95 延迟从 2s 涨到 5s,但准确率提了 3 个点,你怎么决策?
  11. 引用(citation)的准确性怎么单独评估?引用了但内容对不上怎么检测?
  12. 怎么评估"该拒答时有没有拒答"这个能力?

下一篇(B20)讲智能体可观测性——本篇解决的是"RAG 好不好"的离线度量问题,下一篇解决"线上正在发生什么"的运行时可见性问题:Trace 与 Span 的建模、OpenTelemetry 的 GenAI 语义约定、token 成本归因、以及多步 Agent 的故障定位。两篇合起来,才算把"智能体的质量工程"讲完整。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐