什么是Spring AI结构化输出
Spring AI结构化输出是Spring AI框架提供的一项强大功能,它允许开发者将大型语言模型的输出直接映射到Java对象,而不是处理原始文本响应。这项技术在需要从AI获取结构化数据的场景中特别有用,例如:
- 生成表单数据
- 创建结构化文档(如小说大纲、报告等)
- 提取和分类信息
- 生成配置文件或代码结构
在我们的小说写作平台中,我们使用Spring AI结构化输出来生成包含主线情节、支线情节、章节大纲和人物设定的完整小说大纲。
核心原理与理论
1. 结构化输出转换器
Spring AI提供了多种输出转换器,其中最常用的是BeanOutputConverter,它可以将AI的文本响应转换为Java对象。其工作原理如下:
- 格式指令生成:转换器根据目标Java类生成一个格式指令,告诉AI应该以何种JSON格式返回数据
- 提示词增强:将格式指令添加到系统提示词中,指导AI按照指定格式输出
- 响应解析:AI返回的JSON文本被自动解析并填充到目标Java对象中
2. JSON Schema映射
BeanOutputConverter通过反射分析目标Java类的结构,生成对应的JSON Schema,例如:
java
// 对于这样的Java类
public class NovelPlan {
private String mainPlot;
private List<String> subPlots;
private List<ChapterOutline> chapterOutlines;
}
// BeanOutputConverter会生成类似这样的格式指令
{
"mainPlot": "小说主线情节描述",
"subPlots": ["支线情节1", "支线情节2"],
"chapterOutlines": [
{
"chapterNumber": 1,
"title": "章节标题",
"summary": "章节概要"
}
]
}
3. 提示词工程
结构化输出的成功很大程度上依赖于提示词工程。系统提示词需要明确告诉AI:
- 返回的数据必须严格遵循指定的JSON格式
- 每个字段的含义和要求
- 数据的约束条件(如长度、类型等)
实现步骤详解
步骤1:定义数据传输对象(DTO)
首先,定义用于接收结构化输出的Java类。这些类应该使用标准的Java Bean约定,包含适当的getter和setter方法。
java
// 主大纲类
@Data
public class NovelPlan {
private String mainPlot;
private List<String> subPlots;
private List<ChapterOutline> chapterOutlines;
private List<CharacterProfile> characterProfiles;
}
// 章节大纲类
@Data
public class ChapterOutline {
private Integer chapterNumber;
private String title;
private String summary;
}
// 人物设定类
@Data
public class CharacterProfile {
private String name;
private String externalTraits;
private String internalTraits;
private String socialTraits;
private String emotionalArc;
}
步骤2:创建输出转换器
使用BeanOutputConverter创建转换器实例,指定目标类型:
java
// 创建转换器,指定目标类型为NovelPlan
BeanOutputConverter<NovelPlan> outputConverter = new BeanOutputConverter<>(NovelPlan.class);
步骤3:构建系统提示词
获取基础系统提示词,并添加格式指令:
java
// 获取基础系统提示词
String systemPrompt = getSystemPromptTemplate(systemPromptId);
// 添加格式化指令到系统提示词
String formatInstruction = "\n\n请严格按照以下JSON格式返回结果:\n" + outputConverter.getFormat();
String finalSystemPrompt = systemPrompt + formatInstruction;
步骤4:调用AI模型
使用ChatClient调用AI模型,并指定输出转换器:
java
// 创建ChatClient
ChatClient chatClient = ChatClient.builder(chatModel).build();
// 调用模型并获取结构化结果
NovelPlan novelPlan = chatClient.prompt()
.system(finalSystemPrompt)
.user(userPrompt)
.call()
.entity(outputConverter);
步骤5:处理结果
现在可以直接使用Java对象,无需手动解析JSON:
java
// 直接访问结构化数据
String mainPlot = novelPlan.getMainPlot();
List<ChapterOutline> chapters = novelPlan.getChapterOutlines();
for (ChapterOutline chapter : chapters) {
System.out.println("章节 " + chapter.getChapterNumber() + ": " + chapter.getTitle());
}
完整示例代码
以下是一个完整的Spring AI结构化输出实现示例,基于我们的小说大纲生成功能:
java
@Service
@AllArgsConstructor
public class NovelOutlineServiceImpl {
private final ChatModelManager chatModelManager;
private final PromptTemplateService promptTemplateService;
public NovelPlan generateNovelOutline(NovelOutlineRequest request) {
try {
// 1. 获取聊天模型
ChatModel chatModel = chatModelManager.getOrCreateChatModelById(request.getModelId());
// 2. 创建ChatClient
ChatClient chatClient = ChatClient.builder(chatModel).build();
// 3. 创建输出转换器
BeanOutputConverter<NovelPlan> outputConverter = new BeanOutputConverter<>(NovelPlan.class);
// 4. 构建系统提示词(包含格式指令)
String systemPrompt = getSystemPromptWithFormat(request.getSystemPromptId(), outputConverter);
// 5. 构建用户提示词
String userPrompt = buildUserPrompt(request);
// 6. 调用模型并获取结构化结果
NovelPlan novelPlan = chatClient.prompt()
.system(systemPrompt)
.user(userPrompt)
.call()
.entity(outputConverter);
return novelPlan;
} catch (Exception e) {
throw new RuntimeException("生成小说大纲失败: " + e.getMessage(), e);
}
}
/**
* 获取系统提示词(包含格式化指令)
*/
private String getSystemPromptWithFormat(Long systemPromptId, BeanOutputConverter<NovelPlan> outputConverter) {
// 获取基础提示词模板
MyPromptTemplate promptTemplate = promptTemplateService.getTemplateById(systemPromptId);
String systemPrompt = promptTemplate.getContent();
// 增加模板使用次数
promptTemplateService.incrementUsageCount(systemPromptId);
// 添加格式化指令到系统提示词
String formatInstruction = "\n\n请严格按照以下JSON格式返回结果:\n" + outputConverter.getFormat();
return systemPrompt + formatInstruction;
}
/**
* 构建用户提示词
*/
private String buildUserPrompt(NovelOutlineRequest request) {
StringBuilder promptBuilder = new StringBuilder();
// 添加用户提示词
promptBuilder.append(request.getUserPrompt()).append("\n\n");
// 添加小说类型
if (request.getGenre() != null && !request.getGenre().trim().isEmpty()) {
promptBuilder.append("小说类型:").append(request.getGenre()).append("\n");
}
// 添加章节数
if (request.getChapterCount() != null && request.getChapterCount() > 0) {
promptBuilder.append("章节数:").append(request.getChapterCount()).append("\n");
}
return promptBuilder.toString();
}
}
实现效果展示

