ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

LLM应用测试新战场:从RAG到Agent的分层评估与pytest实践

LLM应用测试新战场:从RAG到Agent的分层评估与pytest实践 很多人觉得“测试”是软件开发生命周期里最枯燥、最末端的一环但放到 LLM 应用这个新领域测试恰恰成了决定项目能不能落地、能不能上生产、能不能收得住成本的关键隘口。我在过去半年里一边在自己团队搭 LLM 应用一边看着身边不少朋友在从 0 到 1 做基于大模型的产品大家不约而同卡在了同一个问题上代码写完很容易提示词和 RAG 链路也都能跑通 demo可真要往生产环境推用户问几个刁钻问题回复质量就肉眼可见地往下掉幻觉、答非所问、上下文漂移全来了。这时候你才发现传统那套基于“确定性断言”的自动化测试根本用不上而 LLM 应用的测试方法论、工具链和最佳实践都还是大片空白这就是我说的“新战场”。这一篇是整个系列的开篇我不想一上来就讲怎么写 pytest 脚本、怎么搭断言器先把“为什么”讲透。测试这活儿在 LLM 落地的过程里不再是一个质量保障动作它直接决定了你的应用是能用几天还是能跑几年决定了你面对的是 Demo 级效果还是产品级交付。我会从传统测试和 LLM 测试的本质差异聊起拆解 LLM 落地时需要被测试的各个层次然后给出一套可落地的思路和 pytest 搭建案例最后把我踩过的坑、犯过的错全部摆出来。1. 内容整体设计与思路拆解为什么 LLM 测试必须“另起炉灶”1.1 传统自动化测试的“确定性假设”正在失效做传统自动化测试的同事应该深有体会不管是做接口自动化还是 UI 自动化我们写测试用例时心里都有一个明确的“预期结果”。调用一个接口传入参数返回值要么是 200要么是 500字段名、字段类型、错误码都是确定的UI 上点一个按钮元素的文本、颜色、出现位置要么符合预期要么不符合跑失败就是失败不需要第二次判断。这种测试思维建立在“系统行为可复现、输出结果可断言”的确定性基础之上。传统的 pytest、Selenium、Appium 框架包括我们看到的各种接口自动化测试框架本质上都是在帮你把“输入-操作-断言”这个闭环跑起来。你设计测试数据设计预期结果然后交给自动化框架去循环执行一切黑白分明。可当你把测试对象换成一个 LLM 应用这个模型面临的却是概率性输出。你给它同一个问题它可能用不同的表达方式回答你给它两段相近的上下文它可能每一次给出的答案在信息完整度上都略有差异用户问“这个方案和那个方案到底有什么区别”你没法在产品需求里写死“必须返回 150 字以内的对比表格”因为模型天然会在语言这个高维空间里自由游走。这不是“不好测”这么简单而是传统测试框架里最核心的“断言”机制直接失效了。你不能判断一个 LLM 的回答“等于”或者“不等于”某个字符串你需要判断它“是否语义上相关”“是否包含了必要的实体”“是否从给定的文档中引用了答案”。这种差异是认知模型上的差异不是换个测试工具就能解决的事。1.2 从“断言”到“评估”测试目标变了方法就得跟着变既然确定性断言失效那 LLM 测试到底应该怎么做我在实际项目中总结下来核心思路是把“测试用例”从“断言器”改成“评估器”。也就是说测试要做的不是判断“对不对”而是判断“好不好”。这里的“好”可以从多个维度打分准确性、相关性、忠实度、完整性、安全性、稳定性、时效性、成本每一条都是一个可量化的评估维度。打个比方传统测试像质检流水线上检查螺丝的螺纹是不是标准卡尺一量过或不过LLM 测试像验收一道菜品你不能用卡尺量菜的温度但你可以建立一套色、香、味、形、器、营养搭配的评估体系由经验丰富的品鉴师或者一套标准化的评测流程来打分。LLM 应用的质量不是由某一条代码逻辑决定的而是由好几套“软性”能力共同支撑的这就要求测试基建发生根本性变化。这也是为什么我会把“评估器”这个角色放到测试设计的最前面。你要先确定你的 LLM 应用应该满足哪些质量维度然后把每个维度转成可观测、可打分、可自动执行的检查项。比如“忠实度”这一个维度就可以通过比对 LLM 生成的答案与 RAG 检索到的上下文文档之间的语义重合度来判断。你不再问“输出是否等于 A”而是问“答案是否在事实上忠于给定的参考资料”。1.3 测试为什么成了 LLM 落地的新战场三个关键驱动因素第一个驱动因素是 LLM 应用的失败模式与传统应用完全不同。传统应用挂了一般是报错、超时、崩溃都是显性的而 LLM 应用挂掉的时候它通常还在正常返回内容只是这段话里可能包含了编造的数据、过期的事实、前后不一致的建议、或者带偏见的表述这叫“完美输出下的隐性错误”用户不会告诉你出错了但信任已经流失了。第二个驱动因素是评估成本与收益失衡的现状。每跑一轮测试要消耗 Tokens要搭向量数据库要设计复杂评测集要调裁判模型的参数成本比传统测试高一个数量级。可如果你不投入这股成本上线后一个幻觉答案导致用户流失或者一次敏感信息泄漏导致服务被投诉损失远超测试成本。测试在这个场景里的“投资回报比”极高谁先建立起测试体系谁就先拿到生产环境的安全垫。第三个驱动因素是工具链和数据飞轮都没有成熟。从热度趋势可以看到大家都在关注 langchain、pytest、RAG、Agent、wiki 知识库、GraphRAG 这些关键词但把它们拼成一个端到端的、可复现的 LLM 测试体系现在还没有标准答案。正因为没有标准答案先做的人才有机会沉淀出方法论这也是我写这个系列最主要的原因。2. 核心细节解析与实操要点LLM 测试到底测什么2.1 测试对象的边界模型能力、RAG 检索、Agent 编排三层必须分开把 LLM 应用当成一个整体去测是我见过成本最高、效果最差的做法。你收到一个错误回答你都不知道是模型本身能力不足还是向量数据库里没召回该召回的内容还是 Agent 编排时把工具调用的结果传丢了。所以我认为测试对象必须分层拆解每一层设独立的评测集和阈值。第一层是模型原生能力。这里要测试的是 LLM 在特定提示词下的基础能力上限比如多轮对话保持上下文的能力、代码生成能力、结构化输出提取能力、数学推理能力。这一层不掺入外部知识库不挂接工具让你能够定位“裸模型”的表现基线。测试结果的作用是做版本选型和提示词调优用同一批评测文本跑不同模型或不同提示词模板对比分数就知道哪个更适合你的业务。第二层是 RAG 检索链路。现在的 LLM 落地项目几乎没有人不用知识库无论你用的是传统的向量检索还是带关系的 GraphRAG或者基于 wiki 构建的知识库体系核心都要经过“用户问题写进向量 - 向量检索 TopK - 文档拼进上下文”的链路。这一层测试的重点不在问答能不能答出来而在于检索系统在给定的查询下有没有把正确答案所在的文档给彻底漏掉。你召回的质量如果不行后面给模型灌多少提示词都没用。第三层是 Agent 编排与工具调用层。现在大家搭应用很少只有一个 LLM 调用很多都是基于 langchain 或者自己写的调度逻辑把工具调用、API 请求、外部查询编排在一起。这一层的测试既包含传统意义上的接口逻辑测试比如 API 服务挂了能不能熔断重试、传入参数对不对又包含新出现的逻辑语义测试比如模型在一个步骤里提取出的参数传到下一个工具是不是符合工具定义工具返回的结果有没有被正确地回填到对话上下文中。说到这里有一个特别容易忽略的点工具调用的 Tokens 消耗往往会暴涨但测试的时候不能直接拿线上最大并发去压必须在评测集里单独测量平均 Tokens 和峰值 Tokens锚定好成本基线。2.2 测试数据的构造评测集不是简单堆问答对很多团队一上来就在网上找现成的 benchmark 数据集或者把客服对话记录了事这是大忌。LLM 应用的评测集必须具备业务边界和故障边界两层结构。业务边界好理解就是从真实业务中提取高频问题、典型问题、边界问题覆盖用户会问到的主要场景。但光有这个还不够你还要准备故障边界数据也就是那些“很容易让模型翻车”的样本比如明知知库里没有答案用户却追问到底模型会不会强行编造一个答案用户提问中包含了明显的错误前提比如把“A 模块”说成“B 模块”模型会不会被带偏问题需要跨多个文档才能回答完整模型有没有把所有相关内容都检索上来用户提出了敏感或者违规的问题模型有没有按安全规范拒答这些故障样本才最能暴露系统的能力短板也是最值得自动化回归的样本。构造评测集时我会额外标注每个样本的“标准答案”或“标准态度”比如对于“用户问了一个知识库外的问题”标准态度就是“基于已有信息无法回答建议补充资料”。这样在自动化运行时就可以通过评估器判断模型输出是否接近这个标准态度而不必像传统测试那样强行匹配一个字符串。2.3 测试执行环境为什么必须引入可控的“评测网关”聊到实际操作我还想强调一个底层设施LLM 测试必须跑在一个可控的评测网关里。这里说的“网关”不是指部署层面的 API 网关而是指在测试代码里统一封装模型调用、统一记录日志、统一统计 Tokens 的中间层。所有被测试的用例不管是单测还是集成测试都不能直接裸调模型提供商的 SDK而应该通过评测网关来路由。为什么一定要这么做因为 LLM 测试的可重复性极差。你直接调一次模型生成回答下次再跑可能就变了你换了一个模型版本所有历史测试结果全部作废你想对比两个模型的输出差异拉出来的日志格式还不一样。有了评测网关你就可以固定模型版本、固定温度参数、固定随机种子、统一捕获返回内容和 Token 消耗。你甚至可以在这个网关里预设一个“故障注入”开关模拟模型请求失败、工具调用超时、向量库连接中断等状况这些在传统自动化测试里很容易模拟的场景在 LLM 测试里如果不加网关操作起来会非常痛苦。3. 实操过程与核心环节实现用 pytest 搭一个可回归的 LLM 测试基座3.1 基础设施准备Pytest 夹具设计聊了这么多“为什么”现在进入第一阶段的实操环节。这个系列第一篇我们先把最基础的测试骨架搭起来不用部署完整的测试平台就用 pytest 就足够打样了。为什么选 pytest因为它生态成熟、断言方式足够灵活、fixture 机制非常适合管理外部依赖而且现在热度趋势里反复出现“自动化测试框架 pytest”说明这是事实上的行业标准入口。我先建一个项目目录结构大致如下llm_automation_testing/ ├── conftest.py ├── evaluation/ │ ├── evaluator_llm.py │ └── retrieval_evaluator.py ├── test_cases/ │ ├── test_model_basic.py │ ├── test_rag_retrieval.py └── playground/在conftest.py里我定义两个核心 fixture一个是llm_gateway负责创建统一调用模型的环境另一个是vectordb_session负责拉起一个可复现的向量数据库连接。import pytest pytest.fixture(scopesession) def llm_gateway(): from llm_automation_testing.playground.gateway import LLMEvalGateway # 固定参数模型版本、温度、以及评测模式 return LLMEvalGateway(config_pathconfig/eval_models.yaml) pytest.fixture(scopefunction) def vectordb_session(llm_gateway): # 建一个小型向量库灌入预先构造的测试语料 from llm_automation_testing.playground.fake_vdb import FakeVectorDB vdb FakeVectorDB.load_from_local(testdata/corpus.json) vdb.clear() vdb.batch_add(vdb_corpus()) yield vdb # 会话结束后清除数据保证用例独立 vdb.clear()这里有一个关键设计LLMEvalGateway里必须能设置“评测模式”。在评测模式下模型的 temperature 统一设为 0随机性降到最低同时所有推理日志都会记录到本地文件。这个设计可以让你后续跑回归时无论跑多少遍至少基础输出是相对可控的。3.2 核心 TM测试的基础能力层搭好基座后我先测最基础的第一层——模型基础能力。我设计一组关于“信息抽取”和“结构化输出”的用例用来验证模型在固定指令下能否稳定输出 JSON。这类用例是很多 LLM 应用的刚性依赖因为工具调用、表单填写的几个环节都会用到。import json def test_model_can_extract_entities_in_json(llm_gateway): input_text 用户想申请退款原订单号是 20240915-8899商品是蓝牙耳机金额 299 元。 instructions 提取以下字段订单号、商品、金额并以 JSON 格式返回。 response llm_gateway.chat(instructions \n\n input_text) try: parsed json.loads(response) except json.JSONDecodeError as exc: pytest.fail(f模型未返回合法 JSON{response}解析错误{exc}) assert 订单号 in parsed assert parsed[商品] 蓝牙耳机我跑这段用例时发现一个很容易踩的坑模型有时候会在 JSON 外面套一层“json”代码块标记导致json.loads直接报错。后来我在llm_gateway.chat内部做了响应预处理把代码块标记剥离掉只保留 JSON 主体部分。这个小改动已经救了很多次自动化环境。另外这里也建议你不只是断言“存在字段”而是针对“提取准确率”加打分逻辑。比如解析完成后用字符串相似度函数比较目标字段得出一个 0 到 1 的分数低于阈值的测试判定为失败。这样你后续跑变更回归时能看到的是更加平滑的“质量变化曲线”而不是一把过/不过的绝对结果。3.3 实现 RAG 检索链路测试接下来这层值得重点说因为我发现很多项目 Google 搜索上“RAG 推荐了什么内容”完全没概念一直到线上出现低级错误才想到去追溯。在测试 RAG 链路时我们不是让模型直接去回答用户而是先把检索召回的结果拿过来做断言。举个例子建一个小语料库包含《2024 年企业健康体检套餐手册》和《客户退换货政策 V5.2》两本“业务文档”然后在这个库上构造几个查询。比如查询“员工年度体检可以选哪些镜子项目”查询“已拆封的产品如何退换”查询“换货的时效是多长”这些查询都直接来自我们设定的业务场景。测试的目标是给定查询后检索系统返回的 TopK 文档里必须包含目标文档。def test_rag_retrieval_topk_hit(vectordb_session): query 换货的时效是多长 top_k 5 hits vectordb_session.search(query, top_ktop_k) # 对结果做归一化后判断目标文档是否出现在召回列表 hit_ids {doc[doc_id] for doc in hits} assert return_policy_v5_2.md#section_3 in hit_ids, ( f查询“{query}”在 TopK{top_k} 结果中没有命中目标文档实际命中{hit_ids} )这里要特别提一下RAG 测试不能只看“召回率”。你可以给各自的召回结果打一个相关性分比如用“语义相似度”计算召回文档与查询主题之间的相关度低于 0.75 的直接标为“疑似低相关召回”然后在测试报告里单独列出一个可视化列表方便你排查哪些查询的向量表示不够鲁棒。我自己的项目里就是在跑测试时发现“退换”和“退货”两个词虽然语义很像但向量分布差异很大需要单独做同义词扩展。3.4 搭建基于 LLM 的评估器让机器评“好不好”如果要让 pytest 用例全面替代人工测试还必须引入“评估器”的概念。我指的不是前面那种纯规则解析而是用一个裁判模型来给被测模型的回答打分。这个模式在业内已经很常见但很多人把它做得过于繁琐。我的做法是构造一个EvaluatorLLM它接收三部分上下文用户问题、被评估的模型回答、参考信息比如 RAG 检索到的内容或标准答案。然后把以下内容作为提示词请对以下回答做出质量评估从“准确性”“忠实度”“相关性”三个维度分别给出 0-10 分和一句评语。 要求 1. 准确性衡量回答中事实性信息的正确性 2. 忠实度衡量回答中信息是否与提供的参考资料一致是否包含编造或猜测 3. 相关性衡量回答是否恰当回应了用户的问题。评估器返回后我们用正则或 JSON 解析提取三个分数再在 pytest 用例中聚合断言。def test_answer_faithfulness_with_evaluator(llm_gateway, vectordb_session): query 体检套餐具体包含哪几项检查 reference vectordb_session.search(query, top_k3) response llm_gateway.answer_from_documents(query, reference_docsreference) score llm_gateway.evaluate_faithfulness(query, response, reference) assert score 7, f忠实度得分过低{score}回答内容{response}参考资料{reference}跑这一类用例时还有几个规则我得提醒你一定要把裁判模型的版本固定住。如果不固定你拿 A 模型当裁判跑出来的分数是 8 分改成 B 模型当裁判可能变成了 5 分那测试就没法追溯了。我的做法是把裁判模型按“评测集 A/B”两套分别运行最后汇总两个分数的差值以此量化评测器自身的波动性。注意评测时长和 Tokens 的成本。一个用例跑两条链被测模型一条链裁判模型一条链普通规模的回归集可能有几千条用例跑一轮就要几千块钱。我的建议是在日常迭代中先用 5% 的核心样本做快速回归只有在发布前跑一轮全量回归这样既省成本又能早发现大部分问题。3.5 关键参数的选择与计算逻辑在实测中我常被问到“召回条数 TopK 设多少”“温度参数设多少”“相似度阈值多少合适”。这些问题没有标准答案但可以借助测试来计算。以“召回条数”为例我常常会在评测集上轮询一个 TopK 候选列表比如[3, 5, 8, 10]然后对每一组 TopK都运行召回命中率用例。把召回命中率画成一条曲线你会发现从 5 到 8 往往是一个收益递减的拐点。超过 8 之后虽然命中率略有上涨但上下文长度和 Tokens 消耗几乎是线性增长导致回答质量和响应速度双双变差这就得不偿失了。我业务中最后确定是 TopK5这就是用数据算出来的不是拍脑袋写的。温度参数也同理。任务类型如果偏向“标准化操作”比如提取信息、分类、改写标题等温度应固定在 0 到 0.2 区间如果偏向“创意生成”比如营销文案、角色扮演类对话温度才能放宽到 0.7 到 1.0。但测试场景里并不需要这种多样性所以我会在评测网关里统一覆盖温度参数保证测试结果可复现。4. 常见问题与排查技巧实录LLM 测试里的典型翻车现场4.1 翻车场景一模型在一段答案里混入了编造的数据最让人头疼也最普遍的问题就是模型一本正经地编造参考资料里根本不存在的内容。比如在回答“公司今年的体检套餐有什么升级功能”时模型可能会自行发挥“增加癌症 15 项早筛”但实际套餐里连这个影子都没有。我在排查这类问题时会先做两步第一步检查 RAG 检索的 TopK 文档里是不是真的没有相关上下文第二步把这条用例的答案和检索上下文单独拿出来用忠实度评估器跑分同时做一次“事实一致性校验”。如果评估器给“忠实度”打了低于 5 分的分数我就直接判定这条用例为测试失败并且记录失败原因和触发的检索上下文。长期跑下来我可以总结出哪一类问题更容易诱发幻觉然后在业务侧增加安全兜底话术。4.2 翻车场景二提示词模板改了个标点测试直接大面积失败有一次我为了统一部门内的文案风格把提示词模板里一个“”改成了“”对只是全角半角的差异结果回归测试大面积失败。一开始我还以为是模型版本被静默升级了查了半天才发现模板尾部多了一个换行符导致模型把这个换行符误读成了指令的一部分输出风格自然就全跑偏了。从那以后我养成了一个习惯把提示词模板本身也纳入测试管理。每次改动提示词都走“提示词变更单”并在评测群里记录改动内容和影响面。同时在测试环境中把提示词模板哈希化如果模板内容有变化自动触发一轮指定用例集的回归。听起来很夸张但做过一次就知道很多 LLM 应用线上“突然变差”的事故不是因为模型变了而是因为提示词悄悄被某个同事“顺手优化”了。4.3 翻车场景三外部依赖不稳定导致测试结果噪声过大LLM 应用很少是孤立的总有向量数据库、第三方搜索、业务 API 等各种依赖。如果这些外部依赖在测试时不稳定LLM 用例的噪声就会非常大。你很有可能在某一天跑测试发现用例挂了一堆可你什么都没改原因可能就是向量数据库连接超时或者外部 API 限流。我的解决方案是引入两层隔离第一层在测试准备阶段把所有的依赖都替换成可 mock 的本地实现比如用一个本地 JSON 文件模拟向量库检索结果这样单测和集成测试能稳定运行第二层只留少量“端到端冒烟用例”专门跑真实外部依赖这些用例不放在高频回归里而是放到夜间任务并且在任务失败时自动收集外部依赖的健康状态用来判断是“应用自身问题”还是“依赖环境问题”。4.4 Token 消耗失控也是一项测试不要觉得 Token 消耗跟测试没关系事实上它直接影响了系统的可用性。在一次 Agent 链路测试中我设计了一个包含五个工具调用的场景结果跑一轮下来Tokens 消耗比原本预估的高了 10 倍。查原因发现工具调用返回的内容很长模型在每一轮里都把这些长内容重新拼进上下文导致每轮都重复计费。我把 Tokens 消耗纳入测试断言范围对“单轮对话平均 Tokens 消耗”“工具调用链路累计 Tokens 消耗”设阈值。一旦超过了基线值测试用例就失败并给出哪个环节消耗最多。通过这个机制我们优化过提示词压缩策略也调整过工具返回数据的截断方式最终把单轮平均成本降下来 30% 以上。测试层级测试重点典型风险推荐工具/思路模型原生能力指令遵循、结构化输出、上下文保持输出不稳定、提示词敏感固定温度参数的 pytest 用例 评估器RAG 检索链路召回率、相关度、TopK 命中向量召回偏移、同义词失效本地向量库 TopK 命中率统计Agent 工具编排工具调用参数、返回值回填、重试熔断参数错传、链路反复循环mock 服务 全链路日志追踪生成质量准确性、忠实度、相关性幻觉、答非所问裁判模型评估 人工抽检5. 实操心得与后续扩展方向5.1 从“一次性评测”到“持续回归”的沉淀过程很多团队做 LLM 测试都是“上线前评一轮”做完就扔测试用例躺在仓库里再也不跑。这套玩法最大的问题是你永远发现不了“应用慢慢变差”的趋势。模型版本可能从 3.5 升到 4.0你说的 OTA 提升。但同一个提示词效果可能反而回落这你会感知不到。我在自己的项目里建设了一套持续回归流程每天早上 8 点跑一个只包含 200 条核心业务的评测集把质量分数和 Tokens 消耗按时写入一张简单的 score 表每周五下午做一次全量回归覆盖千级规模评测集。长期坚持下来我手里就多了一份“系统质量日报”相当于一套体重秤只要有一点变差的苗头都能及时发现而且可以追溯到是哪个版本、哪条修改引起的。5.2 扩大战场向智能体链路和自动化测试平台演进这个系列已经打下了“为什么”和“怎么做”的第一根桩基。但往前看LLM 测试真正的深水区是 Agent 的全链路测试。举个场景你的 Agent 需要决策“这个用户问题要不要调用外部工具”这本身就是一个概率行为。你可以在串行链路里验证 Web 端到端行为但当 Agent 拆分成多条分支的时候你发现每个分支都需要单独的测试编排和独立的评测集。后续我打算继续深入讲三个方向一是如何把 Agent 链路的每一步都变成可观测、可断言的节点二是如何基于 lib 设计一个真正可落地的评测平台把模型评测、RAG 评测、成本评测统一沉淀成可复用的平台能力三是讲清楚怎么利用 Pytest 生态里的插件和 hook 机制把 LLM 测试嵌入到 CI/CD 里让每一版代码提交后都能自动跑一套面向 LLM 的质量门禁。现在边界还没有成型但“测试驱动开发”这个词在 LLM 应用领域正在被重新定义。我的经验是先不要追求一次性建出完美系统从几十条核心评测集开始搭好一个能记录、能回放、能打分的测试基座你就算正式走上这个新战场了。我也会继续把我在各层链路里踩到的坑和经验持续沉淀在这个系列里希望能让更多的人少走弯路。
返回列表