Java 大模型服务怎么验:Schema、依赖隔离与 Eval 回归
模型回答“看起来通顺”并不能说明它能进入业务链路。结构化输出可能不符合契约,检索结果也可能和回答脱节。评估应把这两类问题拆开:先校验硬约束,再用固定样本集观察回答质量。
下面的分层测试适合放进 CI。阈值和样本集必须由具体业务定义,文中的数值仅用于说明配置位置,不是通用上线标准。
flowchart TD subgraph CI Pipeline ["自动化 CI/CD 评估流水线"] UT["单元测试层<br/>(Prompt 结构断言 & JSON Schema 硬打靶)"] IT["集成测试层<br/>(Mock LLM API & 向量数据库隔离测试)"] E2E["端到端 Eval 层<br/>(Faithfulness & Answer Relevance 自动化打分)"] end Input["代码提交 / Prompt 变更"] --> UT UT -->|测试通过| IT IT -->|测试通过| E2E E2E -->|评估分值 ≥ 阈值 0.85| Deployment["金丝雀发布 / 部署上线"] E2E -->|评估分值未达标| Block["阻断构建 & 自动打回"]1. 单元测试层:Prompt 结构化断言与 JSON Schema 硬打靶
单元测试阶段不应直接依赖远程 LLM API。网络延迟和模型流式输出会显著拉长 CI 构建周期,且模型输出的随机性极易导致单元测试出现非确定性的偶发失败(Flaky Tests)。单元测试的核心职责,是验证系统内部解析器针对模型输出的防御性校验逻辑与结构容错能力。
在涉及函数调用(Tool Calling)或结构化数据抽取的场景中,后端需要接收格式严谨的 JSON 数据。解析器必须能正确处理各类畸形响应,例如包含 Markdown 代码块标记(```json)、字段缺失、或者数据类型不匹配(例如将整型输出为字符串)。
运行单元测试打靶命令,可以检测解析逻辑在极端边缘数据下的响应能力:
mvn test -Dtest=LlmResponseParserTest -Dlogging.level.org.springframework=WARN如果运行日志中抛出未处理的运行时异常,表明解析层缺乏足够的容错兜底手段:
[ERROR] LlmResponseParserTest.testMalformedJson:142 Expected Exception, but threw NullPointerException [WARN] Failed to parse LLM response: missing key 'action_name' in JSON payload: {"result": "ok"}防护解析器的核心实现逻辑如下,该类负责提取核心文本段落并执行严格的 JSON Schema 校验:
package com.example.ai.testing; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.networknt.schema.JsonSchema; import com.networknt.schema.JsonSchemaFactory; import com.networknt.schema.SpecVersion; import com.networknt.schema.ValidationMessage; import java.util.Set; public class StrictLlmOutputParser { private final ObjectMapper objectMapper; private final JsonSchema jsonSchema; public StrictLlmOutputParser(String schemaJson) { this.objectMapper = new ObjectMapper(); JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); this.jsonSchema = factory.getSchema(schemaJson); } public JsonNode parseAndValidate(String rawLlmResponse) throws IllegalArgumentException { if (rawLlmResponse == null || rawLlmResponse.isBlank()) { throw new IllegalArgumentException("LLM 响应文本为空"); } String cleanedContent = extractJsonBlock(rawLlmResponse); try { JsonNode node = objectMapper.readTree(cleanedContent); Set<ValidationMessage> errors = jsonSchema.validate(node); if (!errors.isEmpty()) { StringBuilder sb = new StringBuilder("LLM 响应数据未通过 Schema 结构校验: "); for (ValidationMessage error : errors) { sb.append(error.getMessage()).append("; "); } throw new IllegalArgumentException(sb.toString()); } return node; } catch (Exception e) { throw new IllegalArgumentException("无法解析 JSON 格式: " + e.getMessage(), e); } } private String extractJsonBlock(String raw) { String trimmed = raw.trim(); if (trimmed.startsWith("```")) { int firstNewLine = trimmed.indexOf('\n'); int lastBackticks = trimmed.lastIndexOf("```"); if (firstNewLine != -1 && lastBackticks > firstNewLine) { return trimmed.substring(firstNewLine + 1, lastBackticks).trim(); } } return trimmed; } }2. 集成测试层:Mock 隔离与向量数据库状态重置
集成测试关注 AI 后端与依赖组件(如向量数据库 Milvus/PGVector、缓存 Redis)之间的协同工作。在 CI 环境中,推荐使用 WireMock 模拟大模型 HTTP API 接口,并配合 Testcontainers 在 Docker 容器内启动独立的向量数据库实例,确保测试环境互相隔离。
@SpringBootTest @Testcontainers class RAGIntegrationTest { @Container static MilvusContainer milvus = new MilvusContainer("milvusdb/milvus:v2.3.0"); @Autowired private VectorStoreService vectorStoreService; @Test void shouldRetrieveCorrectContextAndHandleApiTimeout() { WireMock.stubFor(WireMock.post(WireMock.urlEqualTo("/v1/chat/completions")) .willReturn(WireMock.aResponse() .withStatus(200) .withHeader("Content-Type", "application/json") .withBody("{\"choices\":[{\"message\":{\"content\":\"Mock response\"}}]}"))); List<Document> docs = vectorStoreService.similaritySearch("架构设计规则"); Assertions.assertFalse(docs.isEmpty(), "向量检索未匹配到相关文档"); } }3. 端到端 Eval 层:自动化指标定量打分体系
端到端评估层用于解决“回答质量如何定量”的挑战。目前行业通行的自动化 Evaluation 框架(例如基于 Ragas 的评估模型)主要依赖四个维度指标:
- Context Precision(上下文精确度):检索到的知识库文档切片中,与用户问题直接相关的切片所占比例。
- Context Recall(上下文召回率):标准答案所需的关键信息中,被检索模块正确捕获的比例。
- Faithfulness(忠实度):大模型生成的回答是否完全基于检索到的上下文,无中生有即判定为忠实度低(幻觉)。
- Answer Relevance(回答相关性):模型的最终输出是否直接切中用户提问的核心意图。
忠实度(Faithfulness)的推导计算公式如下:
$$
F = \frac{|V_{claims} \cap C_{context}|}{|V_{claims}|}
$$
其中 $V_{claims}$ 表示从生成回答中提取出的独立事实断言集合,$C_{context}$ 表示检索到的知识库上下文文本集合。如果模型输出中包含了上下文未提及的语句,该分值将随之降低。
在 CI 自动化阶段,通过预先标注好的黄金测试集(Golden Dataset)运行评测脚本,当总体综合评分低于设定的阈值(例如 0.85)时,构建任务会自动挂起,防止质量退化的 Prompt 被合并到主干分支。
4. 模拟压测场景与生产避坑指南
为验证 AI 后端在异常流量下的鲁棒性,可以通过模拟压测场景观察高并发下的系统状态:
- 场景模拟设定:按目标流量逐步增加长文本生成并发,并向上游模型 API 注入可控的延迟与错误。并发数和延迟区间应来自容量规划,而不是固定套用示例。
- 故障现象观察:底层 HTTP 连接池(如 OkHttp/Apache HttpClient)因为等待响应线程被耗尽,导致后续发起的普通查询请求产生大量
TimeoutException。 - 配置方向:为 AI 调用设置独立的并发限制、超时与熔断,避免占满普通请求资源。是否使用线程池隔离或异步客户端,要结合当前 Web 栈和调用模型选择。例如可用 Resilience4j 表达熔断规则:
resilience4j.circuitbreaker: instances: llmService: slidingWindowSize: 20 failureRateThreshold: 50 slowCallDurationThreshold: 4000ms slowCallRateThreshold: 70 waitDurationInOpenState: 10000ms工程实施要点摘要:
- Prompt 版本化管理:将 Prompt 与业务代码解耦,采用 Git 版本库托管,并为每次调整标记 Semantic Versioning。
- 多级降级兜底:当主模型触发熔断或输出解析持续失败时,系统应自动降级至轻量级模型或静态兜底提示语,确保核心链路可用。
- 日志与 Trace 全链路追踪:在 OpenTelemetry 追踪上下文(Span)中附加
prompt_tokens、completion_tokens以及latency属性,便于后续治理成本与性能瓶颈。