"""DeepSeek API client - OpenAI-compatible, zero extra dependencies.""" import json import logging import time import requests logger = logging.getLogger(__name__) class DeepSeekClient: """Client for DeepSeek API (OpenAI-compatible format).""" BASE_URL = "https://api.deepseek.com/v1/chat/completions" def __init__(self, api_key, model="deepseek-chat", temperature=0.7, max_retries=3): self.api_key = api_key self.model = model self.temperature = temperature self.max_retries = max_retries self.session = requests.Session() self.session.headers.update({ "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" }) def ask(self, system_prompt, user_prompt, temperature=None): """Send a chat completion request. Returns response text.""" messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ] payload = { "model": self.model, "messages": messages, "temperature": temperature or self.temperature } last_error = None for attempt in range(self.max_retries): try: resp = self.session.post(self.BASE_URL, json=payload, timeout=60) resp.raise_for_status() data = resp.json() result = data["choices"][0]["message"]["content"] logger.info( "DeepSeek API OK | model=%s | input=%d chars | output=%d chars", self.model, len(user_prompt), len(result) ) return result except requests.exceptions.Timeout: last_error = "Timeout" logger.warning("Attempt %d/%d timed out", attempt + 1, self.max_retries) except requests.exceptions.HTTPError as e: last_error = str(e) logger.warning("Attempt %d/%d HTTP error: %s", attempt + 1, self.max_retries, e) if "insufficient_quota" in str(e).lower() or "invalid_api_key" in str(e).lower(): raise # don't retry these except (json.JSONDecodeError, KeyError) as e: last_error = f"Parse error: {e}" logger.warning("Attempt %d/%d parse error: %s", attempt + 1, self.max_retries, e) if attempt < self.max_retries - 1: sleep_time = 2 ** attempt logger.info("Retrying in %ds...", sleep_time) time.sleep(sleep_time) raise RuntimeError( f"DeepSeek API failed after {self.max_retries} retries. Last error: {last_error}" )