|
@@ -0,0 +1,177 @@
|
|
|
|
+<?php
|
|
|
|
+
|
|
|
|
+namespace app\api\controller;
|
|
|
|
+
|
|
|
|
+use app\common\controller\Api;
|
|
|
|
+use Symfony\Component\HttpFoundation\StreamedResponse;
|
|
|
|
+use think\Db;
|
|
|
|
+use app\common\library\Deepseek as DeepseekAI;
|
|
|
|
+/**
|
|
|
|
+ * AI
|
|
|
|
+ */
|
|
|
|
+class Deepseek extends Api
|
|
|
|
+{
|
|
|
|
+ protected $noNeedLogin = ['*'];
|
|
|
|
+ protected $noNeedRight = ['*'];
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ public function index()
|
|
|
|
+ {
|
|
|
|
+ $params = input();
|
|
|
|
+
|
|
|
|
+ $chat = new DeepseekAI();
|
|
|
|
+ $response = $chat->completions($params['messages'],$params['model'],$params['type']);
|
|
|
|
+ // dump($response);
|
|
|
|
+
|
|
|
|
+ return new StreamedResponse(function() use ($response) {
|
|
|
|
+ $body = $response->getBody();
|
|
|
|
+ while (!$body->eof()) {
|
|
|
|
+ echo $body->read(1024); // 每次读取 1024 字节
|
|
|
|
+ ob_flush();
|
|
|
|
+ flush();
|
|
|
|
+ }
|
|
|
|
+ }, 200, [
|
|
|
|
+ 'Content-Type' => 'application/json', // 根据实际情况设置响应头
|
|
|
|
+ ]);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ public function mockDeepSeekStream()
|
|
|
|
+ {
|
|
|
|
+
|
|
|
|
+ // 这是模拟的文本片段 - 实际应替换为真实的API调用
|
|
|
|
+ $textChunks = [
|
|
|
|
+ "你好!这是 DeepSeek 的流式文本响应。\n\n",
|
|
|
|
+ "我正在逐块返回文本内容。\n",
|
|
|
|
+ "这种方式可以实现实时输出效果。\n",
|
|
|
|
+ "这种方式可以实现实时输出效果。\n",
|
|
|
|
+ "最后一块内容。\n"
|
|
|
|
+ ];
|
|
|
|
+
|
|
|
|
+ // 逐块输出文本
|
|
|
|
+ foreach ($textChunks as $chunk) {
|
|
|
|
+ echo $chunk;
|
|
|
|
+ ob_flush(); // 刷新PHP输出缓冲区
|
|
|
|
+ flush(); // 刷新系统缓冲区
|
|
|
|
+ sleep(1); // 暂停0.3秒模拟网络延迟
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public function deepseek(){
|
|
|
|
+ $config = config('deepseek');
|
|
|
|
+ $apiKey = $config['key']; //
|
|
|
|
+ $url = 'https://api.deepseek.com/chat/completions';
|
|
|
|
+
|
|
|
|
+ $params = input();
|
|
|
|
+ $data = [
|
|
|
|
+ 'model' => $params['model'],
|
|
|
|
+ 'messages' => $params['messages'],
|
|
|
|
+// "frequency_penalty" => 0,
|
|
|
|
+ "max_tokens" => 2048,
|
|
|
|
+ /*"presence_penalty" => 0,
|
|
|
|
+ "response_format" => [
|
|
|
|
+ "type" => $params['type']
|
|
|
|
+ ],
|
|
|
|
+ "stop" => null,*/
|
|
|
|
+ "stream" => true,
|
|
|
|
+ /*"stream_options" => null,
|
|
|
|
+ "temperature" => 1,
|
|
|
|
+ "top_p" => 1,
|
|
|
|
+ "tools" => null,
|
|
|
|
+ "tool_choice" => "none",
|
|
|
|
+ "logprobs" => false,
|
|
|
|
+ "top_logprobs" => null*/
|
|
|
|
+ ];
|
|
|
|
+
|
|
|
|
+ // 初始化cURL会话
|
|
|
|
+ $ch = curl_init($url);
|
|
|
|
+
|
|
|
|
+// 设置cURL选项
|
|
|
|
+ curl_setopt_array($ch, [
|
|
|
|
+ CURLOPT_RETURNTRANSFER => true,
|
|
|
|
+ CURLOPT_SSL_VERIFYPEER => false,
|
|
|
|
+ CURLOPT_POST => true,
|
|
|
|
+ CURLOPT_POSTFIELDS => json_encode($data),
|
|
|
|
+ CURLOPT_HTTPHEADER => [
|
|
|
|
+ 'Content-Type: application/json',
|
|
|
|
+ 'Authorization: Bearer ' . $apiKey,
|
|
|
|
+ 'Accept: text/event-stream' // 重要:声明接受事件流
|
|
|
|
+ ],
|
|
|
|
+ CURLOPT_WRITEFUNCTION => function($ch, $data) {
|
|
|
|
+ // 实时处理流数据
|
|
|
|
+ $lines = explode("\n", $data);
|
|
|
|
+
|
|
|
|
+ foreach ($lines as $line) {
|
|
|
|
+ if (strpos($line, 'data: ') === 0) {
|
|
|
|
+ $jsonStr = substr($line, 6); // 移除"data: "前缀
|
|
|
|
+
|
|
|
|
+ // 跳过结束标记
|
|
|
|
+ if (trim($jsonStr) === '[DONE]') {
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 解析JSON数据
|
|
|
|
+ $eventData = json_decode($jsonStr, true);
|
|
|
|
+
|
|
|
|
+ if (isset($eventData['choices'][0]['delta']['content'])) {
|
|
|
|
+ $content = $eventData['choices'][0]['delta']['content'];
|
|
|
|
+
|
|
|
|
+ // 输出内容并立即刷新缓冲区
|
|
|
|
+ echo $content;
|
|
|
|
+ ob_flush();
|
|
|
|
+ flush();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return strlen($data); // 返回已处理数据长度
|
|
|
|
+ }
|
|
|
|
+ ]);
|
|
|
|
+
|
|
|
|
+// 执行请求
|
|
|
|
+ $response = curl_exec($ch);
|
|
|
|
+
|
|
|
|
+// 错误处理
|
|
|
|
+ if (curl_errno($ch)) {
|
|
|
|
+ echo 'Error: ' . curl_error($ch);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+// 关闭连接
|
|
|
|
+ curl_close($ch);
|
|
|
|
+
|
|
|
|
+ // 创建cURL句柄
|
|
|
|
+ /* $ch = curl_init();
|
|
|
|
+ curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
|
|
|
|
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
|
|
|
|
+ curl_setopt($ch, CURLOPT_URL, $url);
|
|
|
|
+ curl_setopt($ch, CURLOPT_POST, true);
|
|
|
|
+ curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
|
|
|
+ curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
|
|
+ 'Content-Type: application/json',
|
|
|
|
+ 'Authorization: Bearer ' . $apiKey,
|
|
|
|
+ 'Accept: text/event-stream' // 重要:声明接受事件流
|
|
|
|
+ ]);
|
|
|
|
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 直接输出
|
|
|
|
+ curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $chunk) {
|
|
|
|
+ // 直接输出接收到的数据块
|
|
|
|
+ echo $chunk;
|
|
|
|
+// ob_flush();
|
|
|
|
+// flush();
|
|
|
|
+ return strlen($chunk); // 必须返回写入的字节数
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 执行请求
|
|
|
|
+ curl_exec($ch);
|
|
|
|
+
|
|
|
|
+ // 检查错误
|
|
|
|
+ if (curl_errno($ch)) {
|
|
|
|
+ // 错误处理
|
|
|
|
+ echo "\n\n[ERROR] API请求失败: " . curl_error($ch);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ curl_close($ch);*/
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+}
|