Самый быстрый способ выпустить опасного юридического ассистента — оптимизировать гладкость текста раньше дисциплины работы с источниками. Главная работа не в том, чтобы модель звучала убедительно. В том, чтобы ответ был привязан к правильному документу, правильной странице и минимальному набору фактов, на который можно опереться.
01 Подготовка корпуса
Сохранить идентичность документа
- парсинг PDF с OCR как запасным слоем
- нарезка по структуре, а не по голому размеру
- сохранение заголовка, типа документа, пути по разделам и номера страницы
02 Поиск
Сузить до правильного семейства документов
- гибридный поиск: векторный плюс лексический
- агрегация по документу или семейству закона
- reranking внутри шорт-листа, а не по всему корпусу
03 Ответ
Отвечать в рамках строгого контракта
- маршрутизация по типу вопроса
- разные режимы для ответов да/нет, дат, имён и сравнений
- в генерацию уходит только минимальный защищаемый набор источников
04 Привязка и проверки
Не отпускать ответ без следа
- привязка к конкретным страницам
- телеметрия, связанная с финальным ответом
- раздельные проверки для качества ответа, привязки к источнику и задержки
Единственное, что реально сработало
Я перепробовал все приёмы из литературы по retrieval на юридическом корпусе. Тюнинг BM25, RAG Fusion, HyDE, step-back prompting. Ни один не сдвинул стрелку. Единственное изменение, которое стабильно улучшало качество ответов, состояло в том, чтобы ускорить систему. Prompt caching снизил задержку с 5 086 мс до 1 931 мс, и точность выросла вместе с ним.
Это был не единственный сюрприз. Мой внутренний eval показывал около 0.92. Платформенный held-out eval вернул 0.48. Разрыв в 48 процентных пунктов только из-за сдвига распределения. Вопросы, на которых я тестировал, не представляли вопросы, с которыми система столкнётся в реальности.
Эти две находки определили всё, что идёт дальше. Инженерия поиска важнее изощрённости поиска. Если вы не тестируете на реалистичном распределении вопросов, вы не тестируете вовсе.
Главная поломка — не тот источник, а не слабая проза
Когда говорят, что юридическая система «галлюцинировала», обычно смешивают сразу четыре разных сбоя:
- поднят не тот документ
- поднят правильный документ, но не тот пункт
- найдена одна опорная страница, но потеряна вторая, от которой зависел ответ
- сгенерирован абзац свободного текста там, где задача требовала строгого формата
Списывать галлюцинации на промпт-инженерию значит промахнуться мимо большей части реальных сбоев.
В юридической работе уверенный ответ, построенный на неправильном семействе законов, хуже, чем явный отказ. Система выглядит точной ровно до момента, когда кто-то проверяет источник и обнаруживает, что пункт взят из соседнего акта, старой консолидированной версии или похожего уведомления, которое к вопросу вообще не относится.
Ключевой архитектурный вопрос не «Насколько умна модель?», а:
Может ли система сохранить идентичность управляющего источника от загрузки данных до финального ответа?
Юридический корпус — враждебный вход
Юридические корпуса длинные, повторяющиеся, структурно похожие и полны высокорисковых почти-совпадений. Эта комбинация ломает удивительное количество внешне сильных систем поиска.
Исследования 2025 года дали этому режиму отказа полезное название: Document-Level Retrieval Mismatch. Маркус Ройтер с коллегами показали, что юридические поисковики часто подбирают фрагменты из неправильного исходного документа, потому что шаблонный и формальный язык крайне однообразен по всему корпусу. Их решение, Summary-Augmented Chunking, привлекательно именно своей простотой: вернуть идентичность документа в каждый фрагмент перед поиском, а не притворяться, что локального текста фрагмента достаточно.
Из этого следуют три проектных решения.
Сохраняйте идентичность страницы и раздела с первого дня
Нужны как минимум:
- канонический идентификатор документа
- заголовок документа
- тип документа
- путь по разделам или цепочка заголовков
- номер страницы
- исходный текст фрагмента
Если идентичность страницы теряется в самом начале, потом привязку к источнику приходится восстанавливать эвристиками и сожалениями.
OCR должен быть запасным слоем, а не мыслью задним числом
Юридические PDF не всегда рождаются чистыми. Некоторые сканы, некоторые содержат подписи или уведомления в виде картинок, а в некоторых критичный текст спрятан внутри низкокачественных изображений страниц. OCR не должен сидеть в онлайн-пути, но обязан быть частью подготовки корпуса.
Нарезка должна учитывать структуру
Неправильный вариант по умолчанию до сих пор «фиксированный размер фрагмента и надежда». В юридических материалах структура важна:
- законы хотят нарезку по статьям и разделам
- судебные решения хотят границы мотивировки/фактов/резолютивной части
- договоры хотят границы пунктов и определений
Плоская нарезка работает ровно до момента, когда она отрезает определение от обязательства, которое это определение ограничивает.
Поиск должен находить правильную страницу, не самый большой контекст
Самый рабочий стек поиска для юридического QA по-прежнему гибридный:
- векторный поиск для смысловой близости
- лексический поиск для точных формулировок, номеров законов, статей и имён сторон
Но гибридного поиска самого по себе недостаточно. Более важное проектное решение в том, что происходит после первой стадии поиска.
Я предпочитаю такую последовательность:
- поднять достаточно широко, чтобы не потерять полноту
- агрегировать кандидатов по документу или семейству закона
- применить слой проверки согласованности по документам
- переранжировать внутри шорт-листа
- отдать генератору только минимальный защищаемый набор источников
Именно здесь многие системы ломаются тихо. Правильная страница есть где-то в верхнем наборе, а потом пропадает на этапе формирования шорт-листа, потому что похожий соседний документ кажется семантически достаточно близким.
Этот сосед часто и есть настоящий враг юридического поиска:
- консолидированная версия того же закона
- закон о поправках
- уведомление о вступлении в силу
- связанное приложение
- соседний нормативный акт с почти идентичными формулировками
Система не должна считать их взаимозаменяемыми. Она должна понимать их как семейство документов и сохранять правильного члена семьи в зависимости от вопроса.
Например:
- вопрос о дате вступления в силу может потребовать тело закона и уведомление о вступлении в силу
- вопрос об администрировании может потребовать каноническую страницу закона, а не консолидированный суррогат
- вопрос-сравнение может потребовать по одной странице из каждого названного закона, а не две самые семантически похожие страницы корпуса
Вот почему расширение окна контекста не решает проблему. Оно одновременно повышает полноту и шум. В юридическом QA цель не максимальный контекст, а выживание правильного семейства источников.
Не заставляйте все вопросы отвечаться одним абзацем
Один из самых простых способов улучшить юридическую QA-систему. Перестать делать вид, что каждый вопрос хочет абзац.
Некоторые юридические вопросы действительно требуют свободного текста. Многие нет.
Между следующими вопросами огромная разница:
- «Какова дата?»
- «Кто истцы?»
- «Делает ли статья X это ограничение действующим?»
- «Сравните, как два закона трактуют одно и то же понятие.»
Их не следует пропускать через один и тот же контракт ответа.
Один общий абзац
Каждый вопрос проталкивается через один и тот же режим свободного текста.
- проверка становится размытой
- соответствие формату дрейфует
- модель генерирует ненужную прозу
Строгие форматы ответа
Формат ответа следует за формой вопроса.
- ответы да/нет остаются да/нет
- даты остаются датами
- аналитические сравнения остаются короткими и ограниченными
Вот паттерн, которому я доверяю больше всего:
| Тип вопроса | Более подходящий контракт ответа |
|---|---|
| boolean | JSON true / false или явный отказ |
| number | JSON number |
| date | дата в формате ISO |
| name | точная строка |
| names | список строк |
| analytical comparison | короткий свободный текст с явными границами опоры |
Это важно по двум причинам.
Во-первых, типизированные ответы проще проверять.
Во-вторых, они сокращают количество способов, которыми модель может проявить «креативность», когда задача на самом деле её не требовала.
Тот же принцип касается того, что выходит из системы. Многим системам нужны два разных слоя ответа:
- внутреннее представление с рассуждениями или богатой доказательной базой
- финальный контракт для пользователя или API
Это разделение здоровое. Ошибка в том, чтобы их склеить.
Привязка к источнику: краткая и полная
Система цитирования может сломаться двумя противоположными способами:
- сослаться на слишком многое
- сослаться на слишком малое
Большинство команд замечают первый сбой, потому что он выглядит шумно. В юридических системах второй сбой часто опаснее.
Если ответ зависит от двух фактов, которые живут на разных страницах, нужны обе страницы. Не одна «лучшая» страница, выбранная ради аккуратности.
Это звучит тривиально. Это не так.
На практике элемент юридического ответа может содержать несколько слотов опоры:
- название документа
- дата принятия
- дата вступления в силу
- закон, в который вносятся изменения
- пункт об администрировании
- общий элемент сравнения
Если эти слоты привязаны к разным страницам, обрезка привязки не имеет права схлопнуть их в одну страницу только потому, что ответ всё ещё звучит правдоподобно.
Вот почему я предпочитаю привязку на уровне элементов и слотов ответа, а не «ощущение цитат на уровне предложений». Это также совпадает с общим диагностическим направлением в фреймворках вроде RAGChecker: если вы хотите понять систему с поддержкой retrieval, нужно оценивать, где опора корректна, где отсутствует, где привязана к неправильному источнику.
Правило простое:
Минимальная привязка хороша только если она остаётся полной.
Это также означает, что ответы, пересекающие границу страниц, требуют особой обработки. Если ответ начинается на одной странице и продолжается на следующей, обе страницы должны войти в финальный набор опоры. Многие юридические системы упускают это, потому что оптимизируют аккуратность в рамках одной страницы вместо непрерывности доказательной базы.
Стриминг и телеметрия — часть продукта
Юридические QA-системы часто строят так, как будто задержка и телеметрия это вопросы наблюдаемости. На деле это поведение продукта.
Если первый токен приходит поздно, система кажется неуверенной.
Если телеметрия неполная, невозможно объяснить сбои.
Если путь стриминга и путь финального ответа расходятся, вы создаёте теневую систему, которая ведёт себя по-разному на публике и в трассировках.
Продакшен-паттерн, которому я доверяю больше всего:
- стримить как можно раньше, насколько контракт ответа безопасно позволяет
- финальный ответ остаётся каноническим
- отправлять тайминги по стадиям, счётчики токенов, идентификатор провайдера, поднятые и использованные источники
- никогда не буферизовать весь ответ ради ощущения чистоты
Это не значит «стримить бездумно». Это значит, что стриминг должен проектироваться вместе с:
- маршрутизацией по типу ответа
- привязкой к источникам
- границами верификации
- отчётами об ошибках
Система должна уметь ответить на очень скучный, но очень важный вопрос:
Почему мы решили, что этому ответу можно покинуть систему?
Проверки разделяют качество ответа и качество привязки
Многие команды до сих пор используют одну общую оценку и называют это «проверками».
Этого недостаточно.
Для юридических QA-систем я хочу отдельные сигналы по:
- корректность ответа
- полнота привязки
- доля ответов с неправильным документом
- доля ответов с неправильным семейством
- доля «осиротевших» страниц
- соответствие формату
- задержка
И ещё одно различие, которое становится критическим по мере взросления системы:
- доверенный бенчмарк-уровень
- мониторинговый уровень
Это звучит бюрократически, пока вы не столкнётесь с неправильно размеченными эталонными страницами, унаследованными точками регрессии или кейсами, которые полезны как мониторы, но не годятся как честные жёсткие пороги.
Практический урок прост:
- используйте маленький, проверенный, доверенный уровень для жёстких решений о приёмке
- держите широкий, шумный уровень для мониторинга дрейфа и сортировки
Я также категорически предпочитаю проверки, которые могут диагностировать, где система ломается: выбор семейства источников, выживание страницы, форматирование ответа или полнота опоры. Оценка от LLM-судьи это полезный сигнал. Но не устойчивость.
Ваш фреймворк проверок ровно настолько хорош, насколько хорошо ваше тестовое распределение. Я усвоил это на собственном опыте.
Если бы я отправлял это завтра
Если бы я строил юридическую QA-систему сегодня, я бы по умолчанию взял примерно такой набор:
| Слой | Практический вариант по умолчанию |
|---|---|
| парсинг | надёжная PDF-экстракция с OCR как запасным слоем |
| нарезка | фрагменты с учётом структуры, с сохранением пути по разделам |
| идентичность при поиске | заголовок документа, семейство документа, аннотация документа, номер страницы |
| поиск | гибридный: векторный + лексический |
| reranking | шорт-лист по семейству документов, затем reranking по релевантности пункта |
| ответ | строгие форматы для точных вопросов, лаконичный свободный текст для аналитических |
| привязка | использованные страницы, а не видимые |
| форматирование выхода | отделение формата отправки от внутреннего представления рассуждений |
| проверки | доверенный жёсткий набор + широкий мониторинг дрейфа |
Обратите внимание, чего нет в этом списке:
- гигантские пирамиды промптов
- избыточные мультиагентные циклы (есть случаи, где координированные мультиагентные системы оправданы, об этом ниже)
- забивание контекста всем подряд
- слепое доверие одной фронтирной модели
Чем зрелее эти системы становятся, тем менее магически они выглядят.
Если бы мне пришлось строить сильную юридическую QA-систему с нуля ещё раз, я бы делал это в таком порядке:
- загрузка данных с идентичностью страниц и OCR как запасным слоем
- структурная нарезка с сохранением идентичности документа
- гибридный поиск с проверками согласованности по семействам документов
- строгие форматы ответа для точных типов вопросов
- привязка на уровне страниц с полным покрытием опоры
- маленький доверенный бенчмарк до начала широкой оптимизации
- стриминг плюс телеметрия, объясняющая каждую стадию
Порядок важен.
Большинство слабых систем делают наоборот:
- выбирают модель
- пишут промпты
- надеются, что поиск в порядке
- добавляют проверки потом
Этот порядок обычно порождает системы, которые работают на демо, но быстро деградируют на реальных запросах.
Для юридического ИИ стандарт должен быть скучно высоким.
Не:
- «Модель звучит умно.»
А:
- «Система нашла правильный источник.»
- «Она сохранила правильную страницу.»
- «Она использовала минимальный набор опоры, который всё ещё покрывает всё утверждение.»
- «Она может объяснить, почему ответила именно так.»
- «Она знает, когда отказаться.»
Акцент смещается с размера контекста и красноречия модели на качество доказательств и контракты ответа.
Принципы выше это ограничения на этапе проектирования. Следующий раздел о том, что происходит, когда вы применяете их под реальным дедлайном, с реальной формулой оценки и реальной командой, часть которой оказалась ИИ-агентами. Конкретика из одного конкурсного спринта. Режимы отказа и сюрпризы нет.
Что произошло на самом деле: спринт длиной в 13 дней
Я вошёл в Agentic AI RAG Challenge, чтобы проверить эти принципы на реальном бенчмарке. 300+ юридических PDF из DIFC, 900 вопросов, мультипликативная формула оценки и 13 дней. Система строилась во время активного конфликта в Израиле, что накладывало непредсказуемые ограничения на время разработки и определило инженерную философию: устойчивость и эффективность важнее сложности. Дальше сжатый рассказ о том, что сработало, что нет и что удивило.
Формула
Каждое стратегическое решение восходит к этому уравнению:
Total = (0.7 * Det + 0.3 * Asst) * G * T * F| Компонент | Что измеряет | Рычаг (на 1 п.п.) |
|---|---|---|
| G | привязка к страницам (F-beta, beta=2.5) | +0.93 п.п. Total |
| Det | точное совпадение для типизированных ответов | +0.72 п.п. Total |
| Asst | оценка LLM-судьи для свободного текста | +0.31 п.п. Total |
| T | соответствие телеметрии схеме | мультипликативный порог |
| F | коэффициент TTFT (0.85--1.05) | мультипликативный порог |
Один процентный пункт привязки стоит в 3 раза дороже одного процентного пункта качества свободного текста. Вывод был немедленным: защитить множители любой ценой, а потом повышать качество ответов внутри этого защитного конверта.
Акт 1: Кладбище абляций
Большая часть того, что вы прочитаете в учебниках по RAG, в юридическом домене активно вредит. За 13 дней я провёл 16 серьёзных экспериментов. Доля отказов для экспериментов с расширением поиска: 100 процентов. Вот четыре, которые научили больше всего.
RAG Fusion стал худшей регрессией: -7.8 п.п. привязки, +246 мс. Техника переписывает запрос в три-пять перефразированных вариантов и объединяет результаты. Запрос типа «What is the Date of Issue of CFI 022/2025?» превращался в «provisions related to CFI 022/2025» и «date associated with case filing CFI 022.» Объединение продвигало косвенно связанные страницы выше правильных. Разнообразие запросов добавляет шум, когда юридические запросы и так оптимально точные.
HyDE генерировал гипотетический документ-ответ и использовал его embedding для поиска похожих реальных документов. LLM порождал гипотетические ответы со ссылками на правдоподобные, но несуществующие нормы, например «Article 47(2) of the Regulatory Law». Эти ссылки попадали в векторные окрестности, далёкие от реальных правоприменительных решений. Результат: -0.65 п.п. привязки, +560 мс. Галлюцинированный юридический контекст отравляет поиск.
Step-back rewriting абстрагировал вопросы до более общих форм. «What does Article 16(1)(c) of the Employment Law say about payroll deductions?» превращался в «What are the payroll deduction rules in DIFC?» Это теряло точную ссылку на статью, которая якорит поиск, и добавляло 1 542 мс задержки. Абстракция теряет точность, которую требуют юридические запросы.
BM25 hybrid retrieval добавлял разреженный лексический поиск рядом с плотными embeddings. Ноль прироста привязки, +260 мс. Доменно-настроенный плотный энкодер и так захватывал лексический сигнал. Лексический сигнал не аддитивен с доменно-настроенным плотным энкодером.
Полная таблица абляций со всеми 16 экспериментами в техническом приложении ниже.
Паттерн устойчив: техники, созданные для неоднозначных запросов общего домена, не переносятся на точный юридический текст.
Кульминация Акта 1
Самый большой единичный прирост итогового балла дал prompt caching. Я перестроил системные промпты так, чтобы превысить порог кэширования OpenAI в 1 024 токена (по состоянию на март 2026). TTFT свободного текста упал с 5 086 мс до 1 931 мс. F-коэффициент подпрыгнул с 0.973 до 1.006: +3.0 п.п. итогового балла от изменения, которое не тронуло ни строчки логики ответов и ни строчки кода поиска. Не лучший поиск. Более быстрая инфраструктура.
В сочетании с ранним эмитом токена (перенос mark_first_token() до завершения grounding sidecar) финальный F-коэффициент достиг 1.032. Это +3.19% итогового балла, заработанные чисто за счёт того, когда я остановил таймер.
Я был уверен. Внутренние проверки показывали почти идеальные результаты.
Акт 2: Разрыв
Мой внутренний proxy привязки показывал G=0.9956. Телеметрия идеальная, T=1.000. Скорость оптимизирована, F=1.032. Я оценивал итоговый балл около 0.92.
Платформа вернула 0.48.
Разрыв не был багом. Это был сдвиг распределения. Warmup-набор содержал 100 вопросов:
| Тип | Warmup | Private | Множитель |
|---|---|---|---|
| boolean | 32 | 193 | 6.0x |
| free_text | 30 | 270 | 9.0x |
| date | 1 | 93 | 93.0x |
| name | 15 | 95 | 6.3x |
| names | 5 | 90 | 18.0x |
| number | 17 | 159 | 9.4x |
Вопросы по датам выросли с 1 до 93. По спискам имён с 5 до 90. И 279 вопросов ссылались на типы документов, которых в warmup-наборе было ноль. Мой фреймворк проверок был настроен на распределение, которое не представляло реальный тест.
Proxy привязки тоже подвёл. Он измерял, существуют ли указанные страницы в корпусе, а не являются ли они правильными страницами. Внутренний 0.9956 маскировал платформенный 0.589. Proxy отвечал на неправильный вопрос.
Полный анализ сдвига распределения в техническом приложении.
Мультиагентный спринт
Процесс мультиагентной разработки (12 агентов Claude Code, работающих параллельно на одном ноутбуке, закрывших 737 тикетов за 47 часов) в итоге вырос в Bernstein, оркестратор с открытым исходным кодом. Сам конкурсный пайплайн выложен как Shafi, в честь аш-Шафии (767–820), основателя методологии исламского права. Уместная отсылка для системы, рассуждающей о праве.
Что выдержало
Техники, которые пережили сдвиг распределения, не зависели от обучающего распределения. Prompt caching давал то же улучшение TTFT независимо от типа вопроса. Поиск по метаданным работал для любого вопроса со ссылкой на номер дела, появлялся тот в warmup-наборе или нет. Маршрутизация по типам обобщалась, потому что ориентировалась на структуру вопроса, а не на его содержание.
Техники, которые сломались, были настроены на warmup-распределение: пороги привязки, откалиброванные на 100 вопросах, паттерны ответов, переобученные на горстке типов документов, proxy-метрика, которую ни разу не проверили против эталонных меток. Инфраструктура победила изощрённость. Доменная инженерия победила generic RAG.
Полное исследование абляций, графики прогресса баллов, анализ сдвига распределения и разбивка по типам в research deep-dive во второй вкладке выше.
Что бы я сделал иначе
Я бы вложился в анализ распределения проверок с первого дня. В warmup-наборе был 1 вопрос по датам. В приватном наборе 93. Это 93-кратный множитель для типа, который я почти не тестировал. Сравнение распределения типов в warmup с разумными ожиданиями для юридического корпуса (сколько правоприменительных решений, сколько нормативных актов, сколько практических указаний) сразу бы выявило дыру в покрытии. Я не посмотрел на распределение до прихода финального балла. Этот анализ занимает 30 минут и изменил бы всю стратегию тестирования.
Я бы полностью пропустил эксперименты с поиском. С доменно-настроенным плотным энкодером, уже дающим 90%+ полноту на уровне страниц, поиск был близок к потолку. Каждый час, потраченный на BM25, RAG Fusion, HyDE и step-back rewriting, был потерян. Шесть экспериментов с расширением поиска, все отклонены, все сожравшие время, которого не было. Отдача лежала в качестве ответов и калибровке привязки, а не в поиске. Я бы перенаправил усилия на качество свободного текста, которое оказалось самым большим разрывом.
Я бы строил мультиагентную систему с самого начала. Измеренный прирост пропускной способности в 1.78× от 12 параллельных агентов был реальным. 737 тикетов за 47 часов. Но разворачивание инфраструктуры координации с нуля на 10-й день, под давлением дедлайна, означало, что первые 8 часов ушли на протоколы пробуждения, доски задач и файлы директив, а не на саму задачу. Запуск мультиагентной системы на 3-й день, с уже размеченными границами модулей, размазал бы накладные расходы координации по всему спринту и уменьшил бы конфликты слияния, которые мучили последние 48 часов.
Я бы измерял TTFT с первого дня. F-коэффициент был самым дешёвым множителем в формуле. Один только prompt caching дал +3.0 п.п. итогового балла. Ранний эмит токена добавил ещё +2.0 п.п. Вместе это больше, чем любое улучшение поиска или качества ответов, которое я нашёл. Но я занялся TTFT только в submission v6 на 3-й день. Первые два дня submissions шли с F ниже 1.0, оставляя бесплатные очки на столе. В мультипликативной формуле самый дешёвый множитель всегда первый, который стоит оптимизировать.
Что ещё почитать
Источники и ссылки
- Towards Reliable Retrieval in RAG Systems for Large Legal Datasets
- LegalBench-RAG: A Benchmark for Retrieval-Augmented Generation in the Legal Domain
- RAGChecker: A Fine-grained Framework for Diagnosing Retrieval-Augmented Generation
- OpenAI: Evaluation best practices
- Anthropic: Demystifying evals for AI agents