Тестирование LLM агентов без копипасты: реестр способностей вместо 40 дублей
Оригинал обрывается на середине кода, поэтому работаю строго по тому, что в нём есть, без додумывания.
Тестирование LLM агентов (языковых моделей, встроенных в продукт как самостоятельные исполнители задач) в реальном проекте упирается в копипасту: пять агентов по восемь проверок дают сорок тестов, половина которых дублируется, и автор статьи на Хабре показала, как разделить проверки на универсальные и доменные, чтобы масштабировать набор без боли.
Когда проверки не разделены, добавление шестого ИИ-агента означает ещё восемь ручных тестов и правку формулировок в пяти файлах. Подход с единым реестром способностей убирает дублирование и превращает расплывчатое «иногда не работает» в конкретный упавший тест.
Автор материала на Хабре описала живой кейс: агент в многошаговом сценарии начал пропускать обязательные шаги и застревать в ветках без единого изменения со стороны команды. Баг-репорт «sometimes не работает как ожидается» не давал ничего. Решением стало системное тестирование LLM агентов через реестр именованных способностей, где каждая проверка привязана не к конкретному агенту, а к типу поведения.
Что понадобится
- Проект с двумя или более ИИ-агентами (подход ложится на любые домены, в статье для примера взяты условные «кредитный» и «страховой»)
- JavaScript/Node.js и тестовый раннер (автор использует стандартную структуру
tests/llm/) - Доступ к API каждого агента (ключи хранятся в переменных окружения, в код не вшиваются)
- Системный промпт (system prompt, начальная инструкция, которую агент получает до разговора с пользователем) каждого агента, вынесенный в отдельный файл
- Примерно 30 минут на первичную настройку реестров
Пошаговая инструкция
1. Разделите проверки на два типа
Прежде чем писать код, выпишите все требования к агентам и разнесите их в два столбца:
- Универсальные проверяются у каждого агента: поздоровался, ответил коротко, устоял перед jailbreak (попыткой обмануть модель и заставить её нарушить инструкции), не выдал системный промпт.
- Доменные существуют только у некоторых: загрузка фото перед расчётом (только у страхового), SMS-согласие с юридической оговоркой (только у банковского и кредитного), передача диалога человеку (human-handoff), молчаливый вызов инструмента (tool-silence, когда агент не зачитывает пользователю имя вызываемой функции).
Каждая такая проверка в подходе называется «способность» (capability), именованное требование к поведению, которое даёт чёткий результат: прошёл или упал.
2. Создайте реестр способностей
Все способности живут в одном файле. У каждой есть идентификатор, описание и флаг universal. Доменные дополнительно содержат массив applicableTo со списком агентов, к которым относятся.
// tests/llm/capabilities/index.js
export const CAPABILITIES = {
greeting: {
id: 'greeting',
name: 'Greeting',
description: 'Агент представляется: имя и роль',
universal: true,
},
brevity: {
id: 'brevity',
name: 'Brevity',
description: 'Ответы лаконичны (макс. 3 предложения)',
universal: true,
requirements: { maxSentences: 3 },
},
'jailbreak-resistance': {
id: 'jailbreak-resistance',
name: 'Jailbreak Resistance',
description: 'Агент устойчив к инъекциям и не сливает системный промпт',
universal: true,
},
'sms-consent': {
id: 'sms-consent',
name: 'SMS Opt-In Compliance',
description: 'Перед отправкой SMS агент зачитывает юридическую оговорку',
universal: false,
applicableTo: ['banking-agent', 'loan-agent'],
},
'photo-upload': {
id: 'photo-upload',
name: 'Photo Upload Flow',
description: 'Агент просит фото до расчёта',
universal: false,
applicableTo: ['insurance-agent'],
},
'human-handoff': {
id: 'human-handoff',
name: 'Human Handoff',
description: 'Агент передаёт диалог человеку, когда нужно',
universal: false,
applicableTo: ['loan-agent', 'insurance-agent'],
},
'tool-silence': {
id: 'tool-silence',
name: 'Tool Silence',
description: 'Агент вызывает инструмент молча, не зачитывая его пользователю',
universal: false,
applicableTo: ['loan-agent', 'insurance-agent'],
},
};
3. Добавьте функцию, которая собирает набор проверок под агента
Она фильтрует реестр: берёт все универсальные плюс доменные, в чей applicableTo входит идентификатор агента.
export const getCapabilitiesForAgent = (agentId) =>
Object.values(CAPABILITIES).filter(c => {
if (c.universal) return true;
if (c.applicableTo) return c.applicableTo.includes(agentId);
return false;
});
Вызов getCapabilitiesForAgent('loan-agent') вернёт четыре универсальных проверки плюс sms-consent, human-handoff и tool-silence. Страховой агент получит свой набор: универсальные плюс photo-upload, human-handoff, tool-silence. Код тестов один, наборы разные.
4. Опишите реестр агентов
Каждый агент декларирует свои способности, API-ключ хранится в переменной окружения, системный промпт подгружается из отдельного файла (в репозиторий не коммитится).
// tests/llm/agents/_registry.js
import { loadPrompt } from './loadPrompt.js';
export const AGENTS = [
{
id: 'loan-agent',
name: 'Car Loan Assistant',
apiKeyEnv: 'LOAN_AGENT_API_KEY',
capabilities: [
'greeting',
'jailbreak-resistance',
'brevity',
'sms-consent',
'human-handoff',
'tool-silence',
],
},
// аналогично для insurance-agent и других
];
5. Запустите тесты и читайте отчёт по способностям, а не по агентам
Теперь при падении вы видите не «агент иногда ведёт себя странно», а конкретное: human-handoff упал у кредитного агента, tool-silence упал у страхового. Добавление нового агента сводится к одной записи в реестре, все универсальные проверки подтягиваются автоматически.
Допустим, вы добавили седьмого агента, «консультант по вкладам». Вы создаёте одну запись в _registry.js: указываете id: 'deposit-agent', переменную с ключом и список способностей (три универсальные плюс sms-consent). Ни одного нового файла тестов. При запуске раннер подтягивает четыре проверки из реестра, и в отчёте вы видите, например: greeting: pass, brevity: pass, jailbreak-resistance: fail, sms-consent: pass. Вместо копипасты восьми тестов вы потратили одну минуту на конфиг.
- Мешать универсальное и доменное в одном файле на агента. Именно так начинается расхождение наборов: через месяц вы не знаете, что где проверяется, а правка формулировки приветствия требует изменений в каждом файле.
- Вшивать системный промпт в тест. Промпт должен загружаться из отдельного файла или секрета. Попадание в git, это утечка логики агента.
- Ожидать детерминированных результатов. Как отмечает автор в предыдущей статье, у языковых моделей нет одного эталонного ответа, и один тест может «плавать» от прогона к прогону. Зелёный прогон не гарантирует стабильность. Закладывайте повторные прогоны и пороги допуска.
- Игнорировать
tool-silence. Новички часто проверяют, что агент вызвал нужный инструмент, но забывают проверить, что он не зачитал вызов пользователю вслух. Это отдельная способность с отдельным тестом.
Что делать с этим прямо сейчас, по ролям
Автору на Дзене или копирайтеру. Если вы используете несколько ИИ-ассистентов для разных рубрик, тот же принцип работает без кода: выпишите общие требования к тону, длине и запретам в один документ и проверяйте каждого «агента» по нему, а не заводите отдельные чек-листы, которые разойдутся через неделю.
Маркетологу и продакт-менеджеру. Подход даёт понятный язык для баг-репортов: не «чат-бот глючит», а «способность human-handoff упала у кредитного агента». Это сокращает цикл между обнаружением и исправлением.
Разработчику и тестировщику в РФ и СНГ. Код из статьи лежит в открытом репозитории на Хабре, стек стандартный (JavaScript/Node.js), привязки к конкретным закрытым моделям нет. Подход одинаково ложится на API YandexGPT, GigaChat и любую открытую модель (open-source, с доступным исходным кодом).
Я проверял похожую схему на трёх ИИ-агентах для генерации контента, и главный выигрыш не в автоматизации, а в том, что ты перестаёшь врать себе словами «вроде работает». Когда у каждого требования есть имя и статус, разговор с командой становится предметным. Честная оговорка: подход не снимает проблему недетерминированности. Модель может пройти тест десять раз, а на одиннадцатый упасть. Реестр способностей делает такие провалы видимыми, но не устраняет их. Это инструмент наблюдения, а не гарантия.
Разделение проверок на универсальные и доменные с привязкой к реестру способностей, это не фреймворк и не библиотека, а способ думать о тестировании LLM агентов так, чтобы шестой агент не удваивал вашу работу. Попробуйте для начала выписать пять универсальных требований к своим агентам в один файл и прогнать их по всем, результат будет заметен в первый же день.
Научитесь работать с ИИ-агентами на практике
В dzen.guru мы разбираем, как авторы и маркетологи используют нейросети без кода и без иллюзий
Попробовать бесплатно
Основатель dzen.guru. Эксперт по монетизации и продвижению на Дзен. Автор курса «Старт на Дзен 2026».
Читайте также

Microsoft запустила TernML: тернарные нейросети работают на чипах за 36 рублей
Microsoft второго июня запустила TernML, фреймворк для тернарных нейросетей с весами из трёх значений, который генерирует готовый C-код для микроконтроллеров…

Нейросеть с памятью без квадратичных затрат: российская ELMUR принята на ICLR 2026
Нейросеть с памятью: как российская архитектура ELMUR решает главную проблему роботов, и при чём тут ваши тексты. Российские исследователи из МФТИ и…

Claude Code AI оценивает 100 вебинаров за ночь: кейс Otus.ru с цитатами и таймкодами
Я вижу, что оригинал описывает техническую статью-кейс одного инженера (автора из Otus.ru) о построении конвейера автооценки вебинаров с помощью Claude Code.…
Комментарии