Задача

Вам приходят неструктурированные тексты — резюме кандидатов, клиентские отзывы, счета от поставщиков, описания вакансий, письма — и нужно автоматически извлечь из них ключевые поля в формате JSON. Руками это долго и ненадёжно. LLM справляется за секунды.

Этот рецепт даёт вам универсальный шаблон промпта, который адаптируется под любой тип документа.

ℹ Почему это важно
По данным исследований, до 80% корпоративных данных хранится в неструктурированном виде. Промпт-извлечение — самый быстрый способ превратить текст в таблицу без написания парсеров.

Для кого

  • Аналитики данных — быстрый парсинг отчётов и документов
  • HR-специалисты — обработка резюме и заявок
  • Маркетологи — анализ отзывов и упоминаний бренда
  • Разработчики — интеграция извлечения данных в пайплайны
  • Предприниматели — автоматизация рутинной обработки документов

Как работает промпт


graph LR
    A["📄 Неструктурированный текст"] --> B["🔧 Промпт с JSON-схемой"]
    B --> C["🤖 LLM анализирует"]
    C --> D["📊 Структурированный JSON"]
    D --> E["💾 БД / Таблица / API"]

Принцип прост: вы даёте модели текст + схему нужных полей, и она возвращает заполненный JSON. Ключ к качеству — точное описание каждого поля и правила обработки неопределённости.

Промпт

Ты — система извлечения структурированных данных. Твоя задача — проанализировать 
входной текст и извлечь информацию строго по заданной схеме.

<schema>
{
  "fields": [
    {"name": "ПОЛЕ_1", "type": "string", "description": "Описание, что именно извлекать"},
    {"name": "ПОЛЕ_2", "type": "number", "description": "Описание числового поля"},
    {"name": "ПОЛЕ_3", "type": "array", "description": "Список значений"},
    {"name": "ПОЛЕ_4", "type": "string", "enum": ["вариант_а", "вариант_б", "вариант_в"], "description": "Категория из фиксированного списка"}
  ]
}
</schema>

<rules>
- Извлекай ТОЛЬКО то, что явно указано или однозначно следует из текста
- Если значение отсутствует — используй null, НЕ додумывай
- Числа возвращай без единиц измерения (единицы укажи в отдельном поле, если нужно)
- Даты приводи к формату YYYY-MM-DD
- Массивы сортируй по релевантности (самое важное — первым)
- Если текст содержит противоречия — извлеки оба варианта и добавь поле "conflicts"
</rules>

<output_format>
Верни ТОЛЬКО валидный JSON. Без пояснений, без markdown-обёрток.
Структура:
{
  "extracted": { ...поля по схеме... },
  "confidence": "high|medium|low",
  "notes": "краткие заметки о качестве извлечения, если есть неоднозначности"
}
</output_format>

<input_text>
{{ВСТАВЬТЕ_ТЕКСТ_СЮДА}}
</input_text>
💡 Совет
Используйте XML-теги (<schema>, <rules>, <input_text>) для разделения блоков. Claude и GPT-4o лучше распознают структуру промпта, когда секции явно размечены — это подтверждается официальной документацией Anthropic.

Пример: извлечение данных из резюме

Входной текст:

Мария Иванова, 28 лет. Опыт работы 5 лет в IT. Последнее место — 
старший аналитик данных в Яндексе (2023–2025). До этого — джуниор 
аналитик в Сбере, 2 года. Владеет Python, SQL, Tableau, Power BI. 
Английский — Upper-Intermediate. Ожидаемая зарплата от 250 000 руб. 
Готова к переезду в Москву или удалённой работе. Закончила МГУ, 
факультет ВМК, магистратура по прикладной математике (2021).

Промпт со схемой для резюме:

<schema>
{
  "fields": [
    {"name": "full_name", "type": "string", "description": "ФИО кандидата"},
    {"name": "age", "type": "number", "description": "Возраст"},
    {"name": "total_experience_years", "type": "number", "description": "Общий опыт работы в годах"},
    {"name": "current_position", "type": "string", "description": "Последняя/текущая должность"},
    {"name": "current_company", "type": "string", "description": "Последняя/текущая компания"},
    {"name": "skills", "type": "array", "description": "Технические навыки"},
    {"name": "education", "type": "string", "description": "Образование: вуз и специальность"},
    {"name": "salary_expectation", "type": "number", "description": "Ожидаемая зарплата в рублях"},
    {"name": "work_format", "type": "array", "enum": ["офис", "удалёнка", "гибрид", "переезд"], "description": "Предпочтительный формат работы"},
    {"name": "english_level", "type": "string", "description": "Уровень английского"}
  ]
}
</schema>

Результат:

{
  "extracted": {
    "full_name": "Мария Иванова",
    "age": 28,
    "total_experience_years": 5,
    "current_position": "Старший аналитик данных",
    "current_company": "Яндекс",
    "skills": ["Python", "SQL", "Tableau", "Power BI"],
    "education": "МГУ, факультет ВМК, магистратура по прикладной математике (2021)",
    "salary_expectation": 250000,
    "work_format": ["удалёнка", "переезд"],
    "english_level": "Upper-Intermediate"
  },
  "confidence": "high",
  "notes": "Все поля извлечены однозначно. Зарплата указана как минимальная ('от 250 000')."
}

