介绍:你是否有过这样的经历(让AI提取联系人信息,它返回用户叫张三,邮箱是zhangsan@qq.com,电话是123456,下次再问,格式变成了"姓名:张三 | 邮箱:zhangsan@qq.com")
结构化输出正是解决这一痛点的利器。Spring AI Alibaba为ReactAgent提供了两种优雅的方式,让AI智能体输出可直接解析的JSON数据,应用程序可以像调用普通API一样消费AI能力。
步骤一:结构化输出
结构化输出允许Agent以特定的、可预测的格式返回数据。相比于解析自然语言响应,你可以直接获得JSON对象或POJO。
java
// 未使用结构化输出(返回自然语言)
"用户张三,邮箱zhangsan@example.com,电话(555) 123-4567"
// 使用结构化输出(返回JSON)
{"name": "张三", "email": "zhangsan@example.com", "phone": "(555) 123-4567"}
后者可以使用Jackson反序列化为Java对象,无缝接入业务逻辑。
步骤二:两种方式
Spring AI Alibaba提供了两种控制结构化输出的方式:
| 方式 | 说明 | 推荐度 |
|---|---|---|
| outputType(Class) | 传入Java类,框架自动转换为JSON Schema | ⭐⭐⭐⭐⭐ |
| outputSchema(String) | 手动提供JSON Schema字符串 | ⭐⭐⭐ |
[1]outputType
最简洁的方式,直接传入Java类,框架使用BeanOutputConverter自动处理一切:
java
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import org.springframework.ai.chat.messages.AssistantMessage;
// 1. 定义输出结构(标准POJO)
public static class ContactInfo {
private String name;
private String email;
private String phone;
// 必须提供getter/setter
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
}
// 2. 创建Agent并指定outputType
ReactAgent agent = ReactAgent.builder()
.name("contact_extractor")
.model(chatModel)
.outputType(ContactInfo.class) // 直接传入Class
.saver(new MemorySaver())
.build();
// 3. 调用并获取结构化结果
AssistantMessage result = agent.call(
"提取联系方式:张三,zhangsan@example.com,(555) 123-4567"
);
System.out.println(result.getText());
// 输出: {"name": "张三", "email": "zhangsan@example.com", "phone": "(555) 123-4567"}
方式二:outputSchema
当需要完全自定义输出格式时,可以手动提供JSON Schema:
java
import org.springframework.ai.converter.BeanOutputConverter;
// 定义POJO(同上ContactInfo)
BeanOutputConverter<ContactInfo> converter =
new BeanOutputConverter<>(ContactInfo.class);
String schema = converter.getFormat(); // 自动生成Schema
ReactAgent agent = ReactAgent.builder()
.name("contact_extractor")
.model(chatModel)
.outputSchema(schema) // 使用生成的Schema
.build();
步骤三:实战
案例1:产品评价分析
java
public static class ProductReview {
private int rating; // 评分 1-5
private String sentiment; // 情感倾向
private String[] keyPoints; // 关键点
private ReviewDetails details; // 嵌套详情
// getters/setters...
public static class ReviewDetails {
private String[] pros; // 优点
private String[] cons; // 缺点
// getters/setters...
}
}
ReactAgent agent = ReactAgent.builder()
.name("review_analyzer")
.model(chatModel)
.outputType(ProductReview.class)
.build();
AssistantMessage result = agent.call(
"分析评价:这个产品很棒,5星好评。配送快速,但价格稍贵。"
);
// 输出示例:
// {
// "rating": 5,
// "sentiment": "正面",
// "keyPoints": ["产品很棒", "配送快速", "价格稍贵"],
// "details": {
// "pros": ["产品品质", "配送速度"],
// "cons": ["价格"]
// }
// }
案例2:文本实体识别
java
public static class TextAnalysis {
private String summary; // 摘要
private String[] keywords; // 关键词
private String sentiment; // 情感
private Entities entities; // 实体识别
public static class Entities {
private String[] persons; // 人物
private String[] locations; // 地点
private String[] organizations; // 组织
// getters/setters...
}
}
ReactAgent agent = ReactAgent.builder()
.name("text_analyzer")
.model(chatModel)
.outputType(TextAnalysis.class)
.build();
AssistantMessage result = agent.call(
"分析:昨天,李明在北京参加了阿里巴巴公司的技术大会"
);
// 输出示例:
// {
// "summary": "李明在北京参加阿里巴巴技术大会",
// "keywords": ["李明", "北京", "阿里巴巴", "技术大会"],
// "sentiment": "正面",
// "entities": {
// "persons": ["李明"],
// "locations": ["北京"],
// "organizations": ["阿里巴巴"]
// }
// }
步骤四:原理解析
Spring AI Alibaba根据模型能力,自动选择最佳实现策略:
策略一:模型原生结构化输出
当使用DashScope、OpenAI等支持原生结构化输出的模型时,框架会自动启用模型的JSON模式:
- DashScope:自动设置response_format: {"type": "json_object"}
- OpenAI:使用API层面的JSON模式,严格保证格式正确
同时,框架会增强用户消息,追加格式指令:
java
// 框架自动在UserMessage末尾追加Schema指令
messages.set(i, userMessage.mutate()
.text(originalText + System.lineSeparator() + outputSchema)
.build());
策略二:ToolCall结构化输出
对于不支持原生结构化输出的模型,框架通过动态ToolCall来实现格式化------利用所有现代模型都支持的工具调用能力,构造一个"格式化工具"来规整输出。
优势:适用于大多数模型,兼容性强
步骤五:错误处理
模型可能不总是返回完美的JSON,以下三种策略帮你从容应对:
[1]Try-Catch模式
java
try {
AssistantMessage result = agent.call("提取数据");
ObjectMapper mapper = new ObjectMapper();
DataOutput data = mapper.readValue(result.getText(), DataOutput.class);
// 处理数据
} catch (JsonProcessingException e) {
System.err.println("JSON解析失败: " + e.getMessage());
System.err.println("原始输出: " + result.getText());
// 降级处理:使用默认值或重新请求
}
[2]验证模式
在POJO中添加校验逻辑,确保数据有效性:
java
public static class ValidatedOutput {
private String title;
private Integer rating;
public void validate() throws IllegalArgumentException {
if (title == null || title.isEmpty()) {
throw new IllegalArgumentException("标题不能为空");
}
if (rating != null && (rating < 1 || rating > 5)) {
throw new IllegalArgumentException("评分必须在1-5之间");
}
}
}
// 使用
ValidatedOutput output = mapper.readValue(result.getText(), ValidatedOutput.class);
output.validate(); // 自动校验
[3]重试模式
java
int maxRetries = 3;
DataOutput data = null;
for (int i = 0; i < maxRetries; i++) {
try {
AssistantMessage result = agent.call("提取数据");
data = mapper.readValue(result.getText(), DataOutput.class);
break;
} catch (Exception e) {
if (i == maxRetries - 1) throw new RuntimeException("多次失败", e);
System.out.println("第" + (i + 1) + "次失败,重试中...");
Thread.sleep(1000); // 等待后重试
}
}
步骤六:实践总结
| 实践 | 说明 |
|---|---|
| 优先使用outputType | 类型安全、代码简洁,框架自动处理Schema生成 |
| POJO设计要合理 | 字段命名清晰,类型明确(String、数组等) |
| 必须提供getter/setter | Jackson反序列化依赖标准Java Bean规范 |
| 添加校验逻辑 | 在POJO中实现validate()方法,防御性编程 |
| 使用模型原生能力 | DashScope/OpenAI等模型的JSON模式更稳定可靠 |
| 做好降级处理 | 即使结构化输出也有失败可能,始终准备Plan B |
加入技术群可以获取资料,含AI资料、Spring AI中文文档等,等你加入~