结构化输出
"结构化输出"这个术语有多重含义,可以指两件事:
- LLM 生成结构化格式输出的一般能力(这是我们在本页中介绍的内容)
- OpenAI 的结构化输出功能, 适用于响应格式和工具(函数调用)。
许多 LLM 和 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 和 LLM 提供商的不同,有三种方式可以实现这一目标 (从最可靠到最不可靠):
JSON Schema
一些 LLM 提供商(目前包括 Azure OpenAI、Google AI Gemini、Mistral、Ollama 和 OpenAI)允许 为所需输出指定 JSON schema。 您可以在"JSON Schema"列中查看所有支持的 LLM 提供商 这里。
当在请求中指定 JSON schema 时,LLM 预期生成符合该 schema 的输出。
请注意,JSON schema 是在请求中作为专用属性指定给 LLM 提供商的 API 的, 不需要在提示中包含任何自由格式的指令(例如,在系统或用户消息中)。
LangChain4j 在低级 ChatLanguageModel API 和高级 AI 服务 API 中都支持 JSON Schema 功能 。
使用 ChatLanguageModel 的 JSON Schema
在低级 ChatLanguageModel API 中,可以使用与 LLM 提供商无关的 ResponseFormat 和 JsonSchema 在创建 ChatRequest 时指定 JSON schema:
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();
// 或
ChatLanguageModel 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类型, 但 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)。JsonNullSchema- 支持可空类型。
JsonObjectSchema
JsonObjectSchema 表示具有嵌套属性的对象。
它通常是 JsonSchema 的根元素。
有几种方法可以向 JsonObjectSchema 添加属性:
- 您可以使用
properties(Map<String, JsonSchemaElement> properties)方法一次添加所有属性:
JsonSchemaElement citySchema = JsonStringSchema.builder()
.description("应返回天气预报的城市")
.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") // 必需的属性应明确指定
.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", "应返回天气预报的城市")
.addEnumProperty("temperatureUnit", List.of("CELSIUS", "FAHRENHEIT"))
.required("city")
.build();
更多详细信息请参阅 JsonObjectSchema 的 Javadoc。
JsonStringSchema
创建 JsonStringSchema 的示 例:
JsonSchemaElement stringSchema = JsonStringSchema.builder()
.description("人的名字")
.build();
JsonIntegerSchema
创建 JsonIntegerSchema 的示例:
JsonSchemaElement integerSchema = JsonIntegerSchema.builder()
.description("人的年龄")
.build();
JsonNumberSchema
创建 JsonNumberSchema 的示例:
JsonSchemaElement numberSchema = JsonNumberSchema.builder()
.description("人的身高(米)")
.build();
JsonBooleanSchema
创建 JsonBooleanSchema 的示例:
JsonSchemaElement booleanSchema = JsonBooleanSchema.builder()
.description("人是否已婚?")
.build();
JsonEnumSchema
创建 JsonEnumSchema 的示例:
JsonSchemaElement enumSchema = JsonEnumSchema.builder()
.description("人的婚姻状况")
.enumValues(List.of("SINGLE", "MARRIED", "DIVORCED"))
.build();
JsonArraySchema
创建 JsonArraySchema 以定义字符串数组:
JsonSchemaElement itemSchema = JsonStringSchema.builder()
.description("人的名字")
.build();
JsonSchemaElement arraySchema = JsonArraySchema.builder()
.description("文本中找到的所有人的名字")
.items(itemSchema)
.build();
JsonReferenceSchema
JsonReferenceSchema 可以用于支持递归:
String reference = "person"; // reference 应在模式中唯一
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 支持。