统一环境:IDEA2026.1.1、JDK21、SpringBoot3.5.14、SpringAI1.1.6、智谱glm-4-flash、MySQL8.0

为了后续方便调试运行代码,先将持久化记忆移除,因此需要改动之前代码。

将ChatMemoryConfig类的@Configuration注解注释

package demo.ai.conf;

//......

//@Configuration
public class ChatMemoryConfig {

将ChatClientConfiguration 类的@Configuration注解前的注释去掉,或者加上@Configuration注解

package demo.ai.conf;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ChatClientConfiguration {
    @Bean
    public ChatClient chatClient(ChatClient.Builder chatClientBuilder) {
        return chatClientBuilder.build();
    }
}

一、前言:为什么大模型必须结构化输出?

很多同学做完 Spring AI 入门demo后,都会遇到一个上线必死的致命问题:大模型的返回是「自由文本」。

你让它返回 JSON,它可能:

  • 前后带一大段解释、开场白、总结

  • 键名大小写不统一、缺字段、多字段

  • 换行、空格、注释乱飞

  • 有时候返回数组,有时候返回对象,格式不稳定

这种自由文本,在页面展示可以“凑合看”,但完全无法用于业务开发

  • 无法直接 JSON 反序列化为实体类

  • 无法入库、无法做字段校验、无法做后续业务逻辑

  • 前端无法直接解析,接口数据极不稳定

所谓 AI 工程化,很大一部分工作就是:驯服大模型的输出格式。

Spring AI 提供了一套开箱即用的结构化输出能力,无需手动正则清洗、无需手动截字符串,直接实现:指定实体类 = 模型自动按格式返回 + 框架自动映射

本篇解决 Spring AI 结构化输出全场景实战,覆盖:普通JSON、实体映射、数组返回、格式兜底、生产避坑。

二、原生痛点:无结构化约束的自由输出(反面案例)

我们先看最原始、无任何格式约束的写法,也是生产最不推荐的写法。

2.1 普通自由对话接口(问题复现)

@RestController
@RequestMapping("/struct")
public class StructController {

    @Resource
    private ChatClient chatClient;

    // 纯自由文本输出,生产不可用
    @GetMapping("/chat")
    public String chat(String topic){
        return chatClient.prompt()
                .system("你是专业的技术科普助手")
                .user("简单介绍下" + topic)
                .call()
                .content();
    }
}

访问结果问题

大模型会返回一大段自然语言段落,语句自由、格式随机,我们无法定义 Java 对象接收,无法做字段取值、判断、入库。

三、核心方案一:Prompt 指令约束 JSON 输出(基础版)

最简单的结构化方式:通过系统提示词强制约束返回格式,要求模型只返回纯净 JSON,禁止多余描述。

3.1 固定格式提示词模板

核心约束三要素:

  • 只返回 JSON,不要解释、不要开场白、不要markdown

  • 严格按照指定字段名、字段类型返回

  • 字段缺失填空值,禁止随意增减字段

3.2 代码实战:纯 JSON 字符串返回

/**
 * 基础结构化:提示词约束JSON返回
 * 访问地址:http://localhost:8080/struct/json?name=Java
 */
@GetMapping("/json")
public String jsonStruct(String name){
    String systemPrompt = """
            你是数据生成助手,请严格按照JSON格式返回结果。
            要求:
            1. 只返回纯净JSON字符串,不要任何解释、不要多余文字、不要markdown
            2. 严格遵守字段定义,不新增、不缺失字段
            3. 字段:name(名称), introduce(简介), advantage(核心优势数组), score(评分0-100)
            
            输出示例:
            {"name":"","introduce":"","advantage":[],"score":0}
            """;

    return chatClient.prompt()
            .system(systemPrompt)
            .user("请介绍技术栈:" + name)
            .call()
            .content();
}

此时模型会强制输出标准JSON,我们可以手动使用 Jackson 进行反序列化。

但这种方式依然有缺陷:需要自己写解析代码、异常兜底繁琐、字段类型校验需要手动处理

四、核心方案二:Spring AI 自动实体映射(生产首选)

Spring AI 最强结构化能力:直接根据 Java 实体类结构,自动约束模型输出格式,并自动完成映射

开发者只需定义实体,无需手写 JSON 规则、无需解析、无需兜底,框架全包。

4.1 定义结构化返回实体类

对应上面的技术栈介绍结构,创建实体:

package demo.ai.domain;

import lombok.Data;
import java.util.List;

/**
 * 技术栈结构化返回实体
 */
@Data
public class TechInfo {
    // 技术名称
    private String name;
    // 简介
    private String introduce;
    // 核心优势
    private List<String> advantage;
    // 综合评分
    private Integer score;
}

4.2 entity() 自动结构化映射实战

ChatClient 提供 .entity() 方法,是 Spring AI 生产结构化输出的终极方案

/**
 * 实体自动结构化映射(生产推荐)
 * 访问地址:http://localhost:8080/struct/entity?name=SpringAI
 */
@GetMapping("/entity")
public TechInfo entityStruct(String name){
    String systemPrompt = """
            你是专业的技术科普助手,严格按照用户要求输出结构化数据。
            禁止输出多余解释,只返回符合结构的规整数据。
            """;

    return chatClient.prompt()
            .system(systemPrompt)
            .user("详细介绍技术栈:" + name)
            // 核心:自动根据实体结构约束模型输出 + 自动反序列化
            .call()
            .entity(TechInfo.class);
}

核心优势

