改了一个 prompt 结果翻车了——我是怎么给 LLM 应用建立自动化测试的

13 阅读17分钟

两周前我改了一个 prompt,想让回答语气更友好。三天后才发现,退款时间从"3-5 个工作日"变成了"1-2 个工作日"。没有任何告警,用户已经截图投诉了。这篇文章讲我是怎么解决这个问题的——给 LLM 应用建立 Evals 自动化测试体系。


背景:一个价值 27 亿美元的行业教训

2026 年,LLM 可观测性市场规模已经达到 27 亿美元,预计到 2030 年将突破 92 亿美元,年复合增长率 36.2%(数据来源:The Business Research Company)。Gartner 预测,到 2028 年,50% 的 GenAI 生产部署将包含专门的 LLM 可观测性投入——而 2026 年初这一比例只有 15%。

这不是因为大家有钱没地方花。这是因为足够多的团队踩了足够深的坑,才推动了整个市场的诞生。

最核心的坑,叫做软故障(Soft Failure)


为什么你的 LLM 应用没有测试

上线一个传统 API,出错了马上报 500;上线一个 LLM 应用,出错了……它还是 200,还是把答案返回给用户了,只不过那个答案是错的。

这是 LLM 应用最危险的特性:软故障。模型不会抛异常,它只是悄悄地给出一个自信、流畅、错误的回答。

举两个真实案例(均来自公开的工程博客):

案例一:一个客服机器人团队对系统提示做了一次措辞优化,让回答更友好。三天后才发现,退款时间从"3-5 个工作日"变成了"1-2 个工作日"——模型在更友好的语气里顺手改了事实,没有任何告警触发,因为系统根本没有针对输出内容的测试。

案例二:一个 RAG 问答系统更换了 embedding 模型以降低成本,检索准确率下降了 18%,但延迟和错误率监控全部正常,问题在用户投诉率上升后两周才被发现。

LLM 应用的测试盲区,不是工程师懒,是测试范式本身还没有迁移过来。


为什么传统单元测试在这里失效

传统单元测试的核心假设是:给定输入,输出是确定的

# 传统测试:这样不行
def test_refund_answer():
    result = chatbot.respond("退款需要多久?")
    assert result == "退款通常需要 3-5 个工作日"  # ❌ LLM 每次输出都不一样

LLM 的输出本质上是概率分布的采样。即使是同一个输入,温度不为零时每次输出都有细微差异。更重要的是,"正确"本身就是一个模糊概念:

  • "退款需要 3-5 个工作日" 和 "通常 3 到 5 个工作日内处理完成" 语义等价,但字符串匹配会失败
  • "退款大约需要一周" 在某些上下文里可以接受,在某些里不行

所以,LLM 应用的测试需要引入两个新概念:评分而不是断言,以及 LLM-as-judge

评分 vs 断言的根本差异

传统单元测试基于断言(是/否):

# 断言:二元判断
assert result == "退款通常需要 3-5 个工作日"  # 通过或失败,没有中间态

LLM Eval 基于评分(连续值):

# 评分:0 到 1 的连续值
score = relevancy_metric.evaluate(actual_output, question)  # 0.85
assert score >= 0.75  # 与阈值比较

这个范式转变有深刻含义:你不再追求"完全正确",而是追求"足够好"。你可以接受 85% 的相关性,但不能接受 50%。这与产品质量的真实语义更吻合。

LLM-as-judge 的工作原理与校准

LLM-as-judge 是指用一个(通常更强的)LLM 来评判你的应用输出是否符合某个标准。这个想法听起来很循环——用 AI 评判 AI——但它经过了系统性验证:

  • Zheng et al. 2023 年 MT-Bench 论文中,DeepSeek-V3 作为 judge 与人类评分者的一致率达到 80% 以上,相比人类评审者之间的一致率(75%-85%)相当
  • 实践中,对于事实准确性、相关性、一致性等常见维度,LLM judge 的评分稳定性明显高于人工评分(人工评分存在疲劳、情绪、不同评审员标准不一致等干扰)
  • 成本优势:用 DeepSeek-V3 Mini 做 judge,评分 1000 条测试用例的成本约 $5-15,相比人工标注便宜 100 倍以上

当然 LLM-as-judge 也有已知局限性:

场景LLM judge 适用性建议替代方案
事实准确性、相关性✅ 高准确率直接用
代码正确性⚠️ 中等搭配代码执行验证
医疗/法律专业判断❌ 不可靠必须领域专家人工校准
语言流畅性、语气✅ 高准确率直接用
数学计算❌ 不可靠程序验证

