结构化输出基础
约 631 字大约 2 分钟
布欧-Lewyon
2026-05-15
LLM 默认输出自由文本,但实际业务中往往需要结构化的数据(JSON 格式的 Java 对象)。Spring AI 提供了 StructuredOutputConverter 来解决这个问题。
BeanOutputConverter
将 AI 输出自动映射为 Java Bean:
// 1. 定义 Java Bean
public record Person(String name, int age, String occupation) {}
// 2. 使用 BeanOutputConverter
@GetMapping("/extract")
public Person extractPerson(@RequestParam String text) {
BeanOutputConverter<Person> converter = new BeanOutputConverter<>(Person.class);
// 生成格式化指令
String formatInstruction = converter.getFormatInstruction();
// "You must output in JSON format: {"name": "","age": 0,"occupation": ""}"
// 构造 Prompt
Prompt prompt = new Prompt("""
Extract person info from the following text:
Text: %s
%s
""".formatted(text, formatInstruction));
String response = chatClient.call(prompt)
.getResult().getOutput().getContent();
// 将 JSON 字符串转为 Java 对象
return converter.convert(response);
}简化的 Converter 调用
@GetMapping("/extract/v2")
public Person extractPersonV2(@RequestParam String text) {
BeanOutputConverter<Person> converter = new BeanOutputConverter<>(Person.class);
return new ChatClientBuilder(chatClient).build()
.call(new Prompt("""
Extract person info from: %s
%s
""".formatted(text, converter.getFormatInstruction())))
.getResult()
.getOutput()
.getContent();
}MapOutputConverter
@GetMapping("/keywords")
public Map<String, Object> extractKeywords(@RequestParam String text) {
MapOutputConverter converter = new MapOutputConverter();
String response = chatClient.call(new Prompt("""
Extract keywords and their sentiment from the following text.
Return a JSON object where keys are keywords and values are sentiment scores (0-1).
Text: %s
%s
""".formatted(text, converter.getFormatInstruction())));
return converter.convert(
response.getResult().getOutput().getContent()
);
}ListOutputConverter
@GetMapping("/todos")
public List<String> extractTodos(@RequestParam String text) {
ListOutputConverter converter = new ListOutputConverter();
String response = chatClient.call(new Prompt("""
Extract all TODO items from:
Text: %s
%s
""".formatted(text, converter.getFormatInstruction())));
return converter.convert(
response.getResult().getOutput().getContent()
);
}嵌套对象
public record Order(
String orderId,
Customer customer,
List<OrderItem> items,
BigDecimal totalAmount
) {}
public record Customer(String name, String email, String phone) {}
public record OrderItem(String productName, int quantity, BigDecimal price) {}
// 使用
@GetMapping("/order/extract")
public Order extractOrder(@RequestParam String text) {
BeanOutputConverter<Order> converter = new BeanOutputConverter<>(Order.class);
String json = chatClient.call(new Prompt("""
Extract order information from the text.
Text: %s
%s
""".formatted(text, converter.getFormatInstruction())));
return converter.convert(json);
}OutputParser 接口
自定义 OutputParser 实现更灵活的输出处理:
public class MarkdownTableParser implements OutputParser<List<List<String>>> {
@Override
public List<List<String>> parse(String text) {
// 解析 Markdown 表格
return text.lines()
.filter(line -> line.startsWith("|"))
.skip(1) // 跳过表头
.map(line -> Arrays.stream(line.split("\\|"))
.map(String::trim)
.filter(s -> !s.isEmpty())
.collect(Collectors.toList()))
.collect(Collectors.toList());
}
@Override
public String getFormatInstruction() {
return """
Output in Markdown table format:
| Column1 | Column2 | Column3 |
|---------|---------|---------|
| val1 | val2 | val3 |
""";
}
}错误处理
public <T> T safeConvert(BeanOutputConverter<T> converter, String aiOutput) {
try {
return converter.convert(aiOutput);
} catch (JsonProcessingException e) {
log.error("Failed to parse AI output: {}", aiOutput, e);
// 重试或返回默认值
return null;
}
}小结
BeanOutputConverter将 AI 的 JSON 输出映射为 Java Record/POJO。MapOutputConverter/ListOutputConverter处理简单集合类型。getFormatInstruction()生成格式化指令指导 AI 输出正确结构。- 支持嵌套对象和复杂泛型。
- 自定义
OutputParser可处理非 JSON 的输出格式(如 Markdown 表格)。 - 务必对 AI 输出做 try-catch 处理,防止 JSON 解析失败。
