AiChatService.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. <?php
  2. namespace App\Services;
  3. use Illuminate\Support\Facades\Http;
  4. use Illuminate\Support\Facades\Log;
  5. use Illuminate\Support\Str;
  6. use Illuminate\Http\Client\ConnectionException;
  7. class ChatGPTService
  8. {
  9. protected int $retries = 3;
  10. protected int $delayMs = 2000;
  11. protected string $model = 'gpt-4-1106-preview';
  12. protected string $apiUrl = 'https://api.openai.com/v1/chat/completions';
  13. protected string $apiKey;
  14. protected string $systemPrompt = '你是一个有帮助的助手。';
  15. protected float $temperature = 0.7;
  16. public static function withRetry(int $retries = 3, int $delayMs = 2000): static
  17. {
  18. return (new static())->setRetry($retries, $delayMs);
  19. }
  20. public function setRetry(int $retries, int $delayMs): static
  21. {
  22. $this->retries = $retries;
  23. $this->delayMs = $delayMs;
  24. return $this;
  25. }
  26. public function setModel(string $model): static
  27. {
  28. $this->model = $model;
  29. return $this;
  30. }
  31. public function setApiUrl(string $url): static
  32. {
  33. $this->apiUrl = $url;
  34. return $this;
  35. }
  36. public function setApiKey(string $key): static
  37. {
  38. $this->apiKey = $key;
  39. return $this;
  40. }
  41. public function setSystemPrompt(string $prompt): static
  42. {
  43. $this->systemPrompt = $prompt;
  44. return $this;
  45. }
  46. public function setTemperature(float $temperature): static
  47. {
  48. $this->temperature = $temperature;
  49. return $this;
  50. }
  51. public function ask(string $question): string|array
  52. {
  53. for ($attempt = 1; $attempt <= $this->retries; $attempt++) {
  54. try {
  55. $response = Http::withToken($this->apiKey)
  56. ->timeout(300)
  57. ->retry(3, 2000, function ($exception, $request) {
  58. // 仅当是连接/响应超时才重试
  59. return $exception instanceof ConnectionException;
  60. })
  61. ->post($this->apiUrl, [
  62. 'model' => $this->model,
  63. 'messages' => [
  64. ['role' => 'system', 'content' => $this->systemPrompt],
  65. ['role' => 'user', 'content' => $question],
  66. ],
  67. 'temperature' => $this->temperature,
  68. ]);
  69. $status = $response->status();
  70. $body = $response->json();
  71. // ✅ 判断 429 限流重试
  72. if ($status === 429) {
  73. $retryAfter = $response->header('Retry-After') ?? 10;
  74. Log::warning("第 {$attempt} 次请求被限流(429),等待 {$retryAfter} 秒后重试...");
  75. sleep((int) $retryAfter);
  76. continue;
  77. }
  78. // ✅ 判断是否 GPT 返回 timeout 错误
  79. $isTimeout = in_array($status, [408, 504]) ||
  80. (isset($body['error']['message']) && Str::contains(strtolower($body['error']['message']), 'time'));
  81. if ($isTimeout) {
  82. Log::warning("第 {$attempt} 次 GPT 响应超时,准备重试...");
  83. usleep($this->delayMs * 1000);
  84. continue;
  85. }
  86. if ($response->successful()) {
  87. return $body['choices'][0]['message']['content'] ?? '无内容返回';
  88. }
  89. return [
  90. 'error' => $body['error']['message'] ?? '请求失败',
  91. 'status' => $status
  92. ];
  93. } catch (ConnectionException $e) {
  94. Log::warning("第 {$attempt} 次连接超时:{$e->getMessage()},准备重试...");
  95. usleep($this->delayMs * 1000);
  96. continue;
  97. } catch (\Exception $e) {
  98. Log::error("GPT 请求异常:" . $e->getMessage());
  99. return [
  100. 'error' => $e->getMessage(),
  101. 'status' => 500
  102. ];
  103. }
  104. }
  105. return [
  106. 'error' => '请求多次失败或超时,请稍后再试。',
  107. 'status' => 504
  108. ];
  109. }
  110. }
  111. /**
  112. namespace App\Http\Controllers;
  113. use Illuminate\Http\Request;
  114. use Illuminate\Support\Facades\Http;
  115. use Illuminate\Support\Facades\Log;
  116. use Illuminate\Http\Client\ConnectionException;
  117. use Illuminate\Http\Client\RequestException;
  118. class ChatGPTController extends Controller
  119. {
  120. public function ask(Request $request)
  121. {
  122. $question = $request->input('question', 'Hello, who are you?');
  123. try {
  124. $response = Http::withToken(env('OPENAI_API_KEY'))
  125. ->timeout(10) // 请求超时时间(秒)
  126. ->retry(3, 2000, function ($exception, $request) {
  127. // 仅当是连接/响应超时才重试
  128. return $exception instanceof ConnectionException;
  129. })
  130. ->post('https://api.openai.com/v1/chat/completions', [
  131. 'model' => 'gpt-4-1106-preview',
  132. 'messages' => [
  133. ['role' => 'system', 'content' => '你是一个有帮助的助手。'],
  134. ['role' => 'user', 'content' => $question],
  135. ],
  136. 'temperature' => 0.7,
  137. ]);
  138. $data = $response->json();
  139. return response()->json([
  140. 'reply' => $data['choices'][0]['message']['content'] ?? '没有返回内容。',
  141. ]);
  142. } catch (ConnectionException $e) {
  143. // 所有重试都失败
  144. Log::error('请求超时:' . $e->getMessage());
  145. return response()->json(['error' => '请求超时,请稍后再试。'], 504);
  146. } catch (RequestException $e) {
  147. // 非超时类的请求异常(如 400/500)
  148. Log::error('请求失败:' . $e->getMessage());
  149. return response()->json(['error' => '请求失败:' . $e->getMessage()], 500);
  150. } catch (\Exception $e) {
  151. // 其他异常
  152. Log::error('未知错误:' . $e->getMessage());
  153. return response()->json(['error' => '发生未知错误。'], 500);
  154. }
  155. }
  156. }
  157. */