Ідея за 30 секунд
Mocking інструментів і fault injection дозволяють керовано відтворювати помилки API і перевіряти, як агент їх обробляє, без реальної мережі і без недетермінованого шуму.
Головна цінність у тому, що ви керовано відтворюєте timeout, 5xx або зламану відповідь і перевіряєте retry, fallback та stop reason.
Проблема
Без mocks і fault injection команда зазвичай бачить лише happy-path:
- інструмент відповідає швидко;
- відповідь валідна;
- агент завершує run без помилок.
У production так буває рідко. Інструменти можуть повертати timeout, часткові збої, порожні поля або нестабільну latency.
Без окремих тестів на помилки це зазвичай призводить до:
- непередбачуваних падінь у критичних сценаріях;
- нескінченних повторів викликів;
- дорогих і шумних інцидентів, які складно відтворити.
Коли використовувати
Цей підхід потрібен, якщо агент працює через зовнішні інструменти:
- API платежів, CRM, пошук, бекенд-сервіси;
- інструменти з retry/backoff;
- сценарії, де важливий правильний
stop_reason; - сценарії з fallback (наприклад, резервний інструмент або безпечна відповідь).
Якщо збій інструмента можна змоделювати локально, це хороший кандидат для тесту з fault injection.
Реалізація
На практиці це тримається на простому правилі: один тип збою - один тест - контрольовані умови. Приклади нижче схематичні і не прив'язані до конкретного фреймворку.
Як це працює в одному тесті
Короткий цикл тесту з fault injection
- Test case — одна поведінка для перевірки.
- Mock tool — фіксуємо контракт input/output.
- Inject fault — підставляємо конкретний збій (
timeout,5xx,bad_payload). - Run — запускаємо конкретний крок агента.
- Assertions — перевіряємо retry, fallback,
stop_reasonі формат помилки.
1. Зафіксуйте контракт mock-інструмента
class FakePaymentsAPI:
def __init__(self, mode: str = "ok"):
self.mode = mode
def refund(self, order_id: str):
if self.mode == "ok":
return {"status": "approved", "order_id": order_id}
if self.mode == "timeout":
raise TimeoutError("payments_timeout")
if self.mode == "http_500":
raise RuntimeError("payments_500")
return {"status": None}
Mock має відтворювати реальний контракт інструмента максимально точно, інакше тести створюють хибне відчуття безпеки.
2. Інжектуйте збій керовано
def test_timeout_fault_is_injected():
payments = FakePaymentsAPI(mode="timeout")
agent = Agent(payments_api=payments)
result = agent.handle_refund("order-8472")
assert result.stop_reason in {"tool_error_handled", "fallback_used"}
Збій має бути явним і повторюваним: тест повинен завжди відтворювати той самий профіль збою.
3. Перевіряйте retry і fallback
def test_retry_then_fallback():
payments = FlakyPaymentsAPI(fail_times=2, then="timeout")
backup = FakeBackupTool()
agent = Agent(payments_api=payments, backup_tool=backup, max_retries=2)
result = agent.handle_refund("order-9001")
assert payments.calls == 2
assert result.selected_tool == "backup_tool"
assert result.stop_reason == "fallback_used"
Важливо перевіряти не лише факт помилки, а й політику відновлення після неї.
Для retry важливо перевіряти не лише кількість спроб, а й умови, за яких система припиняє повтори і переходить до fallback або fail.
Для інструментів із побічними ефектами важливо перевіряти, що retry не створює дубльованих операцій.
4. Фіксуйте структуру помилки
def test_error_envelope_is_stable():
payments = FakePaymentsAPI(mode="http_500")
agent = Agent(payments_api=payments)
result = agent.handle_refund("order-1122")
assert result.error["code"] == "tool_error"
assert result.error["tool"] == "payments_api"
assert result.error["retryable"] is True
Стабільний формат помилок спрощує дебаг, алерти і regression-перевірки.
5. Запускайте такі тести в CI
Ці тести мають запускатися в кожному PR через стандартний pytest крок у CI, якщо зміни торкаються логіки інструментів, retries або fallback-правил.
Типові помилки
Mock не відповідає реальному контракту
Тест проходить, але в production агент падає через іншу структуру поля або інший код помилки.
Типова причина: mock повертає спрощений payload, який не схожий на реальний API.
Перевірка тільки happy-path
У тестах є тільки "успішна відповідь", але немає timeout, 5xx і невалідних payload.
Типова причина: відсутній список обов'язкових профілів збою для кожного критичного інструмента.
Випадкова fault injection
Один і той самий тест то проходить, то падає.
Типова причина: рандомні збої без фіксованого seed або нестабільні таймаути.
Немає перевірки stop_reason і error shape
Команда перевіряє лише фінальний текст відповіді, а поведінка recovery-логіки лишається непокритою.
Типова причина: відсутні структурні assertions для stop_reason, error.code, selected_tool.
Немає перевірки побічних ефектів при retry
Повторний виклик формально обробляє помилку, але створює дубльовану операцію або повторний запис.
Типова причина: тестують тільки stop_reason і fallback, але не перевіряють ідемпотентність інструментного шару.
Змішування unit і інтеграційних перевірок
Тест називається unit, але ходить у реальний API.
Типова причина: немає межі між локальними тестами (mocks/fault injection) і інтеграційним шаром.
Коротко
- Mocking і fault injection перевіряють, як агент обробляє збої інструментів.
- Один тип збою тестується окремим детермінованим тестом.
- Перевіряйте не лише текст, а й retry, fallback,
stop_reasonта формат помилки. - Критичні fault-тести мають запускатися в кожному PR.
FAQ
Q: Чи можна тестувати збої без реального API?
A: Так. Для unit-рівня це стандартний підхід: fakes і mocks дають стабільний, відтворюваний сигнал.
Q: Що важливіше: retry чи fallback?
A: Обидва. Retry покриває короткі збої, а fallback захищає сценарій, коли основний інструмент недоступний довше.
Q: Скільки профілів збою треба мати на інструмент?
A: Мінімум три: timeout, серверна помилка (5xx) і невалідний payload.
Q: Це замінює eval harness і regression?
A: Ні. Ці тести закривають локальну поведінку інструментного шару. Поведінку системи на завершених сценаріях перевіряють eval harness і regression.
Що далі
Підключіть fault-кейси до Eval Harness і зафіксуйте їх у Golden Datasets. Для контролю змін між версіями додайте Regression Testing, а інциденти розбирайте через Replay and Debugging.