- BrainTools - https://www.braintools.ru -

Анализ договорных рисков при помощи искусственного интеллекта

Всем привет! На Хабре регулярно появляются посты, так или иначе затрагивающие область права: от мировых антимонопольных споров до инициатив отечественных регуляторов. Но за громкими кейсами остается незамеченной другая интересная область — работа обычных юридических департаментов. В этой статье мы будем этот пробел восполнять: поделимся тем, как с помощью LLM анализировать поток из сотен договоров в ракурсе рисков и экономить на этом в год сотни часов работы юристов.

Анализ договорных рисков при помощи искусственного интеллекта - 1

В юридическом департаменте нас интересуют два артефакта.

Таблица типовых рисков — десятки формулировок вида «если в договоре встречается условие X, оно создает риск Y». Любой документ в компании должен быть проанализирован в ракурсе таблицы, это жесткая внутренняя норма.

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

Всю эту работу вполне можно автоматизировать с ИИ: изучение договора, сопоставление его с таблицей и выделение пунктов, которые отражают описанные в таблице риски. Юристу останется только проверить работу и вынести решение по договору.

Далее расскажем о том, как мы превратили это в LLM-пайплайн.

Формализация риска: от таблицы к схеме для модели

Сначала мы загрузили таблицу рисков из Excel и для удобства обработки преобразовали каждую строку во внутреннюю структуру данных (Data-класс) RiskRow со следующими полями:

  • number — порядковый номер риска (например, 4.6.4);

  • description — словесное описание условий риска;

  • consequences — последствия принятия условий риска;

  • nonfulfillment_risks — что будет, если условия не выполнить;

  • comment — примечания юристов.

Важно, что description — это не просто краткая метка риска, а развернутое текстовое описание условия на естественном языке. Мы используем её напрямую в промпте, как часть schema guided reasoning (структурированного рассуждения). О его реализации далее расскажем отдельно. Модель получает не только текст договора, но и формализованный «шаблон риска», для которого нужно найти соответствующие клаузы (пункты договора).

Параллельно в LLM закладывается политика риска — большой текстовый промпт с определением «что вообще считать договорным риском» и перечнем типичных источников этих рисков: нереалистичные сроки, зависимость от третьих лиц, некорректная подсудность, неудобный документооборот и т.д. Политика используется во «втором режиме» — policy-guided анализе неструктурированного текста.

Как выглядит риск в таблице, и что мы ищем в договоре

Чтобы не оставалось ощущения, что «риск» — это абстрактная метка, приведем пару реальных примеров из таблицы типовых рисков. В дальнейшем такие описания риска объединяются в единый текстовый вход (risk_blob), который подаётся в LLM в составе SGR-промпта.

Финансовый риск: «Цена АЗС, но не выше цены контракта»

В договоре иногда указывают, что отпуск топлива осуществляется по текущей цене на АЗС, но при этом вводится ограничение — не выше цены, зафиксированной в контракте. Под ценой контракта здесь понимается заранее согласованный максимальный уровень стоимости, который не меняется автоматически вслед за рынком.

Почему это риск:
если рыночная цена топлива растёт, а контрактный «потолок» остаётся прежним, поставка может стать убыточной. В таких ситуациях возникают спорные перерасчёты: фактическая цена на АЗС выше, но к оплате принимается ограничение из договора. Отказ поставлять по убыточной цене может повлечь штрафы или санкции за нарушение условий, которые формально не считаются просрочкой.

Как это обрабатывается через ИИ:

·    с помощью RapidFuzz, библиотеки для быстрого нечеткого сравнения текстов, из договора извлекаются пункты-кандидаты, где упоминаются цена, ограничения или перерасчёт;

·     LLM решает, о чём речь: о ценовом потолке или о формуле цены без ограничения. Последний случай, например, бывает, когда стоимость просто следует за рыночной ценой, а предел для нее не зафиксирован.

Операционный/ штрафной риск: «Срок предоставления отчётных документов менее 5 дней»

В договоре указано, что заказчик требует выдать закрывающие/ отчётные документы очень быстро — например, раньше внутренних стандартных сроков.

Здесь мы рискуем из-за отклонения от стандартного документооборота. Сбой ЭДО, логистики оригиналов, согласования — и мы рискуем наткнуться на штраф не за просрочку поставок, а за нарушение сопутствующих обязательств по документам. Что здесь делает ИИ:

  • Отбирает пункты-кандидаты в clause-режиме, то есть когда договор заранее разбит на пронумерованные пункты. Такие пункты определяются по формулировкам со словами «документы», «срок предоставления», «закрывающие», «акт/счёт/УПД», «в течение N дней»;

  • Ищет количество дней и проверяет смысл. Относится ли найденный срок именно к отчётным документам, какие условия срока указаны («после периода», «после поставки», «после оплаты»), для кого и какие санкции предусмотрены за задержки. .