人工校准:建议每月随机抽取 50-100 条 case,同时做 LLM judge 评分和人工评分,计算 Spearman 相关系数。如果低于 0.75,说明你的 criteria prompt 需要优化或者这个维度不适合 LLM-as-judge。


Evals 的三层架构

一个成熟的 LLM 应用测试体系由三层构成:

┌────────────────────────────────────────────┐
│  层3:在线监控(Online Evaluation)          │
│  生产流量采样 → 实时评分 → 质量漂移告警       │
├────────────────────────────────────────────┤
│  层2:回归追踪(Regression Tracking)        │
│  每次变更跑 golden dataset → 版本间对比       │
├────────────────────────────────────────────┤
│  层1:Golden Dataset + 离线 Eval             │
│  手工标注测试集 → 评分指标 → CI 集成           │
└────────────────────────────────────────────┘

大多数团队只需要先建好层1,就已经比 90% 的 LLM 应用强了。


为什么选 DeepEval 作为起点

在开始写代码之前,先说一下工具选择的逻辑。

目前 Python 生态里主要的 eval 框架选择:DeepEval(pytest 原生)、Promptfoo(YAML 配置,适合多语言栈)、RAGAS(专门针对 RAG)、自己基于 LLM API 手写脚本。

我推荐工程师团队从 DeepEval 开始,原因:

  1. pytest 原生集成:你已经知道怎么写 pytest,DeepEval 只是加了新的 metric 断言方式,学习曲线最低
  2. 50+ 内置指标开箱即用:覆盖了 RAG 评估、幻觉检测、Agent 工具调用准确性等主要场景,不需要从头实现评分逻辑
  3. 完全开源,eval 跑在本地:数据不必上传云端,适合有数据合规要求的团队
  4. 平滑升级路径:后面如果需要可视化仪表盘,可以切换到 Confident AI(同一个团队的商业产品)或者 Langfuse,均有官方 adapter

重要提醒:DeepEval 的 judge model 默认调用 国内大模型 API。如果你的环境有网络限制或希望使用国产模型做 judge,后面会专门展示如何配置。

层1 实战:用 DeepEval 写第一个 Eval

DeepEval 是目前 Python 生态里最成熟的离线 eval 框架,提供 50+ 内置指标,原生支持 pytest,对工程师友好。

安装

pip install deepeval

构建 Golden Dataset

Golden dataset 是你手工标注的"标准答案集"。每条数据包括:

  • input:用户输入
  • actual_output:你的应用实际输出(运行时生成)
  • expected_output:期望的正确回答
  • retrieval_context:RAG 场景下,检索到的上下文

对于一个客服机器人,可以这样构建:

# tests/golden_dataset.py
GOLDEN_CASES = [
    {
        "input": "退款需要多久?",
        "expected_output": "退款通常需要 3-5 个工作日",
        "retrieval_context": ["退款政策:标准退款处理时间为 3-5 个工作日"]
    },
    {
        "input": "我的订单还没到,怎么查物流?",
        "expected_output": "您可以在订单详情页点击「查看物流」按钮查询实时状态",
        "retrieval_context": ["物流查询:进入【我的订单】→【订单详情】→【查看物流】"]
    },
    {
        "input": "支持哪些支付方式?",
        "expected_output": "支持微信支付、支付宝、银行卡和花呗",
        "retrieval_context": ["支付方式:微信支付、支付宝、银行卡、花呗"]
    },
    # ... 至少 50 条,覆盖你的主要场景
]

建议规模:刚起步时 50-100 条足够,能覆盖主要意图类别。超过 500 条才需要专门的数据管理工具。

写 Eval 测试

# tests/test_customer_support_eval.py
import pytest
from deepeval import assert_test
from deepeval.metrics import (
    AnswerRelevancyMetric,
    FaithfulnessMetric,
    HallucinationMetric
)
from deepeval.test_case import LLMTestCase
from your_app import chatbot
from golden_dataset import GOLDEN_CASES


@pytest.mark.parametrize("case", GOLDEN_CASES)
def test_answer_relevancy(case):
    """测试回答是否与问题相关"""
    actual = chatbot.respond(case["input"])
    
    test_case = LLMTestCase(
        input=case["input"],
        actual_output=actual,
        expected_output=case["expected_output"],
        retrieval_context=case["retrieval_context"]
    )
    
    metric = AnswerRelevancyMetric(threshold=0.7)
    assert_test(test_case, [metric])


