【SpringAI】10.结构化输出
什么是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类
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方法。
// 主大纲类
@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创建转换器实例,指定目标类型:
// 创建转换器,指定目标类型为NovelPlan
BeanOutputConverter<NovelPlan> outputConverter = new BeanOutputConverter<>(NovelPlan.class);
步骤3:构建系统提示词
获取基础系统提示词,并添加格式指令:
// 获取基础系统提示词
String systemPrompt = getSystemPromptTemplate(systemPromptId);// 添加格式化指令到系统提示词
String formatInstruction = "\n\n请严格按照以下JSON格式返回结果:\n" + outputConverter.getFormat();
String finalSystemPrompt = systemPrompt + formatInstruction;
步骤4:调用AI模型
使用ChatClient调用AI模型,并指定输出转换器:
// 创建ChatClient
ChatClient chatClient = ChatClient.builder(chatModel).build();// 调用模型并获取结构化结果
NovelPlan novelPlan = chatClient.prompt().system(finalSystemPrompt).user(userPrompt).call().entity(outputConverter);
步骤5:处理结果
现在可以直接使用Java对象,无需手动解析JSON:
// 直接访问结构化数据
String mainPlot = novelPlan.getMainPlot();
List<ChapterOutline> chapters = novelPlan.getChapterOutlines();
for (ChapterOutline chapter : chapters) {System.out.println("章节 " + chapter.getChapterNumber() + ": " + chapter.getTitle());
}
完整示例代码
以下是一个完整的Spring AI结构化输出实现示例,基于我们的小说大纲生成功能:
@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. 创建ChatClientChatClient 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对象:
{"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对象,开发者可以直接使用:
// 直接访问结构化数据
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());
}
