乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • Spring AI 学习路径
  • 第1章 Spring AI 概述与核心理念

    • 章节导读
    • Spring AI 概述与可移植 API
    • 核心模块与依赖关系
  • 第2章 快速入门与第一个AI应用

    • 章节导读
    • 环境准备与配置
    • 第一个 AI 对话应用
  • 第3章 聊天模型与ChatClient

    • 章节导读
    • ChatClient 详解
    • ChatModel 底层抽象
    • 多轮对话与 ChatMemory
  • 第4章 提示词管理与模板

    • 章节导读
    • Prompt 与 Message 体系
    • 提示词模板与动态构建
  • 第5章 输出解析与结构化响应

    • 章节导读
    • 输出解析器与 BeanOutputConverter
  • 第6章 嵌入模型与向量存储

    • 章节导读
    • EmbeddingModel 与文本向量化
    • ETL 数据注入流水线
    • 向量存储抽象与配置
  • 第7章 检索增强生成(RAG)

    • 章节导读
    • RAG 核心机制与流程
    • QuestionAnswerAdvisor 详解
    • RAG 实战案例
  • 第8章 函数调用(Function Calling)

    • 章节导读
    • Tool 注解与函数声明
    • 函数调用实战
  • 第9章 多模态

    • 章节导读
    • 图像输入与视觉模型
    • 图像生成
  • 第10章 Advisor拦截器链

    • 章节导读
    • Advisor 链与内置拦截器
  • 第11章 MCP 协议与跨语言工具集成

    • 章节导读
    • MCP 协议深入
  • 第12章 可观测性测试与最佳实践

    • 章节导读
    • 可观测性与指标监控
    • 测试策略与 Mock 实践
    • 最佳实践与面试考点汇总

小崔写了一个 AI 成绩分析接口,AI 返回的一大段 Markdown 文本让他没法直接存数据库。白歌递过一杯咖啡:"试试 BeanOutputConverter,把你的 Record 类传给 AI,它自动生成 JSON Schema,返回直接映射。"

输出解析器与 BeanOutputConverter

定义与作用

输出解析器(Output Converter)是 Spring AI 的 结构化响应映射层。它做了三件事:

  1. 解析:从 AI 返回的文本中提取结构化数据(JSON / Map / List)
  2. 验证:确保输出符合目标类型的字段约束
  3. 映射:将解析结果转换为 Java 对象

Spring AI 提供三种内置解析器:

解析器输出类型适用场景
BeanOutputConverter<T>任意 POJO / Record按自定义 Bean 映射结构化输出
MapOutputConverterMap<String, Object>动态字段输出
ListOutputConverterList<T>列表类输出

核心原理:带输出解析器的完整调用流程

图释:BeanOutputConverter 在 Prompt 发送前自动注入 JSON Schema 到 SystemMessage 中(或通过 user() 方法追加),告诉 AI"请按此格式输出 JSON"。AI 返回 JSON 后,Converter 用 Jackson 反序列化到目标类型。

完整示例一:课程成绩分析(BeanOutputConverter)

场景说明

孔蓝要求 AI 分析学生成绩后返回结构化的分析报告,前端要直接用 JSON 渲染图表。

依赖引入与配置

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>

关键代码

// 步骤1:定义目标数据结构(Record 类)
public record CourseScore(
    @JsonProperty("course_name") String courseName,
    @JsonProperty("score") int score,
    @JsonProperty("rank") String rank   // A/B/C/D
) {}

public record StudentAnalysis(
    @JsonProperty("student_name") String studentName,
    @JsonProperty("overall_grade") String overallGrade,
    @JsonProperty("gpa") double gpa,
    @JsonProperty("courses") List<CourseScore> courses,
    @JsonProperty("strengths") List<String> strengths,
    @JsonProperty("suggestions") List<String> suggestions
) {}

// 步骤2:Controller 中使用 BeanOutputConverter
@RestController
public class ScoreAnalysisController {

    private final ChatClient chatClient;

    public ScoreAnalysisController(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    @GetMapping("/analyze-scores")
    public StudentAnalysis analyze(@RequestParam String studentName) {
        // 创建针对 StudentAnalysis 的转换器
        BeanOutputConverter<StudentAnalysis> converter = 
            new BeanOutputConverter<>(StudentAnalysis.class);

        // 调用 AI,converter 自动注入 Schema
        String responseContent = chatClient.prompt()
                .user(userSpec -> userSpec
                    .text("""
                        分析学生「%s」的以下成绩单,给出综合分析和选课建议:
                        
                        高等数学: 92, 线性代数: 85, 程序设计: 78, 
                        大学英语: 88, 离散数学: 72
                        """.formatted(studentName))
                    .text(converter.getFormat()))   // 注入 Schema 约束
                .call()
                .content();

        // 自动映射
        return converter.convert(responseContent);
    }
}

运行结果

GET /analyze-scores?studentName=大翔

{
  "student_name": "大翔",
  "overall_grade": "B+",
  "gpa": 3.3,
  "courses": [
    {"course_name": "高等数学", "score": 92, "rank": "A"},
    {"course_name": "线性代数", "score": 85, "rank": "B"},
    {"course_name": "程序设计", "score": 78, "rank": "C"},
    {"course_name": "大学英语", "score": 88, "rank": "B"},
    {"course_name": "离散数学", "score": 72, "rank": "C"}
  ],
  "strengths": ["数学基础扎实", "英语水平良好"],
  "suggestions": ["加强程序设计实践", "离散数学建议参加辅导班"]
}

完整示例二:智能客服多维度分析(MapOutputConverter)

场景说明

高英需要 AI 分析用户反馈,但反馈中可能出现的字段不固定。

关键代码

@RestController
public class FeedbackController {

