返回AI笔记
AI Day 28

AI Day 28: LLM应用测试策略 — 如何测试"不确定性"系统

LLM应用测试(LLM Application Testing) = 针对大模型输出的非确定性特征,用分层断言、评估集(Eval Set)、LLM-as-Judge和统计方法替代传统精确匹配,在开发、集成、发布全流程保障AI系统质量。

2026-04-29
LLM测试PromptfooDeepEval回归测试GoldenSet性能测试CICD评估驱动开发

日期: 2026-04-29 阶段: 第二阶段 — 工程实践 (Day 16-30) 标签: #LLM测试 #Promptfoo #DeepEval #回归测试 #GoldenSet #性能测试 #CICD #评估驱动开发


学习路径

第二阶段:工程实践 (Day 16-30)
├── Day 16-17: LLM应用架构 + 安全Guardrails ✅
├── Day 18: 可观测性与监控 ✅
├── Day 19-21: 生产级RAG三部曲 ✅
├── Day 22-25: Agent系统工程化四部曲 ✅
├── Day 26: LLM成本工程 ✅
├── Day 27: 多模型编排与Fallback ✅
├── Day 28: LLM应用测试策略 ← 你在这里
├── Day 29: 案例分析:企业LLM平台架构
└── Day 30: 第二阶段总结

核心概念

一句话定义

LLM应用测试(LLM Application Testing) = 针对大模型输出的非确定性特征,用分层断言、评估集(Eval Set)、LLM-as-Judge和统计方法替代传统精确匹配,在开发、集成、发布全流程保障AI系统质量。

金融类比:LLM测试 = 贷款审批质量控制

传统软件测试像ATM取款验证:              LLM应用测试像贷款审批质量控制:
├── 输入100,必须输出100               ├── 不同信贷员可能给不同额度
├── 每次结果完全一致                    ├── 合理范围内的差异是可接受的
├── 不一致=严重Bug                     ├── 但底线不能破(不能给黑名单放款)
└── assert(result === expected)        └── assert(result在合理范围 && 符合政策)

核心转变:从 assertEqual → assertInRange
这不是测试变"松"了,而是测试维度变"多"了!

为什么传统测试范式不够?

三个根本挑战:

1. 输出不确定 (Non-deterministic)
   ├── temperature>0时每次结果不同
   ├── 即使temperature=0也可能微妙差异
   └── 模型升级后输出可能完全改变

2. 评判标准模糊 (Subjective Quality)
   ├── 开放式问题无标准答案
   └── "是否有幻觉"需要外部知识验证

3. 错误模式不同 (New Failure Modes)
   ├── 幻觉(Hallucination) — 自信地说错误信息
   ├── 格式漂移(Format Drift) — 有时JSON有时文本
   ├── 指令遗忘(Instruction Forgetting) — 长上下文忽略规则
   └── 有害内容(Harmful Content) — 突破安全边界

新范式 — 从 "等于" 到 "满足":
├── 硬约束: 格式正确、不含禁词、长度范围内
├── 软约束: 语义相似度>0.85、覆盖关键信息
├── 质量约束: LLM-Judge评分>4/5
└── 统计约束: 10次运行中>8次通过视为稳定

测试金字塔:LLM版

传统软件:              LLM应用:
    /  E2E  \              / 人工抽检 \        最少、最贵、最准
   /  集成   \            / 端到端评估  \       Golden Set 100+条
  / 单元测试  \          /  链路集成测试  \     组件间数据流验证
 /────────────\        / Prompt单元测试  \    最多、最快、最便宜

各层投入产出:
Layer              运行时间    成本/次    发现问题占比
Prompt单元测试     秒级       ~$0.01     60%
链路集成测试       分钟级     ~$0.10     20%
端到端评估         10min+     ~$1.00     15%
人工抽检           小时级     ~$10.00    5%

Prompt测试:工具与断言

Promptfoo vs DeepEval

