Spring AI:结构化输出 - 大模型响应内容
Spring AI:结构化输出 - 大模型响应内容
Spring AI历史文章列表
Spring AI:对接DeepSeek实战
Spring AI:对接官方 DeepSeek-R1 模型 —— 实现推理效果
Spring AI:ChatClient实现对话效果
Spring AI:使用 Advisor 组件 - 打印请求大模型出入参日志
Spring AI:ChatMemory 实现聊天记忆功能
Spring AI:本地安装 Ollama 并运行 Qwen3 模型
Spring AI:提示词工程
Spring AI:提示词工程 - Prompt 角色分类(系统角色与用户角色)
Spring AI:基于 “助手角色” 消息实现聊天记忆功能
什么是 AI 大模型 Tokens?

我们在使用第三方平台提供的 AI 大模型,如阿里百炼等,在 API 计费文档中,都会写上不同模型的计费方式,如 DeepSeek-R1 这款模型,输入 0.004元 / 每千 Token,输出 0.016 元 / 每千 Token。 那么,问题来了,什么是大模型的 Token 呢?
什么是 Token?
Token 是 AI 模型处理文本的核心要素,它充当了一座桥梁,将我们理解的单词转换为 AI 模型能够处理的格式。这种转换分两个阶段进行:输入时单词被转换为 Token,输出时这些 Token 再被转换回单词。
Token 化(Tokenization),即将文本分解为 Token 的过程,是 AI 模型理解和处理语言的基础。AI 模型正是通过处理这种 Token 化的格式来理解提示(prompt)并做出响应。
为了更好地理解 Token,拿英文语境来举例, 可以将 Token 视为单词的片段。通常,一个 Token 大约相当于四分之三个单词。例如,莎士比亚全集大约有 90 万个单词,对应的 Token 数量大约为 120 万个。如果是中文,一个汉字通常对应 1~2 个 Token,一个字对应 2 个 Token 通常发生在低频字、生僻字或特殊字符上,比如 “熵” 字,会被解析为 2 个 Token。
TIP:小伙伴们可以尝试使用 OpenAI Tokenizer UI 工具: https://platform.openai.com/tokenizer , 来观察 Open AI 的大模型,是如何被转换成 Token 的
我们在使用第三方平台提供的 AI 大模型,如阿里百炼等,在 API 计费文档中,都会写上不同模型的计费方式,如 DeepSeek-R1 这款模型,输入 0.004元 / 每千 Token,输出 0.016 元 / 每千 Token。 那么,问题来了,什么是大模型的 Token 呢?
Token 的作用
Token 除了在 AI 处理过程中扮演技术角色外,还具有重要的实际意义,尤其是在计费和模型能力方面:
1、计费:AI 模型服务通常根据 Token 的使用量进行计费。输入(提示)和输出(响应)的 Token 都会计入总量,因此更简短的提示通常更具成本效益。
2、模型限制:不同的 AI 模型有不同的 Token 数量限制,这定义了它们的上下文窗口 (Context Window)——即模型单次处理所能容纳的最大信息量。例如,GPT-3 的 Token 限制是 4K,而像 Claude 2 和 Meta Llama 2 等其他模型的限制可达 100K Token,一些研究型模型甚至能处理高达 100 万 Token。
3、上下文窗口:模型的 Token 限制决定了其上下文窗口的大小。超出此限制的输入内容不会被模型处理。因此,仅发送处理所需的最少有效信息集至关重要。例如,当询问关于《三国演义》的问题时,就没有必要包含其他书籍文本的 Token。
4、响应元数据:AI 模型响应的元数据中包含所使用的 Token 数量,这是管理和控制使用量及成本的关键信息。
结构化输出:大模型响应内容

