import json import logging from django.conf import settings from openai import OpenAI, APITimeoutError, APIConnectionError, APIStatusError from config.exceptions import AppError logger = logging.getLogger(__name__) def get_client(): return OpenAI( api_key=settings.DEEPSEEK_API_KEY, base_url=settings.DEEPSEEK_BASE_URL, timeout=settings.DEEPSEEK_TIMEOUT_SECONDS, ) def call_deepseek(system_prompt: str, user_content: str) -> dict: """调用 DeepSeek,返回解析后的 JSON dict + usage 信息。 自带 1 次重试:首次失败时将错误信息附给第二次调用。 """ client = get_client() messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_content}, ] last_error = None for attempt in range(1 + settings.DEEPSEEK_MAX_RETRIES): if attempt > 0 and last_error: messages.append({ "role": "user", "content": f"上一次输出不是合法 JSON,错误:{last_error}。请严格输出合法 JSON。", }) try: response = client.chat.completions.create( model=settings.DEEPSEEK_MODEL, messages=messages, response_format={"type": "json_object"}, temperature=0.3, ) except APITimeoutError: raise AppError('AI_TIMEOUT', 'DeepSeek 请求超时', status_code=504) except (APIConnectionError, APIStatusError) as e: logger.error('DeepSeek API error: %s', e) raise AppError('AI_PROVIDER_ERROR', f'DeepSeek 服务异常: {e}', status_code=502) raw = response.choices[0].message.content usage = { 'prompt_tokens': response.usage.prompt_tokens, 'completion_tokens': response.usage.completion_tokens, } if response.usage else {} try: parsed = json.loads(raw) return {'data': parsed, 'usage': usage} except (json.JSONDecodeError, TypeError) as e: last_error = str(e) logger.warning('DeepSeek JSON parse failed (attempt %d): %s', attempt + 1, e) raise AppError('AI_BAD_JSON', f'AI 返回非合法 JSON(重试后仍失败): {last_error}', status_code=500)