Структурированный вывод и вызов функций: надёжные схемы для эксплуатации
Продвинутый13 мин чтенияИИ для бизнеса

Структурированный вывод и вызов функций: надёжные схемы для эксплуатации

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

Что вы сможете сделать

Надёжный структурированный вывод и вызов функций требуют большего, чем режим JSON: строгих схем, явной семантики ошибок, идемпотентности, корректной обработки отказов и проверки результатов инструментов до распространения ошибки дальше по системе.

Сохраняется только в этом браузере.
В этой статье

Переход от чат-бота к системе, которая выполняет задачи с помощью LLM, происходит на уровне структурированного вывода и вызова функций. Здесь модель перестаёт генерировать только обычный текст и начинает формировать данные, выбирать действия и взаимодействовать с остальной инфраструктурой.

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

Статья описывает средства контроля, необходимые рабочей реализации. Сведения об API и их ограничениях проверены 4 августа 2026 года по официальным материалам о структурированном выводе OpenAI, структурированном выводе Claude и структурированном выводе Gemini. Примеры ниже — архитектурные решения, а не результаты сравнительного теста AI Expert.

Два режима

Две связанные, но различимые возможности:

Структурированный вывод: LLM формирует ответ по заданной схеме, обычно в JSON. Он нужен, когда результат модели должен обрабатываться программно.

Вызов функций/инструментов: LLM получает набор функций, которые может вызывать, решает, какую вызвать (если вообще нужно), формирует параметры. Хост-система выполняет функцию и возвращает результат. LLM может вызвать следующие функции или сформировать финальный ответ.

API моделей обычно выставляют это через:

  • Параметр формата ответа, принимающий поддерживаемое подмножество JSON Schema: у OpenAI это формат ответа с JSON-схемой, у Claude — output_config.format, у Gemini — структурированный вывод с управлением через схему.
  • Массив tools, описывающий доступные операции, и ответ с вызовом инструмента. Актуальные модели Claude также поддерживают strict: true в определениях клиентских инструментов, поэтому искусственный инструмент только ради получения JSON больше не является единственным вариантом.

Обе возможности связаны: вызов функции — это разновидность структурированного вывода, где схемой служит сигнатура функции.

Паттерн 1: жёсткие, явные схемы

Самый большой выигрыш в надёжности — в ваших схемах.

Рыхлая схема:

{
  "type": "object",
  "properties": {
    "category": { "type": "string" },
    "priority": { "type": "string" }
  }
}

Жёсткая схема:

{
  "type": "object",
  "properties": {
    "category": {
      "type": "string",
      "enum": ["billing", "technical", "account", "feature_request", "complaint"],
      "description": "The ticket category. Use 'technical' for product bugs and 'account' for login/password issues."
    },
    "priority": {
      "type": "string",
      "enum": ["low", "medium", "high", "urgent"],
      "description": "Use 'urgent' only for outages or business-critical impact. 'high' for blocking issues on important customers. 'medium' for standard impact. 'low' for nice-to-haves."
    }
  },
  "required": ["category", "priority"],
  "additionalProperties": false
}

Жёсткая версия:

  • Ограничивает значения известным перечислением enum (никакого дрейфа в свободную форму).
  • Включает описания, которые работают как встроенные промпты (модель их использует).
  • Требует полей (вы не получите частичных ответов).
  • Запрещает лишние поля (никаких случайных выдуманных ключей).

В продакшене у каждого поля схемы должно быть описание. Каждое перечисление enum должно быть явным. Каждое обязательное поле должно быть помечено. Это «схема как промпт» — ваша схема занимается промпт-инжинирингом.

Паттерн 2: генерация с ограничениями

Крупные поставщики поддерживают генерацию с ограничениями: на этапе декодирования модель может формировать только допустимый по схеме результат.

  • OpenAI: response_format: { type: "json_schema", json_schema: { ..., strict: true } }.
  • Anthropic: JSON через output_config.format и строгие входные данные инструментов с strict: true.
  • Серверы моделей с открытыми весами: декодирование по грамматике или схеме, если его поддерживают выбранные сервер инференса и модель.

