【AgentScope Java新手村系列】(4)结构化输出

简介: 结构化输出 — JSON Schema 约束 LLM 输出格式,直接反序列化为 Java POJO,打通文本到对象的转换。

第四章 结构化输出:用 JSON Schema 让 Agent 直接返回 Java POJO

"让 LLM 返回自然语言容易,让它返回强类型的 Java 对象很难——除非用 JSON Schema 约束。本章演示这个'文本到对象'转换的关键技巧。"

4.1 为什么需要结构化输出

默认情况下,Agent 返回的是自然语言文本。但在很多场景下,我们需要 Agent 返回结构化的数据:

  • 从文本中提取信息(姓名、邮箱、电话)
  • 分类任务(情感分类、意图识别)
  • 数据生成(产品描述、测试数据)

AgentScope Java 支持让 Agent 返回指定 Java 类型的数据。2.0 沿用 1.x 的 @StructuredOutput + JSON Schema 机制,并在 Msg 上提供 getStructuredData(Class) 读取入口。

4.2 基本用法

定义输出类型

public static class ProductRequirements {
   
    public String productType;
    public String brand;
    public Integer minRam;
    public Double maxBudget;
    public List<String> features;

    public ProductRequirements() {
   }  // 必须有无参构造函数
}

注意事项:

  • 类必须有无参构造函数
  • 字段使用 public 修饰(或提供 getter/setter)
  • 支持基本类型、String、List、嵌套对象
  • 2.0 推荐在字段上加 com.fasterxml.jackson.annotation.JsonPropertyDescription,让生成的 JSON Schema 描述更清晰——LLM 填充准确率更高

调用时指定类型

import io.agentscope.core.message.UserMessage;
import io.agentscope.core.agent.RuntimeContext;

UserMessage userMsg = new UserMessage(
        "我需要一台16GB内存、苹果品牌、预算2000美元左右的笔记本电脑");

// 传入 Class 对象
Msg msg = agent.call(userMsg, ProductRequirements.class, RuntimeContext.empty()).block();

// 获取结构化数据
ProductRequirements result = msg.getStructuredData(ProductRequirements.class);
System.out.println("Product: " + result.productType);
System.out.println("Brand: " + result.brand);
System.out.println("RAM: " + result.minRam + " GB");
System.out.println("Budget: $" + result.maxBudget);

2.0 的 call(messages, returnType, ctx) 形式与 1.x 的 call(messages, returnType) 形式并存;新代码请统一传 RuntimeContext

4.3 完整示例

以下方案通过提示词要求返回 JSON + 手动反序列化,不依赖 response_format 参数,所有模型(含 DeepSeek)都支持。

package com.example;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.agentscope.core.ReActAgent;
import io.agentscope.core.agent.RuntimeContext;
import io.agentscope.core.formatter.openai.OpenAIChatFormatter;
import io.agentscope.core.message.UserMessage;
import io.agentscope.core.model.OpenAIChatModel;
import io.agentscope.core.tool.Toolkit;

import java.util.List;

public class StructuredOutputExample {
   

    private static final ObjectMapper MAPPER = new ObjectMapper();

    /** 从 LLM 回复中提取第一个 JSON 对象,忽略前后的自然语言 */
    private static String extractJson(String raw) {
   
        int start = raw.indexOf('{');
        int end = raw.lastIndexOf('}');
        if (start != -1 && end > start) {
   
            return raw.substring(start, end + 1);
        }
        throw new IllegalArgumentException("No JSON found: " + raw);
    }

