Yang's blog Yang's blog
首页
Java
密码学
机器学习
命令手册
关于
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

xiaoyang

编程爱好者
首页
Java
密码学
机器学习
命令手册
关于
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • SpringCloud

    • 微服务架构介绍
    • SpringCloud介绍
    • Spring Cloud:生产者与消费者
    • Spring Cloud Eureka:构建可靠的服务注册与发现
    • Spring Cloud Ribbon:负载均衡
    • Spring Cloud Fegin:服务调用
    • Spring Cloud Hystrix:熔断器
    • Spring Cloud Zuul:统一网关路由
    • Spring Cloud Config:配置中心
  • Java后端框架

    • LangChain4j

      • 介绍
      • 快速开始
      • Chat and Language Models
      • Chat Memory
      • Model Parameters
      • Response Streaming
      • AI Services
      • Agent
      • Tools (Function Calling)
      • RAG
      • Structured Outputs
        • JSON Schema
          • 在 ChatLanguageModel 中使用 JSON Schema
          • JsonObjectSchema
          • JsonStringSchema
          • JsonIntegerSchema
          • JsonNumberSchema
          • JsonBooleanSchema
          • JsonEnumSchema
          • JsonArraySchema
          • JsonReferenceSchema
          • JsonAnyOfSchema
          • 添加描述
          • 限制
          • 使用 JSON Schema 与 AI 服务
          • 添加描述
          • 限制
        • 工具(功能调用)
        • 提示 + JSON 模式
        • 提示
        • 相关教程
      • Classification
      • Embedding (Vector) Stores
      • Image Models
      • Quarkus Integration
      • Spring Boot Integration
      • Kotlin Support
      • Logging
      • Observability
      • Testing and Evaluation
      • Model Context Protocol
  • 八股文

    • 操作系统
    • JVM介绍
    • Java多线程
    • Java集合框架
    • Java反射
    • JavaIO
    • Mybatis介绍
    • Spring介绍
    • SpringBoot介绍
    • Mysql
    • Redis
    • 数据结构
    • 云计算
    • 设计模式
    • 计算机网络
    • 锁核心类AQS
    • Nginx
  • 前端技术

    • 初识Vue3
    • Vue3数据双向绑定
    • Vue3生命周期
    • Vue-Router 组件
    • Pinia 集中式状态存储
  • 中间件

    • RocketMQ
  • 开发知识

    • 请求参数注解
    • 时间复杂度和空间复杂度
    • JSON序列化与反序列化
    • Timestamp vs Datetime
    • Java开发中必备能力单元测试
    • 正向代理和反向代理
    • 什么是VPN
    • 正则表达式
  • Java
  • Java后端框架
  • LangChain4j
xiaoyang
2025-01-16
目录

Structured Outputs

# 结构化输出

“结构化输出”一词具有多重含义,可能指代以下两种情况:

  • LLM 生成结构化格式输出的通用能力(这是本页内容所涉及的)
  • OpenAI 的结构化输出 (opens new window)功能,适用于响应格式和工具(函数调用)。

许多 LLM 和 LLM 提供商支持生成结构化格式的输出,通常是 JSON 格式。这些输出可以轻松映射到 Java 对象,并在应用程序的其他部分使用。

例如,假设我们有一个 Person 类:

record Person(String name, int age, double height, boolean married) {
}
1
2

我们想从以下非结构化文本中提取一个 Person 对象:

John is 42 years old and lives an independent life.
He stands 1.75 meters tall and carries himself with confidence.
Currently unmarried, he enjoys the freedom to focus on his personal goals and interests.
1
2
3

根据 LLM 和提供商的不同,目前有四种方法可以实现这一目标(按可靠性排序):

  • JSON Schema
  • 工具(函数调用)
  • 提示 + JSON 模式
  • 提示

# JSON Schema

一些 LLM 提供商(目前包括 OpenAI、Google AI Gemini 和 Ollama)允许为期望的输出指定 JSON schema (opens new window)。您可以在此处 (opens new window)查看所有支持 JSON Schema 的 LLM 提供商。

