diff --git a/serv/model_router.py b/serv/model_router.py index ba1769e..326394c 100644 --- a/serv/model_router.py +++ b/serv/model_router.py @@ -117,23 +117,58 @@ class QwenModelProvider(BaseModelProvider): def model_name(self) -> str: return self._model_name + @staticmethod + def _normalize_content(value: Any) -> list[dict[str, str]]: + if isinstance(value, list): + normalized: list[dict[str, str]] = [] + for item in value: + if isinstance(item, dict) and item.get("type") == "text" and isinstance(item.get("text"), str): + normalized.append({"type": "text", "text": item["text"]}) + if normalized: + return normalized + text = "" if value is None else str(value) + return [{"type": "text", "text": text}] + + def _normalize_messages(self, messages: list[dict[str, Any]]) -> list[dict[str, Any]]: + normalized: list[dict[str, Any]] = [] + for message in messages: + item = {k: v for k, v in message.items() if k != "content"} + item["content"] = self._normalize_content(message.get("content")) + normalized.append(item) + return normalized + def complete(self, completion_request: CompletionRequest) -> dict[str, Any]: creds = self.oauth.get_valid_credentials() base_url = self.oauth.get_openai_base_url(creds) payload = { "model": self.model_name(), - "messages": completion_request.messages, - "tools": completion_request.tools, - "tool_choice": completion_request.tool_choice, + "messages": self._normalize_messages(completion_request.messages), + "max_tokens": 8000, + "metadata": { + "sessionId": str(uuid.uuid4()), + "promptId": uuid.uuid4().hex[:12], + }, + "vl_high_resolution_images": True, } + if completion_request.tools: + payload["tools"] = completion_request.tools data = json.dumps(payload).encode("utf-8") + + # Add DashScope-specific headers for OAuth tokens + user_agent = "QwenCode/unknown (linux; x64)" + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {creds['access_token']}", + "User-Agent": user_agent, + "X-DashScope-CacheControl": "enable", + "X-DashScope-UserAgent": user_agent, + "X-DashScope-AuthType": "qwen-oauth", + "Accept": "application/json", + } req = request.Request( f"{base_url}/chat/completions", data=data, - headers={ - "Content-Type": "application/json", - "Authorization": f"Bearer {creds['access_token']}", - }, + headers=headers, method="POST", ) try: diff --git a/serv/oauth.py b/serv/oauth.py index 7fac8a8..bcf259b 100644 --- a/serv/oauth.py +++ b/serv/oauth.py @@ -22,9 +22,13 @@ QWEN_SCOPE = "openid profile email model.completion" QWEN_DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code" QWEN_DEFAULT_RESOURCE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1" -# Hard-coded Qwen OAuth models (single source of truth) +# Hard-coded Qwen OAuth models aligned with qwen-code. QWEN_OAUTH_MODELS = [ - {"id": "coder-model", "name": "coder-model", "description": "Qwen 3.6 Plus — efficient hybrid model with leading coding performance"}, + { + "id": "coder-model", + "name": "coder-model", + "description": "Qwen 3.5 Plus — efficient hybrid model with leading coding performance", + }, ] QWEN_OAUTH_ALLOWED_MODELS = [model["id"] for model in QWEN_OAUTH_MODELS]