评估

RAG 出问题,先分清是 没检索到 还是 检索到了却胡编。本课只用课堂上能跑的检查:hit@k、忠实度启发式、「只根据上下文回答」。这不是论文指标,也代替不了人工抽查。

准备 20–50 条 你自己写的问答,答案必须依赖语料里的虚构事实(订单号 KH-8842、时限「7 日」)。网上能搜到的常识测不出检索。


检索:hit@k

每条问题标一个(或几个)应当出现的块 id。检索返回列表后,看 gold id 是否落在前 k 名。

def hit_at_k(retrieved: list[str], gold: str | set[str], k: int) -> float:
    targets = {gold} if isinstance(gold, str) else set(gold)
    return 1.0 if targets & set(retrieved[:k]) else 0.0


cases = [
    {"q": "KH-8842 怎么退款?", "gold": "refund-0"},
    {"q": "客服几点在线?", "gold": "hours-0"},
]

# retrieved_ids = collection.query(...)["ids"][0]
retrieved_ids = ["refund-0", "hours-0", "other"]
print(hit_at_k(retrieved_ids, "refund-0", k=4))

score = sum(hit_at_k(["refund-0"], c["gold"], 4) for c in cases) / len(cases)
print("hit@4", score)

hit@k 低:先改切块、嵌入模型、k、过滤,不要先换更大的聊天模型。hit@k 高但用户仍抱怨:问题在生成侧。

Recall@k(多个 gold 块时算召回)同一套列表就能算;教学阶段 hit@k 足够。


忠实度(faithfulness)

忠实度问的是:回答里的断言能否被上下文支持。与「好不好听」无关。课堂上两层:

1. 数字 / 代号抽查(无模型)

回答里出现的数字和订单号,必须在 context 里出现过。编造 8 日KH-0001 会被抓住。

import re

TOKEN = re.compile(r"KH-\d+|\d+")


def unsupported_tokens(answer: str, context: str) -> list[str]:
    ctx = set(TOKEN.findall(context))
    return sorted({t for t in TOKEN.findall(answer) if t not in ctx})


ctx = "退款须在到货 7 日内凭订单号 KH-8842 申请。"
print(unsupported_tokens("请在 8 日内凭 KH-0001 申请。", ctx))
# ['8', 'KH-0001']
print(unsupported_tokens("请在 7 日内凭 KH-8842 申请。", ctx))
# []

这拦不住「把 7 日说成一周」这类改写,但能挡住最常见的幻觉数字。

2. 短裁判提示词(可选,调用任意 LLM)

你是检查员。只输出 UNSUPPORTED 断言列表,没有则输出 NONE。
CONTEXT:
{context}

ANSWER:
{answer}

不要用「是否有用」当分数。有用但不忠实,对客服场景仍是事故。


「只根据上下文回答」

这是提示词约束,也是测试用例:

问题类型期望
上下文有明确数字引用该数字,不改写政策
上下文完全没提不知道 / 依据不足,而不是常识作文
上下文部分相关只陈述有依据的部分,其余标明未知
REFUSAL_MARKERS = ("不知道", "依据不足", "上下文没有")


def answers_only_from_context(answer: str, context: str, expect_unknown: bool) -> bool:
    leaked = unsupported_tokens(answer, context)
    if leaked:
        return False
    if expect_unknown:
        return any(m in answer for m in REFUSAL_MARKERS)
    return True


assert answers_only_from_context(
    "上下文没有说明股价。",
    "客服在线 09:00–18:00。",
    expect_unknown=True,
)

expect_unknown=True 的题目放进回归集:模型最爱在这里「帮忙」编一段。


建议工作流

  1. 固定切块与嵌入模型,跑 hit@4。
  2. 人工看 10 次完整问答(含应拒绝的题)。
  3. 数字抽查脚本跑全部回答。
  4. 再动 rerank 或混合检索,每次只改一个旋钮。

LangSmith、LlamaIndex evaluators、Dify 检索测试是同一思想的产品化,见 LangChain RAGLlamaIndexDify 知识库。本课不绑定其中任何一个。


下一步

评论