结构化输出(Structured Outputs)
术语 “Structured Outputs” 含义较广,可能指两件事:
- LLM 以结构化格式生成输出的一般能力(本页介绍的内容)
- OpenAI 的 Structured Outputs 功能, 它同时适用于响应格式与工具(函数调用)。
许多 LLM 及 LLM 提供商支持以结构化格式(通常是 JSON)生成输出。 这些输出可以方便地映射为 Java 对象,并在应用程序的其他部分使用。
例如,假设我们有一个 Person 类:
record Person(String name, int age, double height, boolean married) {
}
我们希望从描述虚构角色的非结构化文本中提取一个 Person 对象:
Eldwin Brightblade is 412 years old and serves as court wizard in the kingdom of Aelyria.
He stands 1.65 meters tall and is known for his flowing white beard.
Currently unmarried, he devotes his time to studying ancient runes.
目前,根据 LLM 与 LLM 提供商的不同,可以通过以下三种方式实现 (按可靠性从高到低排序):
JSON Schema
部分 LLM 提供商(目前包括 Amazon Bedrock、Azure OpenAI、Google AI Gemini、Mistral、Ollama 和 OpenAI)允许 为期望的输出指定 JSON schema。 你可以在此处的 “JSON Schema” 列中查看所有受支持的 LLM 提供商。
当请求中指定了 JSON schema 时,LLM 应生成符合该 schema 的输出。
请注意:JSON schema 是在发往 LLM 提供商 API 的请求中通过专用属性指定的, 并不需要在提示词中(例如系统消息或用户消息里)加入任何自由形式的指令。
LangChain4j 在底层 ChatModel API
与高层 AI Service API 中均支持 JSON Schema 功能。
在 ChatModel 中使用 JSON Schema
在底层 ChatModel API 中,可以在创建 ChatRequest 时
使用与 LLM 提供商无关的 ResponseFormat 和 JsonSchema 来指定 JSON schema:
ResponseFormat responseFormat = ResponseFormat.builder()
.type(JSON) // type can be either TEXT (default) or JSON
.jsonSchema(JsonSchema.builder()
.name("Person") // OpenAI requires specifying the name for the schema
.rootElement(JsonObjectSchema.builder() // see [1] below
.addStringProperty("name")
.addIntegerProperty("age")
.addNumberProperty("height")
.addBooleanProperty("married")
.required("name", "age", "height", "married") // see [2] below
.build())
.build())
.build();
UserMessage userMessage = UserMessage.from("""
Eldwin Brightblade is 412 years old and serves as court wizard in the kingdom of Aelyria.
He stands 1.65 meters tall and is known for his flowing white beard.
Currently unmarried, he devotes his time to studying ancient runes.
""");
ChatRequest chatRequest = ChatRequest.builder()
.responseFormat(responseFormat)
.messages(userMessage)
.build();
ChatModel chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.logRequests(true)
.logResponses(true)
.build();
// OR
ChatModel chatModel = AzureOpenAiChatModel.builder()
.endpoint(System.getenv("AZURE_OPENAI_URL"))
.apiKey(System.getenv("AZURE_OPENAI_API_KEY"))
.deploymentName("gpt-4o-mini")
.logRequestsAndResponses(true)
.build();
// OR
ChatModel chatModel = GoogleAiGeminiChatModel.builder()
.apiKey(System.getenv("GOOGLE_AI_GEMINI_API_KEY"))
.modelName("gemini-1.5-flash")
.logRequestsAndResponses(true)
.build();
// OR
ChatModel chatModel = OllamaChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("llama3.1")
.logRequests(true)
.logResponses(true)
.build();
// OR
ChatModel chatModel = MistralAiChatModel.builder()
.apiKey(System.getenv("MISTRAL_AI_API_KEY"))
.modelName("mistral-small-latest")
.logRequests(true)
.logResponses(true)
.build();
// OR
ChatModel chatModel = WatsonxChatModel.builder()
.baseUrl(System.getenv("WATSONX_URL"))
.projectId(System.getenv("WATSONX_PROJECT_ID"))
.apiKey(System.getenv("WATSONX_API_KEY"))
.modelName("ibm/granite-4-h-small")
.logRequests(true)
.logResponses(true)
.build();
// OR
ChatModel chatModel = BedrockChatModel.builder()
.modelId("us.anthropic.claude-haiku-4-5-20251001-v1:0")
.logRequests(true)
.logResponses(true)
.build();
ChatResponse chatResponse = chatModel.chat(chatRequest);
String output = chatResponse.aiMessage().text();
System.out.println(output); // {"name":"Eldwin Brightblade","age":412,"height":1.65,"married":false}
Person person = new ObjectMapper().readValue(output, Person.class);
System.out.println(person); // Person[name=Eldwin Brightblade, age=412, height=1.65, married=false]
说明:
- [1] - 在大多数情况下,根元素必须是
JsonObjectSchema类型, 但:- Amazon Bedrock、Azure OpenAI、Mistral、Ollama、OpenAI 以及 OpenAI Official 也允许将
JsonRawSchema作为根元素 - Gemini 也允许将
JsonEnumSchema和JsonArraySchema作为根元素
- Amazon Bedrock、Azure OpenAI、Mistral、Ollama、OpenAI 以及 OpenAI Official 也允许将
- [2] - 必填属性必须显式指定;否则将被视为可选。
JSON schema 的结构通过 JsonSchemaElement 接口定义,
具有以下子类型:
JsonObjectSchema- 用于对象类型。JsonStringSchema- 用于String、char/Character类型。JsonIntegerSchema- 用于int/Integer、long/Long、BigInteger类型。JsonNumberSchema- 用于float/Float、double/Double、BigDecimal类型。JsonBooleanSchema- 用于boolean/Boolean类型。JsonEnumSchema- 用于enum类型。JsonArraySchema- 用于数组与集合(例如List、Set)。JsonReferenceSchema- 用于支持递归(例如Person有一个Set<Person> children字段)。JsonAnyOfSchema- 用于支持多态(例如Shape可以是Circle或Rectangle)。JsonNullSchema- 用于支持可空类型。JsonRawSchema- 用于使用你自定义的、完整定义的 JSON schema。
JsonObjectSchema
JsonObjectSchema 表示带有嵌套属性的对象。
它通常是 JsonSchema 的根元素。
向 JsonObjectSchema 添加属性有几种方式:
- 可以使用
properties(Map<String, JsonSchemaElement> properties)方法一次性添加所有属性:
JsonSchemaElement citySchema = JsonStringSchema.builder()
.description("The city for which the weather forecast should be returned")
.build();
JsonSchemaElement temperatureUnitSchema = JsonEnumSchema.builder()
.enumValues("CELSIUS", "FAHRENHEIT")
.build();
Map<String, JsonSchemaElement> properties = Map.of(
"city", citySchema,
"temperatureUnit", temperatureUnitSchema
);
JsonSchemaElement rootElement = JsonObjectSchema.builder()
.addProperties(properties)
.required("city") // required properties should be specified explicitly
.build();
- 可以使用
addProperty(String name, JsonSchemaElement jsonSchemaElement)方法逐个添加属性:
JsonSchemaElement rootElement = JsonObjectSchema.builder()
.addProperty("city", citySchema)
.addProperty("temperatureUnit", temperatureUnitSchema)
.required("city")
.build();
- 可以使用
add{Type}Property(String name)或add{Type}Property(String name, String description)方法之一逐个添加属性:
JsonSchemaElement rootElement = JsonObjectSchema.builder()
.addStringProperty("city", "The city for which the weather forecast should be returned")
.addEnumProperty("temperatureUnit", List.of("CELSIUS", "FAHRENHEIT"))
.required("city")
.build();
请参阅 JsonObjectSchema 的 Javadoc 以了解更多详情。
JsonStringSchema
创建 JsonStringSchema 的示例:
JsonSchemaElement stringSchema = JsonStringSchema.builder()
.description("The name of the person")
.build();
JsonIntegerSchema
创建 JsonIntegerSchema 的示例:
JsonSchemaElement integerSchema = JsonIntegerSchema.builder()
.description("The age of the person")
.build();
JsonNumberSchema
创建 JsonNumberSchema 的示例:
JsonSchemaElement numberSchema = JsonNumberSchema.builder()
.description("The height of the person")
.build();
JsonBooleanSchema
创建 JsonBooleanSchema 的示例:
JsonSchemaElement booleanSchema = JsonBooleanSchema.builder()
.description("Is the person married?")
.build();
JsonEnumSchema
创建 JsonEnumSchema 的示例:
JsonSchemaElement enumSchema = JsonEnumSchema.builder()
.description("Marital status of the person")
.enumValues(List.of("SINGLE", "MARRIED", "DIVORCED"))
.build();
JsonArraySchema
创建 JsonArraySchema 以定义字符串数组的示例:
JsonSchemaElement itemSchema = JsonStringSchema.builder()
.description("The name of the person")
.build();
JsonSchemaElement arraySchema = JsonArraySchema.builder()
.description("All names of the people found in the text")
.items(itemSchema)
.build();
JsonReferenceSchema
JsonReferenceSchema 可用于支持递归:
String reference = "person"; // reference should be unique withing the schema
JsonObjectSchema jsonObjectSchema = JsonObjectSchema.builder()
.addStringProperty("name")
.addProperty("children", JsonArraySchema.builder()
.items(JsonReferenceSchema.builder()
.reference(reference)
.build())
.build())
.required("name", "children")
.definitions(Map.of(reference, JsonObjectSchema.builder()
.addStringProperty("name")
.addProperty("children", JsonArraySchema.builder()
.items(JsonReferenceSchema.builder()
.reference(reference)
.build())
.build())
.required("name", "children")
.build()))
.build();
JsonReferenceSchema 目前仅受 Azure OpenAI、Mistral 和 OpenAI 支持。
JsonAnyOfSchema
JsonAnyOfSchema 可用于支持多态:
JsonSchemaElement circleSchema = JsonObjectSchema.builder()
.addNumberProperty("radius")
.build();
JsonSchemaElement rectangleSchema = JsonObjectSchema.builder()
.addNumberProperty("width")
.addNumberProperty("height")
.build();
JsonSchemaElement shapeSchema = JsonAnyOfSchema.builder()
.anyOf(circleSchema, rectangleSchema)
.build();
JsonSchema jsonSchema = JsonSchema.builder()
.name("Shapes")
.rootElement(JsonObjectSchema.builder()
.addProperty("shapes", JsonArraySchema.builder()
.items(shapeSchema)
.build())
.required(List.of("shapes"))
.build())
.build();
ResponseFormat responseFormat = ResponseFormat.builder()
.type(ResponseFormatType.JSON)
.jsonSchema(jsonSchema)
.build();
UserMessage userMessage = UserMessage.from("""
Extract information from the following text:
1. A circle with a radius of 5
2. A rectangle with a width of 10 and a height of 20
""");
ChatRequest chatRequest = ChatRequest.builder()
.messages(userMessage)
.responseFormat(responseFormat)
.build();
ChatResponse chatResponse = model.chat(chatRequest);
System.out.println(chatResponse.aiMessage().text()); // {"shapes":[{"radius":5},{"width":10,"height":20}]}
JsonAnyOfSchema 目前仅受 OpenAI、Azure OpenAI 和 Google AI Gemini 支持。
JsonRawSchema
从已有 schema 字符串创建 JsonRawSchema 的示例:
var rawSchema = """
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"city": {
"type": "string"
}
},
"required": ["city"],
"additionalProperties": false
}
""";
JsonRawSchema schema = JsonRawSchema.from(rawSchema);
JsonRawSchema 目前仅受 Amazon Bedrock、Azure OpenAI、Mistral、Ollama、OpenAI、OpenAI Official 和 Google AI Gemini 支持。
对于 Google AI Gemini,请特别参阅 Response JSON Schema 中的示例。
添加描述
除 JsonReferenceSchema 外,所有 JsonSchemaElement 子类型都有 description 属性。
如果 LLM 未给出期望的输出,可以提供描述,
向 LLM 给出更多指令以及正确输出的示例,例如:
JsonSchemaElement stringSchema = JsonStringSchema.builder()
.description("The name of the person, for example: John Doe")
.build();
限制
在 ChatModel 中使用 JSON Schema 时,存在一些限制:
- 仅适用于受支持的 Amazon Bedrock、Azure OpenAI、Google AI Gemini、Mistral、Ollama 和 OpenAI 模型。
- 对 OpenAI 而言,目前尚不支持流式模式。
对于 Google AI Gemini、Mistral 和 Ollama,可在创建/构建模型时通过
responseSchema(...)指定 JSON Schema。 JsonReferenceSchema和JsonAnyOfSchema目前仅受 Azure OpenAI、Mistral 和 OpenAI 支持。
在 AI Services 中使用 JSON Schema
在使用 AI Services 时,可以更轻松、用更少的代码达到相同效果:
interface PersonExtractor {
Person extractPersonFrom(String text);
}
ChatModel chatModel = OpenAiChatModel.builder() // see [1] below
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.supportedCapabilities(RESPONSE_FORMAT_JSON_SCHEMA) // see [2] below
.strictJsonSchema(true) // see [2] below
.logRequests(true)
.logResponses(true)
.build();
// OR
ChatModel chatModel = AzureOpenAiChatModel.builder() // see [1] below
.endpoint(System.getenv("AZURE_OPENAI_URL"))
.apiKey(System.getenv("AZURE_OPENAI_API_KEY"))
.deploymentName("gpt-4o-mini")
.strictJsonSchema(true)
.supportedCapabilities(RESPONSE_FORMAT_JSON_SCHEMA) // see [3] below
.logRequestsAndResponses(true)
.build();
// OR
ChatModel chatModel = GoogleAiGeminiChatModel.builder() // see [1] below
.apiKey(System.getenv("GOOGLE_AI_GEMINI_API_KEY"))
.modelName("gemini-1.5-flash")
.supportedCapabilities(RESPONSE_FORMAT_JSON_SCHEMA) // see [4] below
.logRequestsAndResponses(true)
.build();
// OR
ChatModel chatModel = OllamaChatModel.builder() // see [1] below
.baseUrl("http://localhost:11434")
.modelName("llama3.1")
.supportedCapabilities(RESPONSE_FORMAT_JSON_SCHEMA) // see [5] below
.logRequests(true)
.logResponses(true)
.build();
// OR
ChatModel chatModel = MistralAiChatModel.builder()
.apiKey(System.getenv("MISTRAL_AI_API_KEY"))
.modelName("mistral-small-latest")
.supportedCapabilities(RESPONSE_FORMAT_JSON_SCHEMA) // see [6] below
.strictJsonSchema(true) // see [6] below
.logRequests(true)
.logResponses(true)
.build();
// OR
ChatModel chatModel = WatsonxChatModel.builder()
.baseUrl(System.getenv("WATSONX_URL"))
.projectId(System.getenv("WATSONX_PROJECT_ID"))
.apiKey(System.getenv("WATSONX_API_KEY"))
.modelName("ibm/granite-4-h-small")
.supportedCapabilities(RESPONSE_FORMAT_JSON_SCHEMA) // see [7] below
.logRequests(true)
.logResponses(true)
.build();
// OR
ChatModel chatModel = BedrockChatModel.builder()
.modelId("us.anthropic.claude-haiku-4-5-20251001-v1:0")
.supportedCapabilities(RESPONSE_FORMAT_JSON_SCHEMA) // see [8] below
.logRequests(true)
.logResponses(true)
.build();
PersonExtractor personExtractor = AiServices.create(PersonExtractor.class, chatModel); // see [1] below
String text = """
Eldwin Brightblade is 412 years old and serves as court wizard in the kingdom of Aelyria.
He stands 1.65 meters tall and is known for his flowing white beard.
Currently unmarried, he devotes his time to studying ancient runes.
""";
Person person = personExtractor.extractPersonFrom(text);
System.out.println(person); // Person[name=Eldwin Brightblade, age=412, height=1.65, married=false]
说明:
- [1] - 在 Quarkus 或 Spring Boot 应用中,无需显式创建
ChatModel和 AI Service, 因为这些 bean 会自动创建。更多信息见: Quarkus、 Spring Boot。 - [2] - 这是为 OpenAI 启用 JSON Schema 功能所必需的,更多详情见此处。
- [3] - 这是为 Azure OpenAI 启用 JSON Schema 功能所必需的。
- [4] - 这是为 Google AI Gemini 启用 JSON Schema 功能所必需的。
- [5] - 这是为 Ollama 启用 JSON Schema 功能所必需的。
- [6] - 这是为 Mistral 启用 JSON Schema 功能所必需的。
- [7] - 这是为 watsonx.ai 启用 JSON Schema 功能所必需的。
- [8] - 这是为 Amazon Bedrock 启用 JSON Schema 功能所必需的。
当同时满足以下所有条件时:
- AI Service 方法返回一个 POJO
- 所使用的
ChatModel支持 JSON Schema 功能 - 所使用的
ChatModel上已启用 JSON Schema 功能
则会根据指定的返回类型自动生成带有 JsonSchema 的 ResponseFormat。
请确保在配置 ChatModel 时显式启用 JSON Schema 功能,
因为它默认是禁用的。
生成的 JsonSchema 的 name 是返回类型的简单类名(getClass().getSimpleName()),
在本例中为:"Person"。
一旦 LLM 返回响应,输出会被解析为对象,并从 AI Service 方法返回。
必填与可选
默认情况下,生成的 JsonSchema 中的所有字段与子字段都被视为可选。
这是因为当 LLM 缺少足够信息时,往往会产生幻觉并用合成数据填充字段
(例如,当姓名缺失时使用 "John Doe")。
请注意:带有原始类型(例如 int、boolean 等)的可选字段,
如果 LLM 未为其提供值,将用默认值初始化(例如 int 为 0,boolean 为 false 等)。
请注意:即使开启严格模式(strictJsonSchema(true)),
可选的 enum 字段仍可能被幻觉值填充。
若要使字段变为必填,可以用 @JsonProperty(required = true) 注解它:
record Person(@JsonProperty(required = true) String name, String surname) {
}
interface PersonExtractor {
Person extractPersonFrom(String text);
}
请注意:与工具一起使用时, 默认情况下所有字段与子字段都被视为必填。
添加描述
如果 LLM 未给出期望的输出,可以用 @Description 注解类和字段,
向 LLM 给出更多指令以及正确输出的示例,例如:
@Description("a person")
record Person(@Description("person's first and last name, for example: John Doe") String name,
@Description("person's age, for example: 42") int age,
@Description("person's height in meters, for example: 1.78") double height,
@Description("is person married or not, for example: false") boolean married) {
}
请注意:放在 enum 值上的 @Description 没有效果,也不会包含在
生成的 JSON schema 中:
enum Priority {
@Description("Critical issues such as payment gateway failures or security breaches.") // this is ignored
CRITICAL,
@Description("High-priority issues like major feature malfunctions or widespread outages.") // this is ignored
HIGH,
@Description("Low-priority issues such as minor bugs or cosmetic problems.") // this is ignored
LOW
}
多态类型
AI Service 方法可以返回多态类型——由 LLM 在运行时决定具体子类型的基类型。 支持两种形式:
- 密封接口与密封类 — 无需注解;子类型通过
Class.getPermittedSubclasses()发现。 - 普通抽象类与接口 — 必须使用 Jackson 的
@JsonSubTypes显式声明其子类型。
多态返回类型适用于类型本身、集合(List<T>、Set<T>)、
嵌套在其他 POJO 中的字段,以及子类型将基类型作为字段包含的递归层次结构
(例如 BinaryOp(left: ExpressionNode, right: ExpressionNode),
其中 ExpressionNode 是密封基类型)。
会为每个子类型添加一个鉴别器属性(默认为 "type"),以便 LLM
能表明它生成了哪个具体类型 ;随后解析器会自动分派到
正确的子类型。
密封接口与类 — 无需注解:
sealed interface Animal permits Dog, Cat {}
record Dog(String name, String breed) implements Animal {}
record Cat(String name, boolean indoor) implements Animal {}
interface AnimalExtractor {
Animal extractAnimalFrom(String text);
}
LLM 会看到一个在 Dog 与 Cat 上使用 anyOf 的 schema,每个选项都被约束为在
type 属性中输出其简单类名。给定:
Rex is a Labrador.
LLM 会输出 {"value":{"type":"Dog","name":"Rex","breed":"Labrador"}},随后被解析
回 Dog 实例。
由于许多 LLM 提供商不支持根级带有 anyOf 的 JSON schema,
schema 会将多态选择包装在 value 属性下(集合则为 values)。
该包装是实现细节——你的 AI Service 方法仍返回未包装的子类型。
多态类型的集合:
interface AnimalsExtractor {
List<Animal> extractAnimalsFrom(String text);
}
嵌套在另一个 POJO 中的多态字段:
record Owner(String name, Animal pet) {}
interface OwnerExtractor {
Owner extractOwnerFrom(String text);
}
Jackson @JsonSubTypes / @JsonTypeInfo 也受支持,并允许你将线上名称
与 Java 类名解耦:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "kind")
@JsonSubTypes({
@JsonSubTypes.Type(value = Square.class, name = "square"),
@JsonSubTypes.Type(value = Circle.class, name = "circle")
})
interface Shape {}
class Square implements Shape { double side; }
class Circle implements Shape { double radius; }
鉴别器值的解析顺序:
- 基类型上的
@JsonSubTypes.Type(name = "...") - 子类型上的
@JsonTypeName Class.getSimpleName()(默认)
因此,当 你不想在基类型上声明线上名称时,@JsonTypeName 是设置线上名称的便捷方式:
sealed interface Bird permits Eagle, Sparrow {}
@JsonTypeName("bird_eagle")
record Eagle(double wingspanMeters) implements Bird {}
@JsonTypeName("bird_sparrow")
record Sparrow(boolean migratory) implements Bird {}
LLM 将看到 "bird_eagle" / "bird_sparrow" 作为鉴别器值,而不是简单
类名("Eagle"/"Sparrow")。
支持的 @JsonTypeInfo 配置:
| 属性 | 支持的值 |
|---|---|
use | Id.NAME、Id.SIMPLE_NAME |
include | As.PROPERTY(默认)、As.EXISTING_PROPERTY |
property | 任意显式值;为空时默认为 "@type" |
defaultImpl | 任意具体子类——当 LLM 的鉴别器缺失或未知时使用 |
visible | true 会在反序列化后的 bean 上保留鉴别器字段(并绕过字段冲突检查) |
其他配置(例如 Id.CLASS、As.WRAPPER_OBJECT)会在 schema 生成时
以 UnsupportedFeatureException 拒绝。
用于容忍幻觉的 defaultImpl:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, defaultImpl = UnknownTool.class)
@JsonSubTypes({
@JsonSubTypes.Type(value = Hammer.class, name = "hammer"),
@JsonSubTypes.Type(value = Wrench.class, name = "wrench")
})
interface Tool {}
如果 LLM 输出未知的鉴别器(例如 "saw")或完全省略它,解析器
会返回 UnknownTool 而不是失败,这样你的代码可以检测并处理该幻觉。
添加描述:
你可以通过用 @Description 注解基类型和/或子类型来引导 LLM。
基类型的描述会附加到 anyOf 元素上,每个子类型的描述会附加到其各自的选项上:
@Description("A pet that lives in your home")
sealed interface Pet permits Hamster, Parrot {}
@Description("A small caged rodent kept as a pet")
record Hamster(String name, double weightGrams) implements Pet {}
@Description("A talking bird that can mimic human speech")
record Parrot(String name, int vocabulary) implements Pet {}
当省略 @Description 时,描述会回退到简单类名
(例如 "Hamster"),以便 LLM 仍能为每个选项获得一个标签。
递归多态类型:
其字段包含基类型的多态基类型同样可用:
sealed interface ExpressionNode permits Literal, BinaryOp {}
record Literal(int value) implements ExpressionNode {}
record BinaryOp(String operator, ExpressionNode left, ExpressionNode right) implements ExpressionNode {}
递归多态 schema 需要支持 $ref / $defs 的模型
(目前为 Azure OpenAI、Mistral 和 OpenAI)。
鉴别器字段冲突:
如果子类型声明了与鉴别器同名的字段(例如密封基类型上的 type 字段),
schema 生成会失败并给出明确消息。修复选项:
- 重命名该字段,或
- 使用
@JsonTypeInfo(property = "...")选择不同的鉴别器名称,或 - 若该字段有意作为子类型的一部分,设置
@JsonTypeInfo(visible = true),或 - 当子类型上的字段是鉴别器的真实来源时,使用
@JsonTypeInfo(include = As.EXISTING_PROPERTY)。
限制
在 AI Services 中使用 JSON Schema 时,存在一些限制:
- 仅适用于受支持的 Amazon Bedrock、Azure OpenAI、Google AI Gemini、Mistral、Ollama 和 OpenAI 模型。
- 配置
ChatModel时需要显式启用对 JSON Schema 的支持。 - 不支持流式模式。
- 并非所有类型都受支持。受支持类型列表见此处。
- POJO 可以包含:
- 标量/简单类型(例如
String、int/Integer、double/Double、boolean/Boolean等) enum- 嵌套 POJO
List<T>、Set<T>和T[],其中T是标量、enum或 POJO- 多态类型(密封接口/类或使用 Jackson
@JsonSubTypes注解的类型)
- 标量/简单类型(例如
- 递归目前仅受 Azure OpenAI、Mistral 和 OpenAI 支持。
- 多态类型需要支持 JSON schema 中
anyOf的 LLM。 - 当 LLM 不支持 JSON Schema 功能、或未启用、或类型不受支持时, AI Service 将回退到提示词。
提示词 + JSON Mode
提示词
使用提示词时(这是默认选择,除非启用了 JSON schema 支持),
AI Service 会自动生成格式指令,并将其追加到 UserMessage 的末尾,
指明 LLM 应以何种格式响应。
在方法返回之前,AI Service 会将 LLM 的输出解析为期望的类型。
你可以通过启用日志观察追加的指令。
这种方法相当不可靠。 如果 LLM 与 LLM 提供商支持上文描述的方法,最好使用那些方法。
支持的类型
| 类型 | JSON Schema | 提示词 |
|---|---|---|
POJO | ✅ | ✅ |
List<POJO>、Set<POJO> | ✅ | ❌ |
Enum | ✅ | ✅ |
List<Enum>、Set<Enum> | ✅ | ✅ |
List<String>、Set<String> | ✅ | ✅ |
多态(密封 / @JsonSubTypes),含 List/Set | ✅ | ❌ |
boolean、Boolean | ✅ | ✅ |
int、Integer | ✅ | ✅ |
long、Long | ✅ | ✅ |
float、Float | ✅ | ✅ |
double、Double | ✅ | ✅ |
byte、Byte | ✅ | ✅ |
short、Short | ✅ | ✅ |
BigInteger | ✅ | ✅ |
BigDecimal | ✅ | ✅ |
Date | ❌ | ✅ |
LocalDate | ❌ | ✅ |
LocalTime | ❌ | ✅ |
LocalDateTime | ❌ | ✅ |
Map<?, ?> | ❌ | ✅ |
几个示例:
record Person(String firstName, String lastName) {}
enum Sentiment {
POSITIVE, NEGATIVE, NEUTRAL
}
interface Assistant {
Person extractPersonFrom(String text);
Set<Person> extractPeopleFrom(String text);
Sentiment extractSentimentFrom(String text);
List<Sentiment> extractSentimentsFrom(String text);
List<String> generateOutline(String topic);
boolean isSentimentPositive(String text);
Integer extractNumberOfPeopleMentionedIn(String text);
}