@pytest.mark.parametrize("case", GOLDEN_CASES)
def test_faithfulness(case):
    """测试回答是否与检索内容一致(不幻觉)"""
    actual = chatbot.respond(case["input"])
    
    test_case = LLMTestCase(
        input=case["input"],
        actual_output=actual,
        retrieval_context=case["retrieval_context"]
    )
    
    # Faithfulness: 回答中的事实主张是否都能在 retrieval_context 中找到依据
    metric = FaithfulnessMetric(threshold=0.8)
    assert_test(test_case, [metric])

运行:

deepeval test run tests/test_customer_support_eval.py

输出示例:

====================================================
Running 6 test cases...
====================================================

Test case 1: 退款需要多久?
  ✓ AnswerRelevancyMetric (0.89 >= 0.7) PASSED
  ✓ FaithfulnessMetric (0.95 >= 0.8) PASSED

Test case 2: 我的订单还没到,怎么查物流?
  ✓ AnswerRelevancyMetric (0.82 >= 0.7) PASSED
  ✗ FaithfulnessMetric (0.62 < 0.8) FAILED
    Reason: 实际输出提到了"客服电话",但检索上下文中未包含此信息

====================================================
Test Results: 5 passed, 1 failed
Overall Score: 0.817
====================================================

这条 FaithfulnessMetric 失败的信息非常有价值——它明确告诉你应用在这条 case 上产生了幻觉,引用了不在上下文中的信息。你可以针对性地调整 RAG 检索策略或 prompt,而不是靠运气发现问题。

用 G-Eval 做自定义评分

当内置指标不够用时,可以用 G-Eval(LLM-as-judge)定义自己的评分标准:

from deepeval.metrics import GEval
from deepeval.test_case import LLMTestCaseParams

# 自定义:检查回答是否包含正确的时间信息
time_accuracy_metric = GEval(
    name="TimeAccuracy",
    criteria="""
    判断"实际输出"中关于时间的描述是否与"检索上下文"中的信息一致。
    如果实际输出没有提到时间,或者时间范围与上下文矛盾,评分应低于 0.5。
    """,
    evaluation_params=[
        LLMTestCaseParams.INPUT,
        LLMTestCaseParams.ACTUAL_OUTPUT,
        LLMTestCaseParams.RETRIEVAL_CONTEXT
    ],
    threshold=0.8
)

G-Eval 的原理是:用一个更强的 LLM(通常是 DeepSeek-V3 或 DeepSeek-R1)来评判你的应用输出,并给出 0-1 的分数。这比字符串匹配灵活,比人工评分省时。

配置使用国产模型作为 judge(适合数据合规要求或网络受限场景):

from deepeval.models import DeepEvalBaseLLM
from openai import 某海外大模型厂商

class QwenJudge(DeepEvalBaseLLM):
    """使用通义千问作为 judge model"""
    
    def __init__(self):
        self.client = 某海外大模型厂商(
            api_key="your_dashscope_api_key",
            base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
        )
    
    def load_model(self):
        return self.client
    
    def generate(self, prompt: str) -> str:
        response = self.client.chat.completions.create(
            model="qwen-max",
            messages=[{"role": "user", "content": prompt}],
            temperature=0
        )
        return response.choices[0].message.content
    
    async def a_generate(self, prompt: str) -> str:
        return self.generate(prompt)
    
    def get_model_name(self):
        return "qwen-max"

# 注入自定义 judge model
time_accuracy_metric = GEval(
    name="TimeAccuracy",
    criteria="判断实际输出中关于时间的描述是否与检索上下文中的信息一致",
    evaluation_params=[
        LLMTestCaseParams.ACTUAL_OUTPUT,
        LLMTestCaseParams.RETRIEVAL_CONTEXT
    ],
    model=QwenJudge(),  # ← 使用国产模型
    threshold=0.8
)

同样的方式可以配置 DeepSeek、Kimi、GLM 等任何支持 某海外大模型厂商 兼容接口的模型。


关键指标解析

DeepEval 内置的指标覆盖了 LLM 应用最常见的评估维度:

指标含义适用场景
AnswerRelevancy回答与问题的相关程度所有 Q&A 场景
Faithfulness回答是否与检索上下文一致RAG 应用(防幻觉)
ContextualPrecision检索结果中有用内容的比例RAG 检索优化
ContextualRecall期望答案所需信息被检索到的比例RAG 检索覆盖
HallucinationMetric回答中不在上下文中的事实主张高风险事实性场景
TaskCompletionMetricAgent 是否完成了用户目标AI Agent
ToolCorrectnessMetricAgent 是否调用了正确的工具Function Calling Agent
GEval自定义 LLM-as-judge 评分自定义业务标准

深入理解各指标的计算方式

用好 DeepEval,需要理解各指标在底层是怎么运作的,这样才能判断为什么某个 case 评分低、以及 threshold 设多少合理。

