Перейти до вмісту
coden

Програмування з AI-агентами: завдання, тести й перевірка

Як написати агентові завдання з критеріями приймання, чому працювати малими кроками, експеримент із мутаційним тестуванням (скільки помилок виявляють тести щасливого шляху, граничні випадки й тести властивостей), хто має писати тести і на що звернути увагу.

10 хвилин Matěj Žalmánek 2026-10-16

Серія Основи ШІ Частина 51 з 52
  1. Мапа штучного інтелекту: що є що і як усе це пов’язано
  2. Коротка історія штучного інтелекту: від Тюрінга до мовних моделей
  3. Дані, модель, тренування, інференс: основні поняття на одному прикладі
  4. Як читати математичний запис: символи, індекси, суми та квантори
  5. Множини й відношення: конструктор, з якого складаються означення в ШІ
  6. Функції й відображення: чому кожна модель у ШІ — це функція
  7. Логіка й доведення: правила, за якими міркують у ШІ
  8. Графи й дерева: структура, на якій тримаються пошук і нейронні мережі
  9. Лінійна алгебра для машинного навчання: вектори, матриці й чому ШІ передусім множить
  10. Похідні, градієнт і правило ланцюжка: як модель дізнається, у якому напрямку вчитися
  11. Градієнтний спуск: як навчається модель
  12. Що таке ймовірність: від кидка кубика до аксіом Колмогорова
  13. Умовна ймовірність і теорема Баєса: як змінити оцінку, коли дізнаєтеся щось нове
  14. Випадкова величина і функція розподілу: одна функція, яка каже про розподіл усе
  15. Функція щільності та функція маси ймовірності: чому неперервна величина має в точці ймовірність нуль
  16. Математичне сподівання, дисперсія і коваріація: де лежить розподіл, наскільки він широкий і що з чим пов’язано
  17. Огляд розподілів ймовірностей: Бернуллі, біноміальний, Пуассона, рівномірний, нормальний і експоненційний
  18. Закон великих чисел і центральна гранична теорема: чому середні поводяться так передбачувано
  19. Статистика й оцінювання параметрів: максимальна правдоподібність, MAP і довірчі інтервали
  20. Теорія інформації: ентропія, перехресна ентропія і KL-дивергенція — звідки беруться функції втрат
  21. Пошук у просторі станів: BFS, DFS, UCS і A* з анімаціями
  22. Ігри та суперник: мінімакс, альфа-бета відсікання і Monte Carlo tree search
  23. Задоволення обмежень: бектрекінг, поширення обмежень і AC-3 на судоку та мапі чеських країв
  24. Логіка й системи, засновані на знаннях: пряме і зворотне виведення, резолюція та експертні системи
  25. Планування: STRIPS, світ кубиків, евристики з релаксації та планування в AI-агентах
  26. Локальний пошук та імітація відпалу: комівояжер через обласні центри Чехії
  27. Генетичні алгоритми: популяція, схрещування, мутація і добір на рюкзаку для походу
  28. Еволюційні стратегії і генетичне програмування: від правила однієї п’ятої до закону Кеплера
  29. Оптимізація мурашиною колонією: як феромоновий слід знаходить короткий шлях
  30. Оптимізація роєм частинок: інерція, власний досвід і досвід рою
  31. Нейроеволюція: еволюція ваг і форми нейронних мереж та алгоритм NEAT
  32. Нечіткі множини й функції належності: як записати «трохи тепло»
  33. Нечітка логіка та ймовірність: чому «трохи тепло» не означає «імовірно тепло»
  34. Нечітка логіка: t-норми, імплікації та правила ЯКЩО–ТО
  35. Нечітке керування: Мамдані та Суґено від вентилятора до маятника на візку
  36. Нечітка кластеризація: алгоритм fuzzy c-means і точки між кластерами
  37. Нейро-нечіткі системи (ANFIS): нечіткі правила, які навчаються з даних
  38. Марковські ланцюги: матриця переходів, стаціонарний розподіл і PageRank
  39. Приховані марковські моделі: алгоритм Вітербі й нечесне казино
  40. Байєсівські мережі: чому трава мокра і що з цього випливає для дощувача
  41. Фільтр Калмана: як за неточними вимірюваннями оцінити, де авто і як швидко воно їде
  42. Види машинного навчання: з учителем, без учителя, з частковим залученням учителя та інші
  43. Промпти, контекст і RAG: як дати моделі правильну інформацію
  44. Варіаційні автокодувальники (VAE): нові зображення з неперервного латентного простору
  45. Генеративні змагальні мережі (GAN): фальшувальник проти детектива
  46. Дифузійні моделі: як із чистого шуму постає зображення
  47. Марковські процеси ухвалення рішень: стани, дії, винагороди й рівняння Беллмана
  48. Q-навчання і DQN: як навчитися цінності дій — від таблиці до нейронної мережі
  49. Policy gradient і PPO: навчання безпосередньо політики
  50. Як працює ШІ-агент: мовна модель у циклі з інструментами
  51. Програмування з AI-агентами: завдання, тести й перевірка
  52. Оцінювання і безпека систем ШІ: як вимірювати і як стежити