当在请求中指定 JSON schema 时,LLM 需要生成符合该 schema 的输出。

请注意,JSON schema 是通过请求中专用的属性指定的,而不需要在提示(例如系统或用户消息)中包含任何自由格式的指令。

LangChain4j 在低级 ChatLanguageModel API 和高级 AI 服务 API 中都支持 JSON Schema 功能。

# 在 ChatLanguageModel 中使用 JSON Schema

在低级的 ChatLanguageModel API 中,可以使用与 LLM 提供商无关的 ResponseFormat 和 JsonSchema 来指定 JSON schema,创建 ChatRequest:

ResponseFormat responseFormat = ResponseFormat.builder()
        .type(JSON) // 类型可以是 TEXT(默认)或 JSON
        .jsonSchema(JsonSchema.builder()
                .name("Person") // OpenAI 要求指定 schema 名称
                .rootElement(JsonObjectSchema.builder() // 见 [1] 下文
                        .addStringProperty("name")
                        .addIntegerProperty("age")
                        .addNumberProperty("height")
                        .addBooleanProperty("married")
                        .required("name", "age", "height", "married") // 见 [2] 下文
                        .build())
                .build())
        .build();

UserMessage userMessage = UserMessage.from("""
        John is 42 years old and lives an independent life.
        He stands 1.75 meters tall and carries himself with confidence.
        Currently unmarried, he enjoys the freedom to focus on his personal goals and interests.
        """);

ChatRequest chatRequest = ChatRequest.builder()
        .responseFormat(responseFormat)
        .messages(userMessage)
        .build();

ChatLanguageModel chatModel = OpenAiChatModel.builder()
        .apiKey(System.getenv("OPENAI_API_KEY"))
        .modelName("gpt-4o-mini")
        .logRequests(true)
        .logResponses(true)
        .build();
// 或
ChatLanguageModel chatModel = GoogleAiGeminiChatModel.builder()
        .apiKey(System.getenv("GOOGLE_AI_GEMINI_API_KEY"))
        .modelName("gemini-1.5-flash")
        .logRequestsAndResponses(true)
        .build();
// 或
ChatLanguageModel chatModel = OllamaChatModel.builder()
        .baseUrl("http://localhost:11434")
        .modelName("llama3.1")
        .logRequests(true)
        .logResponses(true)
        .build();

ChatResponse chatResponse = chatModel.chat(chatRequest);

String output = chatResponse.aiMessage().text();
System.out.println(output); // {"name":"John","age":42,"height":1.75,"married":false}

Person person = new ObjectMapper().readValue(output, Person.class);
System.out.println(person); // Person[name=John, age=42, height=1.75, married=false]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

注意:

  • [1] - 在大多数情况下,根元素必须是 JsonObjectSchema 类型,然而 Gemini 也支持 JsonEnumSchema 和 JsonArraySchema。
  • [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)。

# JsonObjectSchema

JsonObjectSchema 代表一个具有嵌套属性的对象,通常是 JSON schema 的根元素。

可以通过以下几种方式将属性添加到 JsonObjectSchema 中:

  1. 使用 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()
        .properties(properties)
        .required("city") // 必填属性需要明确指定
        .build();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  1. 使用 addProperty(String name, JsonSchemaElement jsonSchemaElement) 方法逐个添加属性:
JsonSchemaElement rootElement = JsonObjectSchema.builder()
        .addProperty("city", citySchema)
        .addProperty("temperatureUnit", temperatureUnitSchema)
        .required("city")
        .build();
1
2
3
4
5
  1. 使用 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();
1
2
3
4
5

更多细节请参阅 JsonObjectSchema (opens new window) 的 Javadoc。

# JsonStringSchema

创建 JsonStringSchema 的示例:

JsonSchemaElement stringSchema = JsonStringSchema.builder()
        .description("The name of the person")
        .build();
1
2
3

# JsonIntegerSchema

创建 JsonIntegerSchema 的示例:

JsonSchemaElement integerSchema = JsonIntegerSchema.builder()
        .description("The age of the person")
        .build();
1
2
3