假设有这么一个需求,要做一个 “电影档案库网站”,必然涉及到展示不同演员的代表作,如果基于 AI 大模型来生成,我们知道,与 AI 大模型进行对话,其响应结果都是聊天式的内容,没有固定格式,非常不方便程序去处理,而数据保存到数据库中,表都是结构化的,每个字段分的很清楚,如电影名、发行时间等等。
那么问题来了,有没有办法让 AI 大模型结构化输出内容呢?
什么是结构化输出?
AI 大模型结构化输出是指通过特定技术手段,使大型语言模型(如 DeepSeek、GPT、Claude 等)生成的数据符合预定义的格式规范(如 JSON、XML、Java 类等),而非自由格式的文本。这种输出具有明确的字段、数据类型和层级关系,可直接被计算机程序解析和使用。
为什么需要结构化输出?
结构化输出 AI 大模型响应,优势如下:
- 系统集成需求:现代软件系统(微服务、数据库、工作流引擎)要求数据格式标准化。结构化输出可直接对接 API、存储到数据库或集成到自动化流程中,无需额外转换。
- 提高数据可靠性:通过预定义结构确保:字段完整性(强制关键数据存在)、类型安全(数字/布尔值不被误转为文本)、值域约束(如年龄范围限制),减少歧义和错误。
- 提升开发效率:对比非结构化数据处理(需要复杂文本解析和错误处理),结构化输出减少70%的数据处理代码量。
- 性能优化:结构化数据比非结构化文本处理速度快 3-5 倍。
- 未来扩展性:当新增数据字段时,结构化输出通过修改Schema即可支持,而无需重写解析逻辑,大幅降低系统演进成本。
Spring AI 中的结构化转换器
在 Spring AI 中,有专门提供的结构化输出转换器,它能够将 AI 大模型的输出,进行结构化。其处理流程如下图:

