Инструменты и зависимости: руки агента
Модель умеет только писать текст. Инструменты дают ей возможность действовать: искать, считать, работать с базой.
Что такое инструмент
Инструмент (tool) — обычная Python-функция, которую ты «показываешь» агенту. Модель сама решает, когда её вызвать и с какими аргументами, получает результат и продолжает рассуждать. Так агент из «говорилки» превращается в исполнителя.
Регистрация — просто пометка над функцией:
import random
from pydantic_ai import Agent, RunContext
agent = Agent('google:gemini-3-flash-preview', deps_type=str)
@agent.tool_plain # простой инструмент, контекст не нужен
def roll_dice() -> str:
"""Бросить шестигранный кубик."""
return str(random.randint(1, 6))
@agent.tool # инструмент с доступом к контексту запуска
def get_player_name(ctx: RunContext[str]) -> str:
"""Узнать имя игрока."""
return ctx.deps
Пример из документации по tools. Два декоратора: @agent.tool_plain — без контекста, @agent.tool — с контекстом.
Откуда модель знает, как пользоваться функцией
Pydantic AI автоматически собирает «паспорт» инструмента из самого кода: имена и типы аргументов берутся из сигнатуры функции, а описания — из docstring (комментария-описания). Модель получает аккуратную схему и понимает, что делает функция и что ей передавать.
Практический вывод: понятные имена функций и внятные описания — это не «красота кода», а буквально качество работы агента. Модель читает их как инструкцию.
Зависимости: как агент получает доступ к твоей системе
Инструментам почти всегда нужно что-то внешнее: API-ключ, подключение к базе, id текущего пользователя. Передавать это через глобальные переменные — путь к хаосу. Pydantic AI предлагает зависимости (dependencies): аккуратный контейнер, который отдаётся агенту при каждом запуске.
from dataclasses import dataclass
import httpx
from pydantic_ai import Agent, RunContext
@dataclass
class MyDeps:
api_key: str
http_client: httpx.AsyncClient
agent = Agent('openai:gpt-5.2', deps_type=MyDeps)
@agent.tool
async def get_joke_material(ctx: RunContext[MyDeps], subject: str) -> str:
response = await ctx.deps.http_client.get(
'https://example.com#jokes',
headers={'Authorization': f'Bearer {ctx.deps.api_key}'},
)
return response.text
# при запуске передаём реальные объекты:
result = await agent.run('Расскажи шутку', deps=MyDeps(key, client))
Внутри любого инструмента контекст ctx.deps — это твой контейнер. Пример из документации по dependencies.
Зачем такие сложности? Ради тестов
Главный выигрыш зависимостей — их можно подменить. В тестах вместо боевой базы подставляется заглушка, и вся логика агента проверяется бесплатно и безопасно:
with joke_agent.override(deps=test_deps): # подменяем на время теста
result = await application_code('Tell me a joke.')
В связке с TestModel из урока 2 получается полноценный автотест агента: ни одного реального вызова нейросети и базы, а логика проверена.
Картина целиком
Один запуск агента с инструментами выглядит так:
- Твой код запускает агента с вопросом и зависимостями.
- Модель читает инструкции и список инструментов.
- Решает: «мне нужно проверить базу» — вызывает инструмент.
- Инструмент выполняет настоящий код и возвращает результат модели.
- Шаги 3–4 повторяются, пока модель не соберёт всё нужное.
- Модель формирует финальный ответ в формате
output_type— Pydantic AI его валидирует.
Весь этот цикл фреймворк ведёт сам. Ты пишешь только функции и структуру результата.