Spring AI 对多模型结构化输出的 Schema 兼容性处理把 LLM 接入生产环境的 Java 业务系统时最让人头疼的往往不是 Prompt 怎么写而是模型返回数据的稳定性。很多团队在单接 OpenAI 时直接使用 Spring AI 提供的BeanOutputConverter或官方推荐的StructuredOutputConverter定义一个 Java POJO框架自动生成 JSON Schema 注入 Prompt解析顺畅无阻。当业务需要做多模型路由把一部分非核心流量切到通义千问、DeepSeek、Claude 甚至本地私有化部署的 Ollama如 Qwen2.5-7B、Llama3.1时JSON 反序列化异常就会频繁冒出来。各个模型厂商在底层对 JSON Schema 的规范支持程度参差不齐。有的模型强制要求 JSON Schema 的根节点必须包含additionalProperties: false有的模型遇到复杂泛型或递归嵌套对象时会直接忽略required约束本地小参数量模型在生成长 JSON 时经常会夹杂 Markdown 代码块包裹符如json或者输出截断。如果不做统一的 Schema 兼容与解析容错多模型路由就会直接演变成下游业务的崩溃源。Spring AI 默认 Schema 生成机制分析Spring AI 默认使用BeanOutputConverterT来处理结构化输出。深入其内部实现它依赖com.github.victools.jsonschema.generator库通过反射扫描传入的 Java Class并生成符合 Draft 7 或 Draft 2020-12 标准的 JSON Schema。生成的 Schema 会被拼接进系统预设的 Format 提示词中Your response should be in JSON format. Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation. Do not include markdown code blocks in the response. Remove the json markdown from the output. Here is the JSON Schema instance your output must adhere to:这种机制在配合 GPT-4o 这类高级模型时基本能正常工作但在多厂商混用场景下暴露出两个明显缺陷Schema 语法方言不兼容部分中转网关或厂商 API例如某些支持 Function Call / Response Format 的专用 endpoint要求极度精简的 Schema 语法不支持title、description以外的扩展关键字甚至不支持 Java 枚举生成的复杂的anyOf结构。后置解析过于脆弱BeanOutputConverter的convert(String text)方法内部直接调用 Jackson 的objectMapper.readValue(text, this.clazz)。一旦模型在 JSON 前后带有一句“好的这是为您提取的信息”或者尾部多了一个逗号整个调用链条立刻抛出ConversionFailedException。统一结构化输出转换器设计为了适配混合模型路由架构不能完全依赖 Spring AI 默认的转换逻辑需要构建一套兼容各主流厂商 Schema 方言并具备自愈能力的RobustBeanOutputConverter。1. 业务返回模型定义定义一个电商领域标准的工单实体提取 POJO包含基础字段、枚举类型以及集合嵌套package com.example.ai.schema.model; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyDescription; import lombok.Data; import java.util.List; Data public class OrderTicketAnalysis { JsonProperty(required true) JsonPropertyDescription(工单所属的订单编号) private String orderId; JsonProperty(required true) JsonPropertyDescription(客户核心诉求类别) private TicketCategory category; JsonPropertyDescription(用户情绪评分范围 1 到 55 为极其愤怒) private Integer urgencyLevel; JsonPropertyDescription(从文本中提取的具体商品问题条目列表) private ListIssueItem issues; public enum TicketCategory { LOGISTICS_DELAY, REFUND_REQUEST, PRODUCT_DAMAGE, OTHER } Data public static class IssueItem { JsonProperty(required true) JsonPropertyDescription(涉及的商品名称) private String productName; JsonPropertyDescription(具体损坏或缺失描述) private String problemDetail; } }2. 自定义 Schema 生成器与清洗逻辑针对不同厂商模型精简 Schema剥离导致解析报错的元数据同时增加对输出内容的预清洗Markdown 代码块剥离、前后缀废话剪裁、宽松反序列化配置package com.example.ai.schema.converter; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.victools.jsonschema.generator.Option; import com.github.victools.jsonschema.generator.OptionPreset; import com.github.victools.jsonschema.generator.SchemaGenerator; import com.github.victools.jsonschema.generator.SchemaGeneratorConfig; import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder; import com.github.victools.jsonschema.generator.SchemaVersion; import org.springframework.ai.converter.StructuredOutputConverter; import org.springframework.lang.NonNull; import java.util.regex.Matcher; import java.util.regex.Pattern; public class RobustBeanOutputConverterT implements StructuredOutputConverterT { private final ClassT targetClass; private final ObjectMapper objectMapper; private final String jsonSchema; private static final Pattern MARKDOWN_JSON_PATTERN Pattern.compile( (?:json)?\\s*([\\s\\S]*?)\\s*, Pattern.CASE_INSENSITIVE ); public RobustBeanOutputConverter(ClassT targetClass) { this.targetClass targetClass; this.objectMapper createResilientObjectMapper(); this.jsonSchema generateCompatibleSchema(targetClass); } private ObjectMapper createResilientObjectMapper() { ObjectMapper mapper new ObjectMapper(); // 允许非标准 JSON 语法单引号、未引号字段、控制字符等 mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); mapper.configure(JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, true); mapper.configure(JsonParser.Feature.ALLOW_TRAILING_COMMA, true); // 忽略目标 POJO 中不存在的额外字段 mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 空字符串允许转换为 null mapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true); return mapper; } private String generateCompatibleSchema(ClassT clazz) { SchemaGeneratorConfigBuilder configBuilder new SchemaGeneratorConfigBuilder( SchemaVersion.DRAFT_2020_12, OptionPreset.PLAIN_JSON ); // 统一配置展开枚举为简单字符串列表避免生成复杂的 anyOf configBuilder.with(Option.FLATTENED_ENUMS) .without(Option.SCHEMA_VERSION_INDICATOR); SchemaGeneratorConfig config configBuilder.build(); SchemaGenerator generator new SchemaGenerator(config); JsonNode jsonNode generator.generateSchema(clazz); return jsonNode.toString(); } Override public String getFormat() { return String.format( 请严格按照以下 JSON Schema 输出纯 JSON 数据。禁止输出解释性文字禁止添加 markdown 代码块标签。\n JSON Schema:\n%s, this.jsonSchema ); } Override public T convert(NonNull String source) { String cleanedJson extractJsonContent(source); try { return this.objectMapper.readValue(cleanedJson, this.targetClass); } catch (Exception e) { throw new IllegalArgumentException(结构化数据反序列化失败原始内容: source, e); } } /** * 智能提取字符串中的纯 JSON 块 */ private String extractJsonContent(String raw) { if (raw null || raw.trim().isEmpty()) { return {}; } String trimmed raw.trim(); // 1. 优先提取 Markdown 代码块内部内容 Matcher matcher MARKDOWN_JSON_PATTERN.matcher(trimmed); if (matcher.find()) { trimmed matcher.group(1).trim(); } // 2. 如果包含首尾外围文本寻找最外层大括号 int startBrace trimmed.indexOf({); int endBrace trimmed.lastIndexOf(}); if (startBrace ! -1 endBrace ! -1 endBrace startBrace) { trimmed trimmed.substring(startBrace, endBrace 1); } return trimmed; } }多模型路由中的客户端调用与降级策略在业务服务中通过 Spring AI 的ChatClient结合我们实现的RobustBeanOutputConverter。当主模型如公有云高参数量模型调用失败或输出结构断裂时无缝降级至备用模型并执行两次重试package com.example.ai.schema.service; import com.example.ai.schema.converter.RobustBeanOutputConverter; import com.example.ai.schema.model.OrderTicketAnalysis; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.PromptTemplate; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import java.util.Map; Slf4j Service RequiredArgsConstructor public class TicketAnalysisService { Qualifier(primaryChatClient) private final ChatClient primaryChatClient; Qualifier(fallbackChatClient) private final ChatClient fallbackChatClient; private final RobustBeanOutputConverterOrderTicketAnalysis converter new RobustBeanOutputConverter(OrderTicketAnalysis.class); public OrderTicketAnalysis analyzeTicket(String userComplaint) { String templateText 请分析以下客户工单反馈内容提取结构化信息\n\n 【客户反馈】{complaint}\n\n {format}; PromptTemplate promptTemplate new PromptTemplate(templateText); Prompt prompt promptTemplate.create(Map.of( complaint, userComplaint, format, converter.getFormat() )); // 优先使用主模型调用 try { String content primaryChatClient.prompt(prompt) .call() .content(); return converter.convert(content); } catch (Exception e) { log.warn(主模型结构化提取失败触发降级模型重试: {}, e.getMessage()); return executeFallback(prompt); } } private OrderTicketAnalysis executeFallback(Prompt prompt) { try { String content fallbackChatClient.prompt(prompt) .call() .content(); return converter.convert(content); } catch (Exception e) { log.error(降级模型调用依然失败进入兜底对象构建, e); OrderTicketAnalysis fallbackObject new OrderTicketAnalysis(); fallbackObject.setCategory(OrderTicketAnalysis.TicketCategory.OTHER); fallbackObject.setUrgencyLevel(3); return fallbackObject; } } }架构演进思考与排坑经验在落地多模型架构时不能只寄希望于大模型具备 100% 的遵循能力。以下是几个经过压测和生产验证的实践准则枚举字段必须声明默认值小模型很容易在枚举匹配上产生微小幻觉如将REFUND_REQUEST写成REFUND_REQUIREMENT。在 Jackson 反序列化时应配合JsonEnumDefaultValue注解或编写自定义反序列化器遇到未知枚举值时兜底为OTHER避免整条链路中断。Schema 深度不宜超过三层超过三层的嵌套数组和对象结构开源 7B / 14B 参数级别模型的失误率呈指数级上升。对于特别复杂的模型应拆解为多次链式调用Chain of Thought每次只提取一个子对象。结合平台原生 JSON 模式如果底层模型支持response_format: { type: json_object }应在 Spring AI 的ChatOptions中显式开启。这能直接从采样层抑制模型吐出 Markdown 标签配合上文的预清洗逻辑双重保障系统的健壮性。
企业数字化 ERP 产品动态
相关推荐
使用 @napi-rs/cli 的 rename 命令重命名 napi-rs 项目:选项详解与源码级工作原理 开发工具后端 【免费下载链接】napi-rs A framework for building compiled Node.js add-ons in Rust via Node-API 项目地址: https://gitcode.com/gh_mirrors/na/napi-rs 点击查看 免费下载 导读
napi rename 是 napi-rs/cli 提供的一条项目级重命名命令&#x… · 2026/9/27 8:04:43
使用 Terratest 为 Azure Cosmos DB Terraform 模块编写自动化测试 测试开发工具DevOps质量保障 【免费下载链接】terratest Terratest is a Go library that makes it easier to write automated tests for your infrastructure code. 项目地址: https://gitcode.com/gh_mirrors/te/terratest 点击查看 免费下载 导读
本文以 Terr… · 2026/9/27 8:04:18
网页的维护与更新完整流程 网页维护更新哪家强?3个步骤告别拖延症 改个文案建站公司拖一周?这种憋屈事谁没干过?很多老板觉得找对 哪家好 的供应商就能一劳永逸,其实大错特错。网站交付只是开始,后续的 网页的维护与更新 才是决定生死的关键。… · 2026/9/27 8:04:18
isomorphic-git readTag 完全指南:直接读取并解析 annotated tag 对象 开发工具 【免费下载链接】isomorphic-git A pure JavaScript implementation of git for node and browsers! 项目地址: https://gitcode.com/gh_mirrors/is/isomorphic-git 点击查看 免费下载 readTag 是 isomorphic-git(一个纯 JavaScript 实现的 Gi… · 2026/9/27 8:46:24
Midway 开源仓库协作指南:Issue 规范、Commit 约束与版本发布全流程解析 后端微服务云原生 【免费下载链接】midway 🍔 A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate w… · 2026/9/27 8:46:17
前端CI流水线构建缓存深度调优:GitHubActions与TurboCache 前端CI流水线构建缓存深度调优:GitHubActions与TurboCache在大型前端 Monorepo 工程或独立全栈产品的持续集成(CI/CD)发版流程中,“每次提 PR 或发版时 CI 流水线构建极其漫长(超过 8~12 分钟)” 是摧毁团队… · 2026/9/27 8:46:17
网站建设公司营销推广:3个报价策略让客户秒懂价值 网站建设公司营销推广:3个报价策略让客户秒懂价值 不会写代码?想做网站又怕被坑?先别急着问建站报价。 很多老板找网站建设公司营销推广时,第一反应是“最便宜多少钱”。但真相是:低价往往意味着模板堆砌、无SEO优化、后期维护扯皮。真正的痛点不是… · 2026/9/27 8:46:11
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现 简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01
汕头网站建设制作厂家避坑指南:5大注意事项救急 汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习 简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现 简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01
汕头网站建设制作厂家避坑指南:5大注意事项救急 汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习 简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01