Downgrade forced tool choice for OpenRouter Qwen
ober
c9c43f1f297082e1b09ae1f6fb5606c48e138fab
--- a/src/jcode/provider/provider.ss +++ b/src/jcode/provider/provider.ss @@ -1566,6 +1566,20 @@ (openai-extended-cache-model? (provider-model provider))) (hash-put! body "prompt_cache_retention" "24h")))) +(def (openrouter-qwen-model? provider) + (and (equal? (provider-name provider) "openrouter") + (string? (provider-model provider)) + (or (string-prefix? "qwen/" (provider-model provider)) + (string-contains (provider-model provider) "/qwen")))) + +(def (openai-tool-choice provider) + ;; Alibaba-backed Qwen on OpenRouter rejects object/required tool_choice in + ;; thinking mode. Keep tools advertised, but let the model choose tools. + (let ((override (current-tool-choice-override))) + (if (and override (openrouter-qwen-model? provider)) + "auto" + (or override "auto")))) + (def (openai-cached-tokens usage) ;; OpenAI-compat servers (openai, openrouter, vLLM, llama-server) report ;; prefix-cache hits in prompt_tokens_details.cached_tokens. Absent on @@ -1588,8 +1602,7 @@ (when (and tools (not (null? tools)) (not (model-rejects-tools? (provider-model provider)))) (hash-put! body "tools" tools) - (hash-put! body "tool_choice" - (or (current-tool-choice-override) "auto"))) + (hash-put! body "tool_choice" (openai-tool-choice provider))) body)) (def (moonshot-kimi-k3? provider) @@ -2331,8 +2344,7 @@ (when (and tools (not (null? tools)) (not (model-rejects-tools? (provider-model provider)))) (hash-put! body "tools" tools) - (hash-put! body "tool_choice" - (or (current-tool-choice-override) "auto"))) + (hash-put! body "tool_choice" (openai-tool-choice provider))) body)) (def (openai-stream-chat provider messages tools token-cb) --- a/test/run.ss +++ b/test/run.ss @@ -12294,6 +12294,38 @@ [captured (vector #f)] [tool (make-hashtable equal-hash equal?)] [fn (make-hashtable equal-hash equal?)] + [params (make-hashtable equal-hash equal?)]) + (hashtable-set! params "type" "object") + (hashtable-set! fn "name" "edit") + (hashtable-set! fn "description" "edit") + (hashtable-set! fn "parameters" params) + (hashtable-set! tool "type" "function") + (hashtable-set! tool "function" fn) + (dynamic-wind + (lambda () (void)) + (lambda () + (serve-one-captured-json! srv captured 200 chat-body) + (let* ([p (make-provider "openrouter" "unit-key" + "qwen/qwen3.7-plus" base-url)] + [_ (parameterize ([current-tool-choice-override + (openai-function-tool-choice "edit")]) + (provider-chat p (list (make-user-message "hi")) (list tool)))] + [req (vector-ref captured 0)] + [req-json (and req (string->json-object req))] + [tool-choice (and req-json + (hashtable-ref req-json "tool_choice" #f))]) + (check! "openrouter qwen forced tool choice downgrades to auto" + tool-choice "auto") + (check! "openrouter qwen forced tool choice still advertises tools" + (and req (str-contains? req "\"tools\"")) #t))) + (lambda () (tcp-close srv)))) + +(let* ([chat-body "{\"choices\":[{\"message\":{\"role\":\"assistant\",\"content\":\"ok\"},\"finish_reason\":\"stop\"}]}\n"] + [srv (tcp-listen "127.0.0.1" 0)] + [base-url (format "http://127.0.0.1:~a/v1" (tcp-server-port srv))] + [captured (vector #f)] + [tool (make-hashtable equal-hash equal?)] + [fn (make-hashtable equal-hash equal?)] [params (make-hashtable equal-hash equal?)] [old-max-tokens (getenv "JCODE_MAX_TOKENS")]) (hashtable-set! params "type" "object")