Содержание
  1. Что такое PydanticAI и как устроен цикл агента
  2. Тип результата: output_type, схемы и валидация
  3. Учебный контракт Ticket: вход, артефакт и эталон
  4. Структурная корректность и фактическая правильность
  5. Инструменты, зависимости и повтор при сбое
  6. Подмена модели в тестах и случай, где подход не решает задачу
  7. Что проверить перед внедрением
  8. Частые вопросы
Обзоры

Обзор PydanticAI: агент с типизированным результатом

11 ноября 2026 · 15 минут чтения

Обзор PydanticAI: агент с типизированным результатом — это разбор Python-фреймворка, в котором модель возвращает данные объявленного типа. Документация проекта на ai.pydantic.dev описывает его как Python AI SDK: типизированный расширяемый цикл агента, где смена модели выполняется строкой, а механизм резервных моделей позволяет настроить переключение при сбое. Ниже — механизм, учебный контракт, проверка ответов и границы подхода.

Что такое PydanticAI и как устроен цикл агента

Документация проекта на ai.pydantic.dev описывает Pydantic AI как Python AI SDK: типизированный расширяемый цикл агента, в котором каждая модель меняется строкой, а на случай сбоя провайдера можно настроить резервную модель. Один и тот же агент запускается за веб-фронтендом, в терминале, в голосовом вызове, в очереди фоновых задач, в GitHub Actions или как обычный объект, у которого вызывается run(). Генерация изображений и эмбеддинги поставляются в том же пакете.

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

Типизация здесь не декорация. В терминах типов агенты обобщены по типу зависимостей и типу вывода: агент, которому нужны зависимости типа Foobar и который выдаёт list[str], имеет тип Agent[Foobar, list[str]]. На практике это означает, что IDE подсказывает, когда тип выбран верно, а статическая проверка типов работает вместе с Pydantic AI. Документация приводит пример агента, который ожидает целочисленную зависимость и выдаёт булев результат: его тип — Agent[int, bool].

Предел механизма простыми словами: фреймворк отвечает за форму данных и порядок вызовов. Он не отвечает за то, правдивы ли факты внутри этой формы. Тип bool гарантирует, что на выходе лежит булево значение, и ничего не говорит о том, верен ли сам ответ по существу задачи.

Запуск агента документация описывает несколькими способами. agent.run_sync() — обычная синхронная функция, возвращающая объект завершённого результата; внутри она вызывает цикл событий. agent.run_stream() — асинхронный контекстный менеджер, возвращающий StreamedRunResult с методами потоковой передачи текста и структурированного вывода как асинхронного итератора; agent.run_stream_sync() даёт синхронный вариант с теми же методами. agent.run_stream_events() выдаёт асинхронный итератор по событиям AgentStreamEvent, завершающийся событием AgentRunResultEvent с финальным результатом запуска.

Тип результата: output_type, схемы и валидация

«Output» в документации — это финальное значение, возвращённое запуском агента. Им может быть простой текст, структурированные данные, изображение или результат функции, вызванной с аргументами от модели. Результат обёрнут в AgentRunResult или StreamedRunResult, чтобы приложение получило и другие данные: расход по запуску и историю сообщений. Обе обёртки обобщены по типу вложенных данных, поэтому информация о типе сохраняется.

Конструктор Agent принимает аргумент output_type, в который входит один или несколько типов либо функций вывода. Поддерживаются простые скалярные типы, списки и словари, включая TypedDict и StructuredDict, датаклассы и модели Pydantic, а также объединения типов — в целом всё, что поддерживается как аннотации типов в модели Pydantic. Можно передать список из нескольких вариантов.

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

Правило завершения запуска: он заканчивается, когда модель отвечает одним из типов вывода, либо — если тип не указан или среди разрешённых есть str — когда получен обычный текстовый ответ. Запуск может быть отменён при превышении лимитов использования. С DeferredToolRequests или PendingModelResponse в типе вывода запуск способен завершиться досрочно, чтобы приложение утвердило инструменты или дождалось запроса, который провайдер ещё обрабатывает, например пакетного, и продолжило позже.

Если схема типа вывода не является объектом — например, это int или list[int] — тип оборачивается в объект из одного элемента, чтобы схемы всех инструментов, зарегистрированных у модели, оставались объектными. Структурированные выводы, как и инструменты, используют Pydantic для построения JSON-схемы инструмента и для валидации данных, вернувшихся от модели.

Элемент Что задаёт Что проверяет
output_type Тип или список типов вывода Соответствие данных схеме
Схема инструмента JSON-схему для модели Аргументы до выполнения кода
AgentRunResult Обёртку результата Сохранение типа данных
Правило приложения Связь полей с задачей Фактическую правильность

