Дообучение LLM для извлечения JSON: пошаговое руководство 2026 | AiManual
AiManual Logo Ai / Manual.
07 Июн 2026 Гайд

Дрессировка LLM: как заставить нейросеть родить валидный JSON и не сломать прод

Полный гайд по fine-tuning open-source LLM для генерации структурированного JSON: от датасета до продакшена. LoRA, QLoRA, грамматики и подводные камни.

Реклама
hor_partv1

Вы когда-нибудь смотрели на ответ модели и думали: «Это JSON? Это какая-то пародия на JSON». Лишние запятые, пропущенные кавычки, вложенность, которая рассыпается при первой попытке распарсить. Знакомо.

Особенно больно, когда работаешь с маленькими локальными моделями — они экономят токены, но ценой структуры. Каталог ошибок и библиотека для восстановления — это паллиатив. Ампутация вместо лечения.

Хотите, чтобы модель сама, без костылей, выдавала идеальный JSON? Тогда добро пожаловать в мир дообучения. Рассказываю, как приручить зверя.

Почему LLM ломают JSON?

Дело не в злом умысле. Большинство open-source моделей натренированы на естественном языке, где правила пунктуации — это рекомендации. JSON же требует педантичной точности: каждая скобка на своём месте, каждая кавычка удвоена. Модель пытается угадать, а не вычислить.

Более того, даже если вы используете structured outputs (промпты вроде «Ответь строго в JSON»), модель может «забыть» закрыть скобку, особенно при длинных ответах. Когда JSON — это не опция, а требование, приходится идти на крайние меры.

Облачные провайдеры, вроде Amazon Bedrock со своим Structured Outputs, решают проблему на своей стороне, но вы привязаны к платформе и платите за каждый запрос. Локальное дообучение даёт свободу и контроль.

Важно: Дообучение — не серебряная пуля. Если вам нужно извлекать JSON из 150-страничного PDF, сначала сравните подходы. А в некоторых случаях дешевле и быстрее вообще не использовать LLM.

Решение: дообучение + грамматика

Комбинация двух техник даёт 99.9% валидного JSON:

  • Дообучение (fine-tuning) — адаптируем модель под конкретный формат вывода. Она «запоминает», что после извлечения имени пациента нужно закрыть кавычку и поставить запятую.
  • Grammar sampling — на этапе инференса принудительно ограничиваем генерацию правилами JSON. Модель может выбрать только те токены, которые соответствуют синтаксису.

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

Пошаговый план (будет больно, но эффективно)

1 Выбор модели и окружения

Берём самую современную open-source модель на июнь 2026 — Qwen 3.5 8B или Llama 4 7B. Они показывают отличное понимание структуры даже в базовой версии. Для дообучения используем Unsloth (ускоряет LoRA в 2 раза) и PyTorch 3.0 с CUDA 13.0.

# Установка
pip install unsloth transformers torch xformers
pip install git+https://github.com/outlines-dev/outlines.git  # для грамматик

2 Создание датасета — ключевой момент

Формат — чат-темплейт (применяется к chat-template токенизатора). Каждый пример: пользовательский запрос (текст для извлечения) + ассистент (правильный JSON). Минимум 200 примеров, лучше 500-1000.

import json
from datasets import Dataset

# Примеры для извлечения данных из медицинских карт
# Вдохновлено: /article/meditsinskie-zapisi-v-json-za-15-minut-kak-zastavit-lokalnyie-llm-chitat-pocherk-vrachej/
samples = [
    {
        "instruction": "Извлеки информацию о пациенте из текста:",
        "input": "Пациент: Иванов Иван, 45 лет. Диагноз: стенокардия. Дата: 2025-12-01",
        "output": '{"name": "Иванов Иван", "age": 45, "diagnosis": "стенокардия", "date": "2025-12-01"}'
    },
    # ... ещё 199+ примеров
]

def format_example(ex):
    return {
        "text": tokenizer.apply_chat_template([
            {"role": "user", "content": ex["instruction"] + "\n" + ex["input"]},
            {"role": "assistant", "content": ex["output"]}
        ], tokenize=False)
    }

dataset = Dataset.from_list([format_example(s) for s in samples])
dataset.save_to_disk("medical_json_dataset")
🔥
Ошибка №1: Использовать один и тот же JSON-шаблон для всех примеров. Модель выучит конкретную структуру и не сможет обобщать. Разнообразьте поля, порядок ключей, вложенность.