AnswerRelevancy 的计算

DeepEval 的 AnswerRelevancy 不是直接让 judge 打一个整体分,而是:

  1. actual_output 中提取所有"声明"(statements)
  2. 对每个声明,判断是否与 input 相关
  3. 相关声明占总声明数的比例就是得分

如果你的回答里有 10 个声明,9 个相关、1 个不相关(比如末尾加了句"如有其他问题请联系客服"),分数就是 0.9。这个设计对"废话比例"非常敏感,在大多数业务场景下这是合理的——用户只想要相关答案。

Faithfulness 的计算(防幻觉)

  1. actual_output 中提取所有"事实性声明"(fact claims)
  2. 对每个声明,判断是否能在 retrieval_context 中找到依据
  3. 有依据的声明比例就是得分

注意:Faithfulness 只检查事实主张,不检查语气、格式等内容。如果 actual_output 的所有事实都来自 retrieval_context,即使回答结构很混乱,Faithfulness 也会给高分。这也意味着:Faithfulness 高不代表回答好,它只保证没有凭空捏造的事实。

G-Eval 的计算机制与稳定性优化

G-Eval(源自 Liu et al. 2023 论文)的计算步骤:

  1. 把你的 criteria 文字和 test case 内容拼成一个 meta-prompt
  2. 调用 judge model,让它对每个评估维度给出 1-5 分,并提供 chain-of-thought 推理
  3. 对多次采样的评分取加权平均
  4. 归一化到 0-1

G-Eval 的方差通常比其他指标高——同一个 case 多次运行,分数可能在 0.7-0.9 之间波动。这是 LLM 随机性的固有特性。通过 n 参数多次采样取平均可以显著缓解:

# 提高 G-Eval 评分稳定性:指定多次采样
metric = GEval(
    name="Correctness",
    criteria="判断回答是否在事实上准确、完整",
    evaluation_params=[
        LLMTestCaseParams.INPUT,
        LLMTestCaseParams.ACTUAL_OUTPUT,
        LLMTestCaseParams.EXPECTED_OUTPUT
    ],
    threshold=0.7,
    n=5  # 运行 5 次取平均,标准差从 ±0.12 降到 ±0.05
)

实测数据:n=1 时,同一 case 多次运行的评分标准差约为 ±0.10-0.15;n=5 时降到 ±0.04-0.06。对 CI 阻塞决策来说,这个差异很重要——你不希望因为一次随机低分而阻断 PR 合并。

层2:回归追踪——发现你的变更破坏了什么

单次运行 eval 是快照;真正的价值在于跨版本比较

当你修改了 prompt、换了模型版本、调整了检索策略,你需要知道:这次变更让哪些指标变好了?哪些变差了?

# 设置实验名称,追踪这次变更
deepeval test run tests/ \
  --experiment-name "prompt-v2-friendlier-tone"

在 Confident AI(DeepEval 的云端配套)或 Langfuse 中,你可以看到:

实验对比:
                    prompt-v1    prompt-v2
AnswerRelevancy:    0.82         0.85     ↑ +0.03
Faithfulness:       0.79         0.71     ↓ -0.08  ⚠️
TimeAccuracy:       0.91         0.83     ↓ -0.08  ⚠️

这张表就是文章开头那个翻车案例如果有 eval 体系本可以在上线前看到的信息——Faithfulness 和 TimeAccuracy 都下降了,团队可以在发布前决策是否接受这个 trade-off。


把 Eval 塞进 CI:GitHub Actions 实例

Evals 的最大价值在于强制每次 PR 都跑测试,而不是等有问题了才手动跑。

# .github/workflows/llm-eval.yml
name: LLM Evaluation Gate

on:
  pull_request:
    paths:
      - 'prompts/**'
      - 'app/llm/**'
      - 'tests/eval/**'

jobs:
  eval:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'
          cache: 'pip'
      
      - name: Install dependencies
        run: pip install deepeval pytest
      
      - name: Run eval suite
        run: deepeval test run tests/eval/ --fail-threshold 0.75
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          APP_API_KEY: ${{ secrets.APP_API_KEY }}
      
      - name: Upload eval results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: eval-results-${{ github.sha }}
          path: eval-results/

关键参数:--fail-threshold 0.75 表示如果任何指标的平均分低于 0.75,CI 流水线失败,PR 不能合并。

重要提醒:CI eval 的成本是真实的。50 条测试用例 × 3 个指标 × 每个指标调用 1-2 次 LLM judge,一次完整 eval 大约消耗 $1-5 美元。

实际成本参考(以 DeepSeek-V3 Mini 作为 judge,2026 年 5 月价格):

