作者 Richer

功能完成

APP_NAME=ai-chat.weigoutong.com
APP_NAME=ai-assistant
APP_NAME_ZH=AI助手
APP_ENV=production
APP_KEY=base64:3I5SI8L6rBQRvOZranhb5Zt+OS7h3WqWWgrdkHTbnFE=
APP_ENV=local
APP_KEY=base64:uiieHf/mBMwSnOuMwnzoclXT97jhhdxC1KoV8KzO+qM=
APP_DEBUG=true
APP_URL=http://ai-chat.weigoutong.com
APP_DOMAIN = ai-chat.weigoutong.com
APP_URL=http://ai-assistant.dev.zhangxinkeji.com
APP_DOMAIN = ai-assistant.dev.zhangxinkeji.com
LOG_CHANNEL=daily
DB_CONNECTION=mysql
DB_PORT=3306
DB_HOST=localhost
DB_DATABASE=ai-chat
DB_HOST=rm-uf63r3yhy6806wdpjqo.mysql.rds.aliyuncs.com
DB_DATABASE=xzy.ai-assistant
DB_USERNAME=root
DB_PASSWORD=4deec965296ff346
DB_PASSWORD=zhangxinkeji@2003
# 远程数据库 美国服务器使用该配置
#DB_CONNECTION_REMOTE=mysql
#DB_PORT_REMOTE=3306
#DB_HOST_REMOTE=rm-uf63r3yhy6806wdpjqo.mysql.rds.aliyuncs.com
#DB_DATABASE_REMOTE=xzy.chatgpt
#DB_DATABASE_REMOTE=ai-assistant
#DB_USERNAME_REMOTE=root
#DB_PASSWORD_REMOTE=zhangxinkeji@2003
... ... @@ -27,9 +27,9 @@ DB_PASSWORD=4deec965296ff346
DB_CONNECTION_REMOTE=mysql
DB_PORT_REMOTE=3306
DB_HOST_REMOTE=47.251.45.96
DB_DATABASE_REMOTE=ai-chat
DB_USERNAME_REMOTE=ai-chat
DB_PASSWORD_REMOTE=5E7mKZ3jKMTrD6wK
DB_DATABASE_REMOTE=xzy.ai-assistant
DB_USERNAME_REMOTE=xzy.ai-assistant
DB_PASSWORD_REMOTE=yEcGyC78WBNTCYCm
BROADCAST_DRIVER=log
CACHE_DRIVER=redis
... ... @@ -64,7 +64,7 @@ PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
JWT_SECRET=3QnPNbxLuAN1DToiUSi9IKVtjN8yh0XZE0n3U77owdGF4GqhJfakCMqFyWDc0c75
JWT_SECRET=kIno1YeaMFvyNAADjNfQ4pRhF1Ikyq55YKkyuGM19amYYMMpy0wNlBXMilfqLLEp
# alipay 配置
ALI_APP_ID=
... ... @@ -79,6 +79,7 @@ WECHAT_MINI_APPID=wxd7941b64f8b9291b
WECHAT_MINI_SECRET=b4d742d47f9f08d3242ca8a426326a7a
#openai
OPENAI_API_KEY=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
OPENAI_API_KEY=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
#OPENAI_API_KEY=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
#OPENAI_API_KEY=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
OPENAI_ORGANIZATION=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
... ...
... ... @@ -16,6 +16,7 @@
*/
namespace App\Models\Traits;
use App\Jobs\ChatRecordItemJob;
use App\Models\Chat\ChatRecordItem;
use App\Models\System\SystemSetting;
use App\Models\User\TimesRecord;
... ... @@ -190,9 +191,17 @@ trait ChatTrait
* @param string $type
* @return string
*/
public static function sendRequest($open_ai, $send_data, $type = 'stream')
public static function sendRequest1($open_ai, $send_data, $type = 'stream')
{
$answer = '';
if($type === 'general') {
$response = $open_ai->chatCompletions()->create(
new \Tectalic\OpenAi\Models\ChatCompletions\CreateRequest($send_data)
)->toModel();
$answer = $response->choices[0]->message->content;
} else {
if ($type === 'chunked') {
// 设置响应头信息
header('Access-Control-Allow-Credentials: true');
... ... @@ -213,6 +222,11 @@ trait ChatTrait
// header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
// header('Access-Control-Allow-Headers: Content-Type');
// header('Connection: keep-alive');
} elseif ($type === 'stream') {
header('Content-type: text/event-stream');
header('Cache-Control: no-cache');
ob_end_flush();
}
$complete = $open_ai->chat($send_data, function ($curl_info, $response) use (&$answer) {
//闭包函数处理流
$data = [];
... ... @@ -234,6 +248,8 @@ trait ChatTrait
$content = $message['choices'][0]['delta']['content'] ?? '';
$answer .= $content;
// record_log('openai', '内容: ' . $content );
// echo str_replace("\n", "\\n", $content )."\n\n";
dump($content);
echo urlencode($content) . "\r\n";
}
}
... ... @@ -241,47 +257,145 @@ trait ChatTrait
flush();
return strlen($response);
});
} elseif ($type === 'stream') {
header('Content-type: text/event-stream');
}
return $answer;
}
/**
* 发送请求
* @param $open_ai
* @param $send_data
* @param string $type
* @return string
*/
public static function sendRequest($open_ai, $send_data, $type = 'stream')
{
$answer = '';
if ($type === 'chunked') {
// 设置响应头信息
header('Access-Control-Allow-Credentials: true');
// 设置响应头信息
header('Transfer-Encoding: chunked');
header('Cache-Control: no-cache');
ob_end_flush();
$complete = $open_ai->chat($send_data, function ($curl_info, $data) use (&$answer) {
// record_log($this->log_channel, '开始请求' );
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
header('Connection: keep-alive');
header('X-Accel-Buffering: no');
$deltas = explode("\n", $data);
$content = '';
foreach ($deltas as $delta) {
if (strpos($delta, 'data: ') !== 0) {
// header('Access-Control-Allow-Credentials: true');
// // 设置响应头信息
// header('Transfer-Encoding: chunked');
// header('Content-Type: text/plain');
// header('Cache-Control: no-cache');
// header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
// header('Access-Control-Allow-Headers: Content-Type');
// header('Connection: keep-alive');
$complete = $open_ai->chat($send_data, function ($curl_info, $response) use (&$answer) {
//闭包函数处理流
$data = [];
$lines = explode("\n", $response);
foreach ($lines as $line) {
if (!str_contains($line, ':')) {
continue;
}
$json = json_decode(substr($delta, 6));
if (isset($json->choices[0]->delta)) {
$content = $json->choices[0]->delta->content ?? "";
$answer .= $content;
} elseif (isset($json->error->message)) {
$content = $json->error->message;
} elseif(trim($delta) == "data: [DONE]") {
$content = " ";
[$name, $value] = explode(':', $line, 2);
if ($name == 'data') {
$data[] = trim($value);
}
}
foreach ($data as $message) {
if ('[DONE]' === $message) {
echo "0\r\n\r\n";
} else {
$content = "对不起,我不知道怎么去回答。";
$message = json_decode($message, true);
$content = $message['choices'][0]['delta']['content'] ?? '';
$answer .= $content;
// record_log('openai', '内容: ' . $content );
echo urlencode($content) . "\r\n";
}
echo str_replace("\n", "\\n", $content )."\n\n";
}
ob_flush();
flush();
return strlen($response);
});
} elseif ($type === 'stream') {
header('Content-type: text/event-stream');
header('Cache-Control: no-cache');
$complete = $open_ai->chat($send_data, function ($curl_info, $response) use (&$answer) {
//闭包函数处理流
$data = [];
$lines = explode("\n", $response);
foreach ($lines as $line) {
if (!str_contains($line, ':')) {
continue;
}
[$name, $value] = explode(':', $line, 2);
if ($name == 'data') {
$data[] = trim($value);
}
}
foreach ($data as $message) {
if ('[DONE]' === $message) {
echo "0\r\n\r\n";
} else {
$message = json_decode($message, true);
$content = $message['choices'][0]['delta']['content'] ?? '';
$answer .= $content;
// record_log('openai', '内容: ' . $content );
// echo urlencode($content) . "\r\n";
echo str_replace("\n", "\\n", $content )."\n\n";
// record_log($this->log_channel, '内容: ' . $content );
if (connection_aborted()) {
return 0;
}
}
echo PHP_EOL;
// echo PHP_EOL;
// ob_flush();
return strlen($data);
ob_flush();
flush();
return strlen($response);
});
echo "event: stop\n";
echo "data: stopped\n\n";
// ob_end_flush();
// $complete = $open_ai->chat($send_data, function ($curl_info, $data) use (&$answer) {
// record_log($this->log_channel, '开始请求' );
//
// $deltas = explode("\n", $data);
// $content = '';
// foreach ($deltas as $delta) {
// if (strpos($delta, 'data: ') !== 0) {
// continue;
// }
// $json = json_decode(substr($delta, 6));
// if (isset($json->choices[0]->delta)) {
// $content = $json->choices[0]->delta->content ?? "";
// $answer .= $content;
// } elseif (isset($json->error->message)) {
// $content = $json->error->message;
// } elseif(trim($delta) == "data: [DONE]") {
// $content = " ";
// } else {
// $content = "对不起,我不知道怎么去回答。";
// }
//// echo "data: " .str_replace("\n", "\\n", $content )."\n\n";
// echo str_replace("\n", "\\n", $content )."\n\n";
// flush();
// }
//
// record_log($this->log_channel, '内容: ' . $content );
//
// if (connection_aborted()) {
// return 0;
// }
//
//// echo PHP_EOL;
//// ob_flush();
// return strlen($data);
// });
//
// echo "event: stop\n";
// echo "data: stopped\n\n";
} elseif($type === 'general') {
$response = $open_ai->chatCompletions()->create(
new \Tectalic\OpenAi\Models\ChatCompletions\CreateRequest($send_data)
... ...
... ... @@ -37,7 +37,7 @@ class ChatRecordItemObserver
{
// 创建成功后,计算每个站点间的距离
// $this->syncRecordItem($model);
ChatRecordItemJob::dispatch($model);
// ChatRecordItemJob::dispatch($model);
}
/**
... ...
... ... @@ -38,7 +38,7 @@ class ChatRecordObserver
// 创建成功后,同步
// $this->syncRecord($model);
// 如果服务器是在本地则不进行同步
ChatRecordJob::dispatch($model);
// ChatRecordJob::dispatch($model);
}
/**
... ...
... ... @@ -16,6 +16,8 @@
*/
namespace App\Services;
use App\Jobs\ChatRecordItemJob;
use App\Jobs\ChatRecordJob;
use App\Models\Category\Category;
use App\Models\Category\CategoryLabel;
use App\Models\Chat\ChatRecordItem;
... ... @@ -23,6 +25,7 @@ use App\Models\Chat\ChatRecord;
use App\Models\System\SystemSetting;
use App\Models\Traits\ChatTrait;
use App\Models\Traits\SystemSettingTrait;
use Carbon\Carbon;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
... ... @@ -175,6 +178,7 @@ class ChatRecordService extends BaseService
try {
// 创建本次的聊天记录
$model = $user->chatRecords()->create($record);
// 创建用户的聊天记录
$item = [
'user_id' => $user->id,
... ... @@ -186,18 +190,19 @@ class ChatRecordService extends BaseService
'ai_model' => $this->ai_model
];
$result = $model->items()->create($item);
if (!$result) {
$result1 = $model->items()->create($item);
if (!$result1) {
record_log('chat', '聊天', 'end');
DB::rollBack();
}
// TODO 根据标签组合完整的聊天内容
// 组合方式:分类起始语句+标签名称:标签明细,标签明细,标签名称:标签明细,提问内容
// 前置内容(每个对话场景的前置内容只有一个),标签内容(标签名称+用户选择的标签,每个标签以逗号分隔),用户输入内容
// 发送消息
$result = $this->send($user, $model, $body_all, $context, $stream);
if (!$result) {
$result2 = $this->send($user, $model, $body_all, $context, $stream);
if (!$result2) {
record_log('chat', '聊天', 'end');
return false;
}
... ... @@ -208,7 +213,14 @@ class ChatRecordService extends BaseService
DB::commit();
record_log('chat', '聊天', 'end');
return $result;
// 同步
ChatRecordJob::dispatch($model);
ChatRecordItemJob::dispatch($result1);
ChatRecordItemJob::dispatch($result2);
return $result2;
} catch (\Exception $e) {
$this->message = $e->getMessage();
... ... @@ -250,6 +262,8 @@ class ChatRecordService extends BaseService
return false;
}
ChatRecordItemJob::dispatch($result);
// 发送消息
$result = $this->send($user, $model, $body, $model->context, $stream);
DB::commit();
... ... @@ -343,8 +357,35 @@ class ChatRecordService extends BaseService
}
$model = $query->latest()->first();
if (\request('test') == 1) {
dump($model);
}
if (!$model) {
return ['timeout' => true];
}
// 获取缓存中的数据是否存在,如果存在
// 获取当前会话的缓存键
// add by Richer 于 2023年5月14日15:01:47 由于缓存是在另外服务器,所以只能通过上次聊天的时间怕判断
$item = $model->items->first();
// if (\request('test') == 1) {
// dump($item);
// }
$item = $model->items->last();
if (!$item) {
return ['timeout' => true];
}
if (Carbon::parse($item->created_at)->diffInMinutes(now()) >= 30){
return ['timeout' => true];
}
return ['timeout' => false];
// if (\request('test') == 1) {
// dump($item);
// }
$cache_key = 'chat_context_' . $model->id;
// 判断当前的聊天是否是基于上下文的聊天,如果是基于上下的文的聊天,需要携带上下文
// 从缓存中获取当前会话的上下文
... ...