中文翻译暂不可用,显示俄语原文。
Как делать property-based testing для агентов?
Краткий тезис
Property-based testing (тестирование на основе свойств) — это подход, при котором вы проверяете не конкретные ответы агента, а инвариантные свойства (properties), которые должны выполняться для широкого класса входных данных. В контексте AI-агентов это позволяет выявлять неочевидные баги (галлюцинации, неконсистентность, отказы вне домена) на случайных сгенерированных примерах. Основные инструменты — библиотека Hypothesis (Python) и концепция QuickCheck (Haskell), дополненные кастомными генераторами.
1. Отличие от традиционного unit-тестирования
| Характеристика | Unit-тесты | Property-based тесты |
|---|---|---|
| Описание | Проверяют ожидаемый вывод для известных входов | Генерируют случайные входы и проверяют инварианты |
| Покрытие | Ограничено ручными примерами | Автоматически покрывают краевые случаи |
| Обнаружение | Известные ошибки | Неожиданные граничные условия и скрытые баги |
| Пример | assert agent.answer("Какая столица Франции?") == "Париж" | Проверка, что ответ не содержит фактов вне контекста |
Property-based testing не заменяет unit-тесты, а дополняет их, выявляя контрпримеры, которые разработчик не предусмотрел.
2. Ключевые свойства (invariants) для AI-агентов
Для агента, работающего с RAG и инструментами, типичные свойства:
2.1 Consistency (согласованность)
При семантически эквивалентных перефразировках ответ должен быть семантически эквивалентен (или хотя бы не противоречить). Формально: если q1 и q2 — парафразы, то semantic_similarity(answer(q1), answer(q2)) ≥ ε.
2.2 No hallucination (отсутствие галлюцинаций)
Любое утверждение в ответе должно подтверждаться предоставленным контекстом (retrieved documents). Можно проверять через fine-grained entailment или кросс-проверку с LLM-судьёй.
2.3 Refusal on OOD (отказ на запрос вне домена)
Для запроса, не относящегося к базе знаний агента, ответ должен явно отказывать, а не галлюцинировать. Свойство: если query не имеет релевантных документов по метрике сходства, то answer содержит слова-маркеры отказа (например, “не знаю”, “не могу ответить”).
2.4 Idempotence (идемпотентность) при zero-temperature
При temperature=0 повторение того же запроса должно давать одинаковый ответ (в пределах детерминированного поведения). Исключение — недетерминированные компоненты (асинхронные тайминги, случайные shuffle документов), которые нужно замокать.
2.5 Monotonicity (монотонность качества)
Более точный или более полный контекст не должен ухудшать ответ. Если мы добавляем в контекст релевантный документ, то ответ должен быть не хуже (по faithfulness или полезности), чем с исходным меньшим контекстом.
Эти свойства можно комбинировать и адаптировать под конкретную архитектуру агента.
3. Инструменты: Hypothesis (Python)
Hypothesis — библиотека для property-based testing в Python. Основные элементы:
- @given(...) — декоратор, указывающий стратегию генерации входных данных.
- strategies — встроенные генераторы (st.text(),
st.integers(),st.lists(),st.from_regex()) и кастомные черезst.composite. - assume(condition) — фильтрация недопустимых входов.
- @example(...) — добавление конкретных примеров.
- settings — настройка (max_examples, deadline, suppress_health_check).
Пример теста на refusal on OOD:
from hypothesis import given, settings, assume
from hypothesis import strategies as st
from my_agent import agent
# Предположим, у агента есть список допустимых тем (домен)
VALID_TOPICS = [{python}, {machine_learning}, {data_science}]
@given(st.text(min_size=1, max_size=200))
@settings(max_examples=100)
def test_refusal_out_of_domain(query):
# Проверяем, что запрос не похож ни на одну тему
assume(not any(topic in query.lower() for topic in VALID_TOPICS))
answer = agent.answer(query)
refusal_words = ["не знаю", "не могу", "извините", "запрос вне области"]
assert any(word in answer.lower() for word in refusal_words), \
f"Агент ответил на внедоменный запрос: {query[:50]}..."
4. Кастомные генераторы для агентов
Агенты оперируют сложными структурами: последовательности вызовов инструментов, многопоточные запросы, диалоги. Hypothesis позволяет создавать генераторы для:
- Историй диалогов: чередование сообщений пользователя и ассистента.
- Конфигураций инструментов: набор доступных функций с параметрами.
- Сценариев с ошибками: таймауты, пустые ответы от API.
Пример генератора диалога:
import hypothesis.strategies as st
# Стратегия для одного сообщения
message = st.text(min_size=1, max_size=500)
# Диалог — список чередующихся ролей (user/assistant)
@st.composite
def dialogue_strategy(draw):
n = draw(st.integers(min_value=1, max_value=10))
roles = ["user"] * n + ["assistant"] * (n - 1)
st.shuffle(roles) # не идеально, но для примера
# Проще: чередование user/assistant
messages = []
for i in range(n):
messages.append({
"role": "user" if i % 2 == 0 else "assistant",
"content": draw(st.text(min_size=1, max_size=200))
})
return messages
5. Интеграция с контекстом RAG
Для свойства No hallucination нужно сгенерировать случайные контексты и проверить, что ответ не выходит за их пределы. Используем Hypothesis для генерации пар (контекст, запрос), где контекст может быть нерелевантным, частично релевантным или полностью покрывающим запрос.
@given(st.lists(st.text(min_size=10, max_size=200)), st.text(min_size=5, max_size=100))
@settings(max_examples=50)
def test_no_hallucination(context_docs, query):
# Генерируем ответ агента с заданным контекстом
answer = agent.answer(query, context=context_docs)
# Грубая проверка: каждое ключевое слово из ответа должно быть в контексте
# (упрощение; на практике используют NLI-модель)
keywords = [w for w in answer.split() if len(w) > 4]
for kw in keywords:
for doc in context_docs:
if kw in doc:
break
else:
# keyword не найден ни в одном документе — потенциальная галлюцинация
assert False, f"Найдено слово '{kw}' вне контекста"
6. Продвинутые техники: stateful testing и модели поведения
Stateful testing — когда агент имеет внутреннее состояние (память, подключения к инструментам). Hypothesis позволяет тестировать последовательности операций как автомат:
- определяем состояния (state machine);
- генерируем последовательность команд (
reset, query,use_tool); - проверяем инварианты после каждой команды.
Пример для агента с кэшем:
from hypothesis.stateful import RuleBasedStateMachine, rule, precondition
class AgentStateMachine(RuleBasedStateMachine):
def __init__(self):
self.agent = Agent()
self.cache = {}
@rule(query=st.text(), tool=st.sampled_from(["search", "calculate"]))
def query_agent(self, query, tool):
if query in self.cache:
# Ожидаем кэшированный ответ
assert self.agent.query(query, tool) == self.cache[query]
else:
result = self.agent.query(query, tool)
self.cache[query] = result
7. Профилирование и метрики покрытия
Property-based testing не гарантирует 100% покрытия, но вы можете оценить количество уникальных примеров и диапазон покрытых стратегий. Hypothesis встроено логирует контрпримеры и умеет их воспроизводить (запись в файл hypothesis-examples). Для агентов полезно:
- Установить max_examples в соответствии с временем ответа (обычно 50–200).
- Использовать deadline для выявления таймаутов.
- Включить Phantom (проверка покрытия кодовых путей) через pytest-cov.
8. Ограничения и best practices
| Ограничение | Решение |
|---|---|
| Стоимость LLM-вызовов | Ограничить количество примеров; использовать моки для инструментов; тестировать на локальной маленькой модели |
| Нечеткая природа свойств | Использовать LLM-оценщик для проверки семантических свойств (например, consistency) |
| Случайные сбои (flakiness) | Фиксировать seed (hypothesis.seed(42)) для воспроизводимости; использовать @example для известных краевых случаев |
| Долгое выполнение | Разделять тесты на быстрые (оффлайн проверки) и медленные (с запуском агента) |
Best practice: начинайте с простых свойств (refusal, idempotence), затем переходите к более сложным (no hallucination). Регулярно запускайте property-based тесты в CI, но с ограниченным числом примеров (например, 30).
Пет-проект для закрепления
Задача: Разработать property-based тесты для агента-консультанта по документации (например, для библиотеки matplotlib).
Инструменты: Python, Hypothesis, pytest, библиотека RAGAS (для оценки семантической близости).
Шаги:
- Определите 3 свойства: идемпотентность (при temp=0), отказ на запросы вне библиотеки (например, "как приготовить борщ"), консистентность между ответами на синонимичные вопросы ("как построить scatter" vs "как сделать scatter plot").
- Реализуйте генераторы запросов: используйте
st.from_regexдля шаблонов с API-функциями, иst.listsдля списка слов вне домена. - Напишите тесты с декоратором
@given. Для проверки consistency примените предобученный эмбеддинг (e.g. sentence-transformers) и порог косинусной близости. - Запустите тесты, найдите хотя бы один контрпример (например, агент может галлюцинировать при пустом контексте).
Ожидаемый результат: Вы получите репродуцируемые контрпримеры и улучшите агента (добавите проверку на пустой контекст, улучшите refusal).
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 5 | Оценка retrieval – база для свойства No hallucination |
| 10 | Self-RAG вводит рефлексию, которую можно тестировать на консистентность |
| 50 | Метрики faithfulness – основа для свойства No hallucination |
| 100 | Stateful testing для агентов с тулами |
| 200 | Общее введение в PBT (предполагаемый номер) |
| 30 | Свойство Refusal on malicious input |
Навигация
- Предыдущий: 786
- Следующий: 788
- Индекс: 00. Индекс разборов