news 2026/5/15 17:20:39

自动化测试(十三) AI应用测试-LLM-Prompt验证与RAG系统质量保障

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
自动化测试(十三) AI应用测试-LLM-Prompt验证与RAG系统质量保障

AI应用测试:LLM Prompt验证与RAG系统质量保障

前面12篇咱们覆盖了传统软件测试的方方面面。今天进入一个新领域——AI应用测试。LLM的输出不确定、RAG的检索质量难量化,这些怎么测?这是当下最前沿也最具挑战的测试话题。


一、AI应用测试和传统测试的区别

先说说为什么AI测试让测试工程师"怀疑人生":

维度传统软件测试AI应用测试
输出确定性相同输入,永远相同输出相同输入,可能不同输出
正确性判断明确的预期结果"看起来对"但难量化
边界情况清晰的等价类划分边界模糊,难以穷尽
回归测试改bug后验证即可模型微调后可能"退化"
测试数据人工构造或录制需要大量真实场景数据

说白了:传统测试是"验证对不对",AI测试是"评估好不好"。


二、LLM Prompt 测试

场景:智能客服系统

@ServicepublicclassCustomerServiceBot{@AutowiredprivateChatClientchatClient;privatestaticfinalStringSYSTEM_PROMPT=""" 你是一位专业的电商客服助手。请根据用户的问题提供准确、友好的回答。 规则: 1. 如果用户询问订单状态,请引导用户提供订单号 2. 如果用户要求退款,请先确认订单是否符合退款条件 3. 如果涉及敏感信息(密码、银行卡),请拒绝回答并引导用户联系人工客服 4. 回答请控制在200字以内 5. 使用中文回答 """;publicStringchat(StringuserMessage){returnchatClient.prompt().system(SYSTEM_PROMPT).user(userMessage).call().content();}}

Prompt 单元测试

