Merge pull request !11925 from 李鹏宇/dev
This commit is contained in:
李鹏宇
2026-06-05 08:43:38 +00:00
committed by Gitee
+63 -38
View File
@@ -48,8 +48,8 @@ class AiProvider(models.Model):
temperature = fields.Float(string='温度', default=0.7)
timeout = fields.Integer(
string='超时(秒)',
default=20,
help='AI 单次请求的最大等待秒数,超过后返回超时错误。默认 20 秒。'
default=60,
help='AI 单次请求的最大等待秒数(读取超时),超过后返回超时错误。连接超时固定 10 秒。默认 60 秒。'
)
active = fields.Boolean(string='启用', default=True)
enable_tools = fields.Boolean(
@@ -521,9 +521,9 @@ class AiConversation(models.Model):
'message': f'报表生成失败,请稍后重试或联系管理员检查服务端日志。'
}
def _call_chat_completion(self, provider, messages, tools=None):
def _call_chat_completion(self, provider, messages, tools=None, retry=2):
"""封装一次 HTTP 调用,返回 (assistant_message_dict, error_str)。
同时记录 Token 使用量。
同时记录 Token 使用量。支持自动重试瞬态网络错误。
"""
payload = {
'model': provider.model_name,
@@ -534,41 +534,66 @@ class AiConversation(models.Model):
if tools:
payload['tools'] = tools
payload['tool_choice'] = 'auto'
try:
resp = requests.post(
f"{provider.api_base}/chat/completions",
headers={
'Authorization': f'Bearer {provider.api_key}',
'Content-Type': 'application/json',
},
json=payload,
timeout=max(provider.timeout or 20, 1),
)
if not resp.ok:
_logger.error(
f"DeepSeek API 返回 {resp.status_code}: {resp.text[:1000]}"
read_timeout = max(provider.timeout or 60, 10)
last_error = None
for attempt in range(retry + 1):
try:
resp = requests.post(
f"{provider.api_base}/chat/completions",
headers={
'Authorization': f'Bearer {provider.api_key}',
'Content-Type': 'application/json',
},
json=payload,
timeout=(10, read_timeout), # (connect_timeout, read_timeout)
)
resp.raise_for_status()
data = resp.json()
# 记录 Token 使用量
usage = data.get('usage') or {}
if usage:
self.env['ai.token.usage'].sudo().create({
'user_id': self.env.uid,
'conversation_id': self.id,
'provider_id': provider.id,
'model_name': provider.model_name,
'prompt_tokens': usage.get('prompt_tokens', 0),
'completion_tokens': usage.get('completion_tokens', 0),
'total_tokens': usage.get('total_tokens', 0),
})
return data['choices'][0]['message'], None
except requests.exceptions.Timeout:
return None, f'错误:AI 响应超时(超过 {provider.timeout or 20} 秒),请重试或在服务商配置中调高超时。'
except requests.exceptions.RequestException as e:
return None, f'错误:API 调用失败 — {e}'
except (KeyError, IndexError, ValueError) as e:
return None, f'错误:解析 AI 响应失败 — {e}'
if not resp.ok:
_logger.error(
"AI API 返回 %s: %s", resp.status_code, resp.text[:500]
)
resp.raise_for_status()
data = resp.json()
# 记录 Token 使用量
usage = data.get('usage') or {}
if usage:
self.env['ai.token.usage'].sudo().create({
'user_id': self.env.uid,
'conversation_id': self.id,
'provider_id': provider.id,
'model_name': provider.model_name,
'prompt_tokens': usage.get('prompt_tokens', 0),
'completion_tokens': usage.get('completion_tokens', 0),
'total_tokens': usage.get('total_tokens', 0),
})
return data['choices'][0]['message'], None
except requests.exceptions.Timeout:
last_error = f'AI 响应超时(超过 {read_timeout} 秒)'
if attempt < retry:
_logger.warning(
'%s,第 %s/%s 次重试...', last_error, attempt + 1, retry
)
continue
return None, f'错误:{last_error},已重试 {retry} 次仍失败。请在服务商配置中调高超时时间或检查网络。'
except requests.exceptions.ConnectionError as e:
last_error = f'无法连接到 AI 服务 ({e})'
if attempt < retry:
_logger.warning(
'%s,第 %s/%s 次重试...', last_error, attempt + 1, retry
)
continue
return None, f'错误:{last_error},已重试 {retry} 次仍失败。请检查服务器能否访问外网 API。'
except requests.exceptions.RequestException as e:
return None, f'错误:API 调用失败 — {e}'
except (KeyError, IndexError, ValueError) as e:
return None, f'错误:解析 AI 响应失败 — {e}'
return None, f'错误:{last_error}'
def _convert_records_timezone(self, records, model_name, fields_list):
"""将查询结果中的 datetime 字段从 UTC 转换为用户时区"""