1.什么是格式化输出
如果您想从 LLM 接收结构化输出,Structured Output 可以协助将 ChatModel/ChatClient 方法的返回类型从 String 更改为其他类型。
LLM 生成结构化输出的能力对于依赖可靠解析输出值的下游应用程序非常重要。开发人员希望快速将 AI 模型的结果转换为可以传递给其他应用程序函数和方法的数据类型,例如 JSON、XML 或 Java 类。Spring AI 结构化输出转换器有助于将 LLM 输出转换为结构化格式。

在 LLM 调用之前,转换器会将期望的输出格式(output format instruction)附加到 prompt 中,为模型提供生成所需输出结构的明确指导,这些指令充当蓝图,塑造模型的响应以符合指定的格式。以下是此类格式说明的示例
java
Your response should be in JSON format.
The data structure for the JSON should match this Java class: java.util.HashMap
Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.
在 LLM 调用之后,转换器获取模型的输出文本并将其转换为结构化类型的实例,此转换过程涉及解析原始文本输出并将其映射到相应的结构化数据表示,例如 JSON、XML 或特定于域的数据结构添加链接描述
2. 新建子模块SAA-07StructuredOutput

其余与前几个子模块中内容大同小异,pom文件中去除lombok,07子模块中使用Record。
2.1. pom文件
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.hf.hong</groupId>
<artifactId>SpringAIAlibaba-v1</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>SAA-07StructuredOutput</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入 springai alibaba DashScope 模型适配的 Starter -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
</dependency>
<!--lombok-->
<!-- <dependency>-->
<!-- <groupId>org.projectlombok</groupId>-->
<!-- <artifactId>lombok</artifactId>-->
<!-- <optional>true</optional>-->
<!-- </dependency>-->
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<source>21</source>
<target>21</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
2.2. application.properties
yaml
server.port=8007
#大模型对话中文乱码UTF8编码处理
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
server.servlet.encoding.charset=UTF-8
spring.application.name=SAA-07StructuredOutput
# ====SpringAIAlibaba Config=============
spring.ai.dashscope.api-key=${aliQwen-api}
2.3.配置类直接复制粘贴
2.4. StuRecord
java
package com.hf.hong.entity;
public record StuRecord(String sId,String sName,Integer sAge,String sMajor,String sEmail) {
}
2.5. StructuredOutputController
java
package com.hf.hong.controller;
import com.hf.hong.entity.StuRecord;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.function.Consumer;
/**
*
* @author admin
* @date 2026/3/13 20:39
* @description: 结构化输出,本代码以Record为例
*/
@RestController
public class StructuredOutputController {
@Resource(name = "deepseekChatClient")
private ChatClient deepseekChatClient;
@Resource(name = "qwenChatClient")
private ChatClient qwenChatClient;
@Resource(name = "deepSeekChatModel")
private ChatModel deepSeekChatModel;
@GetMapping("/structuredOutput/testRecord")
public StuRecord testRecord(String sname, String email) {
return qwenChatClient.prompt()
.user(new Consumer<ChatClient.PromptUserSpec>() {
@Override
public void accept(ChatClient.PromptUserSpec promptUserSpec)
{
promptUserSpec.text("学号001,我叫{sname},今年18,大学专业是计算机科学与技术,邮箱{email}")
.param("sname",sname)
.param("email",email);
}
}).call().entity(StuRecord.class);
}
}
3. 测试
bash
http://localhost:8007/structuredOutput/testRecord?sname=%E5%B0%8F%E7%94%9F%E4%B8%8D%E6%89%8D&email=hst14**@163.com

切换lambda版
java
@GetMapping("/structuredOutput/testRecord2")
public StuRecord testRecord2(@RequestParam(name = "sname") String sname, @RequestParam(name = "email") String email) {
String stringTemplate = """
学号002,我叫{sname},大学专业是软件工程,邮箱{email}
""";
return qwenChatClient.prompt()
.user(promptUserSpec -> promptUserSpec.text(stringTemplate)
.param("sname",sname)
.param("email",email))
.call()
.entity(StuRecord.class);
}
}
测试
bash
http://localhost:8007/structuredOutput/testRecord2?sname=%E5%B0%8F%E7%94%9F%E4%B8%8D%E6%89%8D&email=hst14**@163.com

4. 完整代码
java
package com.hf.hong.controller;
import com.hf.hong.entity.StuRecord;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.function.Consumer;
/**
*
* @author admin
* @date 2026/3/13 20:39
* @description: 结构化输出,本代码以Record为例
*/
@RestController
public class StructuredOutputController {
@Resource(name = "deepseekChatClient")
private ChatClient deepseekChatClient;
@Resource(name = "qwenChatClient")
private ChatClient qwenChatClient;
@Resource(name = "deepSeekChatModel")
private ChatModel deepSeekChatModel;
@GetMapping("/structuredOutput/testRecord")
public StuRecord testRecord(String sname, String email) {
return qwenChatClient.prompt()
.user(new Consumer<ChatClient.PromptUserSpec>() {
@Override
public void accept(ChatClient.PromptUserSpec promptUserSpec)
{
promptUserSpec.text("学号001,我叫{sname},今年18,大学专业是计算机科学与技术,邮箱{email}")
.param("sname",sname)
.param("email",email);
}
}).call().entity(StuRecord.class);
}
@GetMapping("/structuredOutput/testRecord2")
public StuRecord testRecord2(@RequestParam(name = "sname") String sname, @RequestParam(name = "email") String email) {
String stringTemplate = """
学号002,我叫{sname},大学专业是软件工程,邮箱{email}
""";
return qwenChatClient.prompt()
.user(promptUserSpec -> promptUserSpec.text(stringTemplate)
.param("sname",sname)
.param("email",email))
.call()
.entity(StuRecord.class);
}
}