Blog • Open Responses

Après l'arrivée d'Open Responses à l'AAIF, quels contrôles de type, arguments et status avant de rejouer un item d'appel d'outil ?

Le 22 octobre 2026, l'Agentic AI Foundation a fait d'Open Responses un projet hébergé. La spec range un tour en items : message, function_call et function_call_output forment le cœur portable ; un type préfixé appartient à cette implémentation.

Avant d'envoyer un item de journal à un autre modèle, regardez trois champs en local. Un deux-points dans type interdit de le traiter comme un function_call portable. Dans les exemples, arguments est une chaîne JSON : parsez, puis le JSON Schema de l'outil. Si status n'est pas completed, n'ajoutez pas function_call_output.

Le texte d'hier, sorties structurées Bedrock traite la forme du texte du modèle. Chat Completions garde souvent arguments en chaîne, dans une autre enveloppe — voir Agent IA et JSON Schema. Un schema valide n'autorise pas l'exécution — voir la décision de permission. Cet article ne couvre que l'item Open Responses.

Gardez ceci : L'AAIF a repris une forme d'item partagée. Un JSON peut encore n'appartenir qu'à un fournisseur. La suite suit type, arguments, puis status, et quel champ arrête le rejeu.

Le 22 octobre, l'item partagé est passé en gouvernance ouverte

À AGNTCon et MCPCon, l'AAIF a annoncé quatre projets. Open Responses, apporté par OpenAI, rejoint MCP et A2A sous la gouvernance de la Linux Foundation. L'annonce : l'annonce AAIF. Le même jour : Agent Skills d'Anthropic, OpenEnv de Hugging Face et OpenSandbox d'Alibaba. Cet article ne couvre ni ces catalogues ni le format de skill, seulement le JSON d'item Open Responses.

Le même jour, un billet open source AWS indique qu'Amazon Bedrock prend en charge l'API Responses et participe à la spec. Voir le billet open source AWS. Le sens des champs suit la spécification Open Responses. Les corps non streamés sont en application/json, les items se distinguent par type. La question : un function_call est déjà dans le journal ; quels trois champs doivent empêcher la requête suivante avant un changement de fournisseur ou un rejeu le lendemain.

Quels champs minimum pour un function_call rejouable ?

Un function_call qui passe à l'étape suivante porte au moins type, id, name, call_id, status et arguments. La valeur d'arguments est une chaîne ; les paramètres sont dedans. L'exemple a region apac et sera revérifié avec le même schema.

function_call qui peut continuer. arguments est une chaîne.
{
  "type": "function_call",
  "id": "fc_001",
  "name": "get_latest_sales_report",
  "call_id": "call_123",
  "status": "completed",
  "arguments": "{\"region\":\"apac\"}"
}

Une fois l'outil exécuté chez vous, la requête suivante ajoute function_call_output, pas un tableau tool_calls de Chat Completions. L'exemple de suite garde le même call_id et ajoute output. Si le serveur renvoie ce résultat comme item, id et status arrivent alors.

function_call_output de la requête suivante, même call_id.
{
  "type": "function_call_output",
  "call_id": "call_123",
  "output": "sales report for apac"
}

Pourquoi un deux-points dans type empêche l'exécution chez un autre fournisseur ?

La spec permet des items propres, mais type doit porter un préfixe de slug, par exemple openai:web_search_call. Cet enregistrement peut rester dans la session d'origine et revenir au même fournisseur. Ne supposez pas qu'il est portable vers un autre serveur Open Responses. Règle locale : un deux-points marque namespaced, sans function_call_output.

Item préfixé. Ne le réexécutez pas comme get_latest_sales_report.
{
  "type": "openai:web_search_call",
  "id": "ws_001",
  "status": "completed",
  "action": { "type": "search", "query": "apac sales report" }
}
type vuRejeu chez un autre fournisseur ?Action locale
function_callPasse au contrôle suivantEnsuite arguments et status.
messagePeut aller dans inputComme texte de conversation, pas comme résultat d'outil.
openai:web_search_callNon portableLe laisser au fournisseur d'origine, ou le sortir du journal multi-fournisseurs.

Pourquoi arguments doit être une chaîne avant le schema ?

L'événement response.function_call_arguments.delta ajoute du texte avant que l'objet soit fini. arguments stocké est donc une chaîne, pas un objet. Un journal qui met un objet dans arguments échoue au parse suivant ou est refusé par une autre implémentation. Vérifiez que c'est une chaîne, puis json.loads, puis le JSON Schema de l'outil. Le schema ci-dessous n'accepte que apac, emea et amer.

Schema des paramètres. region vaut apac, emea ou amer.
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "required": ["region"],
  "properties": {
    "region": {
      "type": "string",
      "enum": ["apac", "emea", "amer"]
    }
  }
}