# JsonNumberSchema

创建 JsonNumberSchema 的示例:

JsonSchemaElement numberSchema = JsonNumberSchema.builder()
        .description("The height of the person")
        .build();
1
2
3

# JsonBooleanSchema

创建 JsonBooleanSchema 的示例:

JsonSchemaElement booleanSchema = JsonBooleanSchema.builder()
        .description("Is the person married?")
        .build();
1
2
3

# JsonEnumSchema

创建 JsonEnumSchema 的示例:

JsonSchemaElement enumSchema = JsonEnumSchema.builder()
        .description("Marital status of the person")
        .enumValues(List.of("SINGLE", "MARRIED", "DIVORCED"))
        .build();
1
2
3
4

# 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();
1
2
3
4
5
6
7
8

# JsonReferenceSchema

JsonReferenceSchema 可以用于支持递归:

String reference = "person"; // reference 应在 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();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

JsonReferenceSchema 当前仅由 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}]}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

JsonAnyOfSchema 当前仅由 OpenAI 支持。

# 添加描述

所有的 JsonSchemaElement 子类型(除 JsonReferenceSchema 外)都有一个 description 属性。 如果 LLM 没有提供期望的输出,可以提供描述,以向 LLM 提供更多的指令和正确输出的示例,例如:

JsonSchemaElement stringSchema = JsonStringSchema.builder()
        .description("The name of the person, for example: John Doe")
        .build();
1
2
3

# 限制

使用 JSON Schema 与 ChatLanguageModel 时,有一些限制:

  • 它仅与支持的 OpenAI、Google AI Gemini 和 Ollama 模型兼容。
  • 它在 OpenAI 的流式模式 (opens new window)下尚不支持。 对于 Google AI Gemini 和 Ollama,可以在创建/构建模型时通过 responseSchema(...) 指定 JSON Schema。
  • JsonReferenceSchema 和 JsonAnyOfSchema 当前仅由 OpenAI 支持。

# 使用 JSON Schema 与 AI 服务

当使用 AI 服务 (opens new window) 时,可以更轻松地实现相同的功能,且代码更简洁:

interface PersonExtractor {
    
    Person extractPersonFrom(String text);
}

ChatLanguageModel chatModel = OpenAiChatModel.builder() // 参见 [1] 下文
        .apiKey(System.getenv("OPENAI_API_KEY"))
        .modelName("gpt-4o-mini")
        .responseFormat("json_schema") // 参见 [2] 下文
        .strictJsonSchema(true) // 参见 [2] 下文
        .logRequests(true)
        .logResponses(true)
        .build();
// 或者
ChatLanguageModel chatModel = GoogleAiGeminiChatModel.builder() // 参见 [1] 下文
        .apiKey(System.getenv("GOOGLE_AI_GEMINI_API_KEY"))
        .modelName("gemini-1.5-flash")
        .responseFormat(ResponseFormat.JSON) // 参见 [3] 下文
        .logRequestsAndResponses(true)
        .build();
// 或者
ChatLanguageModel chatModel = OllamaChatModel.builder() // 参见 [1] 下文
        .baseUrl("http://localhost:11434")
        .modelName("llama3.1")
        .supportedCapabilities(RESPONSE_FORMAT_JSON_SCHEMA) // 参见 [4] 下文
        .logRequests(true)
        .logResponses(true)
        .build();

PersonExtractor personExtractor = AiServices.create(PersonExtractor.class, chatModel); // 参见 [1] 下文

String text = """
        John is 42 years old and lives an independent life.
        He stands 1.75 meters tall and carries himself with confidence.
        Currently unmarried, he enjoys the freedom to focus on his personal goals and interests.
        """;

Person person = personExtractor.extractPersonFrom(text);

System.out.println(person); // Person[name=John, age=42, height=1.75, married=false]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

