|
@@ -0,0 +1,78 @@
|
|
|
|
+<?php
|
|
|
|
+
|
|
|
|
+namespace App\Services\LLM;
|
|
|
|
+
|
|
|
|
+use Illuminate\Support\Facades\Http;
|
|
|
|
+use Illuminate\Support\Facades\Log;
|
|
|
|
+
|
|
|
|
+class DeepSeekService
|
|
|
|
+{
|
|
|
|
+ public static function streamedResponseChat($message): void
|
|
|
|
+ {
|
|
|
|
+ $headers = [
|
|
|
|
+ 'Authorization' => 'Bearer ' . config('llm.deepseek.key'),
|
|
|
|
+ 'Content-Type' => 'application/json',
|
|
|
|
+ ];
|
|
|
|
+ $body = [
|
|
|
|
+ 'model' => 'deepseek-chat',
|
|
|
|
+ 'messages' => $message,
|
|
|
|
+ 'stream' => true,
|
|
|
|
+ ];
|
|
|
|
+
|
|
|
|
+ // 3. 调用DeepSeek流式API
|
|
|
|
+ $response = Http::withHeaders($headers)->timeout(300)->send('POST', 'https://api.deepseek.com/chat/completions', [
|
|
|
|
+ 'json' => $body
|
|
|
|
+ ]);
|
|
|
|
+
|
|
|
|
+ // 4. 流式转发数据
|
|
|
|
+ $body = $response->toPsrResponse()->getBody();
|
|
|
|
+
|
|
|
|
+ while (true) {
|
|
|
|
+ if ($body->eof() || !$body->isReadable()) {
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ $chunk = $body->getContents();
|
|
|
|
+
|
|
|
|
+ // 分割完整的JSON行
|
|
|
|
+ $lines = explode("\n\n", $chunk);
|
|
|
|
+ foreach ($lines as $line) {
|
|
|
|
+ $line = trim($line);
|
|
|
|
+ if ($line === '') {
|
|
|
|
+ continue; // 跳过空行
|
|
|
|
+ }
|
|
|
|
+ // 处理单条事件
|
|
|
|
+ if (str_starts_with($line, 'data: ')) {
|
|
|
|
+ // 去除 "data: " 前缀
|
|
|
|
+ $jsonStr = substr($line, 6);
|
|
|
|
+ self::processEvent($jsonStr);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 检测连接是否中断
|
|
|
|
+ if (connection_aborted()) {
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 处理单个事件的方法
|
|
|
|
+ protected static function processEvent(string $jsonStr): void
|
|
|
|
+ {
|
|
|
|
+ if ($jsonStr === '[DONE]') {
|
|
|
|
+ echo "event: end\n\n";
|
|
|
|
+ ob_flush();
|
|
|
|
+ flush();
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ $data = json_decode($jsonStr, true, 512, JSON_THROW_ON_ERROR);
|
|
|
|
+ $content = $data['choices'][0]['delta']['content'] ?? '';
|
|
|
|
+ echo "data: " . json_encode(['content' => $content], JSON_UNESCAPED_UNICODE) . "\n\n";
|
|
|
|
+ ob_flush();
|
|
|
|
+ flush();
|
|
|
|
+ } catch (\JsonException $e) {
|
|
|
|
+ Log::error('JSON 解析失败: ' . $e->getMessage());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|