    private final ChatClient chatClient;

    public FeedbackController(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    @GetMapping("/analyze-feedback")
    public Map<String, Object> analyze(@RequestParam String feedback) {
        MapOutputConverter converter = new MapOutputConverter();

        String result = chatClient.prompt()
                .user(userSpec -> userSpec
                    .text("分析以下用户反馈,提取关键信息:\n" + feedback)
                    .text(converter.getFormat()))
                .call()
                .content();
        
        return converter.convert(result);
    }
}

运行结果

GET /analyze-feedback?feedback=你们选课系统的搜索太慢了,而且每次都要重新登录,烦死了!

{
  "sentiment": "负面",
  "pain_points": ["搜索速度慢", "频繁重新登录"],
  "urgency": "高",
  "suggested_action": "优化搜索索引;增加 session 超时时间"
}

对比:手动解析 vs BeanOutputConverter

操作前:手动 JSON 解析

// 操作前:手写 Jackson 解析,需要 try-catch + 字段校验
String aiResponse = chatClient.prompt().user(message).call().content();
// AI 返回:"{\"sentiment\": 负面, ...}"  ← JSON 错误,缺少引号
try {
    ObjectMapper mapper = new ObjectMapper();
    FeedbackAnalysis analysis = mapper.readValue(aiResponse, FeedbackAnalysis.class);
    // 抛异常:Unrecognized token '负面'
} catch (JsonProcessingException e) {
    // 需要重试或手动修复 JSON
}

操作后:BeanOutputConverter 自动处理

// 操作后:Converter 注入 Schema,AI 输出规范 JSON
BeanOutputConverter<FeedbackAnalysis> converter = 
    new BeanOutputConverter<>(FeedbackAnalysis.class);
String aiResponse = chatClient.prompt()
    .user(userSpec -> userSpec.text(message).text(converter.getFormat()))
    .call().content();
FeedbackAnalysis analysis = converter.convert(aiResponse);
// 直接拿到类型安全的对象
维度手动解析BeanOutputConverter
Schema 约束无,AI 随意输出自动注入,AI 按 Schema 输出
异常处理try-catch + 重试Converter 内部处理
字段校验手动写 validate()Jackson 反序列化自动校验
代码量30+ 行5 行

易错场景与面试考点

易错场景一:目标类型字段名与 AI 输出不一致

// ❌ 错误:Java 字段名是 courseName,但 JSON 输出使用 course_name
public record CourseScore(String courseName, int score) {}
// AI 输出:{"course_name": "高等数学", "score": 92}
// BeanOutputConverter 转换失败

问题分析

AI 模型倾向于生成 snake_case 格式的 JSON 字段名(course_name),而 Java 习惯使用 camelCase(courseName)。

// ✅ 正确:使用 @JsonProperty 显式映射
public record CourseScore(
    @JsonProperty("course_name") String courseName,
    @JsonProperty("score") int score
) {}

易错场景二:泛型类型(List)解析失败

// ❌ 错误:直接 new BeanOutputConverter<>(List.class)
// 无法获取泛型参数,转换结果可能是 List<LinkedHashMap>
BeanOutputConverter<List<CourseScore>> converter = 
    new BeanOutputConverter<>(List.class);  // 编译警告

问题分析

Java 的类型擦除导致 List.class 丢失内部的 CourseScore 类型信息。

// ✅ 正确:使用 ParameterizedTypeReference
BeanOutputConverter<List<CourseScore>> converter = new BeanOutputConverter<>(
    new ParameterizedTypeReference<List<CourseScore>>() {});

面试高频题

Q1:BeanOutputConverter 是如何让 AI 输出合法 JSON 的?

通过 converter.getFormat() 生成目标类型的 JSON Schema 文本描述,注入到 UserMessage 或 SystemMessage 中,告诉 AI "请严格按照此 Schema 输出 JSON"。AI 模型配合度很高,因为 JSON Schema 是 LLM 训练数据中常见的结构化格式。

Q2:如果 AI 返回的 JSON 不合法(如多了一个逗号),如何处理?

BeanOutputConverter 内置了 JSON 修复逻辑——它会尝试从 AI 的 Markdown 代码块中提取 JSON,处理常见的格式问题(如多余逗号、缺少引号)。但如果 AI 完全没按 JSON 输出,则需要使用降级策略(如重试或使用更低的 temperature)。

本章小结

  • BeanOutputConverter 自动注入 JSON Schema,让 AI 输出结构化数据
  • 用 @JsonProperty 解决 Java 驼峰命名与 JSON snake_case 的不匹配
  • 泛型场景使用 ParameterizedTypeReference 保留类型信息
  • 对比手动 JSON 解析,Converter 减少 80% 代码量和异常处理成本
上一页
章节导读