Просмотр исходного кода

新增gpt等模型上游报错信息记录

lh 14 часов назад
Родитель
Сommit
90e27a234b
1 измененных файлов с 168 добавлено и 16 удалено
  1. 168 16
      app/Services/DeepSeek/DeepSeekService.php

+ 168 - 16
app/Services/DeepSeek/DeepSeekService.php

@@ -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;
                 }