СОЗДАНИЕ ДС С ЛОКАЛЬНОЙ LLM. ЧАСТЬ 3: ТЕХНИКА «ПОДСКАЗОК» (STRUCTURAL HINTS)
В предыдущих частях мы настроили LLM на выдачу строгого JSON. Но структура — это только полдела. Если модель вернет технически валидный JSON, в котором написано “2 + 2 = 5” или придуман несуществующий пункт договора, автоматизация принесет больше вреда, чем пользы.
Языковые модели — это “гуманитарии”. Они отлично генерируют связный текст, но часто ошибаются в арифметике и строгой навигации по большим документам. Попросить модель “найди последний пункт в разделе 3 и добавь следующий” — это риск. Она может пропустить пункт 3.12 и решить, что последний — 3.9, создав дубликат или дыру в нумерации.
РЕШЕНИЕ: ГИБРИДНЫЙ ИНТЕЛЛЕКТ (PYTHON + LLM)
Чтобы исключить галлюцинации в цифрах, я применил подход “Structural Hints” (структурные подсказки). Идея проста: всю “математику”, навигацию и анализ структуры берет на себя жесткий алгоритм на Python, а LLM занимается только лингвистической частью и упаковкой данных.
АЛГОРИТМ ДЕЙСТВИЙ
Парсинг структуры. Python пробегает по JSON-представлению договора (которое мы получили из базы данных) и строит карту: какие есть разделы, какие в них пункты.
Вычисление следующего шага. Скрипт находит последний пункт в нужном разделе (например, 5.4) и математически вычисляет номер следующего (5.5).
Формирование подсказки. Эти точные, проверенные данные передаются модели вместе с текстом договора.
РЕАЛИЗАЦИЯ В КОДЕ
В функции 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-ответа. Ей не нужно искать, считать или думать. Ей нужно просто “переписать” наши подсказки в финальный формат.
РЕЗУЛЬТАТ
100% точность нумерации. Мы никогда не создадим пункт 2.5, если в договоре уже есть 2.6. Алгоритм видит всё.
Скорость. Модели не нужно сканировать весь текст договора и держать его структуру в “голове”. Мы даем ей готовый ответ, что ускоряет генерацию.
Надежность на слабых моделях. Даже если использовать менее мощную квантованную модель, она справится с задачей, потому что творческая задача “придумать номер” заменена на механическую задачу “скопировать номер из подсказки”.
В следующей, заключительной части, мы рассмотрим финальную сборку документа DOCX из полученных данных.