ブログ • Open Responses
Open Responses が AAIF に入ったあと、ツール呼び出しを別モデルへ再送する前に JSON の type、arguments、status を何から見るか?
2026年10月22日、Agentic AI Foundation は Open Responses をホストプロジェクトにした。仕様は 1 ターンを item にまとめる。message、function_call、function_call_output が移植可能な中核で、type にプロバイダ接頭辞がある記録はその実装専用。
ログの item を別モデルへ送る前に、ローカルで 3 欄を見る。type にコロンがあれば汎用 function_call にしない。仕様例の arguments は JSON 文字列なので、先に parse しツールの JSON Schema と照合する。status が completed でなければ function_call_output を足さない。
昨日の Bedrock 構造化出力 はモデル本文の形を扱う。Chat Completions の arguments も文字列であることが多いが封筒が違う。AI Agent と JSON Schema。schema 通過は実行許可ではない。権限決定。本記事は Open Responses の item だけを書く。
- 移植可能な type は message、function_call、function_call_output。
- コロン付き type はプロバイダ拡張。汎用ツール呼び出しとして再送しない。
- arguments は文字列。parse 後のオブジェクトをツール schema で region などまで検証する。
- status が in_progress か incomplete なら止める。completed のときだけ function_call_output を書き、call_id を一致させる。
先にこれだけ:AAIF が受け取ったのは共有 item の形で、1 本の JSON が単一プロバイダ専用のまま残ることがある。以下は type、arguments、status の順で、再送前にどの欄が止めるかを見る。
10月22日、共有 item がオープンガバナンスへ
当日の AGNTCon と MCPCon で、AAIF は 4 プロジェクトを発表した。OpenAI が拠出した Open Responses は MCP、A2A と並び、Linux Foundation 配下のオープンガバナンスに入る。発表は AAIF の発表。同日に Anthropic の Agent Skills、Hugging Face の OpenEnv、アリババの OpenSandbox も入った。本記事はそれらの目録やスキル形式は書かず、Open Responses の item JSON だけを扱う。
同日の AWS オープンソース記事は、Amazon Bedrock が Responses API をサポートし仕様に参加すると書いた。参照は AWS オープンソース記事。フィールドの意味は Open Responses 仕様 に従う。非ストリーミングは application/json、item は type で区別する。ログに function_call があるとき、プロバイダを換えるか翌日再送する前に、どの 3 フィールドが次リクエストを止めるか。
再送できる function_call に最低限どのフィールドがあるか?
次の検査に進める function_call は、少なくとも type、id、name、call_id、status、arguments を持つ。arguments の値は文字列で、その中にツール引数がある。下の例は region が apac で、同じ schema でもう一度検証する。
{
"type": "function_call",
"id": "fc_001",
"name": "get_latest_sales_report",
"call_id": "call_123",
"status": "completed",
"arguments": "{\"region\":\"apac\"}"
}
ツールを手元で実行したあと、次リクエストに足すのは function_call_output であり、Chat Completions の tool_calls 配列ではない。仕様の継続例は同じ call_id と output を持つ。サーバが結果を item として返すときに id と status が付く。
{
"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 を作らない。
{
"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 は文字列でありオブジェクトではない。オブジェクトを直接書いたログは次の parse で失敗するか、別実装に拒否される。まず string であることを確認し、json.loads のあとツールの JSON Schema にかける。下の schema は 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"]
}
}
}
下の文字列は parse できるが、region が global で enum にない。ゲートは arguments_schema を返し、ツールは実行しない。
{
"type": "function_call",
"id": "fc_009",
"name": "get_latest_sales_report",
"call_id": "call_129",
"status": "completed",
"arguments": "{\"region\":\"global\"}"
}
| 見ている arguments | ゲート結果 | ローカルの動作 |
|---|---|---|
| 中身の region が apac の文字列 | ok | ツールを実行し、function_call_output を書く。 |
| arguments 自体がオブジェクト | arguments_type | 拒否。文字列に戻してから parse。 |
| parse できるが region が global | arguments_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 が次と一致することを確認する。 |
| その他(failed を含む) | いいえ。副作用を実行しない。 |
下のゲートは本稿のサンプルを検査するコードと同じだ。コロン、function_call 以外、status、arguments の型、parse、schema の順で理由を返す。ok のときだけツールを実行し function_call_output を足す。
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 でこの 3 欄をどう分けて見るか?
- 1arguments が文字列か先に見る
JSON フォーマット。item 全体を貼る。arguments が 1 本の文字列でなくオブジェクトに広がっていれば、保存形が既にずれており、ゲートは arguments_type を返す。
- 2次に展開した引数を検証
arguments 文字列を解いたオブジェクトを JSON Schema に貼る。schema は上の region enum。global は印が付く。実行前に apac へ書き換えない。
- 3最後に両プロバイダの item を比較
JSON Diff。左に元プロバイダの item、右に再送予定の item。type が function_call からコロン付き拡張に変わるか、call_id が一致しなければ次リクエストを送らない。
よくある質問
Open Responses が AAIF に入れば、同じ JSON をそのまま Bedrock や別実装へ送れるか?
できない。移植できるのは message、function_call、function_call_output という中核。接頭辞付き item が別サーバで再送できるとはみなさない。Bedrock 上の Claude 構造化出力は別層で、モデル本文の JSON Schema であり、この item 封筒ではない。
arguments をなぜオブジェクトで保存しないか?
仕様例は arguments を JSON 文字列として書く。ストリームでは function_call_arguments.delta で追記し、status が completed になる前の文字列は未完成のことがある。早く parse して実行しない。
status が incomplete のとき、先にツールを実行してよいか?
しない。incomplete は終端状態だ。仕様はその応答の最後の item であること、response 自体も incomplete であることを求める。途中の arguments は schema を通らないことが多い。たまたま通っても完全な呼び出しとして実行しない。
allowed_tools がツール名を限っているのに、ローカル schema はまだ要るか?
要る。allowed_tools が限るのは、このリクエストで実際に実行できるツール名で、ツール定義は tools に全部置ける。arguments 内の region が enum にあるかは見ない。名前が許されても parse と schema は残る。
Chat Completions の tool_calls と Open Responses item を同じ配列に置けるか?
置かない。Open Responses は type、call_id、文字列 arguments を使う。Chat Completions は tool_calls 配列を使う。1 本のログに混ぜると、再送時に別の封筒が次のプロバイダへ行く。Diff で 2 文書を分けて比べる。
まとめ
Open Responses が AAIF に入ったあと、プロバイダ間で共有できるのは item の形であり、すべてのログ行ではない。
結論:type にコロンがあれば汎用 function_call にしない。arguments は文字列であることを確認してからツール schema で検証する。status が completed でなければ function_call_output を書かない。
次:フォーマットで arguments が文字列か見て、Schema で展開後の引数を検証し、Diff で両 item を比べる。