PydanticAI

PydanticAI — Python-библиотека на type hints, встраиваемая в агентный пайплайн как типизированный слой между кодом и LLM. Берёт на себя валидацию вывода, зависимости и retry-логику, оставляя бизнес-логику чистой. Главный сдвиг: от «промпта-инструкции» к схеме-контракту.

Суть

Выход агента — не строка и не словарь, а объект Python с гарантированными типами. Описываем ожидаемую структуру классом BaseModel; библиотека сама генерирует из него JSON Schema, передаёт модели, валидирует ответ и при провале отправляет осмысленный retry.

Зачем это нужно

Ручной разбор ответа LLM (json.loads() + data.get()) надёжен лишь на 80–95% — оставшиеся 5–20% тихо падают в продакшене. Пять типовых поломок JSON: тип вместо ожидаемого ("three" вместо 3), markdown-обёртка, пропуск обязательного поля, лишние поля, вложенный объект строкой. Цена одной такой ошибки в кейсе: поле amount пришло как "$1,200.50" → квартальный отчёт не сошёлся → 3 дня аудита × 4 человека = $8 400.

Как работает

Четыре механизма, каждый раскрыт в отдельной заметке:

  • Structured Output — BaseModel как контракт: схема физически ограничивает генерацию (constrained decoding), типы гарантированы. ValidationError бросается сразу с точным путём к проблеме.
  • Schema Guided Reasoning — порядок полей схемы направляет рассуждение модели.
  • Agent Deps — секреты, права, БД-сессии передаются через типизированные зависимости (ctx.deps), а не через промпт.
  • Validation Loops — при провале валидации ModelRetry возвращает модели точное описание ошибки для самоисправления.
  • Field(description=...) становится частью JSON Schema → описание формата уходит из промпта в саму схему.
  • Наблюдаемость: result.usage (токены/стоимость), ctx.retry (номер попытки), интеграция с Pydantic Logfire.

Пример

from pydantic import BaseModel, Field
from pydantic_ai import Agent

class CityInfo(BaseModel):
    name: str = Field(description="Название города")
    population: int = Field(ge=0, description="Население")

agent = Agent("openai:gpt-4o", output_type=CityInfo)
result = await agent.run("Берлин")
result.output.population  # уже int, не строка

Связано с

  • Structured Output — ядро: схема как контракт и constrained decoding
  • Schema Guided Reasoning — схема направляет рассуждение
  • Agent Deps — контекст и секреты без промпта
  • Validation Loops — автоисправление через ModelRetry
  • Tool Calling — PydanticAI типизирует и аргументы инструментов
  • Agent Anatomy — типизированный слой поверх «мозга» и «рук» агента

Открытые вопросы

  • PydanticAI vs другие фреймворки (LangGraph) — когда что
  • накладные расходы типизированного слоя на латентность/стоимость