Модуль l.2 · Урок 2
RAG по юридическим документам: clause-aware chunking
Чему вы научитесь
- Объяснять, что такое RAG и зачем он нужен для работы с юридическими документами
- Понимать, почему наивный chunking по токенам или по абзацам ломает смысл пункта договора
- Применять clause-aware chunking: нарезать договор по статьям и пунктам, а не по размеру
- Понимать устройство эмбеддингов и векторного поиска на уровне, достаточном для настройки и отладки
- Учитывать лицензионные и регуляторные ограничения при индексировании правовых баз в РФ
Что такое RAG простыми словами
RAG (Retrieval-Augmented Generation, поиск с дополнением генерации) — архитектурный паттерн, при котором языковая модель не «помнит» весь ваш документ наизусть, а получает только те фрагменты, которые нужны для ответа на конкретный вопрос.
Представьте опытного юриста, которому дали договор на 120 страниц. Он не перечитывает его целиком каждый раз, когда вы задаёте вопрос. Он открывает нужный раздел, читает контекст и отвечает. RAG работает так же: сначала находит релевантные пункты, затем передаёт их модели вместе с вопросом.
Для юридических документов это особенно ценно по двум причинам. Во-первых, длинные договоры выходят за пределы контекстного окна многих моделей или делают работу с ними дорогой. Во-вторых, точность ответа резко падает, когда модель одновременно держит в контексте 50+ страниц несвязанного текста.
flowchart LR
subgraph Индексирование
A[Договор PDF] --> B[Clause-aware chunking]
B --> C[Эмбеддинг каждого пункта]
C --> D[(Векторная БД ChromaDB)]
end
subgraph Ответ на вопрос
E[Вопрос пользователя] --> F[Эмбеддинг вопроса]
F --> G[Поиск ближайших пунктов]
D --> G
G --> H[Топ-K пунктов в промпт]
H --> I[Языковая модель]
I --> J[Ответ с ссылками на пункты]
endПроблема наивного chunking на договоре
Chunking — это разбивка текста на фрагменты (chunks) перед индексированием. На первый взгляд задача тривиальна: разрежьте текст на куски по 500 токенов с перекрытием 50 токенов. Именно так работают большинство библиотек по умолчанию.
Для художественного текста или статей этот подход приемлем. Для юридического договора он создаёт серьёзные проблемы.
Пример. Пункт договора об ответственности выглядит так:
8.3. Совокупная ответственность Исполнителя по настоящему Договору, включая ответственность за нарушение конфиденциальности, не может превышать сумму вознаграждения, фактически выплаченного Заказчиком за три (3) месяца, предшествующих событию, повлёкшему ответственность, за исключением случаев умысла или грубой неосторожности.
Если граница чанка проходит после «…фактически выплаченного Заказчиком за три (3) месяца,», то получаются два фрагмента:
- Фрагмент A: «8.3. Совокупная ответственность… фактически выплаченного Заказчиком за три (3) месяца,»
- Фрагмент B: «предшествующих событию… за исключением случаев умысла или грубой неосторожности.»
Фрагмент A без продолжения выглядит как безусловный кэп. Фрагмент B без начала теряет субъекта и смысл. Векторный поиск вернёт один из них — и модель даст неверный ответ о границе ответственности.
| Критерий | Наивный chunking (по токенам) | Clause-aware chunking (по пунктам) |
|---|---|---|
| Единица разбивки | Фиксированное число токенов | Нумерованный пункт, статья |
| Сохранение смысла | Часто нарушается на границах | Сохраняется: пункт всегда целый |
| Размер чанка | Предсказуемый | Варьируется (от 20 до 500+ слов) |
| Ссылки в ответе | Нельзя точно указать номер пункта | Можно указать «п. 8.3» |
| Сложность реализации | Минимальная (библиотека по умолчанию) | Требует парсера нумерации |
Как работает clause-aware chunking
Идея проста: нарезать текст договора по его собственной структуре — по нумерованным пунктам, статьям и подпунктам, — а не по произвольным границам.
Извлечение текста. pdfplumber или аналог отдаёт текст постранично. На этом этапе важно сохранить заголовки разделов и нумерацию пунктов.
Обнаружение границ пунктов. Регулярное выражение или небольшой классификатор ищет паттерны нумерации: «1.», «1.1.», «Статья 8», «п. 3.2.1» и их вариации. В российских договорах встречаются форматы «Статья N», «N.» и «N.N.N.» — важно охватить все.
Разбивка по найденным границам. Каждый пункт становится отдельным чанком. Метаданные чанка: номер пункта, заголовок раздела, номер страницы.
Обработка длинных пунктов. Если один пункт превышает разумный предел (например, 800 токенов), его делят на подпункты или применяют вторичный chunking, сохраняя номер пункта в метаданных каждой части.
Проверка полноты. Сравните число обнаруженных пунктов с оглавлением договора — это быстро покажет, не потерялись ли разделы при парсинге.
Пример псевдокода для простого clause-aware парсера:
import re
CLAUSE_PATTERN = re.compile(
r'^(\d+(?:\.\d+)*\.?)\s+', # 1. / 1.1. / 1.1.1
re.MULTILINE
)
def split_into_clauses(text: str) -> list[dict]:
"""
Разбивает текст договора на пункты по нумерации.
Возвращает список словарей: number, text, char_start.
"""
matches = list(CLAUSE_PATTERN.finditer(text))
clauses = []
for i, match in enumerate(matches):
start = match.start()
end = matches[i + 1].start() if i + 1 < len(matches) else len(text)
clause_text = text[start:end].strip()
clauses.append({
"number": match.group(1),
"text": clause_text,
"char_start": start,
})
return clauses
В проекте zahirnik/legal-agent эту роль выполняет модуль парсинга, совмещённый с вызовом pdfplumber: сначала текст, затем разбивка по структуре документа. Для сложных форматов (вложенные нумерованные списки, таблицы внутри пунктов) может потребоваться более развитый парсер или предварительная нормализация текста.
Эмбеддинги: как договор превращается в числа
После разбивки на пункты каждый чанк передаётся в модель эмбеддингов. Она преобразует текст в вектор — список из нескольких сотен или тысяч чисел, которые отражают смысл текста в многомерном пространстве.
Ключевое свойство: пункты с похожим смыслом оказываются «рядом» в этом пространстве, даже если они сформулированы разными словами. Вопрос «какой кэп ответственности» и пункт «совокупная ответственность не превышает…» окажутся близко по вектору, хотя не совпадают по словам.
Для юридического русскоязычного текста важно выбирать модель эмбеддингов, обученную на профессиональном юридическом корпусе или хотя бы на русскоязычных текстах. Популярные варианты:
multilingual-e5-large— многоязычная модель с хорошим качеством на русскомtext-embedding-3-large(OpenAI) — сильная универсальная модель- специализированные модели под юридический домен (если доступны)
Точность поиска зависит от качества эмбеддингов не меньше, чем от качества chunking.
Векторный поиск: как работает ChromaDB
ChromaDB — векторная база данных, которая хранит эмбеддинги чанков и умеет быстро находить ближайшие по смыслу фрагменты к любому запросу.
Когда пользователь задаёт вопрос, система:
- Превращает вопрос в вектор той же моделью эмбеддингов
- Ищет в базе K ближайших векторов (по косинусному расстоянию)
- Возвращает соответствующие пункты договора
Эти пункты вставляются в промпт языковой модели вместе с вопросом. Модель отвечает, опираясь на конкретный текст договора.
import chromadb
client = chromadb.Client()
collection = client.get_or_create_collection("contract_clauses")
# Индексирование (выполняется один раз при загрузке договора)
collection.add(
documents=[c["text"] for c in clauses],
metadatas=[{"number": c["number"]} for c in clauses],
ids=[f"clause_{c['number']}" for c in clauses],
)
# Поиск по вопросу пользователя
results = collection.query(
query_texts=["кэп ответственности исполнителя"],
n_results=3,
)
Результат — три наиболее релевантных пункта с их номерами. Эти пункты передаются модели, и та формулирует ответ с точными ссылками на текст договора.
Лицензионные и регуляторные ограничения в РФ
Для собственных документов (договоры, внутренние регламенты, накопленная практика) RAG-индексирование правомерно при соблюдении двух условий:
- документы не содержат персональные данные физических лиц (152-ФЗ) — или обработка соответствует требованиям закона
- сведения, составляющие адвокатскую или коммерческую тайну, хранятся в локальном, а не в облачном публичном сервисе
Для практики с публичными моделями (arckep.ru, API Claude, OpenAI) используйте только обезличенные или синтетические учебные договоры. Реальные договоры с данными клиентов или сотрудников в публичные сервисы не передавайте.