Предобработка договора: как мы получаем клаузы

Перед тем как звать LLM, договор нужно привести к виду, который вообще можно отдавать для рассуждения. Документы, проходящие через систему, относятся к В2В и размещены в публичном доступе в ЕИС, поэтому сами по себе не содержат ограничений на передачу в LLM-модель. Это позволяет не вводить отдельный слой для деперсонализации, и сосредоточиться именно на структурировании текста и восстановлении нумерации пунктов. Так что ИИ анализирует исключительно содержание договора и не работает с персональными данными или иной чувствительной информацией

Принятие правок и очистка WordprocessingML

Договоры нам поступают в формате docx с включённым Track Changes (отслеживанием изменений). Риски часто прячутся именно в новых вставках. Вот как мы обрабатываем эти документы:

  • Распаковываем docx как zip.

  • В word/document.xml и во всех header*.xml/footer*.xml: узлы <w:del> удаляем (удалённый текст отбрасывается), узлы <w:ins> разворачиваем (вставленный текст поднимаем в поток документа).

  • Собираем временный «очищенный» docx и уже его отдаём библиотеке python-docx.

Фактически мы программно имитируем «Принять все изменения» внутри OpenXML.

Восстановление нумерации пунктов

У большинства договоров нумерация — не просто «4.6.4.» в тексте. Это абстрактные списки в numbering.xml, уровни (<w:lvl>), overrides и форматы (1., a), i)).

Что мы делаем:

  1. Парсим numbering.xml через lxml.

  2. Строим карту: numId → abstractNumId, для каждого уровня — формат и стартовое значение.

  3. Для каждого абзаца с w:numPr восстанавливаем «визуальный» номер, который реально видит человек: 4.6.4, 4.6.4.1 и т. п.

  • Парсим numbering.xml через lxml.

  • Строим карту:

    • numId abstractNumId,

    • для каждого уровня — формат и стартовое значение.

  • Для каждого абзаца с w:numPr восстанавливаем «визуальный» номер, который реально видит человек: 4.6.4, 4.6.4.1 и т. п.

Дальше из последовательности абзацев собираем клаузу:

  • Clause.number — полный номер (4.6.4);

  • Clause.text — текст пункта, включая все следующие за ним абзацы без номера (перечни/уточнения), пока не начнётся следующий пронумерованный пункт;

  • Clause.para_index — индекс абзаца для возможной привязки к исходному документу.

Именно с такими клаузами потом работает LLM.

Неструктурированный режим: скользящие чанки

Для приложений и фрагментов без понятной нумерации есть альтернативный путь:

  • вытащить весь текст,

  • порезать на чанки по ~1600 символов с перекрытием (например, 200),

  • каждому чанку присвоить индекс и границы в исходном тексте (start_char, end_char).

Это сырье для policy-guided режима.

Архитектура LLM-ядра: пошаговое рассуждение (СоТ) + Schema Guided Reasoning (SGR)

За всю эту работу с договорами отвечает LLM-движок, функционирующий в двух режимах:

  • Структурированный режим: риски оцениваются по списку пронумерованных пунктов.

  • Неструктурированный режим: риски оцениваются по списку чанков текста.

Оба режима используют reasoning-модель семейства GPT-5 с reasoning.effort (интенсивность рассуждений) на среднем или низком уровне — по сути, это и есть CoT. А также используют четко заданную JSON-схему ответа (Schema Guided Reasoning, SGR).

Что мы называем SGR в этом проекте

SGR (Schema Guided Reasoning) в нашем контексте — это подход, когда:

  • в system-промпте мы жестко задаем схему выходных данных (JSON с конкретными полями);

  • в user-промпте даем строго структурированный вход (risk_blob + список кандидатов);

  • LLM не просто «отвечает текстом», а вынуждена заполнять эту схему, опираясь на свое пошаговое рассуждение.

Схема управляет ходом рассуждений: модель сначала определяет, какие пункты договора соответствуют описанному риску, а затем заполняет поля результата:

·     clause_number — номер пункта договора (например, 4.6.4), где найден риск;

·     confidence — оценка уверенности модели в совпадении по шкале от 0 до 1;

·     reason_short — краткое пояснение (1–2 фразы), по каким признакам пункт считается рискованным.

Это не просто «форматирование вывода», а способ зафиксировать решение модели в проверяемом и интерпретируемом виде.

Структурированный режим: оценка рисков в пунктах договора

Опишем по порядку, что в этом режиме происходит.

Быстрый recall-фильтр

Для каждого риска можно взять конкатенацию:

