AIGatewayService.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  1. <?php
  2. namespace app\service;
  3. use think\Db;
  4. use think\Queue;
  5. class AIGatewayService{
  6. /**
  7. * 接口访问配置
  8. *
  9. * 每个模块包含:
  10. * - api_key:API 调用密钥(Token)
  11. * - api_url:对应功能的服务端地址
  12. */
  13. protected $config = [
  14. //图生文 gemini-2.5-flash-preview
  15. 'imgtotxt' => [
  16. 'api_key' => 'sk-LVcDfTx5SYK6pWiGpfcAN2KA0LunymnMiYSVfzUKQXrjlkZv',
  17. 'api_url' => 'https://chatapi.onechats.top/v1/chat/completions'
  18. ],
  19. //文生文 gtp-4
  20. 'txttotxtgtp' => [
  21. 'api_key' => 'sk-fxlawqVtbbQbNW0wInR3E4wsLo5JHozDC2XOHzMa711su6ss',
  22. 'api_url' => 'https://chatapi.onechats.top/v1/chat/completions'
  23. ],
  24. //文生文 gemini-2.0-flash
  25. 'txttotxtgemini' => [
  26. 'api_key' => 'sk-cqfCZFiiSIdpDjIHLMBbH6uWfeg7iVsASvlubjrNEmfUXbpX',
  27. 'api_url' => 'https://chatapi.onechats.top/v1/chat/completions'
  28. ],
  29. //文生图 black-forest-labs/FLUX.1-kontext-pro、dall-e-3、gpt-image-1
  30. 'txttoimg' => [
  31. 'api_key' => 'sk-MB6SR8qNaTjO80U7HJl4ztivX3zQKPgKVka9oyfVSXIkHSYZ',
  32. 'api_url' => 'https://chatapi.onechats.ai/v1/images/generations'
  33. ],
  34. //gemini-3-pro-image-preview
  35. 'txttoimgfour' => [
  36. 'api_key' => 'sk-8yavOx3JxkiNqfgONHS4eSjFDLfErKj9W91PJc8Qvw8y95sT',
  37. 'api_url' => 'https://chatapi.onechats.ai/v1beta/models/gemini-3-pro-image-preview:streamGenerateContent'
  38. ],
  39. //文生图 MID_JOURNEY
  40. 'submitimage' => [
  41. 'api_key' => 'sk-iURfrAgzAjhZ4PpPLwzmWIAhM7zKfrkwDvyxk4RVBQ4ouJNK',
  42. 'api_url' => 'https://chatapi.onechats.ai/mj/submit/imagine'
  43. ]
  44. ];
  45. /**
  46. * 图生文
  47. * @param string $imageUrl 图像 URL,支持公网可访问地址
  48. * @param string $prompt 对图像的提问内容或提示文本
  49. */
  50. public function callGptApi($imageUrl,$prompt,$model)
  51. {
  52. $data = [
  53. "model" => $model,
  54. "messages" => [[
  55. "role" => "user",
  56. "content" => [
  57. ["type" => "text", "text" => $prompt],
  58. ["type" => "image_url", "image_url" => [
  59. "url" => $imageUrl,
  60. "detail" => "auto"
  61. ]]
  62. ]
  63. ]],
  64. "max_tokens" => 1000
  65. ];
  66. //方式二
  67. // $data = [
  68. // "model" => "gpt-4-vision-preview",
  69. // "messages" => [[
  70. // "role" => "user",
  71. // "content" => [
  72. // ["type" => "text", "text" => $prompt],
  73. // ["type" => "image_url", "image_url" => [
  74. // "url" => $imageUrl,
  75. // "detail" => "auto"
  76. // ]]
  77. // ]
  78. // ]],
  79. // "max_tokens" => 1000
  80. // ];
  81. return $this->callApi($this->config['imgtotxt']['api_url'], $this->config['imgtotxt']['api_key'], $data);
  82. }
  83. /**
  84. * 文生文
  85. * @param string $prompt 用户输入的文本提示内容
  86. */
  87. public function txtGptApi($prompt,$model)
  88. {
  89. //判断使用模型
  90. if ($model === 'gemini-2.0-flash') {
  91. $data = [
  92. 'model' => $model,
  93. 'messages' => [
  94. ['role' => 'user', 'content' => $prompt]
  95. ],
  96. 'temperature' => 0.7,
  97. 'max_tokens' => 1024
  98. ];
  99. return $this->callApi(
  100. $this->config['txttotxtgemini']['api_url'],
  101. $this->config['txttotxtgemini']['api_key'],
  102. $data
  103. );
  104. }else if ($model === 'gpt-4') {
  105. $data = [
  106. 'model' => $model,
  107. 'messages' => [
  108. ['role' => 'user', 'content' => $prompt]
  109. ],
  110. 'temperature' => 0.7,
  111. 'max_tokens' => 1024
  112. ];
  113. return $this->callApi(
  114. $this->config['txttotxtgtp']['api_url'],
  115. $this->config['txttotxtgtp']['api_key'],
  116. $data
  117. );
  118. }
  119. }
  120. /**
  121. * 文生图
  122. *
  123. * @param string $prompt 提示文本,用于指导图像生成(最长建议 1000 字符)
  124. * @param string $selectedOption 模型名称,例如 'dall-e-3' 或其他兼容模型
  125. *
  126. * 默认参数说明(适用于所有模型):
  127. * - n: 1(生成 1 张图)
  128. * - size: '1024x1024'(标准正方形图像)
  129. * - quality: 'hd'(高清质量)
  130. * - style: 'vivid'(鲜明风格)
  131. *
  132. * response_format 参数差异:
  133. * - 若模型为 'dall-e-3':返回 base64 图像,字段为 `b64_json`
  134. * - 其他模型默认返回图像 URL,字段为 `url`
  135. *
  136. * ⚠️ 注意:使用此方法后,需在 Job/TextToImageJob.php 中按 response_format 判断提取方式:
  137. * 提取 url 图像
  138. * $base64Image = $dalle1024['data'][0]['url'] ?? null;
  139. * 提取 base64 图像
  140. * $base64Image = $dalle1024['data'][0]['b64_json'] ?? null;
  141. *
  142. * @return array 返回接口响应,成功时包含 'data' 字段,失败时包含 'error' 信息
  143. */
  144. public function callDalleApi($prompt, $model,$size)
  145. {
  146. // 尺寸优先级:传入有效值 > 默认值
  147. $imageSize = !empty($size) ? $size : '1024x1024';
  148. if ($model === 'dall-e-3') {
  149. $data = [
  150. 'prompt' => $prompt,
  151. 'model' => $model,
  152. 'n' => 1,
  153. 'size' => $imageSize,
  154. 'quality' => 'hd',
  155. 'style' => 'vivid',
  156. 'response_format' => 'url',
  157. ];
  158. return $this->callApi($this->config['txttoimg']['api_url'],$this->config['txttoimg']['api_key'],$data);
  159. } else if ($model === 'black-forest-labs/FLUX.1-kontext-pro') {
  160. $data = [
  161. 'prompt' => $prompt,
  162. 'model' => $model,
  163. 'n' => 1,
  164. 'size' => $imageSize,
  165. 'quality' => 'hd',
  166. 'style' => 'vivid',
  167. 'response_format' => 'url',
  168. ];
  169. return $this->callApi($this->config['txttoimg']['api_url'],$this->config['txttoimg']['api_key'],$data);
  170. } else if ($model === 'MID_JOURNEY') {
  171. $data = [
  172. 'botType' => $model,
  173. 'prompt' => $prompt,
  174. 'base64Array' => [],
  175. 'accountFilter' => [
  176. 'channelId' => "",
  177. 'instanceId' => "",
  178. 'modes' => [],
  179. 'remark' => "",
  180. 'remix' => true,
  181. 'remixAutoConsidered' => true
  182. ],
  183. 'notifyHook' => "",
  184. 'state' => ""
  185. ];
  186. return $this->callApi($this->config['submitimage']['api_url'],$this->config['submitimage']['api_key'],$data);
  187. }else if ($model === 'gpt-image-1') {
  188. $data = [
  189. 'prompt' => $prompt,
  190. 'model' => $model,
  191. 'n' => 1,
  192. 'size' => $imageSize,
  193. 'quality' => 'hd',
  194. 'style' => 'vivid',
  195. 'response_format' => 'url',
  196. ];
  197. return $this->callApi($this->config['txttoimg']['api_url'],$this->config['txttoimg']['api_key'],$data);
  198. } else if ($model === 'gemini-3-pro-image-preview') {
  199. // 使用Google Gemini模型的正确参数格式,与curl命令保持一致
  200. $data = [
  201. "contents" => [
  202. [
  203. "role" => "user",
  204. "parts" => [
  205. ["text" => $prompt]
  206. ]
  207. ]
  208. ],
  209. "generationConfig" => [
  210. "responseModalities" => ["TEXT", "IMAGE"],
  211. "imageConfig" => [
  212. "aspectRatio" => "16:9"
  213. // 移除imageSize字段,与curl命令保持一致
  214. ]
  215. ]
  216. ];
  217. return $this->callApi($this->config['txttoimgfour']['api_url'],$this->config['txttoimgfour']['api_key'],$data);
  218. }else{
  219. return [
  220. 'code' => 400,
  221. 'msg' => '未配置的文生图模型: ' . $model,
  222. 'data' => null
  223. ];
  224. }
  225. }
  226. /**
  227. * 图生图
  228. * @param string $prompt 用户输入的文本提示内容
  229. * @param string $new_image_url 原图路径
  230. * @param array $options 可选参数,可覆盖默认配置
  231. * @return array
  232. */
  233. public function txt2imgWithControlNet($prompt, $controlImgUrl, $options = []) {
  234. $apiUrl = "http://20.0.17.188:45001/sdapi/v1/txt2img";
  235. $headers = ['Content-Type: application/json'];
  236. $imgPath = ROOT_PATH . 'public/' . ltrim($controlImgUrl, '/');
  237. if (!file_exists($imgPath)) {
  238. return ['code' => 1, 'msg' => '图片不存在:' . $controlImgUrl];
  239. }
  240. $imgData = file_get_contents($imgPath);
  241. $base64Img = 'data:image/png;base64,' . base64_encode($imgData);
  242. $params = [
  243. 'prompt' => $prompt,
  244. 'negative_prompt' => '(deformed, distorted, disfigured:1.3), poorly drawn, bad anatomy',
  245. 'steps' => 20,
  246. 'sampler_name' => 'DPM++ 2M SDE',
  247. 'cfg_scale' => 7,
  248. 'seed' => -1,
  249. 'width' => 1024,
  250. 'height' => 1303,
  251. 'override_settings' => [
  252. 'sd_model_checkpoint' => 'realisticVisionV51_v51VAE-inpainting',
  253. 'sd_vae' => 'vae-ft-mse-840000-ema-pruned',
  254. 'CLIP_stop_at_last_layers' => 2
  255. ],
  256. 'clip_skip' => 2,
  257. 'alwayson_scripts' => [
  258. 'controlnet' => [
  259. 'args' => [[
  260. 'enabled' => true,
  261. 'input_image' => $base64Img,
  262. 'module' => 'inpaint_only+lama',
  263. 'model' => 'control_v11p_sd15_inpaint_fp16 [be8bc0ed]',
  264. 'weight' => 1,
  265. 'resize_mode' => 'Resize and Fill',
  266. 'pixel_perfect' => false,
  267. 'control_mode' => 'ControlNet is more important',
  268. 'starting_control_step' => 0,
  269. 'ending_control_step' => 1
  270. ]]
  271. ]
  272. ]
  273. ];
  274. if (!empty($options)) {
  275. $params = array_merge($params, $options);
  276. }
  277. $ch = curl_init();
  278. curl_setopt($ch, CURLOPT_URL, $apiUrl);
  279. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  280. curl_setopt($ch, CURLOPT_POST, true);
  281. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  282. curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params, JSON_UNESCAPED_UNICODE));
  283. curl_setopt($ch, CURLOPT_TIMEOUT, 180);
  284. $response = curl_exec($ch);
  285. $error = curl_error($ch);
  286. curl_close($ch);
  287. if ($error) {
  288. return ['code' => 1, 'msg' => '请求失败:' . $error];
  289. }
  290. $data = json_decode($response, true);
  291. if (!isset($data['images'][0])) {
  292. return ['code' => 1, 'msg' => '接口未返回图像数据'];
  293. }
  294. return [
  295. 'code' => 0,
  296. 'msg' => '成功',
  297. 'data' => [
  298. 'base64' => $data['images'][0],
  299. 'info' => $data['info'] ?? ''
  300. ]
  301. ];
  302. }
  303. // 第一阶段:图生图
  304. public function upscaleWithImg2Img($prompt, $imgPath)
  305. {
  306. if (!file_exists($imgPath)) {
  307. return ['code' => 1, 'msg' => '原图不存在:' . $imgPath];
  308. }
  309. // 获取原始图像尺寸
  310. [$origWidth, $origHeight] = getimagesize($imgPath);
  311. if (!$origWidth || !$origHeight) {
  312. return ['code' => 1, 'msg' => '无法识别图片尺寸'];
  313. }
  314. // 按2倍尺寸计算目标大小
  315. $targetWidth = $origWidth * 2;
  316. $targetHeight = $origHeight * 2;
  317. // 编码图像为 base64
  318. $imgData = file_get_contents($imgPath);
  319. $base64Img = 'data:image/png;base64,' . base64_encode($imgData);
  320. // 构造参数
  321. $params = [
  322. 'init_images' => [$base64Img],
  323. 'prompt' => $prompt,
  324. 'steps' => 20,
  325. 'sampler_name' => 'DPM++ 2M SDE Heun',
  326. 'cfg_scale' => 7,
  327. 'seed' => 1669863506,
  328. 'width' => $targetWidth,
  329. 'height' => $targetHeight,
  330. 'denoising_strength' => 0.2,
  331. 'clip_skip' => 2,
  332. 'override_settings' => [
  333. 'sd_model_checkpoint' => 'realisticVisionV51_v51VAE-inpainting.safetensors [f0d4872d24]',
  334. 'sd_vae' => 'vae-ft-mse-840000-ema-pruned.safetensors',
  335. 'CLIP_stop_at_last_layers' => 2
  336. ],
  337. 'override_settings_restore_afterwards' => true
  338. ];
  339. $apiUrl = "http://20.0.17.188:45001/sdapi/v1/img2img";
  340. $headers = ['Content-Type: application/json'];
  341. // 发起请求
  342. $ch = curl_init();
  343. curl_setopt_array($ch, [
  344. CURLOPT_URL => $apiUrl,
  345. CURLOPT_RETURNTRANSFER => true,
  346. CURLOPT_POST => true,
  347. CURLOPT_HTTPHEADER => $headers,
  348. CURLOPT_POSTFIELDS => json_encode($params),
  349. CURLOPT_TIMEOUT => 180
  350. ]);
  351. $response = curl_exec($ch);
  352. $error = curl_error($ch);
  353. curl_close($ch);
  354. if ($error) return ['code' => 1, 'msg' => '图生图请求失败:' . $error];
  355. $data = json_decode($response, true);
  356. if (!isset($data['images'][0])) return ['code' => 1, 'msg' => '图生图接口未返回图像'];
  357. return ['code' => 0, 'data' => ['base64' => $data['images'][0]]];
  358. }
  359. // 第二阶段:高清超分
  360. public function imgtogqGptApi($imageRelPath, $options = [])
  361. {
  362. $imgPath = ROOT_PATH . 'public/' . $imageRelPath;
  363. if (!file_exists($imgPath)) {
  364. return ['code' => 1, 'msg' => '原图不存在:' . $imageRelPath];
  365. }
  366. $defaultParams = [
  367. 'resize_mode' => 0,
  368. 'show_extras_results' => true,
  369. 'gfpgan_visibility' => 0,
  370. 'codeformer_visibility' => 0,
  371. 'codeformer_weight' => 0,
  372. 'upscaling_resize' => 1.62,
  373. 'upscaling_crop' => true,
  374. 'upscaler_1' => 'R-ESRGAN 4x+ Anime6B',
  375. 'upscaler_2' => 'None',
  376. 'extras_upscaler_2_visibility' => 0,
  377. 'upscale_first' => false
  378. ];
  379. $params = array_merge($defaultParams, $options);
  380. try {
  381. $imgData = file_get_contents($imgPath);
  382. if ($imgData === false) {
  383. throw new Exception('无法读取图片文件');
  384. }
  385. $params['image'] = base64_encode($imgData);
  386. } catch (Exception $e) {
  387. return ['code' => 1, 'msg' => '图片读取失败:' . $e->getMessage()];
  388. }
  389. $apiUrl = "http://20.0.17.188:45001/sdapi/v1/extra-single-image";
  390. $headers = ['Content-Type: application/json'];
  391. $ch = curl_init();
  392. curl_setopt_array($ch, [
  393. CURLOPT_URL => $apiUrl,
  394. CURLOPT_RETURNTRANSFER => true,
  395. CURLOPT_POST => true,
  396. CURLOPT_HTTPHEADER => $headers,
  397. CURLOPT_POSTFIELDS => json_encode($params),
  398. CURLOPT_TIMEOUT => 120
  399. ]);
  400. $response = curl_exec($ch);
  401. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  402. $curlErr = curl_error($ch);
  403. curl_close($ch);
  404. if ($curlErr) {
  405. return ['code' => 1, 'msg' => '请求失败:' . $curlErr];
  406. }
  407. if ($httpCode !== 200) {
  408. return ['code' => 1, 'msg' => 'API请求失败,HTTP状态码:' . $httpCode];
  409. }
  410. $data = json_decode($response, true);
  411. if (json_last_error() !== JSON_ERROR_NONE) {
  412. return ['code' => 1, 'msg' => 'API返回数据解析失败:' . json_last_error_msg()];
  413. }
  414. if (empty($data['image'])) {
  415. return ['code' => 1, 'msg' => '接口未返回有效的图像数据'];
  416. }
  417. return [
  418. 'code' => 0,
  419. 'msg' => '高清图生成成功',
  420. 'data' => [
  421. 'base64_image' => $data['image'],
  422. 'original_size' => strlen($imgData),
  423. 'processed_size' => strlen(base64_decode($data['image']))
  424. ]
  425. ];
  426. }
  427. /**
  428. * 通用 API 调用方法(支持重试机制)
  429. *
  430. * @param string $url 接口地址
  431. * @param string $apiKey 授权密钥(Bearer Token)
  432. * @param array $data 请求数据(JSON 格式)
  433. *
  434. * 功能说明:
  435. * - 使用 cURL 发送 POST 请求到指定 API 接口
  436. * - 设置请求头和超时时间等参数
  437. * - 支持最多重试 2 次,当接口调用失败时自动重试
  438. * - 返回成功时解析 JSON 响应为数组
  439. *
  440. * 异常处理:
  441. * - 若全部重试失败,将抛出异常并包含最后一次错误信息
  442. *
  443. * @return array 接口响应数据(成功时返回解析后的数组)
  444. * @throws \Exception 接口请求失败时抛出异常
  445. */
  446. public function callApi($url, $apiKey, $data)
  447. {
  448. $maxRetries = 2;
  449. $attempt = 0;
  450. $lastError = '';
  451. $httpCode = 0;
  452. $apiErrorDetail = '';
  453. while ($attempt <= $maxRetries) {
  454. try {
  455. $ch = curl_init();
  456. curl_setopt_array($ch, [
  457. CURLOPT_URL => $url,
  458. CURLOPT_RETURNTRANSFER => true,
  459. CURLOPT_POST => true,
  460. CURLOPT_POSTFIELDS => json_encode($data),
  461. CURLOPT_HTTPHEADER => [
  462. 'Content-Type: application/json',
  463. 'Authorization: Bearer ' . $apiKey
  464. ],
  465. CURLOPT_TIMEOUT => 120,
  466. CURLOPT_SSL_VERIFYPEER => false,
  467. CURLOPT_SSL_VERIFYHOST => false,
  468. CURLOPT_CONNECTTIMEOUT => 30,
  469. CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  470. CURLOPT_FAILONERROR => false
  471. ]);
  472. $response = curl_exec($ch);
  473. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  474. $curlError = curl_error($ch);
  475. if ($response === false) {
  476. // 尝试从curl错误信息中提取HTTP状态码
  477. if (preg_match('/HTTP\/[0-9.]+\s+([0-9]+)/', $curlError, $matches)) {
  478. $httpCode = (int)$matches[1];
  479. }
  480. throw new \Exception("请求发送失败: " . $curlError);
  481. }
  482. $result = json_decode($response, true);
  483. // 检查API返回的错误
  484. if (isset($result['error'])) {
  485. $apiErrorDetail = $result['error']['message'] ?? '';
  486. $errorType = $result['error']['type'] ?? '';
  487. $errorCode = $result['error']['code'] ?? '';
  488. // 常见错误类型映射
  489. $errorMessages = [
  490. 'invalid_request_error' => '请求参数错误',
  491. 'authentication_error' => '认证失败',
  492. 'rate_limit_error' => '请求频率过高',
  493. 'insufficient_quota' => '额度不足',
  494. 'billing_not_active' => '账户未开通付费',
  495. 'content_policy_violation' => '内容违反政策',
  496. 'model_not_found' => '模型不存在或无可用渠道'
  497. ];
  498. // 优先使用errorCode进行映射,如果没有则使用errorType
  499. $friendlyMessage = $errorMessages[$errorCode] ?? ($errorMessages[$errorType] ?? 'API服务错误');
  500. // 构建详细的错误信息,包含错误代码、类型和详细描述
  501. $detailedError = "{$friendlyMessage}";
  502. if ($errorCode) {
  503. $detailedError .= " (错误代码: {$errorCode})";
  504. }
  505. if ($apiErrorDetail) {
  506. $detailedError .= ": {$apiErrorDetail}";
  507. }
  508. throw new \Exception($detailedError);
  509. }
  510. if ($httpCode !== 200) {
  511. // HTTP状态码映射
  512. $statusMessages = [
  513. 400 => '请求参数不合法',
  514. 401 => 'API密钥无效或权限不足',
  515. 403 => '访问被拒绝',
  516. 404 => 'API端点不存在',
  517. 429 => '请求过于频繁,请稍后再试',
  518. 500 => '服务器内部错误',
  519. 503 => '服务暂时不可用'
  520. ];
  521. $statusMessage = $statusMessages[$httpCode] ?? "HTTP错误({$httpCode})";
  522. throw new \Exception($statusMessage);
  523. }
  524. curl_close($ch);
  525. return $result;
  526. } catch (\Exception $e) {
  527. $lastError = $e->getMessage();
  528. $attempt++;
  529. if ($attempt <= $maxRetries) {
  530. sleep(pow(2, $attempt));
  531. } else {
  532. // 最终失败时的详细错误信息
  533. $errorDetails = [
  534. '错误原因' => $this->getErrorCause($httpCode, $apiErrorDetail),
  535. '解决方案' => $this->getErrorSolution($httpCode),
  536. '请求参数' => json_encode($data, JSON_UNESCAPED_UNICODE),
  537. 'HTTP状态码' => $httpCode,
  538. '重试次数' => $attempt
  539. ];
  540. // 构建最终的错误信息,优先显示原始的详细错误消息
  541. $finalError = "API请求失败\n";
  542. $finalError .= "失败说明: " . $lastError . "\n"; // 使用原始的详细错误消息
  543. $finalError .= "建议解决方案: " . $errorDetails['解决方案'] . "\n";
  544. $finalError .= "技术详情: HTTP {$httpCode} - " . $errorDetails['错误原因'];
  545. throw new \Exception($finalError);
  546. }
  547. }
  548. }
  549. }
  550. /**
  551. * 根据错误类型获取友好的错误原因
  552. */
  553. private function getErrorCause($httpCode, $apiError)
  554. {
  555. $causes = [
  556. 401 => 'API密钥无效、过期或没有访问权限',
  557. 400 => $apiError ?: '请求参数不符合API要求',
  558. 429 => '已达到API调用频率限制',
  559. 403 => '您的账户可能没有开通相关服务权限',
  560. 500 => 'OpenAI服务器处理请求时出错',
  561. 503 => 'API服务暂时不可用,可能是服务器维护或负载过高'
  562. ];
  563. return $causes[$httpCode] ?? '未知错误,请检查网络连接和API配置';
  564. }
  565. /**
  566. * 根据错误类型获取解决方案建议
  567. */
  568. private function getErrorSolution($httpCode)
  569. {
  570. $solutions = [
  571. 401 => '1. 检查API密钥是否正确 2. 确认密钥是否有访问权限 3. 尝试创建新密钥',
  572. 400 => '1. 检查请求参数 2. 验证提示词内容 3. 参考API文档修正参数',
  573. 429 => '1. 等待1分钟后重试 2. 升级账户提高限额 3. 优化调用频率',
  574. 403 => '1. 检查账户状态 2. 确认是否已开通付费 3. 联系OpenAI支持',
  575. 500 => '1. 等待几分钟后重试 2. 检查OpenAI服务状态页',
  576. 503 => '1. 等待几分钟后重试 2. 检查API服务提供商状态 3. 联系服务提供商确认服务可用性'
  577. ];
  578. return $solutions[$httpCode] ?? '1. 检查网络连接 2. 查看服务日志 3. 联系技术支持';
  579. }
  580. }