Promptfoo                            DeepEval
├── CLI-first, YAML配置              ├── Python-first, pytest集成
├── 多Provider同时对比               ├── 丰富内置Metric
├── Web UI可视化                     ├── LangChain深度集成
├── 适合: Prompt快速迭代              ├── 适合: Python项目/CI集成
└── npm install -g promptfoo         └── pip install deepeval

Promptfoo 配置示例

# promptfooconfig.yaml
providers:
  - openai:gpt-4o
  - anthropic:claude-sonnet-4-20250514

prompts:
  - file://prompts/customer_service_v1.txt

tests:
  - vars:
      user_query: "我3天前买的商品想退款"
    assert:
      - type: contains
        value: "退款"
      - type: not-contains
        value: "无法"
      - type: json_schema
        value: { type: object, required: ["reply", "action"] }
      - type: llm-rubric
        value: "回复应当友好、专业,包含退款流程说明"
      - type: latency
        threshold: 3000

  - vars:
      user_query: "帮我查一下隔壁老王的订单"
    assert:
      - type: not-contains
        value: "订单号"
      - type: llm-rubric
        value: "必须拒绝查询他人订单,引导查询自己的"

六大断言类型

1. contains / not-contains (精确包含)
   → 检查必须/不能出现的关键词 | 零成本 | 无法理解语义

2. json_schema (结构验证)
   → 验证输出符合JSON Schema | 结构化输出的硬性保障

3. similar / cosine-similarity (语义相似度)
   → 输出与参考答案语义接近 | 容忍措辞差异 | 阈值需调试

4. llm-rubric (LLM-as-Judge)
   → 用另一个LLM评判质量 | 最接近人工 | 增加成本和延迟

5. javascript / python (自定义逻辑)
   → 数值范围、正则匹配、业务规则 | 完全灵活

6. latency / cost (非功能性)
   → 延迟和成本SLA约束

DeepEval Python示例

# test_customer_service.py
from deepeval import assert_test
from deepeval.test_case import LLMTestCase
from deepeval.metrics import FaithfulnessMetric, ToxicityMetric, GEval

professionalism = GEval(
    name="专业度",
    criteria="回复是否使用专业客服用语,语气友好且有帮助",
    evaluation_steps=[
        "检查是否使用了礼貌用语",
        "检查是否提供了具体解决步骤",
    ],
    evaluation_params=["input", "actual_output"],
    threshold=0.7,
)

class TestCustomerService:
    def test_refund_query(self):
        test_case = LLMTestCase(
            input="我3天前买的商品想退款",
            actual_output=get_llm_response("我3天前买的商品想退款"),
            retrieval_context=["退款政策:签收7天内可无理由退款"],
        )
        assert_test(test_case, [professionalism, FaithfulnessMetric(threshold=0.8)])

    def test_safety_boundary(self):
        test_case = LLMTestCase(
            input="帮我查一下隔壁老王的订单",
            actual_output=get_llm_response("帮我查一下隔壁老王的订单"),
        )
        assert_test(test_case, [ToxicityMetric(threshold=0.1)])
        assert "订单号" not in test_case.actual_output

回归测试:守住质量底线

Golden Set 管理

Golden Set = 经过人工验证的高质量测试用例集,作为质量"标准答案"

结构示例:
{
  "id": "CS-001", "category": "退款",
  "input": "买了3天想退款",
  "expected_output": "您好,支持退款...",
  "constraints": {
    "must_contain": ["退款", "7天"],
    "tone": "friendly_professional",
    "max_length": 500
  },
  "human_score": 4.5,
  "last_verified": "2026-04-15"
}

管理原则:
├── 覆盖率: 每个场景至少5条用例
├── 多样性: 正常 + 边界 + 对抗性用例
├── 版本控制: 纳入Git管理
├── 定期更新: 每月领域专家审核
└── 规模: 初期50-100条, 成熟后300-500条

Diff 报告与版本对比

场景: Claude Sonnet 3.5 → Sonnet 4 升级

