66 lines
2.3 KiB
Python
66 lines
2.3 KiB
Python
|
|
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)
|