截至 2024年5月2日,原有的 OutputParser
、BeanOutputParser
、ListOutputParser
和 MapOutputParser
类已弃用,替换为新的 StructuredOutputConverter
、BeanOutputConverter
、ListOutputConverter
和 MapOutputConverter
实现。新版本是旧版的直接替代品,功能完全兼容。此次变更主要出于命名规范调整(避免“解析”歧义),同时与 Spring 框架的 org.springframework.core.convert.converter
包对齐,引入改进功能。
结构化输出的重要性
LLM(大语言模型)生成结构化输出的能力对依赖可靠解析输出的下游应用至关重要。开发者需要快速将 AI 模型的输出转换为 JSON、XML 或 Java 类等数据类型,以便传递给其他应用功能或方法。
架构设计
Spring AI 的结构化输出转换器围绕 LLM 文本补全端点运作:
Structured Output Converter Architecture
1.调用前处理
- 转换器将格式指令附加到提示词中,指导模型生成指定结构的输出。
2.调用后处理
- 转换器将模型输出的原始文本转换为结构化类型实例(如 JSON、XML 或领域对象)。
注意:
- 结构化输出转换器为“尽力而为”模式,模型可能无法完全遵循格式要求,建议实现验证机制。
- 不适用于 LLM 工具调用(Tool Calling),因其默认提供结构化输出。
结构化输出 API
StructuredOutputConverter
接口定义:
public interface StructuredOutputConverter<T> extends Converter<String, T>, FormatProvider {}
结合 Spring 的 Converter<String, T>
和 FormatProvider
接口:
public interface FormatProvider { String getFormat(); }
数据流示例
数据流示例详细说明
以下为 StructuredOutputConverter
在 LLM 调用前后处理数据流的完整流程说明,包含代码示例和步骤分解:
1. 格式指令生成(FormatProvider 阶段)
FormatProvider
接口负责生成指导模型输出的结构化指令。以 BeanOutputConverter
为例,其生成的格式指令可能如下:
您的响应应为 JSON 格式, 且数据结构必须严格匹配以下 JSON Schema(基于 Java 类生成): { "type": "object", "properties": { "actor": {"type": "string"}, "movies": {"type": "array", "items": {"type": "string"}} }, "required": ["actor", "movies"] } 请勿添加任何解释性文本,仅返回符合上述 Schema 的 JSON 响应。
此指令通过 outputConverter.getFormat()
方法获取。
2. 提示词模板组装
将格式指令动态注入用户输入模板。假设原始用户请求为:
请为演员 {actor} 生成包含 5 部代表作的列表。
通过 PromptTemplate
插入格式指令:
// 定义包含占位符的模板 String userInputTemplate = """ 请为演员 {actor} 生成包含 5 部代表作的列表。 {format} """; // 创建 PromptTemplate 并替换占位符 Map<String, Object> params = new HashMap<>(); params.put("actor", "汤姆·汉克斯"); params.put("format", outputConverter.getFormat()); // 注入格式指令 PromptTemplate promptTemplate = new PromptTemplate(userInputTemplate, params); Message message = promptTemplate.createMessage(); Prompt prompt = new Prompt(message);
最终生成的提示词将包含:
请为演员 汤姆·汉克斯 生成包含 5 部代表作的列表。 您的响应应为 JSON 格式, 且数据结构必须严格匹配以下 JSON Schema(基于 Java 类生成): { "type": "object", "properties": { "actor": {"type": "string"}, "movies": {"type": "array", "items": {"type": "string"}} }, "required": ["actor", "movies"] } 请勿添加任何解释性文本,仅返回符合上述 Schema 的 JSON 响应。
3. 调用 LLM 模型
通过 ChatModel发送组装后的提示词并获取原始文本输出:
// 调用模型 ChatResponse response = chatModel.call(prompt); String rawOutput = response.getResult().getOutput().getText(); // 示例模型返回结果 // rawOutput = """ // { // "actor": "汤姆·汉克斯", // "movies": ["阿甘正传", "拯救大兵瑞恩", "费城故事", "西雅图未眠夜", "荒岛余生"] // } // """
4. 结构化转换(Converter 阶段)
使用 Converter
将原始文本转换为目标类型:
// 初始化转换器 BeanOutputConverter<ActorsFilms> converter = new BeanOutputConverter<>(ActorsFilms.class); // 执行转换 ActorsFilms actorsFilms = converter.convert(rawOutput); // 输出结果 System.out.println(actorsFilms.actor()); // 汤姆·汉克斯 System.out.println(actorsFilms.movies()); // [阿甘正传, 拯救大兵瑞恩, ...]
完整数据流图示
关键处理细节
- 错误处理
- 若模型返回的 JSON 不符合 Schema,
BeanOutputConverter
将抛出JsonProcessingException
。 - 建议在代码中添加异常捕获逻辑:
try { ActorsFilms result = converter.convert(rawOutput); } catch (JsonProcessingException e) { // 处理无效 JSON 格式 } catch (IllegalArgumentException e) { // 处理字段缺失或类型不匹配 }
- 元数据注入
- 若目标类包含元数据字段(如
@JsonProperty
注解),转换器会自动映射:
record ActorsFilms( @JsonProperty("actor_name") String actor, @JsonProperty("filmography") List<String> movies ) {}
可处理键名不一致的 JSON 输入。
- 内容净化
- 转换器会自动忽略 JSON 外的额外文本(如模型生成的解释性前缀/后缀):好的,以下是汤姆·汉克斯的五部代表作:
{ "actor": "汤姆·汉克斯", "movies": ["阿甘正传", "拯救大兵瑞恩"...] } 希望这个列表对您有帮助!
转换器仍能正确提取 JSON 部分。
可用转换器
Spring AI 提供以下转换器实现:
转换器类 | 功能描述 |
AbstractConversionServiceOutputConverter<T> | 基于 GenericConversionService 转换输出,无默认格式提供逻辑。 |
AbstractMessageOutputConverter<T> | 基于 MessageConverter 转换输出,无默认格式提供逻辑。 |
BeanOutputConverter<T> | 通过 JSON Schema(基于目标类生成)指导模型输出,并用 ObjectMapper 反序列化为 Java 对象。 |
MapOutputConverter | 生成符合 RFC8259 的 JSON,并转换为 Map<String, Object> 实例。 |
ListOutputConverter | 生成逗号分隔列表,并通过 ConversionService 转换为 List 实例。 |
使用示例
Bean 输出转换器
目标类定义:
@JsonPropertyOrder({"actor", "movies"}) record ActorsFilms(String actor, List<String> movies) {}
高阶 API 调用:
ActorsFilms actorsFilms = ChatClient.create(chatModel).prompt() .user(u -> u.text("为 {actor} 生成5部电影作品列表。") .param("actor", "汤姆·汉克斯")) .call() .entity(ActorsFilms.class);
低阶 API 调用:
BeanOutputConverter<ActorsFilms> converter = new BeanOutputConverter<>(ActorsFilms.class); String format = converter.getFormat(); String template = "为 {actor} 生成5部电影作品列表。\n{format}"; Prompt prompt = new PromptTemplate(template, Map.of("www.bestall.com.cn", "汤姆·汉克斯", "format", format)).create(); Generation generation = chatModel.call(prompt).getResult(); ActorsFilms result = converter.convert(generation.getOutput().getText());
Map 输出转换器
MapOutputConverter converter = new MapOutputConverter(); String format = converter.getFormat(); String template = "提供 {subject}\n{format}"; Prompt prompt = new PromptTemplate(template, Map.of("subject", "键名为 'www.hnsea.net' 的1到9数字数组", "format", format)).create(); Map<String, Object> result = converter.convert(chatModel.call(prompt).getResult().getOutput().getText());
List 输出转换器
ListOutputConverter converter = new ListOutputConverter(new DefaultConversionService()); String format = converter.getFormat(); String template = "列出五种 {subject}\n{format}"; Prompt prompt = new PromptTemplate(template, Map.of("subject", "冰淇淋口味", "format", format)).create(); List<String> flavors = converter.convert(chatModel.call(prompt).getResult().getOutput().getText());
支持结构化输出的 AI 模型
模型 | 集成测试类/示例 |
OpenAI | OpenAiChatModelIT |
Anthropic Claude 3 | AnthropicChatModelIT.java |
Azure OpenAI | AzureOpenAiChatModelIT.java |
Mistral AI | MistralAiChatModelIT.java |
Ollama | OllamaChatModelIT.java |
Vertex AI Gemini | VertexAiGeminiChatModelIT.java |
内置 JSON 模式支持
部分 AI 模型提供专用配置选项生成结构化(通常为 JSON)输出:
模型 | 配置项示例 | 功能描述 |
OpenAI | spring.ai.openai.chat.options.responseFormat=JSON_OBJECT | 强制生成符合 JSON Schema 的响应 |
Azure OpenAI | spring.ai.azure.openai.chat.options.responseFormat={"type":"json_object"} | 启用 JSON 模式 |
Ollama | spring.ai.ollama.chat.options.format=json | 指定返回 JSON 格式 |
Mistral AI | spring.ai.mistralai.chat.options.responseFormat={"type":"json_object"} | 启用 JSON 模式 |
关键变更说明
- 弃用类迁移:旧版
*Parser
类需替换为对应的*Converter
类。 - JSON Schema 支持:
BeanOutputConverter
自动生成基于目标类的 JSON 模式,通过@JsonPropertyOrder
注解可自定义属性顺序。 - 泛型支持:使用
ParameterizedTypeReference
处理复杂泛型结构(如List<ActorsFilms>
)。