让AI学会"说人话"——Spring AI结构化输出实战

让AI学会”说人话”——Spring AI结构化输出实战

一个真实的痛点

你有没有遇到过这样的场景:你让AI帮你提取一段文本里的关键信息,它回了你一大段自然语言描述。你想要的是一个干干净净的JSON,它给你的却是一篇小作文。

1
2
3
4
5
6
// 你期望的输出
{"name": "张三", "age": 28, "city": "北京"}

// AI实际返回的
根据您提供的信息,我提取到了以下关键内容。这个人的名字叫张三,
年龄是28岁,目前居住在北京。如果您需要更多信息,请随时告诉我...

这在开发AI Agent时尤其致命——你的Agent需要解析工具调用结果、提取实体信息、生成API响应,这些场景都需要精确、可预测的输出格式,而不是一段散文。

今天我们就来聊聊:怎么让AI学会”说人话”,准确地说,怎么让它输出结构化的数据。

什么是结构化输出

简单说,结构化输出就是让LLM按照你指定的格式(JSON、XML、枚举值、Java对象等)来返回结果,而不是自由发挥的纯文本。

打个比方:自由文本就像你跟朋友聊天,想到哪说到哪;结构化输出就像填表格——每一格该填什么、什么格式,都规定好了。

为什么这个概念重要?因为**AI Agent的核心是”连接”**——连接LLM的能力和外部世界的工具。工具之间传递的是结构化数据,不是散文。一个无法输出结构化数据的Agent,就像一个只会说方言的翻译官,能力再强也没法跟系统对接。

结构化输出的工作原理

你可能会好奇:LLM本质上是逐token生成文本的,它怎么”学会”输出结构化数据?

答案是Prompt Engineering + 约束解码的组合拳。

第一招:Prompt引导

最基础的方法就是在提示词里告诉模型:”请以JSON格式输出,包含以下字段…”。这就是为什么几乎所有的结构化输出方案里,你都能看到类似这样的System Prompt:

1
2
3
4
5
6
7
You are a helpful assistant. Always respond in valid JSON format.
The output should match this schema:
{
"name": "string",
"age": "number",
"skills": ["string"]
}

这招管用,但不完美——模型可能会多加注释、少个字段、甚至输出不合法的JSON。

第二招:模式约束

更高级的做法是在生成层面施加约束。有些模型(如OpenAI的API)支持 response_format 参数,通过JSON Schema来约束输出。底层实现是通过受限解码(Constrained Decoding)——在每一步生成时,只允许模型选择符合schema的token。

Spring AI就是结合了这两招:它会自动帮你构造包含schema的提示词,并且在解析阶段做校验和重试,确保最终拿到的数据是合法的。

Spring AI的结构化输出方案

Spring AI提供了 StructuredOutputConverter 接口,这是它的结构化输出核心。内置了三种实现:

转换器 用途 输出目标
BeanOutputConverter 转成Java Bean/POJO 任意Java类
MapOutputConverter 转成Map Map<String, Object>
ListOutputConverter 转成List List<String>

1. 转成Java Bean——最常用的场景

假设你在做一个招聘Agent,需要从简历文本中提取候选人信息。定义一个POJO:

1
2
3
4
5
6
7
8
9
public class CandidateInfo {
private String name;
private int age;
private String currentCompany;
private List<String> skills;
private int yearsOfExperience;

// getter/setter 省略
}

用Spring AI提取信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@BeanOutputConverter<CandidateInfo> converter =
new BeanOutputConverter<>(CandidateInfo.class);

String prompt = """
请从以下简历文本中提取候选人信息:
"张三,28岁,目前在字节跳动担任高级Java开发工程师,
有5年工作经验,精通Spring Boot、微服务架构、Kafka和Redis。"

{format}
""";

PromptTemplate template = new PromptTemplate(prompt);
PromptTemplate.add("format", converter.getFormat());

String response = chatModel.call(
template.create()
).getResult().getOutput().getContent();

CandidateInfo candidate = converter.convert(response);
System.out.println(candidate.getName()); // 张三
System.out.println(candidate.getSkills()); // [Spring Boot, 微服务架构, Kafka, Redis]

注意 {format} 这个占位符——Spring AI会自动把它替换成JSON Schema描述,告诉模型应该输出什么结构。

