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 个步骤,全程对开发者透明:
- Schema 生成:根据目标 Java 类自动生成 JSON Schema 格式说明
- 提示词注入 :将格式说明注入到提示词的
{format}占位符 - 模型调用:发送包含格式要求的完整提示词给 LLM
- 自动反序列化:将 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 并捕获输出
- 执行反序列化并返回强类型对象
支持的类型范围:
- 基本类型:
String、Integer、Long、Boolean、Double等 - 集合类型:
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); // 使用自定义转换器
}