| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- <?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;
- protected $timeout;
- protected $fallbackReply;
- public function __construct()
- {
- $this->apiBase = rtrim((string)$this->env(['coze.coze_api_base', 'coze.api_base'], 'COZE_API_BASE', 'https://api.coze.cn'), '/');
- $this->apiToken = (string)$this->env(['coze.coze_access_token', 'coze.access_token', 'coze.api_token'], 'COZE_ACCESS_TOKEN', '');
- $this->botId = (string)$this->env(['coze.coze_bot_id', 'coze.bot_id'], 'COZE_BOT_ID', '');
- $this->timeout = max(1, (int)$this->env(['coze.coze_timeout', 'coze.timeout'], 'COZE_TIMEOUT', '15'));
- $this->fallbackReply = trim((string)$this->env(['coze.coze_fallback_reply', 'coze.fallback_reply'], 'COZE_FALLBACK_REPLY', ''));
- }
- public static function isEnabled(): bool
- {
- $value = Env::get('coze.coze_enable', Env::get('coze.enable', getenv('COZE_ENABLE') ?: false));
- return filter_var($value, FILTER_VALIDATE_BOOLEAN);
- }
- /**
- * 发送文本消息到 Coze,返回智能体文本回复。
- */
- public function chatText(string $userId, string $content): string
- {
- $content = trim($content);
- if (!self::isEnabled() || $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',
- ], $this->timeout);
- if ($response === false || $response === '') {
- Log::error('Coze 小程序客服调用失败:' . json_encode(HttpService::getStatus(), JSON_UNESCAPED_UNICODE) . ' ' . HttpService::getCurlError());
- return $this->fallbackReply;
- }
- $answer = $this->parseStreamAnswer($response);
- return $answer !== '' ? $answer : $this->fallbackReply;
- }
- 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(array $keys, string $serverKey, string $default = ''): string
- {
- foreach ($keys as $key) {
- $value = Env::get($key, '');
- if ($value !== '') {
- return (string)$value;
- }
- }
- $serverValue = getenv($serverKey);
- if ($serverValue !== false && $serverValue !== '') {
- return (string)$serverValue;
- }
- return $default;
- }
- }
|