【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());
}
相关推荐
QCodingDev2 天前
Spring AI Alibaba ReAct Agent实战:从Tool Calling到Agent,企业AI复杂业务该如何设计?
java·人工智能·agent·ai编程·spring ai
还是鼠鼠9 天前
Spring AI ChatClient详解:创建第一个AI客户端
java·springboot·spring ai·chatclient
还是鼠鼠10 天前
Spring AI连接DeepSeek与通义千问:application.yaml配置详解
java·通义千问·spring ai·deepseek
中间件XL10 天前
ai-agent框架spring ai/alibaba 原理源码分析(十)沙箱
沙箱·ai agent·智能体·spring ai·spring ai graph
还是鼠鼠12 天前
【Spring AI智能体实战 02】Spring Boot 3.4整合Spring AI:项目创建与依赖配置
java·人工智能·spring boot·maven·spring ai
还是鼠鼠13 天前
【Spring AI智能体实战 01】Java开发者接入大模型:国内平台、Token与API Key入门
java·大模型·spring ai·deepseek·阿里云百炼
默辨15 天前
Spring AI Alibaba 核心知识点
java·ai·spring ai·spring alibaba
Muscleheng19 天前
Spring Boot 3.x 集成 DeepSeek 实现 Function Calling(工具调用)
人工智能·spring boot·后端·ai·spring ai·deepseek
佛祖让我来巡山19 天前
SpringAI保姆级实战教程
spring ai·springai·springai教程·springai入门
BerryS3N19 天前
Java 后端转型大模型:Demo 能跑不等于能上线
java·人工智能·python·java后端·spring ai·langchain4j·大模型转型