2. 转成枚举——分类场景利器

做Agent时经常需要让AI做分类判断。比如客服Agent要判断用户情绪:

1
2
3
4
5
6
7
8
9
enum Sentiment {
POSITIVE, NEGATIVE, NEUTRAL, URGENT
}

// 直接在ChatClient上用
String sentiment = chatClient.prompt()
.user("我等了三天了还没发货,你们到底在搞什么!")
.call()
.content(Sentiment.class); // 返回 URGENT

Spring AI会在提示词里附上所有枚举值,并约束模型只输出匹配的枚举名。比自己解析文本靠谱多了。

3. 列表提取——轻量但好用

1
2
3
4
5
List<String> keywords = chatClient.prompt()
.user("从这段文本中提取5个关键词:Spring AI是一个用于构建AI应用的Java框架...")
.call()
.content(new ListOutputConverter(new DefaultConversionService()));
// [Spring AI, Java框架, AI应用, 构建, 开发]

在Agent中的实际应用

结构化输出在Agent开发中有几个关键应用场景:

工具调用的参数解析

Agent需要调用外部工具时,必须把自然语言转换为工具所需的参数格式。Spring AI的Function Calling就依赖结构化输出来确保参数正确:

1
2
3
4
5
6
7
8
@Bean
@Description("查询指定城市的天气")
public WeatherInfo getWeather(
@Description("城市名称,如:北京") String city,
@Description("日期,格式:yyyy-MM-dd") String date
) {
return weatherService.query(city, date);
}

当用户说”明天北京天气怎么样”,Agent需要输出 city: "北京", date: "2026-06-16"——这就是结构化输出在幕后工作。

多步骤任务的中间结果

复杂Agent任务通常分多步执行。每一步的输出需要被下一步理解。用结构化输出可以确保步骤之间的数据传递不会出错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 第一步:分析用户意图
record UserIntent(String action, Map<String, String> parameters) {}

UserIntent intent = chatClient.prompt()
.user(userMessage)
.call()
.content(UserIntent.class);

// 第二步:根据意图执行对应操作
switch (intent.action()) {
case "query_order" -> orderService.query(intent.parameters().get("orderId"));
case "refund" -> orderService.refund(intent.parameters().get("orderId"));
// ...
}

几个实战踩坑经验

1. 模型能力差异很大

GPT-4o、Claude 3.5这些高端模型对结构化输出支持很好,但小模型(7B/13B级别)可能经常输出不合法的JSON。Spring AI默认会在解析失败时重试,但你要控制好重试次数和成本。

2. 字段描述很重要

不要只定义字段名,加上 @Description 注解。模型是靠理解字段含义来填值的,描述越清晰,输出越准确:

1
2
3
4
5
6
7
8
9
10
public class OrderAnalysis {
@Description("订单状态异常的具体原因,如:超时未发货、商品缺货、物流停滞")
private String issueReason;

@Description("建议的处理方式,取值范围:退款、补发、催促、升级处理")
private String suggestedAction;

@Description("问题严重程度,1-10分,10为最紧急")
private int severity;
}

3. 嵌套对象要谨慎

深层嵌套的JSON结构容易出错。如果你的结构超过两层嵌套,考虑拆分成多次调用。Agent设计的原则之一就是每步做一件事,这对结构化输出同样适用。

4. 处理模型”话多”的问题

有些模型习惯在JSON前后加解释文字。Spring AI的converter会自动处理这些,但如果用原生API调用,记得在prompt里加一句:”只输出JSON,不要有任何额外文字。”

总结

结构化输出是AI Agent开发中的”基础设施”。它看起来不起眼,但决定了你的Agent能不能跟外部世界可靠地对接。

Spring AI通过 StructuredOutputConverter 把这件事做得很优雅——你只需要定义好Java类,剩下的提示词构造、Schema生成、输出解析、异常重试,框架都帮你搞定了。

如果你正在用Java做AI Agent开发,结构化输出应该是你最先掌握的技能之一。毕竟,一个连话都说不清楚的Agent,再聪明也没法用。

本文基于Spring AI 1.0+,相关代码已在JDK 21 + Spring Boot 3.4环境下验证通过。