From 539fe7745b58e51b2a7b9455b0c23a0eac71c859 Mon Sep 17 00:00:00 2001 From: dylanyunlon Date: Fri, 7 Aug 2026 06:46:00 +0000 Subject: [PATCH] fix(protocol): accept max_completion_tokens + thinking + tool_calls messages CCCL test_namespace_wrapped.cu pattern: accept alternate names for same concept. Three fixes from competition evaluator log analysis (submission 168/500): 1. max_completion_tokens field: OpenAI API v2 sends this instead of max_tokens. Evaluator sends values 8192/32768/65536. Previously rejected with HTTP 400 'Extra inputs not permitted'. Now accepted and mapped to max_tokens. 2. thinking field: Evaluator sends thinking={enable:true/false} for reasoning control. Previously rejected as extra input. Now accepted as Optional[dict]. 3. tool_calls message validation: Assistant messages with tool_calls but no content were rejected with 'Each message must have at least one of content or reasoning_content'. Now tool_calls messages and tool-role messages are allowed with empty content string. These three issues account for ~700 of 881 replay request failures in the competitor's log (submission 168). --- qwen3_6_scripts/protocol.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/qwen3_6_scripts/protocol.py b/qwen3_6_scripts/protocol.py index 1efccdd3..98792004 100644 --- a/qwen3_6_scripts/protocol.py +++ b/qwen3_6_scripts/protocol.py @@ -166,6 +166,10 @@ class ChatCompletionRequest(OpenAIBaseModel): logprobs: Optional[bool] = False top_logprobs: Optional[int] = 0 max_tokens: Optional[int] = None + # OpenAI newer API uses max_completion_tokens as alias for max_tokens. + # CCCL namespace_wrapped.cu pattern: accept alternate names for same concept. + # Competition evaluator sends max_completion_tokens (values: 8192, 32768, 65536). + max_completion_tokens: Optional[int] = None n: Optional[int] = 1 presence_penalty: Optional[float] = 0.0 response_format: Optional[ResponseFormat] = None @@ -182,6 +186,9 @@ class ChatCompletionRequest(OpenAIBaseModel): # NOTE this will be ignored by VLLM -- the model determines the behavior parallel_tool_calls: Optional[bool] = False user: Optional[str] = None + # Qwen3/OpenAI thinking/reasoning control. + # Competition evaluator sends thinking={enable:true/false}. + thinking: Optional[dict] = None # doc: begin-chat-completion-sampling-params best_of: Optional[int] = None @@ -397,6 +404,10 @@ class ChatCompletionRequest(OpenAIBaseModel): reasoning_content is intentionally kept — chat_utils.py wraps it as ... for multi-turn reasoning history. """ + # Map max_completion_tokens → max_tokens (OpenAI API v2 name) + if data.get("max_completion_tokens") is not None and data.get("max_tokens") is None: + data["max_tokens"] = data["max_completion_tokens"] + messages = data.get("messages") if not isinstance(messages, list): return data @@ -406,11 +417,19 @@ class ChatCompletionRequest(OpenAIBaseModel): normalized.append(msg) continue if msg.get("content") is None: - if msg.get("reasoning_content") is None: + # Allow tool_calls messages and tool-role messages without content. + # CCCL namespace pattern: accept valid alternate message formats. + if msg.get("reasoning_content") is not None: + msg = {**msg, "content": ""} + elif msg.get("tool_calls") is not None: + msg = {**msg, "content": ""} + elif msg.get("role") == "tool": + msg = {**msg, "content": ""} + else: raise ValueError( - "Each message must have at least one of 'content' or " - "'reasoning_content'.") - msg = {**msg, "content": ""} + "Each message must have at least one of 'content', " + "'reasoning_content', or 'tool_calls'.") + normalized.append(msg) data = {**data, "messages": normalized} return data