本文是《LangChain4j从入门到精通》系列的第十一篇,深入解析了如何利用LangChain4j框架实现大模型结构化输出,将非文本数据自动转换为规范的Java对象。文章详细对比了三种实现方案:JSON Schema(最可靠,支持Azure OpenAI、Gemini等主流模型)、提示词+JSON模式(平衡方案)和纯提示词(基础方案),并通过完整代码示例演示了从POJO、枚举到集合类型的自动映射技巧。针对生产环境需求,特别介绍了JSON Schema的高级配置,包括递归结构、多态支持、字段校验规则及描述信息优化等实战经验。掌握结构化输出技术,能让开发者轻松实现从文本提取到数据解析的端到端自动化,大幅提升AI应用的数据处理效率与准确性。
#Java #大模型开发 #LangChain4j #结构化输出 #JSONSchema
- LLM以结构化格式生成输出的总体能力(这是我们在本页介绍的内容)
- 结构化输出 OpenAI 的特性,适用于响应格式和工具(函数调用)。
:::
许多大型语言模型(LLM)及其服务提供商支持以结构化格式(通常是JSON)生成输出。这些输出可以轻松映射到Java对象,并在应用程序的其他部分使用。
例如,假设我们有一个Person类:
record Person(String name, int age, double height, boolean married) { }
我们的目标是从这样的非结构化文本中提取一个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.
目前,根据大语言模型(LLM)及其提供商的不同,有三种实现方式(按可靠性从高到低排序):
LangChain4j在低级别的ChatModelAPI和高级别的AI服务API中都支持JSON Schema功能。
将JSON Schema与ChatModel结合使用
在底层 ChatModelAPI 中,创建 ChatRequest时可以使用与LLM提供商无关的 ResponseFormat和 JsonSchema来指定 JSON 模式。
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(“””
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();
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();
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] - 在大多数情况下,根元素必须是 JsonObjectSchema类型, 然而
- Azure OpenAI、Mistral、Ollama、OpenAI 和 OpenAI 官方也允许将 JsonRawSchema作为根元素
- Gemini 还允许将 JsonEnumSchema和 JsonArraySchema作为根元素
- [2] - 必须明确指定必填属性;否则,它们将被视为可选属性。
JSON模式的结构使用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字段).children -
JsonAnyOfSchema- 支持多态(等等.,Shape可以是Circle或者Rectangle). -
JsonNullSchema- 支持可空类型. -
JsonRawSchema- 使用您自定义的完整JSON模式
JsonObjectSchema
JsonObjectSchema表示一个具有嵌套属性的对象。 它通常是 JsonSchema的根元素。
有几种方法可以向JsonObjectSchema添加属性:
- 你可以使用 properties(
Map)方法一次性添加所有属性: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
"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();
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();
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}]}
JsonRawSchema
从现有模式字符串创建 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);
添加描述
除了 JsonReferenceSchema之外,所有 JsonSchemaElement子类型都有一个 description属性。 如果大型语言模型(LLM)未能生成预期输出,可以通过提供描述来向模型传递更多指令和正确输出的示例,例如:
JsonSchemaElement stringSchema = JsonStringSchema.builder() .description("The name of the person, for example: John Doe") .build();
限制
在使用JSON Schema与ChatModel时,存在以下限制:
- 仅适用于受支持的Azure OpenAI、Google AI Gemini、Mistral、Ollama和OpenAI模型。
- 目前OpenAI的流式模式尚不支持该功能。 对于Google AI Gemini、Mistral和Ollama,可以在创建/构建模型时通过responseSchema(…)指定JSON Schema。
- 目前只有Azure OpenAI、Mistral和OpenAI支持JsonReferenceSchema和JsonAnyOfSchema。
在AI服务中使用JSON Schema
使用AI服务时,可以更轻松地用更少的代码实现同样的效果:
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 .logRequests(true) .logResponses(true) .build();
PersonExtractor personExtractor = AiServices.create(PersonExtractor.class, chatModel); // see [1] below
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] - 在Quarkus或Spring Boot应用中,无需显式创建ChatModel和AI服务,这些Bean会自动生成。更多信息请参考:
Quarkus,
Spring Boot.
当满足以下所有条件时:
- AI服务方法返回一个POJO
- 使用的 ChatModel 支持 JSON Schema 功能
- 所使用的 ChatModel已启用 JSON Schema 功能
然后会根据指定的返回类型自动生成带有 JsonSchema的 ResponseFormat。
:::
生成的 JsonSchema的 name是返回类型的简单名称(getClass().getSimpleName()),在本例中为:“Person”。一旦LLM响应,输出将被解析为一个对象并从AI服务方法中返回。 你可以找到许多支持的使用案例示例。
必填和选填
record Person(@JsonProperty(required = true) String name, String surname) { } interface PersonExtractor {
Person extractPersonFrom(String text);
}
请注意,当与工具一起使用时,默认情况下所有字段和子字段均被视为必填项。
添加描述
如果大型语言模型(LLM)未能生成预期的输出结果,可以通过为类和字段添加@Description注解来为其提供更多指令和正确输出的示例,例如:
@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) {
}
请注意,在枚举值上使用 @Description注解 不会产生任何效果,也 不会包含在生成的 JSON 模式中。
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
}
限制
在使用JSON Schema与AI服务时存在以下限制:
- 仅支持特定的Azure OpenAI、Google AI Gemini、Mistral、Ollama和OpenAI模型。
- 需在配置ChatModel时显式启用JSON Schema支持功能。
- 无法在流式传输模式下使用。
- 并非所有类型都受支持,可查看支持的类型列表。
- POJO可包含:
- 标量/简单类型(如String、int/Integer、double/Double、boolean/Boolean等)
- enum枚举类型
- 嵌套POJO
-
List、Set及T[](其中T为标量、枚举或POJO)
- 目前仅Azure OpenAI、Mistral和OpenAI支持递归结构。 暂不支持多态,返回的POJO及其嵌套POJO必须是具体类,不支持接口或抽象类。
- 当LLM不支持JSON Schema功能、未启用该功能或类型不受支持时,AI服务将回退至提示词方式。
:::
POJO
List
, Set
Enum
List
, Set
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
extractPeopleFrom(String text); Sentiment extractSentimentFrom(String text); List
extractSentimentsFrom(String text); List
generateOutline(String topic); boolean isSentimentPositive(String text); Integer extractNumberOfPeopleMentionedIn(String text);
}
- 数据提取:让大语言模型输出JSON内容的各种方法 by Guillaume Laforge
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/266739.html