|
|
@@ -34,6 +34,8 @@ class DeepSeekService
|
|
|
private $gpt_text_models;
|
|
|
private $gemini_text_models;
|
|
|
private $baidu_glm_text_models;
|
|
|
+ // GPT 上游错误详情缓存(按异常对象 id 缓存,避免流式响应体被重复读取后丢失)
|
|
|
+ private $gpt_upstream_error_cache = [];
|
|
|
protected $aiImageGenerationService;
|
|
|
protected $pointsService;
|
|
|
|
|
|
@@ -3835,6 +3837,85 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
+ * 解析 GPT 上游错误详情:状态码、错误码(如 content_filter)、错误信息与响应体
|
|
|
+ * 流式响应的 body 不可 seek,Guzzle 自带的 body summary 取不到内容,这里手动读取并截断
|
|
|
+ */
|
|
|
+ private function resolveGptUpstreamError(\Exception $e)
|
|
|
+ {
|
|
|
+ // 同一个异常对象可能被内层与外层 catch 多次解析,而流式响应体只能读取一次,这里缓存解析结果
|
|
|
+ $cacheKey = spl_object_id($e);
|
|
|
+ if (isset($this->gpt_upstream_error_cache[$cacheKey])) {
|
|
|
+ return $this->gpt_upstream_error_cache[$cacheKey];
|
|
|
+ }
|
|
|
+
|
|
|
+ $detail = [
|
|
|
+ 'status' => 0,
|
|
|
+ 'code' => '',
|
|
|
+ 'message' => '',
|
|
|
+ 'body' => '',
|
|
|
+ ];
|
|
|
+
|
|
|
+ if (!($e instanceof \GuzzleHttp\Exception\BadResponseException) || !$e->getResponse()) {
|
|
|
+ $this->gpt_upstream_error_cache[$cacheKey] = $detail;
|
|
|
+ return $detail;
|
|
|
+ }
|
|
|
+
|
|
|
+ $response = $e->getResponse();
|
|
|
+ $detail['status'] = (int)$response->getStatusCode();
|
|
|
+
|
|
|
+ try {
|
|
|
+ $body = $response->getBody();
|
|
|
+ if ($body->isSeekable()) {
|
|
|
+ $body->rewind();
|
|
|
+ }
|
|
|
+ $content = $body->isReadable() ? (string)$body : '';
|
|
|
+ } catch (\Throwable $t) {
|
|
|
+ $content = '';
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($content === '') {
|
|
|
+ $this->gpt_upstream_error_cache[$cacheKey] = $detail;
|
|
|
+ return $detail;
|
|
|
+ }
|
|
|
+
|
|
|
+ $detail['body'] = mb_substr($content, 0, 4000);
|
|
|
+
|
|
|
+ $decoded = json_decode($content, true);
|
|
|
+ $decoded_error = is_array($decoded) ? getProp($decoded, 'error') : null;
|
|
|
+ if (is_array($decoded_error)) {
|
|
|
+ $detail['code'] = (string)getProp($decoded_error, 'code', '');
|
|
|
+ $detail['message'] = (string)getProp($decoded_error, 'message', '');
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->gpt_upstream_error_cache[$cacheKey] = $detail;
|
|
|
+ return $detail;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将上游错误转换为展示给前端的提示:内容安全策略拦截等给出可操作的说明
|
|
|
+ */
|
|
|
+ private function buildChatErrorForUser(\Exception $e, $upstreamError = null)
|
|
|
+ {
|
|
|
+ if (!is_array($upstreamError)) {
|
|
|
+ $upstreamError = $this->resolveGptUpstreamError($e);
|
|
|
+ }
|
|
|
+
|
|
|
+ $code = (string)getProp($upstreamError, 'code', '');
|
|
|
+ $message = (string)getProp($upstreamError, 'message', '');
|
|
|
+ $status = (int)getProp($upstreamError, 'status', 0);
|
|
|
+
|
|
|
+ if ($code === 'content_filter') {
|
|
|
+ return '本集内容被上游模型的内容安全策略拦截,请调整剧本中过于血腥、暴力等敏感描述后重试,或改用其他模型';
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($message !== '') {
|
|
|
+ return '上游模型返回错误(' . $status . '):' . mb_substr($message, 0, 300);
|
|
|
+ }
|
|
|
+
|
|
|
+ return '处理请求时发生异常: ' . $e->getMessage();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
* GPT 中转站探活:GET /v1/models,5 秒短超时
|
|
|
* 连接成功(含 401/403/404)视为可用;连接失败/超时/5xx/429 视为不可用
|
|
|
*/
|
|
|
@@ -4111,6 +4192,9 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
];
|
|
|
return;
|
|
|
} catch (\Exception $e) {
|
|
|
+ // 提前解析上游错误详情(流式响应体只能读取一次,后续日志与前端提示共用)
|
|
|
+ $upstream_error = $this->resolveGptUpstreamError($e);
|
|
|
+
|
|
|
// 报错或长时间无返回时,切到 GPT_56_LUNA_API_KEY 重试一次(收费更高,慎用;仅原本未用 luna key 时触发)
|
|
|
if (!$usedLunaFallback && !empty($lunaApiKey) && $apiKey !== $lunaApiKey) {
|
|
|
$usedLunaFallback = true;
|
|
|
@@ -4121,8 +4205,15 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
|
|
|
// 已输出内容不重试(避免重复);非可重试异常不重试;重试次数用完则抛出
|
|
|
if (!empty($fullContent) || !$this->isGptRetryableException($e) || $attempt >= $maxRetries) {
|
|
|
- dLog('deepseek')->error('GPT 流式请求失败: ' . $e->getMessage());
|
|
|
- logDB('deepseek', 'error', 'GPT 流式请求失败', ['error' => $e->getMessage()]);
|
|
|
+ // 记录上游返回的错误详情(流式响应 Guzzle 不会自动附带 body,需手动读取)
|
|
|
+ $error_context = ['error' => $e->getMessage()];
|
|
|
+ if ($upstream_error['status'] || $upstream_error['body'] !== '') {
|
|
|
+ $error_context['upstream_status'] = $upstream_error['status'];
|
|
|
+ $error_context['upstream_code'] = $upstream_error['code'];
|
|
|
+ $error_context['upstream_body'] = $upstream_error['body'];
|
|
|
+ }
|
|
|
+ dLog('deepseek')->error('GPT 流式请求失败: ' . $e->getMessage(), $error_context);
|
|
|
+ logDB('deepseek', 'error', 'GPT 流式请求失败', $error_context);
|
|
|
throw $e;
|
|
|
}
|
|
|
|
|
|
@@ -6684,14 +6775,22 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
}
|
|
|
}
|
|
|
} catch (\Exception $e) {
|
|
|
+ // 解析上游错误详情(如 content_filter),便于日志定位与前端提示
|
|
|
+ $upstream_error = $this->resolveGptUpstreamError($e);
|
|
|
dLog('deepseek')->error('addChat流式处理异常: ' . $e->getMessage(), [
|
|
|
'model' => $model,
|
|
|
'anime_id' => $anime_id,
|
|
|
+ 'upstream_status' => $upstream_error['status'],
|
|
|
+ 'upstream_code' => $upstream_error['code'],
|
|
|
+ 'upstream_body' => $upstream_error['body'],
|
|
|
'trace' => $e->getTraceAsString()
|
|
|
]);
|
|
|
logDB('deepseek', 'error', 'addChat流式处理失败', [
|
|
|
'error' => $e->getMessage(),
|
|
|
- 'model' => $model
|
|
|
+ 'model' => $model,
|
|
|
+ 'upstream_status' => $upstream_error['status'],
|
|
|
+ 'upstream_code' => $upstream_error['code'],
|
|
|
+ 'upstream_body' => $upstream_error['body']
|
|
|
]);
|
|
|
|
|
|
yield [
|
|
|
@@ -6701,7 +6800,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
'answer' => '',
|
|
|
'reasoning' => '',
|
|
|
'usage' => [],
|
|
|
- 'error' => '处理请求时发生异常: ' . $e->getMessage()
|
|
|
+ 'error' => $this->buildChatErrorForUser($e, $upstream_error)
|
|
|
];
|
|
|
return;
|
|
|
}
|
|
|
@@ -7538,13 +7637,21 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
}
|
|
|
}
|
|
|
} catch (\Exception $e) {
|
|
|
+ // 解析上游错误详情(如 content_filter),便于日志定位与前端提示
|
|
|
+ $upstream_error = $this->resolveGptUpstreamError($e);
|
|
|
dLog('deepseek')->error('方法名流式处理异常: ' . $e->getMessage(), [
|
|
|
'model' => $model,
|
|
|
+ 'upstream_status' => $upstream_error['status'],
|
|
|
+ 'upstream_code' => $upstream_error['code'],
|
|
|
+ 'upstream_body' => $upstream_error['body'],
|
|
|
'trace' => $e->getTraceAsString()
|
|
|
]);
|
|
|
logDB('deepseek', 'error', '方法名流式处理失败', [
|
|
|
'error' => $e->getMessage(),
|
|
|
- 'model' => $model
|
|
|
+ 'model' => $model,
|
|
|
+ 'upstream_status' => $upstream_error['status'],
|
|
|
+ 'upstream_code' => $upstream_error['code'],
|
|
|
+ 'upstream_body' => $upstream_error['body']
|
|
|
]);
|
|
|
|
|
|
yield [
|
|
|
@@ -7553,7 +7660,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
'answer' => '',
|
|
|
'reasoning' => '',
|
|
|
'usage' => [],
|
|
|
- 'error' => '处理请求时发生异常: ' . $e->getMessage()
|
|
|
+ 'error' => $this->buildChatErrorForUser($e, $upstream_error)
|
|
|
];
|
|
|
return;
|
|
|
}
|
|
|
@@ -8414,15 +8521,23 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
}
|
|
|
}
|
|
|
} catch (\Exception $e) {
|
|
|
+ // 解析上游错误详情(如 content_filter),便于日志定位与前端提示
|
|
|
+ $upstream_error = $this->resolveGptUpstreamError($e);
|
|
|
dLog('deepseek')->error('chat流式处理异常: ' . $e->getMessage(), [
|
|
|
'model' => $model,
|
|
|
'anime_id' => $anime_id,
|
|
|
'episode_number' => $episode_number,
|
|
|
+ 'upstream_status' => $upstream_error['status'],
|
|
|
+ 'upstream_code' => $upstream_error['code'],
|
|
|
+ 'upstream_body' => $upstream_error['body'],
|
|
|
'trace' => $e->getTraceAsString()
|
|
|
]);
|
|
|
logDB('deepseek', 'error', 'chat流式处理失败', [
|
|
|
'error' => $e->getMessage(),
|
|
|
- 'model' => $model
|
|
|
+ 'model' => $model,
|
|
|
+ 'upstream_status' => $upstream_error['status'],
|
|
|
+ 'upstream_code' => $upstream_error['code'],
|
|
|
+ 'upstream_body' => $upstream_error['body']
|
|
|
]);
|
|
|
|
|
|
yield [
|
|
|
@@ -8431,7 +8546,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
'answer' => '',
|
|
|
'reasoning' => '',
|
|
|
'usage' => [],
|
|
|
- 'error' => '处理请求时发生异常: ' . $e->getMessage()
|
|
|
+ 'error' => $this->buildChatErrorForUser($e, $upstream_error)
|
|
|
];
|
|
|
return;
|
|
|
}
|
|
|
@@ -9254,14 +9369,22 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
}
|
|
|
}
|
|
|
} catch (\Exception $e) {
|
|
|
+ // 解析上游错误详情(如 content_filter),便于日志定位与前端提示
|
|
|
+ $upstream_error = $this->resolveGptUpstreamError($e);
|
|
|
dLog('deepseek')->error('addChat流式处理异常: ' . $e->getMessage(), [
|
|
|
'model' => $model,
|
|
|
'anime_id' => $anime_id,
|
|
|
+ 'upstream_status' => $upstream_error['status'],
|
|
|
+ 'upstream_code' => $upstream_error['code'],
|
|
|
+ 'upstream_body' => $upstream_error['body'],
|
|
|
'trace' => $e->getTraceAsString()
|
|
|
]);
|
|
|
logDB('deepseek', 'error', 'addChat流式处理失败', [
|
|
|
'error' => $e->getMessage(),
|
|
|
- 'model' => $model
|
|
|
+ 'model' => $model,
|
|
|
+ 'upstream_status' => $upstream_error['status'],
|
|
|
+ 'upstream_code' => $upstream_error['code'],
|
|
|
+ 'upstream_body' => $upstream_error['body']
|
|
|
]);
|
|
|
|
|
|
yield [
|
|
|
@@ -9271,7 +9394,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
'answer' => '',
|
|
|
'reasoning' => '',
|
|
|
'usage' => [],
|
|
|
- 'error' => '处理请求时发生异常: ' . $e->getMessage()
|
|
|
+ 'error' => $this->buildChatErrorForUser($e, $upstream_error)
|
|
|
];
|
|
|
return;
|
|
|
}
|
|
|
@@ -10649,13 +10772,21 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
}
|
|
|
}
|
|
|
} catch (\Exception $e) {
|
|
|
+ // 解析上游错误详情(如 content_filter),便于日志定位与前端提示
|
|
|
+ $upstream_error = $this->resolveGptUpstreamError($e);
|
|
|
dLog('deepseek')->error('方法名流式处理异常: ' . $e->getMessage(), [
|
|
|
'model' => $model,
|
|
|
+ 'upstream_status' => $upstream_error['status'],
|
|
|
+ 'upstream_code' => $upstream_error['code'],
|
|
|
+ 'upstream_body' => $upstream_error['body'],
|
|
|
'trace' => $e->getTraceAsString()
|
|
|
]);
|
|
|
logDB('deepseek', 'error', '方法名流式处理失败', [
|
|
|
'error' => $e->getMessage(),
|
|
|
- 'model' => $model
|
|
|
+ 'model' => $model,
|
|
|
+ 'upstream_status' => $upstream_error['status'],
|
|
|
+ 'upstream_code' => $upstream_error['code'],
|
|
|
+ 'upstream_body' => $upstream_error['body']
|
|
|
]);
|
|
|
|
|
|
yield [
|
|
|
@@ -10664,7 +10795,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
'answer' => '',
|
|
|
'reasoning' => '',
|
|
|
'usage' => [],
|
|
|
- 'error' => '处理请求时发生异常: ' . $e->getMessage()
|
|
|
+ 'error' => $this->buildChatErrorForUser($e, $upstream_error)
|
|
|
];
|
|
|
return;
|
|
|
}
|
|
|
@@ -12261,15 +12392,23 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
}
|
|
|
}
|
|
|
} catch (\Exception $e) {
|
|
|
+ // 解析上游错误详情(如 content_filter),便于日志定位与前端提示
|
|
|
+ $upstream_error = $this->resolveGptUpstreamError($e);
|
|
|
dLog('deepseek')->error('chat流式处理异常: ' . $e->getMessage(), [
|
|
|
'model' => $model,
|
|
|
'anime_id' => $anime_id,
|
|
|
'episode_number' => $episode_number,
|
|
|
+ 'upstream_status' => $upstream_error['status'],
|
|
|
+ 'upstream_code' => $upstream_error['code'],
|
|
|
+ 'upstream_body' => $upstream_error['body'],
|
|
|
'trace' => $e->getTraceAsString()
|
|
|
]);
|
|
|
logDB('deepseek', 'error', 'chat流式处理失败', [
|
|
|
'error' => $e->getMessage(),
|
|
|
- 'model' => $model
|
|
|
+ 'model' => $model,
|
|
|
+ 'upstream_status' => $upstream_error['status'],
|
|
|
+ 'upstream_code' => $upstream_error['code'],
|
|
|
+ 'upstream_body' => $upstream_error['body']
|
|
|
]);
|
|
|
|
|
|
yield [
|
|
|
@@ -12278,7 +12417,7 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
'answer' => '',
|
|
|
'reasoning' => '',
|
|
|
'usage' => [],
|
|
|
- 'error' => '处理请求时发生异常: ' . $e->getMessage()
|
|
|
+ 'error' => $this->buildChatErrorForUser($e, $upstream_error)
|
|
|
];
|
|
|
return;
|
|
|
}
|
|
|
@@ -12757,6 +12896,10 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
$finalResult = $chunk;
|
|
|
break; // 找到 done 就退出循环
|
|
|
}
|
|
|
+ // 出错时直接带上原因返回(如上游内容安全策略拦截),避免被吞成"未获取到最终结果"
|
|
|
+ if (isset($chunk['type']) && $chunk['type'] === 'error' && !empty($chunk['error'])) {
|
|
|
+ return ['error' => $chunk['error']];
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// 验证最终结果
|
|
|
@@ -16404,6 +16547,9 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
'usage' => $usage
|
|
|
];
|
|
|
} catch (\Exception $e) {
|
|
|
+ // 提前解析上游错误详情,便于日志中记录真实原因(如 content_filter)
|
|
|
+ $upstream_error = $this->resolveGptUpstreamError($e);
|
|
|
+
|
|
|
// 报错或超时时,切到 GPT_56_LUNA_API_KEY 重试一次(收费更高,慎用;仅原本未用 luna key 时触发)
|
|
|
if (!$usedLunaFallback && !empty($lunaApiKey) && $apiKey !== $lunaApiKey) {
|
|
|
$usedLunaFallback = true;
|
|
|
@@ -16413,8 +16559,14 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
|
|
|
}
|
|
|
|
|
|
if (!$this->isGptRetryableException($e) || $attempt >= $maxRetries) {
|
|
|
- dLog('deepseek')->error('GPT-5.4 请求失败: ' . $e->getMessage());
|
|
|
- logDB('deepseek', 'error', 'GPT-5.4 请求失败', ['error' => $e->getMessage()]);
|
|
|
+ $error_context = ['error' => $e->getMessage()];
|
|
|
+ if ($upstream_error['status'] || $upstream_error['body'] !== '') {
|
|
|
+ $error_context['upstream_status'] = $upstream_error['status'];
|
|
|
+ $error_context['upstream_code'] = $upstream_error['code'];
|
|
|
+ $error_context['upstream_body'] = $upstream_error['body'];
|
|
|
+ }
|
|
|
+ dLog('deepseek')->error('GPT-5.4 请求失败: ' . $e->getMessage(), $error_context);
|
|
|
+ logDB('deepseek', 'error', 'GPT-5.4 请求失败', $error_context);
|
|
|
throw $e;
|
|
|
}
|
|
|
|