LangChain4j全集-11-Structured Outputs
Structured Outputs梳理structured-outputs下面我按这篇 LangChain4j 文档的结构帮你用 Spring Boot/Java 开发者容易理解的方式梳理一遍。这篇文章讲的是如何让大模型返回“结构化结果”比如 JSON然后在 Java 里直接映射成对象。1. 什么是 Structured OutputsStructured Outputs 可以理解为让大模型不要随便输出一段自然语言而是按照我们指定的结构输出比如 JSON、Java 对象对应的字段结构。比如你有一个 JavarecordrecordPerson(Stringname,intage,doubleheight,booleanmarried){}现在有一段非结构化文本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.你希望大模型帮你提取成{name:Eldwin Brightblade,age:412,height:1.65,married:false}然后你可以在 Java 里直接得到PersonpersonnewPerson(Eldwin Brightblade,412,1.65,false);这就是 Structured Outputs 的核心作用。2. 为什么这个能力很重要如果大模型只返回自然语言比如这个人叫 Eldwin Brightblade年龄 412 岁身高 1.65 米未婚。你在 Java 后端里要处理就很麻烦不好解析容易变格式很难入库很难传给其他接口很难参与业务判断但是如果它返回 JSON你就可以直接反序列化成 Java DTO存数据库返回给前端作为业务流程的下一步输入做校验、判断、规则匹配所以 Structured Outputs 特别适合这些场景场景例子信息抽取从简历中提取姓名、年龄、技能表单填充从用户描述中生成订单对象分类结果返回{category: 投诉, priority: 高}数据清洗把自然语言转成标准字段AI 工作流上一步模型输出结构化数据下一步继续处理3. 文档先提醒Structured Outputs 这个词有两个含义文档开头有一个注意点“Structured Outputs” 这个词有时候会有歧义。它可能指3.1 广义的结构化输出也就是本文讲的内容让大模型返回 JSON 等结构化数据。这是 LangChain4j 文档这一页主要讲的。3.2 OpenAI 专门的 Structured Outputs 功能OpenAI 自己也有一个功能叫 Structured Outputs。它不只是普通 JSON 输出还包括response formattools / function calling也就是说OpenAI 里面的 Structured Outputs 是一个更具体的官方能力。但这篇 LangChain4j 文档主要讲的是在 LangChain4j 中如何让不同大模型稳定地产生结构化输出。4. LangChain4j 里实现结构化输出的三种方式文档说目前根据不同 LLM 和 LLM Provider 的能力主要有三种方式。可靠性从高到低是JSON Schema ↓ Prompting JSON Mode ↓ Prompting也就是方式可靠性简单理解JSON Schema最高直接告诉模型必须按某个 JSON Schema 输出Prompting JSON Mode中等提示词里说格式同时开启 JSON 模式Prompting最低只靠提示词让模型输出 JSON下面分别解释。5. 第一种方式JSON Schema这是最推荐的方式。5.1 JSON Schema 是什么JSON Schema 可以理解为用一套标准规则描述 JSON 应该长什么样。比如你希望模型返回{name:Eldwin Brightblade,age:412,height:1.65,married:false}那么对应的结构可以描述成name 字符串 age 整数 height 数字 married 布尔值在 LangChain4j 中可以把这个结构告诉大模型。模型就会尽量按照这个结构输出。5.2 JSON Schema 的作用它的主要作用是约束大模型输出格式让模型尽量生成符合你 Java 对象结构的 JSON。比如你定义了recordPerson(Stringname,intage,doubleheight,booleanmarried){}那么你可以要求模型返回符合这个结构的 JSON。这样 LangChain4j 或你自己的代码就可以把 JSON 转成Person对象。5.3 哪些模型支持 JSON Schema文档中提到目前一些 LLM Provider 支持 JSON Schema比如Amazon BedrockAzure OpenAIGoogle AI GeminiMistralOllamaOpenAI不是所有模型都支持。所以你在实际项目里要注意不是你用了 LangChain4j 就一定能用 JSON Schema还要看底层模型供应商支不支持。6. JSON Schema 和 Prompt 有什么区别这是一个很重要的点。文档特别说明JSON Schema 是通过模型 API 请求里的专门字段传给模型的不是写在 prompt 里的自然语言说明。也就是说JSON Schema 不是这样请你返回一个 JSON格式如下 { name: ..., age: ... }而是通过 API 的参数告诉模型responseFormatJSONjsonSchemaPersonschema所以 JSON Schema 比单纯写提示词更可靠。7. 在 ChatModel API 中使用 JSON SchemaLangChain4j 里有两种常用 APIAPI特点ChatModel底层 API控制力强需要自己处理更多细节AI Service高层 API更适合业务开发接口式调用文档先讲的是底层的ChatModel用法。7.1 ChatModel 是什么你可以把ChatModel理解为LangChain4j 对大模型聊天能力的底层封装。你直接构造请求发送给模型然后拿到响应。类似你自己调用 OpenAI/Gemini 的 HTTP API只不过 LangChain4j 帮你封装了一层。7.2 ResponseFormat 是什么文档里出现了这个代码ResponseFormatresponseFormatResponseFormat.builder().type(JSON).jsonSchema(...).build();ResponseFormat的作用是告诉模型我希望你返回什么格式。常见有两种TEXTJSON默认一般是TEXT也就是普通文本。如果设置成.type(JSON)就表示我希望模型返回 JSON。如果再配合.jsonSchema(...)就表示不仅要返回 JSON而且要符合我指定的结构。7.3 JsonSchema 是什么文档中的核心代码大概是这样的ResponseFormatresponseFormatResponseFormat.builder().type(JSON).jsonSchema(JsonSchema.builder().name(Person).rootElement(JsonObjectSchema.builder().addStringProperty(name).addIntegerProperty(age).addNumberProperty(height).addBooleanProperty(married).required(name,age,height,married).build()).build()).build();这段代码的意思是我要模型返回一个 JSON 对象这个对象叫Person它有四个字段字段类型说明namestring姓名ageinteger年龄heightnumber身高marriedboolean是否已婚7.4.name(Person)是干什么的.name(Person)这个是给 JSON Schema 起名字。文档里提到OpenAI 要求 schema 有 name。所以这里写.name(Person)你可以理解为告诉模型这个结构叫 Person。7.5.rootElement(...)是干什么的.rootElement(JsonObjectSchema.builder()....build())这个表示返回的 JSON 根元素是什么类型。这里使用的是JsonObjectSchema也就是说模型应该返回一个 JSON 对象。类似{name:...,age:412,height:1.65,married:false}而不是数组[...]也不是普通字符串hello7.6.addStringProperty(name)是干什么的.addStringProperty(name)表示 JSON 中要有一个字段name:Eldwin Brightblade并且它的类型是字符串。7.7.addIntegerProperty(age)是干什么的.addIntegerProperty(age)表示 JSON 中要有一个整数类型字段age:4127.8.addNumberProperty(height)是干什么的.addNumberProperty(height)表示 JSON 中要有一个数字类型字段。和 integer 不同number 可以是小数。比如height:1.657.9.addBooleanProperty(married)是干什么的.addBooleanProperty(married)表示 JSON 中要有一个布尔字段married:false7.10.required(...)是干什么的.required(name,age,height,married)表示这些字段都是必填的。也就是说模型不能只返回{name:Eldwin Brightblade}而是应该返回完整结构{name:Eldwin Brightblade,age:412,height:1.65,married:false}这个对 Java 反序列化非常重要。因为你的 Java record 是recordPerson(Stringname,intage,doubleheight,booleanmarried){}如果缺了age后面映射就可能出问题。8. ChatModel 方式的整体流程如果用底层ChatModel整体流程是1. 定义 Java 对象 Person 2. 定义 JSON Schema 3. 构造 ResponseFormat 4. 构造 ChatRequest 5. 调用 chatModel.chat(...) 6. 得到模型返回的 JSON 字符串 7. 手动把 JSON 转成 Person 对象伪代码大概是recordPerson(Stringname,intage,doubleheight,booleanmarried){}ResponseFormatresponseFormatResponseFormat.builder().type(JSON).jsonSchema(JsonSchema.builder().name(Person).rootElement(JsonObjectSchema.builder().addStringProperty(name).addIntegerProperty(age).addNumberProperty(height).addBooleanProperty(married).required(name,age,height,married).build()).build()).build();ChatRequestrequestChatRequest.builder().messages(UserMessage.from( Extract person information from the following 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. )).responseFormat(responseFormat).build();ChatResponseresponsechatModel.chat(request);Stringjsonresponse.aiMessage().text();得到的json可能是{name:Eldwin Brightblade,age:412,height:1.65,married:false}然后你可以用 Jackson 转对象ObjectMapperobjectMappernewObjectMapper();PersonpersonobjectMapper.readValue(json,Person.class);9. ChatModel 方式适合什么场景底层ChatModel方式适合你想精细控制模型请求你想自己指定 JSON Schema你想自己处理 JSON 解析你在封装自己的 AI SDK 层你要做复杂链路比如多轮、多模型、多参数控制但是在业务开发里尤其是 Spring Boot 项目中很多时候更推荐使用下面的 AI Service。10. 使用 AI Service 实现结构化输出LangChain4j 的 AI Service 是一个非常适合 Java/Spring Boot 开发者的高级抽象。它的使用方式很像 MyBatis Mapper 或 Feign Client。你定义一个接口interfacePersonExtractor{UserMessage( Extract person information from the following text: {{text}} )PersonextractPerson(Stringtext);}然后 LangChain4j 会帮你生成实现。你调用PersonpersonpersonExtractor.extractPerson(text);就可以直接得到一个Person对象。10.1 AI Service 的作用AI Service 帮你隐藏了很多底层细节底层事情AI Service 帮你做什么构造 prompt通过注解生成调用模型自动调用处理返回值自动解析JSON 转 Java 对象自动映射支持 JSON Schema 时可以自动利用模型的结构化输出能力所以它特别适合 Spring Boot 业务代码。你可以像调用普通 Service 一样调用 AI。10.2 用 AI Service 的好处假设你的业务代码里需要提取人物信息。不用 AI Service 时你可能要写Stringprompt...;ChatRequestrequest...;ChatResponseresponsechatModel.chat(request);Stringjsonresponse.aiMessage().text();PersonpersonobjectMapper.readValue(json,Person.class);用了 AI Service 后可以变成PersonpersonpersonExtractor.extractPerson(text);这就很符合 Java 后端开发习惯。11. 第二种方式Prompting JSON Mode文档说第二种方式叫Prompting JSON Mode它的可靠性低于 JSON Schema但高于纯 Prompting。11.1 JSON Mode 是什么JSON Mode 可以理解为模型供应商提供的一种模式告诉模型必须输出合法 JSON。比如你设置ResponseFormat.builder().type(JSON).build();这表示请你返回 JSON。但这里没有指定完整的 JSON Schema。也就是说它只能保证模型大概率返回一个合法 JSON但不一定完全符合你想要的字段结构。11.2 为什么还需要 Prompting因为 JSON Mode 只告诉模型你要返回 JSON但是没有告诉它JSON 里必须有 name、age、height、married所以你还需要在提示词里写清楚Extract person information from the text. Return JSON with the following fields: - name: string - age: integer - height: number - married: boolean Return only JSON, no markdown, no explanation.这就是 Prompting JSON Mode。11.3 它和 JSON Schema 的区别对比项JSON SchemaPrompting JSON Mode是否有正式 Schema 约束有没有格式可靠性更高中等字段类型可靠性更高中等是否依赖提示词较少依赖模型支持要求需要支持 JSON Schema只需要支持 JSON Mode11.4 示例提示词可以写成Stringprompt Extract person information from the following text. Return only valid JSON. The JSON must have this structure: { name: string, age: 0, height: 0.0, married: true } 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. ;然后设置 response formatResponseFormatresponseFormatResponseFormat.builder().type(JSON).build();这样模型会尽量返回{name:Eldwin Brightblade,age:412,height:1.65,married:false}12. 第三种方式Prompting第三种方式就是最原始的只靠提示词要求模型输出 JSON。比如请从下面文本中提取人物信息并以 JSON 格式返回 { name: ..., age: 0, height: 0.0, married: false } 不要输出其他内容。12.1 Prompting 的优点优点是最简单几乎所有模型都能用不依赖模型供应商是否支持 JSON Schema 或 JSON Mode12.2 Prompting 的缺点缺点也明显模型可能返回json{name:Eldwin Brightblade,age:412,height:1.65 meters,married:no}或者返回 text Here is the JSON: { name: Eldwin Brightblade, age: 412, height: 1.65, married: false }这些对 Java 代码来说都不太友好。因为你可能要额外清理去掉 Markdown 代码块去掉解释文字修正字段类型处理缺失字段做容错解析13. 三种方式的选择建议对于 Spring Boot 后端开发我建议你这样选13.1 生产环境优先选 JSON Schema如果你用的模型支持 JSON Schema比如 OpenAI、Gemini、Mistral 等优先使用JSON Schema AI Service原因输出最稳定最容易映射成 Java 对象最适合后端服务最适合做业务流程13.2 如果模型不支持 JSON Schema但支持 JSON Mode那就用Prompting JSON Mode这种方案也比较实用。你要在 prompt 里把格式写清楚。13.3 如果模型什么都不支持那就只能用Prompting这种方式最好配合更严格的提示词JSON 解析容错失败重试输出校验14. 从 Spring Boot 开发角度怎么理解你可以把 Structured Outputs 理解成大模型版本的 DTO 填充器。以前你可能写 ControllerPostMapping(/person/extract)publicPersonextract(RequestBodyStringtext){// 自己写正则、NLP 或规则提取}现在可以让大模型做PostMapping(/person/extract)publicPersonextract(RequestBodyStringtext){returnpersonExtractor.extractPerson(text);}其中PersonExtractor是一个 AI Service。15. 推荐写法AI Service Java record你可以定义对象publicrecordPerson(Stringname,intage,doubleheight,booleanmarried){}定义 AI ServicepublicinterfacePersonExtractor{UserMessage( Extract person information from the following text. Text: {{text}} )PersonextractPerson(Stringtext);}然后业务代码调用PersonpersonpersonExtractor.extractPerson( 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[nameEldwinBrightblade,age412,height1.65,marriedfalse]这就是 LangChain4j 的高层用法。16. 实际项目中的注意事项16.1 尽量使用包装类型表示可空字段如果字段可能没有就不要用 primitive 类型intage;doubleheight;booleanmarried;可以改成Integerage;Doubleheight;Booleanmarried;因为如果模型提取不到null更合理。比如publicrecordPerson(Stringname,Integerage,Doubleheight,Booleanmarried){}16.2 字段名要稳定你希望 JSON 是{name:...,age:...}那 Java 里也最好叫name age不要 Java 字段叫personName personAge但 prompt 里又叫name age字段名尽量统一减少模型混乱。16.3 提示词里说明不要输出 Markdown尤其是不用 JSON Schema只靠 prompt 时要加一句Return only valid JSON. Do not include markdown code fences or explanations.中文就是只返回合法 JSON不要返回 Markdown 代码块不要解释。否则模型很容易返回json{...}这对反序列化不友好。 --- ## 16.4 做结果校验 不要完全相信模型输出。 即使使用 JSON Schema也建议后端做校验。 比如 java if (person.age() 0) { throw new IllegalArgumentException(年龄不合法); }或者配合 Bean ValidationpublicrecordPerson(NotBlankStringname,Min(0)Integerage,PositiveDoubleheight,Booleanmarried){}16.5 失败时可以重试结构化输出场景里常见问题包括模型返回 JSON 不合法字段缺失类型不对内容不符合业务规则生产环境建议加重试fallback日志记录原始响应保存异常告警17. 总结一句话这篇文档的核心就是LangChain4j 可以让大模型输出 JSON 等结构化结果并把它映射成 Java 对象。最可靠的方式是 JSON Schema其次是 JSON Mode Prompt最后是纯 Prompt。对于 Spring Boot 开发者最佳实践是定义 Java DTO / record ↓ 定义 AI Service 接口 ↓ 让 LangChain4j 调用模型 ↓ 直接拿到结构化 Java 对象 ↓ 做业务处理你可以把它理解成让大模型帮你把自然语言转成后端能直接处理的 DTO。