图片

让Java开发者像写Spring Boot一样开发AI应用——第二阶段第六篇

写在前面

前五天,我们给Agent装上了完整的记忆系统——短时记忆、长时记忆、用户画像,三层架构正式闭环。Agent不仅能“思考”、能“动手”,还能“记住你”。

但还有一个工程化的问题没有解决:Agent的“输出”不够规整。

你问Agent“帮我提取一下张三的联系方式”,它可能回答:

  • “姓名:张三,邮箱:zhangsan@example.com,电话:13800138000”

  • “联系人信息如下:张三,zhangsan@example.com,13800138000”

  • “张三 | zhangsan@example.com | 13800138000”

三种回答,三种格式。对人类来说都看得懂,但对程序来说,每种都要写一套解析逻辑。

这就像让员工随意填写报表——每个人写得都不一样,你根本没法用程序自动处理。

今天,我们给Agent发一张“标准表格”——结构化输出(Structured Output)。

📌 本文是第二阶段“核心能力实战篇”的第六篇。前面我们解决了Agent的“思考”“动手”“记忆”问题,今天解决“输出工程化”问题——让Agent的回复从“人类友好”升级为“机器友好”,真正融入业务系统。

一、为什么需要结构化输出?

自然语言输出的“三宗罪”

在Spring AI Alibaba中,默认情况下Agent的输出是纯文本的自然语言。这种输出对人类很友好,但对程序完全不友好。

问题一:格式不可靠

同样的信息,模型可能用完全不同的格式表达:

<span leaf="">可能的输出A:”姓名:张三,邮箱:zhangsan<span>@example</span>.<span>com</span>,电话:<span>13800138000</span>”</span>

问题二:字段缺失

模型可能遗漏必填字段,导致下游解析失败。

问题三:解析脆弱

微小格式变化(如多了一个空格、换行位置不同)都可能导致正则表达式或字符串解析失败。

💡 让大模型“自由发挥”就像让员工随意填写报表——每个人写得都不一样,你根本没法用程序自动处理。结构化输出就是给模型发了一张“标准表格”,必须按格填写。

结构化输出的核心价值

| 价值

|

说明

可预测性

强制模型遵循预定义的Schema,输出格式完全可控

| | 类型安全 |

直接映射到Java对象,编译期类型检查

| | 简化集成 |

无需后处理(正则、字符串解析),直接用于业务逻辑

| | 消除歧义 |

字段有明确的类型和含义,不会产生误解

|

💡 如果说前11天我们都在教Agent“怎么说”,那今天教的就是让Agent“说什么格式”——前者关乎内容,后者关乎工程。

适用场景

| 场景

|

说明

数据提取

从非结构化文本中提取结构化信息(联系方式、简历信息等)

| | 表单填充 |

生成符合表单Schema的JSON数据

| | API响应生成 |

作为下游微服务的标准输入

| | 实体识别 |

识别并结构化输出人物、地点、组织等实体

| | 路由决策 |

根据结构化输出做工作流路由

|

二、两种配置方式:outputType vs outputSchema

Spring AI Alibaba的ReactAgent.Builder通过outputSchema和outputType两个方法处理结构化输出:

| 方式

|

代码示例

|

优势

|

适用场景

outputType(Class) .outputType(ContactInfo.class)

类型安全、自动Schema生成、代码简洁

| 推荐!90%场景 | | outputSchema(String) | .outputSchema("{\"type\":\"object\",...}") |

完全控制Schema细节

|

需要自定义验证规则

|

方式一:outputType——类型安全,强烈推荐

核心思想:直接传入一个Java POJO类,框架通过BeanOutputConverter自动生成JSON Schema。

<span leaf=""><span>// 1. 定义POJO(标准Java Bean)</span></span>

outputType的优势:

| 优势

|

说明

✅ 类型安全

|

编译期类型校验,IDE自动补全

| |

✅ 零维护成本

|

修改POJO即自动更新Schema

| |

✅ 代码简洁

|

一行.outputType(Class)搞定

