СОЗДАНИЕ ДС С ЛОКАЛЬНОЙ LLM. ЧАСТЬ 4: РАБОТА С РУССКОЙ ГРАММАТИКОЙ

Автоматизация документов на английском языке — это прогулка в парке. Написал “Section [number]” — и готово. В русском языке мы сталкиваемся с падежами, родами и склонениями. “Дополнить Статью 5”, но “Руководствоваться Статьей 5”. “В лице Директора Иванова”, но “Подписал Директор Иванов”.

Если мы просто возьмем данные из JSON договора (где всё в именительном падеже) и вставим в шаблон ДС, получится: “в лице Генеральный директор Иванов И.И.”. Это выглядит непрофессионально и требует ручной правки.

Мы решаем эту проблему на трех уровнях: промпт-инжиниринг, структурные подсказки и постобработка кодом.

1. СКЛОНЕНИЕ ФИО И ДОЛЖНОСТЕЙ (LLM КАК ЛИНГВИСТ)

Самая сложная часть — преамбула. Нам нужно превратить “Иванов Иван Иванович” в “Иванова Ивана Ивановича”. Программные библиотеки для склонения (типа pymorphy2) хороши, но часто ошибаются на редких фамилиях или сложных должностях.

Языковая модель (Qwen 30B) обладает врожденным “чувством языка”. Поэтому мы делегируем эту задачу ей. В схеме данных мы запрашиваем два варианта:

  • “подписант_заказчика”: для блока подписей (Именительный падеж).
  • “подписантзаказчикав_родительном падеже”: для преамбулы.

В системном промпте есть строгая инструкция:

“Верни… должность и ФИО подписантов в родительном падеже… ВНИМАНИЕ: поля родительного падежа возвращай БЕЗ предлога ‘в лице’, только ‘<должность> <ФИО>’“.

Почему без “в лице”? Потому что этот предлог уже жестко прописан в шаблоне документа. Если модель вернет его снова, мы получим “в лице в лице…“.

2. ЗАЧИСТКА МУСОРА (PYTHON CLEANUP)

Даже с хорошим промптом LLM иногда “перестарается”. Модель может вернуть:

  • “генерального директора Иванова И.И. (действующего на основании Устава)” — лишнее уточнение в скобках.
  • “в лице директора Петрова” — всё-таки добавила предлог.

Чтобы гарантировать чистоту данных, я написал функцию-санитар sanitize_signatory_gen. Она использует регулярные выражения (Regex) для очистки строки перед вставкой в документ:

def sanitize_signatory_gen(s):
    # Удаляем всё в скобках (например, основания действий)
    x = re.sub(r"((?:[^()]*|([^()]*))*)", "", x)
    # Удаляем "в лице", если оно есть (регистронезависимо)
    x = re.sub(r"вs+лице", "", x, flags=re.IGNORECASE)
    # Убираем лишние пробелы и знаки препинания на концах
    return x.strip().strip(",;")

Это страховочная сетка, которая спасает от 99% мелких огрехов генерации.

3. ПАДЕЖИ ДЛЯ СТРУКТУРНЫХ ЕДИНИЦ

Вторая проблема — формулировки изменений. “Дополнить раздел 3 пунктом 3.5”. Слово “раздел” стоит в Винительном падеже (дополнить кого/что?), а “пункт” — в Творительном (кем/чем?). Если раздел называется “Статья”, то склонение меняется: “Дополнить статью 3”.

Здесь мы не полагаемся на LLM, а вычисляем правильную фразу алгоритмически (как описано в части про “Подсказки”) и передаем её в модель. Модель просто возвращает нам подготовленную фразу:

  • фраза_единицы_предмет_вин: “статью 3”
  • фраза_пункт_предмет_твор: “пунктом 3.5”

В шаблоне Word это выглядит так:

”[фразаединицыпредмет_вин] дополнить [фразапунктпредмет_твор] следующего содержания…”

Это делает шаблон универсальным: он сам подстраивается под тип единицы (Глава, Раздел, Статья) и сохраняет грамотность русского языка.

4. ИНИЦИАЛЫ

Мелочь, но важно. В договоре может быть написано “Иванов Иван Иванович”, а в подписи нужно “Иванов И.И.”. Функция to_initials на Python разбивает ФИО на слова и собирает сокращенную версию. Это чисто алгоритмическая задача, тратить на неё ресурсы нейросети нерационально.

ИТОГ

Работа с русским языком в автогенерации — это баланс.

  • Творческое склонение ФИО мы отдаем нейросети.
  • Жесткую грамматику терминов (“статью/раздел”) берем на себя через алгоритмы.
  • Мусор зачищаем регулярками.

В следующей части мы рассмотрим техническую сторону: как Python общается с локальным API-сервером и обрабатывает ошибки сети.

enes