СОЗДАНИЕ ДС С ЛОКАЛЬНОЙ LLM. ЧАСТЬ 3: ТЕХНИКА «ПОДСКАЗОК» (STRUCTURAL HINTS)

В предыдущих частях мы настроили LLM на выдачу строгого JSON. Но структура — это только полдела. Если модель вернет технически валидный JSON, в котором написано “2 + 2 = 5” или придуман несуществующий пункт договора, автоматизация принесет больше вреда, чем пользы.

Языковые модели — это “гуманитарии”. Они отлично генерируют связный текст, но часто ошибаются в арифметике и строгой навигации по большим документам. Попросить модель “найди последний пункт в разделе 3 и добавь следующий” — это риск. Она может пропустить пункт 3.12 и решить, что последний — 3.9, создав дубликат или дыру в нумерации.

РЕШЕНИЕ: ГИБРИДНЫЙ ИНТЕЛЛЕКТ (PYTHON + LLM)

Чтобы исключить галлюцинации в цифрах, я применил подход “Structural Hints” (структурные подсказки). Идея проста: всю “математику”, навигацию и анализ структуры берет на себя жесткий алгоритм на Python, а LLM занимается только лингвистической частью и упаковкой данных.

АЛГОРИТМ ДЕЙСТВИЙ

  1. Парсинг структуры. Python пробегает по JSON-представлению договора (которое мы получили из базы данных) и строит карту: какие есть разделы, какие в них пункты.

  2. Вычисление следующего шага. Скрипт находит последний пункт в нужном разделе (например, 5.4) и математически вычисляет номер следующего (5.5).

  3. Формирование подсказки. Эти точные, проверенные данные передаются модели вместе с текстом договора.

РЕАЛИЗАЦИЯ В КОДЕ

В функции structural_hints_for_llm скрипт ищет ключевые разделы с помощью регулярных выражений (Regex):

  • “ПРЕДМЕТ”
  • “ЦЕНА” или “ОПЛАТА”
  • “СРОК”

Для каждого найденного раздела вычисляется набор параметров:

  • Тип единицы (Статья, Раздел, Глава)
  • Текущий номер раздела
  • Номер последнего существующего пункта
  • Номер НОВОГО пункта (last + 1)

Пример логики вычисления номера (упрощенно):

def compute_next(unit_number, clause_numbers):
    nums = []
    # Извлекаем числа из строк "3.1", "3.2"...
    for c in clause_numbers:
         m = re.match(...)
         nums.append(int(m.group(1)))
    
    last = max(nums)
    return f"{unit_number}.{last}", f"{unit_number}.{last+1}"

ГЕНЕРАЦИЯ ГРАММАТИКИ

Здесь же Python готовит фразы в нужных падежах, чтобы разгрузить модель от лишних размышлений. Функция acc_phrase проверяет тип раздела:

  • Если “Статья” -> возвращает “статью 5” (Винительный падеж)
  • Если “Раздел” -> возвращает “раздел 5”

ИНЪЕКЦИЯ В ПРОМПТ

Все вычисленные данные собираются в объект hints и добавляются в конец пользовательского промпта:

"Подсказки: {
    'тип_единицы_предмет': 'Раздел',
    'номер_единицы_предмет': '2',
    'последний_пункт_предмет': '2.4',
    'добавляемый_пункт_предмет': '2.5',
    'фраза_единицы_предмет_вин': 'раздел 2',
    ...
}"

Модели остается только взять эти значения и положить их в соответствующие поля JSON-ответа. Ей не нужно искать, считать или думать. Ей нужно просто “переписать” наши подсказки в финальный формат.

РЕЗУЛЬТАТ

  1. 100% точность нумерации. Мы никогда не создадим пункт 2.5, если в договоре уже есть 2.6. Алгоритм видит всё.

  2. Скорость. Модели не нужно сканировать весь текст договора и держать его структуру в “голове”. Мы даем ей готовый ответ, что ускоряет генерацию.

  3. Надежность на слабых моделях. Даже если использовать менее мощную квантованную модель, она справится с задачей, потому что творческая задача “придумать номер” заменена на механическую задачу “скопировать номер из подсказки”.

В следующей, заключительной части, мы рассмотрим финальную сборку документа DOCX из полученных данных.

enes