3 Конфигурация LoRA

LoRA (Low-Rank Adaptation) — дообучаем только небольшие веса, экономя память. Для генерации JSON ключевые модули — q_proj, v_proj, k_proj, o_proj.

from unsloth import FastLanguageModel

model, tokenizer = FastLanguageModel.from_pretrained(
    "Qwen/Qwen3.5-8B-Instruct",
    max_seq_length=2048,
    load_in_4bit=True,  # QLoRA
)

model = FastLanguageModel.get_peft_model(
    model,
    r=16,
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
    lora_alpha=32,
    lora_dropout=0.05,
    bias="none",
    use_gradient_checkpointing=True,
    random_state=42,
)

4 Запуск обучения

Используем SFTTrainer из trl. Гиперпараметры проверены на практике.

from trl import SFTTrainer
from transformers import TrainingArguments

trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    train_dataset=dataset,
    dataset_text_field="text",
    args=TrainingArguments(
        per_device_train_batch_size=2,
        gradient_accumulation_steps=4,
        num_train_epochs=3,
        learning_rate=2e-4,
        fp16=True,
        logging_steps=10,
        save_strategy="epoch",
        output_dir="outputs-json",
    ),
)

trainer.train()
model.save_pretrained("medical-json-lora")

5 Инференс с гарантией JSON (grammar)

Без грамматики даже дообученная модель иногда ошибается. Подключаем llama.cpp grammar (outlines умеет конвертировать в GBNF).

from outlines import generate, models

# Загружаем дообученную модель через llama.cpp
model = models.llamacpp("path/to/gguf", grammar="json")

generator = generate.json(model, 
    # Определяем схему вывода
    {
        "type": "object",
        "properties": {
            "name": {"type": "string"},
            "age": {"type": "integer"},
            "diagnosis": {"type": "string"}
        },
        "required": ["name", "diagnosis"]
    }
)

result = generator("Пациент: Петров Петр, 60 лет, диагноз: гипертония")
print(result)
# {'name': 'Петров Петр', 'age': 60, 'diagnosis': 'гипертония'}

Нюансы, которые вас закопают

  • Только обучение ≠ гарантия. Без грамматики на сложных текстах (PDF с таблицами, рукописный ввод) модель может сгенерировать невалидный JSON. Loot-JSON — хорошая аварийная библиотека, но лучше не доводить.
  • Размер датасета. 50 примеров — переобучение. 5000 — почти всегда избыточно. Золотая середина — 200-1000.
  • Валидация на тесте. Считайте метрику: доля валидных JSON. Если < 95%, увеличивайте датасет или меняйте модель.
  • Не смешивайте типы JSON. Если вам нужно извлекать и медицину, и юридические документы — делайте отдельные LoRA-адаптеры или объединяйте датасеты, но с явным промптом.
  • Контекст 4K+. Большие запросы требуют больше памяти и больше примеров с длинными текстами. Используйте трюки с системным промптом, чтобы уложиться в лимит.

Практический совет: Перед тем, как дообучать, попробуйте прогнать вашу задачу через простой промпт + грамматику. Если accuracy > 80% — дообучение может быть избыточным. Экономия времени и денег. Проверено на реальных ETL-пайплайнах.

Личный опыт: мои грабли

Первый раз я дообучал Llama 3.2 на 100 примерах извлечения email-ов из писем. Результат: модель идеально извлекала email, но если в тексте его не было — она выдумывала что-то похожее (например, "user@example.com"). Причина: в датасете не было негативных примеров. Добавил 30 примеров с пустым полем "email": null — проблема ушла.

Второй раз я забыл про грамматику и на проде получил 15% невалидных JSON. Пользователи сходили с ума. Теперь даже с дообучением я всегда ставлю grammar sampling.

И последнее: ASON (асинхронный JSON) — тема, которая набирает обороты в 2026. Если ваша модель тормозит на генерации длинных JSON — присмотритесь к этому формату. Он экономит до 70% токенов без потери информации.

Итог: дообучение + грамматика = 99.9% надёжности. Не экономьте на датасете, не забывайте про валидацию, и ваш прод не упадёт из-за лишней скобки. Hugging Face Hub — лучшая платформа для хранения адаптеров. Делитесь своими LoRA, комьюнити скажет спасибо.

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