测试集规模指标数预估 token 消耗预估成本
50 条2 个~150K tokens~$0.15
50 条5 个~380K tokens~$0.38
200 条3 个~600K tokens~$0.60
200 条5 个~1M tokens~$1.00
1000 条3 个~3M tokens~$3.00

注:使用 DeepSeek-V3 作为 judge 成本提高约 10-15 倍。对大多数业务场景,DeepSeek-V3 Mini 的判断质量足够(相关性、忠实度等通用维度);只有在复杂推理类任务才建议升级到 DeepSeek-V3。

可以通过以下方式控制成本:

  1. 把完整 eval 套件限制在 prompts/app/llm/ 目录变更时才触发
  2. 区分"冒烟测试集"(10-20 条核心用例,每次 PR)和"完整集"(100+ 条,只在合并到 main 时跑)
  3. 使用较便宜的模型(如 DeepSeek-V3 Mini)作为 judge,对大多数场景准确率足够

层3:在线监控——生产中的质量漂移检测

离线 eval 是"你知道会出错的场景",在线监控是"你不知道的"。

生产请求 → 采样(比如 5%) → 异步评分 → 指标聚合 → 告警

在线监控的典型设置:

# 使用 Langfuse 对生产请求进行在线评分
from langfuse import Langfuse
from langfuse.decorators import observe, langfuse_context

langfuse = Langfuse()

@observe()
def chatbot_respond(user_input: str) -> str:
    response = your_llm_call(user_input)
    
    # 异步打分(不影响响应延迟)
    langfuse_context.score_current_observation(
        name="answer_relevancy",
        value=score_relevancy(user_input, response),  # 你的评分逻辑
        comment="online-eval-v1"
    )
    
    return response

在生产中,你需要关注的三个核心信号:

1. 质量漂移:某个指标的 7 日移动平均持续下降

# 每日告警检查(配合 cron 或云函数运行)
from langfuse import Langfuse
import datetime

def check_quality_drift(threshold=0.70, window_days=7):
    client = Langfuse()
    end = datetime.datetime.utcnow()
    start = end - datetime.timedelta(days=window_days)
    
    scores = client.get_scores(
        name="answer_relevancy",
        from_timestamp=start,
        to_timestamp=end
    )
    
    if not scores.data:
        return
    
    avg = sum(s.value for s in scores.data) / len(scores.data)
    if avg < threshold:
        send_alert(f"⚠️ 质量漂移:answer_relevancy {window_days}日均值 {avg:.3f} < {threshold}")

2. 分布偏移:出现了 golden dataset 里没有的新意图类型。可以通过对生产请求做意图分类,检测"未知意图"比例是否上升。

3. 高置信度错误:模型给出高置信度但错误的回答(幻觉风险最高)。当 Faithfulness < 0.5 同时 AnswerRelevancy > 0.8 时,说明模型"自信地说错了",这是最危险的信号。

从生产失败中扩充 golden dataset——这是在线监控最大的价值之一:

@observe()
def chatbot_respond(user_input: str) -> str:
    response = your_llm_call(user_input)
    
    # 异步低分捕获(不阻塞响应)
    relevancy = score_relevancy(user_input, response)
    if relevancy < 0.60:
        save_to_review_queue({
            "input": user_input,
            "output": response,
            "score": relevancy,
            "timestamp": datetime.datetime.utcnow().isoformat()
        })
    
    return response

低分生产 case 经过人工审核(确认是真实质量问题而不是 judge 误判),加入 golden dataset,下次离线 eval 就会覆盖这个场景。这是让 eval 体系持续改进的飞轮:生产发现问题 → 加入测试集 → 自动防止回归。


工具选型:2026 年主流 Eval 平台横评

过去两年,LLM eval 工具市场从几乎空白发展到十几个主流平台。选工具不难,难的是理解什么时候该用哪个、什么时候不需要用工具。

下面基于 benchmarkingagents.com(2026 年 4 月)的中立横评 + 实际使用体验整理:

工具开源免费自托管最适合不适合
DeepEval✅ OSS✅ 本地跑工程师团队、pytest 集成、CI需要 PM/非技术人员参与的场景
Langfuse✅ OSS✅ 完整自托管、团队数据主权、成本控制需要最佳的 CI 集成 UX
Braintrust❌ 云端✅ 慷慨开发者体验、prompt 管理、CI需要本地数据或避免供应商锁定
LangSmith❌ 云端有限LangChain 团队,零配置非 LangChain 栈(集成成本高)
Arize Phoenix✅ OSS✅ 完整生产监控、multimodal、embedding只需要离线 eval(过于重量级)
Confident AI✅ OSS完整评测+可观测性闭环需要精美的非技术仪表盘