Отдельная тонкость — статические проверки типов. Pyright и mypy стараются вывести тип вывода агента из указанного output_type, и при передаче функций или нескольких типов в объединении либо списке это удаётся не всегда, хотя Pydantic AI ведёт себя корректно. В такой ситуации проверяльщик типов жалуется даже при верном output_type, и документация советует явно указать обобщённые параметры в конструкторе Agent.

Учебный контракт Ticket: вход, артефакт и эталон

Возьмём учебный контракт, спроектированный для этой статьи. Он описывает заявку в поддержку: category принимает одно из значений delivery, payment, other; order_id — строка или null; needs_human — булево значение. Полный вход: «Заказ A-42 не приехал. Когда будет доставка?» Эталон задачи: {"category":"delivery","order_id":"A-42","needs_human":true}.

Инструмент lookup_order работает только на чтение и возвращает {"status":"in_transit","eta":null}. Это не позволяет придумать срок. Явное учебное правило приложения: если клиент спрашивает срок доставки, а lookup_order возвращает eta=null, итог должен иметь needs_human=true; срок не выводится из статуса in_transit.

PYTHON41 строка
import os
from typing import Literal, Optional

from pydantic import BaseModel
from pydantic_ai import Agent, RunContext


class Ticket(BaseModel):
    category: Literal["delivery", "payment", "other"]
    order_id: Optional[str]
    needs_human: bool


class Deps(BaseModel):
    order_id: str


agent = Agent(
    os.environ["PYDANTIC_AI_MODEL"],
    deps_type=Deps,
    output_type=Ticket,
    instructions=(
        "Разбери обращение клиента в заявку Ticket. "
        "Если клиент спрашивает срок доставки, а lookup_order вернул eta=null, "
        "поставь needs_human=true и не выводи срок из статуса."
    ),
)


@agent.tool
def lookup_order(ctx: RunContext[Deps]) -> dict:
    """Вернуть статус заказа и срок доставки, если он известен."""
    return {"status": "in_transit", "eta": None}


if __name__ == "__main__":
    result = agent.run_sync(
        "Заказ A-42 не приехал. Когда будет доставка?",
        deps=Deps(order_id="A-42"),
    )
    print(result.output.model_dump_json())

Это учебный пример: модель в нём не вызывалась, ответы спроектированы вручную. Имя модели берётся из переменной окружения PYDANTIC_AI_MODEL; задайте её перед запуском учебного кода. Документация проекта описывает @agent.tool так: функция получает RunContext, несущий зависимости; остальная часть сигнатуры и docstring становятся схемой инструмента, аргументы валидируются до выполнения кода, успешный запуск с валидированным выводом возвращает объявленный тип. Ошибки и исчерпание допустимых повторов приложение должно обрабатывать отдельно. Типовая аннотация инструмента помогает статической проверке и не заменяет проверку логики его кода.

Эталон проверки — три спроектированных ответа на один и тот же вход. Первый: {"category":"delivery","order_id":"A-42","needs_human":true} — схема и заданное правило проходят. Второй: {"category":"refund","order_id":"A-42","needs_human":true} — нарушен только enum категории. Третий: {"category":"delivery","order_id":"A-42","needs_human":false} — форма допустима, но нарушено правило ручной передачи при неизвестном ETA. Все ответы содержат только объявленные поля, сопроводительного текста вне Ticket нет.

Структурная корректность и фактическая правильность

Разберём три ответа по двум разным осям. Схема проверяет форму: набор полей, их типы, допустимые значения перечисления. Отдельное правило приложения связывает needs_human с входным вопросом и результатом lookup_order. Это два независимых слоя проверки, и они ловят разные ошибки.

Ответ Схема Правило приложения Что поймано
Ответ 1 Проходит Проходит Ничего
Ответ 2 Нарушен enum Проходит Ошибка формы
Ответ 3 Проходит Нарушено Ошибка смысла

Второй ответ отклоняется на уровне Pydantic: значение refund отсутствует в перечислении, и валидация не пропустит его дальше. Третий ответ проходит валидацию полностью: все поля на месте, типы совпадают, категория допустима. Ошибку видит только правило приложения, которое знает, что при eta=null и вопросе о сроке заявку нужно передать человеку.

Отсюда практический вывод для читателя. Типизированная структура сама не подтверждает фактическую правильность. Модель может вернуть идеально оформленный объект с неверным решением внутри, и валидация Pydantic промолчит. Правила предметной области остаются на стороне приложения: их пишут отдельно и проверяют отдельно.

