Как победить галлюцинации LLM в юриспруденции: граундинг и пайплайны | AiManual
AiManual Logo Ai / Manual.
06 Июл 2026 Гайд

Как бороться с галлюцинациями локальных LLM в юридических документах: техники граундинга и пайплайны

Полное руководство по борьбе с галлюцинациями локальных LLM в юридических документах: RAG, chain-of-thought, верификация цитат, самовосстанавливающиеся пайплайн

Юрист, твой локальный LLM врёт — и это нормально (пока не нормально)

Представь: ты загружаешь 500-страничный договор в локальную модель, просишь найти скрытые риски, а она выдаёт ссылку на несуществующий пункт 12.3. Или, ещё хлеще, уверенно цитирует статью 228 УК РФ, когда речь идёт о налогах. Галлюцинации — раковая опухоль любого LLM, но в юриспруденции они стоят не просто денег, а свободы клиента. Особенно когда работаешь локально, без API-шлюзов и фильтров.

Почему локальные модели галлюцинируют чаще? Две причины: маленький контекст (8-16K токенов против 128K у облачных) и отсутствие встроенного поиска по источнику. Модели вроде Mistral 7B или Llama 3.2 (8B) просто не знают твои 500 страниц — они выдумывают, чтобы не молчать. Но есть техники, которые превращают этот хаос в рабочий инструмент. Я собрал четыре слоя защиты: граундинг, верификация, пайплайны и самовосстановление. Без магии, только код и логика.

Важно: Если ты ещё не прочитал статью про самовосстанавливающийся RAG — сделай это сейчас. Дальше будет жёстче.

Слой 1: Граундинг через RAG — база, без которой не обойтись

Просто скормить документ в промпт — путь к катастрофе. Модель либо выйдет за лимит токенов, либо выдумает половину. Retrieval-Augmented Generation (RAG) решает проблему: разбиваешь документ на чанки, индексируешь векторной базой (Chroma, FAISS, Qdrant), а модель отвечает только по найденным фрагментам.

from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter

# Загрузка и чанкование
with open('contract.pdf', 'rb') as f:
    text = PyPDF2.PdfReader(f).pages
    full_text = ' '.join([page.extract_text() for page in text])

splitter = RecursiveCharacterTextSplitter(
    chunk_size=512, chunk_overlap=64, separators=['\n\n', '\n', '.']
)
chunks = splitter.split_text(full_text)

# Эмбеддинги и хранилище
embeddings = HuggingFaceEmbeddings(model_name='all-MiniLM-L6-v2')
db = Chroma.from_texts(chunks, embeddings, persist_directory='./chroma_db')
retriever = db.as_retriever(search_kwargs={'k': 5})

Главный нюанс: юридические документы полны перекрёстных ссылок («согласно п. 3.2.1»). Если чанк разорвал логику — модели не хватит контекста. Решение — слияние чанков на основе ссылок. Парсишь все ссылки на пункты внутри документа и объединяешь связанные блоки в один супер-чанк. Подробнее о таком семантическом пайплайне я писал здесь.

Слой 2: Chain-of-Thought с автоверификацией — заставь модель думать, а не гадать

CoT — старо, как мир, но в юриспруденции даёт сбой: модель красиво рассуждает, а потом выдаёт липу. Добавляем принудительную верификацию каждого шага. Промпт выглядит так:

{
  "system": "Ты — юридический аналитик. Отвечай строго по контексту. 
  Каждый факт должен иметь цитату из документа. Если не уверен — напиши 'Нет данных'.",
  "messages": [
    {"role": "user", 
     "content": "Контекст: {chunks}\nВопрос: Есть ли риск штрафа за просрочку поставки?\n
     Пошагово: 1) Найди в контексте пункты о сроках. 2) Найди пункты о штрафах. 
     3) Укажи точные номера пунктов. 4) Сделай вывод."}
  ]
}

Но даже CoT не спасёт, если модель «уверена» в неверном пункте. Тут нужна метрика атрибуции. Мы вычисляем cosine similarity между ответом и каждым чанком. Если ближайший чанк имеет схожесть < 0.6 — ответ считается необоснованным. Этот подход разобран в статье «Как ловить уверенные галлюцинации».

Слой 3: Верификация цитат с помощью Tool Calling — когда модель сама проверяет себя

Tool Calling (function calling) позволяет модели вызывать внешние инструменты. В нашем случае — поиск по документу заново. Схема: модель формирует ответ, затем передаёт его верификатору, который извлекает все цитаты (номера пунктов) и проверяет их в исходном PDF через регулярные выражения.

import re
from pdfminer.high_level import extract_text