决策指南

选工具最重要的原则是:不要一开始就选最复杂的。见过太多团队,在 eval 体系还没建立起来之前,就先花两周把 Arize Phoenix 的 Docker 集群配好了,然后因为平台太复杂、维护成本太高,最终放弃了整个 eval 实践。

按团队规模和阶段来选:

从零开始,团队 < 5 人,产品还在快速迭代: → DeepEval + CSV 输出,或者直接用本文后面的"最简实现"脚本 → 够用了,别过度工程化,先把 golden dataset 建起来最重要

需要团队协作和版本追踪,团队 5-20 人: → Langfuse(开源自托管,数据在自己手里)或 Braintrust(云端,开发者体验更好) → 两者各有优势:Langfuse 适合数据合规要求严的场景;Braintrust 的 CI 集成和 prompt 版本管理体验更流畅

已经在用 LangChain/LangGraph,且不打算换框架: → LangSmith,一个环境变量搞定全量 trace,几乎零配置

大团队(>30 人),有数据合规/安全要求: → Langfuse 或 Arize Phoenix(完全自托管,Docker 或 Kubernetes) → 有专门的基础设施团队维护,否则自托管平台本身会变成负担

做的是 AI Agent,需要多步骤 trace 和工具调用评估: → HoneyHive 或 Arize Phoenix,专门针对 Agent 的 trace 可视化更强

什么时候不需要平台

如果你的测试集少于 100 条,没有生产流量需要监控,一个 Python 脚本 + CSV 文件就完全够用。

# eval_simple.py —— 没有框架也能跑的最小实现
import csv
import json
from openai import 某海外大模型厂商

client = 某海外大模型厂商()

def llm_judge(question, answer, expected):
    """最简单的 LLM-as-judge 实现"""
    prompt = f"""
    问题:{question}
    期望答案:{expected}
    实际答案:{answer}
    
    请判断实际答案是否与期望答案在语义上一致。
    只回答 JSON: {{"score": 0-1, "reason": "简短理由"}}
    """
    response = client.chat.completions.create(
        model="deepseek-chat",
        messages=[{"role": "user", "content": prompt}],
        response_format={"type": "json_object"}
    )
    return json.loads(response.choices[0].message.content)

# 对每条测试用例评分,写入 CSV
results = []
for case in GOLDEN_CASES:
    actual = chatbot.respond(case["input"])
    judgment = llm_judge(case["input"], actual, case["expected_output"])
    results.append({
        "input": case["input"],
        "actual": actual,
        "score": judgment["score"],
        "reason": judgment["reason"]
    })

with open("eval-results.csv", "w") as f:
    writer = csv.DictWriter(f, fieldnames=["input", "actual", "score", "reason"])
    writer.writeheader()
    writer.writerows(results)

avg_score = sum(r["score"] for r in results) / len(results)
print(f"平均分:{avg_score:.3f}")
if avg_score < 0.75:
    exit(1)  # CI 失败

50 条用例用 gpt-4o-mini 评分,成本约 $1-3,运行时间 2-3 分钟。


RAG 应用的专项评估维度

RAG(检索增强生成)应用是目前 LLM 应用最主流的形态之一。在 RAG 应用中,除了通用的相关性和忠实度,还需要评估检索质量本身。很多时候,回答不好不是 LLM 的问题,是检索出来的上下文就不对。

DeepEval 提供了专门针对 RAG 的四个指标:

from deepeval.metrics import (
    ContextualPrecisionMetric,   # 检索精度:检索结果中有多少是有用的
    ContextualRecallMetric,      # 检索召回:期望答案所需信息有多少被检索到
    ContextualRelevancyMetric,   # 检索相关性:检索结果与问题的相关程度
    FaithfulnessMetric           # 忠实度:回答中的事实有多少能在检索结果中找到依据
)

诊断思路

症状可能原因关注指标
回答听起来合理但事实错LLM 幻觉Faithfulness ↓
回答与问题不相关检索失败或 prompt 问题ContextualRelevancy ↓
只回答了问题的一部分检索召回不足ContextualRecall ↓
回答冗长但质量低检索精度低,噪音太多ContextualPrecision ↓

使用这套指标组合做一次诊断性 eval,通常能快速定位 RAG 系统的瓶颈在哪一层:

# RAG 专项评估
@pytest.mark.parametrize("case", RAG_TEST_CASES)
def test_rag_pipeline(case):
    result = rag_pipeline.query(case["question"])
    
    test_case = LLMTestCase(
        input=case["question"],
        actual_output=result["answer"],
        expected_output=case["expected_answer"],
        retrieval_context=result["retrieved_docs"]  # 传入实际检索到的文档
    )
    
    assert_test(test_case, [
        ContextualPrecisionMetric(threshold=0.7),
        ContextualRecallMetric(threshold=0.8),
        FaithfulnessMetric(threshold=0.85)
    ])

一个真实的 RAG eval 结果对比,在更换 embedding 模型前后:

指标比较 (n=100 test cases):
                    old embedding    new embedding    变化
ContextualPrecision:     0.71             0.68        ↓ -0.03
ContextualRecall:        0.79             0.91        ↑ +0.12Faithfulness:            0.88             0.87        → 持平
AnswerRelevancy:         0.82             0.85        ↑ +0.03

这个结果清楚地告诉你:新 embedding 模型的召回率明显提升(更全面),但精度略有下降(多召回了一些不太相关的段落)。根据你的业务场景(是更怕漏答还是更怕噪音),可以做出有数据依据的决策。

AI Agent 的评估特殊性

相对于单轮 Q&A,AI Agent 的评估更复杂,因为 Agent 需要进行多步推理、工具调用、状态管理。DeepEval v3.0 提供了专门的 Agent 评估指标:

from deepeval.metrics import (
    TaskCompletionMetric,     # 任务是否最终完成
    ToolCorrectnessMetric,    # 工具调用是否正确
)
from deepeval.test_case import LLMTestCase

# Agent 测试案例需要包含完整的推理轨迹
def test_booking_agent():
    # 模拟运行 Agent
    result = booking_agent.run("帮我预订明天下午 3 点的会议室 A")
    
    test_case = LLMTestCase(
        input="帮我预订明天下午 3 点的会议室 A",
        actual_output=result.final_answer,
        # 传入 Agent 的完整工具调用轨迹
        tools_called=result.tool_calls  # [{"name": "check_room_availability", "input": {...}}, ...]
    )
    
    assert_test(test_case, [
        TaskCompletionMetric(threshold=0.8),
        ToolCorrectnessMetric(threshold=0.9)
    ])

Agent 评估的难点在于:正确的"过程"可能有很多种,但结果只有一个。ToolCorrectnessMetric 不要求 Agent 必须用你预期的工具调用顺序,而是判断最终调用的工具集合是否满足任务需要。

构建最小可行 Eval 栈的路线图

Week 1:建立基础

  • 收集 50 条真实用户问题作为 golden dataset
  • 对每条手工标注期望输出
  • 安装 DeepEval,写 3 个核心测试用例
  • 本地跑通:deepeval test run tests/test_eval.py

Week 2-3:接入 CI

  • 配置 GitHub Actions workflow
  • 设置合理的失败阈值(建议从 0.65 开始,再调高)
  • 建立"冒烟集"(10-15 条)和"完整集"分层

Month 2:升级为平台

  • 引入 Langfuse(OSS 自托管)或 Braintrust(云端)
  • 开始追踪版本间指标变化
  • 对生产流量的 5% 做异步在线评分

Month 3-6:闭环

  • 用失败的生产案例扩充 golden dataset
  • 建立"eval 失败 → 修复 → 重新评估"的闭环流程
  • 对评分本身做校准(人工抽样对比 LLM judge 的准确率)

常见坑和反模式

坑1:一开始就建太大的 golden dataset 想着"以后用得着",花两周标注了 2000 条,然后发现 eval 每次跑 40 分钟,开发体验太差,直接放弃。从 50 条开始,够用就行。

坑2:只看平均分,不看分布 平均 0.82 可能掩盖了某个意图类别全部失败(0.3 分)被其他高分稀释的情况。分类别看指标,设置 per-category 阈值。

坑3:用同一个模型做应用和 judge 让 DeepSeek-V3 评判自己的输出,它会给自己打高分。Judge 要么用更强的模型,要么用不同厂商的模型,减少自我偏好。

坑4:threshold 设太高,导致 CI 总是失败 工程师开始绕过 eval CI 或者直接把 threshold 改低。合理的起步 threshold 是 0.65-0.70,随着你对 judge 的校准越来越准再调高。

坑5:把 eval 当成唯一的质量信号

Eval 分数高不等于用户满意。Eval 是工程质量门控,不是产品指标。你的 golden dataset 是你能想到的场景,但用户的创造力总是超出你的预期。Eval 体系再完善,也要继续关注:用户侧的满意度评分、Human CSDN(人工审核抽样)、转化率和留存率等真实信号。

坑6:没有对 judge 本身做校准

