English translation is not available yet. Showing Russian content.
Как тестировать агентов на недетерминированность?
Краткий тезис
Агентные системы по своей природе недетерминированы: LLM выводят вероятностные ответы, API|внешние API могут давать разные результаты, время выполнения варьируется. Чтобы тестировать такие системы, нельзя просто сравнивать «ожидаемый результат». Вместо этого применяют детерминированные семена (seed), многократные прогоны (multiple runs), статистические тесты, Монте-Карло симуляции и выделение золотого пути (path|golden path) — детерминированного ядра агента. Цель — оценить разброс метрик и убедиться, что агент стабильно решает задачу в пределах допустимой вариативности.
1. Что такое недетерминированность агента
Недетерминированность — свойство системы, при котором один и тот же вход (запрос пользователя, состояние окружения) может приводить к разным выходам. В агентах источники недетерминированности:
- LLM с temperature > 0 — вероятностная выборка токенов;
- Внешние API — задержки, ошибки, нестабильные ответы (например, поисковые системы, базы данных);
- Тайминги — разное время выполнения может влиять на порядок обработки параллельных вызовов;
- Рандомизированные алгоритмы — эвристики с random seed (например, выбор инструмента на основе семпла).
Почему это проблема для тестирования Традиционные подходы (юнит-тесты с assert на точный ответ) не работают — ожидаемый результат может отличаться даже при корректном поведении.
2. Зачем тестировать недетерминированность
- Воспроизводимость багов: если сбой случается не каждый раз, его трудно локализовать без контроля случайности.
- Надёжность в production: агент должен стабильно давать полезный результат, а не «зависать» на одной из ветвей.
- Метрики качества: нужно убедиться, что разброс метрик (accuracy, latency) находится в допустимых пределах.
- Доверие пользователей: если агент сегодня ответил одно, а завтра другое — пользователь перестаёт ему верить.
3. Метод 1: Детерминированный seed (Deterministic seed)
Фиксируем все источники случайности:
- LLM: установить temperature=0 (жадная декодировка — детерминированно) и фиксировать seed (где поддерживается).
- Генератор случайных чисел Python: random.seed(42) и numpy.random.seed(42).
- Прочие библиотеки: torch.manual_seed(42), tf.random.set_seed(42).
import random, numpy as np, torch, os
def set_global_seed(seed=42):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
os.environ["PYTHONHASHSEED"] = str(seed)
# Пример вызова LLM с детерминированным режимом
llm = ChatOpenAI(model="gpt-4", temperature=0, seed=42)
Ограничения:
- Не все провайдеры LLM поддерживают seed (например, Anthropic Claude в API не принимает seed).
- Внешние API (поиск, погода) не управляются seed.
- Результат может меняться при обновлении модели.
Когда использовать: для регрессионных тестов и отладки конкретных сценариев.
4. Метод 2: Многократные прогоны (Multiple runs)
Запускаем агента на одном и том же входе N раз (например, 10-50) и собираем статистику.
Шаги:
- Зафиксировать вход (query, контекст, доступные инструменты).
- Запустить агента N раз.
- Для каждого прогона записать: вывод, время выполнения, использованные инструменты.
- Построить распределение метрик.
Метрики для сбора:
| Метрика | Что измеряет |
|---|---|
| Точность (accuracy) | Доля прогонов с корректным конечным ответом (если есть золотой ответ) |
| Разброс ответов (diversity) | Количество уникальных ответов / N |
| Среднее время | Средняя задержка |
| Std времени | Стабильность времени |
| % успешных вызовов | Доля прогонов без ошибок |
import statistics
def test_agent_several_runs(agent, query, n=10):
results = []
for _ in range(n):
result = agent.run(query)
results.append(result)
# Пример: проверка, что точность >= 80%
correct = sum(1 for r in results if is_correct(r)) / n
assert correct >= 0.8, f"Accuracy too low: {correct}"
Ограничения: требует больше времени, не даёт строгой гарантии.
5. Метод 3: Статистические тесты (Statistical tests)
Используем доверительные интервалы и гипотезы:
- Доверительный интервал для метрики: запускаем N прогонов, считаем среднее и 95% доверительный интервал (через bootstrap или нормальное приближение).
- Проверка гипотезы: H0 — точность >= порога, H1 — ниже. Если p-value > 0.05, то не отвергаем H0.
Формула bootstrap для среднего (accuracy):
1. Из N наблюдений с заменой генерируем B=1000 выборок.
2. Для каждой считаем среднее.
3. 95% CI — 2.5-й и 97.5-й перцентили этих средних.
import numpy as np
def bootstrap_ci(accuracies, B=1000, alpha=0.05):
boots = []
for _ in range(B):
sample = np.random.choice(accuracies, size=len(accuracies), replace=True)
boots.append(np.mean(sample))
lower = np.percentile(boots, 100 * alpha / 2)
upper = np.percentile(boots, 100 * (1 - alpha / 2))
return lower, upper
accuracies = [0.9, 0.7, 0.8, 1.0, 0.9, 0.6, 0.95, 0.85, 0.75, 0.9]
ci = bootstrap_ci(accuracies)
assert ci[0] >= 0.7, f"CI lower bound {ci[0]:.2f} < 0.7"
Ограничения: нужно достаточно прогонов (обычно >= 30 для нормального приближения), интерпретация p-value может быть неочевидна.
6. Метод 4: Монте-Карло симуляции (Monte Carlo simulation)
Агент может иметь несколько точек недетерминизма (выбор инструмента, вызов LLM, задержки). Монте-Карло — имитация множества траекторий с разными случайными выборами.
Идея:
- Заменяем реальные LLM-вызовы на mock с заданной вероятностью успеха/неудачи.
- Или используем симулятор окружения с заданным распределением задержек.
- Прогоняем тысячи симуляций, оцениваем вероятность неудачи (risk) или распределение метрик.
Пример: агент делает три шага; на каждом шаге есть 10% шанс ошибки. Какова вероятность, что агент завершит успешно?
import random
def simulate_agent_trajectory(error_prob=0.1):
steps = 3
for _ in range(steps):
if random.random() < error_prob:
return "fail"
return "success"
def monte_carlo_estimate(num_simulations=10000):
successes = sum(1 for _ in range(num_simulations) if simulate_agent_trajectory() == "success")
return successes / num_simulations
prob_success = monte_carlo_estimate()
print(f"Вероятность успеха: {prob_success:.2%}")
Применение: тестирование отказоустойчивости, изучение "хвостов" (p99 latency), определение необходимого числа повторных попыток.
7. Метод 5: Golden path (золотой путь)
Выделяем детерминированное ядро агента — часть, которая не вызывает LLM или внешние API. Например:
- Логика выбора инструмента на основе фиксированных правил (if-else).
- Обработка результатов, форматирование ответа.
- Безопасные проверки (валидация входов).
Тестирование golden path можно делать обычными юнит-тестами с assert.
Пример: агент сначала вызывает инструмент поиска, потом LLM. Можно отдельно протестировать:
- Функцию парсинга результатов поиска (детерминированная).
- Функцию сборки промпта (детерминированная).
- Функцию пост-обработки ответа LLM (детерминированная).
def parse_search_results(raw: str) -> list:
# детерминированный парсинг
return [line.strip() for line in raw.split("\n") if line]
def test_parse_search_results():
raw = "result1\nresult2"
assert parse_search_results(raw) == ["result1", "result2"]
Преимущество: недетерминированность изолируется в тонких слоях (LLM, API), а ядро можно тестировать быстро и надёжно.
8. Инструменты для тестирования недетерминированных агентов
| Инструмент | Назначение | Пример использования |
|---|---|---|
| pytest + параметризация | Многократный запуск тестов | @pytest.mark.parametrize("run", range(5)) |
| hypothesis | Property-based testing, поиск крайних случаев | @given(st.integers()) |
langchain @configurable | Настройка seed, temperature | config = {"llm": ChatOpenAI(temperature=0, seed=42)} |
| mlflow / wandb | Логирование распределений метрик | wandb.log({"accuracy_mean": acc_mean, "accuracy_std": acc_std}) |
| deepdiff | Сравнение структурированных выводов (игнорируя небольшие расхождения) | from deepdiff import DeepDiff |
9. Метрики нестабильности агента
Для количественной оценки недетерминированности вводим:
- Variance of accuracy: std(accuracy_i) по разным seed или прогонам.
- Response diversity: количество уникальных финальных ответов / число прогонов.
- Latency stability: коэффициент вариации (std/mean) времени выполнения.
- Tool call consistency: для заданного входа агент всегда ли выбирает одинаковый инструмент?
from collections import Counter
def tool_consistency(agent, query, n=10):
tools_used = []
for _ in range(n):
trace = agent.get_trace(query)
tools_used.append(trace.final_tool) # предположим
counts = Counter(tools_used)
most_common_ratio = counts.most_common(1)[0][1] / n
return most_common_ratio # 1.0 = всегда один и тот же инструмент
10. Практическая стратегия тестирования
Рекомендуемый подход в enterprise:
- Golden path → 90% кода покрыть обычными юнит-тестами (детерминированная логика).
- Seed-тесты → для критических LLM-вызовов зафиксировать temperature=0 и seed, записать эталонные ответы.
- Статистические тесты → для end-to-end сценариев: 5-10 прогонов, проверка, что accuracy >= порога с bootstrap CI.
- Монте-Карло → для оценки рисков при высокой нагрузке или ненадёжных API.
- CI/CD пайплайн:
Пет-проект для закрепления
Задача: Разработать простого агента, который отвечает на вопрос, используя результаты поиска через API (например, DuckDuckGo). Агент недетерминирован из-за разных результатов поиска и LLM температуры. Написать тесты.
Инструменты: Python, pytest, duckduckgo_search, langchain или прямые вызовы OpenAI/Anthropic API, библиотека numpy для статистики.
Шаги:
- Реализуйте агента: получает вопрос → вызывает DuckDuckGo → формирует промпт с результатами → LLM генерирует ответ.
- Напишите тест
test_golden_path— замените поиск и LLM на заглушки (детерминированные). - Напишите тест
test_seed_reproducibility— зафиксируйте temperature=0 и seed, проверьте, что ответ совпадает с эталоном. - Напишите тест
test_multiple_runs— запустите 10 раз с реальным поиском, убедитесь, что ответ всегда осмыслен (например, содержит ключевые слова). - Напишите тест
test_monte_carlo_risk— симулируйте 1000 сценариев, где поиск возвращает ошибку с вероятностью 20% (mock), проверьте, что агент обрабатывает ошибки.
Ожидаемый результат: тесты проходят, вы получаете количественное представление о вариативности агента и можете уверенно его релизить.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 781 | Как спроектировать агентную архитектуру? |
| 782 | Какие инструменты и функции должен использовать агент? |
| 783 | Как управлять памятью агента (conversation history)? |
| 784 | Как обеспечить безопасность и пермишены в агенте? |
| 786 | Как обеспечить наблюдаемость и отладку агента? |
Навигация
- Предыдущий: 784
- Следующий: 786
- Индекс: 00. Индекс разборов