Блог • Open Responses

После входа Open Responses в AAIF: какие проверки type, arguments и status нужны перед повтором tool-call item?

22 октября 2026 Agentic AI Foundation приняла Open Responses как проект. Спека складывает ход в item: message, function_call и function_call_output — переносимое ядро; type с префиксом провайдера принадлежит этой реализации.

Перед отправкой item из журнала другой модели проверьте три поля локально. Двоеточие в type значит, что это не переносимый function_call. В примерах спеки arguments — JSON-строка: сначала parse, затем JSON Schema инструмента. Если status не completed, не добавляйте function_call_output.

Вчерашний текст structured outputs Bedrock про форму текста модели. В Chat Completions arguments тоже часто строка, но конверт другой — см. AI Agent и JSON Schema. Прохождение schema не разрешает выполнение — см. решение о правах. Эта статья только про item Open Responses.

Сначала это: AAIF приняла общую форму item. Одна JSON-запись всё ещё может принадлежать одному провайдеру. Дальше по порядку: type, arguments, status и какое поле останавливает повтор.

22 октября общий item перешёл под открытое управление

На AGNTCon и MCPCon AAIF объявила четыре проекта. Open Responses от OpenAI стоит рядом с MCP и A2A под управлением Linux Foundation. Анонс: анонс AAIF. В тот же день вошли Agent Skills от Anthropic, OpenEnv от Hugging Face и OpenSandbox от Alibaba. Здесь не каталоги и не формат skill, только JSON item Open Responses.

В тот же день пост AWS Open Source написал, что Amazon Bedrock поддерживает Responses API и участвует в спеке. См. пост AWS Open Source. Смысл полей — по спецификации Open Responses. Тела без стриминга — application/json, item различаются по type. Вопрос: function_call уже в журнале; какие три поля должны остановить следующий запрос перед сменой провайдера или повтором на следующий день.

Какие поля минимум нужны replayable function_call?

function_call для следующей проверки несёт как минимум type, id, name, call_id, status и arguments. Значение arguments — строка, параметры инструмента внутри. Ниже region равен apac и снова проверяется той же schema.

function_call для следующих проверок. arguments — строка.
{
  "type": "function_call",
  "id": "fc_001",
  "name": "get_latest_sales_report",
  "call_id": "call_123",
  "status": "completed",
  "arguments": "{\"region\":\"apac\"}"
}

После выполнения инструмента у вас следующий запрос добавляет function_call_output, не массив tool_calls из Chat Completions. Пример продолжения в спеке сохраняет тот же call_id и output. Если сервер вернёт результат как item, тогда появятся id и status.

function_call_output следующего запроса, тот же call_id.
{
  "type": "function_call_output",
  "call_id": "call_123",
  "output": "sales report for apac"
}

Почему двоеточие в type не даёт выполнить вызов у другого провайдера?

Спека разрешает свои item, но type обязан нести префикс slug провайдера, например openai:web_search_call. Запись может остаться в исходной сессии и вернуться тому же провайдеру. Переносимость на другой сервер Open Responses не предполагается. Локально: двоеточие значит namespaced, function_call_output не создаётся.

Item с префиксом провайдера. Не выполняйте снова как get_latest_sales_report.
{
  "type": "openai:web_search_call",
  "id": "ws_001",
  "status": "completed",
  "action": { "type": "search", "query": "apac sales report" }
}
Видимый typeПовтор у другого провайдера?Локальное действие
function_callДальше к следующей проверкеДальше arguments и status.
messageМожно положить в inputКак текст диалога, не как результат инструмента.
openai:web_search_callНе переносимоОставить у исходного провайдера или вынуть из общего журнала.

Почему arguments сначала строка, и только потом schema?

Событие response.function_call_arguments.delta дописывает текст до готовности объекта. Сохранённый arguments — строка, не объект. Журнал с объектом в arguments упадёт на следующем parse или будет отклонён другой реализацией. Сначала убедитесь, что это string, затем json.loads, затем JSON Schema инструмента. Schema ниже допускает только apac, emea и amer.

Schema параметров. region только apac, emea или amer.
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "required": ["region"],
  "properties": {
    "region": {
      "type": "string",
      "enum": ["apac", "emea", "amer"]
    }
  }
}

Строка ниже разбирается, но region равен global и его нет в enum. Шлюз должен вернуть arguments_schema, инструмент не выполняется.

Parse успешен, schema нет. Не угадывайте регион.
{
  "type": "function_call",
  "id": "fc_009",
  "name": "get_latest_sales_report",
  "call_id": "call_129",
  "status": "completed",
  "arguments": "{\"region\":\"global\"}"
}
Видимый argumentsРезультат шлюзаЛокальное действие
Строка, в объекте region равен apacokВыполнить инструмент, затем записать function_call_output.
arguments сам является объектомarguments_typeОтклонить. Сначала вернуть строку, потом parse.
Строка разбирается, но region равен globalarguments_schemaНе выполнять. Вернуть ошибку проверки, регион не угадывать.

Почему не писать результат инструмента, если status не completed?

Спека делает каждый item конечным автоматом. in_progress значит, вывод ещё идёт. incomplete терминален, обязан быть последним item этого ответа, и сам response тоже incomplete. После completed item больше не меняется. При incomplete arguments может быть обрезан; неполный JSON выполнять нельзя.

