Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions agentscope-core/src/main/java/io/agentscope/core/ReActAgent.java
Original file line number Diff line number Diff line change
Expand Up @@ -1038,7 +1038,17 @@ private Mono<Msg> doStructuredCall(List<Msg> msgs, Class<?> targetClass, JsonNod
? model.supportsNativeStructuredOutputWithTools()
: model.supportsNativeStructuredOutput();
if (useNative) {
return doNativeStructuredCall(msgs, jsonSchema);
return doNativeStructuredCall(msgs, jsonSchema)
.onErrorResume(
e -> {
log.warn(
"Native structured output failed ({}) — falling back to"
+ " synthetic tool path",
e.getMessage() != null
? e.getMessage()
: e.getClass().getSimpleName());
return doFallbackStructuredCall(msgs, jsonSchema);
});
}
return doFallbackStructuredCall(msgs, jsonSchema);
}
Expand Down Expand Up @@ -1075,11 +1085,21 @@ private Mono<Msg> doNativeStructuredCall(List<Msg> msgs, Map<String, Object> jso
.strict(true)
.build());

int contextSizeBefore = scope.state.contextMutable().size();

return scope.doCallInner(msgs)
.flatMap(
result -> {
Msg out = wrapNativeStructuredResult(result);
return saveStateToSession(scope).thenReturn(out);
})
.doOnError(
e -> {
List<Msg> ctx = scope.state.contextMutable();
while (ctx.size() > contextSizeBefore) {
ctx.remove(ctx.size() - 1);
}
scope.nativeResponseFormat = null;
});
});
}
Expand Down Expand Up @@ -1912,7 +1932,7 @@ private Mono<Msg> reasoning(int iter, boolean ignoreMaxIters) {
event.getEffectiveGenerateOptions() != null
? event.getEffectiveGenerateOptions()
: buildGenerateOptions();
if (nativeResponseFormat != null) {
if (nativeResponseFormat != null && soTool == null) {
options =
GenerateOptions.mergeOptions(
GenerateOptions.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.agentscope.core.formatter.dashscope;

import io.agentscope.core.formatter.ResponseFormat;
import io.agentscope.core.formatter.dashscope.dto.DashScopeFunction;
import io.agentscope.core.formatter.dashscope.dto.DashScopeParameters;
import io.agentscope.core.formatter.dashscope.dto.DashScopeTool;
Expand Down Expand Up @@ -103,6 +104,12 @@ public void applyOptions(
if (parallelToolCalls != null) {
params.setParallelToolCalls(parallelToolCalls);
}

ResponseFormat responseFormat =
getOption(options, defaultOptions, GenerateOptions::getResponseFormat);
if (responseFormat != null) {
params.setResponseFormat(responseFormat);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public class DashScopeChatModel extends ChatModelBase {
private final boolean stream;
private final Boolean enableThinking; // nullable
private final Boolean enableSearch; // nullable
private Boolean nativeStructuredOutput; // nullable, set by Builder
private final EndpointType endpointType;
private final GenerateOptions defaultOptions;
private final Formatter<DashScopeMessage, DashScopeResponse, DashScopeRequest> formatter;
Expand Down Expand Up @@ -152,6 +153,7 @@ public DashScopeChatModel(
this.stream = enableThinking != null && enableThinking ? true : stream;
this.enableThinking = enableThinking;
this.enableSearch = enableSearch;
this.nativeStructuredOutput = null;
this.endpointType = endpointType != null ? endpointType : EndpointType.AUTO;
this.defaultOptions =
defaultOptions != null ? defaultOptions : GenerateOptions.builder().build();
Expand Down Expand Up @@ -384,7 +386,13 @@ public String getModelName() {

@Override
public boolean supportsNativeStructuredOutput() {
return true;
if (Boolean.TRUE.equals(enableThinking)) {
return false;
}
if (nativeStructuredOutput != null) {
return nativeStructuredOutput;
}
return false;
}

public static class Builder {
Expand All @@ -401,6 +409,7 @@ public static class Builder {
private boolean enableEncrypt = false;
private ProxyConfig proxyConfig;
private int contextWindowSize = -1;
private Boolean nativeStructuredOutput;
private Boolean nativeStructuredOutputWithTools;

/**
Expand Down Expand Up @@ -631,13 +640,33 @@ public Builder contextWindowSize(int contextWindowSize) {
return this;
}

/**
* Sets whether this model supports native structured output via {@code response_format}
* with {@code json_schema} type.
*
* <p>Defaults to {@code false}. DashScope's native endpoint only supports
* {@code json_object} (free-form JSON), not {@code json_schema} (strict schema
* validation). When {@code false}, the framework uses the {@code generate_response}
* tool fallback for structured output requests.
*
* <p>Set to {@code true} only if your model/endpoint is confirmed to support
* {@code json_schema} in {@code response_format}.
*
* @param nativeStructuredOutput true to enable native json_schema path
* @return this builder instance
*/
public Builder nativeStructuredOutput(boolean nativeStructuredOutput) {
this.nativeStructuredOutput = nativeStructuredOutput;
return this;
}

/**
* Sets whether this model correctly handles native structured output
* ({@code response_format}) alongside tool calling.
*
* <p>Defaults to {@code true}, which is correct for Qwen models on DashScope.
* Set to {@code false} for third-party models hosted on DashScope that
* prioritise {@code response_format} over tool invocations.
* <p>Defaults to {@code false} (inherits from
* {@link #nativeStructuredOutput(boolean)}). Set to {@code true} only for models
* that support both {@code response_format} and tool calling simultaneously.
*
* @param nativeStructuredOutputWithTools false to use fallback when tools are present
* @return this builder instance
Expand Down Expand Up @@ -699,6 +728,9 @@ public DashScopeChatModel build() {
contextWindowSize >= 0
? contextWindowSize
: ModelContextWindows.lookup(modelName, ModelContextWindows.DASHSCOPE));
if (nativeStructuredOutput != null) {
model.nativeStructuredOutput = nativeStructuredOutput;
}
if (nativeStructuredOutputWithTools != null) {
model.setNativeStructuredOutputWithTools(nativeStructuredOutputWithTools);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,12 +250,22 @@ protected ChatResponse parseCompletionResponse(OpenAIResponse response, Instant

Map<String, Object> argsMap = new HashMap<>();
if (!arguments.isEmpty()) {
@SuppressWarnings("unchecked")
Map<String, Object> parsed =
JsonUtils.getJsonCodec()
.fromJson(arguments, Map.class);
if (parsed != null) {
argsMap.putAll(parsed);
try {
@SuppressWarnings("unchecked")
Map<String, Object> parsed =
JsonUtils.getJsonCodec()
.fromJson(arguments, Map.class);
if (parsed != null) {
argsMap.putAll(parsed);
}
} catch (Exception parseEx) {
log.warn(
"Failed to parse tool call arguments as JSON;"
+ " preserving raw arguments: id={},"
+ " name={}, error={}",
toolCallId,
name,
parseEx.getMessage());
}
}

Expand Down
Loading