【SpringAI】10.结构化输出

什么是Spring AI结构化输出

Spring AI结构化输出是Spring AI框架提供的一项强大功能,它允许开发者将大型语言模型的输出直接映射到Java对象,而不是处理原始文本响应。这项技术在需要从AI获取结构化数据的场景中特别有用,例如:

  • 生成表单数据
  • 创建结构化文档(如小说大纲、报告等)
  • 提取和分类信息
  • 生成配置文件或代码结构

在我们的小说写作平台中,我们使用Spring AI结构化输出来生成包含主线情节、支线情节、章节大纲和人物设定的完整小说大纲。

核心原理与理论

1. 结构化输出转换器

Spring AI提供了多种输出转换器,其中最常用的是BeanOutputConverter,它可以将AI的文本响应转换为Java对象。其工作原理如下:

  1. 格式指令生成:转换器根据目标Java类生成一个格式指令,告诉AI应该以何种JSON格式返回数据
  2. 提示词增强:将格式指令添加到系统提示词中,指导AI按照指定格式输出
  3. 响应解析: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:

  1. 返回的数据必须严格遵循指定的JSON格式
  2. 每个字段的含义和要求
  3. 数据的约束条件(如长度、类型等)

实现步骤详解

步骤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());
}
相关推荐
腾飞开源2 天前
31_Spring AI 干货笔记之嵌入模型 Amazon Bedrock
人工智能·amazon bedrock·嵌入模型·spring ai·converse api·cohere嵌入·titan嵌入
腾飞开源3 天前
27_Spring AI 干货笔记之 OpenAI SDK 聊天功能(官方支持)
人工智能·多模态·工具调用·spring ai·openai sdk·github models·示例控制器
腾飞开源4 天前
23_Spring AI 干货笔记之 NVIDIA 聊天
人工智能·nvidia·spring ai·聊天模型·llm api·openai客户端·配置属性
大佐不会说日语~6 天前
SSE 流式输出 Markdown 实时渲染问题解决方案
java·vue.js·sse·spring ai·前端实时渲染
腾飞开源6 天前
17_Spring AI 干货笔记之谷歌生成式AI聊天
人工智能·多模态·工具调用·gemini·spring ai·google genai·思维配置
腾飞开源8 天前
14_Spring AI 干货笔记之 Azure OpenAI 对话模型
人工智能·向量存储·对话模型·工具调用·spring ai·azure openai·ai模型集成
GEM的左耳返9 天前
Java面试实战:从Spring Boot到AI集成的技术深度挑战
spring boot·redis·微服务·kafka·java面试·spring ai·缓存优化
腾飞开源10 天前
09_Spring AI 干货笔记之多模态
图像处理·人工智能·spring ai·多模态大语言模型·多模态api·媒体输入·文本响应
腾飞开源10 天前
10_Spring AI 干货笔记之 Spring AI API
人工智能·工具调用·spring ai·多模态ai·流式api·ai模型api·etl框架
腾飞开源11 天前
08_Spring AI 干货笔记之结构化输出
人工智能·spring ai·数据类型转换·结构化输出·ai模型集成·输出转换器·json模式