Предпочитайте ограниченный вывод, если его поддерживают поставщик, модель и необходимое подмножество схемы. Он предотвращает синтаксические ошибки и нарушение формы схемы, но не гарантирует фактическую правильность извлечённых значений и не разрешает побочные действия. Поставщики документируют неподдерживаемые ключевые слова и ограничения сложности. Компиляция грамматики может увеличить задержку первого запроса, а некоторые поставщики кэшируют скомпилированные грамматики, поэтому измеряйте холодный и прогретый режимы вместо предположения о незначительных накладных расходах.

Когда генерация с ограничениями недоступна (некоторые открытые модели, некоторые конфигурации), запасной вариант — валидация и повтор (см. Паттерн 4).

Паттерн 3: версионирование схем

Схемы эволюционируют. Вы добавляете поля. Помечаете поля как устаревшие. Меняете перечисления enum.

Изменение схемы — это изменение кода. Оно должно быть:

  • Версионировано. Помечайте каждую схему номером версии.
  • Протестировано. Набор для оценки прогоняется по новой схеме до развёртывания.
  • Согласовано. Последующие компоненты, использующие результат, знают об изменении.
  • По возможности обратно совместимо. Добавляйте новые опциональные поля; не убирайте обязательные.

Паттерн, который хорошо работает: хранить схемы как TypeScript-типы или Pydantic-модели, версионировать их в системе контроля версий, генерировать из них JSON Schema. Типы обслуживают и API модели, и код вашего приложения.

class TicketClassificationV2(BaseModel):
    category: Literal["billing", "technical", "account", "feature_request", "complaint"]
    priority: Literal["low", "medium", "high", "urgent"]
    confidence: float = Field(ge=0, le=1, description="Confidence in this classification, 0-1")
    needs_human_review: bool = Field(description="True if any field has low confidence or unusual signal")
    reasoning: str = Field(description="Brief reasoning for the classification, especially for non-obvious cases")

Модель Pydantic определяет схему, валидирует результат и служит типом в коде Python. Это единый источник истины.

Паттерн 4: валидация и повтор

Даже при генерации с ограничениями валидируйте результат до использования:

from pydantic import ValidationError

def call_with_validation(prompt, schema, max_retries=2):
    for attempt in range(max_retries + 1):
        response = llm_call(prompt, response_format=schema)
        try:
            parsed = schema.model_validate_json(response.content)
            return parsed
        except ValidationError as e:
            if attempt < max_retries:
                prompt = build_retry_prompt(prompt, response.content, e)
                continue
            raise

Промпт для повтора должен включать исходные инструкции, предыдущий вывод модели и конкретное описание того, что пошло не так:

В вашем предыдущем ответе была ошибка валидации:
{error message}

Ваш предыдущий вывод:
{previous output}

Исправьте проблему и выдайте корректный ответ.

Повторы работают на удивление хорошо — обычно одного повтора хватает, чтобы исправиться после ошибки модели.

Ограничения: не повторяйте бесконечно (максимум 2–3 раза). Не повторяйте при ошибках, не связанных с валидацией (лимиты запросов, контент-фильтр и т. п.). Логируйте повторы, чтобы следить за их частотой (растущая доля повторов сигнализирует о дрейфе модели или проблемах с промптом).

Паттерн 5: рефлексия над результатами

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

Голый цикл:

1. Вызовите LLM с доступными инструментами.
2. Модель решает вызвать инструмент X.
3. Выполните X.
4. Верните результат модели.
5. Модель формирует финальный ответ.

Цикл с рефлексией:

1. Вызовите LLM с доступными инструментами.
2. Модель решает вызвать инструмент X.
3. Выполните X.
4. Верните результат модели.
5. Модель оценивает: результат совпадает с ожидаемым? Стоит ли на него опираться?
6. Если да — формирует финальный ответ. Если нет — вызывает другой инструмент или просит уточнение.

