Spring AI结构化输出

1. 核心概念与设计思想

1.1 什么是结构化输出

Spring AI 结构化输出是一种类型安全的 AI 交互范式。它通过向 LLM 注入格式指令,强制模型输出符合特定 Schema 的内容,再由框架自动将字符串转换为 Java 对象,实现了从 "字符串拼接与解析" 到 "面向对象编程" 的跨越。

1.2 核心接口契约

Spring AI 定义了两个职责单一的核心接口,共同构成结构化输出的基础:

java 复制代码
// 格式提供者:生成AI能够理解并严格遵守的输出格式说明
public interface FormatProvider {
    String getFormat();
}

// 结构化输出转换器:组合"格式生成"与"字符串转对象"能力
public interface StructuredOutputConverter<T> 
    extends Converter<String, T>, FormatProvider {
}

接口职责分离原则

  • Converter<String, T>:定义从字符串到目标类型 T 的反序列化逻辑
  • FormatProvider:定义如何生成 AI 可执行的格式指令(通常是 JSON Schema)
  • StructuredOutputConverter:组合两者,形成完整的 "指令 - 生成 - 解析" 闭环

1.3 底层工作原理

Spring AI 内部会自动执行以下 4 个步骤,全程对开发者透明:

  1. Schema 生成:根据目标 Java 类自动生成 JSON Schema 格式说明
  2. 提示词注入 :将格式说明注入到提示词的{format}占位符
  3. 模型调用:发送包含格式要求的完整提示词给 LLM
  4. 自动反序列化:将 LLM 返回的字符串转换为强类型 Java 对象

2. 快速上手:恋爱报告实战

2.1 第一步:定义结构化输出类

使用 Java 16 + 的Record特性(推荐)定义不可变的数据载体,这是结构化输出的最佳实践:

java 复制代码
/**
 * 恋爱报告结构化输出类
 * 字段名会直接影响AI生成内容的准确性,请使用清晰易懂的命名
 */
public record LoveReport(
    String title,       // 报告标题(要求:{用户名}的恋爱报告)
    List<String> s      // 恋爱建议列表(每条建议为一个字符串元素)
) {}

2.2 第二步:实现结构化输出调用

使用 Spring AI 的ChatClient流式 API,一行代码完成从对话到对象的转换:

java 复制代码
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.ChatMemoryAdvisor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class LoveConsultationService {

    private final ChatClient chatClient;
    
    // 基础系统提示词
    private static final String SYSTEM_PROMPT = 
        "你是一位专业、温和的恋爱咨询师,擅长提供客观、可执行的恋爱建议。";

    // 构造注入ChatClient.Builder(Spring Boot自动配置)
    public LoveConsultationService(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    /**
     * 与AI对话并生成结构化的恋爱报告
     * @param userMessage 用户输入的恋爱问题
     * @param chatId 会话唯一标识,用于实现对话记忆
     * @return 强类型的LoveReport对象
     */
    public LoveReport doChatWithReport(String userMessage, String chatId) {
        LoveReport loveReport = chatClient
                .prompt()
                // 系统提示词:明确业务要求+格式要求
                .system(SYSTEM_PROMPT + 
                    "每次对话后必须生成恋爱结果,标题严格为'用户名的恋爱报告',内容为建议列表。")
                .user(userMessage)
                // 配置对话记忆:指定会话ID和历史消息检索条数
                .advisors(spec -> spec
                    .param(ChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
                    .param(ChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
                .call()
                // 核心魔法:自动将AI输出转换为LoveReport对象
                .entity(LoveReport.class);
        
        log.info("成功生成恋爱报告: {}", loveReport);
        return loveReport;
    }
}

3. 核心 API 详解

3.1 ChatClient.entity () 方法

entity(Class<T> entityType)是 Spring AI 结构化输出的核心入口,它内部封装了所有复杂逻辑:

  • 自动创建对应类型的StructuredOutputConverter实例
  • 生成该类型的完整 JSON Schema
  • 将 Schema 注入到提示词的适当位置
  • 调用 LLM 并捕获输出
  • 执行反序列化并返回强类型对象

支持的类型范围

  • 基本类型:StringIntegerLongBooleanDouble
  • 集合类型:List<T>Set<T>Map<K, V>
  • 自定义 POJO/Record(支持嵌套)
  • 枚举类型
  • 泛型类型(需使用ParameterizedTypeReference

3.2 内置转换器

Spring AI 提供了多种开箱即用的转换器:

转换器 用途 特点
JacksonStructuredOutputConverter JSON 格式转换 默认使用,基于 Jackson,支持所有 Java 类型
BeanOutputConverter Bean 属性映射 基于 BeanUtils,兼容性好
EnumOutputConverter 枚举类型转换 自动处理枚举值的大小写和别名
ListOutputConverter 列表类型转换 专门优化列表输出的格式指令

4. 高级用法:自定义转换器

当内置转换器无法满足需求时,可以实现自定义的StructuredOutputConverter

java 复制代码
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.ai.converter.StructuredOutputConverter;
import org.springframework.core.convert.ConversionException;

public class CustomLoveReportConverter implements StructuredOutputConverter<LoveReport> {
    
    private final ObjectMapper objectMapper;
    
    public CustomLoveReportConverter(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }
    
    @Override
    public LoveReport convert(String source) {
        try {
            // 自定义解析逻辑:可以处理AI输出中的额外文本
            String json = extractJsonFromText(source);
            return objectMapper.readValue(json, LoveReport.class);
        } catch (Exception e) {
            throw new ConversionException("无法转换为LoveReport对象", e);
        }
    }
    
    @Override
    public String getFormat() {
        // 自定义格式说明:比自动生成的更简洁、更符合业务需求
        return """
            请严格按照以下JSON格式输出,不要添加任何其他内容:
            {
              "title": "报告标题,格式为'用户名的恋爱报告'",
              "s": ["建议1", "建议2", "建议3"]
            }
            """;
    }
    
    // 从AI输出的文本中提取JSON部分
    private String extractJsonFromText(String text) {
        int start = text.indexOf('{');
        int end = text.lastIndexOf('}');
        if (start == -1 || end == -1 || start >= end) {
            throw new IllegalArgumentException("未找到有效的JSON内容");
        }
        return text.substring(start, end + 1);
    }
}

使用自定义转换器:

java 复制代码
public LoveReport doChatWithCustomConverter(String userMessage, String chatId) {
    CustomLoveReportConverter converter = new CustomLoveReportConverter(objectMapper);
    return chatClient
            .prompt()
            .system(SYSTEM_PROMPT)
            .user(userMessage)
            .call()
            .entity(converter); // 使用自定义转换器
}

相关推荐
1892280486111 小时前
NQ486固态MT29F16T08GSLDHL8-QM:D
大数据·人工智能·科技·microsoft·缓存
IT技术分享社区12 小时前
微软Office 2024离线版安装指南与功能亮点介绍
microsoft·微软技术·office·电脑干货
风别鹤12 小时前
windows android studio 工程gradlew.bat不是64位程序
android·ide·windows·android studio
muddjsv12 小时前
Python核心语法分类详解:从入门到精通
开发语言·windows·python
岳麓丹枫00112 小时前
Windows 版 smem_通过服务名获取对应进程树的内存统计
windows·postgresql
岳麓丹枫00113 小时前
Windows版本smem_通过进程名统计对应内存占用
windows·postgresql
likerhood13 小时前
Java ArrayList 详解:从动态数组到扩容机制与常见陷阱
java·开发语言·windows
暴躁小师兄数据学院13 小时前
【AI大模型应用开发工程师特训笔记】第04讲(第6章):复合数据类型
人工智能·windows·笔记·python
半壶清水13 小时前
如何将手机APP安装到windows上,让你在电脑大屏上用手机
windows·智能手机·app·电脑