  • 无需手写 JSON 字段规则,框架自动根据实体生成格式约束

  • 自动 JSON 解析、自动类型转换

  • 返回值直接是 Java 对象,可直接业务处理、入库、返回前端

五、高阶实战:数组类型结构化输出

业务中经常需要返回列表数据(例如:推荐列表、知识点列表、案例列表)。

Spring AI 支持直接映射 List 集合 结构化输出。

5.1 数组结构化代码实现

/**
 * 数组/列表 结构化输出
 * 访问地址:http://localhost:8080/struct/list
 */
@GetMapping("/list")
public List<TechInfo> listStruct(){
    String systemPrompt = """
            你是技术推荐助手,输出规整结构化数据。
            """;

    return chatClient.prompt()
            .system(systemPrompt)
            .user("推荐3个当下热门的AI开发技术栈,输出完整信息")
            // 核心:返回实体集合
            .call()
            .entity(new ParameterizedTypeReference<List<TechInfo>>() {});
}

通过 ParameterizedTypeReference 实现泛型列表映射,完美解决 List 结构化返回问题,直接得到实体列表。

六、生产关键:结构化异常兜底与容错机制

虽然 Spring AI 结构化能力极强,但生产环境必须做容错兜底,杜绝极低概率格式解析异常导致服务报错。

6.1 全局 try-catch 兜底

封装统一结构化调用模板,异常时返回空对象或默认值,不抛500:

/**
 * 带生产容错的结构化接口
 */
@GetMapping("/safe")
public TechInfo safeStruct(String name){
    try {
        String systemPrompt = "你是专业技术助手,严格结构化输出";
        return chatClient.prompt()
                .system(systemPrompt)
                .user("介绍技术栈:" + name)
                .call()
                .entity(TechInfo.class);
    }catch (Exception e){
        // 解析异常兜底,返回空对象,保证接口稳定
        TechInfo empty = new TechInfo();
        empty.setName(name);
        empty.setIntroduce("暂无数据");
        return empty;
    }
}

6.2 生产容错规范

  • 所有结构化接口必须加异常捕获,禁止直接抛出解析异常

  • 异常时返回默认空实体/空集合,保证前端不报错

  • 关键场景可打印异常日志,用于监控告警

七、结合 PromptTemplate 模板化结构化(终极生产方案)

结合上一篇 Prompt模板化 能力,将结构化提示词统一外置、参数化,实现:提示词可配置 + 输出结构化 + 代码高复用

7.1 外置结构化模板文件

resources/prompts/tech-struct.st

你是专业的{field}技术科普助手。
严格按照结构化数据输出,只返回规整数据,禁止多余解释、禁止markdown、禁止多余文字。
使用中文请详细介绍:{techName}

7.2 模板化 + 结构化 整合代码

@Value("classpath:prompts/tech-struct.st")
private Resource techStructTmpl;

/**
 * 模板化 + 结构化 生产终极方案
 */
@GetMapping("/tmplStruct")
public TechInfo tmplStruct(String techName){
    // 加载模板并渲染参数
    SystemPromptTemplate template = new SystemPromptTemplate(techStructTmpl);
    Message systemMsg = template.createMessage(Map.of("field", "后端AI开发", "techName", techName));

    return chatClient.prompt()
            .messages(systemMsg)
            .call()
            .entity(TechInfo.class);
}

这套方案是企业级标准写法:提示词外置、动态参数、格式约束、结构化映射、稳定可运维。

八、生产最佳实践 & 避坑指南

✅ 最佳实践

  • 业务场景一律使用 entity() 结构化:禁止接收原始 String 自由文本

  • 复杂结构化提示词外置 .st 文件,不硬编码、可动态调整

  • 严格定义实体字段:字段名、类型、注释清晰,对齐业务库字段

  • 所有结构化接口加异常兜底,保证服务高可用

  • 列表返回统一使用 ParameterizedTypeReference 处理泛型

❌ 高频踩坑点

  • 不写格式约束:大模型自由发挥,结构化解析概率性失败

  • 实体字段随意命名:与模型返回字段不匹配,解析为null

  • 返回JSON带Markdown标记:未约束禁止多余文本,导致解析报错

  • 无异常兜底:偶发格式异常直接500,线上事故

  • 复杂场景硬编码提示词:迭代维护成本极高

九、完整整合:StructController 全量可运行源码

这里给出本篇所有案例完整 Controller 代码,可直接复制运行测试。

package demo.ai.controller;

import demo.ai.domain.TechInfo;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/struct")
public class StructController {

    @Resource
    private ChatClient chatClient;