| |

✅ 自动推导

|

Spring AI用BeanOutputConverter自动生成JSON Schema

|

💡 outputType是推荐方式,适用于90%以上的业务场景。它让开发者只需要关心“定义什么数据结构”,而不需要关心“怎么写JSON Schema”。

方式二:outputSchema——完全控制

核心思想:手动编写JSON Schema字符串,告诉模型“必须按这个结构来”。

<span leaf=""><span>// 1. 手动定义JSON Schema</span></span>

outputSchema的优势与局限:

| 维度

|

说明

✅ 完全控制

|

可以精细控制每个字段的类型、约束、验证规则

| |

✅ 自定义验证

|

支持正则、枚举、范围等复杂验证

| |

❌ 维护成本高

|

Schema变更需要手动同步修改

| |

❌ 易出错

|

手写JSON Schema容易有语法错误

| |

❌ 无编译期检查

|

错误只能在运行时发现

|

💡 outputSchema就像“手写合同”——每个条款都精确控制,但写起来费劲、改起来麻烦。outputType就像“用标准模板”——省心省力,大部分场景都够用。

选型决策

<span leaf="">开始:我需要结构化输出</span>

💡 优先使用outputType。只有当需要自定义验证规则、字段约束等精细控制时,才考虑outputSchema。

三、BeanOutputConverter:幕后的“翻译官”

BeanOutputConverter是Spring AI提供的StructuredOutputConverter接口的一个实现,它使用JSON Schema将LLM输出转换为特定的对象类型。

工作原理

<span leaf="">① 生成<span>Schema</span>(调用前)</span>

核心接口

| 接口

|

方法

|

职责

FormatProvider getFormat()

生成JSON Schema格式指令,供模型遵循

| | Converter<String, T> | convert(String) |

将模型输出的文本转换为目标Java对象

|

复杂嵌套结构的支持

BeanOutputConverter完美支持嵌套对象、数组、复杂实体:

<span leaf=""><span>// 商品评价分析 - 嵌套结构</span></span>

四、结构化输出的完整调用链路

原生结构化输出 vs ToolCall回退

Spring AI Alibaba在指定了outputSchema或outputType后,会做一层“智能选择”:

| 模型类型

|

处理方式

|

特点

支持原生结构化输出的模型

(如DashScopeChatModel、OpenAiChatModel)

|

使用模型原生结构化输出能力

|

服务端做格式校验,非常稳定

| | 不支持原生结构化输出的模型 |

回退到Spring AI内置的ToolCall策略

|

通过动态ToolCall将输出“揉”成结构化数据

|

💡 就像有些手机支持无线充电(原生支持),有些不支持(需要插线)。Spring AI Alibaba会自动判断你的模型支不支持原生结构化输出,支持就用“无线充”,不支持就自动“插线”——开发者不需要关心底层差异。

完整调用链路

<span leaf="">用户:调用 agent.<span>call</span>(”从文本中提取联系方式”)</span>

五、实战:旅游攻略结构化输出

Step 1:定义旅游攻略POJO

