Автоматизация с языковыми моделями без сюрпризов: проверка, повтор и человек в контуре
Практический шаблон встраивания LLM в рабочий процесс: структурированный ответ, проверка по схеме, повторы с ограничением, порог ручной проверки, журнал и регрессионные тесты.
Языковая модель хорошо превращает неструктурированный текст в структурированные данные: разбирает письма, классифицирует обращения, извлекает реквизиты, готовит черновики ответов. Но её ответ — вероятностный. Один и тот же запрос может дать разный результат, а уверенный тон не гарантирует правильности. Поэтому надёжная автоматизация строится так, чтобы ошибки модели обнаруживались до того, как превратятся в действие.
Где модель уместна, а где нет
Хорошие задачи для LLM:
- классификация текстов по заранее известному списку категорий;
- извлечение полей из писем, заявок и документов;
- черновики ответов, которые потом проверяет человек;
- нормализация данных: приведение адресов, названий и дат к одному виду.
Задачи, где модель не должна принимать решение одна:
- точные расчёты — их лучше выполнять обычным кодом, а модели оставить разбор входных данных;
- решения с юридическими или финансовыми последствиями без проверки человеком;
- всё, где ошибку нельзя обнаружить и исправить.
Шаблон конвейера
Надёжный конвейер выглядит так:
- Вход — исходный текст и минимально необходимый контекст.
- Запрос к модели с требованием ответить в строго заданном формате, например JSON.
- Проверка формата по схеме: обязательные поля, типы, допустимые значения.
- Бизнес-правила: сумма не отрицательна, дата не в будущем, категория существует.
- Решение о ручной проверке: если ответ не прошёл правила или модель указала низкую уверенность, задача уходит человеку.
- Действие — только после всех проверок, с ключом идемпотентности.
- Журнал: вход, версия инструкции и модели, ответ, результат проверок.
Проверка ответа
Формат удобно описывать стандартом JSON Schema. Если подключать внешнюю библиотеку не хочется, основную часть проверок легко написать на стандартном Python:
import json
CATEGORIES = {"счёт", "договор", "претензия", "другое"}
def validate(raw: str) -> dict:
data = json.loads(raw) # не JSON — сразу ошибка
if set(data) != {"category", "amount", "confidence"}:
raise ValueError(f"неожиданные поля: {sorted(data)}")
if data["category"] not in CATEGORIES:
raise ValueError(f"неизвестная категория: {data['category']!r}")
amount = data["amount"]
if amount is not None and (not isinstance(amount, (int, float)) or amount < 0):
raise ValueError("сумма должна быть неотрицательным числом или null")
if not 0 <= data["confidence"] <= 1:
raise ValueError("confidence должен быть в диапазоне 0..1")
return data
Обратите внимание: проверяется не только синтаксис, но и смысл значений. Поле confidence — самооценка модели; она полезна как дополнительный сигнал, но не как гарантия, поэтому порог ручной проверки подбирают на реальных данных.
Повторы и ограничения
Сетевые ошибки, перегрузка API и невалидные ответы случаются регулярно. Правила обработки:
- Тайм-аут на каждый запрос. Без него один зависший вызов останавливает весь конвейер.
- Ограниченное число повторов — обычно два-три. Если ответ невалиден, при повторе можно передать модели текст ошибки проверки.
- Экспоненциальная задержка со случайным разбросом между повторами, чтобы множество клиентов не обращались к сервису одновременно. Подход подробно разобран в статье Exponential Backoff And Jitter в блоге AWS Architecture.
- Очередь на ручную обработку для всего, что не удалось обработать автоматически. Задача не должна молча теряться.
import random
import time
def call_with_retries(call, attempts=3, base=1.0, cap=20.0):
last_error = None
for attempt in range(attempts):
try:
return validate(call())
except (ValueError, TimeoutError) as exc: # json.JSONDecodeError — подкласс ValueError
last_error = exc
time.sleep(random.uniform(0, min(cap, base * 2 ** attempt)))
raise RuntimeError(f"передать человеку: {last_error}")
Идемпотентность действий
Если после ответа модели выполняется действие — создаётся задача, отправляется письмо, меняется запись в базе, — повтор запроса не должен приводить к дублю. Для этого каждой операции присваивают ключ идемпотентности, например хеш исходного сообщения, и перед действием проверяют, не выполнялось ли оно уже. Это обычная инженерная практика, но в системах с повторами она особенно важна.
Оценка качества и регрессии
Изменение инструкции или переход на другую модель может улучшить одни случаи и сломать другие. Минимальный набор мер:
- соберите эталонный набор из 50–200 реальных, обезличенных примеров с правильными ответами;
- считайте метрики по каждой категории отдельно — точность и полноту, а не только общую долю правильных ответов;
- прогоняйте набор при каждом изменении и сравнивайте с предыдущей версией;
- храните версию инструкции рядом с результатами, чтобы понимать, что именно изменилось.
Для более системного подхода к рискам ИИ-систем полезна рамка NIST AI Risk Management Framework: она описывает, как выявлять, измерять и контролировать риски на протяжении жизненного цикла системы.
Данные и конфиденциальность
- Передавайте модели только то, что нужно для задачи: если для классификации письма не нужна подпись с телефоном, её можно удалить заранее.
- Не отправляйте во внешние API пароли, ключи и токены — ни в инструкциях, ни во входных данных.
- Если обрабатываются персональные данные граждан России, учитывайте требования Федерального закона № 152-ФЗ «О персональных данных», в том числе к трансграничной передаче.
- Храните журналы с тем же уровнем защиты, что и исходные данные: в них оказываются фрагменты входных текстов.
Пример из практики этого сайта
Дайджесты Kamradu построены по тем же принципам, но в ещё более консервативном варианте. Автоматический процесс дважды в неделю читает официальные RSS-ленты, проверяет и очищает заголовки и ссылки, отбрасывает материалы без даты и уже использованные ссылки — и создаёт черновик, который не публикуется без проверки редактором. Языковая модель в этом процессе не используется вовсе: если правило можно выполнить обычным кодом, так и стоит делать.