结构化输出转换器在调用 AI 大模型(LLM)之前和之后都扮演着关键角色,确保能够拿到所需的输出结构:
- 在调用 AI 大模型之前,转换器将格式指令附加到提示词(prompt)中,为模型生成所需的输出结构提供明确的指导。这些指令充当蓝图,塑造模型的响应以符合指定的格式。
- 在调用 AI 大模型之后,转换器获取模型的输出文本并将其转换为结构化类型的实例。此转换过程涉及解析原始文本输出,并将其映射到相应的结构化数据表示形式,例如 JSON、XML 或特定领域的数据结构。
注意:转换器也只是尽最大努力将 AI 大模型输出的内容,进行结构化输出。不能保证 AI 模型一定会按要求返回结构化输出。因为大模型可能不理解提示词,或者无法按要求生成结构化输出。可以在拿到转换结构后,添加验证机制,以确保模型输出符合预期。
案例1:演员代表作
接下来,我们通过几个小案例,来切身感受一下,如何通过 Spring AI 调用大模型,拿到结构化输出结果。第一个案例是,输入某个演员,获取其代表作,并且以 Bean 对象的方式输出。
首先,在 /model 包下,新增一个 ActorFilmography 记录,定义上两个字段,分别是 actor 演员,以及代表作电源的集合 movies,代码如下:
@JsonPropertyOrder({"actor", "movies"})
public record ActorFilmography(String actor, List<String> movies) {
}
接着,在 /controller 包下,新增一个 StructuredOutputController 控制器:
@RestController
@RequestMapping("/v8/ai")
public class StructuredOutputController {
@Resource
private ChatClient chatClient;
/**
* 示例1: BeanOutputConverter - 获取演员电影作品集
* @param name
* @return
*/
@GetMapping("/actor/films")
public ActorFilmography generate(@RequestParam(value = "name") String name) {
// 一次性返回结果
return chatClient.prompt()
.user(u -> u.text("""
请为演员 {actor} 生成包含5部代表作的电影作品集,
只包含 {actor} 担任主演的电影,不要包含任何解释说明。
""")
.param("actor", name))
.call()
.entity(ActorFilmography.class);
}
// 省略...
}
上述代码中,通过提示词,约束大模型的输出格式,另外通过 call() 方法,调用大模型拿到结果后,使用 entity() 方法,将其转换为 ActorFilmography 对象。
重启后端项目,浏览器访问如下地址,入参的演员名为 “周星驰”, 测试一下是否能够结构化输出:
http://localhost:8080/v8/ai/actor/films?name=周星驰
案例2:获取编程语言信息
继续下一个案例,输入某个语言名,如 Java, 结构化输出相关信息,如语言特性、发布年份等,这次不是转换为对象,而是 Map 字典。
编辑 StructuredOutputController 控制器,声明一个 /v8/ai/language-info 接口,代码如下:
@RestController
@RequestMapping("/v8/ai")
public class StructuredOutputController {
@Resource
private ChatClient chatClient;
// 省略...
/**
* 示例2: MapOutputConverter - 获取编程语言信息
* @param language
* @return
*/
@GetMapping("/language-info")
public Map<String, Object> getLanguageInfo(@RequestParam(value = "lang") String language) {
String userText = """
请提供关于编程语言 {language} 的结构化信息,包含以下字段:"
name (语言名称), "
popularity (流行度排名,整数), "
features (主要特性,字符串数组), "
releaseYear (首次发布年份). "
不要包含任何解释说明,直接输出 JSON 格式数据。
""";
return chatClient.prompt()
.user(u -> u.text(userText).param("language", language))
.call()
.entity(new MapOutputConverter());
}
// 省略...
}
上述代码中,通过提示词约束 AI 大模型的输出,要求输出 JSON 格式数据。另外,entity() 方法中,改为使用 MapOutputConverter 转换器,转换为 Map 字典。
重启后端项目,浏览器请求如下地址,测试一下,是否能够拿到对应编程语言的相关信息
http://localhost:8080/v8/ai/language-info?lang=Java
案例3:获取城市列表
除了转换为 Map 字典,也可以转换为 List 集合,以输入某个国家名,获取主要城市名称为案例。编辑 StructuredOutputController 控制器,声明一个 /v8/ai/city-list 接口,代码如下:
@RestController
@RequestMapping("/v8/ai")
public class StructuredOutputController {
@Resource
private ChatClient chatClient;
// 省略...
/**
* 示例3: ListOutputConverter - 获取城市列表
* @param country
* @return
*/
@GetMapping("/city-list")
public List<String> getCityList(@RequestParam(value = "country") String country) {
return chatClient.prompt()
.user(u -> u.text(
"""
列出 {country} 的8个主要城市名称。
不要包含任何编号、解释或其他文本,直接输出城市名称列表。
""")
.param("country", country))
.call()
.entity(new ListOutputConverter(new DefaultConversionService()));
}
// 省略...
}
上述代码中,通过提示词约束输出结果,调用 AI 大模型拿到结果后,通过 .entity() 方法指定转换器,ListOutputConverter 的功能是,将 AI 的文本输出转换为 List 集合,而 DefaultConversionService 提供了基础的字符串到其他类型的转换能力。
重启后端项目,浏览器请求如下地址,获取中国的主要城市列表:
http://localhost:8080/v8/ai/city-list?country=中国
案例4:获取书籍信息
最后一个案例,获取某本书籍的信息,同样是转换为对象,这次的结构化信息相对复杂一点。在 /model 包下,新增 Book 书籍实体类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
/**
* 书名
*/
private String title;
/**
* 作者
*/
private String author;
/**
* 发布年份
*/
private Integer publishYear;
/**
* 类型
*/
private List<String> genres;
/**
* 简介
*/
private String description;
}
接着,在 StructuredOutputController 控制器中,声明 /v8/ai/book-info 接口,代码如下:
@RestController
@RequestMapping("/v8/ai")
public class StructuredOutputController {
@Resource
private ChatClient chatClient;
// 省略..
/**
* 使用低级 API 的 BeanOutputConverter - 获取书籍信息
* @param bookTitle
* @return
*/
@GetMapping("/book-info")
public Book getBookInfo(@RequestParam(value = "name") String bookTitle) {
// 使用 BeanOutputConverter 定义输出格式
BeanOutputConverter<Book> converter = new BeanOutputConverter<>(Book.class);
// 提示词模板
String template = """
请提供关于书籍《{bookTitle}》的详细信息:
1. 作者姓名
2. 出版年份
3. 主要类型(数组)
4. 书籍描述(不少于50字)
不要包含任何解释说明,直接按指定格式输出。
{format}
""";
// 创建 Prompt
PromptTemplate promptTemplate = new PromptTemplate(template);
Prompt prompt = promptTemplate.create(Map.of(
"bookTitle", bookTitle,
"format", converter.getFormat()
));
// 调用模型并转换结果
String result = chatClient.prompt(prompt)
.call()
.content();
// 结构化转换
return converter.convert(result);
}
}
上述代码中:
- 定义 BeanOutputConverter 转换器,转换的目标对象为 Book 实例;
- 编写提示词模板,包含两个占位符,bookTitle 是接口入参中指定的书籍名称;format 为 Spring AI 内置提供的,指定 AI 大模型输出 Json 格式的提示词,其提示词写的很细致,点击 getFormat() 的源码,即可看到它。

-
创建 Prompt 提示词对象,并调用 AI 大模型;
-
拿到结果后,使用 BeanOutputConverter 转换器,将结果转换为 Book 对象;
重启后端项目,测试一下,浏览器请求地址如下:
http://localhost:8080/v8/ai/book-info?name=三体
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)