JSON-структуры OpenAI Agents API: как связаны Agent, Tool Call, Tool Result и Handoff (2026)
После публичной беты трудность не в том, «умеет ли звать инструмент». А в том, как четыре объекта сидят на одной цепи. Item типа handoff нет. Скрепки — ID: session_id, turn_id, call_id, agent_id. Этот текст только разбирает эту JSON-цепь.
Перед чтением
| Объект | Официальная форма | Скрепка |
|---|---|---|
| Agent | Модель, таблица инструментов, multi_agent | Пишется при создании session |
| Tool Call | function_call в required_actions | turn_id + call_id |
| Tool Result | agent.session.input.tool_result | Та же пара назад |
| Handoff | items create / send / wait / interrupt | agent_id / subagent_id |
function_call в истории не значит, что результат ещё ждут. Ожидание — в required_actions.
Четыре объекта сходятся на ID, не на абзаце
В Agents API Agent — конфигурация при создании session. Tool Call — function_call в required_actions, когда session остановилась. Tool Result — agent.session.input.tool_result в поток событий. «Handoff» здесь — набор координационных items, не объект handoff из SDK.
- Та же пара turn_id / call_id: вызов и результат должны сойтись. Иначе harness не продолжит.
- Истории мало: function_call в items значит лишь, что вызов был. Ещё ждут? Читать
required_actions. - Handoff — не type: смотреть
create_subagent_callиsubagent_id. Не искать поле с именем handoff.
Как зовут инструменты: Разбор OpenAI Agents API. Как продолжается длинная задача: Долгий Agents API. Кому держать цикл: Agents API vs Responses API. Если Agent и Function Calling ещё одно слово — сначала Полный гид по AI-агенту.
Agent: объект конфигурации, не ответ
Создание пишет модель, инструкции, инструменты и multi_agent один раз. Этот объект — подлежащее всех следующих items. Официально: POST /v1/agents/sessions с OpenAI-Beta: agents=v1:
{
"agent": {
"model": "gpt-6-astra",
"instructions": "Look up orders. Confirm before any refund.",
"tools": [
{
"type": "function",
"name": "lookup_order",
"parameters": {
"type": "object",
"properties": {
"order_id": { "type": "string", "pattern": "^[A-Z]-\\d{4}$" }
},
"required": ["order_id"],
"additionalProperties": false
}
}
],
"multi_agent": { "enabled": true, "max_concurrent_subagents": 3 }
},
"environment": { "type": "openai_hosted" },
"input": [{ "role": "user", "content": [{ "type": "input_text", "text": "Look up A-1042." }] }]
}
Конфиг Agent остаётся с session. Смена модели или силы рассуждения обновляет эту session; сохранённый шаблон действует только на новые session. Среда — отдельный ресурс: openai_hosted или ваша песочница. Удаление session не чистит sandbox.
Tool Call: верить required_actions
Когда нужен результат функции, session шлёт agent.session.requires_action. Ожидающие вызовы — в required_actions как function_call с turn_id / call_id:
{
"type": "function_call",
"turn_id": "turn_2",
"call_id": "call_3c91",
"name": "lookup_order",
"arguments": { "order_id": "A-1042" }
}
Аргументы должны пройти Schema. Широкий object — не дверь. См. Зачем ИИ нужен JSON Schema. В истории items тоже может быть function_call; официально: одна история не доказывает, что результат ещё ждут.
Tool Result: та же пара в поток событий
После выполнения отправьте agent.session.input.tool_result на endpoint событий. Скопируйте turn_id и call_id из ожидающего действия. Успех: success: true и строковый output. Ошибка: success: false и error:
{
"type": "agent.session.input.tool_result",
"turn_id": "turn_2",
"call_id": "call_3c91",
"success": true,
"output": "{\"order_id\":\"A-1042\",\"status\":\"paid\"}"
}
Объекты сначала сериализовать в строки. Если функция уже отработала до обрыва — снова сдать сохранённый результат той же парой. Не открывать новую session как «повтор». Harness продолжит turn только когда придут нужные результаты.
Handoff: координационные items, не имя поля
С multi_agent.enabled harness даёт create, message, wait и interrupt. Эти инструменты не объявляете. Координация видна как items:
{
"data": [
{
"type": "create_subagent_call",
"agent_id": "agent_main",
"task": "Compare A-1042 packing notes with the invoice."
},
{
"type": "send_subagent_input_call",
"subagent_id": "sub_7a12",
"input": "Focus on line items only."
},
{
"type": "agent_message",
"from": "sub_7a12",
"text": "Invoice lines match packing notes for A-1042."
}
]
}
- create_subagent_call: отправка.
agent_id— кто запросил. Готово не значит, что подзадача кончилась. - send / wait / interrupt: сказать, ждать, остановить.
agent_messageможет нести видимый меж агентный текст; поток — не полная стенограмма. - Объект handoff в SDK: живёт в вашем процессном runner. Это не тот же ресурс, что эти items.
То, что вы отдаёте следующему подлежащему, должно быть проверяемым JSON: номер заказа, намерение, основание. Как уложить в Schema: JSON Schema и рабочие процессы агента.
Цепь держится, только если скрепки совпали
Сложить четыре объекта в цепь, которую можно собрать локально. Вымышленный заказ — без настоящих секретов:
{
"session_id": "sess_8f21",
"links": [
{ "object": "agent", "id": "agent_main" },
{ "object": "turn", "id": "turn_2" },
{ "object": "function_call", "call_id": "call_3c91" },
{ "object": "tool_result", "call_id": "call_3c91" },
{ "object": "subagent", "id": "sub_7a12", "via": "create_subagent_call" }
]
}
| Объект | Тип, который видите | Приколото к | Обычный сдвиг |
|---|---|---|---|
| Agent | session.agent | session_id | Новая session как «повтор» |
| Tool Call | function_call | turn_id + call_id | Читать только историю items |
| Tool Result | input.tool_result | Та же пара | output ещё объект, не строка |
| Handoff | create_subagent_call | agent_id / subagent_id | Искать поле с именем handoff |
| Turn | turn.completed / failed | turn_id; у корня subagent_id пуст | Простой читать как успех |
Строка MCP — ещё одна таблица инструментов. Вторую схему ID она не изобретает. Граница: MCP Tool Schema vs Function Calling.
Разбирать цепь — разложить два JSON-следа
Большинство сдвигов — плывущие скрепки, не более глупая модель.
- формат required_actions рядом с событием записи назад.
call_idдолжен совпасть буква в букву. - извлечь JSONPath вытащить
turn_id,call_id,subagent_id. - JSON Diff вызов и результат одного заказа. Поплыло — сначала ID.
- Items главного агента и субагента — отдельными списками. В одной странице координация прилипнет не к тому.
- Не вставлять заказы, session и внутренний MCP в публичный форматтер. JSONSort работает в браузере локально.
Если оба следа собираются по ID, вы читаете структуру. Иначе — лозунги.
Частые ловушки
- Считать function_call в items ещё ожидающим. Ожидание — required_actions.
- Писать назад другую пару ID. Harness не сведёт; turn встанет.
- Отправить output сырым объектом. Официально нужна строка или поддерживаемый массив content.
- Искать type: handoff в payload. Agents API использует create / send / wait / interrupt.
- Отдать function субагенту. Субагенты не поддерживают function-инструменты. Поиск заказа — у главного агента.
- Считать SDK handoff и items session одним ресурсом. Удаление одного не чистит другое.
FAQ
У каждого Tool Call должен быть Tool Result?
Каждый ожидающий вызов нужно записать назад. Исторический function_call, который уже кончился, второго результата не требует. Критерий — required_actions, не длина списка items.
Есть ли в Agents API JSON-поле с именем handoff?
Не как первоклассный item type. Координация: create_subagent_call, send_subagent_input_call, wait_for_subagents_call, interrupt_subagent_call. Handoff в SDK — другой объект.
Чем call_id отличается от item_id?
call_id скрепляет вызов функции с её результатом. item_id именует сохранённый item. Поток вешает дельты текста на ту же часть через item_id. Две скрепки — не смешивать.
Как turn может быть completed, если инструмент упал?
Официально: completed не значит, что каждый инструмент успешен. Смотреть также success у tool_result и items этого turn.
Можно ли завести свой handoff JSON в приложении?
Можно, и нужно. Это ваш деловой объект: номер заказа, намерение, основание. Не ждите, что официальные items запомнят суммы. Пишите Schema и храните отдельно от items session.
Вывод
Agent, Tool Call, Tool Result и Handoff — не четыре абзаца. Это четыре объекта на одной JSON-цепи, скреплённые ID. Официальная запись назад — событие; официальная передача — координационный item.
Сначала запишите, сведёте ли вызов и результат одним call_id. Если да, структура держится. Модели сменятся; скрепки должны устояться раньше.
Дальше
- OpenAI Agents API, полный разбор Как стыкуются Session, tool calling и Schema
- Долгий Agents API Session, compact, субагенты
- Agents API vs Responses API Кто должен крутить цикл
- Зачем ИИ нужен JSON Schema Schema / Function Calling / Structured Output
- JSON Schema и рабочие процессы агента Уложить инструменты и состояние в Schema
- Локальный ящик JSONSort Формат, проверка, Diff
- Все статьи Ещё заметки про JSON и ИИ
Журнал изменений: Первая публикация