Повна програма серії Готово: 52 з 83 тем

Що ви винесете
  • Що має бути в завданні для агента, щоб він знав, коли готово.
  • Скільки помилок виявляють тести різної ретельності і чому граничні випадки важливі.
  • Які помилки роблять агенти і як їх знайти під час перевірки.

Що варто знати заздалегідь: Як працює ШІ-агент: мовна модель у циклі з інструментами, Як підходити до розробки з AI-агентами

У попередній частині ми розібрали, як агент працює всередині. Ця частина практична: як ставити йому завдання, як їх перевіряти і як не втратити огляд того, що відбувається в проєкті. Більшість порад можна звести до одного речення — ви маєте дати агентові спосіб самому зрозуміти, що роботу завершено, а самі маєте мати спосіб це перевірити. На експерименті покажемо, наскільки це важливо.

Частина продовжує Як працює ШІ-агент і Як підходити до розробки з AI-агентами, де більше про роль людини і про те, чому варто спершу дати агентові запропонувати рішення.

1. Завдання: що, де і як зрозуміти, що готово

Означення — критерії приймання

Критерії приймання — це перевірювані умови, які має виконати результат, щоб завдання вважалося завершеним: конкретна поведінка («для порожнього кошика повертає 0 CZK»), тести, які мають пройти, команда, яка має виконатися без помилки. На відміну від опису мети, про них можна однозначно вирішити, чи вони виконуються.

Простими словами

«Прибери в кімнаті» — це мета. «На підлозі нічого не лежить, ліжко застелене, а сміття винесене» — це критерії приймання. Лише з ними можна сказати, чи прибрано, — і дитина (або агент) знає, коли можна зупинитися.

Означення — специфікація як передумова і зобов’язання

Точно критерії приймання можна записати трійкою Гоара (C. A. R. Hoare, 1969; сучасний запис із фігурними дужками): {P} C {Q} означає, що коли перед запуском програми C виконується умова P і C завершується, то після цього виконується умова Q. P — це передумова (що програма може очікувати від входу), Q — зобов’язання (що має бути істинним щодо результату). Програма коректна відносно специфікації, якщо трійка виконується для кожного стану, який задовольняє P.

{ castka ≥ 0 }   s := sazba(castka)   { s = 0 для castka < 1 000;  s = 0,05 для 1 000 ≤ castka < 5 000;  s = 0,1 інакше }

castka ≥ 0
передумова: за від’ємну суму функція не відповідає — вона тут, щоб було ясно, що ми тестувати не мусимо і що має перевіряти той, хто викликає
s := sazba(castka)
програма, про яку йдеться; специфікація нічого не каже про те, як її написано, тож агент може виконати її будь-як
s = 0 / 0,05 / 0,1
зобов’язання: рівно три діапазони та їхні межі (≤ чи <) — саме тут найчастіше помиляються, тому до критеріїв приймання належать значення 999, 1 000, 4 999 і 5 000
Рядок «Готово, коли» в завданні нижче — це те саме зобов’язання, записане так, щоб його можна було перевірити тестом.

