DeepSeekService.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. <?php
  2. namespace App\Services\LLM;
  3. use App\Models\LlmMessage;
  4. use App\Models\LlmSession;
  5. use Illuminate\Support\Facades\Auth;
  6. use Illuminate\Support\Facades\Http;
  7. use Illuminate\Support\Facades\Log;
  8. use Illuminate\Support\Str;
  9. class DeepSeekService
  10. {
  11. public static function streamedResponseChat($llmConfig, $message, $sessionId = null): void
  12. {
  13. ini_set('output_buffering', 'off');
  14. $headers = [
  15. 'Authorization' => 'Bearer ' . $llmConfig['key'],
  16. 'Content-Type' => 'application/json',
  17. ];
  18. $body = [
  19. 'model' => $llmConfig['model'],
  20. 'messages' => $message,
  21. 'stream' => true,
  22. 'max_tokens' => 8192,
  23. 'temperature' => 0.6,
  24. 'top_p' => 0.7,
  25. "top_k"=> 50,
  26. "frequency_penalty" => 2
  27. ];
  28. // 保存用户消息
  29. $session = self::saveUserMessage($message, $sessionId, $llmConfig['model']);
  30. // 3. 调用DeepSeek流式API
  31. $response = Http::withHeaders($headers)->timeout(300)->send('POST', $llmConfig['base_url'] , [
  32. 'json' => $body,
  33. 'stream' => true, // 启用Guzzle流式接收
  34. ]);
  35. // 4. 流式转发数据
  36. $body = $response->toPsrResponse()->getBody();
  37. $buffer = '';
  38. $saveContent = '';
  39. $saveReasoningContent = '';
  40. while (true) {
  41. if ($body->eof() || !$body->isReadable()) {
  42. break;
  43. }
  44. $chunk = $body->read(500);
  45. $buffer .= $chunk;
  46. // 处理SSE事件分割
  47. while (($pos = strpos($buffer, "\n\n")) !== false) {
  48. $event = substr($buffer, 0, $pos);
  49. $buffer = substr($buffer, $pos + 2);
  50. // 解析并输出内容
  51. $startStr = 'data: ';
  52. if (str_starts_with($event, $startStr)) {
  53. $jsonEvent = substr($event, strlen($startStr));
  54. if ($jsonEvent === '[DONE]') {
  55. break;
  56. }
  57. try {
  58. $data = json_decode($jsonEvent, true, 512, JSON_THROW_ON_ERROR);
  59. $reasoningContent = $data['choices'][0]['delta']['reasoning_content'] ?? '';
  60. $content = $data['choices'][0]['delta']['content'] ?? '';
  61. $saveContent.= $content;
  62. $saveReasoningContent.= $reasoningContent;
  63. if ($reasoningContent !== '') {
  64. echo "data: " . json_encode(['reasoning_content' => $reasoningContent], JSON_UNESCAPED_UNICODE) . "\n\n";
  65. ob_flush();
  66. flush();
  67. }
  68. if ($content === '') {
  69. break;
  70. }
  71. echo "data: " . json_encode(['content' => $content], JSON_UNESCAPED_UNICODE) . "\n\n";
  72. ob_flush();
  73. flush();
  74. } catch (\JsonException $e) {
  75. Log::error('JSON 解析失败: ' . $e->getMessage());
  76. }
  77. }
  78. }
  79. // 与客户端连接关闭时
  80. if (connection_aborted()) {
  81. break;
  82. }
  83. }
  84. echo "data: [DONE]\n";
  85. ob_flush();
  86. flush();
  87. ob_end_flush();
  88. // 保存模型回复
  89. self::saveAssistantMessage($session->id, $saveContent, $saveReasoningContent);
  90. }
  91. /**
  92. * 保存用户消息并创建或获取会话
  93. */
  94. private static function saveUserMessage($messages, $sessionId = null, $model = null)
  95. {
  96. // 获取最后一条用户消息
  97. $userMessage = end($messages);
  98. // 创建或获取会话
  99. if (!$sessionId) {
  100. $title = mb_substr($userMessage['content'], 0, 50) . (mb_strlen($userMessage['content']) > 50 ? '...' : '');
  101. $session = LlmSession::create([
  102. 'session_id' => Str::uuid(),
  103. 'user_id' => Auth::id() ?? 0,
  104. 'model' => $model,
  105. 'title' => $title,
  106. ]);
  107. } else {
  108. $session = LlmSession::where('session_id', $sessionId)->firstOrFail();
  109. $session->updated_at = now();
  110. $session->save();
  111. }
  112. // 保存用户消息
  113. LlmMessage::create([
  114. 'session_id' => $session->id,
  115. 'role' => 'user',
  116. 'content' => $userMessage['content'],
  117. ]);
  118. return $session;
  119. }
  120. /**
  121. * 保存模型回复
  122. */
  123. private static function saveAssistantMessage($sessionId, $content, $reasoningContent = null)
  124. {
  125. return LlmMessage::create([
  126. 'session_id' => $sessionId,
  127. 'role' => 'assistant',
  128. 'content' => trim($content),
  129. 'reasoning_content' => trim($reasoningContent),
  130. ]);
  131. }
  132. }