提供商原生结构化输出
默认情况下,.entity(…) 会将 JSON Schema 以文本指令的形式附加到提示词中。这是一种响应端方法:模型被要求遵守该 Schema,随后再对响应进行解析。
另一种互补的方法是一种请求端约束:在 API 层面告知模型的提供方,响应必须符合某个 schema。大多数现代提供方都支持这一功能(例如 OpenAI 的 Structured Outputs、Anthropic 的 structured output extension、Gemini 的 responseSchema 以及 Mistral 的 response_format)。
Spring AI 通过 EntityParamSpec 消费者上的开关,以可移植的方式暴露其功能:
ActorsFilms films = chatClient.prompt()
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorsFilms.class, spec -> spec.useProviderStructuredOutput());
在底层线路上发生的变化:
-
系统提示词不再包含 JSON 格式指令(更简洁,token 消耗更少)。
-
Schema 作为 API 级别的字段发送给 provider。
-
Provider 的运行时强制执行合规性 —— 根本无法发出无效的响应。
它提供以下功能:
-
更高的可靠性:模型可确保输出符合 schema。
-
更简洁的提示词:无需附加格式说明。
-
更优的性能:模型可在内部针对结构化输出进行优化。
Spring AI 如何检测支持情况
Spring AI 通过检查模型的聊天选项是否实现了 StructuredOutputChatOptions 接口来检测对原生支持。如果没有实现,该标志将被静默忽略,调用将回退到基于提示词的默认行为。
支持的模型
Spring AI 2.0 中,以下提供商支持原生结构化输出。无论接入的是哪个提供商,相同的 .useProviderStructuredOutput() 调用方式均适用:
- OpenAI:支持 JSON Schema 的 GPT-4o 及后续模型。
- Anthropic:Claude 3.5 Sonnet 及后续模型。
- Google GenAI:Gemini 1.5 Pro 及后续模型。
- Mistral AI:支持 JSON Schema 的 Mistral Small 及后续模型。
- Ollama:支持 JSON Schema 的模型(因模型而异;参见 已知限制)。
为什么默认关闭
兼容性。较旧或不支持的模型会拒绝该请求,而基于提示词的默认设置在所有地方均可用。
原生结构化输出默认未启用,因为不同模型和提供商的支持情况差异很大。仅在需要其提供的更强 API 级模式(schema)强制时启用,并且务必使用特定的模型版本进行测试。
已知限制
即使在那些宣称支持该功能的服务提供商处,原生的结构化输出支持通常也是部分的——所接受的 JSON Schema 范围各不相同。$ref、深层嵌套数组、allOf/anyOf/oneOf、正则表达式模式以及递归类型是常见的限制。由此可能导致的结构漂移,正是 validateSchema() 擅长捕获的问题。
Ollama: 特定模型的不稳定性
并非所有 Ollama 模型都能可靠地遵循结构化输出模式约束。特别是那些内置推理或“思考”模式的模型(例如 qwen3:8b、qwen3.5:9b 以及其他较新的 Qwen 变体)可能会以纯文本形式返回其内部推理轨迹,而非结构化的 JSON,从而在 BeanOutputConverter 中引发反序列化错误,例如:
StreamReadException: Unrecognized token 'The': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
如果您在使用 Ollama 时遇到此问题,请尝试其他模型(例如 llama3.1:latest),或者回退到默认的基于提示(prompt-based)的方法。您还可以将 useProviderStructuredOutput() 与 validateSchema() 结合使用,以便自动重试格式错误的响应:
ActorsFilms films = chatClient.prompt()
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorsFilms.class, spec -> spec
.useProviderStructuredOutput()
.validateSchema());
OpenAI:不支持顶级数组
OpenAI 结构化输出 API 不接受将顶级 JSON 数组作为响应架构(参见 OpenAI 社区讨论)。在启用原生结构化输出的情况下,请求 List<T> 会导致 API 错误。
// Does NOT work with OpenAI native structured output:
List<ActorsFilms> films = chatClient.prompt()
.user("Generate filmographies for Tom Hanks and Bill Murray.")
.call()
.entity(new ParameterizedTypeReference<List<ActorsFilms>>() {},
spec -> spec.useProviderStructuredOutput()); // fails with OpenAI
请改用以下任一替代方法:
// Option 1: wrap the list in a container record
record FilmographyList(List<ActorsFilms> films) {}
FilmographyList result = chatClient.prompt()
.user("Generate filmographies for Tom Hanks and Bill Murray.")
.call()
.entity(FilmographyList.class, spec -> spec.useProviderStructuredOutput());
List<ActorsFilms> films = result.films();
// Option 2: use the default prompt-based approach (no native output required)
List<ActorsFilms> films = chatClient.prompt()
.user("Generate filmographies for Tom Hanks and Bill Murray.")
.call()
.entity(new ParameterizedTypeReference<List<ActorsFilms>>() {});
默认的基于提示(prompt)的流程没有此类限制——顶层数组无需使用 useProviderStructuredOutput() 即可正常工作。
全局启用
useProviderStructuredOutput() 是按调用生效的开关。若要在 ChatClient 的每次调用中都启用原生结构化输出,请设置 AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT 顾问参数——可作为构建器的默认值,也可按请求进行设置:
// Per request
ActorsFilms films = chatClient.prompt()
.advisors(AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT)
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorsFilms.class);
// Globally on the builder
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultAdvisors(AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT)
.build();
}
Provider 内置 JSON 模式
独立于 useProviderStructuredOutput(),某些 AI 模型提供了专门的配置选项,以直接生成结构化(通常为 JSON)输出:
-
OpenAI Structured Outputs 可以确保模型生成的响应严格遵循您提供的 JSON Schema。在
JSON_OBJECT(有效 JSON)或JSON_SCHEMA(提供模式)之间进行选择(spring.ai.openai.chat.response-format选项)。 -
Ollama 提供
spring.ai.ollama.chat.format选项以指定响应格式。目前,唯一接受的值是json。 -
Mistral AI 提供
spring.ai.mistralai.chat.response-format选项。将其设置为{ "type": "json_object" }可启用 JSON 模式;将其设置为{ "type": "json_schema" }并提供模式可启用与您的模式匹配的原生结构化输出。