|
@@ -39,10 +39,24 @@ class QwenService:
|
|
|
normalized = (raw_url or "").rstrip("/")
|
|
normalized = (raw_url or "").rstrip("/")
|
|
|
if normalized.endswith("/chat/completions"):
|
|
if normalized.endswith("/chat/completions"):
|
|
|
return normalized
|
|
return normalized
|
|
|
|
|
+ if normalized.endswith("/api/v1"):
|
|
|
|
|
+ return f"{normalized}/chat/completions"
|
|
|
if normalized.endswith("/v1"):
|
|
if normalized.endswith("/v1"):
|
|
|
return f"{normalized}/chat/completions"
|
|
return f"{normalized}/chat/completions"
|
|
|
return f"{normalized}/v1/chat/completions"
|
|
return f"{normalized}/v1/chat/completions"
|
|
|
|
|
|
|
|
|
|
+ def _build_api_v1_fallback_url(self, target_url: str) -> Optional[str]:
|
|
|
|
|
+ normalized = (target_url or "").rstrip("/")
|
|
|
|
|
+ if not normalized.endswith("/v1/chat/completions"):
|
|
|
|
|
+ return None
|
|
|
|
|
+ if normalized.endswith("/api/v1/chat/completions"):
|
|
|
|
|
+ return None
|
|
|
|
|
+ return normalized.replace(
|
|
|
|
|
+ "/v1/chat/completions",
|
|
|
|
|
+ "/api/v1/chat/completions",
|
|
|
|
|
+ 1,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
def _should_fallback(self, status_code: int) -> bool:
|
|
def _should_fallback(self, status_code: int) -> bool:
|
|
|
return status_code in (429, 500, 502, 503, 504)
|
|
return status_code in (429, 500, 502, 503, 504)
|
|
|
|
|
|
|
@@ -451,6 +465,39 @@ class QwenService:
|
|
|
logger.error(
|
|
logger.error(
|
|
|
f"[Qwen API] HTTP 错误 - 状态码: {e.response.status_code}, URL: {target_url}")
|
|
f"[Qwen API] HTTP 错误 - 状态码: {e.response.status_code}, URL: {target_url}")
|
|
|
logger.error(f"[Qwen API] HTTP 错误响应: {e.response.text[:500]}")
|
|
logger.error(f"[Qwen API] HTTP 错误响应: {e.response.text[:500]}")
|
|
|
|
|
+ fallback_url = None
|
|
|
|
|
+ if e.response.status_code == 404:
|
|
|
|
|
+ fallback_url = self._build_api_v1_fallback_url(target_url)
|
|
|
|
|
+ if fallback_url:
|
|
|
|
|
+ logger.warning(
|
|
|
|
|
+ f"[Qwen API] 检测到 404,尝试兼容地址重试: {fallback_url}"
|
|
|
|
|
+ )
|
|
|
|
|
+ response = await self._client.post(
|
|
|
|
|
+ fallback_url,
|
|
|
|
|
+ json=data,
|
|
|
|
|
+ headers=headers,
|
|
|
|
|
+ )
|
|
|
|
|
+ elapsed_ms = int((time.monotonic() - start_at) * 1000)
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"[Qwen API] 兼容地址响应: status={response.status_code} elapsed_ms={elapsed_ms}"
|
|
|
|
|
+ )
|
|
|
|
|
+ logger.debug(f"[Qwen API] 兼容地址响应头: {dict(response.headers)}")
|
|
|
|
|
+ logger.debug(
|
|
|
|
|
+ f"[Qwen API] 兼容地址响应预览: {(response.text[:500] if response.text else '(空响应)')}"
|
|
|
|
|
+ )
|
|
|
|
|
+ response.raise_for_status()
|
|
|
|
|
+ if not response.text:
|
|
|
|
|
+ logger.error("[Qwen API] 兼容地址返回空响应")
|
|
|
|
|
+ return ""
|
|
|
|
|
+ try:
|
|
|
|
|
+ result = response.json()
|
|
|
|
|
+ content = result.get('response', result.get('choices', [{}])[
|
|
|
|
|
+ 0].get('message', {}).get('content', ''))
|
|
|
|
|
+ logger.info(f"[Qwen API] 兼容地址 JSON 解析成功,内容长度: {len(content)}")
|
|
|
|
|
+ return content
|
|
|
|
|
+ except json.JSONDecodeError as je:
|
|
|
|
|
+ logger.error(f"[Qwen API] 兼容地址响应不是有效的 JSON: {response.text[:200]}")
|
|
|
|
|
+ raise ValueError(f"无效的 JSON 响应: {str(je)}")
|
|
|
if is_qwen3_target and self._should_fallback(e.response.status_code):
|
|
if is_qwen3_target and self._should_fallback(e.response.status_code):
|
|
|
return await self._fallback_deepseek(final_messages)
|
|
return await self._fallback_deepseek(final_messages)
|
|
|
raise
|
|
raise
|