Добре завдання для агента має чотири частини. Не тому, що це ритуал, а тому, що кожна відповідає одній речі, яку агент інакше мусить вгадувати:

Мета: На сторінці кошика покажи знижку залежно від суми (від 1 000 CZK 5 %, від 5 000 CZK 10 %).

Контекст: Розрахунок ціни — у src/kosik.js, шаблон — у pohledy/kosik.ejs.
Схожим чином рахується доставка у функції doprava() — дотримуйся того самого стилю.

Обмеження: Не змінюй формат даних у базі даних. Жодних нових бібліотек.

Готово, коли:
- функція sazba(castka) повертає 0 / 0,05 / 0,1 і тести в test/kosik.test.js проходять,
- ти додаси тести для меж 999, 1 000, 4 999 і 5 000 CZK,
- npm test виконується без помилок і ти покажеш мені різницю (git diff).

Контекст заощаджує агентові пошук (а вам — токени, див. ціна контексту). Обмеження не дають йому «розв’язати» проблему інакше, ніж ви хочете. А критерії приймання дають йому зворотний зв’язок, без якого довгі завдання розвалюються.

2. Малі кроки, часті перевірки

У частині про агентів ми підрахували, що завдання з двадцяти кроків, кожен з яких вдається в 90 % випадків, без перевірки вдасться лише в 12 % випадків. Те саме правило діє і для вас: що більший шматок роботи агент здає за раз, то важче його перевірити і то дорожче виправлення. Практичні наслідки:

  • Одна зміна = одне завдання. Велику справу розділіть на кроки, які можна перевірити окремо (спершу розрахунок, потім відображення, потім зміна даних).
  • Система контролю версій як страхувальна сітка. Перед кожним завданням — чистий стан у git, після нього — коміт. Коли щось зламається, ви повернетеся однією командою.
  • Читайте різницю, а не підсумок. Агентове «готово, усе працює» — це твердження. Різниця в коді (git diff) і вивід тестів — це докази.

Pуспіх = pn

p
імовірність, що один крок вдасться; вона тут, бо кожен крок — ще одна нагода помилитися
n
кількість кроків між двома перевірками; вона в показнику степеня, бо ймовірності незалежних кроків перемножуються — і з кожним наступним кроком без перевірки шанс падає геометрично, а не лінійно
Pуспіх
шанс, що вся ділянка між перевірками завершиться добре і нічого не доведеться переробляти
Спрощено: кроки зазнають невдачі незалежно, а помилку виявляють лише під час перевірки.
Приклад — скільки роботи заощадить перевірка після кожних п’яти кроків

Без перевірки: 0,920 = 0,1216, тобто 12 %. З перевіркою після кожних п’яти кроків: 0,95 = 0,5905 для кожної з чотирьох ділянок.

Припустімо, що перевірка завжди виявляє помилку, а зіпсовану ділянку роблять заново. Кількість спроб до першого успіху має геометричний розподіл із математичним сподіванням 1 / Pуспіх. Усе завдання без перевірки коштує в середньому 20 / 0,1216 ≈ 165 кроків, з перевіркою після кожних п’яти 4 · 5 / 0,5905 ≈ 34 кроків, а з перевіркою після кожного кроку 20 / 0,9 ≈ 22 кроків. Малі кроки не підвищують шанс, що один крок вдасться, — вони зменшують, скільки роботи доведеться викинути в разі помилки.

3. Тести як запобіжник: експеримент

Наскільки допомагають тести? Візьмемо п’ять малих функцій, які агент зазвичай пише, — середнє, високосний рік, скорочення тексту, двійковий пошук і знижку залежно від суми — і зробимо з них навмисно зіпсовані версії.

Означення — мутаційне тестування

Мутант — це копія програми з однією дрібною автоматичною зміною: < замість <=, - 1 замість + 1, || замість &&, інша константа. Набір тестів виявляє мутанта, коли на ньому падає хоча б один тест. Частка виявлених мутантів показує, наскільки добре тести стережуть від помилок. Мутанти, які поводяться так само, як оригінал (еквівалентні), не враховуються.

