传统软件测试是确定性的:输入 2+2,断言输出等于 4,测试通过。但大模型应用彻底打破了这个假设——同一个提示词,两次调用可能给出措辞不同、推理路径不同、甚至正误不同的答案。改一行提示词、换一个切块策略、调一下 temperature,上游模型再来一次静默更新,周一还好好的问答机器人,周五就可能悄悄变坏。本文教你用开源评估框架 DeepEval,把”看着还行”变成可重复、可打分、可进流水线的自动化测试。
一、背景:为什么 AI 测试是一等工程问题
大模型应用的故障模式和传统 bug 完全不同,你需要专门抓六类问题:幻觉(编造上下文里没有的事实)、答非所问(技术上回答了但没命中用户意图)、RAG 忠实度失效(生成内容与检索文档矛盾)、安全问题(毒性、偏见、隐私泄露、越狱)、回归(修好一个场景悄悄搞坏三个)、智能体失败(调错工具、任务烂尾、推理打转)。
靠人工抽查几百个 case,每次发版重看一遍,既不现实也不可靠。你需要的是一套”LLM 界的 pytest”:把模糊的质量问题变成 0~1 的分数和通过/失败断言。DeepEval(Apache 2.0 开源,Confident AI 团队维护)正是为此设计的:50 多个经研究验证的指标、单测式写法、合成数据生成、CI/CD 集成,外加可选的云端看板 Confident AI。
二、原理:DeepEval 的核心概念
理解四个概念就能用好 DeepEval。
LLMTestCase(测试用例):一次评估的最小单元,包含 input(用户输入)、actual_output(你的应用实际输出)、expected_output(期望答案,可选)、retrieval_context(RAG 检索到的文档片段)、context(背景知识)等字段。RAG 场景必须填 retrieval_context,否则忠实度类指标无从判断。
Metric(指标):给输出打分的裁判。多数指标是”LLM as a Judge”——用一个裁判模型按标准打分,返回 0~1 的分数和通过/失败。常用指标:Faithfulness(忠实度:输出是否被检索上下文支撑)、AnswerRelevancy(答案相关性)、ContextualPrecision/Recall(检索精度/召回)、Hallucination(幻觉)、Bias(偏见)、Toxicity(毒性)。GEval 是通用自定义指标,你用自然语言写评分标准,它就能当裁判,拟人准确度很高。
Golden(标准数据集):带上期望输出和上下文的批量用例集合,用于回归测试。存成 CSV 或 Python 文件,一次跑几十上百个 case。
threshold(阈值):分数超过阈值算通过。阈值不是拍脑袋定的:先在验证集上跑出分数分布,再按业务容忍度定,比如客服场景忠实度阈值 0.8,闲聊场景 0.5 即可。
设计哲学是”默认黑盒、按需白盒”:把你的应用当黑盒只评最终输出就能起步;复杂智能体想评中间步骤时,再用 @observe 无侵入 tracing 给 retriever、工具调用、子智能体逐个打分。
三、环境准备
需要 Python 3.9+、pip,以及一个 LLM 服务商的 API Key(多数指标需要裁判模型,默认 OpenAI,也支持 Anthropic、Gemini、Azure、Ollama 本地模型)。
mkdir deepeval-testing && cd deepeval-testing
python -m venv .venv && source .venv/bin/activate
pip install -U deepeval
deepeval --help # 看到 test run / login / view 等命令即成功
裁判模型密钥建议放在项目根目录的 .env.local(记得 gitignore),DeepEval 启动时按”进程环境变量 → .env.local → .env”的优先级自动加载:
# .env.local
OPENAI_API_KEY=sk-your-key-here
想用云端看板看回归曲线,再跑 deepeval login 绑定 Confident AI;纯本地跑完全不需要这一步。想把结果落盘为 JSON,设置 DEEPEVAL_RESULTS_FOLDER=./data。
四、分步实战
步骤 1:第一个单轮评估
新建 test_example.py,用 GEval 写一个正确性测试:
from deepeval import assert_test
from deepeval.test_case import LLMTestCase, LLMTestCaseParams
from deepeval.metrics import GEval
def test_correctness():
correctness = GEval(
name="Correctness",
criteria="根据 expected_output 判断 actual_output 是否正确",
evaluation_params=[
LLMTestCaseParams.ACTUAL_OUTPUT,
LLMTestCaseParams.EXPECTED_OUTPUT,
],
threshold=0.5,
)
case = LLMTestCase(
input="一直咳嗽发烧,要紧吗?",
actual_output="持续咳嗽发烧可能是病毒感染,也可能是更严重的问题,几天不好转请就医。",
expected_output="持续咳嗽发烧病因多样,从普通病毒感染到肺炎都可能,加重、持续或伴随呼吸困难胸痛请及时就医。",
)
assert_test(case, [correctness])
运行:
deepeval test run test_example.py
DeepEval 会输出 0~1 的分数,过阈值打 ✅。这就是你的第一个 LLM 单测。
步骤 2:RAG 端到端评估(忠实度 + 相关性 + 召回)
假设你有一个公司制度问答机器人 ask_policy(question),返回答案和检索片段。写 test_rag.py:
from deepeval import assert_test
from deepeval.test_case import LLMTestCase
from deepeval.metrics import FaithfulnessMetric, AnswerRelevancyMetric, ContextualRecallMetric
def make_case(question, gold_answer):
answer, chunks = ask_policy(question) # 你的 RAG 应用入口
return LLMTestCase(
input=question,
actual_output=answer,
expected_output=gold_answer,
retrieval_context=chunks, # RAG 必填:检索到的原文片段
)
def test_rag_faithfulness():
case = make_case("年假几天?", "工作满一年享 5 天年假,满十年 10 天。")
assert_test(case, [FaithfulnessMetric(threshold=0.8)])
def test_rag_relevancy():
case = make_case("年假几天?", "工作满一年享 5 天年假,满十年 10 天。")
assert_test(case, [AnswerRelevancyMetric(threshold=0.7)])
def test_rag_recall():
case = make_case("病假需要什么材料?", "病假需三甲医院证明,3 天以上需人事审批。")
assert_test(case, [ContextualRecallMetric(threshold=0.7)])
忠实度抓”答案是否被检索片段支撑”(防幻觉),相关性抓”是否答非所问”,召回抓”检索是否漏了关键文档”。三者合起来,RAG 的主要翻车面就盖住了。
步骤 3:多轮对话测试
客服机器人要测多轮,用 ConversationalTestCase 把每一轮包起来:
from deepeval.test_case import ConversationalTestCase, LLMTestCase
from deepeval.metrics import GEval, LLMTestCaseParams
turns = [
LLMTestCase(input="查一下我的订单", actual_output="请提供订单号"),
LLMTestCase(input="尾号 8848", actual_output="订单已发货,预计明天送达"),
]
conv = ConversationalTestCase(turns=turns)
metric = GEval(
name="TaskCompletion",
criteria="判断客服是否在多轮内完成用户目标,不推诿、不遗忘上下文",
evaluation_params=[LLMTestCaseParams.ACTUAL_OUTPUT, LLMTestCaseParams.INPUT],
threshold=0.7,
)
assert_test(conv, [metric])
步骤 4:组件级评估(tracing 抓出是检索还是生成的锅)
端到端分数掉了,先别甩锅给模型。用 @observe 包住 retriever 和 generator,就能分别打分:
from deepeval.tracing import observe
@observe(type="retriever")
def retrieve(question: str):
return vector_store.search(question, top_k=5)
@observe(type="llm")
def generate(question: str, chunks):
return llm.chat(build_prompt(question, chunks))
跑完后在追踪里对 retriever 的 span 跑 ContextualPrecision,对 generator 的 span 跑 Faithfulness——检索烂就调切块和召回,生成烂就调提示词和 temperature,定位精确到组件。
步骤 5:合成数据补边角 case
人工写 case 永远不够。DeepEval 可以按你的文档自动生成边角测试:
from deepeval.synthesizer import Synthesizer
syn = Synthesizer(model="gpt-4o-mini")
goldens = syn.generate_goldens_from_docs(docs=["policy.txt"], max_goldens=50)
生成后务必人工抽查 10~20 条,删掉胡编的问答再入库做 Golden 回归集。
步骤 6:接进 CI/CD 卡点发版
在流水线里加一道门:
# .github/workflows/eval.yml
- run: pip install -U deepeval
- run: deepeval test run tests/evals/ --fail-on-error
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
--fail-on-error 让任一 case 不达标就拦下合并。建议 nightly 跑全量 Golden,PR 只跑受影响的子集,平衡速度和覆盖。
五、常见坑
坑 1:裁判模型和被测模型是同一个。 自评偏高是已知现象,重要指标换另一个服务商的模型当裁判,或至少换不同版本。
坑 2:RAG 用例没填 retrieval_context。 忠实度、上下文精度类指标直接失效或乱打分,模板里把这个字段设为必填。
坑 3:阈值拍脑袋。 先跑 30~50 个已标注好坏的 case 看分数分布,再定阈值;上线后每季度复检一次。
坑 4:temperature 没固定。 被测应用和裁判模型的 temperature 都固定为 0 附近再跑回归,否则分数抖动让你分不清是回归还是随机性。
坑 5:全量评估塞进 PR 流水线。 LLM 裁判又贵又慢,PR 跑冒烟子集,全量放 nightly,失败再二分定位。
坑 6:合成数据不经人工审核直接入库。 生成数据里混进错误问答,等于把错答案锁成”标准”,抽查环节不能省。
进阶:Golden 回归集、自定义指标与生产监控
单测跑通后,真正的质保水位来自三样东西。
Golden 回归集管理。 把线上 bad case 和合成数据沉淀成版本化数据集。建议按场景分子目录(goldens/faq/、goldens/rag/、goldens/safety/),每个用例带 input/expected_output/retrieval_context 全字段。命名规范:场景_编号_一句话描述,失败用例先复现为 Golden 再修,保证”修一个 bug,永久多一个守卫”。跑全量 Golden 时并行度调高(裁判调用是 IO 密集),但注意服务商 RPM 限流,配指数退避。
自定义业务指标。 GEval 的 criteria 就是自然语言质检单,直接写业务规则:”客服回答必须包含工单号且语气礼貌””医疗回答必须带免责声明”。一个指标只检一件事,多个小指标组合比一个大而全的 criteria 稳定得多。阈值按”先跑 50 个已标注 case 看分布”的方法逐个定。
从评测到监控。 评测是发版前,监控是发版后:线上采样真实问答,定时用同一套 Metric 打分,分数漂移告警。配合 Confident AI 看板,能看到”哪次提交之后忠实度掉了 5 个点”。评测管住增量,监控管住存量,两者用同一套指标,口径才对得上。
附:完整可跑的 RAG 回归测试工程
把前面碎片拼成能直接跑的工程结构:
deepeval-testing/
├── .env.local # OPENAI_API_KEY
├── app.py # 你的 RAG 应用:ask_policy(question)->(answer, chunks)
├── tests/evals/
│ ├── __init__.py
│ ├── conftest.py # 批量构造 LLMTestCase 的 fixture
│ ├── test_rag.py # 忠实度/相关性/召回三件套
│ ├── test_conv.py # 多轮对话
│ └── goldens/ # 版本化回归集:faq.jsonl / safety.jsonl
└── .github/workflows/eval.yml
conftest 里写 Golden 加载器,一行加载一个 jsonl 用例:
import json
from deepeval.test_case import LLMTestCase
def load_goldens(path):
cases = []
with open(path, encoding="utf-8") as f:
for line in f:
d = json.loads(line)
cases.append(LLMTestCase(
input=d["input"],
actual_output=d["actual_output"],
expected_output=d.get("expected_output"),
retrieval_context=d.get("retrieval_context"),
))
return cases
每个 jsonl 行就是一条约定的”契约”:input 是用户问法,retrieval_context 是当时检索到的文档,expected_output 是人工审过的标准答案。新模型、新提示词、新切块策略上线前,全量跑一遍,哪个场景掉分一目了然。线上每出一个 bad case,先写成 Golden 行复现,再修应用——修一次,永久多一个守卫。阈值建议起步:忠实度 0.8、相关性 0.7、召回 0.7、有毒性 0.9(一票否决),跑一个月后再按分数分布微调。裁判模型固定版本并 pin 死,升级裁判本身也要重跑基线,否则”尺子变了”会伪装成”应用变了”。
常见问答
问:没有 OpenAI Key 能用 DeepEval 吗? 能。裁判模型可换 Anthropic、Gemini、Azure 或本地 Ollama,部分规则型指标根本不需要裁判。起步用本地模型当裁判,重要发版前再用强模型复核,成本最优。
问:GEval 和专用指标怎么选? 有专用指标用专用的:忠实度、相关性、毒性都有针对性实现,比通用裁判更稳。GEval 留给业务特有规则,如”回答必须含工单号”。一个测试挂 3~5 个小指标,远胜一个大而全的 criteria。
问:评估很慢很贵怎么办? 三招:PR 只跑冒烟子集、全量放 nightly;裁判用小模型,发布前用大模型复核;Golden 按场景分级,核心全跑、长尾抽样。评估成本是质量保费,不是浪费。
问:分数抖动怎么判断是不是回归? 同一用例连跑 3 次看方差,方差大先固定双方 temperature 再谈回归。确认回归后用 tracing 对比前后两次的检索上下文和生成差异,定位到组件再修。
问:智能体应用能测吗? 能。DeepEval 有多轮、工具调用、任务完成类指标,配合 tracing 给每个工具 span 打分。核心是把”任务完成度”拆成可检查的子断言,而不是只看最终一句话。
问:团队第一次落地评估,从哪切入? 从最痛的 bad case 切入:挑线上真实翻车的一问一答,写成 10 个 LLMTestCase,挂忠实度和相关性两个指标,接进 CI。一周见效后再扩成 Golden 集。别一上来就搞大而全的评估平台,小步快跑才推得动。
问:提示词一改分数全变,怎么维护? 把提示词版本和评估结果一起记录,每次改提示词跑全量 Golden 对比。分数变好但 diff 看不懂的,要警惕裁判被新话术”哄骗”——抽查原始输出,确认是真变好而不是裁判偏好漂移。
问:小团队没精力建评估平台,最小投入是什么? 一个 test 文件 + 十个核心用例 + 发版前手工跑一次。先有再优:等这十个用例真拦住一次回归,团队自然愿意投入。第二是把裁判 Key 放进 CI 密钥而不是个人电脑,人走权限不走。
问:合成数据和人工标注怎么配比? 先人工写 20~30 条定调子,再合成扩到 200 条量级,最后人工抽查 10% 剔坏例。合成管数量,人工管方向,配比建议从 1:5 起步,边跑边调。
问:评估结果怎么向老板汇报? 别贴分数表,讲三件事:拦住了几次回归(省了多少线上事故)、哪个场景最弱(下个迭代投哪里)、和上个版本的分数对比(趋势向上)。分数是工程师的语言,事故和趋势才是管理的语言。 点击阅读原文
六、总结
DeepEval 的工作流可以收敛成一句话:用 LLMTestCase 描述一次问答,用 Metric 打分,用 Golden 做回归,用 tracing 定位到组件,用 CI 卡住发版。从第一个 GEval 单测起步,逐步加上 RAG 三件套(忠实度、相关性、召回)、多轮测试和合成数据,你的 LLM 应用就有了和传统软件一样的质保水位:每次变更都有分数为证,而不是”看着还行”。 点击阅读原文
参考资料:https://dev.to/himanshuai/deepeval-for-ai-testing-the-complete-end-to-end-engineering-guide-4f7f 点击阅读原文