Документация проекта на ai.pydantic.dev в разделе про тестирование формулирует ту же границу. Модульные тесты проверяют собственный код вокруг модели: инструменты, зависимости, валидацию вывода и обработку сбоев, без запросов к модели и с одинаковым результатом при каждом запуске. Они не говорят, хороши ли ответы модели; для этого предназначены evals. Это разделение стоит держать в голове при проектировании: тест на форму и тест на смысл — разные задачи с разными инструментами.

Инструменты, зависимости и повтор при сбое

Зависимости в Pydantic AI передаются через RunContext. Документация приводит пример: RunContext параметризован типом зависимости int, и при ошибке в типе зависимости возникает ошибка типизации. Тот же механизм несёт зависимости в инструменты и в код вокруг модели.

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

Для длительных задач документация описывает Pydantic AI Harness: он даёт агенту память, guardrails, подагентов, планирование, управление контекстом и сохранение состояния, вплоть до готового coding-агента. Отдельные открытые пакеты решают смежные задачи: Pydantic Evals оценивает финальные выводы агента и траектории вызовов инструментов на наборах данных или на живом трафике, Pydantic Graph даёт типизированное управление потоком, когда одного цикла агента мало.

Сохранение, возобновление и ветвление запуска документация описывает без движка: StepPersistence из Pydantic AI Harness снимает снимок каждого завершённого шага в выбранное хранилище, как это делает checkpointer. Движок нужен для восстановления запуска, который упал посреди шага. Для долговечного исполнения документация предлагает TemporalDurability: тот же агент работает внутри workflow Temporal, каждый вызов модели и инструмента становится долговечной активностью, и запуск в фоновой очереди переживает перезапуски, сбои и долгие ожидания. DBOS и Prefect подключаются так же, есть интеграции с Restate, AWS Lambda, Airflow, Absurd, pgtask и Azure Durable Task.

Наблюдаемость строится на OpenTelemetry. Документация проекта на ai.pydantic.dev называет Pydantic Logfire платформой, для наблюдения за приложением, включая вызовы LLM, а Pydantic AI Gateway — одним ключом для всех моделей с мониторингом расходов и контролем бюджета в реальном времени; инструментация при этом обычная OpenTelemetry, поэтому подходит любой уже работающий бэкенд. Статья Langfuse про интеграцию с Pydantic AI описывает похожий путь: ключи Langfuse задаются переменными окружения, клиент инициализируется через get_client(), затем включается инструментация Pydantic AI, которая автоматически захватывает операции и экспортирует спаны OpenTelemetry в Langfuse. Там же отмечено, что при корректной инструментации, но отсутствии наблюдений в Langfuse помогает вызов langfuse.flush() в конце приложения и проверка ключей и базового URL.

Документация OpenInference для PydanticAI описывает библиотеку автоинструментации: трассы совместимы с OpenTelemetry и отправляются в коллектор для просмотра, например в Arize Phoenix или Arize AX. Phoenix по умолчанию слушает http://localhost:6006, и та же страница отмечает, что данные не уходят в интернет и обрабатываются локально на машине.

Подмена модели в тестах и случай, где подход не решает задачу

Документация проекта на ai.pydantic.dev в разделе про модульное тестирование предлагает TestModel и FunctionModel вместо реальной модели, чтобы избежать расхода, задержки и изменчивости настоящих вызовов LLM. TestModel по умолчанию вызывает все инструменты агента, затем возвращает простой текст или структурированный ответ в зависимости от типа вывода. Его «умная» часть пытается сгенерировать валидные структурированные данные для функциональных инструментов и типов вывода по схеме зарегистрированных инструментов. Никакого машинного обучения внутри нет: это обычный процедурный Python, который подбирает данные под JSON-схему инструмента. Результат выглядит некрасиво и нерелевантно, но в большинстве случаев проходит валидацию Pydantic; для более сложной логики документация советует FunctionModel с собственным кодом генерации данных.

Отдельное ограничение: TestModel не умеет эмулировать нативные инструменты, выполняемые провайдером. Если в продакшене агент настроен на такие инструменты через capabilities, в тестах документация советует переопределять их вызовом agent.override(model=TestModel(), native_tools=[]), если тест не проверяет именно передачу нативных инструментов модели. Ещё одна мера безопасности — защита от случайных реальных запросов к LLM во время тестирования, описанная в документации через ALLOW_MODEL_REQUESTS.