class CitationVerifier:
    def __init__(self, pdf_path):
        self.text = extract_text(pdf_path)
    
    def verify(self, cited_sections: list[str]) -> dict:
        results = {}
        for section in cited_sections:
            pattern = rf'{re.escape(section)}[\s\S]*?(?=\n\d+\.|\Z)'
            match = re.search(pattern, self.text, re.IGNORECASE)
            results[section] = bool(match)
        return results

Затем ответ модели отправляется обратно в LLM с пометкой: «Пункт 12.3 не найден в документе. Скорректируй ответ». Это радикально снижает галлюцинации, но жрёт время. На локальной машине (например, RTX 3090) итерация занимает 3-5 секунд. Терпимо для анализа одного договора, но не для пачки из 50.

Ошибка: Не используй один и тот же prompt для каждого шага итерации. У модели может зациклиться верификация: «Пункта нет — перепиши — опять нет — перепиши». Ставь лимит ретраев (3), иначе получишь бесконечный цикл.

Слой 4: Самовосстанавливающийся пайплайн — если всё же ошиблась, пусть чинит сама

В идеале пайплайн должен детектить галлюцинации и перезапускать генерацию с исправленным контекстом. Я использую архитектуру Retry-RAG: после первого ответа запускается эвалюатор (маленькая LLM типа Phi-3-mini), который оценивает «уверенность» и «наличие цитат». Если оценка ниже порога — пайплайн расширяет контекст (k=10 вместо 3) и делает второй проход.

class SelfHealingPipeline:
    def __init__(self, retriever, llm, verifier):
        self.retriever = retriever
        self.llm = llm
        self.verifier = verifier
    
    def generate(self, question, max_retries=3):
        for attempt in range(max_retries):
            chunks = self.retriever.get_relevant_documents(
                question, k=5+attempt*2
            )
            response = self.llm(f"Context: {chunks}\nQ: {question}")
            if self._is_confident(response):
                citations = extract_citations(response)
                if self.verifier.verify(citations):
                    return response
            # иначе увеличиваем контекст и повторяем
        return "[ERROR] Не удалось верифицировать ответ."
    
    def _is_confident(self, resp):
        return 'нет данных' not in resp.lower()

Этот подход детально описан в статье про самовосстанавливающийся RAG. Советую почитать, там ещё и про кэширование неудачных запросов.

Слой 5 (бонус): Fine-tune на юридических кейсах — когда техники не спасают

Если RAG и верификация всё равно пропускают галлюцинации — пора точить модель под свою предметку. Локальный fine-tune на 500 размеченных юридических документах уменьшает ошибки на 30-40%. Используй LoRA с QLoRA — влезет на 16GB VRAM. Пример:

# Установка unsloth
pip install unsloth

# LoRA адаптер
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="unsloth/llama-3.2-8b-bnb-4bit",
    max_seq_length=8192,
    load_in_4bit=True,
)
model = FastLanguageModel.get_peft_model(
    model,
    r=16,
    target_modules=["q_proj", "v_proj"],
    lora_alpha=16,
    lora_dropout=0,
    bias="none",
)
# Обучать на парах {документ+вопрос -> верифицированный ответ}

Но помни: fine-tune не заменяет RAG, а дополняет. Только в связке они дают результат. Если хочешь пример полного пайплайна — глянь проект автоматизации анализа договорных рисков — там весь код открыт.

Когда ничего не помогает — проверь модель и железо

Бывает, галлюцинации — не баг, а фича квантования. 4-bit модели типа Gemma 2 (9B) Q4_K_M теряют точность на юридических терминах. Перейди на 8-bit или используй модели, заточенные под tool calling — вот обзор лучших. И не экономь на контексте: 32K токенов — минимум для юрдокумента.

💡
Совет: никогда не верь ответу, который не сопровождается точной цитатой из документа. Если модель заявляет «согласно реестру должников» — пусть покажет номер реестра и страницу. Иначе — delete.

Техника на прокачку: оценка атрибуции через спектральный анализ

Хочешь совсем хардкора? Есть метод, который я описал в статье про линейку в векторном пространстве: считаем не только cosine similarity, но и spectral angle между эмбеддингами ответа и чанков. Если угол больше 45 градусов — ответ почти гарантированно не соответствует источнику. Работает даже на коротких цитатах. Читай «Когда LLM врет».

Итог (без занудства)

Галлюцинации локальных LLM в юриспруденции — решаемая проблема, если не лениться. RAG + CoT + tool calling + верификация + самовосстановление = 5 слоёв защиты. Когда-нибудь модели научатся не врать, но пока это наша работа — достраивать костыли. Кстати, если твой локальный LLM всё ещё галлюцинирует после всех слоёв — проверь, не используешь ли ты модель, обученную на кулинарных книгах. Юридический домен требует специального обучения. Но это уже совсем другая история.

Подписаться на канал