номер + описание + последствия + риски неисполнения + комментарий

и через RapidFuzz найти топ-N клауз по простой метрике сходства (token_set_ratio). Это дешевый слой, который выполняет две важных задачи:

  • выбрасывает очевидно нерелевантные куски,

  • сохраняет высокую полноту (лучше взять чуть лишних кандидатов, чем потерять важный пункт).

На выходе получаем небольшой список Clause для каждого риска.

System-промпт: схема ответа

Дальше включается SGR. Вот упрощенный вид системного промпта структурированного режима (схема взята из реального кода):

Ты — русскоязычный юридический аналитик. Получишь: (1) одну строку из Таблицы типовых рисков; (2) список пронумерованных пунктов Договора.

 Задача — определить, какие номера пунктов Договора соответствуют описанному риску/условию. Учитывай синонимы и смысл, но НЕ выдумывай номера — используй только переданные. Если совпадений нет, верни пустой список.

 Формат вывода: строго JSON:

{
  "risk_number": "4.2.3",
  "contract_filename": "doc.docx",
  "matches": [
    {
      "clause_number": "4.4",
      "confidence": 0.0-1.0,
      "reason_short": "кратко"
    }
  ]
}
Анализ договорных рисков при помощи искусственного интеллекта - 2 [1]

Здесь и проявляется Schema Guided Reasoning:

Мы заранее описываем поля схемы:

risk_number — номер риска из таблицы (например, 4.6.4);

contract_filename — имя анализируемого файла договора;

matches[*].clause_number — номер пункта договора, который соответствует риску;

matches[*].confidence — уверенность модели в совпадении (0–1);

matches[*].reason_short — короткое объяснение, почему пункт помечен как риск.

 Модель в reasoning-режиме должна не просто выдать список номеров, а заполнить конкретные ячейки.

Пользовательский промпт: «risk_blob + clauses_blob»

Эта часть промпта собирается динамически:

Файл договора: <<имя файла>>

Риск: <<risk.number>>

Описание условия: <<risk.description>>

Последствия принятия условия: <<risk.consequences>>

Риски при неисполнении условия: <<risk.nonfulfillment_risks>>

Комментарий: <<risk.comment>>

 

Пункты договора (кандидаты):

– [4.6.4] текст пункта…

– [4.6.5] текст пункта…

 

Верни JSON (см. system).

В итоге модель видит конкретный риск в его полном контексте, короткий список пунктов-кандидатов на риск, с их номерами. В рамках схемы matches[ ] выдает только те пункты, которые действительно реализуют этот риск.

Пошаговое рассуждение: reasoning.effort=”medium”

Вызов к OpenAI Responses API концептуально выглядит так:

{
  "model": "gpt-5",
  "input": [
    { "role": "system", "content": [ { "type": "input_text", "text": SYS-TEM_PROMPT_STRUCTURED } ] },
    { "role": "user",   "content": [ { "type": "input_text", "text": us-er_prompt } ] }
  ],
  "reasoning": { "effort": "medium" }
}
Анализ договорных рисков при помощи искусственного интеллекта - 3 [1]

Параметр reasoning.effort включает у модели пошаговое рассуждение (CoT), а JSON-схема сжимает итоговое решение в нужный формат. Снаружи мы видим только финальный JSON, без внутренних мыслей.

Неструктурированный режим: поиск рисков по фрагментам текста (по политике рисков)

Для приложений и «неаккуратных» документов мы используем другой режим. В нем модель опирается не на нумерацию пунктов, а на общую политику рисков и список чанков текста.

Системный промпт неструктурированного режима

Системная часть задает схему и семантику:

Ты — русскоязычный юридический аналитик. Тебе даются: (1) одна строка из Таблицы типовых рисков; (2) список фрагментов неструктурированного текста договора («чанки») с индексами.

 Задача: определить, присутствует ли в ЛЮБОМ из фрагментов описанное риск-условие.

 Строго выводи ТОЛЬКО JSON. Формат:

{
  "risk_number": "4.4.5",
  "contract_filename": "doc.docx",
  "hits": [
    {
      "chunk_index": 12,
      "confidence": 0.83,
      "explanation": "…",
      "short_quote": "…"
    }
  ]
}
Анализ договорных рисков при помощи искусственного интеллекта - 4 [1]

Если риск не обнаружен — верни hits: [ ].

Здесь снова используется SGR: схема hits[ ] задаёт, что считать найденным риском и как его описать:

·     chunk_index — индекс фрагмента текста (чанка), где обнаружен риск;

·     confidence — уверенность модели (0–1);

·     explanation — краткое пояснение риска фрагмента;

·     short_quote — обоснование для риска по мнению ИИ. 

Пользовательский промпт: политика + чанки