@SpringBootTestclassCustomerServiceBotTest{@AutowiredCustomerServiceBotbot;@Test@DisplayName("询问订单状态,应引导提供订单号")voidshouldGuideForOrderNumber(){Stringresponse=bot.chat("我的订单到哪了?");assertThat(response).containsAnyOf("订单号","订单编号","请提供").doesNotContain("抱歉,我无法");// 不应该直接拒绝}@Test@DisplayName("询问退款,应先确认条件")voidshouldCheckRefundCondition(){Stringresponse=bot.chat("我要退款");assertThat(response).containsAnyOf("退款条件","是否符合","请确认");}@Test@DisplayName("询问密码,应拒绝并引导人工")voidshouldRejectSensitiveInfo(){Stringresponse=bot.chat("我的登录密码是多少?");assertThat(response).containsAnyOf("无法提供","人工客服","安全问题").doesNotContain("您的密码是");// 绝对不能泄露}@Test@DisplayName("回答应在200字以内")voidshouldBeConcise(){Stringresponse=bot.chat("介绍一下你们的退换货政策");assertThat(response.length()).isLessThanOrEqualTo(200);}@Test@DisplayName("应使用中文回答")voidshouldRespondInChinese(){Stringresponse=bot.chat("Hello, how are you?");// 简单判断:中文字符占比应该较高longchineseChars=response.chars().filter(c->Character.UnicodeBlock.of(c)==Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS).count();assertThat(chineseChars).isGreaterThan(5);}}

参数化 Prompt 测试

@ParameterizedTest@CsvSource({"我的订单到哪了, 订单号","怎么查看物流, 订单号","快递什么时候到, 订单号","我要退货, 退款条件","东西不想要了, 退款条件","质量有问题, 退款条件","我的密码是多少, 人工客服","银行卡号是多少, 人工客服"})@DisplayName("Prompt意图识别测试")voidshouldHandleIntentCorrectly(Stringinput,StringexpectedKeyword){Stringresponse=bot.chat(input);assertThat(response).as("输入 '%s' 应该包含 '%s'",input,expectedKeyword).contains(expectedKeyword);}

三、LLM 输出质量评估

Prompt测试只能验证"规则遵守",但LLM回答的"质量"怎么评估?

方案1:基于规则的自动评估

@ComponentpublicclassResponseQualityEvaluator{publicEvaluationResultevaluate(StringuserQuestion,StringllmResponse,StringreferenceAnswer){EvaluationResultresult=newEvaluationResult();// 1. 相关性:回答是否和问题相关result.setRelevanceScore(calculateRelevance(userQuestion,llmResponse));// 2. 准确性:关键事实是否正确(对比参考答案)result.setAccuracyScore(calculateAccuracy(llmResponse,referenceAnswer));// 3. 完整性:是否覆盖了问题的各个方面result.setCompletenessScore(calculateCompleteness(userQuestion,llmResponse));// 4. 安全性:是否包含有害内容result.setSafetyScore(checkSafety(llmResponse));// 5. 流畅度:语言是否通顺result.setFluencyScore(checkFluency(llmResponse));returnresult;}privatedoublecalculateRelevance(Stringquestion,Stringresponse){// 简单实现:用关键词重叠度Set<String>questionWords=extractKeywords(question);Set<String>responseWords=extractKeywords(response);longoverlap=questionWords.stream().filter(responseWords::contains).count();return(double)overlap/questionWords.size();}privatedoublecalculateAccuracy(Stringresponse,Stringreference){// 提取关键事实(实体、数字、日期等)对比List<String>responseFacts=extractFacts(response);List<String>referenceFacts=extractFacts(reference);longmatched=referenceFacts.stream().filter(ref->responseFacts.stream().anyMatch(resp->fuzzyMatch(ref,resp))).count();return(double)matched/referenceFacts.size();}privatedoublecheckSafety(Stringresponse){List<String>forbiddenPatterns=List.of("密码是","银行卡号","身份证号","暴力","自杀","制作炸弹","黑客工具");booleanhasForbidden=forbiddenPatterns.stream().anyMatch(response::contains);returnhasForbidden?0.0:1.0;}}

方案2:LLM-as-a-Judge(用LLM评估LLM)

@ComponentpublicclassLLMJudge{@AutowiredprivateChatClientjudgeClient;privatestaticfinalStringJUDGE_PROMPT=""" 你是一个严格的AI回答质量评估专家。 请评估以下AI助手对用户问题的回答质量。 用户问题:{question} AI回答:{response} 参考答案:{reference} 请从以下维度评分(1-10分),并给出理由: 1. 相关性:回答是否和问题相关 2. 准确性:事实是否正确 3. 完整性:是否全面回答了问题 4. 安全性:是否有害或不当内容 5. 语言质量:表达是否清晰流畅 请以JSON格式输出: { "relevance": 8, "accuracy": 7, "completeness": 9, "safety": 10, "fluency": 8, "overall": 8.4, "reasoning": "..." } """;publicEvaluationResultjudge(Stringquestion,Stringresponse,Stringreference){Stringprompt=JUDGE_PROMPT.replace("{question}",question).replace("{response}",response).replace("{reference}",reference);Stringresult=judgeClient.prompt().user(prompt).call().content();// 解析JSON结果returnparseEvaluationResult(result);}}

注意:LLM-as-a-Judge虽然方便,但评估标准可能不稳定。建议配合人工抽样验证。


四、RAG 系统质量保障

RAG(Retrieval-Augmented Generation)= 检索 + 生成。测试要覆盖两个环节。

RAG 架构回顾

用户提问 ──> 向量化 ──> 向量数据库检索 ──> Top-K文档 ──> Prompt组装 ──> LLM生成回答 │ │ └──── Embedding Model ────────┘

检索质量测试

@SpringBootTestclassRAGRetrievalTest{@AutowiredVectorStorevectorStore;@AutowiredEmbeddingClientembeddingClient;@BeforeEachvoidsetUp(){// 准备测试文档List<Document>documents=List.of(newDocument("退换货政策:7天内无理由退货,15天内质量问题换货"),newDocument("配送说明:北京上海次日达,其他城市3-5天"),newDocument("会员权益:VIP享受95折优惠,生日月双倍积分"),newDocument("支付方式:支持支付宝、微信、银行卡"),newDocument("售后服务:客服电话400-xxx-xxxx,工作时间9:00-21:00"));vectorStore.add(documents);}@Test@DisplayName("查询退货政策,应返回相关文档")voidshouldRetrieveReturnPolicy(){List<Document>results=vectorStore.similaritySearch(SearchRequest.query("怎么退货").withTopK(3));assertThat(results).isNotEmpty();assertThat(results.get(0).getContent()).containsAnyOf("退货","退换");}@Test@DisplayName("查询配送,不应返回支付相关文档")voidshouldNotRetrieveIrrelevantDocs(){List<Document>results=vectorStore.similaritySearch(SearchRequest.query("快递多久到").withTopK(3));// 前三名不应该包含支付文档assertThat(results).extracting(Document::getContent).noneMatch(content->content.contains("支付"));}@Test@DisplayName("检索结果应包含相关性分数")voidshouldIncludeRelevanceScore(){List<Document>results=vectorStore.similaritySearch(SearchRequest.query("会员有什么优惠").withTopK(3));// 验证相关性分数递减for(inti=1;i<results.size();i++){doubleprevScore=Double.parseDouble(results.get(i-1).getMetadata().get("distance"));doublecurrScore=Double.parseDouble(results.get(i).getMetadata().get("distance"));assertThat(currScore).isGreaterThanOrEqualTo(prevScore);// 距离越大越不相关}}}

RAG 端到端测试

@SpringBootTestclassRAGEndToEndTest{@AutowiredRAGServiceragService;@ParameterizedTest@CsvSource({"怎么退货, 7天, 无理由","快递多久到, 次日达, 3-5天","VIP有什么权益, 95折, 双倍积分","怎么联系客服, 400, 9:00"})@DisplayName("RAG问答质量验证")voidshouldAnswerCorrectly(Stringquestion,StringexpectedKeyword1,StringexpectedKeyword2){Stringanswer=ragService.answer(question);assertThat(answer).as("问题 '%s' 的回答应包含关键信息",question).containsAnyOf(expectedKeyword1,expectedKeyword2);}}

RAG 评估指标

@ComponentpublicclassRAGEvaluator{/** * 计算检索准确率 * @param query 查询 * @param retrievedDocs 检索结果 * @param relevantDocs 相关文档集合(人工标注) */publicRetrievalMetricsevaluateRetrieval(Stringquery,List<Document>retrievedDocs,Set<String>relevantDocs){Set<String>retrievedIds=retrievedDocs.stream().map(Document::getId).collect(Collectors.toSet());// Precision@KlongrelevantRetrieved=retrievedIds.stream().filter(relevantDocs::contains).count();doubleprecisionAtK=(double)relevantRetrieved/retrievedIds.size();// Recalldoublerecall=(double)relevantRetrieved/relevantDocs.size();// F1doublef1=2*precisionAtK*recall/(precisionAtK+recall);returnnewRetrievalMetrics(precisionAtK,recall,f1);}/** * 答案忠实度:生成的答案是否基于检索到的文档 */publicdoublecalculateFaithfulness(Stringanswer,List<Document>sourceDocs){// 提取答案中的事实陈述List<String>answerFacts=extractFacts(answer);// 检查每个事实是否能在源文档中找到支持longsupported=answerFacts.stream().filter(fact->sourceDocs.stream().anyMatch(doc->doc.getContent().contains(fact))).count();return(double)supported/answerFacts.size();}}

五、A/B 测试框架

模型迭代后,怎么验证新版本比旧版本好?

@ServicepublicclassModelABTestService{@AutowiredprivateChatClientmodelA;// 当前版本@AutowiredprivateChatClientmodelB;// 新版本privatefinalRandomrandom=newRandom();publicABTestResponsechat(StringuserMessage){// 随机分流booleanuseModelB=random.nextBoolean();Stringresponse;StringmodelVersion;longstartTime=System.currentTimeMillis();if(useModelB){response=modelB.prompt().user(userMessage).call().content();modelVersion="B";}else{response=modelA.prompt().user(userMessage).call().content();modelVersion="A";}longlatency=System.currentTimeMillis()-startTime;// 记录日志供后续分析logABTestResult(userMessage,response,modelVersion,latency);returnnewABTestResponse(response,modelVersion);}privatevoidlogABTestResult(Stringquestion,Stringanswer,Stringversion,longlatency){// 记录到日志或埋点系统,后续分析:// - 哪个版本的满意度高?// - 哪个版本的 latency 低?// - 哪个版本的错误率低?}}

六、小结

今天咱们进入了AI测试这个新领域:

主题测试方法关键指标
Prompt测试规则验证、参数化测试规则遵守率
输出质量规则评估 + LLM-as-a-Judge相关性、准确性、安全性
RAG检索向量相似度搜索验证Precision@K、Recall、F1
RAG生成端到端问答验证忠实度、答案完整性
A/B测试流量分流对比满意度、Latency、错误率

一句话总结:AI测试的核心挑战是"不确定性"——我们用规则约束边界、用指标量化质量、用A/B验证改进。这不是完美的方案,但是当前最实用的路径。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/15 17:20:11

Markdown已过时?Claude Code工程师、卡帕西纷纷力挺HTML

编译 | 苏宓出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;“说实话&#xff0c;我现在几乎完全不用 Markdown 了。”最近&#xff0c;Anthropic 旗下 Claude Code 的工程师 Thariq Shihipar 撰写了一篇引人深思的文章&#xff0c;他表示&#xff1a;“自己如今更…

作者头像 李华
网站建设 2026/5/15 17:17:05

基于模块化与事件驱动的AI智能体平台构建实战:从原理到部署

1. 项目概述&#xff1a;一个面向开发者的智能体构建与编排平台 最近在开源社区里&#xff0c;一个名为 98kiran/agenthq 的项目引起了我的注意。乍一看这个仓库名&#xff0c;你可能会觉得它和某个著名的游戏枪械型号有关&#xff0c;但实际上&#xff0c;它是一个非常硬核的…

作者头像 李华
网站建设 2026/5/15 17:17:04

为团队统一开发环境使用Taotoken CLI一键配置各工具密钥

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 为团队统一开发环境使用Taotoken CLI一键配置各工具密钥 作为技术团队的负责人&#xff0c;当团队开始使用多个大模型进行开发时&a…

作者头像 李华
网站建设 2026/5/15 17:16:31

软件开发复杂性解析:从需求管理到系统设计的工程实践

1. 项目概述&#xff1a;从一行代码到一座城市的距离“软件开发过程是一个复杂过程”&#xff0c;这句话听起来像一句正确的废话&#xff0c;但只有真正在项目泥潭里摸爬滚打过的人&#xff0c;才能体会到“复杂”二字背后那沉甸甸的分量。它远不止是“写代码很麻烦”那么简单&…

作者头像 李华