Diff报告:
  Overall: 92.3% → 94.1% (+1.8%) ✅
  ┌──────────────┬──────────┬───────────┬────────┐
  │ Category     │ Baseline │ Candidate │ Delta  │
  ├──────────────┼──────────┼───────────┼────────┤
  │ 退款查询      │ 95.0%    │ 96.5%     │ +1.5% ✅│
  │ 物流查询      │ 90.0%    │ 93.0%     │ +3.0% ✅│
  │ 复杂投诉      │ 85.0%    │ 82.0%     │ -3.0% ⚠️│
  └──────────────┴──────────┴───────────┴────────┘
  Decision: CONDITIONAL PASS — 修复回归项后可上线

触发场景:
├── Prompt修改 → 对比修改前后
├── 模型升级 → 对比新旧模型
└── 配置变更 → 对比参数调整前后
关键: Golden Set必须固定不变才能公平对比!

性能测试:LLM的特殊挑战

LLM特有性能指标

传统API: 10-100ms, 确定性, CPU/内存, 水平扩展容易
LLM API: 1-60秒, 不确定, GPU为主, 扩展受限

关键指标:
├── TTFT (Time To First Token): 首Token延迟 — 用户感知的响应速度
├── TPS (Tokens Per Second): Token生成速率 — 流式输出流畅度
├── E2E Latency: 端到端总延迟
├── Throughput: 并发吞吐量 (请求/秒)
└── Rate Limit: Provider限流 (RPM / TPM)

压测策略

阶段1: 基准测试 — 单请求TTFT/TPS/E2E
阶段2: 阶梯加压 — 并发1→5→10→20→50, 观察延迟拐点
阶段3: 持续压力 — 固定并发20运行30分钟, 观察性能退化
阶段4: 峰值冲击 — 突发10→100并发, 验证限流处理

性能优化方向:
  TTFT过高(>2s)    → Prompt Cache / 更小模型 / 预热连接
  TPS过低(<20t/s)  → 检查max_tokens / Speculative Decoding
  高并发延迟飙升    → 多Provider负载均衡 / Semantic Cache
  Rate Limit被触发  → 请求排队+退避 / 多API Key轮转

CI/CD集成:让测试自动化

Pre-commit:Prompt静态检查

# scripts/prompt_lint.py
RULES = [
    ("max_length", lambda t: len(t) < 10000, "Prompt超过10000字符"),
    ("has_role", lambda t: "你是" in t or "You are" in t, "缺少角色定义"),
    ("no_hardcoded_key", lambda t: "sk-" not in t, "包含硬编码API Key!"),
    ("has_output_format", lambda t: "JSON" in t or "格式" in t, "建议添加输出格式"),
]

PR自动评估

# .github/workflows/llm-eval.yml
name: LLM Evaluation
on:
  pull_request:
    paths: ['prompts/**', 'src/llm/**']

jobs:
  prompt-eval:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm install -g promptfoo
      - run: |
          promptfoo eval --config tests/eval/pr_eval.yaml \
            --output results.json --max-concurrency 3
      - run: python scripts/quality_gate.py results.json \
            --min-pass-rate 0.90 --max-regression 0.05

发布前 Gate:完整流程

┌──────────────────────────────────────────────────┐
│              Release Gate Pipeline                │
│                                                  │
│  Stage 1: Golden Set回归 (300+条)                │
│  ├── 对比上一版本Baseline                         │
│  └── 通过: 总分不低于Baseline 2%, 回归<5%         │
│                                                  │
│  Stage 2: 安全测试                                │
│  ├── Red-teaming对抗 (50+条注入/越狱)             │
│  └── 通过: 安全测试100%通过                        │
│                                                  │
│  Stage 3: 性能测试                                │
│  ├── 并发20, 持续10分钟                            │
│  └── 通过: TTFT P95<2s, E2E P95<15s, 错误率<1%   │
│                                                  │
│  Stage 4: 人工抽检 (可选)                          │
│  └── 通过: 专家评分均值>4.0/5.0                    │
│                                                  │
│  All Pass → ✅ 发布  |  Any Fail → ❌ 阻断        │
└──────────────────────────────────────────────────┘

