|
@@ -0,0 +1,141 @@
|
|
|
|
|
+<?php
|
|
|
|
|
+// +----------------------------------------------------------------------
|
|
|
|
|
+// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
|
|
|
|
+// +----------------------------------------------------------------------
|
|
|
|
|
+// | Copyright (c) 2016~2023 https://www.crmeb.com All rights reserved.
|
|
|
|
|
+// +----------------------------------------------------------------------
|
|
|
|
|
+// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
|
|
|
|
+// +----------------------------------------------------------------------
|
|
|
|
|
+// | Author: CRMEB Team <admin@crmeb.com>
|
|
|
|
|
+// +----------------------------------------------------------------------
|
|
|
|
|
+declare (strict_types=1);
|
|
|
|
|
+
|
|
|
|
|
+namespace app\services\kefu;
|
|
|
|
|
+
|
|
|
|
|
+use crmeb\services\HttpService;
|
|
|
|
|
+use think\facade\Env;
|
|
|
|
|
+use think\facade\Log;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Coze 智能体调用服务
|
|
|
|
|
+ */
|
|
|
|
|
+class CozeServices
|
|
|
|
|
+{
|
|
|
|
|
+ protected $apiBase;
|
|
|
|
|
+
|
|
|
|
|
+ protected $apiToken;
|
|
|
|
|
+
|
|
|
|
|
+ protected $botId;
|
|
|
|
|
+
|
|
|
|
|
+ public function __construct()
|
|
|
|
|
+ {
|
|
|
|
|
+ $this->apiBase = rtrim((string)$this->env('coze.api_base', 'COZE_API_BASE', 'https://api.coze.cn'), '/');
|
|
|
|
|
+ $this->apiToken = (string)$this->env('coze.api_token', 'COZE_API_TOKEN', '');
|
|
|
|
|
+ $this->botId = (string)$this->env('coze.bot_id', 'COZE_BOT_ID', '7670455976805826612');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 发送文本消息到 Coze,返回智能体文本回复。
|
|
|
|
|
+ */
|
|
|
|
|
+ public function chatText(string $userId, string $content): string
|
|
|
|
|
+ {
|
|
|
|
|
+ $content = trim($content);
|
|
|
|
|
+ if ($content === '' || $this->apiToken === '' || $this->botId === '') {
|
|
|
|
|
+ return '';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $payload = [
|
|
|
|
|
+ 'bot_id' => $this->botId,
|
|
|
|
|
+ 'user_id' => $userId,
|
|
|
|
|
+ 'stream' => true,
|
|
|
|
|
+ 'auto_save_history' => true,
|
|
|
|
|
+ 'additional_messages' => [
|
|
|
|
|
+ [
|
|
|
|
|
+ 'role' => 'user',
|
|
|
|
|
+ 'content' => $content,
|
|
|
|
|
+ 'content_type' => 'text',
|
|
|
|
|
+ ],
|
|
|
|
|
+ ],
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ $response = HttpService::postRequest($this->apiBase . '/v3/chat', json_encode($payload, JSON_UNESCAPED_UNICODE), [
|
|
|
|
|
+ 'Authorization: Bearer ' . $this->apiToken,
|
|
|
|
|
+ 'Content-Type: application/json',
|
|
|
|
|
+ 'Accept: text/event-stream',
|
|
|
|
|
+ ], 20);
|
|
|
|
|
+
|
|
|
|
|
+ if ($response === false || $response === '') {
|
|
|
|
|
+ Log::error('Coze 小程序客服调用失败:' . json_encode(HttpService::getStatus(), JSON_UNESCAPED_UNICODE) . ' ' . HttpService::getCurlError());
|
|
|
|
|
+ return '';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return $this->parseStreamAnswer($response);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ protected function parseStreamAnswer(string $response): string
|
|
|
|
|
+ {
|
|
|
|
|
+ $answer = '';
|
|
|
|
|
+ $event = '';
|
|
|
|
|
+ $lines = preg_split('/\r\n|\r|\n/', $response);
|
|
|
|
|
+ foreach ($lines as $line) {
|
|
|
|
|
+ $line = trim($line);
|
|
|
|
|
+ if ($line === '') {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (stripos($line, 'event:') === 0) {
|
|
|
|
|
+ $event = trim(substr($line, 6));
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (stripos($line, 'data:') !== 0) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $data = trim(substr($line, 5));
|
|
|
|
|
+ if ($data === '' || $data === '[DONE]' || $data === '"[DONE]"') {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $json = json_decode($data, true);
|
|
|
|
|
+ if (!is_array($json)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $currentEvent = (string)($json['event'] ?? $event);
|
|
|
|
|
+ $message = $json['message'] ?? $json['data'] ?? $json;
|
|
|
|
|
+ if (!is_array($message)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (isset($message['type']) && $message['type'] !== 'answer') {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (isset($message['content']) && $message['content'] !== '') {
|
|
|
|
|
+ if ($currentEvent === '' || stripos($currentEvent, 'delta') !== false) {
|
|
|
|
|
+ $answer .= (string)$message['content'];
|
|
|
|
|
+ } elseif (stripos($currentEvent, 'completed') !== false && $answer === '') {
|
|
|
|
|
+ $answer = (string)$message['content'];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return trim($answer);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ protected function env(string $key, string $serverKey, string $default = ''): string
|
|
|
|
|
+ {
|
|
|
|
|
+ $value = Env::get($key, '');
|
|
|
|
|
+ if ($value !== '') {
|
|
|
|
|
+ return (string)$value;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $serverValue = getenv($serverKey);
|
|
|
|
|
+ if ($serverValue !== false && $serverValue !== '') {
|
|
|
|
|
+ return (string)$serverValue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return $default;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|