Автоматизация с языковыми моделями без сюрпризов: проверка, повтор и человек в контуре

Практический шаблон встраивания LLM в рабочий процесс: структурированный ответ, проверка по схеме, повторы с ограничением, порог ручной проверки, журнал и регрессионные тесты.

Опубликовано · 5 мин чтения

  • автоматизация
  • надёжность
  • практика

Языковая модель хорошо превращает неструктурированный текст в структурированные данные: разбирает письма, классифицирует обращения, извлекает реквизиты, готовит черновики ответов. Но её ответ — вероятностный. Один и тот же запрос может дать разный результат, а уверенный тон не гарантирует правильности. Поэтому надёжная автоматизация строится так, чтобы ошибки модели обнаруживались до того, как превратятся в действие.

Где модель уместна, а где нет

Хорошие задачи для LLM:

  • классификация текстов по заранее известному списку категорий;
  • извлечение полей из писем, заявок и документов;
  • черновики ответов, которые потом проверяет человек;
  • нормализация данных: приведение адресов, названий и дат к одному виду.

Задачи, где модель не должна принимать решение одна:

  • точные расчёты — их лучше выполнять обычным кодом, а модели оставить разбор входных данных;
  • решения с юридическими или финансовыми последствиями без проверки человеком;
  • всё, где ошибку нельзя обнаружить и исправить.

Шаблон конвейера

Надёжный конвейер выглядит так:

  1. Вход — исходный текст и минимально необходимый контекст.
  2. Запрос к модели с требованием ответить в строго заданном формате, например JSON.
  3. Проверка формата по схеме: обязательные поля, типы, допустимые значения.
  4. Бизнес-правила: сумма не отрицательна, дата не в будущем, категория существует.
  5. Решение о ручной проверке: если ответ не прошёл правила или модель указала низкую уверенность, задача уходит человеку.
  6. Действие — только после всех проверок, с ключом идемпотентности.
  7. Журнал: вход, версия инструкции и модели, ответ, результат проверок.

Проверка ответа

Формат удобно описывать стандартом 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-ленты, проверяет и очищает заголовки и ссылки, отбрасывает материалы без даты и уже использованные ссылки — и создаёт черновик, который не публикуется без проверки редактором. Языковая модель в этом процессе не используется вовсе: если правило можно выполнить обычным кодом, так и стоит делать.

Источники

← Все статьи