Так ловятся случаи вроде:

  • Инструмент вернул 0 результатов, когда должен был вернуть данные → модель распознаёт пустой случай.
  • Инструмент вернул ошибку → модель обрабатывает её явно, а не игнорирует.
  • Инструмент вернул неожиданные данные → модель замечает это и адаптируется.

Реализация: просите модель явно оценивать результаты инструмента, возможно, через структурированный паттерн «оценить, потом действовать».

Это добавляет задержку и токены. Для высокорисковых действий (отправить письмо, провести платёж, изменить запись) оно того стоит. Для низкорискового извлечения информации — пропускайте.

Паттерн 6: идемпотентность

LLM иногда вызывают один и тот же инструмент дважды или повторяют уже успешные вызовы. Без идемпотентности вы получаете дубликаты: два возврата средств, два письма, две записи.

Паттерны идемпотентности:

Ключи идемпотентности. Каждый вызов инструмента получает уникальный ключ (генерируется на стороне клиента, передаётся в вызов). Нижестоящий API или ваша обёртка вокруг инструмента использует ключ, чтобы обнаружить дубликаты и вернуть уже существующий результат.

Семантика «найти или создать». Инструменты, создающие записи, сначала выполняют поиск. «Создать клиента с email X» сперва проверяет, есть ли клиент с email X; если есть, возвращает существующего вместо создания дубликата.

Журналы операций. Инструменты логируют каждую операцию. Обёртка проверяет журнал перед выполнением; если уже сделано — возвращает кэшированный результат.

Консервативный дизайн инструментов. Инструменты, выполняющие значимые действия, спроектированы так, чтобы требовать явного подтверждения или одобрения человеком. LLM не может случайно запустить их в плотном цикле.

Для любого инструмента с побочными эффектами проектируйте под идемпотентность. Пропуск этого шага — крупный источник продакшен-багов.

Паттерн 7: наблюдаемость вызовов инструментов

Вы должны знать, что происходит с вызовами инструментов. Для каждого вызова логируйте:

  • Метку времени.
  • Имя инструмента и аргументы.
  • Результат (или ошибку).
  • Длительность.
  • Пользователя или сессию, которым он принадлежит.
  • Цепочку вызовов в этом ходе (был ли этот вызов частью более длинной цепочки?).

Стройте дашборды на этих данных. Типичные разрезы:

  • Объём вызовов по инструментам.
  • Доля ошибок по инструментам.
  • Средняя длительность по инструментам.
  • Паттерны последовательностей («какие инструменты часто вызываются вместе?»).
  • Вызовы несуществующих инструментов (LLM попыталась вызвать инструмент, которого нет).

Это показывает, где ваша система валится и где она обходится дорого.

Паттерн 8: выдуманные аргументы

LLM иногда выдумывают значения для параметров инструмента. Они вызовут search_customers(email="...") с адресом, не соответствующим реальному вопросу пользователя. Или вызовут book_meeting(date="...") с датой, которая нигде не упоминалась.

Способы смягчения:

Строгая схема с описаниями. «user_id должен быть упомянут ранее в диалоге. Не выдумывайте ID.»

Валидация в обёртке инструмента. Если значение неправдоподобно (user_id не существует, дата в прошлом), инструмент возвращает структурированную ошибку и модель пересматривает решение.

Рефлексия. «Перед вызовом этого инструмента подтвердите, что используемые значения опираются на диалог.»

Ограниченные описания инструментов. Инструменты, оперирующие конкретными сущностями, выставляют только ID сущностей, полученные ранее в диалоге. Не выставляйте прямой поиск.

Журналы аудита. Отлавливайте паттерны выдуманных аргументов и подстраивайте промпты и схемы.

Паттерн 9: плавная деградация

Инструменты падают. API падают. Срабатывают лимиты запросов. Правильным ответом редко является «скажи пользователю, что ничего не работает».

Паттерны:

Кэш или устаревшие данные. Если живой источник недоступен, верните кэшированные данные с пометкой, что они устарели.

Частичное выполнение. Если 3 из 5 подзадач удались — сообщите, что сделано, а что нет.

Запасные пути. Если основной инструмент падает, модель знает про резервный. Например, если search_documents падает, переходим на search_web с соответствующими оговорками.