<span leaf="">public&nbsp;<span><span>class</span></span><span>&nbsp;</span><span><span>TravelGuide</span></span><span>&nbsp;</span>{</span>

Step 2:构建Agent并配置outputType

<span leaf=""><span>@Configuration</span></span>

Step 3:Controller调用

<span leaf=""><span>@RestController</span></span>

六、异常处理与降级策略

结构化输出的风险

重要提醒:StructuredOutputConverter是尽最大努力将模型输出转换为结构化输出。AI模型不能保证按照要求返回结构化输出。

| 风险

|

表现

|

原因

格式不符合

返回纯文本而非JSON

|

模型不理解格式指令

| | 字段缺失 |

必填字段未出现

|

模型未正确提取信息

| | 类型错误 |

字符串字段返回数字

|

模型理解偏差

| | Markdown包裹 |

JSON被json ...包裹

|

模型习惯性添加代码块

| | JSON不合法 |

尾随逗号、控制字符

|

模型生成质量不佳

|

💡 结构化输出不是“银弹”——它大幅提高了可靠性,但并不能保证100%成功。就像你给员工发了一张标准表格,他可能会填错、漏填、甚至不填。我们需要有“容错机制”。

策略一:JSON清理与修复

<span leaf=""><span>public</span>&nbsp;<span>class</span>&nbsp;<span>JsonCleaner</span>&nbsp;{</span>

策略二:验证 + 重试

Spring AI提供了Schema验证和自校正机制。当验证失败时,第二次尝试不是盲目重试——模型知道哪里错了,可以进行修正。

<span leaf=""><span>@Service</span></span>

生产环境最佳实践

| 实践

|

说明

始终使用outputType

类型安全,自动生成Schema

| | 在Prompt中明确字段要求 |

在systemPrompt中说明每个字段的含义和约束

| | 实现验证机制 |

验证模型输出是否符合预期

| | 提供降级方案 |

解析失败时返回默认值或友好提示

|

七、结构化输出的最佳实践

POJO设计建议

  • 字段命名清晰、语义明确

  • 使用基础类型(String、int、double、boolean)和标准集合(List、Map)

  • 嵌套层级不超过3层,避免模型理解困难

  • 为每个字段提供合理的默认值,用于降级场景

outputType vs outputSchema 决策矩阵

| 场景

|

推荐方式

|

理由

标准业务DTO

| outputType |

类型安全,零维护成本

| |

需要自定义验证规则

|

outputSchema

|

精细控制字段约束

| |

数据结构频繁变更

| outputType |

修改POJO即可

| |

对接第三方API的固定格式

|

outputSchema

|

必须精确匹配第三方格式

| |

快速原型开发

| outputType |

代码最少,开发最快

| |

生产环境

| outputType |

编译期检查,更可靠

|

💡 除非有特殊需求,否则一律使用outputType。

八、课后挑战

任务:为第六天构建的“智能天气助手Agent”增加结构化输出能力

要求:

  1. 定义WeatherResponse POJO,包含以下字段:
  • city:城市名称

  • temperature:温度范围

  • condition:天气状况

  • humidity:湿度

  • wind:风力

  • joke:天气冷笑话

  • advice:出行建议

  1. 使用outputType(WeatherResponse.class)配置Agent

  2. 实现异常处理:当结构化输出解析失败时,返回默认的天气对象

  3. 提供REST API返回JSON格式的天气数据

验收标准:

  • WeatherResponse POJO定义完整,包含所有必填字段

  • Agent配置了outputType(WeatherResponse.class)

  • 调用Agent后能成功将JSON反序列化为WeatherResponse对象

  • 解析失败时有降级策略(返回默认值)

  • REST API返回的是JSON格式(而非纯文本)

九、本日核心收获

  1. 自然语言输出“三宗罪” ——格式不可靠、字段缺失、解析脆弱

  2. 两种配置方式——outputType(推荐,类型安全)和outputSchema(手写Schema,精细控制)

  3. BeanOutputConverter是幕后“翻译官” ——自动生成Schema、注入格式指令、转换JSON为Java对象

  4. 智能选择机制——支持原生结构化输出的模型用原生能力,不支持的自动回退到ToolCall策略

  5. 异常处理不可少——结构化输出不是100%可靠,需要清理、验证、重试、降级

  6. 除非有特殊需求,否则一律使用outputType

📌 本文是第二阶段“核心能力实战篇”的第六篇。前面我们解决了Agent的“思考”“动手”“记忆”问题,今天解决了“输出工程化”问题——让Agent的回复从“人类友好”升级为“机器友好”。至此,Agent的核心能力拼图(工具调用→RAG→记忆系统→结构化输出)已基本完整。下一篇,我们将进入多智能体协同的进阶实战,让多个Agent组成“组织”协同工作。

有任何问题,欢迎在评论区留言交流!


作者:Java老兵搞AI,专注Java生态下的AI应用开发

如果觉得有用,点个「关注」支持一下吧,下期见!