Теперь случай, где типизированный результат не решает задачу. Возьмём третий спроектированный ответ: {"category":"delivery","order_id":"A-42","needs_human":false}. Схема проходит, поля на месте, перечисление корректно. Приложение получает аккуратный объект и, если у него нет отдельного правила про eta=null, просто закрывает заявку без передачи человеку. Клиент остаётся без ответа о сроке, хотя формально система отработала без ошибок.

Этот пример показывает границу подхода. Типизация ловит расхождения формы и отсекает часть мусора на входе в бизнес-логику. Она не заменяет правило предметной области и не проверяет, соответствует ли решение реальности заказа. Универсального улучшения здесь нет: для каждого поля, связанного со смыслом задачи, правило пишется отдельно и проверяется отдельно.

Ситуация Помогает ли тип результата Что нужно дополнительно
Возвращена категория refund вне enum Валидация отклоняет значение Обработка ошибки или повтора
Возвращена payment вместо delivery Обе категории допустимы по типу Сверка с текстом обращения
needs_human=false при неизвестном ETA bool имеет допустимую форму Явная проверка правила приложения
Верный тип order_id, но другой заказ Строка проходит проверку типа Сверка идентификатора со входом и правами

Что проверить перед внедрением

Этот обзор держится в границах Python-фреймворка: класс Agent, модель, тип результата, зависимости, инструменты, валидация, повтор и подмена модели для тестирования. Базовый Pydantic с его моделями и валидацией здесь служит фундаментом и отдельно не разбирается. Инструменты тестирования агентов заслуживают отдельного материала: здесь они нужны только для того, чтобы показать границу между проверкой формы и проверкой смысла.

Перед внедрением стоит пройти по нескольким пунктам. Проверьте, что выбранная модель поддерживает нужный режим структурированного вывода: по умолчанию фреймворк опирается на вызов инструментов, и для объединений типов каждый вариант регистрируется отдельным инструментом. Убедитесь, что статическая проверка типов настроена: pyright и mypy иногда требуют явно указать обобщённые параметры Agent, и без этого проверяльщик будет жаловаться на верный код.

Отдельно решите, где живут правила предметной области. Схема описывает форму, правило связывает поля со смыслом задачи, и эти два слоя стоит держать раздельно, чтобы ошибку было видно на нужном уровне. Для длительных задач заранее выберите механизм сохранения состояния: StepPersistence снимает снимки завершённых шагов, а движок вроде Temporal нужен для восстановления упавшего посреди шага запуска. Для наблюдаемости подойдёт любая OpenTelemetry-совместимая система: документация проекта называет Logfire, статья Langfuse описывает свой путь, документация OpenInference — Phoenix на локальном порту.

Частые вопросы

Что такое PydanticAI простыми словами?
Это Python-фреймворк для агентов, где модель возвращает данные объявленного типа. Документация проекта на ai.pydantic.dev описывает его как Python AI SDK с типизированным расширяемым циклом агента. Агент собирается из модели, инструкций, инструментов, зависимостей и типа результата. Форму ответа проверяет Pydantic, а правила предметной области остаются в приложении.

Почему схема пропустит ошибочное решение о передаче человеку?
Поле needs_human имеет тип bool, поэтому и true, и false допустимы по форме. В учебном обращении клиент спрашивает срок, а lookup_order возвращает eta=null. Условие передачи человеку должно проверяться отдельным правилом: needs_human=true. Третий спроектированный ответ нарушает это правило при корректной структуре.

Инструкция модели уже обеспечивает проверку бизнес-правила?
Инструкция сообщает модели требуемое поведение, но результат всё равно нужно проверить в приложении. В учебном коде lookup_order возвращает фиксированные данные заглушки. Настоящий заказ не запрашивается. Все три ответа спроектированы вручную. Перед рабочим использованием нужно реализовать проверку правила и обработку ошибочного результата.

Как тестировать агента без реальных запросов к модели?
Документация проекта на ai.pydantic.dev советует TestModel и FunctionModel вместо настоящей модели. TestModel вызывает все инструменты и возвращает текст или структурированный ответ по типу вывода, подбирая данные под JSON-схему процедурным кодом. Нативные инструменты провайдера он не эмулирует, и в тестах их переопределяют через agent.override(model=TestModel(), native_tools=[]).

Что даёт Pydantic Graph и когда одного агента мало?
Документация проекта на ai.pydantic.dev описывает Pydantic Graph как типизированное управление потоком для случаев, когда одного цикла агента недостаточно. Это отдельный открытый пакет, как и Pydantic Evals для оценки выводов и траекторий вызовов инструментов. Один агент способен обслуживать всё приложение или компонент, а несколько агентов взаимодействуют в более сложных сценариях.

Читайте также

3 материала