Видимые пользователю состояния ошибок. Если инструмент реально не может завершиться, модель формирует понятное сообщение об ошибке для пользователя — а не выдуманный успех.

Модель должна знать об этих паттернах. Документируйте их в системном промпте:

Если инструмент вернул ошибку:
- Попробуйте запасной инструмент, если он есть.
- Ясно сообщите о частичных результатах, если пользователь уже предоставил информацию.
- Никогда не заявляйте об успехе, когда инструмент вернул ошибку.

Паттерн 10: потоковая передача структурированного вывода

Ради удобства пользователя стриминг частичного структурированного вывода — это здорово: пользователь видит, как результат формируется в реальном времени, а не ждёт.

Реализация:

  • Большинство современных API моделей передают JSON по частям, токен за токеном.
  • Парсите частичный JSON инкрементально (библиотеки вроде partial-json-parser или собственный небольшой потоковый парсер).
  • Обновляйте интерфейс по мере появления полей.

Это особенно полезно для результатов из нескольких разделов. Длинное описание продукта, многочастный аналитический отчёт или проверка кода с несколькими замечаниями воспринимаются быстрее, когда передаются постепенно.

Не принимайте решений по неполному результату. Потоковая передача подходит для отображения, но перед действием дождитесь завершения и валидации структурированного результата.

Паттерн 11: вызов функций против явных «decide»-вызовов

Нативный вызов функций удобен — модель «решает», когда вызвать инструмент. Но в некоторых рабочих процессах явный «decide»-вызов надёжнее.

Пример: рабочий процесс клиентской поддержки, где модель должна выбрать между несколькими действиями.

Подход с нативным вызовом функций: дайте модели 5 инструментов (refund, send_article, escalate_to_human, ask_clarifying_question, close_ticket). Пусть решает сама.

Подход с явным decide-вызовом: сначала вызовите модель с единственным инструментом decide_action, у которого один параметр — какое действие. Затем на основании решения вызовите модель снова — уже только с релевантным инструментом.

Явный подход медленнее и многословнее, но надёжнее. Модель собраннее на каждом шаге. У хост-системы больше контроля над рабочим процессом.

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

Паттерн 12: форматирование результата инструмента

То, как вы возвращаете результат инструмента, имеет значение. Модель читает результат; формат важен.

Плохо:

{"id": "cus_123", "n": "John", "p": "12345"}

Лучше:

{
  "customer_id": "cus_123",
  "name": "John Doe",
  "phone": "+1-555-0123",
  "tier": "premium",
  "open_tickets": 0
}

Лучше всего (для некоторых случаев):

Найден клиент:
- ID: cus_123
- Имя: John Doe
- Уровень: Premium
- Телефон: +1-555-0123
- Открытых обращений: 0

Этот клиент на уровне premium, открытых обращений нет.

Форма «лучше всего» — человекочитаемая, включает контекст, и модели проще использовать её в последующей генерации. Форма «лучше» — более структурирована и машиночитаема. Используйте тот формат, с которым модель лучше справляется на ваших последующих задачах (проверяйте на практике).

Для некоторых инструментов хорошо работает возврат и того, и другого («Вот результат: [нарратив]. Сырые данные: [JSON]»).

Паттерн 13: повторы с учётом схемы

Некоторые ошибки валидации непоправимы (модель фундаментально не поняла задачу). Другие — простые правки.

Полезный паттерн: классифицировать ошибку и реагировать соответственно.

def handle_validation_error(error):
    if "missing required field" in str(error):
        return retry_with_message("You omitted required field X. Please include it.")
    elif "value not in enum" in str(error):
        return retry_with_message("Value X is not in the allowed set. Choose from: ...")
    elif "type mismatch" in str(error):
        return retry_with_message("Field X must be a number, not a string.")
    else:
        # Unknown error — single generic retry
        return retry_with_message("There was an error in your response. Please try again.")

Повторы, заточенные под конкретную ошибку, срабатывают чаще, чем универсальные.

Паттерн 14: компонуемость

