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

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

2026/03/01 15:46
11м. чтение

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

8287ff51b3356853ca29026fb48b0980.png

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

Таблица типовых рисков — десятки формулировок вида «если в договоре встречается условие 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. Вот упрощенный вид системного промпта структурированного режима (схема взята из реального кода):

{ "risk_number": "4.2.3", "contract_filename": "doc.docx", "matches": [ { "clause_number": "4.4", "confidence": 0.0-1.0, "reason_short": "кратко" } ] }

Здесь и проявляется 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»

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

В итоге модель видит конкретный риск в его полном контексте, короткий список пунктов-кандидатов на риск, с их номерами. В рамках схемы 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" } }

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

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

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

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

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

{ "risk_number": "4.4.5", "contract_filename": "doc.docx", "hits": [ { "chunk_index": 12, "confidence": 0.83, "explanation": "…", "short_quote": "…" } ] }

Здесь снова используется 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 разделяется по договорам и разделам, а не случайно по строкам.

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

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

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

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

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

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

  • Бэкенд на FastAPI;

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

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

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

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

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

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

Итоги

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

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

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

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

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

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

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

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

Источник

Отказ от ответственности: Статьи, размещенные на этом веб-сайте, взяты из общедоступных источников и предоставляются исключительно в информационных целях. Они не обязательно отражают точку зрения MEXC. Все права принадлежат первоисточникам. Если вы считаете, что какой-либо контент нарушает права третьих лиц, пожалуйста, обратитесь по адресу [email protected] для его удаления. MEXC не дает никаких гарантий в отношении точности, полноты или своевременности контента и не несет ответственности за любые действия, предпринятые на основе предоставленной информации. Контент не является финансовой, юридической или иной профессиональной консультацией и не должен рассматриваться как рекомендация или одобрение со стороны MEXC.

Вам также может быть интересно