正在显示
3 个修改的文件
包含
558 行增加
和
23 行删除
app/Helper/Resource.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace App\Helper; | ||
| 4 | + | ||
| 5 | + | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * api结果处理 | ||
| 9 | + * @author:dc | ||
| 10 | + * @time 2023/12/16 14:56 | ||
| 11 | + * Class Resource | ||
| 12 | + * @package GlobalSo\Tool\Gpt | ||
| 13 | + */ | ||
| 14 | +abstract class Resource { | ||
| 15 | + | ||
| 16 | + /** | ||
| 17 | + * 函数 | ||
| 18 | + * @var array | ||
| 19 | + */ | ||
| 20 | + protected $func = []; | ||
| 21 | + | ||
| 22 | + /** | ||
| 23 | + * 是否是调试模式 | ||
| 24 | + * @var bool | ||
| 25 | + */ | ||
| 26 | + public static $debugInfo = false; | ||
| 27 | + | ||
| 28 | + /** | ||
| 29 | + * 获取内容body所有内容 | ||
| 30 | + * @return string | ||
| 31 | + */ | ||
| 32 | + abstract public function getBody(): string; | ||
| 33 | + | ||
| 34 | + | ||
| 35 | + /** | ||
| 36 | + * 获取数据的状态 | ||
| 37 | + * @return int | ||
| 38 | + */ | ||
| 39 | + abstract public function getCode(): int; | ||
| 40 | + | ||
| 41 | + /** | ||
| 42 | + * 验证code 默认200 | ||
| 43 | + * @param int $code | ||
| 44 | + * @return bool | ||
| 45 | + * @time 2023/12/16 16:45 | ||
| 46 | + */ | ||
| 47 | + public function checkCode(int $code = 200):bool { | ||
| 48 | + return $this->getCode() === $code; | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + | ||
| 52 | + /** | ||
| 53 | + * 获取内容 数据 | ||
| 54 | + * @return mixed | ||
| 55 | + * @author:dc | ||
| 56 | + * @time 2024/1/2 15:56 | ||
| 57 | + */ | ||
| 58 | + abstract public function getData(); | ||
| 59 | + | ||
| 60 | + | ||
| 61 | + /** | ||
| 62 | + * 获取错误消息 | ||
| 63 | + * @return string | ||
| 64 | + */ | ||
| 65 | + abstract public function getMessage(): string; | ||
| 66 | + | ||
| 67 | + | ||
| 68 | + /** | ||
| 69 | + * 是否是函数 | ||
| 70 | + * @return bool | ||
| 71 | + * @author:dc | ||
| 72 | + * @time 2024/1/10 12:35 | ||
| 73 | + */ | ||
| 74 | + public function isFun(){ | ||
| 75 | + | ||
| 76 | + if($this->getBodyFunc()){ | ||
| 77 | + return true; | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + return false; | ||
| 81 | + } | ||
| 82 | + | ||
| 83 | + /** | ||
| 84 | + * @return array|mixed | ||
| 85 | + * @author:dc | ||
| 86 | + * @time 2024/4/9 15:55 | ||
| 87 | + */ | ||
| 88 | + public function getBodyFunc(){ | ||
| 89 | + $json = @json_decode($this->getBody(),1); | ||
| 90 | + if(is_array($json) && isset($json['tool_calls'])){ | ||
| 91 | + return $json['tool_calls']; | ||
| 92 | + } | ||
| 93 | + return []; | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + /** | ||
| 97 | + * 这个是提交了多少token | ||
| 98 | + * @return int | ||
| 99 | + * @author:dc | ||
| 100 | + * @time 2023/12/18 13:37 | ||
| 101 | + */ | ||
| 102 | + public function getPromptToken():int{ | ||
| 103 | + // 这个是老版本 | ||
| 104 | + if(isset($this->getUsage()['prompt_tokens'])){ | ||
| 105 | + return (int) ($this->getUsage()['prompt_tokens']??0); | ||
| 106 | + } | ||
| 107 | + // 这个是新版本 | ||
| 108 | + $num = 0; | ||
| 109 | + foreach ($this->getUsage() as $item){ | ||
| 110 | + $num += (int) ($item['prompt_tokens']??0); | ||
| 111 | + } | ||
| 112 | + return $num; | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + /** | ||
| 116 | + * 这个是吐出了多少token | ||
| 117 | + * @return int | ||
| 118 | + * @author:dc | ||
| 119 | + * @time 2023/12/18 13:37 | ||
| 120 | + */ | ||
| 121 | + public function getCompletionToken():int{ | ||
| 122 | + // 这个是老版本 | ||
| 123 | + if(isset($this->getUsage()['completion_tokens'])){ | ||
| 124 | + return (int) ($this->getUsage()['completion_tokens']??0); | ||
| 125 | + } | ||
| 126 | + // 这个是新版本 | ||
| 127 | + $num = 0; | ||
| 128 | + foreach ($this->getUsage() as $item){ | ||
| 129 | + $num += (int) ($item['completion_tokens']??0); | ||
| 130 | + } | ||
| 131 | + return $num; | ||
| 132 | + } | ||
| 133 | + | ||
| 134 | + | ||
| 135 | + /** | ||
| 136 | + * 获取函数 | ||
| 137 | + * @return array|mixed | ||
| 138 | + */ | ||
| 139 | + public function getFunc($call=null,...$params) | ||
| 140 | + { | ||
| 141 | + if(!$this->func){ | ||
| 142 | + $this->func = $this->getBodyFunc(); | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + // {"code":200,"func":{"name":"taocan","arguments":{"attribute":"\u7528\u91cf","usetime":"\u4eca\u5929"}}} | ||
| 146 | + $function = $this->func; | ||
| 147 | + | ||
| 148 | + if(!empty($this->func) && is_array($this->func)) { | ||
| 149 | + if (!isset($this->func['name'])) { | ||
| 150 | + $this->func = [$function[0]]; | ||
| 151 | + } | ||
| 152 | + } | ||
| 153 | + | ||
| 154 | + $result = $this->getFuncAll($call,...$params); | ||
| 155 | + | ||
| 156 | + $this->func = $function; | ||
| 157 | + | ||
| 158 | + return array_values($result)[0]??null; | ||
| 159 | + } | ||
| 160 | + | ||
| 161 | + | ||
| 162 | + /** | ||
| 163 | + * 获取所有的函数体 | ||
| 164 | + * @param null $call | ||
| 165 | + * @param mixed ...$params | ||
| 166 | + * @return array|false|mixed | ||
| 167 | + * @author:dc | ||
| 168 | + * @time 2024/3/11 14:32 | ||
| 169 | + */ | ||
| 170 | + public function getFuncAll($call=null,...$params) | ||
| 171 | + { | ||
| 172 | + if(!$this->func){ | ||
| 173 | + $this->func = $this->getBodyFunc(); | ||
| 174 | + } | ||
| 175 | + // {"code":200,"func":{"name":"taocan","arguments":{"attribute":"\u7528\u91cf","usetime":"\u4eca\u5929"}}} | ||
| 176 | + $function = []; | ||
| 177 | + if(!empty($this->func) && is_array($this->func)){ | ||
| 178 | + $function = $this->func; | ||
| 179 | + } | ||
| 180 | + | ||
| 181 | + if($call){ | ||
| 182 | + if($function){ | ||
| 183 | + // 是否是老版本 | ||
| 184 | + if(!empty($function['name'])){ | ||
| 185 | + // 整理参数 | ||
| 186 | + return [$function['name']=> $this->callFunc( | ||
| 187 | + $call, | ||
| 188 | + $function['name'], | ||
| 189 | + $params, | ||
| 190 | + $function['arguments']??[] | ||
| 191 | + )]; | ||
| 192 | + }else{ | ||
| 193 | + // 循环 函数 | ||
| 194 | + $result = []; | ||
| 195 | + foreach ($function as $func){ | ||
| 196 | + $result[$func['name']] = $this->callFunc( | ||
| 197 | + $call, | ||
| 198 | + $func['name'], | ||
| 199 | + $params, | ||
| 200 | + $func['arguments']??[] | ||
| 201 | + ); | ||
| 202 | + } | ||
| 203 | + return $result; | ||
| 204 | + } | ||
| 205 | + } | ||
| 206 | + return []; | ||
| 207 | + } | ||
| 208 | + | ||
| 209 | + return $function; | ||
| 210 | + } | ||
| 211 | + | ||
| 212 | + | ||
| 213 | + | ||
| 214 | + /** | ||
| 215 | + * call 回调 | ||
| 216 | + * @param $call | ||
| 217 | + * @param $funcName | ||
| 218 | + * @param $params | ||
| 219 | + * @return false|mixed | ||
| 220 | + * @author:dc | ||
| 221 | + * @time 2024/3/11 11:35 | ||
| 222 | + */ | ||
| 223 | + private function callFunc($call,$funcName,$params,$attr) { | ||
| 224 | + $params[] = $attr; | ||
| 225 | + // 匿名函数 | ||
| 226 | + if($call instanceof \Closure){ | ||
| 227 | + return $call($funcName, ...$params); | ||
| 228 | + } | ||
| 229 | + // 类名称 | ||
| 230 | + elseif ($call){ | ||
| 231 | + // 定义了类 | ||
| 232 | + if(is_object($call) || class_exists($call)){ | ||
| 233 | + // 掉用类 | ||
| 234 | + return call_user_func( | ||
| 235 | + [$call,$funcName] | ||
| 236 | + ,...$params | ||
| 237 | + ); | ||
| 238 | + }else | ||
| 239 | + return call_user_func( | ||
| 240 | + [$call,$funcName] | ||
| 241 | + ,...$params | ||
| 242 | + ); | ||
| 243 | + } | ||
| 244 | + } | ||
| 245 | + | ||
| 246 | + | ||
| 247 | + | ||
| 248 | + | ||
| 249 | + | ||
| 250 | +} |
app/Helper/Stream.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace App\Helper; | ||
| 4 | + | ||
| 5 | + | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * 流输出 | ||
| 9 | + * @author:dc | ||
| 10 | + * @time 2024/1/2 14:46 | ||
| 11 | + * Class Stream | ||
| 12 | + * @package GlobalSo\Tool\Gpt\Resource | ||
| 13 | + */ | ||
| 14 | +class Stream extends Resource{ | ||
| 15 | + | ||
| 16 | + /** | ||
| 17 | + * body内容 | ||
| 18 | + * @var string | ||
| 19 | + */ | ||
| 20 | + private $body = ''; | ||
| 21 | + | ||
| 22 | + /** | ||
| 23 | + * 流输出的文本 | ||
| 24 | + * @var string | ||
| 25 | + */ | ||
| 26 | + private $text = ''; | ||
| 27 | + | ||
| 28 | + /** | ||
| 29 | + * http 状态 | ||
| 30 | + * @var int | ||
| 31 | + */ | ||
| 32 | + private $status = 200; | ||
| 33 | + | ||
| 34 | + | ||
| 35 | + /** | ||
| 36 | + * @var \Psr\Http\Message\StreamInterface | ||
| 37 | + */ | ||
| 38 | + private $stream; | ||
| 39 | + | ||
| 40 | + /** | ||
| 41 | + * @var array 使用了多少token | ||
| 42 | + */ | ||
| 43 | + private $usage = []; | ||
| 44 | + | ||
| 45 | + | ||
| 46 | + /** | ||
| 47 | + * Resource constructor. | ||
| 48 | + * @param \Psr\Http\Message\StreamInterface|array $response | ||
| 49 | + */ | ||
| 50 | + public function __construct($response) | ||
| 51 | + { | ||
| 52 | + if($response instanceof \Psr\Http\Message\StreamInterface){ | ||
| 53 | + $this->stream = $response; | ||
| 54 | + } | ||
| 55 | + // 数组,带上下文 | ||
| 56 | + elseif(is_array($response)){ | ||
| 57 | + $this->stream = false; | ||
| 58 | + // 回答的文本 | ||
| 59 | + $this->text = end($response); | ||
| 60 | + // 计算token | ||
| 61 | + $this->usage = [ | ||
| 62 | + [ | ||
| 63 | + 'model'=>'', | ||
| 64 | + ] | ||
| 65 | + ]; | ||
| 66 | + | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + /** | ||
| 72 | + * 最后一行 | ||
| 73 | + * @var array | ||
| 74 | + */ | ||
| 75 | + private $endLine = []; | ||
| 76 | + | ||
| 77 | + | ||
| 78 | + /** | ||
| 79 | + * 获取流输出内容 | ||
| 80 | + * @return null | ||
| 81 | + * @author:dc | ||
| 82 | + * @time 2024/1/2 13:57 | ||
| 83 | + */ | ||
| 84 | + public function getStreamContent(\Closure $call) | ||
| 85 | + { | ||
| 86 | + // 文本 | ||
| 87 | + if($this->stream===false){ | ||
| 88 | + $this->body = $this->text; | ||
| 89 | + $call($this->text); | ||
| 90 | + } | ||
| 91 | + // 流输出 | ||
| 92 | + else{ | ||
| 93 | + while (!$this->stream->eof()) { | ||
| 94 | + // 获取一行数据 | ||
| 95 | + $line = $this->getStreamContentLine(); | ||
| 96 | + // 必须要有数据 | ||
| 97 | + if($line){ | ||
| 98 | + // 解析成数组 | ||
| 99 | + $arr = @json_decode($line,true); | ||
| 100 | + // 必须是一个数组 | ||
| 101 | + if(is_array($arr)){ | ||
| 102 | + | ||
| 103 | + // 是否是函数 | ||
| 104 | + if(!empty($arr['func'])){ | ||
| 105 | + $this->func = $arr['func'] ? : ($arr['tool_calls']??[]); | ||
| 106 | + continue; | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + // 这里是新版本 | ||
| 110 | + // 文本 | ||
| 111 | + if(isset($arr['text'])){ | ||
| 112 | + // 拼接 | ||
| 113 | + $this->text .= $arr['text']; | ||
| 114 | + // 调用 | ||
| 115 | + $call($arr['text']); | ||
| 116 | + } | ||
| 117 | + // 到了最后一行 | ||
| 118 | + if (isset($arr['usage'])){ | ||
| 119 | + $this->usage = $arr['usage']; | ||
| 120 | + $this->endLine = $arr; | ||
| 121 | + } | ||
| 122 | + }else{ | ||
| 123 | + // 拼接 | ||
| 124 | + $this->text .= $line; | ||
| 125 | + // 这里兼容下老版本 | ||
| 126 | + $call($line); | ||
| 127 | + } | ||
| 128 | + | ||
| 129 | + } | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + // 兼容老版本 老版本没办法获取 实际使用了多少token | ||
| 133 | + if(!$this->usage){ | ||
| 134 | + $this->usage = [ | ||
| 135 | + [ | ||
| 136 | + 'model'=>'', | ||
| 137 | + ] | ||
| 138 | + ]; | ||
| 139 | + } | ||
| 140 | + | ||
| 141 | + } | ||
| 142 | + | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + /** | ||
| 146 | + * 流 读取一行 | ||
| 147 | + * @return string | ||
| 148 | + * @author:dc | ||
| 149 | + * @time 2024/1/2 14:16 | ||
| 150 | + */ | ||
| 151 | + private function getStreamContentLine(){ | ||
| 152 | + $text = ''; | ||
| 153 | + while (!$this->stream->eof()){ | ||
| 154 | + // 读取一个字符串 | ||
| 155 | + $t = $this->stream->read(1); | ||
| 156 | + $this->body .= $t; | ||
| 157 | + if($t === "\n"){ | ||
| 158 | + break; | ||
| 159 | + } | ||
| 160 | + // 结束了 | ||
| 161 | + if(ord($t)==1){ | ||
| 162 | + break; | ||
| 163 | + } | ||
| 164 | + $text .= $t; | ||
| 165 | + | ||
| 166 | + } | ||
| 167 | + return $text; | ||
| 168 | + } | ||
| 169 | + | ||
| 170 | + | ||
| 171 | + /** | ||
| 172 | + * 流输出的所有内容 | ||
| 173 | + * @return string | ||
| 174 | + */ | ||
| 175 | + public function getBody(): string | ||
| 176 | + { | ||
| 177 | + return $this->body; | ||
| 178 | + } | ||
| 179 | + | ||
| 180 | + | ||
| 181 | + /** | ||
| 182 | + * @return int | ||
| 183 | + */ | ||
| 184 | + public function getCode(): int | ||
| 185 | + { | ||
| 186 | + return 200; | ||
| 187 | + } | ||
| 188 | + | ||
| 189 | + | ||
| 190 | + /** | ||
| 191 | + * 这个是文本内容,就是回答的内容 | ||
| 192 | + * @return array|string | ||
| 193 | + * @author:dc | ||
| 194 | + * @time 2024/1/2 14:57 | ||
| 195 | + */ | ||
| 196 | + public function getData() | ||
| 197 | + { | ||
| 198 | + return $this->text; | ||
| 199 | + } | ||
| 200 | + | ||
| 201 | + /** | ||
| 202 | + * @return string | ||
| 203 | + */ | ||
| 204 | + public function getMessage(): string | ||
| 205 | + { | ||
| 206 | + return ''; | ||
| 207 | + } | ||
| 208 | + | ||
| 209 | + | ||
| 210 | + /** | ||
| 211 | + * @return array | ||
| 212 | + * @author:dc | ||
| 213 | + * @time 2024/1/2 14:57 | ||
| 214 | + */ | ||
| 215 | + public function getUsage(): array | ||
| 216 | + { | ||
| 217 | + return $this->usage; | ||
| 218 | + } | ||
| 219 | + | ||
| 220 | + | ||
| 221 | + /** | ||
| 222 | + * 是否已经输出过头部了 | ||
| 223 | + * @var bool | ||
| 224 | + */ | ||
| 225 | + protected static $isHeader = false; | ||
| 226 | + | ||
| 227 | + /** | ||
| 228 | + * 是否是sse输出 | ||
| 229 | + * @var bool | ||
| 230 | + */ | ||
| 231 | + public static $echoSse = false; | ||
| 232 | + | ||
| 233 | + | ||
| 234 | + /** | ||
| 235 | + * 设置头部 | ||
| 236 | + * @param false $sse | ||
| 237 | + * @author:dc | ||
| 238 | + * @time 2024/5/31 15:02 | ||
| 239 | + */ | ||
| 240 | + public static function setStreamHeader(array $header=[]){ | ||
| 241 | + // 默认配置的 头信息 输出一次即可 | ||
| 242 | + if(!self::$isHeader){ | ||
| 243 | + // 流输出 必须的 头信息 | ||
| 244 | + if(self::$echoSse) header("Content-Type:event-stream;Charset=UTF-8;");//event-stream 开启这个数据必须是规定格式 | ||
| 245 | + header("cache-control:no-cache;"); // 告诉浏览器不要进行数据缓存 | ||
| 246 | + header('X-Accel-Buffering: no'); // 关键是加了这一行。告诉浏览器不进行输出的缓冲 | ||
| 247 | + header('Access-Control-Expose-Headers: Content-Disposition, Content-Length, X-Content-Range, X-Duration'); | ||
| 248 | + header('Content-Type: application/json'); // json数据头 | ||
| 249 | + header('Access-Control-Allow-Origin:*'); // 这个是 跨域 | ||
| 250 | + self::$isHeader = true; | ||
| 251 | + } | ||
| 252 | + | ||
| 253 | + // 输出其他header | ||
| 254 | + foreach ($header as $head){ | ||
| 255 | + header($head); | ||
| 256 | + } | ||
| 257 | + | ||
| 258 | + } | ||
| 259 | + | ||
| 260 | + /** | ||
| 261 | + * 其他地方调用,在ai返回前后都可以调用这个 | ||
| 262 | + * @param $data | ||
| 263 | + * @param string $type 数据类型 | ||
| 264 | + * @author:dc | ||
| 265 | + * @time 2024/5/31 15:05 | ||
| 266 | + */ | ||
| 267 | + public static function echo_flush($data,string $type='text'){ | ||
| 268 | + | ||
| 269 | + self::setStreamHeader(); | ||
| 270 | + | ||
| 271 | + echo self::$echoSse ? en_sse_data($data,$type) : $data; | ||
| 272 | + | ||
| 273 | + ob_flush(); | ||
| 274 | + flush(); | ||
| 275 | + } | ||
| 276 | + | ||
| 277 | + | ||
| 278 | + /** | ||
| 279 | + * 输出 信息到前端 | ||
| 280 | + * @author:dc | ||
| 281 | + * @time 2024/5/31 10:16 | ||
| 282 | + */ | ||
| 283 | + public function echo(){ | ||
| 284 | + | ||
| 285 | + // 如果用户断开,继续脚本的运行 | ||
| 286 | + ignore_user_abort(1); | ||
| 287 | + set_time_limit(400); | ||
| 288 | +// // 先把之前的内容 也发送到浏览器 | ||
| 289 | +// @ob_implicit_flush(); // 开启隐式刷新 使用 echo函数时会立即发送到浏览器 开启后就不需要flush调用了 | ||
| 290 | + // 输出内容 | ||
| 291 | + $this->getStreamContent(function ($text) { | ||
| 292 | + self::echo_flush($text); | ||
| 293 | + }); | ||
| 294 | + if(self::$debugInfo){ | ||
| 295 | + self::echo_flush($this->endLine['debug']??[],'debug'); | ||
| 296 | + } | ||
| 297 | + } | ||
| 298 | + | ||
| 299 | + | ||
| 300 | + | ||
| 301 | + | ||
| 302 | +} |
| @@ -9,6 +9,7 @@ | @@ -9,6 +9,7 @@ | ||
| 9 | 9 | ||
| 10 | namespace App\Http\Logic\Bside\Gpt; | 10 | namespace App\Http\Logic\Bside\Gpt; |
| 11 | 11 | ||
| 12 | +use App\Helper\Stream; | ||
| 12 | use App\Http\Logic\Bside\BaseLogic; | 13 | use App\Http\Logic\Bside\BaseLogic; |
| 13 | use App\Models\Gpt\Chat; | 14 | use App\Models\Gpt\Chat; |
| 14 | use App\Models\Gpt\ChatItem; | 15 | use App\Models\Gpt\ChatItem; |
| @@ -61,29 +62,11 @@ class ChatLogic extends BaseLogic | @@ -61,29 +62,11 @@ class ChatLogic extends BaseLogic | ||
| 61 | ]; | 62 | ]; |
| 62 | } | 63 | } |
| 63 | $data = ['message' => $message]; | 64 | $data = ['message' => $message]; |
| 64 | - | ||
| 65 | - return response()->stream(function () use ($gptService, $data, $chatId) { | ||
| 66 | - $fullResponse = ''; // **存储完整 AI 回复** | ||
| 67 | - $stream = $gptService->get_ai_chat($data); // 获取流 | ||
| 68 | - if ($stream) { | ||
| 69 | - while (!$stream->eof()) { | ||
| 70 | - $chunk = $stream->read(1024); // **逐步读取数据块** | ||
| 71 | - echo "data: " . json_encode(['message' => $chunk], JSON_UNESCAPED_UNICODE) . "\n\n"; | ||
| 72 | - ob_flush(); | ||
| 73 | - flush(); | ||
| 74 | - $fullResponse .= $chunk; // **拼接完整 AI 回复** | ||
| 75 | - } | ||
| 76 | - } else { | ||
| 77 | - $fullResponse = '服务器繁忙,请重试'; | ||
| 78 | - echo "data: " . json_encode(['message' => $fullResponse], JSON_UNESCAPED_UNICODE) . "\n\n"; | ||
| 79 | - } | ||
| 80 | - // **流结束后,保存完整 AI 回复** | ||
| 81 | - $this->saveChatItem($chatId, $fullResponse, 1); | ||
| 82 | - }, 200, [ | ||
| 83 | - "Content-Type" => "text/event-stream", | ||
| 84 | - "Cache-Control" => "no-cache", | ||
| 85 | - "Connection" => "keep-alive", | ||
| 86 | - ]); | 65 | + $stream = $gptService->get_ai_chat($data); // 获取流 |
| 66 | + $streamHelper = new Stream($stream); | ||
| 67 | + $streamHelper->echo(); | ||
| 68 | + $res_message = $streamHelper->getData(); | ||
| 69 | + return $this->saveChatItem($chatId, $res_message,1); | ||
| 87 | } | 70 | } |
| 88 | 71 | ||
| 89 | /** | 72 | /** |
-
请 注册 或 登录 后发表评论