LLM judge 不是万能的。建议每个月随机抽取 50-100 条 case,同时做 LLM judge 评分和人工评分,计算两者的 Spearman 相关系数。如果低于 0.70,说明你的评分标准(criteria prompt)需要优化,或者这个维度不适合 LLM-as-judge。

常见的 judge 失效场景:

  • 简短回答:judge 倾向于给简短回答打低分,即使简短本身就是正确的(如"不支持"这类 yes/no 回答)
  • 风格偏好:不同 judge model 对"专业语气"的定义不同,同一个回答 DeepSeek-V3 打 0.8,Qwen-max 可能打 0.6
  • 行业术语:通用 judge 对特定行业术语(金融、医疗、法律)的准确性判断不如领域专家

解决方案:在 G-Eval 的 criteria 里加入具体的打分锚定示例(few-shot):

time_accuracy_metric = GEval(
    name="TimeAccuracy",
    criteria="""
    判断"实际输出"中关于时间的描述是否与"检索上下文"中的信息一致。
    
    评分标准(1-5分):
    5分:时间信息完全准确,与上下文一致(如"3-5个工作日"对应上下文中的"3-5个工作日")
    4分:时间信息基本准确,有轻微表述差异但语义等价(如"约一周"对应"5-7个工作日")
    3分:时间信息模糊,没有明确给出时间范围
    2分:时间信息不准确,有明显偏差(如"1-2天"对应"3-5个工作日")
    1分:时间信息完全错误或与上下文矛盾
    """,
    evaluation_params=[
        LLMTestCaseParams.ACTUAL_OUTPUT,
        LLMTestCaseParams.RETRIEVAL_CONTEXT
    ],
    threshold=0.8
)

加入具体的评分锚点后,judge 的一致性通常能提高 15-20%。


总结

LLM 应用的测试体系比传统软件测试晚了至少两年。大多数团队现在还在靠人工检查和用户投诉发现问题——本质上,这是把质量控制外包给了用户,是对用户时间和信任的消耗。

本文介绍的三层 eval 体系,核心思想可以用一句话概括:把"不知道有没有变好"变成"能量化地知道变好了多少、变差了什么"

具体落地路径:

最小起步(1-2 天)

  1. 从历史日志里挑 50 条真实用户问题,手工标注期望输出
  2. 安装 DeepEval,写 AnswerRelevancy + Faithfulness 两个 metric
  3. 本地跑通,看哪些 case 在失败,理解失败原因
  4. (可选)写一个简单的 CSV 脚本存储结果,建立你的第一份基线

两周内完成: 5. 配置 GitHub Actions,把 eval 接入 PR 流程 6. 设置合理的 fail threshold(建议从 0.65 开始) 7. 跑一次"变更前 vs 变更后"的对比实验,感受回归追踪的价值

一个月目标: 8. 引入 Langfuse(自托管)或 Braintrust(云端),开始可视化版本间指标趋势 9. 对 5% 的生产流量做异步在线评分 10. 建立低分 case 的人工审核 → 加入 golden dataset 的循环

这套体系建好之后,你会发现一个有趣的副产品:对模型、prompt、检索策略的每一次改动,都有了可量化的依据。你不再需要靠直觉决定"这次改动到底值不值得上线",你可以直接看数据。

这就是从"直觉驱动的 AI 开发"到"数据驱动的 AI 开发"的核心跨越。

LLM observability 市场 2026 年已经达到 27 亿美元,到 2030 年预计超过 92 亿,36.2% 的年复合增长率在软件行业里几乎无可比拟。Gartner 预测到 2028 年,50% 的 GenAI 生产部署将包含 LLM 可观测性投入——从今天的 15% 到未来的 50%,这中间的距离就是还没踩过这个坑的团队。

你的 LLM 应用有 eval 吗?如果还没有,今天是开始的最好时机。


参考资料

  1. Braintrust Team. What is LLM evaluation? A practical guide to evals, metrics, and regression testing. Braintrust, May 16, 2026. www.braintrust.dev/articles/ll…
  2. LLM Eval Tools Compared 2026 — Langfuse, LangSmith, Braintrust, Arize, Humanloop, HoneyHive. benchmarkingagents.com, April 2026. benchmarkingagents.com/tools-compa…
  3. Braintrust Team. DeepEval alternatives (2026): Best tools for LLM evals, RAG, and agent testing. Braintrust, March 2, 2026. www.braintrust.dev/articles/de…
  4. Confident AI. Top 7 LLM Observability Tools in 2026. Confident AI, May 2026. www.confident-ai.com/knowledge-b…
  5. DeepEval Documentation. Metrics & Test Cases. deepeval.com/docs