Почему тестировать AI-агентов больно и дорого
Если вы уже собирали агента с тул-коллингом (читали гайд по архитектуре), то знаете этот момент: вы дергаете живой API, он падает, тарифицирует каждый вызов, а потом агент случайно удаляет тестовые данные. Знакомо? Теперь представьте, что таких вызовов сотни в одном тесте. Больно и дорого.
Раньше разработчики шли двумя путями: либо мокали всё руками (и теряли реалистичность), либо запускали тесты на production-подобных стендах с изолированными сервисами (и платили за инфраструктуру). Ни то, ни другое не помогает, когда агент принимает решения на основе ответов внешних инструментов — он обязан реагировать на непредсказуемые данные.
Проблема: фиксированные моки не учат агента справляться с изменчивыми ответами. А живой API — это риск счета за 1000 запросов к GPT и нечаянного DDoS на сервер погоды.
ToolSimulator: LLM-симуляция вместо живых сервисов
Amazon AWS выкатил ToolSimulator — часть пакета strands-evals. Идея простая: вместо реального API вы подсовываете агенту LLM, который эмулирует поведение инструмента. Тот же тул-коллинг, те же аргументы, но ответ генерируется нейросеткой. Вы контролируете сценарии: хотите — возвращайте ошибку, хотите — задержку, хотите — специфичный формат данных.
Фреймворк интегрируется с OpenAI, Anthropic, Bedrock и локальными моделями. Зачем это нужно? Чтобы тестировать краевые случаи — когда API отвечает битым JSON, когда сервер молчит 30 секунд, когда инструмент возвращает неожиданные поля. Живой API так не подстроишь.
Установка: одна команда, никаких зависимостей к Амазону
pip install strands-evalsВсё. Ни регистрации в AWS, ни ключей доступа к API. ToolSimulator не требует облачных ресурсов — он работает локально как часть вашего Python-окружения. Если хотите симуляцию без внешних LLM, можно подключить локальные модели через Ollama или LM Studio.
Совет: ставьте SDK в отдельный virtualenv — в нём много зависимостей (transformers, pydantic, httpx), но конфликтов с проектом не будет.
Первые шаги: симулируем инструмент 'get_weather'
Представьте, что у вас есть агент, который вызывает get_weather(city). Вместо реального API погоды подключим симулятор.
from strands_evals.simulator import ToolSimulator
sim = ToolSimulator(model="gpt-4o") # можно 'claude-sonnet-4-20250514' или локальную
@sim.mock("get_weather")
def weather_sim(city: str) -> dict:
return {
"city": city,
"temperature": 22,
"condition": "sunny"
}
# Теперь агент видит этот инструмент как настоящий
result = sim.call("get_weather", city="Berlin")
print(result) # {'city': 'Berlin', 'temperature': 22, 'condition': 'sunny'}За кулисами LLM генерирует ответ строго по схеме, которую вы задали. Если попросить result["error"] = "timeout" — модель подстроит текст и структуру. Никакого реального HTTP-запроса.
Как НЕ надо делать: не пишите в @mock декоратор, который вызывает настоящий API — вы сведёте на нет смысл симуляции. ToolSimulator должен быть единственным источником ответов для тестируемого агента.
Сравнение с альтернативами
| Инструмент | Тип симуляции | LLM-генерация | Стоимость |
|---|---|---|---|
| ToolSimulator | Динамическая, LLM | Да | Бесплатно (open source) |
| WireMock / MockServer | Статическая, на основе записей | Нет | Бесплатно |
| unittest.mock (Python) | Жёсткая, ручная | Нет | Бесплатно |
| VCR.py | Запись/воспроизведение | Нет | Бесплатно |
Статические моки (WireMock, VCR) хороши, но они не умеют адаптироваться к нестандартным аргументам. Если агент передаст в get_weather(city="New York", units="metric"), а мок знает только "city" — тест упадёт. ToolSimulator с LLM сам сгенерирует осмысленный ответ на любое сочетание параметров. Это критично при тестировании сложных цепочек вызовов, например, multi-turn сценариев.
Продвинутый сценарий: симуляция с ошибками и задержками
Настоящая мощь ToolSimulator раскрывается, когда вы начинаете провоцировать агента на нештатные ситуации. Вот пример, как заставить симулятор возвращать таймаут или битые данные:
import random
from strands_evals.simulator import ToolSimulator, SimulationScenario
scenario = SimulationScenario(
failure_rate=0.3, # 30% вызовов — ошибка
latency_range=(0.1, 5.0), # задержка от 100 мс до 5 сек
corrupted_response_rate=0.1 # 10% — невалидный JSON
)
sim = ToolSimulator(model="gpt-4o-mini", scenario=scenario)
@sim.mock("send_email")
def email_sim(to: str, subject: str, body: str):
if random.random() < 0.3:
raise TimeoutError("SMTP server not responding")
return {"status": "sent", "to": to}Агент, который не обрабатывает таймауты, провалит тест. ToolSimulator даёт вам сотни вариаций без единого реального письма. В статье про суб-агентов мы показывали, как такая симуляция помогает отлавливать баги в цепочках вызовов.
Кому ToolSimulator нужен прямо сейчас
- Разработчикам AI-агентов, которые устали мокать вручную и хотят реалистичное поведение инструментов.
- QA-инженерам, пишущим регрессионные тесты на агентов — симуляция позволяет проверить реакцию на любые сценарии без риска для production.
- Командам, которые внедряют оценочные фреймворки от Amazon — ToolSimulator легко интегрируется с ними.
- Всем, кто занимается бенчмаркингом LLM и хочет повторяемые условия тестов.
Инструмент открыт, бесплатен, не требует интернета (если используете локальную модель). Единственный минус — нужно хотя бы минимальное понимание, как работают tool calling и LLM. Но если вы уже читали нашу статью про сборку агентов из LEGO, то проблем не будет.
Попробуйте начать с малого: симулируйте один инструмент, потом два с зависимостями. Когда увидите, как агент справляется с 20% ошибок, вы поймёте, что без симуляции вы тестировали только идеальные сценарии. А идеальных сценариев в продакшене не бывает.