Простими словами

Перевірка пожежного датчика: ви навмисно запалюєте маленьку свічку і дивитеся, чи він запищить. Тут ви навмисно вносите в код маленьку помилку — саме таку, яку роблять люди й агенти, — і дивитеся, чи тести її помітять.

Із вихідного коду п’яти функцій автоматично виникло 32 мутантів. 3 з них на 2 000 випадкових входів жодного разу не відрізнялися від оригіналу (наприклад, a[s] <= x замість a[s] < x у двійковому пошуку, де рівність уже обробив попередній рядок), тож лишається 29 справжніх помилок. Проти них — чотири рівні тестів, кожен містить і попередні:

  • Щасливий шлях — один чи два типові приклади на функцію. Такі тести з’являються, коли сказати лише «додай тести».
  • Граничні випадки — порожній масив, межі діапазонів (999, 1 000, 4 999, 5 000 CZK), роки 1900, 2000 і 2100, текст рівно на межі довжини.
  • Властивості — 200 випадкових входів і правила, які мають виконуватися завжди: результат скорочення має щонайбільше max символів і є початком тексту, знайдений індекс справді вказує на шукане число, середнє збігається з інакше записаним обчисленням.
0 %25 %50 %75 %100 % 0 з 29 жодних 15 з 29 щасливий шлях 26 з 29 + граничні 28 з 29 + властивості рівень тестів (кумулятивно, кожен містить попередній) виявлені помилки (% з 29)
Скільки з 29 помилок виявлять тести

Тести щасливого шляху ловлять лише 15 з 29 помилок (52 %). Вони проходять і з функцією, яка для порожнього масиву повертає 1, зі знижкою, що починається лише від 1 001 CZK, або з двійковим пошуком, який у масиві з одного елемента нічого не знаходить. Додавання граничних випадків піднімає частку до 90 %, тести властивостей — до 97 %.

функціямутантівщасливий шлях+ граничні+ властивості
prumer5455
jePrestupny8557
zkrat3233
najdi6366
sazba7177

MS = |O| / ( |M| − |E| )

MS
мутаційна оцінка (mutation score) — частка справжніх помилок, які ловить набір тестів, — висота стовпця на графіку
|O|
кількість виявлених мутантів (на них упав хоча б один тест, або вони аварійно завершилися чи зациклилися); те, що тести вміють
|M|
усі створені мутанти — запас помилок, яким ми вимірюємо тести
|E|
еквівалентні мутанти; їх віднімають, бо вони не відрізняються від оригіналу, і тому жоден тест їх виявити не може — інакше навіть досконалі тести не дійшли б до 100 %

У нас |M| = 32 і |E| = 3, тож знаменник дорівнює 29. Щасливий шлях: 15 / 29 = 0,517; із граничними випадками 26 / 29 = 0,897; із властивостями 28 / 29 = 0,966. Процедура, якою отримано ці числа:

для кожної функції f:
    оригінал = скомпілюй(код f)
    для кожного оператора (з → на) і кожного входження «з» у коді:
        мутант = скомпілюй(код, де це входження «з» замінено на «на»)
        якщо мутант на 2 000 випадкових входів ніколи не відрізняється від оригіналу:
            еквівалентний — не враховується, далі
        для кожного рівня тестів u:
            виявлено[u] = якийсь тест рівня u на мутанті падає,
                          аварійно завершується або зациклюється (запобіжник після 10 000 проходів)
MS(u) = кількість виявлених на рівні u / кількість нееквівалентних

Найцікавіший — високосний рік. Граничні випадки пропускають мутантів, які вважають високосним кожен парний рік, або які відкидають роки, що діляться на десять, — тобто й 2020 рік. Тести з роками 1900, 2000 і 2100 цього не помічають, бо на них ті випадково відповідають правильно. Тест властивості з випадковими роками знаходить обидві помилки. Єдиний мутант, що пережив усе, — (rok % 4 === 0 && rok % 100 !== 0) || rok % 200 === 0: він відрізняється лише для років, що діляться на 200, але не на 400 (200, 600, 1000…), а серед 200 випадкових років жодного такого не було. Навіть випадкові тести не всемогутні.

