8b6a0139db
Co-authored-by: Cursor <cursoragent@cursor.com>
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));
|
|
}
|
|
} |