    // 纯自由文本输出,生产不可用
    @GetMapping("/chat")
    public String chat(String topic){
        return chatClient.prompt()
                .system("你是专业的技术科普助手")
                .user("简单介绍下" + topic)
                .call()
                .content();
    }
    /**
     * 基础结构化:提示词约束JSON返回
     * 访问地址:http://localhost:8080/struct/json?name=Java
     */
    @GetMapping("/json")
    public String jsonStruct(String name){
        String systemPrompt = """
            你是数据生成助手,请严格按照JSON格式返回结果。
            要求:
            1. 只返回纯净JSON字符串,不要任何解释、不要多余文字、不要markdown
            2. 严格遵守字段定义,不新增、不缺失字段
            3. 字段:name(名称), introduce(简介), advantage(核心优势数组), score(评分0-100)
            
            输出示例:
            {"name":"","introduce":"","advantage":[],"score":0}
            """;

        return chatClient.prompt()
                .system(systemPrompt)
                .user("请介绍技术栈:" + name)
                .call()
                .content();
    }
    /**
     * 实体自动结构化映射(生产推荐)
     * 访问地址:http://localhost:8080/struct/entity?name=SpringAI
     */
    @GetMapping("/entity")
    public TechInfo entityStruct(String name){
        String systemPrompt = """
            你是专业的技术科普助手,严格按照用户要求输出结构化数据。
            禁止输出多余解释,只返回符合结构的规整数据,并且使用中文返回。
            """;

        return chatClient.prompt()
                .system(systemPrompt)
                .user("详细介绍技术栈:" + name)
                // 核心:自动根据实体结构约束模型输出 + 自动反序列化
                .call()
                .entity(TechInfo.class);
    }
    /**
     * 数组/列表 结构化输出
     * 访问地址:http://localhost:8080/struct/list
     */
    @GetMapping("/list")
    public List<TechInfo> listStruct(){
        String systemPrompt = """
            你是技术推荐助手,输出规整结构化数据。
            """;

        return chatClient.prompt()
                .system(systemPrompt)
                .user("推荐3个当下热门的AI开发技术栈,输出完整信息,并且使用中文返回。")
                // 核心:返回实体集合
                .call()
                .entity(new ParameterizedTypeReference<List<TechInfo>>() {});
    }

    /**
     * 带生产容错的结构化接口
     访问地址:http://localhost:8080/struct/safe?name=SpringAI
     */
    @GetMapping("/safe")
    public TechInfo safeStruct(String name){
        try {
            String systemPrompt = "你是专业技术助手,严格结构化输出";
            return chatClient.prompt()
                    .system(systemPrompt)
                    .user("使用中文介绍技术栈:" + name)
                    .call()
                    .entity(TechInfo.class);
        }catch (Exception e){
            // 解析异常兜底,返回空对象,保证接口稳定
            TechInfo empty = new TechInfo();
            empty.setName(name);
            empty.setIntroduce("暂无数据");
            return empty;
        }
    }

    @Value("classpath:prompts/tech-struct.st")
    private org.springframework.core.io.Resource techStructTmpl;

    /**
     * 模板化 + 结构化 生产终极方案
     * http://localhost:8080/struct/tmplStruct?techName=RAG
     */
    @GetMapping("/tmplStruct")
    public TechInfo tmplStruct(String techName){
        // 加载模板并渲染参数
        SystemPromptTemplate template = new SystemPromptTemplate(techStructTmpl);
        Message systemMsg = template.createMessage(Map.of("field", "后端AI开发", "techName", techName));

        return chatClient.prompt()
                .messages(systemMsg)
                .call()
                .entity(TechInfo.class);
    }





}

接口统一测试地址

  • 自由文本输出:http://localhost:8080/struct/chat?topic=Java

  • 基础JSON约束:http://localhost:8080/struct/json?name=SpringBoot

  • 实体结构化映射:http://localhost:8080/struct/entity?name=SpringAI

  • 列表结构化输出:http://localhost:8080/struct/list

  • 容错兜底接口:http://localhost:8080/struct/safe?name=Vue

  • 模板+结构化整合:http://localhost:8080/struct/tmplStruct?techName=MyBatis-Plus

十、本篇学习总结

本篇完成 Spring AI 结构化输出全套生产能力落地,彻底解决大模型输出不可控的核心工程痛点,核心收获:

  • 理解自由文本输出的生产致命缺陷,明确结构化是 AI 业务上线的必备条件

  • 掌握基础提示词约束 JSON 的写法,适合简单场景快速落地

  • 精通 Spring AI 核心能力:entity() 实体自动映射,实现零解析代码结构化

  • 掌握 List 泛型集合结构化返回,适配列表类业务场景

  • 掌握生产级异常兜底、容错机制,保证接口高可用

  • 整合 Prompt模板化 + 结构化输出,形成企业级标准开发范式

结构化输出是 AI 从「玩具Demo」走向「正式业务系统」的最重要分水岭之一。没有结构化能力,所有 AI 业务都无法标准化、无法运维、无法迭代。

十一、结语

优秀的 AI 工程开发,不是调参、不是写提示词,而是把不稳定的大模型能力,变成稳定、标准、可运维、可迭代的业务能力

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