大模型最让我们头疼的问题之一,就是它返回的不是结构化文本,而业务代码需要的是有类型的数据结构。
否则,我们很难将 LLM 返回的结果和 Java 的对象一一对应起来。
本篇内容第一版的作者:星球嘉宾灰灰。二哥做了二期的优化和迭代。

举例来说,让大模型“返回五部电影”,它可能会给出一段 Markdown 列表、一段带编号的纯文本。
每次格式都不一样,上层业务代码根本没法正确解析。
Spring AI 2.0 的结构化返回机制就是来解决这个问题的,一行 .entity() 调用,大模型返回的无结构化文本就能变成规范的 Java 对象。
求职派的意图识别、用户画像提取、岗位数据采集,全都依赖这个能力。
这篇文章从 Spring AI 的结构化返回 API 讲起,拆解求职派源码里的三个生产级用例,最后讲清楚这套机制的边界在哪,什么场景靠谱,什么场景必须加兜底。
01、Spring AI 的结构化返回
Spring AI 提供了两条路径实现结构化返回,区别在于“谁来拼接提示词”。
第一种,ChatClient.entity() 自动挡
ActorsFilms films = ChatClient.create(chatModel)
.prompt("帮我返回五个周星驰导演的电影名")
.call()
.entity(ActorsFilms.class);
一行代码搞定。
Spring AI 在底层做了三件事。
- 第一步,根据目标类的字段生成 JSON Schema;
- 第二步,把 Schema 注入到提示词的上下文中(通过 Advisor 机制在请求发出前拦截并追加格式指令);
- 第三步,拿到大模型返回的 JSON 后用 Jackson 反序列化成 Java 对象。
我们发送的提示词里是不包含任何格式约束的,但 debug 查看真正发给大模型的请求,会发现 context 参数里已经注入了一段完整的 JSON Schema,这就是 Advisor 在背后干的事情。

上图是 debug 截图,可以看到提示词的 context 部分被自动追加了返回格式要求。
我们写的提示词只有“帮我返回五个周星驰导演的电影名”,格式约束完全由框架注入。

第二种,BeanOutputConverter 手动挡
var converter = new BeanOutputConverter<>(ActorsFilms.class);
String format = converter.getFormat();
Prompt prompt = new PromptTemplate("帮我返回五个{actor}导演的电影名\n{format}")
.create(Map.of("actor", "周星驰", "format", format));
手动挡把格式指令(format)暴露出来,我们自己决定在提示词的什么位置插入。
getFormat() 返回的是一段 JSON Schema 描述文本,告诉大模型“按这个格式返回”。拿到响应后再手动调用 converter.convert(text) 完成反序列化。
两种方式是一个原理,在提示词里塞入格式约...
回复