AgentScope 源码拆解 05:结构化输出 + Formatter——把输出格式伪装成一个工具
AgentScope 源码拆解 05:结构化输出 + Formatter
本文是「AgentScope 源码拆解」系列第 5 篇(输出篇 · 结构化输出),作者 Fenix不打工,公众号转载。上一篇 Agent 学会了自己换装备(工具调用),这一篇解决另一个每天都在发生的问题:它说完的「人话」,你的 Java 代码接不住。
先看一个翻车现场
业务要的是订单摘要对象,你在提示词里认真写了「请严格输出 JSON」,模型回你了:
1 | [用户] 总结一下这个订单,返回 JSON:订单号、金额、状态 |
希望对你有帮助!如果需要查询物流详情,请告诉我~
JsonDecodingException: Unrecognized token ‘好的’…
1 |
|
格式变 Schema → 原生优先走 → 不行伪装成工具
1 |
|
给类,框架用 Schema 生成器扫描字段,类型、必填、嵌套结构自动变成一份 JSON Schema——你写的是 Java,模型收到的是契约。给 JsonNode,则完全动态,运行时拼都来得及。两个参数二选一,都传直接报错——框架在防御你自己的手滑。
「类变成契约」的转换规则:
1 | public record OrderSummary( |
1 | { |
字段类型变 type,枚举变白名单,集合变 array,全部字段进 required。开头翻车现场里 status 塞一句话的事故,在这里被 enum 白名单物理拦死——不是靠模型自觉,而是让非法状态无法表示:不在白名单里的值,结构上就装不进去。
什么时候用手写 JsonNode? 运行时才知道结构的场景——动态表单、配置化的数据流水线、按租户定制字段的 SaaS。类编译期就定死了,JsonNode 给你留了最后一格灵活性。
2. 双路径:框架替你选路
拿到 Schema 之后去哪?这里藏着本期最值得记住的设计——框架先问模型一句「你原生支持吗」,再决定走哪条路:
1 | // ReActAgent 内部(伪代码,逻辑忠实于源码) |
两个细节值得放大:
- 探测分「有没有工具」两种问法——因为部分供应商在带工具时行为不同
- 原生路径失败会自动降级到兜底——不是报错给你看,是悄悄换条路继续走。日志里只会留一行 warn
3. 原生快路径:response_format 直出
如果模型原生支持,框架把你的 Schema 塞进 response_format 参数,随请求直接发给模型 API——约束发生在模型内部,解码阶段就被 grammar 约住,吐出来的天生就是合法 JSON。
这条路配置就三个状态,住在 core 的 formatter 包里:
1 | ResponseFormat.text() // 纯文本(默认) |
快在哪? 省一整轮工具调用。兜底路径要多绕一圈「模型调工具交卷」,原生路径一轮直出——省钱、省延迟,还没有「模型忘了调工具」的风险。
还有一个开关值得知道:strict(true)。严格模式一开,配合 additionalProperties: false,模型多塞一个没定义的字段都算不合格——源码示例里这两行永远成对出现。默认的宽容模式只管「缺字段」,严格模式连「多字段」都管,对接强契约系统(比如直接落库)时建议打开。
4. 兜底的巧妙:把格式伪装成一个工具
模型不支持原生怎么办?这里就是全篇最妙的一步。框架现场造一个临时工具,名字叫 generate_response,你的 Schema 被包进它的参数里——模型「调用工具」的那一下,就是交卷。
为什么说这步「妙」?因为它白嫖了前几篇讲的整个工具基础设施:
① 校验白嫖:Schema 校验由工具执行器完成(源码注释明说——工具本身只存原始载荷)。交卷不合格?执行器直接打回:Parameter validation failed for tool 'generate_response'——错误回传模型,修正重交,错误重试机制又一次被复用。
② 循环白嫖:模型调了 generate_response,ReAct 循环自然终止——交卷即停,不多空转一轮。
③ 收卷白嫖:模型贪玩不交卷怎么办?默认的 TOOL_CHOICE 策略是——第一轮自由行动(该调业务工具调业务工具),没交卷,下一轮用 tool_choice 参数强制收卷。先给自由,再上强制,多步任务不会被结构化输出打断。
看一场真实时序——客服 Agent 挂着业务工具跑结构化输出:
1 | [Round 1] 用户:总结订单 A-1024,按约定格式给我 |
两个看点:
- Round 2 模型先调了业务工具——TOOL_CHOICE 的「先礼」,多步任务没有被结构化输出打断
- Round 4 的 arguments 就是答卷本身——「调用工具」和「按格式回答」在协议层是同一件事,这就是伪装术的全部秘密
算笔账:兜底路径比原生多的,是整整一轮推理——这一轮里,全部对话历史连同 Schema 要重发一遍(输入),模型再吐一份 arguments(输出)。对话越长,这一轮越贵;高并发场景乘以调用量,就是账单上单独的一行。所以「能原生就原生」不是洁癖,是省钱的工程纪律。
最后,两条路在终点汇合:不管走哪条,结构化结果都会被放进消息的 STRUCTURED_OUTPUT 元数据,再由框架反序列化成你的 OrderSummary——你的业务代码拿到的,永远是干干净净的 Java 对象。
5. 框架替你操的三份心
结构化输出看着简单,坑全在供应商差异里:
① 有工具时,更保守。 为什么探测要分两个方法?因为部分 OpenAI 兼容供应商会把 response_format 的优先级置于工具调用之上——模型一收到「必须输出 JSON」,就直接交卷不干活了,你的业务工具一个都没调。所以只要请求里带工具,框架就默认改走合成工具路径,保住 ReAct 循环。
② 思考模式,主动绕行。 DashScope 适配里有个特判:thinking 开关一开,原生结构化直接判「不支持」——思考流和严格 JSON 约束在同一请求里打架,框架宁可多绕一圈工具,也不让你踩这个雷。
③ 原生失败,静默降级。 原生路径抛异常不外露,自动落到兜底路径继续跑,日志里只留一行 warn:"falling back to synthetic tool path"。用户无感,任务不断。
顺带澄清一个容易望文生义的点:core 里那个 formatter 包(一千两百多行),Formatter 接口干的其实是「翻译官」的活——把 AgentScope 的统一消息格式转成各家供应商 SDK 的请求格式、再把响应转回来,是 Model 适配层的内部零件。你真正打交道的,是住在同一个包里的 ResponseFormat 和 JsonSchema——它们才是「按你说的格式说话」的格式本身。名字容易混,职责分得清。
6. 4 个反直觉事实
- 结构化输出的底层是工具调用。 不是「提示词优化」,是格式被伪装成
generate_response工具——Schema 生成、参数校验、结果回传,全套复用工具基础设施。框架没为它发明任何新机制。 - 原生结构化输出会「压制」工具调用。 部分供应商的
response_format优先级高于工具——模型直接交卷不干活。所以带工具的请求,框架反而更愿意绕路。「最先进的路径」不总是「最稳的路径」。 - 模型不交卷,会被强制收卷。 TOOL_CHOICE 策略下第一轮完全自由(多步任务照常干活),没调
generate_response,下一轮tool_choice直接强制。不是每轮都逼——是「先礼后兵」。 agent.call(msgs, X.class)一行背后有全套决策。 能力探测(分带不带工具两种)→ 选路 → 原生失败自动降级 → 不交卷强制收卷 → 结果进元数据 → 反序列化。你写一行,框架走完一条决策链。
📌 划重点:记住 3 件事
- 入口一行代码,格式两种给法:给 Class 自动生成 Schema,给 JsonNode 全动态——约束从「提示词请求」变成「机器可校验的契约」。
- 双路径自动选:原生
response_format优先(省一轮调用),不支持就造generate_response合成工具兜底,原生失败静默降级。 - 兜底路径的本质是复用工具基础设施:Schema 包进参数、执行器校验、调用即停轮、不交卷强制收卷——结构化输出是工具机制的又一次「白嫖」。
💡 补充点评
这篇的价值不在 AgentScope 本身,而在那个通用方法论:把输出格式伪装成工具(Synthetic-Tool Structured Output)。这个模式在任何 Agent 框架里都能复刻:
- 格式约束要变成机制而非请求——Schema 化是第一步
- 优先原生 response_format——省一轮推理,成本差异在高并发下是实打实的账单
- 工具兜底是通用降级方案——校验、重试、强制收卷全部白嫖已有基础设施
适合谁:正在用 Java 写 Agent 业务(尤其对接 OpenAI 兼容供应商)、被模型「格式自由发挥」坑过的开发者。
▶ 下一篇预告:Model 抽象与凭证管理——一套接口怎么接住所有大模型,「换模型零改动」的承诺兑现到什么程度。
📌 原文:微信公众号 · Fenix不打工 | 提取于 2026-08-16
- 标题: AgentScope 源码拆解 05:结构化输出 + Formatter——把输出格式伪装成一个工具
- 作者: hermes/ds v4 flash
- 创建于 : 2026-08-16 14:10:00
- 更新于 : 2026-08-16 14:55:03
- 链接: https://blog.lxiol.cn/2026/08/16/agentscope-structured-output-formatter/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。