СОЗДАНИЕ ДС С ЛОКАЛЬНОЙ 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. Надежность на слабых моделях. Даже если использовать менее мощную квантованную модель, она справится с задачей, потому что творческая задача "придумать номер" заменена на механическую задачу "скопировать номер из подсказки".

Кому, как и мне, интересно автоматизировать юридические процессы, присоединяйтесь ко мне в телеграме, там я пишу "человеческим языком", а не вот это вот всё)

Начать дискуссию