diff --git a/mateclaw-server/src/main/java/vip/mate/agent/AgentGraphBuilder.java b/mateclaw-server/src/main/java/vip/mate/agent/AgentGraphBuilder.java index 1462e5d1..49f5c421 100644 --- a/mateclaw-server/src/main/java/vip/mate/agent/AgentGraphBuilder.java +++ b/mateclaw-server/src/main/java/vip/mate/agent/AgentGraphBuilder.java @@ -1058,6 +1058,7 @@ public class AgentGraphBuilder { chatRequest = sanitizeReasoningEffortForProvider(chatRequest, provider); chatRequest = patchReasoningContent(chatRequest, provider); chatRequest = stripReasoningEffortIfIncompatible(chatRequest); + chatRequest = stripAutoToolChoice(chatRequest); chatRequest = patchVideoMediaContent(chatRequest); if (kimiSearchEnabled) { chatRequest = injectKimiWebSearch(chatRequest); @@ -1078,6 +1079,7 @@ public class AgentGraphBuilder { chatRequest = sanitizeReasoningEffortForProvider(chatRequest, provider); chatRequest = patchReasoningContent(chatRequest, provider); chatRequest = stripReasoningEffortIfIncompatible(chatRequest); + chatRequest = stripAutoToolChoice(chatRequest); chatRequest = patchVideoMediaContent(chatRequest); if (kimiSearchEnabled) { chatRequest = injectKimiWebSearch(chatRequest); @@ -1857,6 +1859,65 @@ public class AgentGraphBuilder { return family.isThinking(); } + /** + * Strip {@code tool_choice="auto"} from outbound chat-completion requests. + * + *
Per the OpenAI spec, omitting {@code tool_choice} when {@code tools} is non-empty + * is functionally equivalent to {@code "auto"} (the server defaults to auto-pick). + * Stripping the explicit literal {@code "auto"}: + *
Explicit values other than {@code "auto"} ({@code "none"}, {@code "required"}, + * or a specific function descriptor) are passed through unchanged. + */ + private static OpenAiApi.ChatCompletionRequest stripAutoToolChoice(OpenAiApi.ChatCompletionRequest request) { + Object tc = request.toolChoice(); + if (tc == null || !"auto".equals(String.valueOf(tc))) { + return request; + } + return new OpenAiApi.ChatCompletionRequest( + request.messages(), + request.model(), + request.store(), + request.metadata(), + request.frequencyPenalty(), + request.logitBias(), + request.logprobs(), + request.topLogprobs(), + request.maxTokens(), + request.maxCompletionTokens(), + request.n(), + request.outputModalities(), + request.audioParameters(), + request.presencePenalty(), + request.responseFormat(), + request.seed(), + request.serviceTier(), + request.stop(), + request.stream(), + request.streamOptions(), + request.temperature(), + request.topP(), + request.tools(), + null, // toolChoice — strip "auto" so strict OpenAI-compatible servers accept the request + request.parallelToolCalls(), + request.user(), + request.reasoningEffort(), + request.webSearchOptions(), + request.verbosity(), + request.promptCacheKey(), + request.safetyIdentifier(), + request.extraBody() + ); + } + /** * 将 Spring AI 错误地序列化为 image_url 的视频内容块转换为 video_url 格式。 *