67 lines
3.6 KiB
PHP
67 lines
3.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\KnowledgeChunk;
|
|
use App\Services\Bot\BotAnswerService;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Http\Client\Request;
|
|
use Illuminate\Support\Facades\Http;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
/**
|
|
* Личный вопрос («сколько у меня на балансе») раньше ВСЕГДА уходил к живому специалисту
|
|
* (PERSONAL_PATTERN → эскалация). Дорого и медленно: у вошедшего клиента цифры уже
|
|
* посчитаны кодом (ClientFacts) — бот должен отвечать сам, положив карточку фактов
|
|
* в контекст модели ПЕРВЫМ блоком. У гостя с лендинга карточки нет — звать специалиста
|
|
* ради цифры, которую тот всё равно не знает, глупо: вместо этого зовём в кабинет.
|
|
*/
|
|
beforeEach(function () {
|
|
config()->set('services.yandexgpt', [
|
|
'api_key' => 'k', 'folder_id' => 'f', 'model' => 'yandexgpt-lite/latest',
|
|
'endpoint' => 'https://llm.api.cloud.yandex.net/foundationModels/v1/completion',
|
|
'timeout_seconds' => 8,
|
|
]);
|
|
});
|
|
|
|
it('отвечает вошедшему клиенту по его карточке фактов', function () {
|
|
KnowledgeChunk::create([
|
|
'source_path' => 'help/balance.md', 'title' => 'Баланс и списания', 'tour' => null,
|
|
'topics' => 'баланс, списание, пополнение', 'chunk_index' => 0,
|
|
'content' => 'Баланс — это сумма на вашем счёте в Лидерре, из неё списываются заявки.',
|
|
]);
|
|
|
|
// Модель отвечает по делу ТОЛЬКО если увидела карточку фактов в системном промпте —
|
|
// так тест доказывает, что карточка реально дошла до модели, а не потерялась по пути.
|
|
Http::fake(function (Request $request) {
|
|
$systemPrompt = (string) ($request['messages'][0]['text'] ?? '');
|
|
$hasFacts = str_contains($systemPrompt, 'ДАННЫЕ ЭТОГО КЛИЕНТА');
|
|
|
|
return Http::response([
|
|
'result' => ['alternatives' => [['message' => ['role' => 'assistant', 'text' => $hasFacts
|
|
? 'Ваш баланс: 3 400 ₽ ≈ 40 заявок.'
|
|
: 'В инструкции этого нет.',
|
|
]]]],
|
|
]);
|
|
});
|
|
|
|
$facts = "ДАННЫЕ ЭТОГО КЛИЕНТА (посчитаны только что):\nБаланс: 3 400 ₽ ≈ 40 заявок.";
|
|
$answer = app(BotAnswerService::class)->answer('какой у меня баланс?', [], true, $facts);
|
|
|
|
// Сторож AnswerGuard режет числа, которых нет в $context, — «3 400 ₽» пройдёт только
|
|
// если карточка реально попала в $context (а не потерялась где-то по пути).
|
|
expect($answer->escalate)->toBeFalse()
|
|
->and($answer->text)->toContain('3 400 ₽');
|
|
});
|
|
|
|
it('гостю с лендинга предлагает войти в кабинет, а не зовёт специалиста', function () {
|
|
Http::fake();
|
|
|
|
$answer = app(BotAnswerService::class)->answer('сколько у меня на балансе?', [], false, null);
|
|
|
|
expect($answer->escalate)->toBeFalse()
|
|
->and($answer->text)->toBe(BotAnswerService::LOGIN_TEXT);
|
|
Http::assertNothingSent();
|
|
});
|