Chapter 11

评估与观测 · RAG 质量不能靠感觉

做完 RAG 的人最常问的问题:"我这个效果到底好不好?"——靠开发自己试几条 query 不叫评估。这章讲怎么量化 RAG 质量、怎么持续观测、怎么做 A/B,让你的 RAG 迭代有据可依。

一、RAG 的三类失败与对应指标

RAG 不是一个黑盒——把它拆成检索 + 合成两段,失败可归三类:

失败类型表现对应指标
召回错相关片段没找到hit_rate / MRR / NDCG(检索指标)
合成错片段在但答案乱合Faithfulness(忠实度)
幻觉编造片段里没有的内容Faithfulness + Groundedness
答非所问答对但不回应问题Relevancy(答案相关性)
事实错和标准答案不一致Correctness / SemanticSimilarity
只看一个指标没用——Faithfulness 100% 但 Relevancy 0 就是"完全照抄但答非所问"。至少 Faithfulness + Relevancy + Retrieval hit_rate 三项一起看,才能定位问题出在哪一段。

二、LlamaIndex 内置 Evaluator

from llama_index.core.evaluation import (
    FaithfulnessEvaluator,       # 答案是否能由 context 支持(反幻觉)
    RelevancyEvaluator,          # 答案是否回应了 query
    CorrectnessEvaluator,        # 和 reference 答案的一致性(1-5 打分)
    SemanticSimilarityEvaluator, # 和 reference 的 embedding 相似度
    AnswerRelevancyEvaluator,    # 答案 vs query 相关性
    ContextRelevancyEvaluator,   # context 本身 vs query 相关性
)
from llama_index.llms.openai import OpenAI

judge = OpenAI(model="gpt-4o")    # 评审用强模型,别省

faith = FaithfulnessEvaluator(llm=judge)
rel   = RelevancyEvaluator(llm=judge)

response = query_engine.query("双因素认证怎么配?")
f_result = faith.evaluate_response(response=response)
r_result = rel.evaluate_response(query="双因素认证怎么配?", response=response)

print(f_result.passing, f_result.score, f_result.feedback)

关键细节:

三、RetrieverEvaluator:检索阶段单独评

召回阶段有没有问题,不用等到合成完再看——RetrieverEvaluator 直接在节点层评估:

from llama_index.core.evaluation import RetrieverEvaluator

retriever = index.as_retriever(similarity_top_k=10)

r_evaluator = RetrieverEvaluator.from_metric_names(
    ["hit_rate", "mrr", "ndcg", "precision", "recall"],
    retriever=retriever,
)

# 单条评估
result = await r_evaluator.aevaluate(
    query="怎么申请退款?",
    expected_ids=["node_42", "node_58"],    # 黄金集里标好的 node id
)
print(result.metric_dict)
# {"hit_rate": 1.0, "mrr": 0.5, "ndcg": 0.63, ...}

指标含义:

四、BatchEvalRunner:批量 + 并发

from llama_index.core.evaluation import BatchEvalRunner

runner = BatchEvalRunner(
    {"faithfulness": faith, "relevancy": rel, "correctness": correct},
    workers=8,              # 并发评估
    show_progress=True,
)

eval_results = await runner.aevaluate_queries(
    query_engine=qe,
    queries=golden_questions,        # 黄金集 query list
    reference=golden_answers,         # 对应 reference 答案(Correctness 需要)
)

# 聚合统计
from collections import Counter
for metric, rs in eval_results.items():
    pass_rate = sum(r.passing for r in rs) / len(rs)
    avg_score = sum(r.score or 0 for r in rs) / len(rs)
    print(f"{metric}: pass={pass_rate:.2%} avg={avg_score:.2f}")

跑 200 条黄金集 + 3 个指标 = 600 次 LLM 调用,用 gpt-4o 约 $3-5、5-10 分钟。每次改 retriever 参数就跑一遍,把结果存到 CSV,趋势清清楚楚

五、黄金集(Golden Set)构建

评估的前提是有标准答案。RAG 项目的"黄金集"通常是 200-500 条高质量 Q&A 对:

方法 A:LLM 自动生成

from llama_index.core.evaluation import DatasetGenerator, QueryResponseDataset

generator = DatasetGenerator.from_documents(
    documents=docs[:50],       # 不用全量,采样几十篇
    llm=OpenAI(model="gpt-4o"),
    num_questions_per_chunk=2,
    show_progress=True,
)

