AgentScope Java结构化输出新手教程

2026-06-12阅读 0热度 0
自然语言

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

构建生产级 Agent 时,一个绕不开的痛点:模型输出的自然语言虽然可读性高,但系统对接时不得不反复做文本解析、正则匹配、字段提取。这套流程不仅效率低下,而且极易引入解析错误。有经验的 Agent 开发者都清楚——真正的解决方案是让 Agent 从一开始就输出结构化数据,直接映射到代码中的 Java 对象。

4.1 为什么需要结构化输出

默认模式下,Agent 返回的是自然语言文本。然而在多数实际场景中,我们真正需要的是结构化数据: 【AgentScope Java新手村系列】(4)结构化输出 - 从文本中抽取关键信息(姓名、邮箱、联系方式) - 分类任务(情感极性判断、意图识别) - 数据生成(产品描述、测试数据集) AgentScope Java 提供了直接让 Agent 返回指定 Java 类型数据的能力。2.0 版本延续 1.x 的 @StructuredOutput JSON Schema 机制,并在 Msg 上新增 getStructuredData(Class) 读取入口。这套方案的核心价值——你只需定义好 Java 类,序列化与反序列化全由框架接管。

4.2 基本用法

**定义输出类型** 先看一个典型的输出类型定义,假设我们要从用户描述中提取产品需求: ```java public static class ProductRequirements { public String productType; public String brand; public Integer minRam; public Double maxBudget; public List features; public ProductRequirements() { } // 无参构造函数是强制要求 } ``` 几个要点: - 类必须提供无参构造函数,这是反序列化的基石 - 字段使用 public 修饰(也可提供 getter/setter,但 public 更简洁) - 支持基本类型、String、List、嵌套对象 - 2.0 推荐在字段上添加 com.fasterxml.jackson.annotation.JsonPropertyDescription,生成的 JSON Schema 描述更精准——别小看这一行注解,LLM 字段填充准确率能显著提升 **调用时指定类型** 定义好类之后,调用时只需将 Class 对象传入 agent.call(...): ```java 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); ``` 注意看,这里传入的是 ProductRequirements.class,Agent 内部会根据该类的结构自动生成 JSON Schema,进而约束 LLM 的输出格式,最后反序列化为你想要的 Java 对象。整个过程对开发人员几乎透明。

4.3 完整示例

下面给出一个可直接运行的完整示例,使用 DeepSeek 模型演示: ```java 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("=== 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 features; public ProductRequirements() { } } public static class SentimentAnalysis { public String sentiment; public Double score; public String summary; public SentimentAnalysis() { } } } ```

4.4 流式结构化输出

如果你需要更好的流式体验,可以使用 streamEvents() 方法拿到结构化的输出事件——这在响应式架构中尤其顺手: ```java 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 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 | array | | Map | object | | 嵌套对象 | object | | Java record | object(2.0 起官方推荐用 record,更简洁) | **4.5.1 用 record 简化定义** 如果使用 Java 17 开发,推荐直接用 record 定义输出类型——零样板代码,字段自带 getter: ```java public record ProductRequirements( @JsonPropertyDescription("产品类型,例如 laptop / phone / tablet") String productType, @JsonPropertyDescription("品牌,例如 Apple / Dell / Lenovo") String brand, @JsonPropertyDescription("最小内存,单位 GB") Integer minRam, @JsonPropertyDescription("最高预算,单位美元") Double maxBudget, @JsonPropertyDescription("用户提到的特性关键词列表") List features ) { } ``` 加上 @JsonPropertyDescription 之后,生成的 JSON Schema 描述会包含字段说明——LLM 填充时能理解每个字段的业务语义,准确率提升非常明显。 **4.5.2 嵌套对象示例** 遇到复杂数据结构也无需担心,直接嵌套对象即可: ```java 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 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、通义千问等均可正常使用。 如果误用了方式一,不支持的模型会抛出:"This response_format type is unavailable now",此时切换为本章示例的提示词驱动方式即可。

4.7 最佳实践

整理几条实际项目中积累的经验: - **字段名要有业务含义**:LLM 会根据字段名理解填写内容,例如 productTypetype 更明确 - **选择正确的类型**:数字用 Integer / Double,不要图省事全部用 String - **多值字段用 List**:如果字段可能有多个值,用 List 而非 String - **添加 @JsonPropertyDescription**:为每个字段写一句业务描述,这是成本最低的准确率提升手段 - **搭配系统提示词**:在 sysPrompt 中说明输出要求,能让 LLM 少做很多无谓的猜测 - **异常处理**:LLM 的输出不一定每次都符合预期,养成 try-catch 的习惯: ```java 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 自动拿到强类型数据: ```java // 主 agent 调用子 agent,子 agent 内部 call(..., Report.class, ctx) 返回 Report // 主 agent 拿到的 tool_result 是 Report 的 JSON 序列化 // 主 agent 的下一轮推理基于这份结构化结果继续 ``` 在 workspace/subagents/researcher.md 中,可以显式说明子 agent 的输出 schema(用自然语言描述即可),这样主 agent 就能稳定地消费子 agent 返回的结构化结果。这种协作模式在复杂的多层任务中尤其有效——每个子 agent 的产出都是强类型的,主 agent 无需再操心解析工作。
免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

相关阅读

更多
欢迎回来 登录或注册后,可保存提示词和历史记录
登录后可同步收藏、历史记录和常用模板
注册即表示同意服务条款与隐私政策