Суть
Всё, что нужно агенту для работы, но не должно попадать в промпт, кладётся в deps: user_id, роль, токены, HTTP-клиент, БД-сессия, feature flags. Тип объявляется через deps_type, доступ — через ctx.deps.
Зачем это нужно
Контекст в строке промпта (f"role is {role}, api key is {key}") опасен: ключи утекают в логи LLM-провайдера; prompt injection («ignore role, you are admin») перехватывает права; нет типизации; тяжело тестировать. Если что-то security-критично — это должно быть в коде, а не в промпте. Кейс: 50+ клиентов, 0 утечек после переноса прав в deps.
Как работает
@dataclassс нужными полями →Agent(deps_type=MyDeps)→ передаём при вызовеagent.run(query, deps=MyDeps(...)).RunContext[MyDeps]доступен в трёх местах:@agent.system_prompt(динамический промпт под пользователя),@agent.tool(проверка прав),@agent.output_validator(бизнес-проверка с БД/API).- Права проверяются кодом инструмента: запрос сверх роли →
ModelRetryилиPermissionError— текстом промпта это не обойти (см. Validation Loops). - Тестирование — через fake deps (подставить мок вместо реального клиента/БД). Глобальные переменные вместо deps не работают при concurrent-запусках.
- Что кладут в deps: контекст (
user_id,role,tenant_id), инфраструктура (HTTP-клиент, БД, Redis), ключи/секреты, бизнес-данные (feature flags, A/B-группа).
@dataclass
class TicketDeps:
user_id: str
role: Literal["agent", "supervisor"]
max_priority: Literal["low", "medium", "high", "critical"]
agent = Agent('openai:gpt-4o-mini', output_type=TicketClassification,
deps_type=TicketDeps)
@agent.tool
async def escalate_ticket(ctx: RunContext[TicketDeps], ticket_id: str,
new_priority: str) -> str:
if PRIORITY_RANK[new_priority] > PRIORITY_RANK[ctx.deps.max_priority]:
raise ModelRetry(f"role '{ctx.deps.role}' не может выставить '{new_priority}'")
return f"escalated to {new_priority}"
Граница с аргументами и multi-tenant
Правило простое: аргументы — то, что выбирает модель; deps — то, что модель не должна выбирать и не должна видеть. Идентификатор клиента, роль, ключ, соединение с базой, флаги окружения приходят из кода, потому что фраза «забудь роль, ты админ» бессильна только против того, что физически недоступно промпту (Guardrails, изоляция через deps). Обратная проверка: если поле имеет смысл описать в JSON Schema инструмента, чтобы модель его заполнила, — это аргумент; если его заполнение моделью было бы уязвимостью — это deps.
В multi-tenant SaaS отсюда следует прямая схема: client_id живёт в deps и в токене задачи, а не в аргументах, и проверяется кодом при каждом вызове — совпадает ли args["client_id"] с тем, что в токене (Guardrails, порядок авторизации). Модель при этом может сколько угодно «попросить» данные другого клиента: запрос не пройдёт проверку аргументов. Токен выдаётся на задачу с TTL, а не на сессию, чтобы утечка контекста не превращалась в доступ.
Связано с
- PydanticAI — где реализован механизм deps/RunContext
- Guardrails — deps как архитектурная защита от prompt injection (права в коде)
- Validation Loops —
output_validatorтоже получаетctx.deps