Agent SDK: что вы покупаете
Этот блок раскладывает инструменты вокруг цикла агента по тому, что каждый берёт на себя, и показывает, чего за вас не делает ни один из них.
Northwind хочет агента-сортировщика, который передаёт дела о возвратах специалисту. Один инженер предлагает OpenAI Agents SDK, другой Claude Agent SDK, третий FastMCP. Спорят они о трёх разных вещах.
Телефонистка на коммутаторе
До сих пор на звонки коллеги вы отвечали сами. Теперь представьте, что наняли телефонистку. Она читает вслух блокнот, выполняет просьбы из утверждённого вами списка, переводит звонки в другие отделы и записывает каждый разговор. Список и правила о том, что можно делать, остаются вашими, а решена ли проблема покупателя, она не знает. Она знает только, что разговор закончился.
Agent SDK — это такая телефонистка. Проследим за одним tool call и посмотрим, какой продукт где стоит.
Инструмент | Слой | Что берёт на себя |
|---|---|---|
OpenAI Agents SDK | Цикл в вашем процессе | Ходы, запуск инструментов, handoff, guardrail, сессии, трейсинг |
Claude Agent SDK | Harness: библиотека, которая запускает бинарник Claude Code | Цикл плюс встроенные инструменты для файлов и shell, разрешения, хуки, сессии |
Google ADK, Vercel AI SDK | Цикл в вашем процессе, как в первой строке | Цикл и управление контекстом, у каждого через свой API |
FastMCP | Сервер и клиент MCP | Схема из вашей функции, валидация, транспорт, авторизация |
mcp-use | Агент и клиент поверх MCP | |
Термины ещё не устоялись. В документации LangChain Agents SDK назван фреймворком, а Claude Agent SDK — harness, хотя в Agents SDK теперь тоже есть sandbox-агенты с настоящим рабочим окружением.
Прежний цикл, переданный SDK
Вот агент Northwind на OpenAI Agents SDK. Мы запускали этот код на версии 0.24.0 с моделью-заглушкой.
async def over_limit(ctx, params, call_id) -> bool:
return params["amount_cents"] > 20000
@function_tool(needs_approval=over_limit)
def issue_refund(ctx: RunContextWrapper[Session], order_id: str,
amount_cents: int) -> str:
"""Refund part or all of an order."""
return refunds.issue(ctx.context.customer_id, order_id, amount_cents)
refunds_agent = Agent(name="Refunds", instructions=REFUND_RULES,
tools=[issue_refund])
triage = Agent(name="Triage", instructions=TRIAGE_RULES,
handoffs=[refunds_agent])
result = await Runner.run(triage, "Order 4417 arrived broken",
context=session, max_turns=8)Runner.run — это ваш цикл for. Он вызывает модель, запускает инструменты и дописывает результаты. По умолчанию max_turns равен 10, а при превышении выбрасывается MaxTurnsExceeded, который ваш код должен поймать или передать в error_handlers.
Схема берётся из сигнатуры. SDK читает аннотации типов и docstring. Первый параметр с типом RunContextWrapper он в схему не включает, поэтому личность покупателя по-прежнему приходит из вашей сессии и модель её задать не может.
Docstring читает модель, так что относитесь к нему как к промпту. FastMCP делает то же самое на стороне сервера. Вот настоящая запись tools/list с сервера FastMCP 4.1.7, с сокращениями:
{
"name": "lookup_order",
"description": "Look up one order. Use the number the customer gave, digits only.",
"inputSchema": {
"type": "object",
"properties": {
"order_id": {
"type": "string"
}
},
"required": [
"order_id"
]
}
}Пауза — это сохранённая строка. Когда over_limit вернул True, прогон останавливается, а в result.interruptions лежит этот вызов. Вы сохраняете result.to_state().to_string(), потом загружаете через await RunState.from_string(triage, saved, context_override=session), вызываете state.approve(...) и запускаете снова. Когда мы убрали context_override, продолженный инструмент упал при чтении покупателя. SDK записал предупреждение в лог, но до нашего кода исключение не дошло, модель просто получила An error occurred while running the tool. Please try again.
У Claude Agent SDK другая форма, потому что у него есть собственные руки. Он подходит для задачи вроде починки упавшего теста в репозитории Northwind, а для чата с покупателем подходит хуже.
options = ClaudeAgentOptions(
allowed_tools=["Read", "Glob", "Grep", "Bash(pytest *)"],
permission_mode="acceptEdits",
max_turns=30,
max_budget_usd=2.0,
)
async for message in query(prompt="Fix the failing tests in refunds/",
options=options):
if isinstance(message, ResultMessage):
print(message.subtype, message.total_cost_usd)Действовать ему позволяют allowed_tools и permission_mode: в режиме по умолчанию вызов инструмента, которому нужно одобрение, отклоняется, если его не покрывает правило и вы не дали callback. На версии 0.2.180 мы убрали эти два параметра и запустили тот же промпт. Edit был отклонён, файл не изменился, а подтип результата всё равно оказался success. Отклонённые вызовы перечислены в permission_denials.
Без ограничений он работает, пока Claude не закончит сам. При достижении max_turns у результата будет подтип error_max_turns, при достижении max_budget_usd — error_max_budget_usd, а query() после такого результата выбрасывает исключение.
Перевести или посоветоваться
По телефону можно перевести покупателя в отдел возвратов, и дальше он говорит с ними. А можно попросить его подождать, самому спросить отдел возвратов и вернуться с ответом.
Handoff выбирайте, когда специалист должен отвечать сам, а промпты должны оставаться узкими. Agent.as_tool() выбирайте, когда итоговый ответ должен принадлежать одному агенту или нужно объединить нескольких специалистов.
Где на самом деле работают проверки
Guardrail звучит как проверка всего подряд. В Agents SDK каждый вид запускается в определённых точках.
Input guardrail по умолчанию идёт параллельно с агентом, поэтому агент мог уже вызвать инструменты к моменту, когда guardrail остановит прогон. Чтобы сначала проверять, поставьте run_in_parallel=False. Tool guardrail не покрывает handoff и инструменты на стороне OpenAI вроде WebSearchTool. При needs_approval input guardrail инструмента по умолчанию запускается уже после одобрения.
По умолчанию OpenAI Agents SDK и Claude Agent SDK завершают цикл, когда модель отвечает без tool call. В документации OpenAI написано, что цикл идёт, пока задача не выполнена ("continues until the task is complete"), но правило для финального вывода там такое: текст без tool call. Что возврат состоялся, никто не подтвердил, как и показывал блок о ложном успехе.
Чем вы платите
Anthropic ещё в 2024 году предупреждала: слои фреймворка могут скрывать промпты и ответы и усложнять отладку. OpenAI сама проводит границу в документации: берите Responses API напрямую, если хотите владеть циклом или задача короткая и сводится к ответу модели, и SDK, если хотите отдать рантайму ходы, инструменты, guardrail, handoff или сессии.
Вы также берёте на себя темп релизов. За 12 месяцев до 10 ноября 2026 на PyPI вышло 84 релиза openai-agents, всё ещё в ветке 0.x, 144 релиза claude-agent-sdk и 74 релиза fastmcp, считая патчи, а у fastmcp ещё и 19 предварительных версий. Фиксируйте версии и читайте changelog перед обновлением.
Вывод
- Прежде чем сравнивать инструменты, спросите, какой слой занимает каждый.
- Начинайте с обычного цикла. Переходите, когда ходы, handoff, сессии и трейсинг начинают повторяться в вашем коде.
- Знайте, где работает каждый guardrail, и проверяйте результат сами: цикл знает только то, что модель перестала просить инструменты.