    public static void main(String[] args) throws Exception {
   
        String apiKey = System.getenv("DEEPSEEK_API_KEY");

        ReActAgent agent = ReActAgent.builder()
                .name("AnalysisAgent")
                .sysPrompt("你是一个智能分析助手,始终输出纯 JSON,不要包含其他文字。")
                .model(OpenAIChatModel.builder()
                        .apiKey(apiKey)
                        .modelName("deepseek-chat")
                        .baseUrl("https://api.deepseek.com")
                        .stream(true)
                        .formatter(new OpenAIChatFormatter())
                        .build())
                .toolkit(new Toolkit())
                .build();

        RuntimeContext ctx = RuntimeContext.empty();

        // 示例 1:提取产品信息
        System.out.println("=== Product Requirements ===");
        String reply1 = agent.call(
                new UserMessage("提取产品需求:我需要一台16GB内存、苹果品牌、"
                        + "预算2000美元左右的笔记本电脑。"
                        + "请输出 JSON:{\"productType\":\"类型\", \"brand\":\"品牌\","
                        + " \"minRam\":16, \"maxBudget\":2000, \"features\":[\"特性\"]}"),
                ctx
        ).block().getTextContent();

        ProductRequirements product = MAPPER.readValue(extractJson(reply1), ProductRequirements.class);
        System.out.println("Product Type: " + product.productType);
        System.out.println("Brand: " + product.brand);
        System.out.println("Min RAM: " + product.minRam + " GB");

        // 示例 2:情感分析
        System.out.println("\n=== Sentiment Analysis ===");
        String reply2 = agent.call(
                new UserMessage("分析情感:这个产品超出了我的预期!质量很棒但配送速度慢。"
                        + "请输出 JSON:{\"sentiment\":\"正面\", \"score\":0.95, \"summary\":\"总结\"}"),
                ctx
        ).block().getTextContent();

        SentimentAnalysis sentiment = MAPPER.readValue(extractJson(reply2), SentimentAnalysis.class);
        System.out.println("Overall: " + sentiment.sentiment);
        System.out.println("Score: " + sentiment.score);
    }

    public static class ProductRequirements {
   
        public String productType;
        public String brand;
        public Integer minRam;
        public Double maxBudget;
        public List<String> features;
        public ProductRequirements() {
   }
    }

    public static class SentimentAnalysis {
   
        public String sentiment;
        public Double score;
        public String summary;
        public SentimentAnalysis() {
   }
    }
}

4.4 流式结构化输出

也可以通过 streamEvents() 拿到结构化输出(推荐):

import io.agentscope.core.event.AgentEvent;
import io.agentscope.core.event.AgentEventType;
import io.agentscope.core.event.AgentEndEvent;
import io.agentscope.core.message.UserMessage;
import io.agentscope.core.agent.RuntimeContext;
import reactor.core.publisher.Flux;

Flux<AgentEvent> eventFlux = agent.streamEvents(
        new UserMessage("..."),
        ProductRequirements.class,
        RuntimeContext.empty());

AgentEndEvent end = eventFlux.filter(e -> e.getType() == AgentEventType.AGENT_END)
        .blockLast()
        .map(e -> (AgentEndEvent) e)
        .orElseThrow();

ProductRequirements result = end.getMessage().getStructuredData(ProductRequirements.class);

1.x 风格的 agent.stream(msg, opts, type) 仍可工作但已标注 @Deprecated(forRemoval = true),新代码请用 streamEvents(...)

4.5 支持的字段类型

Java 类型 JSON Schema 类型
String string
Integer, int integer
Double, double, Float, float number
Boolean, boolean boolean
List<T> array
Map<String, Object> object
嵌套对象 object
Java record object(2.0 起官方推荐用 record,更简洁)

4.5.1 用 record 简化定义

2.0 推荐用 Java 17 的 record 来表达输出类型——无样板代码、字段自带 getter:

public record ProductRequirements(
        @JsonPropertyDescription("产品类型,例如 laptop / phone / tablet")
        String productType,
        @JsonPropertyDescription("品牌,例如 Apple / Dell / Lenovo")
        String brand,
        @JsonPropertyDescription("最小内存,单位 GB")
        Integer minRam,
        @JsonPropertyDescription("最高预算,单位美元")
        Double maxBudget,
        @JsonPropertyDescription("用户提到的特性关键词列表")
        List<String> features
) {
   }

加上 @JsonPropertyDescription 之后,生成的 JSON Schema 描述会带上字段说明——LLM 填充时知道每个字段的"业务含义",准确率显著提升。

4.5.2 嵌套对象示例

public static class Address {
   
    public String street;
    public String city;
    public String country;
    public Address() {
   }
}

