跳到主要内容
版本:2.0.1

提供商原生结构化输出

QWen 3.8 中英对照 Provider-Native Structured Output

默认情况下,.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" } 并提供模式可启用与您的模式匹配的原生结构化输出。