181 lines
6.2 KiB
PHP
181 lines
6.2 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace OCA\GigaChat\Service;
|
||
|
||
use OCA\GigaChat\AppInfo\Application;
|
||
|
||
use OCP\Files\IRootFolder;
|
||
use OCP\Files\File;
|
||
use OCP\Http\Client\IClientService;
|
||
use OCP\IConfig;
|
||
|
||
/**
|
||
* Класс для работы с GigaChat (Сбер)
|
||
* Документация: https://developers.sber.ru/docs/ru/gigachat/api/reference/rest/gigachat-api
|
||
*/
|
||
|
||
class GigaChatService {
|
||
|
||
private const
|
||
TOKEN_URL = 'https://ngw.devices.sberbank.ru:9443/api/v2/oauth',
|
||
METHOD_URL = 'https://gigachat.devices.sberbank.ru/api/v1/',
|
||
TOKEN_FILE = 'token.json',
|
||
CONFIG_ACCESS_SCOPE = 'gigachat_integration_scope',
|
||
CONFIG_ACCESS_KEY = 'gigachat_integration_key',
|
||
CONFIG_MODEL = 'gigachat_integration_model',
|
||
DEFAULT_MODEL = 'GigaChat',
|
||
MAX_TOKENS = 100;
|
||
|
||
public const
|
||
ROLES = [
|
||
'user',
|
||
'assistant'
|
||
];
|
||
|
||
public function __construct(
|
||
private IConfig $config,
|
||
private IClientService $clientService,
|
||
private IRootFolder $rootFolder,
|
||
) {
|
||
|
||
}
|
||
|
||
public function get_config_key(string $key, string $default = ''): string {
|
||
$result = $this->config->getSystemValue($key);
|
||
return empty($result) ? $default : $result;
|
||
}
|
||
|
||
public function get_response(string $message, array $history = []): string {
|
||
$result = '';
|
||
if (empty($message)) {
|
||
throw new \Exception('Empty message');
|
||
}
|
||
$data = [
|
||
// 'max_tokens' => self::MAX_TOKENS,
|
||
'model' => $this->get_config_key(self::CONFIG_MODEL, self::DEFAULT_MODEL),
|
||
'function_call' => 'none',
|
||
'messages' => [
|
||
[
|
||
'role' => 'system',
|
||
'content' => 'Ты ассистент с искусственным интелектом в системе F7cloud',
|
||
],
|
||
],
|
||
];
|
||
foreach ($history as $item) {
|
||
if (
|
||
!empty($item['content'])
|
||
&& !empty($item['role'])
|
||
&& in_array($item['role'], self::ROLES)
|
||
) {
|
||
$data['messages'][] = $item;
|
||
}
|
||
}
|
||
$data['messages'][] = [
|
||
'role' => 'user',
|
||
'content' => $message,
|
||
];
|
||
try {
|
||
$response_data = $this->send_request('chat/completions', $data);
|
||
foreach ($response_data['choices'] as $choice) {
|
||
$result .= $choice['message']['content'];
|
||
}
|
||
} catch (\Exception $e) {
|
||
throw new \Exception('GigaChat request error');
|
||
}
|
||
return $result;
|
||
}
|
||
|
||
public function get_models(): array {
|
||
return $this->send_request('models', [], false);
|
||
}
|
||
|
||
public function get_balance(): array {
|
||
return $this->send_request('balance', [], false);
|
||
}
|
||
|
||
function send_request(string $function, array $data, bool $is_post = true): array {
|
||
$client = $this->clientService->newClient();
|
||
$options = [
|
||
'headers' => [
|
||
'Accept' => 'application/json',
|
||
'Authorization' => 'Bearer ' . $this->get_access_token()
|
||
],
|
||
'verify' => false,
|
||
];
|
||
if (!empty($data)) {
|
||
$options['headers']['Content-Type'] = 'application/json';
|
||
$options['body'] = json_encode($data);
|
||
}
|
||
if ($is_post) {
|
||
$response = $client->post(self::METHOD_URL . $function, $options);
|
||
} else {
|
||
$response = $client->get(self::METHOD_URL . $function, $options);
|
||
}
|
||
|
||
$out = $response->getBody();
|
||
|
||
return json_decode($out, true) ?: [];
|
||
}
|
||
|
||
function get_access_token(): string {
|
||
$token = '';
|
||
$token_file = $this->get_access_token_file();
|
||
$token_data = json_decode($token_file->getContent(), true);
|
||
if (
|
||
empty($token_data['expires_at'])
|
||
|| (($token_data['expires_at'] / 1000) < time())
|
||
) {
|
||
$token = $this->refresh_token()['access_token'];
|
||
} else {
|
||
$token = $token_data['access_token'];
|
||
}
|
||
return $token;
|
||
}
|
||
|
||
public function get_access_token_file(): File {
|
||
$app_data_folder_name = $this->rootFolder->getAppDataDirectoryName();
|
||
$app_data_folder = $this->rootFolder->get($app_data_folder_name);
|
||
if (!$app_data_folder->nodeExists('/' . Application::APP_ID)) {
|
||
$app_data_folder->newFolder('/' . Application::APP_ID);
|
||
}
|
||
$app_folder = $app_data_folder->get('/' . Application::APP_ID);
|
||
if (!$app_folder->nodeExists(self::TOKEN_FILE)) {
|
||
$app_folder->newFile(self::TOKEN_FILE);
|
||
}
|
||
return $app_folder->get(self::TOKEN_FILE);
|
||
}
|
||
|
||
function refresh_token(): array
|
||
{
|
||
$client = $this->clientService->newClient();
|
||
$response = $client->post(self::TOKEN_URL, [
|
||
'body' => http_build_query(['scope' => $this->get_config_key(self::CONFIG_ACCESS_SCOPE)]),
|
||
'verify' => false,
|
||
'headers' => [
|
||
'Content-Type' => 'application/x-www-form-urlencoded',
|
||
'Accept' => 'application/json',
|
||
'RqUID' => self::get_uuid4(),
|
||
'Authorization' => ' Basic ' . $this->get_config_key(self::CONFIG_ACCESS_KEY)
|
||
],
|
||
]);
|
||
|
||
$out = $response->getBody();
|
||
|
||
$token_data = json_decode($out, true) ?: [];
|
||
if (empty($token_data['access_token'])) {
|
||
throw new \Exception('Empty access_token ' . $out);
|
||
}
|
||
$token_file = $this->get_access_token_file();
|
||
$token_file->putContent($out);
|
||
return $token_data;
|
||
}
|
||
|
||
public static function get_uuid4(): string {
|
||
$data = random_bytes(16);
|
||
$data[6] = chr((ord($data[6]) & 0x0f) | 0x40);
|
||
$data[8] = chr((ord($data[8]) & 0x3f) | 0x80);
|
||
return vsprintf("%s%s-%s-%s-%s-%s%s%s", str_split(bin2hex($data), 4));
|
||
}
|
||
} |