Инструменты должны компоноваться. Маленькие сфокусированные инструменты, делающие одно дело, модель может комбинировать в сложные рабочие процессы.

Монолитный инструмент process_customer_request(query), делающий всё, — это чёрный ящик. Модель не может ни наблюдать, ни управлять внутренней логикой.

Набор сфокусированных инструментов — search_customer(email), get_recent_orders(customer_id), check_subscription_status(customer_id), escalate_to_human(reason) — модель может скомпоновать в правильный поток под каждую ситуацию.

Проектируйте инструменты с правильной гранулярностью. Каждый делает одно дело. Инструменты компонуются в рабочие процессы.

Паттерн 15: схема для «не знаю»

Тонкий паттерн: явно моделируйте неопределённость в схеме.

class CustomerInfo(BaseModel):
    name: str
    name_confidence: Literal["high", "medium", "low"]
    needs_clarification: bool
    clarification_question: Optional[str] = None

Модель может вернуть «низкую уверенность» с уточняющим вопросом вместо того, чтобы фантазировать.

Это намного лучше, чем модель, которая всегда уверенно заполняет поля — иногда выдуманными данными.

Разобранный пример: обработка счетов

Чтобы собрать всё вместе, вот система обработки счетов продакшен-уровня.

Вход: PDF-счёт во вложении к письму. Цель: извлечь структурированные данные, направить в бухгалтерскую систему.

Схема:

class LineItem(BaseModel):
    description: str
    quantity: float
    unit_price: float
    total: float
    confidence: Literal["high", "medium", "low"]

class Invoice(BaseModel):
    vendor_name: str
    vendor_id: Optional[str] = None  # null if not found in our records
    invoice_number: str
    invoice_date: date
    due_date: Optional[date] = None
    line_items: List[LineItem]
    subtotal: float
    tax: float
    total: float
    currency: str  # ISO 4217
    confidence: Literal["high", "medium", "low"]
    needs_review: bool
    review_reasons: List[str]  # Specific reasons review is needed

Рабочий процесс:

  1. Шаг OCR: модель компьютерного зрения извлекает текст из PDF.
  2. Шаг извлечения: вызов LLM со схемой выше, генерация с ограничениями включена.
  3. Шаг валидации: Pydantic валидирует. Ошибки валидации запускают один повтор с обратной связью.
  4. Шаг кросс-проверки: вызов инструмента, ищущий поставщика в нашей системе. Если vendor_name совпадает с известным — прикрепляется vendor_id. Если нет — ставится needs_review=true.
  5. Шаг проверки арифметики: проверяем sum(line_items.total) ≈ subtotal и subtotal + tax ≈ total. Если нет — needs_review=true.
  6. Шаг проверки уверенности: если confidence низкий или у какой-то позиции низкая уверенность — needs_review=true.
  7. Маршрутизация: если needs_review=true, отправляем в очередь на проверку человеком. Иначе — в бухгалтерскую систему.
  8. Журналирование: для каждого шага сохраняются входные данные, результат, длительность и ошибки.

Обрабатываемые режимы отказа:

  • Некорректный JSON: генерация с ограничениями предотвращает; повтор ловит граничные случаи.
  • Выдуманные поля: схема строгая.
  • Арифметические ошибки: проверены.
  • Неизвестные поставщики: помечены.
  • Низкая уверенность: помечена.
  • Ошибки инструмента: явная обработка.

Не заимствуйте из статьи показатель полностью автоматической обработки. Создайте размеченный набор из тех форматов счетов, языков, валют, сканов и исключений, которые действительно получаете. До автоматизации согласуйте точность по каждому полю, сверку денежных сумм, допустимую долю ошибочного автоматического одобрения и поставщиков либо суммы, которые всегда требуют проверки. Сначала запустите систему в теневом режиме, учитывайте ошибки по категориям и разрешайте запись в бухгалтерскую систему только для той части потока, которая проходит согласованные пороги.

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

Типичные ошибки

Несколько паттернов, которые мы видим раз за разом:

Ошибка 1: нет валидации. Pydantic, zod — что угодно — просто валидируйте. Не доверяйте модели.

Ошибка 2: размытые описания. «category: string» модели не помогает. «category: одно из billing, technical, account_access, где billing охватывает…» — помогает.

Ошибка 3: нерелевантные инструменты. Широкий каталог усложняет выбор для модели. Показывайте только те инструменты, которые нужны и разрешены в текущем состоянии, а рабочее количество определяйте тестами выбора инструментов, а не универсальным правилом «меньше десяти».

Ошибка 4: нет повтора при ошибке валидации. Один некорректный выход убивает весь поток. Делайте один повтор с обратной связью.

Ошибка 5: нет наблюдаемости. Когда вызовы инструментов валятся в продакшене, без трассировок вы не сможете поставить диагноз.

Ошибка 6: нет идемпотентности на инструментах с побочными эффектами. Дубликаты возвратов, дубликаты писем. Предсказуемый баг.

Ошибка 7: доверие к аргументам, выбранным LLM, без валидации. Выдуманные идентификаторы пользователей, выдуманные даты. Валидируйте аргументы перед выполнением.

Ошибка 8: пропуск версионирования схем. Изменения схемы ломают нижестоящих потребителей. Версионируйте.

От демо к продакшен-системе

Структурированный вывод и вызов функций переводят систему от разговоров к выполнению задач. При хорошем проектировании они позволяют безопасно применять ИИ в рабочих процессах; при плохом приводят к сложным и дорогим сбоям.

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

Каждый из этих паттернов отделяет демо от продакшен-системы. Закладывайте их с самого начала.

Читать дальше

Продолжайте тот же учебный путь со следующими практическими статьями.

Проектирование промптов для продакшена: системный слой, слой разработчика и пользовательский слой

Проектирование промптов для продакшена: системный слой, слой разработчика и пользовательский слой

Разделять системные инструкции, инструкции слоя разработчика и пользовательские инструкции и тестировать продакшен-промпты как версионируемые компоненты системы.

Читать дальше
Строим оценки, которые реально ловят регрессии

Строим оценки, которые реально ловят регрессии

Большинство оценочных наборов выглядят внушительно, но пропускают реальные регрессии. Чтобы построить оценки, которые ловят важное, нужны аккуратно собранный датасет, чувствительные метрики, калиброванные судьи и культура доверия. Паттерны команд, у которых это получается.

Читать дальше
Наблюдаемость LLM-приложений: трассировка, стоимость, задержка, дрейф качества

Наблюдаемость LLM-приложений: трассировка, стоимость, задержка, дрейф качества

Расширьте стандартную наблюдаемость многошаговыми трассировками, атрибутированной стоимостью, версиями промптов и моделей, оценёнными сигналами качества, механизмами конфиденциальности и оповещениями, формируемыми на основе рабочей нагрузки.

Читать дальше

Углубиться

Тщательно подобранные внешние курсы, которые глубже раскрывают эту тему.

Coursera · Emory University

Generative AI in Marketing

Emory University Goizueta Business School faculty

Более глубокий университетский аналог нашего начинающего HubSpot-курса по маркетингу — подход бизнес-школы Emory идёт дальше «как писать промпты» к обучению генеративных моделей под брендовый вывод, экономике воронки покупок для ИИ-контента и целому модулю настоящего скепсиса о том, когда генеративный ИИ в маркетинге стоит использовать, а когда нет.

Продвинутый~14 часов · в своём темпе (4 модуля)
Coursera · IBM

Generative AI for Executives and Business Leaders

IBM AI Academy

Ответ эпохи генеративного ИИ на вопрос о стратегии для руководителей. Три коротких курса, адресованных напрямую бизнес-лидерам — техническая подготовка не нужна — о том, где генеративный ИИ создаёт ценность, как им ответственно управлять и как превратить расплывчатое «нам нужен ИИ» в конкретный, обоснованный сценарий использования. Оценка 4,6 примерно по 700 отзывам.

Продвинутый~10 часов · специализация из 3 курсов

Все курсы в категории «ИИ для бизнеса»