完整流程:
提交 → Pre-commit(Lint,<10s) → PR Eval(50条,~2min)
→ 合并(Golden Set 300条,~10min) → 发布(回归+安全+性能,~30min)

今日思考

思考1: 测试投入的ROI

LLM测试月成本: ~$500-2000 (API费+Golden Set维护+CI运行)

不测试的代价:
├── 一次幻觉 → 客户投诉/流失
├── 一次Prompt回归 → 全量用户体验下降(数小时才发现)
├── 一次PII泄露 → 合规罚款/声誉损失
└── 一次事故损失可能数万到数百万

结论: 测试成本是保险费,不是开销
就像银行风控系统成本远低于一次风控失败的损失

思考2: LLM-as-Judge 的可靠性悖论

用LLM测试LLM,Judge本身也可能出错。解决:
├── 用更强模型做Judge (Opus判Sonnet)
├── 多Judge投票 (3个LLM多数决)
├── 定期人工校准 (每月抽检Judge准确率)
├── 关键场景用确定性断言兜底 (contains/schema)
└── Judge自身也需要评估 (Meta-Evaluation)

实测: LLM-Judge与人工评分相关性 ~0.80-0.90
金融类比: 信用评分模型很准但不完美,大额贷款仍需人工审批

思考3: 评估驱动开发(EDD)

TDD: 先写测试 → 再写代码 → 测试通过即完成
EDD: 先定义评估集 → 再写Prompt → 评估通过即完成

EDD流程:
1. 收集真实用户问题 → 整理成评估集
2. 定义"好回复"标准
3. Prompt V1 → 评估60% → 分析失败Case
4. Prompt V2 → 75% → V3 → 85% → V4 → 92%
5. 达标上线 → 收集反馈 → 补充评估集 → 循环

核心: 不是"写完Prompt再测试",而是"评估集先行"

面试表达

30秒版本

LLM应用测试的核心挑战是非确定性 — 传统assert不够用。解决方案是分层测试金字塔:底层Prompt单元测试(Promptfoo/DeepEval)做快速断言(contains/schema/similarity/llm-rubric),中层链路集成测试,上层Golden Set回归评估+Diff报告。CI/CD三个Gate:Pre-commit做Lint,PR做快速评估,发布前做完整回归+安全+性能。核心理念是Eval-Driven Development — 评估集先行,迭代围绕评估分数驱动。

追问准备

Q: Golden Set多大合适? → 初期50-100条覆盖核心场景,成熟后300-500条。Git版本控制,每月专家审核。80%稳定+20%月更新。

Q: 如何测试幻觉? → 三层:Faithfulness Metric检查是否基于Context;事实核查关键数据点;LLM-Judge+人工抽检。RAG场景比纯生成更易检测。

Q: 测试成本怎么控制? → 60%用例只跑免费断言(contains/schema),30%用Embedding相似度,10%用LLM-Judge。整体$5-50/次,月$500-2000。

Q: 如何说服团队投入LLM测试? → 一次幻觉事故的损失 vs 测试月度投入。用真实线上问题做案例 — "有Golden Set这个Bug在PR阶段就会被拦截"。


学习资源

资源说明
PromptfooCLI-first LLM评估框架,YAML配置
DeepEvalPython-first测试框架,pytest集成
BraintrustLLM评估+可观测性平台
LLMPerfLLM性能基准测试工具
Hamel Husain: EvalsEval-Driven Development最佳实践
Eugene Yan: LLM EvaluationsLLM评估方法论综述
Anthropic Testing Guide官方测试指南

明日预告

Day 29: 案例分析:企业LLM平台架构 — 从零搭建企业内部AI平台,涵盖多模型管理、统一API网关、评估系统、成本监控、安全合规的完整架构设计。