Pвиживе = ( 1 − q )n

q
частка входів, на яких мутант відрізняється від оригіналу; 1 − q — шанс, що один випадковий вхід його не виявить
n
кількість випадкових входів; входи вибираються незалежно, тож шанси «не виявив» перемножуються
Pвиживе
імовірність, що всі n входів мутанта проминуть — помилка пройде, хоча властивість написано правильно
Випадкові тести надійно ловлять помилки, які проявляються часто; рідкісні помилки — лише за великої кількості входів.
Приклад — чому вижив мутант із роком, що ділиться на 200

Тест властивості вибирає роки від 0 до 2 399. Мутант rok % 200 відрізняється від оригіналу лише на 6 з них (200, 600, 1000, 1400, 1800, 2200), тож q = 6 / 2400 = 0,0025 і за n = 200 він виживає з імовірністю (1 − 0,0025)200 = 0,606 — приблизно в шести спробах із десяти. Те, що він вижив, було не невдачею, а очікуваним результатом.

Мутант rok % 2 відрізняється на 600 роках (q = 0,25): він виживає з імовірністю 0,75200 ≈ 10-25, тобто практично ніколи. Мутант rok % 10 відрізняється на 96 роках (q = 0,04) і виживає з імовірністю 0,96200 = 0,00028.

Скільки входів знадобилося б, щоб мутанта з роком 200 упіймати з імовірністю 95 %? З (1 − q)n ≤ 0,05 випливає n ≥ ln 0,05 / ln(1 − q) = −2,996 / −0,00250 ≈ 1 196,8, тобто 1 197 входів. Дешевше додати граничний випадок — рік 200 або 600: хто знає правило календаря, той знає, де шукати.

4. Хто має писати тести

Тести в експерименті ми писали за завданням (що має робити функція), а не за кодом. Це суттєво. Коли агент пише код і одразу до нього тести, є ризик, що тести лише повторять те, що робить код, — разом із його помилками. Вони пройдуть і нічого не доведуть. Допомагає таке:

  • Тести (або хоча б їхній перелік) наперед. Дайте агентові спершу написати тести за завданням, прочитайте їх, і лише потім — код. Критерії приймання із завдання — це саме такий перелік.
  • Перелічити граничні випадки. Порожній вхід, один елемент, межі діапазонів, від’ємні числа, діакритика. Агент часто додає їх сам, але лише тоді, коли ви їх хочете.
  • Властивості там, де це можливо. «Відсортований масив лишається відсортованим», «закодувати й декодувати = початковий текст», «нове обчислення дає те саме, що й старе». Бібліотеки на кшталт fast-check (JavaScript) чи Hypothesis (Python) генерують випадкові входи самі.
Означення — тестування властивостей (property-based testing)

Замість конкретних пар вхід–вихід записують властивість, яка має виконуватися для всіх входів, і інструмент перевіряє її на багатьох випадково згенерованих входах. Коли він знаходить контрприклад, то зазвичай зменшує його до найпростішого випадку, що не проходить. Ідею популяризувала бібліотека QuickCheck (Koen Claessen і John Hughes, 2000).

Простими словами

Ви питаєте не «скільки буде 7 + 5», а «чи правда, що a + b завжди дорівнює b + a?» — і даєте комп’ютерові випробувати тисячу випадкових пар. Він знайде випадки, про які ви б не подумали.

Властивість — це висловлення з квантором «для всіх». Для функції zkrat з експерименту тест записує її так:

∀ t, m ≥ 1: |v| ≤ m ∧ ( |t| ≤ m ⇒ v = t ) ∧ ( |t| > m ⇒ v = t[0, |v| − 1) + «…» ),  де v = zkrat(t, m)