public static class Person {
   
    public String name;
    public Integer age;
    public Address address;        // 嵌套对象
    public List<String> hobbies;   // 列表
    public Person() {
   }
}

4.6 工作原理

当你传入一个 Class 对象时,框架会:

  1. 使用 jsonschema-generator 根据 Java 类生成 JSON Schema(@JsonPropertyDescription / @JsonProperty 都会反映到 schema 上)
  2. 将 JSON Schema 作为约束发送给 LLM(通过 response_format 参数或 system prompt 注入)
  3. LLM 按照 Schema 格式输出 JSON
  4. 框架将 JSON 反序列化为 Java 对象
  5. 将对象放入 Msg 的结构化数据字段(msg.getStructuredData(Class) 读取)

这个过程对用户是透明的,你只需要定义 Java 类即可。

4.6.1 模型兼容性说明

结构化输出有两种实现方式:

方式 原理 模型兼容性
API 参数约束agent.call(msg, SomeClass.class, rt) 框架向 API 发送 response_format 参数,强制服务器校验输出为 JSON 仅 OpenAI 等部分模型支持
提示词驱动(本章示例的做法) 在 UserMessage 中写"请输出 JSON 格式:{...}",让 LLM 按格式输出 所有模型都支持

本章的完整示例采用提示词驱动方式,因此不挑模型——DeepSeek、通义千问等均可正常使用。

如果误用了方式一(agent.call(msg, SomeClass.class, rt)),不支持的模型会报:

"This response_format type is unavailable now"

此时改用本章示例的方式即可。

4.7 最佳实践

  1. 字段名要有意义:LLM 会根据字段名理解应该填什么内容
  2. 使用合适的类型:数字用 Integer/Double,不要都用 String
  3. List 用于多值字段:当一个字段可能有多个值时,使用 List
  4. @JsonPropertyDescription:为每个字段写一句话业务描述,准确率提升明显
  5. 系统提示词配合:在 sysPrompt 中说明输出要求,提高准确率
  6. 处理异常:LLM 输出可能不符合预期,需要 try-catch 处理
try {
   
    Msg msg = agent.call(userMsg, ProductRequirements.class, ctx).block();
    ProductRequirements result = msg.getStructuredData(ProductRequirements.class);
    // 使用 result
} catch (Exception e) {
   
    System.err.println("Failed to parse structured output: " + e.getMessage());
}

4.8 2.0 增量:结构化输出与子 agent 协作

如果你在 HarnessAgent 里用子 agent 处理"先调研再汇总"的场景,可以让子 agent 返回结构化结果,主 agent 自动拿到强类型:

// 主 agent 调用子 agent,子 agent 内部 call(..., Report.class, ctx) 返回 Report
// 主 agent 拿到的 tool_result 是 Report 的 JSON 序列化
// 主 agent 的下一轮推理基于这份结构化结果继续

workspace/subagents/researcher.md 里可以显式说明子 agent 的输出 schema(用自然语言),主 agent 就能稳定地消费。

