本文介绍如何在Spring Boot应用程序中使用JSON Schema验证AI的结构化输出
pom.xml
<dependency> <groupId>com.networknt</groupId> <artifactId>json-schema-validator</artifactId> <version>1.4.0</version> </dependency>
{ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "review": { "type": "object", "properties": { "rating": { "type": "integer", "minimum": 1, "maximum": 5, "description": "产品评分(1-5星)" }, "summary": { "type": "string", "maxLength": 100, "description": "评论摘要" }, ... "sentiment": { "type": "string", "enum": ["positive", "neutral", "negative"], "description": "整体情感倾向" } }, "required": ["rating", "summary", "sentiment"] } } }
@Service public class AIReviewService { private final OpenAiClient openAiClient; private final JsonSchemaValidator schemaValidator; @Value("classpath:schemas/review-output-schema.json") private Resource schemaResource; public ReviewResponse generateStructuredReview(String productDescription) { var request = ChatCompletionRequest.builder() .model("gpt-4") .messages(List.of( new Message("system", "你是一个专业的产品评论分析师。请根据用户提供的产品描述生成结构化的评论。"), new Message("user", productDescription) )) .functions(List.of( new FunctionDefinition( "generate_review", "生成产品评论", schemaResource // JSON Schema作为函数参数定义 ) )) .functionCall("generate_review") // 强制使用指定的函数 .build(); return openAiClient.createChatCompletion(request) .map(this::validateAndParseResponse) .orElseThrow(() -> new AIGenerationException("Failed to generate review")); } private ReviewResponse validateAndParseResponse(String jsonResponse) { // 验证生成的JSON是否符合Schema if (!schemaValidator.isValid(jsonResponse)) { throw new InvalidOutputException("AI generated invalid review format"); } // 解析验证通过的JSON return objectMapper.readValue(jsonResponse, ReviewResponse.class); } }
@Component public class AIOutputValidator { private final Map<String, JsonSchema> schemaCache = new ConcurrentHashMap<>(); private final ObjectMapper objectMapper; public AIOutputValidator(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } public ValidationResult validateOutput(String output, String schemaPath) { JsonSchema schema = getOrLoadSchema(schemaPath); try { JsonNode outputNode = objectMapper.readTree(output); Set<ValidationMessage> errors = schema.validate(outputNode); if (errors.isEmpty()) { return ValidationResult.success(); } return ValidationResult.failure(errors.stream() .map(ValidationMessage::getMessage) .collect(Collectors.toList())); } catch (Exception e) { return ValidationResult.failure(List.of("Invalid JSON format: " + e.getMessage())); } } private JsonSchema getOrLoadSchema(String schemaPath) { return schemaCache.computeIfAbsent(schemaPath, this::loadSchema); } private JsonSchema loadSchema(String schemaPath) { try { Resource resource = new ClassPathResource(schemaPath); JsonNode schemaNode = objectMapper.readTree(resource.getInputStream()); return JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7) .getSchema(schemaNode); } catch (Exception e) { throw new SchemaLoadException("Failed to load schema: " + schemaPath, e); } } }
@ControllerAdvice public class AIExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(InvalidOutputException.class) public ResponseEntity<ErrorResponse> handleInvalidOutput(InvalidOutputException ex) { ErrorResponse error = new ErrorResponse( "AI_OUTPUT_VALIDATION_ERROR", "AI生成的输出格式无效", ex.getValidationErrors() ); return ResponseEntity.badRequest().body(error); } @ExceptionHandler(AIGenerationException.class) public ResponseEntity<ErrorResponse> handleAIGeneration(AIGenerationException ex) { ErrorResponse error = new ErrorResponse( "AI_GENERATION_ERROR", "AI生成内容失败", Collections.singletonList(ex.getMessage()) ); return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(error); } }
请按照以下 JSON Schema 格式生成输出: { "type": "object", "properties": { "field1": {"type": "string"}, "field2": {"type": "number"} }, "required": ["field1", "field2"] }