statusПисать function_call_output?
in_progressНет. Ждать конца потока.
incompleteНет. Терминально, аргументы могут быть обрезаны.
completedДа. Затем: в type нет двоеточия, arguments проходит schema, call_id совпадает со следующим item.
Любое другое значение, включая failedНет. Побочный эффект не выполнять.

Шлюз ниже — тот же код, который проверяет образцы этой статьи. Он возвращает причину по порядку: двоеточие, не function_call, status, тип arguments, parse, schema. Только ok позволяет выполнить инструмент и добавить function_call_output.

Локальный шлюз перед повтором. Всё, кроме ok, инструмент не выполняет.
def actual_type(data: object) -> str:
    if data is None:
        return "null"
    if isinstance(data, bool):
        return "boolean"
    if isinstance(data, list):
        return "array"
    if isinstance(data, int):
        return "integer"
    if isinstance(data, float):
        return "number"
    if isinstance(data, str):
        return "string"
    if isinstance(data, dict):
        return "object"
    raise TypeError(type(data))

def validate(data: object, schema: dict) -> list[str]:
    errors: list[str] = []
    if not isinstance(schema, dict):
        return errors
    types = schema.get("type")
    if isinstance(types, str):
        types = [types]
    actual = actual_type(data)
    if types and not any(t == actual or (t == "number" and actual == "integer") for t in types):
        errors.append("type")
    if "enum" in schema and not any(value == data for value in schema["enum"]):
        errors.append("enum")
    if isinstance(data, dict) and schema.get("properties"):
        for key in schema.get("required") or []:
            if key not in data:
                errors.append(f"required:{key}")
        if schema.get("additionalProperties") is False:
            for key in data:
                if key not in schema["properties"]:
                    errors.append(f"additional:{key}")
        for key, sub in schema["properties"].items():
            if key in data:
                errors.extend(validate(data[key], sub))
    if isinstance(data, list) and schema.get("items"):
        for item in data:
            errors.extend(validate(item, schema["items"]))
    return errors

def replay_gate(item: dict, schema: dict) -> str:
    item_type = item.get("type")
    if not isinstance(item_type, str) or ":" in item_type:
        return "namespaced"
    if item_type != "function_call":
        return "not_function_call"
    if item.get("status") != "completed":
        return "status"
    arguments = item.get("arguments")
    if not isinstance(arguments, str):
        return "arguments_type"
    try:
        parsed = json.loads(arguments)
    except json.JSONDecodeError:
        return "arguments_parse"
    if validate(parsed, schema):
        return "arguments_schema"
    return "ok"

Как разобрать три поля в JSONNote?

  1. 1
    Сначала: arguments — строка?

    Откройте JSON Формат. Вставьте item целиком. Если arguments раскрывается в объект, а не в одну строку, форма хранения уже неверна, и шлюз вернёт arguments_type.

  2. 2
    Затем проверить разобранные параметры

    Объект из строки arguments вставьте в JSON Schema. Schema — enum region сверху. global будет отмечен; не меняйте его на apac до выполнения.

  3. 3
    В конце сравните item двух провайдеров

    Откройте JSON Diff. Слева item исходного провайдера, справа тот, что собираетесь повторить. Если type сменился с function_call на расширение с двоеточием или call_id не совпал, следующий запрос не отправляйте.

Вопросы

После входа Open Responses в AAIF тот же JSON можно сразу отдать Bedrock или другой реализации?

Нет. Переносимое ядро — message, function_call и function_call_output. Item с префиксом провайдера не считается повторяемым на другом сервере. Structured outputs Claude в Bedrock — другой слой: JSON Schema текста модели, не этот конверт item.

Почему arguments не хранят объектом?

В примерах спеки arguments хранится JSON-строкой. Поток дописывает её через function_call_arguments.delta, и до completed строка может быть неполной. Не разбирайте раньше времени и не выполняйте.

Если status равен incomplete, можно ли всё же выполнить инструмент?

Нет. incomplete терминален. Спека требует, чтобы это был последний item ответа и сам response был incomplete. Обрезанный arguments обычно не проходит schema; даже если случайно пройдёт, не считайте его законченным вызовом.

Если allowed_tools уже ограничивает имена инструментов, нужна ли локальная schema?

Да. allowed_tools ограничивает, какие имена могут реально выполниться в этом запросе; определения могут целиком оставаться в tools. Он не проверяет, есть ли region внутри arguments в enum. Разрешённое имя всё равно требует parse и schema.

Можно ли класть tool_calls из Chat Completions в один массив с item Open Responses?

Нет. Open Responses использует type, call_id и arguments-строку. Chat Completions использует массив tool_calls. В одном журнале повтор отправит неверный конверт следующему провайдеру. Сравните два документа отдельно через Diff.

Итог

После входа Open Responses в AAIF провайдеры делят форму item, а не каждую строку журнала.

Решение: двоеточие в type значит, что это не переносимый function_call; сначала подтвердите, что arguments — строка, затем schema инструмента; если status не completed, function_call_output не пишите.

Дальше: форматированием проверьте, что arguments — строка, Schema проверит разобранные параметры, Diff сравнит два item.

Назад в блог