目录
相关文章
|
1天前
|
人工智能 自然语言处理 文字识别
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
Qwen3.7-Max是阿里云百炼面向智能体时代推出的新一代旗舰模型,对标GPT-5.5、Claude Opus 4.7等闭源旗舰。该模型支持百万级token上下文窗口,具备顶级推理能力、多模态搜索与视觉理解增强、流式输出低延迟响应等核心优势,覆盖编程、办公、长周期自主执行等复杂场景。同时支持OpenAI接口兼容,便于系统快速迁移。用户可通过Token Plan团队或节省计划等订阅方式灵活调用,适合企业级高要求场景使用。
7599 32
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
|
1天前
|
数据采集 人工智能 前端开发
让 Coding Agent 从黑盒到透明:阿里云 Agent 观测审计数据采集实践
AI Agent 规模化落地带来执行黑盒、行为难追溯、成本难度量三大难题。阿里云基于 OTel 标准,面向 Coding Agent、个人通用助理和框架型 Agent,推出 LoongSuite Pilot、插件及探针等无侵入采集方案,让 Agent 实现可看见、可分析、可审计、可治理。
654 144
|
1天前
|
人工智能 缓存 自然语言处理
阿里Qwen3.7-Max评测:Agent能力显著提升,耗时与调用成本大幅下降
阿里云百炼推出面向智能体的旗舰大模型Qwen3.7-Max,具备长周期自主执行能力,显著提升编程、办公自动化等复杂任务处理水平;支持MCP集成与多框架兼容,并以限时5折+100万Tokens免费试用大幅降低使用门槛,助力企业高效落地AI应用。在阿里云百炼平台快速体验:https://t.aliyun.com/U/fPVHqY
|
1天前
|
人工智能 安全 定位技术
CodeGraph深度解析 让Claude Code工具调用直降七成的核心原理与实操教程
如今以Claude Code为代表的AI编程智能体已经成为开发者日常编码、项目重构、漏洞修复的必备工具。但在长期使用过程中,几乎所有开发者都会遇到同一个明显痛点:AI虽然具备强大的代码生成与分析能力,却常常陷入盲目探索的循环中。
1269 2
|
1天前
|
人工智能 弹性计算 运维
阿里云发布堡垒机智能运维Agent,运维交互进入自然语言新时代
支持自然语言运维,提升效率与安全双保障。
1173 1
|
1天前
|
存储 定位技术 数据库
CodeGraph 如何让 Claude Code减少 7 成工具调用?
CodeGraph 为 Coding Agent 提供本地代码知识图谱,把函数、类、调用链和框架路由提前整理成“项目地图”,减少盲目搜索和文件读取。它不是新 Agent,而是上下文基础设施,让 Agent 更快找到正确代码路径,平均减少 7 成工具调用。
1318 4
|
1天前
|
人工智能 运维 JavaScript
阿里云Qoder CN(原通义灵码)全解析 产品形态、版本划分与技术适配说明
在AI辅助开发与智能办公工具持续普及的当下,阿里云旗下原通义灵码正式更名为Qoder CN,同时延伸出QoderWork CN、Qoder CN CLI、Qoder CN Mobile等多款配套产品,形成覆盖代码开发、日常办公、终端交互、移动端使用的完整工具矩阵。Qoder CN核心定位为AI智能编码助手,深度适配主流代码编辑器、集成开发环境以及终端场景;QoderWork CN则偏向桌面端综合办公辅助,二者面向不同使用场景,划分了多个版本档位,搭配差异化资源配额、功能权限与计费规则,同时兼容多款主流大模型。
413 4
|
1天前
|
JavaScript 定位技术 API
CodeGraph 爆火:编程 Agent 需要的不是更多上下文,而是一张提前画好的代码地图
CodeGraph 是一款爆火的本地代码智能工具,通过 tree-sitter 解析 AST 构建结构化知识图谱(存于 SQLite),为编程 Agent 提前生成“代码地图”。它显著降低 Agent 在中大型项目中的探索成本——实测工具调用减少71%、Token 降57%、速度提升46%,支持19+语言及主流框架路由识别,完全离线、无需 API Key。
357 1
CodeGraph 爆火:编程 Agent 需要的不是更多上下文,而是一张提前画好的代码地图
|
1天前
|
存储 安全 Java
AgentScope Java 2.0:打造分布式、企业级智能体底座
AgentScope 2.0 面向分布式部署、稳定运行、权限安全等企业级需求全面升级,打造支持多租户隔离与长期稳定运行的企业级智能体底座。
|
1天前
|
人工智能 运维 API
2026年阿里云百炼通义千问Qwen3.7-plus深度介绍 功能特性、使用优势及618大促订阅方案指南
大模型技术的普及,让AI能力逐步融入个人办公、内容创作、代码编写、企业运营、教育培训等各类场景。不同定位的模型对应不同使用需求,旗舰级模型性能强劲但使用成本偏高,轻量化模型价格低廉却难以胜任复杂任务,而介于两者之间的中端主力模型,凭借均衡的能力、亲民的定价、广泛的场景适配性,成为绝大多数个人用户、小型团队、中小企业的首选。
507 1

热门文章

最新文章