Сравнение подходов к извлечению данных

ПараметрРучной парсинг (regex)LLM-извлечениеStructured Outputs API
Время настройкиЧасы-дниМинутыМинуты
Гибкость форматаНизкаяВысокаяВысокая
Точность на «чистых» данных95-99%90-97%95-99%
Работа с «грязным» текстомПлохоХорошоХорошо
Стоимость на 1000 документовБесплатно$2-15$2-15
Гарантия валидного JSONДаНет*Да
Нужны примерыНетЖелательноЖелательно

При промпт-подходе модель может иногда вернуть невалидный JSON. Structured Outputs API (доступен в Claude и OpenAI) решает эту проблему — схема компилируется в грамматику, и модель физически не может нарушить формат.

⚠ Важно
LLM может «галлюцинировать» внутри валидной схемы — вернуть правильный JSON с неправильными данными. Всегда добавляйте поле confidence и проверяйте результаты для критичных задач.

Вариации и настройки

1. Пакетная обработка

Если нужно обработать несколько документов за один запрос:

<batch_mode>
Обработай каждый документ из списка ниже отдельно.
Верни массив JSON-объектов в том же порядке.
Если документ невозможно обработать — верни {"error": "причина"} на его позиции.
</batch_mode>

<documents>
[ДОКУМЕНТ 1]
---
[ДОКУМЕНТ 2]
---
[ДОКУМЕНТ 3]
</documents>

2. Извлечение из отзывов (сентимент + сущности)

<schema>
{
  "fields": [
    {"name": "sentiment", "type": "string", "enum": ["positive", "negative", "mixed", "neutral"]},
    {"name": "rating_inferred", "type": "number", "description": "Предполагаемая оценка 1-5"},
    {"name": "product_mentions", "type": "array", "description": "Упомянутые продукты/функции"},
    {"name": "pain_points", "type": "array", "description": "Проблемы и жалобы"},
    {"name": "praise_points", "type": "array", "description": "Что понравилось"},
    {"name": "action_required", "type": "boolean", "description": "Требует ли отзыв реакции"}
  ]
}
</schema>

3. Извлечение из счетов и инвойсов

<schema>
{
  "fields": [
    {"name": "vendor_name", "type": "string"},
    {"name": "invoice_number", "type": "string"},
    {"name": "date", "type": "string", "description": "Дата в формате YYYY-MM-DD"},
    {"name": "items", "type": "array", "description": "Список позиций: {name, quantity, unit_price, total}"},
    {"name": "subtotal", "type": "number"},
    {"name": "tax", "type": "number"},
    {"name": "total", "type": "number"},
    {"name": "currency", "type": "string"}
  ]
}
</schema>

Советы по улучшению

Добавьте few-shot примеры

Один-два примера «вход → выход» резко повышают точность. Модель перестаёт гадать формат и следует образцу:

<example>
Input: "Иван Петров, Python-разработчик, 3 года опыта, ищет от 200к"
Output: {"full_name": "Иван Петров", "position": "Python-разработчик", 
         "experience_years": 3, "salary_expectation": 200000}
</example>

Используйте цепочку рассуждений для сложных случаев

Для текстов с неявной информацией добавьте блок рассуждений:

<rules>
Перед извлечением:
1. Определи тип документа
2. Выдели ключевые сущности
3. Разреши противоречия (если есть)
4. Только потом заполни JSON

Рассуждения помести в поле "reasoning" (строка).
</rules>
📝 Пример цепочки рассуждений
Текст: “Работал тимлидом 2 года, до этого 3 года сеньором в той же компании”
Reasoning: “Общий опыт = 2 + 3 = 5 лет. Текущая позиция — тимлид. Компания одна, но не названа → company = null”
Без chain-of-thought модель может указать опыт как 2 или 3 года вместо суммарных 5.

Валидируйте результат программно

Не полагайтесь на модель — проверяйте JSON на стороне кода:

import json
from pydantic import BaseModel, validator
from typing import Optional

class CandidateData(BaseModel):
    full_name: str
    age: Optional[int] = None
    total_experience_years: Optional[float] = None
    skills: list[str] = []
    salary_expectation: Optional[int] = None

    @validator("age")
    def age_must_be_reasonable(cls, v):
        if v is not None and not (16 <= v <= 100):
            raise ValueError(f"Unreasonable age: {v}")
        return v

# Парсинг и валидация ответа LLM
raw = json.loads(llm_response)
candidate = CandidateData(**raw["extracted"])

Используйте Structured Outputs API для продакшена

В Claude (модели Sonnet 4.5, Opus 4.5, Haiku 4.5) и OpenAI (GPT-4o) доступны нативные structured outputs — схема компилируется в грамматику на уровне inference, и модель физически не может вернуть невалидный JSON. Для продакшен-пайплайнов это надёжнее промпт-подхода.

Промпт-извлечение — это не замена базы данных и не замена парсера. Это мост между хаосом неструктурированного текста и порядком структурированных данных. Используйте его там, где regex сдаётся, а полноценный NLP-пайплайн — избыточен.