文章详细介绍了LangChain4j中如何使用结构化输出(Structured Outputs)。主要内容包括:
- JSON Schema:通过指定JSON Schema,LLM可以生成符合结构的输出。
- 工具(Tools):通过工具调用实现结构化输出。
- 提示 + JSON模式:通过提示和JSON模式实现结构化输出。
- 提示:通过自由文本提示实现结构化输出。
文章还详细介绍了如何在低级ChatLanguageModel API和高级AI服务API中使用JSON Schema,以及如何通过@Description注解提供更详细的指令。此外,还列举了使用JSON Schema时的限制和注意事项。
这种结构化输出功能使得LLM生成的输出可以更方便地被应用程序解析和使用。
结构化输出(Structured Outputs)
注意:
“结构化输出”这个术语存在歧义,可以指代两件事:
- 语言模型(LLM)生成结构化格式输出的通用能力(本页面所涵盖的内容)。
OpenAI的结构化输出功能,它适用于响应格式和工具(函数调用)。
许多语言模型和语言模型提供商支持以结构化格式(通常是 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.
目前,根据语言模型和语言模型提供商的不同,有四种方法可以实现这一目标(从最可靠到最不可靠)
- JSON Schema
- 工具(函数调用)
- 提示 + JSON模式
- 提示
1. JSON Schema
一些LLM提供商(目前包括OpenAI、Google AI Gemini和Ollama)允许为期望的输出指定JSON Schema。你可以在这里查看支持JSON Schema的LLM提供商。
当在请求中指定了JSON Schema时,LLM将生成符合该Schema的输出。
注意:
JSON Schema是在请求中专门指定的属性,不需要在提示(例如系统消息或用户消息)中包含任何自由形式的指令。
LangChain4j在低级ChatLanguageModel API和高级AI服务API中都支持JSON Schema功能。
使用JSON Schema与ChatLanguageModel
在低级ChatLanguageModel API中,可以通过ResponseFormat和JsonSchema指定JSON Schema,这些与LLM提供商无关:
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 = AzureOpenAiChatModel.builder()
.endpoint(System.getenv("AZURE_OPENAI_URL"))
.apiKey(System.getenv("AZURE_OPENAI_API_KEY"))
.deploymentName("gpt-4o-mini")
.logRequestsAndResponses(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]:在大多数情况下,根元素必须是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 children字段)。
- JsonAnyOfSchema:支持多态(例如Shape可以是Circle或Rectangle)。
1. 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()
.properties(properties)
.required("city") // 必填属性必须明确指定
.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。
2. JsonStringSchema
创建JsonStringSchema的示例:
JsonSchemaElement stringSchema = JsonStringSchema.builder()
.description("The name of the person")
.build();
1.3. JsonIntegerSchema
创建JsonIntegerSchema的示例:
JsonSchemaElement integerSchema = JsonIntegerSchema.builder()
.description("The age of the person")
.build();
1.4 JsonNumberSchema
创建JsonNumberSchema的示例:
JsonSchemaElement numberSchema = JsonNumberSchema.builder()
.description("The height of the person")
.build();
1.5 JsonBooleanSchema
创建JsonBooleanSchema的示例:
JsonSchemaElement booleanSchema = JsonBooleanSchema.builder()
.description("Is the person married?")
.build();
1.6 JsonEnumSchema
创建JsonEnumSchema的示例:
JsonSchemaElement enumSchema = JsonEnumSchema.builder()
.description("Marital status of the person")
.enumValues(List.of("SINGLE", "MARRIED", "DIVORCED"))
.build();
1.7 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.8 JsonReferenceSchema
JsonReferenceSchema用于支持递归:
String reference = "person"; // 引用应在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目前仅支持OpenAI和Azure OpenAI。
1.9 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。
添加描述(Adding Description)
除了JsonReferenceSchema之外的所有JsonSchemaElement子类型都有一个description属性。如果LLM没有提供期望的输出,可以通过描述向LLM提供更多指令和正确输出的示例,例如:
JsonSchemaElement stringSchema = JsonStringSchema.builder()
.description("The name of the person, for example: John Doe")
.build();
限制(Limitations)
使用ChatLanguageModel时,JSON Schema有一些限制:
它仅支持OpenAI、Azure OpenAI、Google AI Gemini和Ollama模型。
OpenAI的流式模式目前还不支持。
对于Google AI Gemini和Ollama,可以通过responseSchema(…)在创建/构建模型时指定JSON Schema。
JsonReferenceSchema和JsonAnyOfSchema目前仅支持OpenAI和Azure OpenAI。
1.10JsonIntegerSchema
创建JsonIntegerSchema的示例:
JsonSchemaElement integerSchema = JsonIntegerSchema.builder()
.description("The age of the person")
.build();
1.11JsonNumberSchema
创建JsonNumberSchema的示例:
JsonSchemaElement numberSchema = JsonNumberSchema.builder()
.description("The height of the person")
.build();
1.12 JsonBooleanSchema
创建JsonBooleanSchema的示例:
JsonSchemaElement booleanSchema = JsonBooleanSchema.builder()
.description("Is the person married?")
.build();
1.13 JsonEnumSchema
创建JsonEnumSchema的示例:
JsonSchemaElement enumSchema = JsonEnumSchema.builder()
.description("Marital status of the person")
.enumValues(List.of("SINGLE", "MARRIED", "DIVORCED"))
.build();
1.14 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.14 JsonReferenceSchema
JsonReferenceSchema用于支持递归:
String reference = "person"; // 引用应在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目前仅支持OpenAI和Azure OpenAI。
1.14 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。
添加描述(Adding Description)
除了JsonReferenceSchema之外的所有JsonSchemaElement子类型都有一个description属性。如果LLM没有提供期望的输出,可以通过描述向LLM提供更多指令和正确输出的示例,例如:
JsonSchemaElement stringSchema = JsonStringSchema.builder()
.description("The name of the person, for example: John Doe")
.build();
限制(Limitations)
使用ChatLanguageModel时,JSON Schema有一些限制: 它仅支持OpenAI、Azure OpenAI、Google
AI Gemini和Ollama模型。 OpenAI的流式模式目前还不支持。 对于Google AI
Gemini和Ollama,可以通过responseSchema(…)在创建/构建模型时指定JSON Schema。
2. 使用JSON Schema与AI服务(Using JSON Schema with AI Services)
使用AI服务时,可以更轻松地实现相同的功能,代码也更少:
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 = AzureOpenAiChatModel.builder() // 见[1]
.endpoint(System.getenv(“AZURE_OPENAI_URL”))
.apiKey(System.getenv(“AZURE_OPENAI_API_KEY”))
.deploymentName(“gpt-4o-mini”)
.strictJsonSchema(true)
.supportedCapabilities(Set.of(RESPONSE_FORMAT_JSON_SCHEMA)) // 见[3]
.logRequestsAndResponses(true)
.build();
// 或者
ChatLanguageModel chatModel = GoogleAiGeminiChatModel.builder() // 见[1]
.apiKey(System.getenv(“GOOGLE_AI_GEMINI_API_KEY”))
.modelName(“gemini-1.5-flash”)
.responseFormat(ResponseFormat.JSON) // 见[4]
.logRequestsAndResponses(true)
.build();
// 或者
ChatLanguageModel chatModel = OllamaChatModel.builder() // 见[1]
.baseUrl(“http://localhost:11434”)
.modelName(“llama3.1”)
.supportedCapabilities(RESPONSE_FORMAT_JSON_SCHEMA) // 见[5]
.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]:在Quarkus或Spring Boot应用程序中,无需显式创建ChatLanguageModel和AI服务,因为这些Bean会自动创建。更多相关信息:
Quarkus的链接
Spring Boot的链接
[2]:这是启用OpenAI的JSON Schema功能所必需的,更多详情请参阅链接。
[3]:这是启用Azure OpenAI的JSON Schema功能所必需的。
[4]:这是启用Google AI Gemini的JSON Schema功能所必需的。
[5]:这是启用Ollama的JSON Schema功能所必需的。
当满足以下所有条件时:
AI服务方法返回一个POJO。
使用的ChatLanguageModel支持JSON Schema功能。
在使用的ChatLanguageModel上启用了JSON Schema功能。
那么将根据指定的返回类型自动生成带有JsonSchema的ResponseFormat。
注意:
在配置ChatLanguageModel时,必须明确启用JSON Schema功能,因为默认情况下它是禁用的。
生成的JsonSchema的name是返回类型的简单名称(getClass().getSimpleName()),在这个例子中是“Person”。
一旦LLM响应,输出将被解析为一个对象,并从AI服务方法中返回。
注意:
虽然我们正在逐步迁移到Jackson,但AI服务中仍然使用Gson来解析输出,因此你的POJO上的Jackson注解将不会生效。
你可以在这里和这里找到许多支持的用例示例。
添加描述(Adding Description)
如果LLM没有提供期望的输出,可以通过@Description注解类和字段,向LLM提供更多指令和正确输出的示例,例如:
java
复制
@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) {
}
限制(Limitations)
使用AI服务时,JSON Schema有一些限制:
它仅支持OpenAI、Azure OpenAI、Google AI Gemini和Ollama模型。
必须在配置ChatLanguageModel时明确启用JSON Schema功能。
流式模式不支持。
目前,仅当返回类型是单个POJO或Result时才支持。如果你需要其他类型(例如List、enum等),请将它们包装到一个POJO中。我们很快会支持更多返回类型。
POJO可以包含:
标量/简单类型(例如String、int/Integer、double/Double、boolean/Boolean等)。
enum。
嵌套POJO。
List、Set和T[],其中T是标量、enum或POJO。
在生成的JsonSchema中,所有字段和子字段都被自动标记为required,目前没有使它们成为可选的方法。
如果LLM不支持JSON Schema功能,或者没有启用,或者返回类型不是POJO,AI服务将回退到提示。
递归目前仅支持OpenAI和Azure OpenAI。
尚不支持多态。返回的POJO及其嵌套POJO必须是具体类;接口或抽象类不支持。
更多详细信息即将推出。
在此期间,请阅读此部分和这篇文章。在这里插入代码片
11.15. 提示(Prompting)
当使用提示时,需要在系统或用户消息中以自由文本形式指定期望输出的格式,并希望LLM遵守。这种方法相当不可靠。如果LLM和LLM提供商支持上述方法,建议使用那些方法。
更多详细信息即将推出。
在此期间,请阅读此部分和这篇文章。
导读说明:
这是LangChain开发智能体的系列文档,欢迎连读
- 第1章:LangChain4j的聊天与语言模型
- 第2章:如何基于LangChain4j实现聊天记忆
- 第3章:在LangChain中如何设置模型参数
- 第4章:在LangChain中如何实现响应式流(Response Streaming)
- 第5章:在LangChain中如何使用AI Services
- 第6章:基于LangChain如何开发Agents,附带客户支持智能体示例
- 第7章:在LangChain中如何调用函数Tools (Function Calling)
- 第8章:LangChain检索增强生成RAG–1概述
- 第8章:LangChain检索增强生成RAG–2.1Easy RAG实现
- 第8章:LangChain检索增强生成RAG–2.2Core RAG APIs
- 第8章:LangChain检索增强生成RAG–2.3Naive RAG
- 第8章:LangChain检索增强生成RAG–2.4Advanced RAG【高级RAG】
- 第9章:LangChain让大模型结构化输出
- 第9章:LangChain结构化输出-示例1(情感分析AI服务)
- 第9章:LangChain结构化输出-示例2(数字提取服务)
- 第9章:LangChain结构化输出-示例3(日期和时间提取服务)
- 第9章:LangChain结构化输出-示例4(基于大模型从自然语言中提取POJO)
- 第9章:LangChain结构化输出-示例5(基于大模型如何精确匹配POJO的字段)
- 第9章:LangChain结构化输出-示例6(设置系统消息和用户消息模版)
- 第10章:基于LangChain的综合实战[开发企业助手,查知识、查订单、表单登记…]