diff --git a/app/app/Services/Bot/YandexGptClient.php b/app/app/Services/Bot/YandexGptClient.php new file mode 100644 index 00000000..0fe77641 --- /dev/null +++ b/app/app/Services/Bot/YandexGptClient.php @@ -0,0 +1,51 @@ +withHeaders(['Authorization' => 'Api-Key '.$cfg['api_key']]) + ->post((string) $cfg['endpoint'], [ + 'modelUri' => sprintf('gpt://%s/%s', $cfg['folder_id'], $cfg['model']), + 'completionOptions' => ['stream' => false, 'temperature' => 0.2, 'maxTokens' => 500], + 'messages' => [ + ['role' => 'system', 'text' => $systemPrompt], + ['role' => 'user', 'text' => $userText], + ], + ]); + + if (! $response->ok()) { + Log::warning('YandexGPT non-OK', ['status' => $response->status()]); + + return null; + } + + $text = $response->json('result.alternatives.0.message.text'); + + return is_string($text) && $text !== '' ? $text : null; + } catch (\Throwable $e) { + Log::warning('YandexGPT failure', ['error' => $e->getMessage()]); + + return null; + } + } +} diff --git a/app/tests/Unit/Bot/YandexGptClientTest.php b/app/tests/Unit/Bot/YandexGptClientTest.php new file mode 100644 index 00000000..3ac4d9d5 --- /dev/null +++ b/app/tests/Unit/Bot/YandexGptClientTest.php @@ -0,0 +1,47 @@ +set('services.yandexgpt', [ + 'api_key' => 'test-key', 'folder_id' => 'b1gtest', 'model' => 'yandexgpt-lite/latest', + 'endpoint' => 'https://llm.api.cloud.yandex.net/foundationModels/v1/completion', + 'timeout_seconds' => 8, + ]); +}); + +it('шлёт правильный запрос и возвращает текст ответа', function () { + Http::fake([ + 'llm.api.cloud.yandex.net/*' => Http::response([ + 'result' => ['alternatives' => [['message' => ['role' => 'assistant', 'text' => 'Проект — это…']]]], + ]), + ]); + + $text = app(YandexGptClient::class)->complete('системный наказ', 'что такое проект?'); + + expect($text)->toBe('Проект — это…'); + Http::assertSent(function ($request) { + return $request->hasHeader('Authorization', 'Api-Key test-key') + && $request['modelUri'] === 'gpt://b1gtest/yandexgpt-lite/latest' + && $request['messages'][0]['role'] === 'system' + && $request['messages'][1]['role'] === 'user' + && $request['completionOptions']['temperature'] === 0.2; + }); +}); + +it('пустой api_key → null (бот не настроен, не исключение)', function () { + config()->set('services.yandexgpt.api_key', ''); + + expect(app(YandexGptClient::class)->complete('s', 'u'))->toBeNull(); +}); + +it('ошибка API → null (эскалация решается выше)', function () { + Http::fake(['llm.api.cloud.yandex.net/*' => Http::response('err', 500)]); + + expect(app(YandexGptClient::class)->complete('s', 'u'))->toBeNull(); +});