注意:

  • [1] - 在 Quarkus 或 Spring Boot 应用程序中,无需显式创建 ChatLanguageModel 和 AI 服务, 这些 bean 会自动创建。更多信息: for Quarkus (opens new window), for Spring Boot (opens new window)。
  • [2] - 这要求启用 OpenAI 的 JSON Schema 功能,更多细节请见 这里 (opens new window)。
  • [3] - 这要求启用 Google AI Gemini (opens new window) 的 JSON Schema 功能。
  • [4] - 这要求启用 Ollama (opens new window) 的 JSON Schema 功能。

当满足以下所有条件时:

  • AI 服务方法返回一个 POJO
  • 使用的 ChatLanguageModel 支持 (opens new window) JSON Schema 功能
  • 在使用的 ChatLanguageModel 上启用了 JSON Schema 功能

则 ResponseFormat 将根据指定的返回类型自动生成带有 JsonSchema 的响应。

确保在配置 ChatLanguageModel 时显式启用 JSON Schema 功能, 因为它默认是禁用的。

生成的 JsonSchema 的 name 是返回类型的简单名称(getClass().getSimpleName()), 在本例中为:Person。

一旦 LLM 响应,输出会被解析成对象并从 AI 服务方法返回。

虽然我们正在逐步迁移到 Jackson,但 AI 服务中仍使用 Gson 来解析输出, 因此 POJO 上的 Jackson 注解将无效。

你可以在以下位置找到许多支持的用例示例: 这里 (opens new window) 和 这里 (opens new window)。

# 添加描述

如果 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) {
}
1
2
3
4
5
6

# 限制

在使用 JSON Schema 与 AI 服务时,有一些限制:

  • 仅支持 OpenAI、Google AI Gemini 和 Ollama 模型。
  • 配置 ChatLanguageModel 时需要显式启用对 JSON Schema 的支持。
  • 在流式模式 (opens new window)下不支持。
  • 当前仅当返回类型是单个 POJO 或 Result<POJO> 时有效。 如果需要其他类型(例如 List<POJO>、enum 等),请将这些类型包装成 POJO。 我们正在努力 (opens new window)尽快支持更多的返回类型。
  • POJO 可以包含:
    • 标量/简单类型(例如 String、int/Integer、double/Double、boolean/Boolean 等)
    • enum
    • 嵌套 POJO
    • List<T>、Set<T> 和 T[],其中 T 是标量、enum 或 POJO
  • 所有字段和子字段在生成的 JsonSchema 中默认标记为 required,当前没有方法将其设置为可选。
  • 当 LLM 不支持 JSON Schema 功能,或者该功能未启用,或者返回类型不是 POJO 时,AI 服务将回退到提示方式 (opens new window)。
  • 递归当前仅由 OpenAI 支持。
  • 多态尚未支持。返回的 POJO 及其嵌套的 POJO 必须是具体类;接口或抽象类不支持。

# 工具(功能调用)

此方法假设(错误)地使用工具 (opens new window)来生成结构化输出。 请求中指定一个工具,工具参数描述了期望的输出结构。 一旦 LLM 返回一个包含 ToolExecutionRequest 的 AiMessage, ToolExecutionRequest.arguments() 中的 JSON 字符串将被解析为 POJO。

更多信息即将发布。

同时,请阅读这一部分 (opens new window)和这篇文章 (opens new window)。

# 提示 + JSON 模式

更多信息即将发布。

同时,请阅读这一部分 (opens new window)和这篇文章 (opens new window)。

# 提示

在使用提示时,必须在系统或用户消息中以自由文本的形式指定期望输出的格式,并希望 LLM 遵守此格式。此方法不太可靠。 如果 LLM 和 LLM 提供商支持上述方法,最好使用这些方法。

更多信息即将发布。

同时,请阅读这一部分 (opens new window)和这篇文章 (opens new window)。

# 相关教程

  • 数据提取:让 LLM 输出 JSON 内容的多种方式 (opens new window) by Guillaume Laforge (opens new window)
编辑 (opens new window)
上次更新: 2025/04/01, 01:48:12

← RAG Classification→

最近更新
01
操作系统
03-18
02
Nginx
03-17
03
后端服务端主动推送消息的常见方式
03-11
更多文章>
Theme by Vdoing | Copyright © 2023-2025 xiaoyang | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式