Skip to content

第 5 章:结构化输出,让 Agent 别把表格写成散文

前几章的 Agent 已经能聊、能用工具、能记住上下文。但真实业务里,我们经常不只要一段回答,还要稳定字段:意图、紧急程度、联系人、订单号、评分、分类结果。模型可以自由发挥,系统不能自由崩。

本章边界

这一章讲“如何让模型尽量按结构输出,以及 Java 侧如何解析、校验、兜底”。请先记住一句话:outputType(...) 不是魔法按钮,它不会保证 call(...) 直接返回 Java record。

1. 生活类比:别让厨师把菜单写成散文

你去餐厅点套餐,后厨不能回你一篇《我与番茄炒蛋的故事》。收银系统需要的是:菜名、数量、价格、备注。

结构化输出也是这个道理:

餐厅系统Agent 概念作用
点菜单模板outputType(...) / schema告诉模型应该有哪些字段
菜品备注prompt / instruction告诉模型缺失、异常时怎么处理
取餐口编号outputKey(...)告诉后续流程从哪里取结果
收银校验JSON 解析和字段校验防止错误格式进入业务系统

2. 最小示例:抽取联系人

java
public record ContactInfo(String name, String email, String phone) {}

ReactAgent contactAgent = ReactAgent.builder()
        .name("contact_agent")
        .model(chatModel)
        .instruction("从用户输入中抽取联系人信息。缺失字段用空字符串。")
        .outputType(ContactInfo.class)
        .outputKey("contact")
        .build();

AssistantMessage answer = contactAgent.call("张三,邮箱 zhangsan@example.com,电话 13800000000");
String json = answer.getText();

这里最容易误会的是最后两行。返回值仍然是 AssistantMessage,你拿到的通常还是文本。outputType(ContactInfo.class) 的价值,是帮助框架把格式要求告诉模型,让模型更可能输出 ContactInfo 形状的 JSON。

也就是说:

text
outputType 负责“提醒模型按格式答”
业务代码负责“解析、校验、兜底”

3. 结构化输出的真实闭环

生产里不要把结构化输出写成“一行配置”。它应该是一个小闭环:

示例解析可以这样写:

java
ContactInfo parseContact(AssistantMessage answer, ObjectMapper mapper) {
    try {
        ContactInfo contact = mapper.readValue(answer.getText(), ContactInfo.class);
        if (contact.email() == null || !contact.email().contains("@")) {
            throw new IllegalArgumentException("email invalid");
        }
        return contact;
    }
    catch (Exception ex) {
        throw new IllegalStateException("结构化输出解析失败,需要重试或转人工", ex);
    }
}

如果你的后续 Graph 节点要继续使用这个结果,可以用 outputKey(...) 把输出放到状态里。但要记住:状态里保存什么对象,取决于框架节点和你的解析逻辑,不要想当然地认为它已经是强类型业务对象。

4. 什么时候适合结构化输出

场景是否适合原因
意图分类适合结果集合比较稳定
联系人抽取适合字段清楚,便于校验
评分打标适合可以约束范围和格式
长篇解释不一定自然语言更灵活
创意写作不适合强约束结构会压掉表达空间

一句经验:需要进入数据库、流程分支、规则判断的结果,优先结构化;只是给人看的解释,可以保留自然语言。

5. 新手容易误解什么

误解一:outputType(...) 等于反序列化一定成功。

别把它当保险箱。它更像“答题纸模板”,模型仍然可能写歪。

误解二:所有输出都应该结构化。

不是。结构化输出适合抽取、分类、评分、路由;开放问答和解释型内容,不必硬塞进 JSON。

误解三:只要模型输出 JSON 就万事大吉。

JSON 语法正确不代表业务正确。邮箱格式、枚举范围、分数上下限、必填字段,都要校验。

6. 本章小结

结构化输出的工程心法:

text
先约束格式,再解析文本,再校验字段,最后决定重试还是兜底。

下一章我们给 Agent 加检查站和切面:Hooks 与 Interceptors。

7. 练习题

  1. 定义 ProductReviewSummary,包含 sentimentscorekeywords 三个字段,让 Agent 从评论里抽取。
  2. ContactInfo 写校验规则:邮箱必须包含 @,手机号为空时允许通过。
  3. 设计一个“解析失败后重试一次”的 prompt,要求模型只返回 JSON,不要解释。
课后源码索引:想验证实现时再打开
你想验证的结论源码锚点
官方结构化输出示例examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/StructuredOutputExample.java,类 StructuredOutputExample,方法 basicJsonSchema()complexNestedSchema()outputTypeContactInfo()outputTypeProductReview()comprehensiveExample()
模板渲染示例examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/AgentsExample.java,类 AgentsExample,方法 customTemplateRendererExample()
Builder 暴露的结构化配置spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/Builder.java,类 Builder,方法 outputType(...)outputSchema(...)outputKey(...)inputType(...)includeContents(...)
outputType 如何生成格式说明spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/DefaultBuilder.java,类 DefaultBuilder,方法 build()
schema 和模板如何进入模型请求spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/node/AgentLlmNode.java,类 AgentLlmNode,方法 augmentUserMessage(...)renderTemplatedUserMessage(...)apply(...)

Built with VitePress. Deployed on Cloudflare Pages.