dataset: QueryResponseDataset = await generator.agenerate_dataset_from_nodes(num=200)
dataset.save_json("./golden_set.json")

# 加载
dataset = QueryResponseDataset.from_json("./golden_set.json")
queries = list(dataset.queries.values())
references = list(dataset.responses.values())
LLM 生成的黄金集要人工抽检——生成的问题往往太"八股"(和文档句子相似度太高,RAG 一搜就中,虚高分数)。抽 20% 人工过一遍,把泛泛的删掉、加入真实用户会问的口语化问题。生产上最有价值的黄金集其实是线上 bad case 沉淀——客服反馈"这条答错了",标注后进黄金集。

方法 B:从线上 log 提取

# 从 production log 提 1000 条真实 query
real_queries = load_prod_queries(days=7)

# 用强模型生成参考答案
refs = []
for q in real_queries:
    r = await gpt4.aquery(q, context=get_full_context(q))   # 喂完整文档
    refs.append(r.text)

# 人工审 + 修正,得到真实分布的黄金集

六、RAGAs:更系统化的 4 指标体系

RAGAs 提出的 4 指标是业界事实标准:

指标含义要不要 reference
faithfulness答案里每句话是否能从 context 推出
answer_relevancy答案是否紧扣 query
context_precision召回的 context 里相关 chunk 的位置(前置得分高)是(golden answer)
context_recallgolden answer 里的信息在 context 里的覆盖率
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision, context_recall
from datasets import Dataset

# 准备数据
data = {
    "question": queries,
    "answer": [str(qe.query(q)) for q in queries],
    "contexts": [[n.text for n in qe.query(q).source_nodes] for q in queries],
    "ground_truth": references,
}
ds = Dataset.from_dict(data)

result = evaluate(
    ds,
    metrics=[faithfulness, answer_relevancy, context_precision, context_recall],
)
print(result)
# {"faithfulness": 0.87, "answer_relevancy": 0.92, "context_precision": 0.78, "context_recall": 0.81}

把这 4 个数画成雷达图跟版本迭代一起看——换 embedding 模型后 context_recall 涨、context_precision 降,就是"召回更多但更杂"。

七、TruLens-Eval

TruLens 的卖点是 "RAG 三角":Context Relevance / Groundedness / Answer Relevance——可视化界面直观:

from trulens_eval import Tru, Feedback
from trulens_eval.feedback.provider import OpenAI as TOpenAI
from trulens_eval import TruLlama

provider = TOpenAI(model_engine="gpt-4o")

f_groundedness = Feedback(provider.groundedness_measure_with_cot_reasons).on(
    ...
)
# 包装 query engine,所有 query 自动记录
tru_qe = TruLlama(
    qe, app_id="rag_v1",
    feedbacks=[f_groundedness, f_answer_relevance, f_context_relevance],
)

with tru_qe as r:
    for q in queries:
        qe.query(q)

Tru().run_dashboard()    # localhost:8501 看结果

八、Arize Phoenix:Trace 观测利器

评估告诉你"好不好",观测告诉你"为什么不好"——每一次 query 走了哪些步骤、各段耗时、prompt 长什么样。Phoenix 是 LlamaIndex 官方集成最深的观测平台:

pip install arize-phoenix llama-index-callbacks-arize-phoenix
import phoenix as px
from llama_index.core import set_global_handler

# 1. 启动 Phoenix UI
px.launch_app()          # 打开浏览器 http://localhost:6006

# 2. 一行接入 LlamaIndex
set_global_handler("arize_phoenix")

# 3. 正常跑 RAG,每一次 query 会在 Phoenix 里自动落盘
qe.query("退款流程是什么?")
qe.query("2024 年 Q3 营收?")

# 4. 在 UI 看 trace,点开一个 query 能看到:
#    - retrieve 召回了哪 5 个 chunk 分数多少
#    - synthesize 传给 LLM 的完整 prompt
#    - LLM 的每一 token 输出
#    - 各阶段耗时饼图

Phoenix 还能直接跑 Evaluator 给 trace 打分——让"哪条答得差"跟"为什么"在一个界面里看。生产上强烈推荐。

九、OpenTelemetry / OpenLLMetry

如果你们公司有统一的 observability stack(Datadog/Grafana Tempo/Jaeger),用 OTel 接入更自然:

from openinference.instrumentation.llama_index import LlamaIndexInstrumentor
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace.export import BatchSpanProcessor

provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint="http://otel:4317")))
trace.set_tracer_provider(provider)

LlamaIndexInstrumentor().instrument()
# 之后每个 query/retrieve/llm call 都是标准 OTel span

这样延迟/错误率/成本同一套图表看得到,和 API 服务、DB 查询串在一起。

十、Langfuse / Literal AI:SaaS 产品化

想省事、要团队协作、要长期存储 trace——用 Langfuse 或 Literal AI:

from langfuse.llama_index import LlamaIndexInstrumentor

LlamaIndexInstrumentor(
    public_key="pk-...", secret_key="sk-...",
    host="https://cloud.langfuse.com",
).start()

# 之后所有 LlamaIndex 调用自动上报 Langfuse
# UI 里能看 trace、评估分数、用户反馈、prompt 版本对比、成本

Langfuse 额外好处:支持人工打标(标注员直接在 UI 点"答得好/不好"),这些标签一回到本地就是新黄金集。

十一、在线 A/B:让版本决定谁赢

离线指标再漂亮,上线可能翻车。稳的做法:

  1. 影子流量(shadow):新 pipeline 跟着老 pipeline 同时跑,不返回给用户,对齐指标
  2. 分流 A/B:5% 用户走 V2,和 V1 对比用户侧信号(满意度、追问率、停留时长)
  3. bandit 分流:根据实时 metric 动态调分流比
class ABRouter:
    def __init__(self, v1, v2, v2_ratio=0.05):
        self.v1, self.v2, self.ratio = v1, v2, v2_ratio

    async def query(self, q, user_id):
        variant = "v2" if hash(user_id) % 100 < self.ratio * 100 else "v1"
        qe = self.v2 if variant == "v2" else self.v1
        resp = await qe.aquery(q)
        log_event(user_id, variant, q, str(resp))   # 关键:打标
        return resp

配合后面的用户反馈按钮("👍/👎"),每天看两个变体的分组指标——这才是真正的 RAG 质量反馈闭环。

十二、成本与延迟监控

质量只是一面——生产上要持续看:

维度指标预警阈值示例
延迟p50/p95/p99 端到端p95 > 5s 预警
成本每 query 平均 token、每天总花费day-over-day +30% 预警
召回top_k 命中率、空召回率空召回 > 3% 排查
合成截断率、refusal 率refusal > 5% 看 prompt
用户反馈👎 率、追问率周环比下滑

十三、LLM-as-Judge 的偏差

用 LLM 给 LLM 打分便宜好使,但有系统偏差:

十四、反模式

  1. 只跑 5 条 query 感觉"看起来挺好"就上线:样本太小毫无统计意义。至少 200。
  2. 没有 baseline:V2 Faithfulness 0.85 是好是坏?不知道。每次都和上一版/最朴素版本对比。
  3. 用生产 LLM 自己评自己:自夸偏差严重。评审永远用更强或不同家族的模型。
  4. 只看 hit_rate 不看 MRR:top_10 里命中了但排第 8——相当于给 LLM 喂噪声。
  5. 黄金集一次生成后再不更新:数据漂移,新知识点永远测不到。每月补 bad case。
  6. Phoenix/Langfuse 装了不看:观测工具的价值在 weekly review——固定时间翻 trace 才有收益。
  7. 在线 A/B 没有打标:两个版本流量混在一起,没法归因。variant 一定要写进每条 log。
  8. 忽视用户反馈:一个 👎 比十个 LLM 评估都值钱。务必在产品里埋点。
  9. 评估和代码解耦:改 retriever 后忘跑 eval——CI 里加一步 eval regression test。
  10. 只评单轮 RAG:Agent/Workflow 多步,要拆每一跳独立评。

十五、本章小结

记住:
① 三大指标缺一不可:Faithfulness(反幻觉) + Relevancy(回应问题) + Retrieval(hit_rate/MRR)。
② 黄金集 200 条起步,线上 bad case 持续补——比 LLM 生成的更贴真实分布。
③ RAGAs 4 指标(faithfulness/answer_relevancy/context_precision/context_recall)做离线评,Phoenix/Langfuse 做在线 trace,两手抓。
④ 任何新版本上线前跑 regression eval,5% 影子/分流 A/B,结合用户 👍/👎 闭环——这才是 RAG 的工程化。