agentscope-ai / agentscope-ai/agentscope-java

[Bug]:DashScope 结构化输出失效:native 路径从不下发 response_format,导致 hasStructuredData() 恒为 false

Aberta
#1,852 0 comentários 0 reações 0 responsáveis Ver no GitHub
area/core/model bug
Linguagem predominante
Java
Estrelas
5.6k
Forks
1.3k
Merge médio
4d 12h
PRs com merge (30d)
77

Descrição

## 标题
DashScope 结构化输出失效:native 路径从不下发 response_format,导致 hasStructuredData() 恒为 false

## 版本 / 环境
- agentscope-java:2.0.0-RC3(tag v2.0.0-RC3)
- 模型:DashScope 通道(DashScopeChatModel),如 qwen3.6-flash / qwen-max
- 触发入口:ReActAgent.call(msgs, MyClass.class, ctx)(结构化输出)

## 问题简述
对 DashScopeChatModel 使用结构化输出(call(msgs, Class, ...) 或 JsonNode schema 重载)时,
返回的 Msg.hasStructuredData() 始终为 false,getStructuredData(...) 也取不到 STRUCTURED_OUTPUT
元数据。同样的代码换成 OpenAIChatModel 工作正常。

## 期望表现
最终 Msg 的 metadata 携带解析后的结构化对象,hasStructuredData() 返回 true。

## 实际表现
hasStructuredData() 恒为 false。每次调用打一条 WARN:
`Failed to parse native structured output as JSON: ...`
(ReActAgent.java:1102,来自 wrapNativeStructuredResult)。

## 根因分析
DashScopeChatModel.supportsNativeStructuredOutput() 返回 true
(model/DashScopeChatModel.java:361),因此 ReActAgent 走 **native 结构化路径**
(doStructuredCall → doNativeStructuredCall,命中 ReActAgent.java:982),
并在每轮 reasoning 把 ResponseFormat.jsonSchema(...) 合进 GenerateOptions.responseFormat。

但 DashScope 的请求构建链 **从未读取 GenerateOptions.getResponseFormat()**:
- DashScopeChatFormatter.applyOptions(...) 仅委托给 toolsHelper.applyOptions(...)
(formatter/dashscope/DashScopeChatFormatter.java:76、83)。
- DashScopeToolsHelper.applyOptions(...) 只映射 temperature / topP / maxTokens / thinkingBudget /
topK / seed / frequencyPenalty / presencePenalty / parallelToolCalls,
**唯独不映射 responseFormat**(formatter/dashscope/DashScopeToolsHelper.java:54-106)。

后果:DashScopeParameters 里的 response_format 字段
(dto/DashScopeParameters.java:111-112,getter/setter/builder 见 268、272、378-379 都齐全)始终为 null,
发往 DashScope 的请求体里没有 response_format。于是模型从未被要求输出 JSON,最终回复是自然语言文本;
wrapNativeStructuredResult 对该文本执行 JsonUtils.fromJson(text, Object.class)(ReActAgent.java:1090)抛异常,
被 catch 后只打 WARN(:1102)、原样返回 result,不写入 STRUCTURED_OUTPUT 元数据。
最终 hasStructuredData() 恒为 false。

对照:OpenAI 通道在 OpenAIChatFormatter 里有
request.setResponseFormat(getResponseFormat(...))(formatter/openai/OpenAIChatFormatter.java:124、126),
所以同样的结构化调用在 OpenAIChatModel 下正常。这是 DashScope 通道独有的缺陷——
声明了 supportsNativeStructuredOutput()=true,但 formatter 把 response_format 丢了。

## 复现步骤
1. 用 DashScopeChatModel(如 qwen3.6-flash)构建 ReActAgent。
2. 定义任意结构化类 Foo,调用 agent.call(List.of(new UserMessage("...")), Foo.class, ctx).block()。
3. 观察返回 Msg:hasStructuredData() == false;日志出现
"Failed to parse native structured output as JSON"。

## 影响范围
所有依赖 DashScope 通道 + 结构化输出(Class / JsonNode 两个重载)的调用全部拿不到结构化结果。

## 修复建议(任选)
1. 在 DashScopeToolsHelper.applyOptions(...) 补上
params.setResponseFormat(getOption(options, defaultOptions, GenerateOptions::getResponseFormat)),
让 response_format 真正下发。**但需先确认 DashScope 原生端点对目标模型是否支持 strict json_schema**
(历史上 qwen 原生端点只支持 {"type":"json_object"}),否则可能下发无效甚至报错。
2. 若 DashScope 原生协议不完整支持 json_schema,则
DashScopeChatModel.supportsNativeStructuredOutput() 改为返回 false,
让 ReActAgent 自动走 generate_response 合成工具的 fallback 路径(依赖 tool-calling,qwen 支持良好)。
3. 退一步,至少让 wrapNativeStructuredResult 在解析失败时把信息透出得更明显
(现在仅一条 WARN,调用方难以区分是“模型没按格式”还是“通道根本没发 response_format”)。

## 附注
DashScopeParameters 早已预留 response_format 字段,但 RC2 → RC3 始终没有接线,
说明这是一处未完成的实现,而非回归。

Guia de contribuição

Abrir o guia de contribuição

Avaliação

Esta issue ainda não foi avaliada.

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.