プログラムによるツール呼び出しでは、モデルが JavaScript を生成して実行し、複数のツールを組み合わせて使えます。プログラムはツールを並列に呼び出し、ループや条件分岐を使い、中間結果をホスト型ランタイム内に保持できます。関連するツールを順に呼び出す場合や、ツールが返す大量のデータを処理してから結果を返す場合に適しています。
Responses API では、プログラムによるツール呼び出しを有効にするかどうかをアプリケーションで決めます。対象のツールごとに、モデルからの直接呼び出し、プログラムからの呼び出し、またはその両方を許可できます。クライアント側のツール呼び出しは、引き続きアプリケーションが実行します。Agents API では、プログラムによるツール呼び出しがデフォルトで有効になっており、エージェントの実行ループも API が管理します。
プログラムによるツール呼び出しを有効にする前に、モデルのページを確認してください。
OpenAI は、生成されたプログラムを実行するたびに、新しい隔離された V8 ランタイムを用意します。このランタイムでは JavaScript のトップレベルで await を使用できます。一方、Node.js、パッケージのインストール、ネットワークへの直接アクセス、汎用ファイルシステム、サブプロセスの実行、コンソールは利用できず、JavaScript の状態を次回の実行に引き継ぐこともできません。プログラムが外部システムとやり取りできるのは、リクエストで有効にしたツールを介する場合だけです。結果は text(...) または image(...) で出力できます。
Responses API のリクエストでは、プログラムによるツール呼び出しでゼロデータ保持(ZDR)のワークフローを利用できます。永続的なコード実行コンテナは不要です。ZDR は組織またはプロジェクトで有効にする必要があります。store: false を設定するとステートレスで処理を継続できますが、この設定だけでは ZDR は有効になりません。ZDR の適用可否とデータの保持条件は、モデル、ツール、サードパーティのサービスを含むリクエスト全体で決まります。詳しくはデータ管理を参照してください。
処理の流れをあらかじめ決められ、コードで結果をコンパクトな構造化データにまとめられる段階では、プログラムによるツール呼び出しを使います。1 回の呼び出しで足りる場合、結果を得るたびにモデルの判断が必要な場合、承認が必要な場合、引用やツール固有の成果物を保持する必要がある場合は、ツールを直接呼び出します。
| タスクの種類 |
推奨する呼び出し方法 |
| 1 回の検索または操作 |
ツールを直接呼び出します。 |
| コードで複数の結果を絞り込み、結合、順位付け、重複排除、集計、検証できる処理 |
プログラムで結果をコンパクトな構造化データにまとめて返せる場合は、プログラムによるツール呼び出しを使います。 |
| 呼び出し間に依存関係があり、データの流れを予測できる処理 |
後続の呼び出しの引数をコードで求められ、制限と失敗時の動作が明確な場合は、プログラムによるツール呼び出しを使います。 |
| 結果に応じて進め方を変える検索や、意味に基づく評価 |
個々の結果をモデルの次の判断に反映する必要がある場合は、ツールを直接呼び出します。 |
| 書き込みや、承認の扱いに注意が必要な操作 |
どの操作にどの権限が必要かを明確に保つため、原則としてツールを直接呼び出します。 |
| 引用やツール固有の成果物の最終検証 |
プログラムがツール本来の出力を保持し、必要な項目をすべて検証できる場合を除き、ツールを直接呼び出します。 |
Responses API では、ホスト型ツール programmatic_tool_calling をリクエストに追加します。次に、プログラムから呼び出す対象のツールごとに allowed_callers を設定します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28[
{
"type": "function",
"name": "get_inventory",
"description": "Return an object with sku (string) and available_units (number).",
"parameters": {
"type": "object",
"properties": {
"sku": { "type": "string" }
},
"required": ["sku"],
"additionalProperties": false
},
"output_schema": {
"type": "object",
"properties": {
"sku": { "type": "string" },
"available_units": { "type": "number" }
},
"required": ["sku", "available_units"],
"additionalProperties": false
},
"allowed_callers": ["programmatic"]
},
{
"type": "programmatic_tool_calling"
}
]
allowed_callers で、モデルに許可するツールの呼び出し方法を指定します。
| 値 |
動作 |
省略または ["direct"] |
モデルがツールを直接呼び出せます。 |
["programmatic"] |
program 項目内のコードからのみツールを呼び出せます。 |
["direct", "programmatic"] |
モデルがツールを直接呼び出すことも、プログラムから呼び出すこともできます。 |
parameters は関数の引数を定義します。関数があらかじめ決まった構造のデータを返す場合は、output_schema で function_call_output.output の文字列にエンコードされる JSON オブジェクトの構造を定義します。生成された JavaScript が戻り値のフィールドを確実に扱えるように、両方を定義してください。
次のツールタイプは allowed_callers: ["programmatic"] に対応しています。
function と custom
mcp
apply_patch
- ローカルおよびホスト型の
shell
code_interpreter
MCP ツールでは、ツールの require_approval ポリシーに応じて、呼び出しを承認するまでプログラムが一時停止することがあります。
OpenAI がホストするツールをプログラムで有効にする前に、そのツールのデータ保持とセキュリティに関するガイドを確認してください。
ツール検索は、Responses API のトップレベルのツールとして実行されます。生成された JavaScript 内からは実行できません。defer_loading: true を設定した関数ツール、カスタムツール、MCP ツールは、初期状態ではプログラムから利用できません。モデルが検索条件に合うツールを読み込むと、その後に開始するプログラムから tools.* を通じて呼び出せます。ただし、そのツールの allowed_callers に "programmatic" が含まれている必要があります。実行中のプログラムからツール検索を呼び出すことはできないため、読み込みを遅延させたツールが必要な場合は、プログラムの開始前にモデルがそのツールを読み込む必要があります。
モデルが関数を直接呼び出すことも、プログラムから呼び出すこともできる場合は、ワークフローのどの段階でどちらを使うかを指定します。「プログラムによるツール呼び出しを効率よく使う」といった一般的な指示では、どこで使い分けるのかが伝わりません。たとえば、次のように指示します。
<tool_orchestration>
[bounded stage] では、プログラムから [eligible tools] だけを呼び出してください。
互いに独立した呼び出しは、安全に実行できる場合に並行して実行してください。ツールの入出力には、ドキュメントに記載されたフィールドだけを使ってください。
中間結果を処理して必要な情報に絞り、[program result shape] に厳密に従って出力してください。最終回答に必要な根拠も含めてください。
[condition] を満たしたら処理を終了してください。一時的な障害で呼び出しに失敗した場合、再試行は [R] 回までにしてください。
完了した呼び出しを繰り返したり、副作用のある操作を実行したりしないでください。それでも必要な結果が得られない場合は、失敗の内容が明確に分かる構造化データを返してください。
[semantic judgment, approval, or final validation] には、ツールの直接呼び出しを使ってください。
</tool_orchestration>
このテンプレートを使った例を示します。
<tool_orchestration>
sku_123 の在庫数と需要量を比較するには、プログラムから get_inventory と get_demand だけを呼び出してください。
両方の呼び出しを並行して実行してください。ツールの入出力には、ドキュメントに記載されたフィールドだけを使ってください。
中間結果を処理して必要な情報に絞り、sku、available_units、requested_units、shortage_units を含む JSON オブジェクトを 1 つだけ出力してください。
shortage_units は max(requested_units - available_units, 0) で求めてください。計算の根拠として available_units と requested_units を含めてください。
両方のツールの結果にそれぞれ必要なフィールドがそろったら、処理を終了してください。一時的な障害で呼び出しに失敗した場合、再試行は 1 回までにしてください。
完了した呼び出しを繰り返したり、副作用のある操作を実行したりしないでください。それでも必要な結果が得られない場合は、失敗の内容が明確に分かる構造化データを返してください。
在庫を変更する操作を行う場合は、どの操作でも実行前にツールを直接呼び出して承認を得てください。直接呼び出しは、この事前承認にのみ使ってください。
</tool_orchestration>
両方の呼び出し方法が必要なワークフローでは、処理の引き継ぎ箇所を 1 か所に決め、呼び出し方法を何度も切り替えたり、同じ処理を繰り返したりしないようにします。安全な代替手段がある場合は、一度だけ定義し、再試行回数に上限を設けます。
各 API 呼び出しは、通常どおり標準の Responses API オブジェクトを返します。プログラムによるツール呼び出し専用のレスポンス形式はありません。モデルがプログラムによるツール呼び出しを使うと、レスポンスの output 配列に次の項目が含まれることがあります。
program 項目には、生成された JavaScript、call_id、プログラムの再開またはリプレイに使う不透明な識別子 fingerprint が含まれます。
function_call 項目は、プログラムからの関数呼び出しを表します。各呼び出しには固有の call_id があり、アプリケーションはこれを使って関数の結果を返します。caller.caller_id はプログラムの call_id と一致します。
program_output 項目には、プログラムの最終結果とステータスが含まれます。この項目の call_id はプログラムの call_id と一致し、status は completed または incomplete です。
これらは response.output 内の独立したトップレベル項目です。どのプログラムが呼び出し元かは caller フィールドに記録されます。
たとえば、アプリケーションが get_inventory と get_demand を実行している間、プログラムは一時停止できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31[
{
"type": "program",
"id": "prog_123",
"call_id": "call_prog_123",
"code": "const [stock, demand] = await Promise.all([tools.get_inventory({ sku: 'sku_123' }), tools.get_demand({ sku: 'sku_123' })]); text(JSON.stringify({ sku: stock.sku, available_units: stock.available_units, requested_units: demand.requested_units, shortage_units: Math.max(demand.requested_units - stock.available_units, 0) }));",
"fingerprint": "opaque_replay_state"
},
{
"type": "function_call",
"id": "fc_123",
"call_id": "call_inventory_123",
"name": "get_inventory",
"arguments": "{\"sku\":\"sku_123\"}",
"caller": {
"type": "program",
"caller_id": "call_prog_123"
}
},
{
"type": "function_call",
"id": "fc_456",
"call_id": "call_demand_123",
"name": "get_demand",
"arguments": "{\"sku\":\"sku_123\"}",
"caller": {
"type": "program",
"caller_id": "call_prog_123"
}
}
]
これらの例では、response.output のうち関連する項目だけを示し、それらを含む標準の Responses オブジェクトは省略しています。アプリケーションがプログラムから呼び出された関数の結果を返すと、その後のレスポンスに完全な program_output 項目が含まれることがあります。
1
2
3
4
5
6
7{
"type": "program_output",
"id": "prog_out_123",
"call_id": "call_prog_123",
"result": "{\"sku\":\"sku_123\",\"available_units\":42,\"requested_units\":31,\"shortage_units\":0}",
"status": "completed"
}
program_output.result 内の JSON 文字列は、指示で指定したプログラムの結果形式に従います。それを含む program_output 項目自体は、上で示した API 仕様に従います。この 2 つの形式は別々に定義されます。最終的な message は、プログラムの出力と同じレスポンスに含まれることも、その後のレスポンスで返されることもあります。そのメッセージを受け取るまで処理を続けてください。
モデルが生成した JavaScript は、OpenAI がホスト型ランタイムで実行します。アプリケーションが実行するのは、レスポンスで返されたクライアント側の関数呼び出しです。生成された JavaScript をアプリケーションで実行することはありません。
関数の結果は function_call_output として返します。関数呼び出しの caller は変更せずにコピーしてください。サービスはこの値を使って、該当するプログラムを再開します。
プログラムは、クライアント側のツールを呼び出すたびに一時停止することがあり、1 つのプログラムで複数回の一時停止が発生する場合があります。レスポンスにアシスタントの最終メッセージが含まれるまで、次の手順で処理を続けます。
- ホスト型ツールと、プログラムからの呼び出しを許可した関数を含めてリクエストを送信します。
- レスポンスで返されたクライアント側の関数呼び出しをすべて実行します。
- 各関数の結果を、元の
call_id および caller とともに返します。
- レスポンスが未完了の場合は、先にその状態を処理します。
- レスポンスに保留中の
function_call 項目も最終的な message 項目もない場合は、そのレスポンスから処理を続けます。store: false の場合はレスポンスの出力項目を再送します。保存済みのレスポンスの場合は previous_response_id を使います。
- レスポンスに最終的な
message 項目が含まれたら終了します。response.output_text、またはメッセージに含まれる拒否の内容を読み取ります。
次の例では store: false を使い、すべてのレスポンス項目を保持しながら、各関数の結果をプログラムに返します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112import OpenAI from "openai";
import { toResponseInputItems } from "openai/lib/responses/ResponseInputItems";
const client = new OpenAI();
const implementations = {
get_inventory: async ({ sku }) => ({ sku, available_units: 42 }),
get_demand: async ({ sku }) => ({ sku, requested_units: 31 }),
};
const tools = [
{
type: "function",
name: "get_inventory",
description:
"Return an object with sku (string) and available_units (number).",
parameters: {
type: "object",
properties: { sku: { type: "string" } },
required: ["sku"],
additionalProperties: false,
},
output_schema: {
type: "object",
properties: {
sku: { type: "string" },
available_units: { type: "number" },
},
required: ["sku", "available_units"],
additionalProperties: false,
},
allowed_callers: ["programmatic"],
strict: true,
},
{
type: "function",
name: "get_demand",
description:
"Return an object with sku (string) and requested_units (number).",
parameters: {
type: "object",
properties: { sku: { type: "string" } },
required: ["sku"],
additionalProperties: false,
},
output_schema: {
type: "object",
properties: {
sku: { type: "string" },
requested_units: { type: "number" },
},
required: ["sku", "requested_units"],
additionalProperties: false,
},
allowed_callers: ["programmatic"],
strict: true,
},
{ type: "programmatic_tool_calling" },
];
const input = [
{
role: "user",
content: "Compare inventory with demand for sku_123.",
},
];
while (true) {
const response = await client.responses.create({
model: "gpt-6-astra",
store: false,
input,
tools,
});
if (response.status !== "completed") {
throw new Error(`Response ended with status ${response.status}`);
}
// Preserve replayable output, including program and reasoning items.
input.push(...toResponseInputItems(response.output));
const calls = response.output.filter((item) => item.type === "function_call");
if (calls.length === 0) {
const message = response.output.find((item) => item.type === "message");
if (message) {
const refusal = message.content.find((part) => part.type === "refusal");
console.log(response.output_text || refusal?.refusal || "");
break;
}
continue;
}
const outputs = await Promise.all(
calls.map(async (call) => {
const run = implementations[call.name];
if (!run) throw new Error(`Unknown tool: ${call.name}`);
const result = await run(JSON.parse(call.arguments));
return {
type: "function_call_output",
call_id: call.call_id,
output: JSON.stringify(result),
// Preserve caller so the runtime can resume the correct program.
caller: call.caller,
};
})
);
input.push(...outputs);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117import json
from openai import OpenAI
client = OpenAI()
model = "gpt-6-astra"
def get_inventory(sku):
return {"sku": sku, "available_units": 42}
def get_demand(sku):
return {"sku": sku, "requested_units": 31}
implementations = {
"get_inventory": get_inventory,
"get_demand": get_demand,
}
tools = [
{
"type": "function",
"name": "get_inventory",
"description": "Return an object with sku (string) and available_units (number).",
"parameters": {
"type": "object",
"properties": {"sku": {"type": "string"}},
"required": ["sku"],
"additionalProperties": False,
},
"output_schema": {
"type": "object",
"properties": {
"sku": {"type": "string"},
"available_units": {"type": "number"},
},
"required": ["sku", "available_units"],
"additionalProperties": False,
},
"allowed_callers": ["programmatic"],
},
{
"type": "function",
"name": "get_demand",
"description": "Return an object with sku (string) and requested_units (number).",
"parameters": {
"type": "object",
"properties": {"sku": {"type": "string"}},
"required": ["sku"],
"additionalProperties": False,
},
"output_schema": {
"type": "object",
"properties": {
"sku": {"type": "string"},
"requested_units": {"type": "number"},
},
"required": ["sku", "requested_units"],
"additionalProperties": False,
},
"allowed_callers": ["programmatic"],
},
{"type": "programmatic_tool_calling"},
]
input_items = [
{
"role": "user",
"content": "Compare inventory with demand for sku_123.",
}
]
while True:
response = client.responses.create(
model=model,
store=False,
input=input_items,
tools=tools,
)
if response.status != "completed":
raise RuntimeError(f"Response ended with status {response.status}")
# Preserve every output item, including program and reasoning items.
input_items.extend(item.model_dump(exclude_none=True) for item in response.output)
calls = [item for item in response.output if item.type == "function_call"]
if not calls:
message = next(
(item for item in response.output if item.type == "message"), None
)
if message:
refusal = next(
(part.refusal for part in message.content if part.type == "refusal"),
"",
)
print(response.output_text or refusal)
break
continue
for call in calls:
run = implementations.get(call.name)
if run is None:
raise ValueError(f"Unknown tool: {call.name}")
result = run(**json.loads(call.arguments))
input_items.append(
{
"type": "function_call_output",
"call_id": call.call_id,
"output": json.dumps(result),
# Preserve caller so the runtime can resume the correct program.
"caller": call.caller.model_dump() if call.caller else None,
}
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179package main
import (
"context"
"encoding/json"
"fmt"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/responses"
)
type toolArguments struct {
SKU string `json:"sku"`
}
func main() {
client := openai.NewClient()
input := responses.ResponseInputParam{
responses.ResponseInputItemParamOfMessage(
"Compare inventory with demand for sku_123.",
responses.EasyInputMessageRoleUser,
),
}
tools := []responses.ToolUnionParam{
functionTool(
"get_inventory",
"Return an object with sku (string) and available_units (number).",
"available_units",
),
functionTool(
"get_demand",
"Return an object with sku (string) and requested_units (number).",
"requested_units",
),
programmaticTool(),
}
for {
response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{
Model: "gpt-6-astra",
Store: openai.Bool(false),
Input: responses.ResponseNewParamsInputUnion{OfInputItemList: input},
Tools: tools,
})
if err != nil {
panic(err)
}
if response.Status != "completed" {
panic(fmt.Errorf("response ended with status %s", response.Status))
}
// Preserve every output item, including program and reasoning items.
input = append(input, outputAsInput(response.Output)...)
calls := functionCalls(response.Output)
if len(calls) == 0 {
if text, ok := finalMessageText(response); ok {
fmt.Println(text)
break
}
continue
}
for _, call := range calls {
result, err := runTool(call.Name, call.Arguments)
if err != nil {
panic(err)
}
output, err := json.Marshal(result)
if err != nil {
panic(err)
}
toolOutput := responses.ResponseInputItemParamOfFunctionCallOutput(string(output))
toolOutput.OfFunctionCallOutput.CallID = openai.String(call.CallID)
caller := call.Caller.AsProgram()
if caller.CallerID == "" {
panic("function call is missing its program caller")
}
// Preserve caller so the runtime can resume the correct program.
toolOutput.OfFunctionCallOutput.Caller.OfProgram =
&responses.ResponseInputItemFunctionCallOutputCallerProgramParam{
CallerID: caller.CallerID,
}
input = append(input, toolOutput)
}
}
}
func functionTool(name, description, resultField string) responses.ToolUnionParam {
parameters := map[string]any{
"type": "object",
"properties": map[string]any{
"sku": map[string]any{"type": "string"},
},
"required": []string{"sku"},
"additionalProperties": false,
}
outputSchema := map[string]any{
"type": "object",
"properties": map[string]any{
"sku": map[string]any{"type": "string"},
resultField: map[string]any{"type": "number"},
},
"required": []string{"sku", resultField},
"additionalProperties": false,
}
tool := responses.ToolParamOfFunction(name, parameters, true)
tool.OfFunction.Description = openai.String(description)
tool.OfFunction.AllowedCallers = []string{"programmatic"}
tool.OfFunction.OutputSchema = outputSchema
return tool
}
func programmaticTool() responses.ToolUnionParam {
tool := responses.NewToolProgrammaticToolCallingParam()
return responses.ToolUnionParam{OfProgrammaticToolCalling: &tool}
}
func outputAsInput(
output []responses.ResponseOutputItemUnion,
) []responses.ResponseInputItemUnionParam {
input := make([]responses.ResponseInputItemUnionParam, 0, len(output))
for _, item := range output {
var converted responses.ResponseInputItemUnion
if err := json.Unmarshal([]byte(item.RawJSON()), &converted); err != nil {
panic(err)
}
input = append(input, converted.ToParam())
}
return input
}
func functionCalls(
output []responses.ResponseOutputItemUnion,
) []responses.ResponseFunctionToolCall {
calls := make([]responses.ResponseFunctionToolCall, 0)
for _, item := range output {
if item.Type == "function_call" {
calls = append(calls, item.AsFunctionCall())
}
}
return calls
}
func finalMessageText(response *responses.Response) (string, bool) {
for _, item := range response.Output {
if item.Type != "message" {
continue
}
text := response.OutputText()
if text != "" {
return text, true
}
for _, content := range item.AsMessage().Content {
if content.Type == "refusal" {
return content.AsRefusal().Refusal, true
}
}
return "", true
}
return "", false
}
func runTool(name, argumentsJSON string) (map[string]any, error) {
var arguments toolArguments
if err := json.Unmarshal([]byte(argumentsJSON), &arguments); err != nil {
return nil, fmt.Errorf("parse %s arguments: %w", name, err)
}
switch name {
case "get_inventory":
return map[string]any{"sku": arguments.SKU, "available_units": 42}, nil
case "get_demand":
return map[string]any{"sku": arguments.SKU, "requested_units": 31}, nil
default:
return nil, fmt.Errorf("unknown tool: %s", name)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112import com.fasterxml.jackson.databind.ObjectMapper;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.core.JsonValue;
import com.openai.models.responses.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
var json = new ObjectMapper();
var tools =
List.of(
Tool.ofFunction(function("get_inventory", "available_units")),
Tool.ofFunction(function("get_demand", "requested_units")),
Tool.ofProgrammaticToolCalling());
var input = new ArrayList<ResponseInputItem>();
input.add(
ResponseInputItem.ofEasyInputMessage(
EasyInputMessage.builder()
.role(EasyInputMessage.Role.USER)
.content("Compare inventory with demand for sku_123.")
.build()));
while (true) {
var response =
client
.responses()
.create(
ResponseCreateParams.builder()
.model("gpt-6-astra")
.store(false)
.inputOfResponse(input)
.tools(tools)
.build());
if (!response.status().orElseThrow().equals(ResponseStatus.COMPLETED)) {
throw new IllegalStateException("Response ended with status " + response.status());
}
// Preserve every replayable output item, including program and reasoning items.
response.output().stream()
.map(item -> JsonValue.from(item).convert(ResponseInputItem.class))
.forEach(input::add);
var calls = response.output().stream().flatMap(item -> item.functionCall().stream()).toList();
if (calls.isEmpty()) {
var messages = response.output().stream().flatMap(item -> item.message().stream()).toList();
if (!messages.isEmpty()) {
messages.forEach(
message ->
message
.content()
.forEach(
content -> {
content.outputText().ifPresent(text -> System.out.println(text.text()));
content
.refusal()
.ifPresent(refusal -> System.out.println(refusal.refusal()));
}));
break;
}
continue;
}
for (var call : calls) {
String sku = json.readTree(call.arguments()).get("sku").asText();
var result =
switch (call.name()) {
case "get_inventory" -> Map.of("sku", sku, "available_units", 42);
case "get_demand" -> Map.of("sku", sku, "requested_units", 31);
default -> throw new IllegalArgumentException("Unknown tool: " + call.name());
};
var output =
ResponseInputItem.FunctionCallOutput.builder()
.callId(call.callId())
.output(json.writeValueAsString(result));
// Preserve caller so the runtime can resume the correct program.
call.caller()
.ifPresent(
caller ->
output.caller(
JsonValue.from(caller)
.convert(ResponseInputItem.FunctionCallOutput.Caller.class)));
input.add(ResponseInputItem.ofFunctionCallOutput(output.build()));
}
}
private static FunctionTool function(String name, String outputField) {
var parameters =
Map.of(
"type",
"object",
"properties",
Map.of("sku", Map.of("type", "string")),
"required",
List.of("sku"),
"additionalProperties",
false);
var outputSchema =
Map.of(
"type",
"object",
"properties",
Map.of("sku", Map.of("type", "string"), outputField, Map.of("type", "number")),
"required",
List.of("sku", outputField),
"additionalProperties",
false);
return FunctionTool.builder()
.name(name)
.description("Return an object with sku (string) and " + outputField + " (number).")
.parameters(JsonValue.from(parameters).convert(FunctionTool.Parameters.class))
.outputSchema(JsonValue.from(outputSchema).convert(FunctionTool.OutputSchema.class))
.allowedCallers(List.of(FunctionTool.AllowedCaller.PROGRAMMATIC))
.strict(true)
.build();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125require "json"
require "openai"
client = OpenAI::Client.new
def get_inventory(sku:)
{
sku: sku,
available_units: 42
}
end
def get_demand(sku:)
{
sku: sku,
requested_units: 31
}
end
implementations = {
"get_inventory" => method(:get_inventory),
"get_demand" => method(:get_demand)
}
tools = [
{
type: :function,
name: "get_inventory",
description: "Return an object with sku (string) and available_units (number).",
parameters: {
type: :object,
properties: { sku: { type: :string } },
required: ["sku"],
additionalProperties: false
},
output_schema: {
type: :object,
properties: {
sku: { type: :string },
available_units: { type: :number }
},
required: %w[sku available_units],
additionalProperties: false
},
allowed_callers: [:programmatic],
strict: true
},
{
type: :function,
name: "get_demand",
description: "Return an object with sku (string) and requested_units (number).",
parameters: {
type: :object,
properties: { sku: { type: :string } },
required: ["sku"],
additionalProperties: false
},
output_schema: {
type: :object,
properties: {
sku: { type: :string },
requested_units: { type: :number }
},
required: %w[sku requested_units],
additionalProperties: false
},
allowed_callers: [:programmatic],
strict: true
},
{ type: :programmatic_tool_calling }
]
input = [
{
role: :user,
content: "Compare inventory with demand for sku_123."
}
]
loop do
response = client.responses.create(
model: "gpt-6-astra",
store: false,
input: input,
tools: tools
)
unless response.status == OpenAI::Responses::ResponseStatus::COMPLETED
raise "Response ended with status #{response.status}"
end
# Preserve every output item, including program and reasoning items.
input.concat(response.output)
calls = response.output.grep(OpenAI::Models::Responses::ResponseFunctionToolCall)
if calls.empty?
message = response.output.find do |item|
item.is_a?(OpenAI::Models::Responses::ResponseOutputMessage)
end
next unless message.is_a?(OpenAI::Models::Responses::ResponseOutputMessage)
refusal = message.content.find do |content|
content.is_a?(OpenAI::Models::Responses::ResponseOutputRefusal)
end
text = response.output_text
if text.empty? &&
refusal.is_a?(OpenAI::Models::Responses::ResponseOutputRefusal)
text = refusal.refusal
end
puts(text)
break
end
calls.each do |call|
implementation = implementations.fetch(call.name) do
raise ArgumentError, "Unknown tool: #{call.name}"
end
result = implementation.call(**JSON.parse(call.arguments, symbolize_names: true))
output = {
type: :function_call_output,
call_id: call.call_id,
output: JSON.generate(result)
}
# Preserve caller so the runtime can resume the correct program.
output[:caller] = call.caller_.to_h if call.caller_
input << output
end
end
レスポンスを保存する場合は、それまでのレスポンス項目をすべて再送せずに、previous_response_id を使って処理を継続できます。新しい function_call_output 項目を次の入力として送信します。store: false の場合は、program、推論、関数呼び出し、関数呼び出しの出力、program_output の各項目をすべて含め、元の順序で再送してください。
リーズニングモデルへのステートレスなリクエストでは、返された推論項目をすべて再送します。各項目にはデフォルトで encrypted_content が含まれます。ステートレスで処理を継続する一般的な方法については、会話状態を参照してください。
- JavaScript が文章を解析せずに中身を確認できるよう、コンパクトな構造化データを返します。
output_schema で各ツールが返すフィールドと型を定義し、エラー時の動作を文書化します。戻り値の構造が事前に分からない場合は、モデルが結果を確認できるよう、そのツールには直接呼び出しだけを許可します。
- プログラムが返す結果の形式と、結果に含めるべき根拠を明確に定義します。有効な結果を生成できない場合は、失敗の内容が明確に分かる構造化データを返します。
- 可能な限り、関数呼び出しを冪等にします。再試行やリプレイによって、安全でない副作用が繰り返し発生しないようにします。
- ホスト型プログラムからの呼び出しであっても、呼び出しごとにアプリケーションで引数と権限を確認します。
- モデルがツールを正しく組み合わせられるよう、それぞれに具体的な名前と説明を付けます。
- 影響の大きい操作では、呼び出し元にかかわらず、実行前にアプリケーション側での承認を必須にします。
プログラムによるツール呼び出しを使うと、モデルのコンテキストに追加するツールの中間出力を減らせる場合があります。効果はタスクやツールのレスポンスによって異なります。まずツールの直接呼び出しを基準とし、代表的なタスクで両方の方法を比較します。
効率を測定する前に、最終回答に求める品質水準と必要な根拠を定めます。トークン使用量やツール呼び出し回数に加え、回答の正確性、完全性、必要な根拠がどこまでそろっているかも評価します。効率を優先して品質の低下を許容する場合は、その範囲を明示します。
次の項目を測定します。
- 最終回答の正確性、完全性、必要な根拠の網羅度
- 入力トークン数と総トークン数、処理全体のレイテンシ、コスト
- モデルのターン数、ツール呼び出し回数、再試行回数、障害からの復旧時の動作
- 安全性に関する結果、特に副作用の扱いと承認要件の充足状況
- 実際の呼び出し方法が、そのワークフローの段階で想定した方法と一致したかどうか
Agents API では、プログラムによるツール呼び出しは OpenAI が管理するエージェントハーネス上で実行され、デフォルトで有効です。ハーネスはエージェントに exec ツールを提供し、生成された JavaScript 内でエージェントの既存ツールを使えるようにします。これらのツールをコマンドラインプログラムとしてラップしたり、サンドボックスにインストールしたりする必要はありません。
プログラムによるツール呼び出しを無効にするには、agent.tools に次のエントリを追加します。
1234{
"type": "programmatic_tool_calling",
"enabled": false
}
エントリ自体またはその enabled フィールドを省略すると、プログラムによるツール呼び出しは有効なままです。タイプのみを指定した { "type": "programmatic_tool_calling" } の場合も有効です。前述の allowed_callers の設定とレスポンスから処理を継続するループは、Responses API を使う場合のものです。
プログラムによるツール呼び出しは、environment.type を none に設定した会話専用セッションでも利用できます。Bash、エグゼキューター MCP、およびサンドボックス内で動作するその他のツールには、引き続き実行環境が必要です。
JavaScript からツールを呼び出しても、ツールの実行場所は変わりません。シェル呼び出しではサンドボックス内でコマンドが実行されます。JavaScript ランタイム自体がシステムプロセスを起動することはありません。エグゼキューター MCP は引き続きサンドボックスを使い、関数ツールは引き続きアプリケーションサーバーを呼び出します。エージェントはそれらの結果を処理してから、モデルのコンテキストに含める内容を決めます。
前述の使い分けの指針に沿って、ワークフローのどの段階でコードを使うかを定めてください。Agents API の設定と呼び出しの処理方法については、関数と MCP 接続を参照してください。
- クライアント側の関数を定義するには、Function Calling を使います。
- 大きなツール定義の読み込みをモデルが必要とする時点まで遅らせるには、ツール検索を使います。
- レスポンスを保存する場合もステートレスで扱う場合も、Responses API のリクエストを継続する方法については、会話状態を参照してください。
- 保存モードを選ぶ前に、データ管理を確認してください。