"""DeepSeek API 客户端封装,含指数退避重试逻辑""" import time from typing import Callable from openai import OpenAI import openai from exceptions import ApiError # 文件扩展名 → MIME 类型映射 EXTENSION_MIME_MAP = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".bmp": "image/bmp", ".webp": "image/webp", } class ApiClient: """封装 DeepSeek API 调用,含重试和错误处理""" RETRY_DELAYS = [1, 2, 4] # 指数退避延迟(秒) MAX_RETRIES = 3 def __init__( self, api_key: str, base_url: str = "https://api.deepseek.com", _sleep: Callable[[float], None] = time.sleep, _client: "OpenAI | None" = None, ): self._client = _client or OpenAI(api_key=api_key, base_url=base_url) self._sleep = _sleep def chat(self, system_prompt: str, user_content: str, model: str = "deepseek-chat") -> str: """ 调用 Chat Completion API。 速率限制和网络异常时自动指数退避重试,最多 3 次。 Raises: ApiError: API 调用失败(非可重试错误或重试耗尽) """ def _call(): response = self._client.chat.completions.create( model=model, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_content}, ], ) return response.choices[0].message.content return self._retry(_call) def vision( self, system_prompt: str, image_base64: str, mime_type: str = "image/png", model: str = "deepseek-chat", ) -> str: """ 调用 Vision API 识别图片内容。 速率限制和网络异常时自动指数退避重试,最多 3 次。 Args: system_prompt: 系统提示词 image_base64: 图片的 base64 编码 mime_type: 图片 MIME 类型(如 image/jpeg、image/png) model: 模型名称 Raises: ApiError: API 调用失败 """ def _call(): response = self._client.chat.completions.create( model=model, messages=[ {"role": "system", "content": system_prompt}, { "role": "user", "content": [ { "type": "image_url", "image_url": { "url": f"data:{mime_type};base64,{image_base64}", }, }, ], }, ], ) return response.choices[0].message.content return self._retry(_call) def _retry(self, call: Callable[[], str]) -> str: """执行带指数退避重试的 API 调用,对速率限制和网络异常重试""" for attempt in range(self.MAX_RETRIES + 1): try: return call() except openai.RateLimitError: if attempt < self.MAX_RETRIES: self._sleep(self.RETRY_DELAYS[attempt]) else: raise ApiError("速率限制重试耗尽", status_code=429) except openai.APIConnectionError: if attempt < self.MAX_RETRIES: self._sleep(self.RETRY_DELAYS[attempt]) else: raise ApiError("网络连接失败,重试耗尽") except openai.APITimeoutError: if attempt < self.MAX_RETRIES: self._sleep(self.RETRY_DELAYS[attempt]) else: raise ApiError("API 请求超时,重试耗尽") except openai.APIError as e: raise ApiError(str(e), status_code=getattr(e, "status_code", None))