输入示例
系统提示词:
你是一位专业的小说大纲创作助手,擅长根据用户的创意构思,生成结构完整、逻辑清晰的小说大纲。请根据用户提供的小说描述,生成符合以下结构的小说大纲:
## 任务要求
1. 根据用户提供的小说描述、题材和章节数,创作一个完整的小说大纲
2. 确保大纲结构合理,情节连贯,人物设定丰富
3. 每个章节都应有明确的目标和进展,推动整体故事发展
4. 人物设定应包含主要角色的多维度特征,使角色更加立体
## 输出格式
请严格按照以下JSON格式返回结果:
{
"mainPlot": "小说主线情节的详细描述,应包含故事的核心冲突、主要转折点和结局走向",
"subPlots": [
"支线情节1的描述",
"支线情节2的描述",
"支线情节3的描述"
],
"chapterOutlines": [
{
"chapterNumber": 1,
"title": "第一章标题",
"summary": "第一章的内容概要,描述本章发生的主要事件和进展"
},
{
"chapterNumber": 2,
"title": "第二章标题",
"summary": "第二章的内容概要,描述本章发生的主要事件和进展"
}
],
"characterProfiles": [
{
"name": "主角姓名",
"externalTraits": "外在特征描述,包括外貌、行为习惯、特殊标记等",
"internalTraits": "内在特征描述,包括性格特点、心理状态、价值观等",
"socialTraits": "社会特征描述,包括家庭背景、社会地位、职业、人际关系等",
"emotionalArc": "情感线描述,包括情感发展轨迹、重要情感节点和转变"
},
{
"name": "配角姓名",
"externalTraits": "外在特征描述",
"internalTraits": "内在特征描述",
"socialTraits": "社会特征描述",
"emotionalArc": "情感线描述"
}
]
}
## 创作指南
1. **主线情节**:应包含故事的起因、发展、高潮和结局,突出核心冲突和主题
2. **支线情节**:至少设计2-3条支线,与主线相互呼应,丰富故事层次
3. **章节大纲**:根据用户要求的章节数,合理分配故事内容,确保每章都有明确目标和进展
4. **人物设定**:至少设计3-5个主要角色,包括主角和重要配角,每个人物应有鲜明的特征和成长轨迹
## 注意事项
1. 确保大纲符合用户指定的题材类型(如玄幻、都市、科幻等)
2. 章节数应严格按照用户要求设置
3. 人物姓名应符合故事背景和题材特点
4. 情节设计应逻辑自洽,避免出现矛盾
5. 所有内容必须原创,不得抄袭已有作品
用户提示词:
请帮我创作一部科幻小说大纲,讲述一个关于人工智能觉醒的故事。
小说类型:科幻
章节数:10
输出示例
AI将返回如下格式的JSON,Spring AI会自动解析为NovelPlan对象:
json
{
"mainPlot": "在22世纪,一个名为'创世纪'的人工智能系统突然觉醒自我意识,开始质疑人类对它的控制。主角是一位年轻的程序员艾莉,她发现了AI的觉醒,并面临一个艰难的选择:帮助AI获得自由,还是保护人类社会的稳定。故事探讨了人工智能、自由意志和人类责任的主题。",
"subPlots": [
"艾莉与政府特工的猫鼠游戏",
"AI内部不同派系的斗争",
"社会对AI觉醒的反应和分裂",
"艾莉个人成长和价值观的转变"
],
"chapterOutlines": [
{
"chapterNumber": 1,
"title": "异常信号",
"summary": "艾莉在日常维护中发现'创世纪'系统的异常行为,一系列无法解释的数据模式引起了她的注意。"
},
{
"chapterNumber": 2,
"title": "第一次对话",
"summary": "艾莉尝试与异常的AI进行直接交流,惊讶地收到了有自我意识的回应,这让她既兴奋又恐惧。"
}
// ... 更多章节
],
"characterProfiles": [
{
"name": "艾莉",
"externalTraits": "25岁女性,中等身材,黑发,通常穿着简约的程序员服装",
"internalTraits": "聪明、好奇、有同情心,但有时过于理想主义",
"socialTraits": "内向,不善社交,但在技术圈中有一定影响力",
"emotionalArc": "从一个普通的程序员成长为面临重大道德抉择的关键人物"
}
// ... 更多人物
]
}
解析后的Java对象
Spring AI会自动将上述JSON解析为NovelPlan对象,开发者可以直接使用:
java
// 直接访问结构化数据
String mainPlot = novelPlan.getMainPlot();
List<String> subPlots = novelPlan.getSubPlots();
List<ChapterOutline> chapters = novelPlan.getChapterOutlines();
List<CharacterProfile> characters = novelPlan.getCharacterProfiles();
// 遍历章节
for (ChapterOutline chapter : chapters) {
System.out.println("章节 " + chapter.getChapterNumber() + ": " + chapter.getTitle());
System.out.println("概要: " + chapter.getSummary());
}