mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-20 05:27:59 +08:00
refactor(openai): drop SDK dependency and switch to native HTTP client
This commit is contained in:
@@ -1,102 +1,163 @@
|
||||
"""
|
||||
OpenAI compatibility layer for different versions.
|
||||
OpenAI-compatible exception layer.
|
||||
|
||||
This module provides a compatibility layer between OpenAI library versions:
|
||||
- OpenAI < 1.0 (old API with openai.error module)
|
||||
- OpenAI >= 1.0 (new API with direct exception imports)
|
||||
This module used to bridge between openai SDK 0.x and 1.x exception types.
|
||||
Since we no longer depend on the `openai` SDK at all (we call HTTP directly
|
||||
via :mod:`models.openai.openai_http_client`), this file now provides:
|
||||
|
||||
1. Pure Python exception classes that match the *names* the rest of the
|
||||
codebase already imports (RateLimitError / Timeout / APIError /
|
||||
APIConnectionError / AuthenticationError / InvalidRequestError ...).
|
||||
2. A :func:`map_http_error` helper that converts an
|
||||
:class:`OpenAIHTTPError` (or any HTTP status code + message) into the
|
||||
appropriate exception subclass, so existing ``except RateLimitError``
|
||||
``except Timeout`` etc. blocks keep working unchanged.
|
||||
|
||||
This keeps the behavior of all existing bots (rate-limit backoff, timeout
|
||||
retry, auth-error fast-fail) identical to the openai-SDK-based version, while
|
||||
removing the hard dependency on the `openai` package.
|
||||
"""
|
||||
|
||||
try:
|
||||
# Try new OpenAI >= 1.0 API
|
||||
from openai import (
|
||||
OpenAIError,
|
||||
RateLimitError,
|
||||
APIError,
|
||||
APIConnectionError,
|
||||
AuthenticationError,
|
||||
APITimeoutError,
|
||||
BadRequestError,
|
||||
)
|
||||
|
||||
# Create a mock error module for backward compatibility
|
||||
class ErrorModule:
|
||||
OpenAIError = OpenAIError
|
||||
RateLimitError = RateLimitError
|
||||
APIError = APIError
|
||||
APIConnectionError = APIConnectionError
|
||||
AuthenticationError = AuthenticationError
|
||||
Timeout = APITimeoutError # Renamed in new version
|
||||
InvalidRequestError = BadRequestError # Renamed in new version
|
||||
|
||||
error = ErrorModule()
|
||||
|
||||
# Also export with new names
|
||||
Timeout = APITimeoutError
|
||||
InvalidRequestError = BadRequestError
|
||||
|
||||
except ImportError:
|
||||
# Fall back to old OpenAI < 1.0 API
|
||||
try:
|
||||
import openai.error as error
|
||||
|
||||
# Export individual exceptions for direct import
|
||||
OpenAIError = error.OpenAIError
|
||||
RateLimitError = error.RateLimitError
|
||||
APIError = error.APIError
|
||||
APIConnectionError = error.APIConnectionError
|
||||
AuthenticationError = error.AuthenticationError
|
||||
InvalidRequestError = error.InvalidRequestError
|
||||
Timeout = error.Timeout
|
||||
BadRequestError = error.InvalidRequestError # Alias
|
||||
APITimeoutError = error.Timeout # Alias
|
||||
except (ImportError, AttributeError):
|
||||
# Neither version works, create dummy classes
|
||||
class OpenAIError(Exception):
|
||||
pass
|
||||
|
||||
class RateLimitError(OpenAIError):
|
||||
pass
|
||||
|
||||
class APIError(OpenAIError):
|
||||
pass
|
||||
|
||||
class APIConnectionError(OpenAIError):
|
||||
pass
|
||||
|
||||
class AuthenticationError(OpenAIError):
|
||||
pass
|
||||
|
||||
class InvalidRequestError(OpenAIError):
|
||||
pass
|
||||
|
||||
class Timeout(OpenAIError):
|
||||
pass
|
||||
|
||||
BadRequestError = InvalidRequestError
|
||||
APITimeoutError = Timeout
|
||||
|
||||
# Create error module
|
||||
class ErrorModule:
|
||||
OpenAIError = OpenAIError
|
||||
RateLimitError = RateLimitError
|
||||
APIError = APIError
|
||||
APIConnectionError = APIConnectionError
|
||||
AuthenticationError = AuthenticationError
|
||||
InvalidRequestError = InvalidRequestError
|
||||
Timeout = Timeout
|
||||
|
||||
error = ErrorModule()
|
||||
from typing import Optional
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Exception hierarchy (mirrors openai SDK names so call sites don't change)
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
class OpenAIError(Exception):
|
||||
"""Base exception for all OpenAI-compatible API errors."""
|
||||
|
||||
def __init__(self, message: str = "", status_code: Optional[int] = None,
|
||||
body=None):
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
self.status_code = status_code
|
||||
self.body = body
|
||||
|
||||
|
||||
class APIError(OpenAIError):
|
||||
"""Generic API error (5xx and unclassified errors)."""
|
||||
|
||||
|
||||
class APIConnectionError(OpenAIError):
|
||||
"""Network / connection failure (DNS, refused, reset...)."""
|
||||
|
||||
|
||||
class Timeout(OpenAIError):
|
||||
"""Request timeout. Aliased as APITimeoutError for new-SDK style imports."""
|
||||
|
||||
|
||||
class AuthenticationError(OpenAIError):
|
||||
"""401 Unauthorized."""
|
||||
|
||||
|
||||
class PermissionDeniedError(OpenAIError):
|
||||
"""403 Forbidden."""
|
||||
|
||||
|
||||
class NotFoundError(OpenAIError):
|
||||
"""404 Not Found."""
|
||||
|
||||
|
||||
class InvalidRequestError(OpenAIError):
|
||||
"""400 Bad Request. Aliased as BadRequestError."""
|
||||
|
||||
|
||||
class RateLimitError(OpenAIError):
|
||||
"""429 Too Many Requests."""
|
||||
|
||||
|
||||
# Aliases used by some new-SDK-style code paths in the project.
|
||||
APITimeoutError = Timeout
|
||||
BadRequestError = InvalidRequestError
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Backward-compat ``error`` module-style accessor
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Some legacy code in the codebase (and possibly user plugins) does
|
||||
# from models.openai.openai_compat import error
|
||||
# except error.RateLimitError: ...
|
||||
# Keep that path working by exposing an attribute namespace.
|
||||
class _ErrorModule:
|
||||
OpenAIError = OpenAIError
|
||||
APIError = APIError
|
||||
APIConnectionError = APIConnectionError
|
||||
Timeout = Timeout
|
||||
AuthenticationError = AuthenticationError
|
||||
PermissionDeniedError = PermissionDeniedError
|
||||
NotFoundError = NotFoundError
|
||||
InvalidRequestError = InvalidRequestError
|
||||
RateLimitError = RateLimitError
|
||||
|
||||
|
||||
error = _ErrorModule()
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# HTTP -> exception mapping
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def map_http_error(status_code: Optional[int], message: str = "",
|
||||
body=None) -> OpenAIError:
|
||||
"""Convert an HTTP status (+ optional message/body) to the right subclass.
|
||||
|
||||
Used by HTTP-based bot wrappers so that downstream ``except RateLimitError``
|
||||
blocks behave identically to when the openai SDK was raising them.
|
||||
"""
|
||||
sc = status_code or 0
|
||||
msg = message or ""
|
||||
msg_lower = msg.lower()
|
||||
|
||||
# Connection-level (no status / non-HTTP failure)
|
||||
if sc == 0:
|
||||
if "timeout" in msg_lower or "timed out" in msg_lower:
|
||||
return Timeout(msg, sc, body)
|
||||
return APIConnectionError(msg, sc, body)
|
||||
|
||||
if sc == 408:
|
||||
return Timeout(msg, sc, body)
|
||||
if sc == 401:
|
||||
return AuthenticationError(msg, sc, body)
|
||||
if sc == 403:
|
||||
return PermissionDeniedError(msg, sc, body)
|
||||
if sc == 404:
|
||||
return NotFoundError(msg, sc, body)
|
||||
if sc == 429:
|
||||
return RateLimitError(msg, sc, body)
|
||||
if 400 <= sc < 500:
|
||||
return InvalidRequestError(msg, sc, body)
|
||||
if sc >= 500:
|
||||
return APIError(msg, sc, body)
|
||||
|
||||
return APIError(msg, sc, body)
|
||||
|
||||
|
||||
def wrap_http_error(http_err) -> OpenAIError:
|
||||
"""Adapter for :class:`OpenAIHTTPError` -> compat exception subclass.
|
||||
|
||||
Accepts any object with ``status_code`` / ``message`` / ``body`` attrs.
|
||||
"""
|
||||
sc = getattr(http_err, "status_code", None)
|
||||
msg = getattr(http_err, "message", "") or str(http_err)
|
||||
body = getattr(http_err, "body", None)
|
||||
return map_http_error(sc, msg, body)
|
||||
|
||||
|
||||
# Export all for easy import
|
||||
__all__ = [
|
||||
'error',
|
||||
'OpenAIError',
|
||||
'RateLimitError',
|
||||
'APIError',
|
||||
'APIConnectionError',
|
||||
'AuthenticationError',
|
||||
'InvalidRequestError',
|
||||
'Timeout',
|
||||
'BadRequestError',
|
||||
'APITimeoutError',
|
||||
"error",
|
||||
"OpenAIError",
|
||||
"APIError",
|
||||
"APIConnectionError",
|
||||
"Timeout",
|
||||
"APITimeoutError",
|
||||
"AuthenticationError",
|
||||
"PermissionDeniedError",
|
||||
"NotFoundError",
|
||||
"InvalidRequestError",
|
||||
"BadRequestError",
|
||||
"RateLimitError",
|
||||
"map_http_error",
|
||||
"wrap_http_error",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user