∀ t, m ≥ 1
для кожного тексту і кожної довжини — тому її можна перевіряти на випадкових входах і не треба знати правильний результат наперед
|v| ≤ m
головна обіцянка функції: результат уміщується; ловить мутанта, який забуває врахувати «…» (slice(0, max))
|t| ≤ m ⇒ v = t
короткий текст не змінюється; без цього пройшла б функція, яка скорочує завжди
v = t[0, |v| − 1) + «…»
скорочений результат — це початок вихідного тексту, і він закінчується трикрапкою; без цього пройшла б функція, яка повертає, скажімо, порожній текст
t[0, k) — це перші k символів тексту t. Властивість не каже, скільки саме символів залишиться, — лише що має виконуватися; тим вона загальніша за один конкретний приклад.

Інструмент для тестування властивостей тоді робить приблизно таке:

для i = 1 … N:                       # N зазвичай від 100 до 1 000
    x = згенеруй випадковий вхід
    якщо властивість(x, f(x)) не виконується:
        # зменшення: шукай простіший вхід, на якому все ще падає
        поки існує y = спрости(x), на якому властивість не виконується:
            x = y                    # коротший текст, менше число, коротший масив
        повідом контрприклад x
повідом «пройшло N входів»           # це не доведення, лише добра новина

Останній рядок суттєвий. Тести, навіть випадкові, не доводять коректності — вони лише шукають контрприклади. Edsger Dijkstra 1969 року сказав це так: тестування може показати наявність помилок, але ніколи їхню відсутність. Імовірність, що рідкісна помилка прослизне, ми підрахували вище.

5. На що звернути увагу

Агенти роблять інші помилки, ніж люди. Найчастіші, які варто шукати під час перевірки:

  • «Готово» без перевірки. Агент повідомляє про успіх, не запустивши тести, або запустивши їх лише частково. Вимагайте вивід тестів.
  • Зміна тестів замість коду. Коли тест падає, найпростіше «виправлення» — змінити очікуване значення або видалити тест. У різниці шукайте зміни в тестах.
  • Приглушені помилки. Порожній catch, значення за замовчуванням замість помилки, жорстко прописаний результат для тестового входу.
  • Зайва робота. Переписаний файл, не пов’язаний із завданням, нова бібліотека, «покращений» формат даних. Обмеження в завданні та малі кроки тримають це в межах.
  • Секрети й незворотні дії. Паролі в коді, видалення файлів, запис у продакшн-базу даних, надсилання у віддалений репозиторій. Такі дії залишайте на ваше схвалення.

6. Не втратити огляд

Агент нічого не пам’ятає між сесіями. Те, що він має знати щоразу, — як проєкт запускається і тестується, які конвенції, чого уникати, — належить до файлу з інструкціями в корені проєкту, який агент читає на початку. Пишіть туди те, про що вам довелося нагадувати агентові більше ніж раз. А те, що знаєте лише ви (чому щось зроблено саме так, що пробували і що не спрацювало), належить до комітів і документації — для агента і для себе за пів року.

Остання порада найпростіша: залишайтеся поруч. Не обов’язково біля комп’ютера — агент може працювати сам, а ви подивитеся, коли йому знадобиться рішення. Для цього й з’явився coden: завдання, схвалення кроків і перевірка результату — у вашому телефоні, поки агент працює на вашому комп’ютері.

7. Підсумок

  • Добре завдання: мета, контекст (файли, зразки), обмеження і перевірювані критерії приймання.
  • Малі кроки, коміт після кожного, і перевірка різниці в коді та виводу тестів, а не підсумку.
  • В експерименті тести щасливого шляху виявили 52 % помилок, із граничними випадками 90 %, із властивостями 97 %.
  • Тести пишіть (або схвалюйте) за завданням і в ідеалі до коду, інакше вони лише повторять помилки реалізації.
  • Стежте за змінами тестів, приглушеними помилками, зайвою роботою і незворотними діями.

Хочете спробувати самі? Попросіть AI-агента запустити на вашому проєкті інструмент для мутаційного тестування (Stryker для JavaScript, mutmut для Python) і показати вам мутантів, які вижили. Кожен із них — це тест, якого бракує.

Маєте іншу думку? Напишіть на support@coden-app.com.

Агент на вашому комп’ютері, керований з телефона