QA Interview Hub · База знань · Тести
AI
Як тестувати LLM-агента
Чому LLM не тестується як звичайний код
Класичний тест: фіксований вхід → expect(actual).toBe(expected). З LLM це ламається з трьох причин:
- Недетермінізм. Той самий вхід дає різні, але однаково правильні відповіді: «Замовлення скасовано» і «Ваше замовлення успішно скасовано». Рівність рядків не працює;
- Проблема оракула. Для вільного тексту немає єдиної «правильної» відповіді, тож неочевидно, з чим порівнювати;
- Неявні залежності. Поведінку змінюють версія моделі, промпт, температура, дані в RAG, результати tools — і жодна зміна не схожа на «зламаний код».
Тому перевіряють властивості, а не точний текст, і міряють частку успіху на наборі прикладів, а не «пройшов / не пройшов» на одному:
- Детерміновані перевірки там, де можна: валідний JSON за схемою, обов'язкові поля, відсутність заборонених слів, довжина, потрібний tool викликано з правильними аргументами, дані в БД змінилися;
- Семантичні перевірки для вільного тексту: схожість за embeddings, перевірка фактів з довідки, LLM-as-judge;
- Статистика: запускати кейс N разів і вимагати, наприклад, ≥ 90 % успіху, або порівнювати середню оцінку між версіями.
Піраміда лишається: найбільше швидких детермінованих тестів із замоканою моделлю (логіка навколо LLM: парсинг, маршрутизація, обробка помилок), менше eval-набору з реальною моделлю, ще менше e2e.
Eval-набір, метрики й LLM-as-judge
Eval (evaluation) — автоматизований прогін набору прикладів через систему з оцінкою результатів. Це «регресійні тести» для LLM-функціональності.
Golden dataset — курований набір кейсів: вхід, очікувана поведінка (не обов'язково точний текст), теги (категорія, складність). Звідки брати: реальні логи й тікети (з анонімізацією), крайні випадки, відомі баги (кожен знайдений баг стає кейсом), синтетичні приклади, згенеровані моделлю й перевірені людиною. Набір має покривати негативні сценарії: відмови, неоднозначні запити, спроби обійти правила.
LLM-as-judge: інша модель оцінює відповідь за рубрикою («чи спирається відповідь лише на надані документи? 1–5», «так/ні: чи є в тексті персональні дані»). Практики, щоб суддя був надійним:
- чітка рубрика й бінарні або вузькі шкали замість «оціни якість»;
- просити коротке обґрунтування перед оцінкою;
- судити іншою моделлю, ніж тестована (self-preference bias), температура 0;
- калібрувати суддю на розмічених людиною прикладах і міряти згоду з людьми;
- враховувати bias позиції та довжини: довша відповідь не обов'язково краща.
Метрики: pass rate по категоріях, а не одне середнє; частка відмов; середнє та p95 токенів, вартість і latency на кейс. Кожна зміна промпту, моделі чи RAG порівнюється з базовою лінією на тому самому наборі.
Тестування агента: tools, траєкторія, результат
Агент складніший за чат: у нього є кроки і побічні ефекти. Перевіряють три рівні:
- Окремий tool — звичайні тести: контракт, валідація аргументів, помилки, ідемпотентність. LLM тут не потрібна;
- Рішення моделі — чи обрано правильний tool з правильними аргументами для цього запиту (tool selection accuracy), чи не викликано зайвих;
- Траєкторія й результат — уся послідовність кроків і підсумок у світі (стан БД, створений тікет), а не лише фінальний текст.
Основний прийом — записувати траєкторію (трейс усіх повідомлень і викликів) і робити асерти по ній та по стану системи:
test('refund request creates exactly one refund', async () => {
const run = await agent.run('Поверніть гроші за замовлення 1042', { seed: fixtureDb });
// шлях: правильні tools, без зайвих
expect(run.toolCalls.map((c) => c.name)).toEqual(['get_order', 'create_refund']);
expect(run.toolCalls[1].args).toMatchObject({ orderId: 1042 });
// результат у світі, а не лише текст
expect(await db.refunds.count({ orderId: 1042 })).toBe(1);
// обмеження: бюджет і кроки
expect(run.steps).toBeLessThanOrEqual(6);
expect(run.usage.totalTokens).toBeLessThan(8000);
});
Практики:
- Мокати зовнішній світ (tools, API, час) для швидких і стабільних прогонів; окремо мати набір із реальними інтеграціями в sandbox;
- Не вимагати єдиного шляху, якщо їх кілька правильних: перевіряти обов'язкові кроки, заборонені дії й кінцевий стан;
- Record / replay: записані відповіді моделі для детермінованих регресій логіки агента;
- Побічні ефекти: ідемпотентність, підтвердження небезпечних дій людиною (human-in-the-loop), права tools за принципом мінімальних привілеїв;
- Багатокроковий діалог: втрата контексту, суперечливі уточнення, зміна наміру;
- Збої: tool повернув помилку чи таймаут, порожню відповідь, неочікуваний формат — агент не має зациклитись чи вигадати результат;
- Ліміти: макс. кроків, токенів, часу, вартості; поведінка при досягненні ліміту.
Evals у CI і моніторинг у проді
Тестування LLM-системи не закінчується релізом: поведінка дрейфує від оновлень моделі, змін даних і нових типів запитів користувачів.
У CI:
- швидкий шар без мережі: unit-тести парсингу, маршрутизації, схем, обробки помилок з моканою моделлю — на кожен коміт;
- eval-набір з реальною моделлю — на PR, що чіпає промпт, модель, tools чи RAG (дорого й повільно, тому підмножина «smoke» + повний набір вночі);
- quality gate: pass rate не нижче порога й не гірше за базову лінію більш ніж на допустиму похибку; ліміти вартості й latency;
- зафіксована версія моделі й промптів, щоб різниця була пояснюваною; на недетермінізм закладають повтори й допуск.
У продакшні:
- Observability: логувати (з обережністю щодо PII) промпт, версію моделі, tool calls, токени, latency, вартість; трейс на кожен запит;
- Онлайн-оцінка: вибірка реальних діалогів проганяється через суддю; зворотний зв'язок користувачів (👍/👎) і ескалації;
- Канарковий реліз і A/B нової моделі чи промпту;
- Алерти: стрибок вартості, частки відмов, помилок tools, довжини відповідей;
- Кожен реальний інцидент → новий кейс у golden dataset.