La chaîne ci-dessous se parse, mais region vaut global, absent de l'enum. Le garde doit renvoyer arguments_schema, et l'outil ne s'exécute pas.

Parse réussi, schema en échec. Ne devinez pas une région.
{
  "type": "function_call",
  "id": "fc_009",
  "name": "get_latest_sales_report",
  "call_id": "call_129",
  "status": "completed",
  "arguments": "{\"region\":\"global\"}"
}
arguments vuRésultat du gardeAction locale
Chaîne dont l'objet a region apacokExécuter l'outil, puis écrire function_call_output.
arguments est un objet, pas une chaînearguments_typeRejeter. Revenir à une chaîne avant parse.
Parse réussi, region vaut globalarguments_schemaNe pas exécuter. Renvoyer l'erreur de validation, sans deviner la région.

Pourquoi ne pas écrire le résultat si status n'est pas completed ?

La spec fait de chaque item une machine à états. in_progress signifie que la sortie continue. incomplete est terminal, doit être le dernier item de cette réponse, et la réponse elle-même doit être incomplete. Après completed, l'item ne change plus. En incomplete, arguments peut être coupé ; un JSON partiel ne doit pas s'exécuter.

statusÉcrire function_call_output ?
in_progressNon. Attendre la fin du flux.
incompleteNon. Terminal, les arguments peuvent être tronqués.
completedOui. Puis : pas de deux-points dans type, arguments passe le schema, call_id identique à l'item suivant.
Toute autre valeur, y compris failedNon. Ne pas exécuter d'effet de bord.

Le garde ci-dessous est le code qui contrôle les exemples de cet article. Il renvoie une raison dans l'ordre : deux-points, pas un function_call, status, type d'arguments, parse, schema. Seul ok autorise l'outil et function_call_output.

Garde local avant rejeu. Tout sauf ok n'exécute pas l'outil.
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"

Comment inspecter les trois champs dans JSONNote ?

  1. 1
    D'abord : arguments est-il une chaîne ?

    Ouvrez JSON Format. Collez l'item entier. Si arguments s'ouvre en objet au lieu d'une chaîne, la forme stockée est déjà fausse et le garde renvoie arguments_type.

  2. 2
    Puis valider les paramètres parsés

    Collez l'objet parsé depuis la chaîne arguments dans JSON Schema. Le schema est l'enum region ci-dessus. global est signalé ; ne le changez pas en apac avant l'exécution.

  3. 3
    Enfin comparer les items des deux fournisseurs

    Ouvrez JSON Diff. À gauche l'item du fournisseur d'origine, à droite celui à rejouer. Si type passe de function_call à une extension préfixée, ou si call_id ne correspond pas, n'envoyez pas la requête suivante.

Questions fréquentes

Une fois Open Responses à l'AAIF, le même JSON part-il tel quel vers Bedrock ou une autre implémentation ?

Non. Le cœur portable est message, function_call et function_call_output. Ne supposez pas qu'un item préfixé se rejoue sur un autre serveur. Les sorties structurées Claude sur Bedrock visent une autre couche : le JSON Schema du texte du modèle, pas cette enveloppe d'item.

Pourquoi arguments n'est-il pas stocké comme objet ?

Les exemples de la spec stockent arguments comme chaîne JSON. Le flux l'allonge via function_call_arguments.delta, et avant completed la chaîne peut être incomplète. Ne parsez pas trop tôt pour exécuter.

Si status vaut incomplete, puis-je quand même exécuter l'outil ?

Non. incomplete est terminal. La spec exige que ce soit le dernier item de la réponse, et que la réponse soit incomplete. Un arguments partiel échoue en général au schema ; même s'il passe, ne le traitez pas comme un appel fini.

Si allowed_tools limite déjà les noms d'outils, faut-il encore un schema local ?

Oui. allowed_tools limite quels noms peuvent vraiment s'exécuter pour cette requête ; les définitions peuvent rester entières dans tools. Il ne vérifie pas si region dans arguments est dans l'enum. Un nom autorisé exige encore parse et schema.

Les tool_calls de Chat Completions peuvent-ils partager le tableau des items Open Responses ?

Non. Open Responses utilise type, call_id et arguments en chaîne. Chat Completions utilise un tableau tool_calls. Mélangés dans un journal, le rejeu envoie la mauvaise enveloppe au fournisseur suivant. Comparez les deux documents à part avec Diff.

Conclusion

Depuis qu'Open Responses a rejoint l'AAIF, les fournisseurs partagent la forme de l'item, pas chaque ligne de journal.

Décision : un deux-points dans type interdit le function_call portable ; confirmez qu'arguments est une chaîne, puis le schema de l'outil ; si status n'est pas completed, pas de function_call_output.

Ensuite : formatez pour voir si arguments est une chaîne, validez les paramètres parsés avec Schema, comparez les deux items avec Diff.

Retour au blog