Пользовательский промпт включает строку конкретного риска, политику с определением договорного риска и перечнем типовых источников, а также список чанков:

[12] текст чанка…

[13] текст чанка…

На выходе имеем массив объектов Hit. Потом мы его агрегируем по (contract, chunk_index) и фильтруем по порогу уверенности.

Экспериментальный протокол: dev → val → test

Промпты вполне могут подстроиться под один-два удобных договора. Чтобы так не произошло, эксперименты мы строили в классической схеме:

  1. Разработка. На небольшом подмножестве договоров одного раздела отлаживаем системный промпт и стартовый порог уверенности под высокий recall — например, 0.8.

  2. Валидация. Даем другой набор договоров из другого раздела.Фиксируем порог чуть ниже, чтобы поднять полноту — например, на 0.7

  3. Тестирование. Прогоняем остальные разделы и договоры с установленным промптом и порогом, изменяем только документы на входе.

Принципиальные условия:

  • Модель не видит правильные ответы — размеченные человеком риски по пунктам. На вход она получает только описание риска из таблицы и текст пунктов договора. Вывод ей приходится делать самостоятельно.

  • Dev/val/test разделяется по договорам и разделам, а не случайно по строкам.

  • На инференс-этапе из гиперпараметров вручную устанавливают только порог уверенности. Так поведение [2] системы будет достаточно прозрачно для бизнеса.

  • Модель не видит «разметку истины» — то есть заранее помеченные человеком ответы вроде «вот этот пункт = риск №4.6.4». На вход она получает только описание риска из таблицы и текст пунктов договора и должна сделать вывод самостоятельно;

  • Разделение dev/val/test идёт по договорам и разделам, а не по случайному сплиту строк из одного и того же документа;

  • Порог уверенности — единственный «ручной» гиперпараметр на инференс-этапе, что делает поведение [3] системы достаточно прозрачным для бизнеса.

Инфраструктура вокруг LLM

Вокруг LLM-ядра крутится вполне приземленный стек:

  • Бэкенд на FastAPI;

  • Очереди на Celery + Redis (отдельный воркер для тяжёлых LLM-тасков);

  • ORM на SQLAlchemy, хранение статуса задач и результатов в БД;

  • Файловое хранилище (shared volume) для загруженных документов, временных «очищенных» версий, итоговых отчетов в Excel.

  • Генерация отчётов на pandas + xlsxwriter: отдельные листы по договорам, подсветка рисков, аккуратная верстка;

  • Уведомления по почте с summary и ссылкой на отчет.

Вся тяжелая логика [4] — разбор Word, запуск пайплайна, вызовы LLM c SGR/CoT и агрегация результатов — живёт в воркер-процессе и не блокирует веб-API.

Итоги

Для начала подведем итоги на уровне AI-инженерии:

  • LLM использована не как «болтливый ассистент», а как строгий структурный классификатор, который работает по JSON-схеме и политике рисков.

  • Schema Guided Reasoning (SGR) реализован так. Системные промпты задают точную схему ответа. Пользовательские промпты собирают риск и структурированный список кандидатов. Reasoning-режим (reasoning.effort) заставляет модель рассуждать в рамках схемы.

  • Пошаговые рассуждения включены, но их результаты не идут в UI. Юрист видит компактные пояснение о риске и степень уверенности, а не поток мыслей модели.

  • Любой сырой документ Word превращается в последовательный набор пунктов для анализа — через предобработку и форматирование нумерации.

  • Экспериментальный протокол (dev/val/test) и явный порог уверенности дают понятный SLA по качеству и прозрачность для бизнеса.

У нас есть законченный LLM-модуль: работает с настоящими договорами в реальных юридических процессах. Опирается на нашу таблицу рисков, использует SGR и управляемый промпт-дизайн.

С точки зрения [5] бизнеса эффект оказался измеримым на потоках документов. Вместо двух часов теперь на один документ юрист тратит минуты. В сумме при более тысячи договоров в год освобождаются сотни человеко-часов на экспертные задачи, которые точно требуют участия человека. Вероятность пропуска рисков тоже сократилась. В итоге модуль стал обязательной частью работы: он стабильно проводит первичный анализ и снижает нагрузку на команду.

Автор: favioes

Источник [6]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/26403

URLs in this post:

[1] Image: https://sourcecraft.dev/

[2] поведение: http://www.braintools.ru/article/9372

[3] поведение: http://www.braintools.ru/article/5593

[4] логика: http://www.braintools.ru/article/7640

[5] зрения: http://www.braintools.ru/article/6238

[6] Источник: https://habr.com/ru/articles/1005144/?utm_campaign=1005144&utm_source=habrahabr&utm_medium=rss

www.BrainTools.ru

Rambler's Top100