set('services.jivo_bot.outbound_url', ''); // исходящие в лог 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, ]); Http::fake([ 'llm.api.cloud.yandex.net/*' => Http::response([ 'result' => ['alternatives' => [['message' => ['role' => 'assistant', 'text' => 'Ответ.']]]], ]), ]); for ($i = 0; $i < 20; $i++) { KnowledgeChunk::create([ 'source_path' => "help/a{$i}.md", 'title' => "Статья {$i}", 'tour' => null, 'topics' => 'проект, баланс, тариф', 'chunk_index' => 0, 'content' => str_repeat("Текст про проект и баланс номер {$i}. ", 30), ]); } $latencies = []; for ($i = 0; $i < 10; $i++) { (new ProcessJivoMessageJob("chat-{$i}", 'c', 'что такое проект?'))->handle(); $latencies[] = (int) BotDialog::where('jivo_chat_id', "chat-{$i}") ->where('direction', 'out')->value('latency_ms'); } sort($latencies); $p95 = $latencies[(int) floor(count($latencies) * 0.95) - 1] ?? end($latencies); // Бюджет спеки §6: поиск ≤300мс + сборка/журнал ≤200мс. LLM (до 3с) и сеть // Jivo (до 0.5с) — вне нашего кода, замоканы; живой p95 — на приёмке. expect($p95)->toBeLessThan(500); });