Function Calling (ツール呼び出し とも呼ばれます)を使うと、OpenAI のモデルが外部システムと柔軟に連携し、学習データに含まれないデータにアクセスできます。このガイドでは、アプリケーションが提供するデータや操作をモデルから利用できるようにする方法を説明します。JSON スキーマで定義する関数ツールと、自由形式のテキストを入出力に使うカスタムツールを取り上げます。
Agents API のセッションでは、関数 を使って関数を登録し、セッションのアクションリクエストを処理します。このガイドの例では、Responses API と Chat Completions での実装を示します。
アプリケーションに多数の関数や大規模なスキーマがある場合は、Function Calling とツール検索 を組み合わせると、使用頻度の低いツールの読み込みを遅らせ、モデルが必要とするときだけ読み込めます。gpt-5.4 以降のモデルのみが tool_search に対応しています。
GPT-6 Astra と GPT-6.1 Sol でツールを呼び出すには、Responses API が必要です。Chat Completions の例では、推論を無効にした GPT-5.6 Terra を使用します。既存の実装を更新するには、移行ガイド を参照してください。
まず、ツール呼び出しの基本用語を確認します。そのうえで、具体例を使って実装方法を説明します。
ツール:モデルに提供する機能 関数 またはツール とは、モデルに対して利用可能であると伝える機能のことです。モデルはプロンプトへの応答を生成する際に、その指示に従うにはツールが提供するデータや機能が必要だと判断する場合があります。
たとえば、次のようなツールをモデルに提供できます。
指定した場所の今日の天気を取得するツール
指定したユーザー ID のアカウント情報にアクセスするツール
配送中に紛失した注文の代金を返金するツール
このほかにも、プロンプトに応答する際にモデルに取得させたい情報や実行させたい操作をツールとして提供できます。
プロンプトを含む API リクエストをモデルに送る際に、モデルが利用を検討できるツールの一覧を渡せます。たとえば、世界各地の現在の天気についての質問に答えられるようにするには、get_weather ツールを提供し、location を引数として受け取るようにします。
ツール呼び出し:モデルからのツール利用リクエスト 関数呼び出し またはツール呼び出し とは、モデルがプロンプトを検討し、その指示に従うには利用可能なツールのいずれかを呼び出す必要があると判断したときに返す特殊な形式の応答です。
API リクエストで「パリの天気は?」というプロンプトを受け取った場合、モデルは get_weather ツールの呼び出しを返し、Paris を location 引数の値として指定できます。
ツール呼び出しの出力:モデルに返す実行結果 関数呼び出しの出力 またはツール呼び出しの出力 とは、モデルのツール呼び出しで指定された入力を使ってツールが生成する応答です。ツール呼び出しの出力には、構造化された JSON またはプレーンテキストを使用できます。出力には、モデルのどのツール呼び出しに対応するのかを示す参照(以降の例では call_id)を含めてください。
天気の例では、次のようになります。
モデルは get_weather ツール を利用できます。このツールは location を引数として受け取ります。
「パリの天気は?」というプロンプトに対して、モデルはツール呼び出し を返します。その location 引数の値は Paris です。
ツール呼び出しの出力 として、JSON オブジェクト(たとえば、現在の気温が摂氏 25 度であることを示す {"temperature": "25", "unit": "C"})、画像の内容 、またはファイルの内容 を返せます。
次に、ツールの定義、元のプロンプト、モデルのツール呼び出し、その出力をすべてモデルに送り返すと、最終的に次のようなテキスト応答が得られます。
The weather in Paris today is 25C.
関数とツールの違い
関数は、JSON スキーマで定義するツールの一種です。関数を定義すると、モデルからアプリケーションにデータを渡せます。アプリケーション側のコードは、そのデータを使って情報を取得したり、モデルが提案した操作を実行したりできます。
関数ツールのほかに、自由形式のテキストを入出力に使うカスタムツールもあります。このガイドの後半で説明します。
OpenAI プラットフォームでは、組み込みツール も提供しています。モデルはこれらのツールを使って、ウェブ検索 やコードの実行 のほか、MCP サーバー の機能も利用できます。
ツール呼び出しでは、アプリケーションとモデルが OpenAI API を介して複数回やり取りします。大まかな流れは次の 5 ステップです。
呼び出し可能なツールを含むリクエストをモデルに送信します。
モデルからツール呼び出しを受け取ります。
ツール呼び出しの入力を使って、アプリケーション側でコードを実行します。
ツールの出力を含む 2 回目のリクエストをモデルに送信します。
モデルから最終応答または追加のツール呼び出しを受け取ります。
Responses では、タスクに必要な回数だけこの流れを繰り返せます。このループに伴う一連の処理をまとめて管理できるフレームワークが必要な場合は、Responses API と Agents SDK の比較 を参照してください。
指定した星座の今日の運勢を取得する get_horoscope 関数を例に、ツール呼び出しの一連の流れを見てみましょう。
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 import OpenAI from "openai";
const openai = new OpenAI();
// 1. Define a list of callable tools for the model
const tools = [
{
type: "function",
function: {
name: "get_horoscope",
description: "Get today's horoscope for an astrological sign.",
parameters: {
type: "object",
properties: {
sign: {
type: "string",
description: "An astrological sign like Taurus or Aquarius",
},
},
required: ["sign"],
additionalProperties: false,
},
strict: true,
},
},
];
function getHoroscope(sign) {
return `${sign}: Next Tuesday you will befriend a baby otter.`;
}
const messages = [
{ role: "user", content: "What is my horoscope? I am an Aquarius." },
];
// 2. Prompt the model with tools defined
let response = await openai.chat.completions.create({
model: "gpt-5.6-terra",
reasoning_effort: "none",
messages,
tools,
});
messages.push(response.choices[0].message);
for (const toolCall of response.choices[0].message.tool_calls ?? []) {
if (toolCall.type !== "function") continue;
if (toolCall.function.name === "get_horoscope") {
// 3. Execute the function logic for get_horoscope
const args = JSON.parse(toolCall.function.arguments);
const horoscope = getHoroscope(args.sign);
// 4. Provide function call results to the model
messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: JSON.stringify({ horoscope }),
});
}
}
response = await openai.chat.completions.create({
model: "gpt-5.6-terra",
reasoning_effort: "none",
messages,
tools,
});
// 5. The model should be able to give a response!
console.log(response.choices[0].message.content); 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 from openai import OpenAI
import json
client = OpenAI()
# 1. Define a list of callable tools for the model
tools = [
{
"type" : "function" ,
"function" : {
"name" : "get_horoscope" ,
"description" : "Get today's horoscope for an astrological sign." ,
"parameters" : {
"type" : "object" ,
"properties" : {
"sign" : {
"type" : "string" ,
"description" : "An astrological sign like Taurus or Aquarius" ,
},
},
"required" : [ "sign" ],
"additionalProperties" : False ,
},
"strict" : True ,
},
},
]
def get_horoscope (sign):
return f " { sign } : Next Tuesday you will befriend a baby otter."
messages = [{ "role" : "user" , "content" : "What is my horoscope? I am an Aquarius." }]
# 2. Prompt the model with tools defined
response = client.chat.completions.create(
model = "gpt-5.6-terra" ,
reasoning_effort = "none" ,
messages = messages,
tools = tools,
)
messages.append(response.choices[ 0 ].message)
for tool_call in response.choices[ 0 ].message.tool_calls or []:
if tool_call.function.name == "get_horoscope" :
# 3. Execute the function logic for get_horoscope
args = json.loads(tool_call.function.arguments)
horoscope = get_horoscope(args[ "sign" ])
# 4. Provide function call results to the model
messages.append(
{
"role" : "tool" ,
"tool_call_id" : tool_call.id,
"content" : json.dumps({ "horoscope" : horoscope}),
}
)
response = client.chat.completions.create(
model = "gpt-5.6-terra" ,
reasoning_effort = "none" ,
messages = messages,
tools = tools,
)
# 5. The model should be able to give a response!
print (response.choices[ 0 ].message.content) 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 package main
import (
"context"
"encoding/json"
"fmt"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/shared"
)
func main() {
client := openai.NewClient()
tool := horoscopeChatTool()
messages := []openai.ChatCompletionMessageParamUnion{
openai.UserMessage("What is my horoscope? I am an Aquarius."),
}
completion, err := client.Chat.Completions.New(context.Background(), openai.ChatCompletionNewParams{
Model: "gpt-5.6-terra", Messages: messages, Tools: []openai.ChatCompletionToolUnionParam{tool},
ReasoningEffort: shared.ReasoningEffortNone,
})
if err != nil {
panic(err)
}
messages = append(messages, completion.Choices[0].Message.ToParam())
for _, call := range completion.Choices[0].Message.ToolCalls {
if call.Type != "function" || call.Function.Name != "get_horoscope" {
continue
}
var arguments struct {
Sign string `json:"sign"`
}
if err := json.Unmarshal([]byte(call.Function.Arguments), &arguments); err != nil {
panic(err)
}
horoscope := getHoroscope(arguments.Sign)
messages = append(messages, openai.ToolMessage(horoscope, call.ID))
}
completion, err = client.Chat.Completions.New(context.Background(), openai.ChatCompletionNewParams{
Model: "gpt-5.6-terra", Messages: messages, Tools: []openai.ChatCompletionToolUnionParam{tool},
ReasoningEffort: shared.ReasoningEffortNone,
})
if err != nil {
panic(err)
}
fmt.Println(completion.Choices[0].Message.Content)
}
func horoscopeChatTool() openai.ChatCompletionToolUnionParam {
parameters := map[string]any{
"type": "object",
"properties": map[string]any{
"sign": map[string]any{"type": "string", "description": "An astrological sign like Taurus or Aquarius"},
},
"required": []string{"sign"},
"additionalProperties": false,
}
return openai.ChatCompletionToolUnionParam{OfFunction: &openai.ChatCompletionFunctionToolParam{
Function: shared.FunctionDefinitionParam{
Name: "get_horoscope", Description: openai.String("Get today's horoscope for an astrological sign."), Parameters: parameters, Strict: openai.Bool(true),
},
}}
}
func getHoroscope(sign string) string {
return fmt.Sprintf("%s: Next Tuesday you will befriend a baby otter.", sign)
} 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 import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.core.JsonValue;
import com.openai.models.FunctionDefinition;
import com.openai.models.FunctionParameters;
import com.openai.models.ReasoningEffort;
import com.openai.models.chat.completions.ChatCompletionCreateParams;
import com.openai.models.chat.completions.ChatCompletionToolMessageParam;
import java.util.List;
import java.util.Map;
FunctionDefinition horoscope =
FunctionDefinition.builder()
.name("get_horoscope")
.description("Get today's horoscope for an astrological sign.")
.parameters(
FunctionParameters.builder()
.putAdditionalProperty("type", JsonValue.from("object"))
.putAdditionalProperty(
"properties",
JsonValue.from(
Map.of(
"sign",
Map.of(
"type", "string",
"description",
"An astrological sign like Taurus or Aquarius"))))
.putAdditionalProperty("required", JsonValue.from(List.of("sign")))
.putAdditionalProperty("additionalProperties", JsonValue.from(false))
.build())
.strict(true)
.build();
ChatCompletionCreateParams params =
ChatCompletionCreateParams.builder()
.model("gpt-5.6-terra")
.reasoningEffort(ReasoningEffort.NONE)
.addUserMessage("What is my horoscope? I am an Aquarius.")
.addFunctionTool(horoscope)
.build();
var assistant = client.chat().completions().create(params).choices().get(0).message();
var calls = assistant.toolCalls().orElseThrow(() -> new IllegalStateException("No tool calls"));
var followUp = params.toBuilder().addMessage(assistant);
record HoroscopeArguments(String sign) {}
for (var toolCall : calls) {
var function = toolCall.asFunction();
if (function.function().name().equals("get_horoscope")) {
String sign = function.function().arguments(HoroscopeArguments.class).sign();
followUp.addMessage(
ChatCompletionToolMessageParam.builder()
.toolCallId(function.id())
.content(sign + ": Embrace an unexpected opportunity today.")
.build());
}
}
client.chat().completions().create(followUp.build()).choices().stream()
.flatMap(choice -> choice.message().content().stream())
.forEach(System.out::println); 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 require "json"
require "openai"
client = OpenAI::Client.new
messages = [
{
role: :user,
content: "What is my horoscope? I am an Aquarius."
}
]
tools = [
{
type: :function,
function: {
name: "get_horoscope",
description: "Get today's horoscope for an astrological sign.",
parameters: {
type: :object,
properties: { sign: { type: :string } },
required: ["sign"],
additionalProperties: false
},
strict: true
}
}
]
first_completion = client.chat.completions.create(
model: "gpt-5.6-terra",
reasoning_effort: :none,
messages: messages,
tools: tools
)
assistant_message = first_completion.choices.fetch(0).message
tool_calls = assistant_message.tool_calls || []
raise "The model did not call get_horoscope" if tool_calls.empty?
messages << {
role: :assistant,
content: assistant_message.content,
tool_calls: tool_calls.map(&:to_h)
}
tool_calls.each do |tool_call|
next unless tool_call.is_a?(OpenAI::Models::Chat::ChatCompletionMessageFunctionToolCall)
next unless tool_call.function.name == "get_horoscope"
arguments = JSON.parse(tool_call.function.arguments, symbolize_names: true)
sign = arguments.fetch(:sign)
messages << {
role: :tool,
tool_call_id: tool_call.id,
content: "#{sign}: Embrace an unexpected opportunity today."
}
end
final_completion = client.chat.completions.create(
model: "gpt-5.6-terra",
reasoning_effort: :none,
messages: messages,
tools: tools
)
puts(final_completion.choices.fetch(0).message.content)
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 import OpenAI from "openai";
import { toResponseInputItems } from "openai/lib/responses/ResponseInputItems";
const openai = new OpenAI();
// 1. Define a list of callable tools for the model
const tools = [
{
type: "function",
name: "get_horoscope",
description: "Get today's horoscope for an astrological sign.",
parameters: {
type: "object",
properties: {
sign: {
type: "string",
description: "An astrological sign like Taurus or Aquarius",
},
},
required: ["sign"],
additionalProperties: false,
},
strict: true,
},
];
function getHoroscope(sign) {
return `${sign}: Next Tuesday you will befriend a baby otter.`;
}
// Create a running input list we will add to over time
let input = [
{ role: "user", content: "What is my horoscope? I am an Aquarius." },
];
// 2. Prompt the model with tools defined
let response = await openai.responses.create({
model: "gpt-6-astra",
tools,
input,
});
// Preserve model output for the next turn
input.push(...toResponseInputItems(response.output));
for (const item of response.output) {
if (item.type !== "function_call") continue;
if (item.name === "get_horoscope") {
// 3. Execute the function logic for get_horoscope
const { sign } = JSON.parse(item.arguments);
const horoscope = getHoroscope(sign);
// 4. Provide function call results to the model
input.push({
type: "function_call_output",
call_id: item.call_id,
output: horoscope,
});
}
}
console.log("Final input:");
console.log(JSON.stringify(input, null, 2));
response = await openai.responses.create({
model: "gpt-6-astra",
instructions: "Respond only with a horoscope generated by a tool.",
tools,
input,
});
// 5. The model should be able to give a response!
console.log("Final output:");
console.log(response.output_text); 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 from openai import OpenAI
import json
client = OpenAI()
# 1. Define a list of callable tools for the model
tools = [
{
"type" : "function" ,
"name" : "get_horoscope" ,
"strict" : True ,
"description" : "Get today's horoscope for an astrological sign." ,
"parameters" : {
"type" : "object" ,
"properties" : {
"sign" : {
"type" : "string" ,
"description" : "An astrological sign like Taurus or Aquarius" ,
},
},
"required" : [ "sign" ],
"additionalProperties" : False ,
},
},
]
def get_horoscope (sign):
return f " { sign } : Next Tuesday you will befriend a baby otter."
# Create a running input list we will add to over time
input_list = [{ "role" : "user" , "content" : "What is my horoscope? I am an Aquarius." }]
# 2. Prompt the model with tools defined
response = client.responses.create(
model = "gpt-6-astra" ,
tools = tools,
input = input_list,
)
# Save function call outputs for subsequent requests
input_list += response.output
for item in response.output:
if item.type == "function_call" :
if item.name == "get_horoscope" :
# 3. Execute the function logic for get_horoscope
sign = json.loads(item.arguments)[ "sign" ]
horoscope = get_horoscope(sign)
# 4. Provide function call results to the model
input_list.append(
{
"type" : "function_call_output" ,
"call_id" : item.call_id,
"output" : horoscope,
}
)
response = client.responses.create(
model = "gpt-6-astra" ,
instructions = "Respond only with a horoscope generated by a tool." ,
tools = tools,
input = input_list,
)
# 5. The model should be able to give a response!
print (response.output_text) 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 package main
import (
"context"
"encoding/json"
"fmt"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/responses"
)
func main() {
client := openai.NewClient()
tool := horoscopeResponseTool()
response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{
Model: "gpt-6-astra",
Input: responses.ResponseNewParamsInputUnion{OfString: openai.String("What is my horoscope? I am an Aquarius.")},
Tools: []responses.ToolUnionParam{tool},
})
if err != nil {
panic(err)
}
var functionOutput responses.ResponseInputItemUnionParam
for _, output := range response.Output {
if output.Type != "function_call" {
continue
}
call := output.AsFunctionCall()
if call.Name != "get_horoscope" {
continue
}
var arguments struct {
Sign string `json:"sign"`
}
if err := json.Unmarshal([]byte(call.Arguments), &arguments); err != nil {
panic(err)
}
functionOutput = responses.ResponseInputItemParamOfFunctionCallOutput(getHoroscope(arguments.Sign))
functionOutput.OfFunctionCallOutput.CallID = openai.String(call.CallID)
}
if functionOutput.OfFunctionCallOutput == nil {
panic("the model did not call get_horoscope")
}
response, err = client.Responses.New(context.Background(), responses.ResponseNewParams{
Model: "gpt-6-astra",
PreviousResponseID: openai.String(response.ID),
Instructions: openai.String("Respond only with a horoscope generated by a tool."),
Input: responses.ResponseNewParamsInputUnion{OfInputItemList: responses.ResponseInputParam{functionOutput}},
Tools: []responses.ToolUnionParam{tool},
})
if err != nil {
panic(err)
}
fmt.Println(response.OutputText())
}
func horoscopeResponseTool() responses.ToolUnionParam {
parameters := map[string]any{
"type": "object",
"properties": map[string]any{
"sign": map[string]any{"type": "string", "description": "An astrological sign like Taurus or Aquarius"},
},
"required": []string{"sign"},
"additionalProperties": false,
}
tool := responses.ToolParamOfFunction("get_horoscope", parameters, true)
tool.OfFunction.Description = openai.String("Get today's horoscope for an astrological sign.")
return tool
}
func getHoroscope(sign string) string {
return fmt.Sprintf("%s: Next Tuesday you will befriend a baby otter.", sign)
} 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 import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.core.JsonValue;
import com.openai.models.responses.FunctionTool;
import com.openai.models.responses.ResponseCreateParams;
import com.openai.models.responses.ResponseInputItem;
import java.util.List;
import java.util.Map;
FunctionTool horoscope =
FunctionTool.builder()
.name("get_horoscope")
.description("Get today's horoscope for an astrological sign.")
.parameters(
FunctionTool.Parameters.builder()
.putAdditionalProperty("type", JsonValue.from("object"))
.putAdditionalProperty(
"properties",
JsonValue.from(
Map.of(
"sign",
Map.of(
"type", "string",
"description",
"An astrological sign like Taurus or Aquarius"))))
.putAdditionalProperty("required", JsonValue.from(List.of("sign")))
.putAdditionalProperty("additionalProperties", JsonValue.from(false))
.build())
.strict(true)
.build();
var firstResponse =
client
.responses()
.create(
ResponseCreateParams.builder()
.model("gpt-6-astra")
.input("What is my horoscope? I am an Aquarius.")
.addTool(horoscope)
.build());
var functionCall =
firstResponse.output().stream()
.flatMap(item -> item.functionCall().stream())
.filter(call -> call.name().equals("get_horoscope"))
.findFirst()
.orElseThrow(() -> new IllegalStateException("The model did not call get_horoscope"));
record HoroscopeArguments(String sign) {}
String sign = functionCall.arguments(HoroscopeArguments.class).sign();
ResponseCreateParams followUp =
ResponseCreateParams.builder()
.model("gpt-6-astra")
.instructions("Respond only with a horoscope generated by a tool.")
.previousResponseId(firstResponse.id())
.inputOfResponse(
List.of(
ResponseInputItem.ofFunctionCallOutput(
ResponseInputItem.FunctionCallOutput.builder()
.callId(functionCall.callId())
.output(sign + ": Embrace an unexpected opportunity today.")
.build())))
.addTool(horoscope)
.build();
client.responses().create(followUp).output().stream()
.flatMap(item -> item.message().stream())
.flatMap(message -> message.content().stream())
.flatMap(content -> content.outputText().stream())
.forEach(text -> System.out.println(text.text())); 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 require "json"
require "openai"
client = OpenAI::Client.new
tools = [
{
type: :function,
name: "get_horoscope",
description: "Get today's horoscope for an astrological sign.",
parameters: {
type: :object,
properties: { sign: { type: :string } },
required: ["sign"],
additionalProperties: false
},
strict: true
}
]
first_response = client.responses.create(
model: "gpt-6-astra",
input: "What is my horoscope? I am an Aquarius.",
tools: tools
)
function_call = first_response.output.find do |item|
item.is_a?(OpenAI::Models::Responses::ResponseFunctionToolCall) &&
item.name == "get_horoscope"
end
unless function_call.is_a?(OpenAI::Models::Responses::ResponseFunctionToolCall)
raise "The model did not call get_horoscope"
end
arguments = JSON.parse(function_call.arguments, symbolize_names: true)
sign = arguments.fetch(:sign)
response = client.responses.create(
model: "gpt-6-astra",
previous_response_id: first_response.id,
input: [
{
type: :function_call_output,
call_id: function_call.call_id,
output: "#{sign}: Embrace an unexpected opportunity today."
}
],
tools: tools
)
puts(response.output_text)
GPT-5 や o4-mini などのリーズニングモデルを使う場合、モデルの応答でツール呼び出しとともに返された推論項目は、すべてツール呼び出しの出力と一緒にモデルに送り返す必要があります。これらの推論項目の生成に至るまでのやり取りの履歴を保持する方法については、プレフィックス変更時のベストプラクティス を参照してください。
通常、関数は各 API リクエストの tools パラメーターで宣言します。ツール検索 を使うと、アプリケーションは読み込みを遅らせた関数をやり取りの途中で読み込むこともできます。どちらの場合も、呼び出し可能な関数は同じ形式のスキーマを使用します。関数定義には次のプロパティがあります。
フィールド
説明
type
常に function を指定します。
name
関数名(例:get_weather)
description
関数をいつ、どのように使うかについての詳しい説明
parameters
関数の入力引数を定義する JSON スキーマ
strict
関数呼び出しに厳格モードを適用するかどうか
get_weather 関数の定義例を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 {
"type" : "function" ,
"name" : "get_weather" ,
"description" : "Retrieves current weather for the given location." ,
"parameters" : {
"type" : "object" ,
"properties" : {
"location" : {
"type" : "string" ,
"description" : "City and country e.g. Bogotá, Colombia"
},
"units" : {
"type" : "string" ,
"enum" : [ "celsius" , "fahrenheit" ],
"description" : "Units the temperature will be returned in."
}
},
"required" : [ "location" , "units" ],
"additionalProperties" : false
},
"strict" : true
}
parameters は JSON スキーマ で定義するため、プロパティの型、列挙型、説明、ネストしたオブジェクト、再帰的なオブジェクトなど、多くの機能を利用できます。
名前空間を使うと、関連するツールを crm、billing、shipping などの業務領域ごとにまとめられます。名前空間は似たツールを整理するのに役立ちます。特に、CRM 用の検索ツールとサポートチケットシステム用の検索ツールなど、対象システムや用途の異なるツールをモデルが使い分ける必要がある場合に便利です。
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 {
"type" : "namespace" ,
"name" : "crm" ,
"description" : "CRM tools for customer lookup and order management." ,
"tools" : [
{
"type" : "function" ,
"name" : "get_customer_profile" ,
"description" : "Fetch a customer profile by customer ID." ,
"parameters" : {
"type" : "object" ,
"properties" : {
"customer_id" : { "type" : "string" }
},
"required" : [ "customer_id" ],
"additionalProperties" : false
}
},
{
"type" : "function" ,
"name" : "list_open_orders" ,
"description" : "List open orders for a customer ID." ,
"defer_loading" : true ,
"parameters" : {
"type" : "object" ,
"properties" : {
"customer_id" : { "type" : "string" }
},
"required" : [ "customer_id" ],
"additionalProperties" : false
}
}
]
}
モデルから多数のツールを利用できるようにする必要がある場合、tool_search を使って一部またはすべてのツールの読み込みを遅らせることができます。tool_search ツールを使うと、モデルは関連するツールを検索し、自身のコンテキストに追加してから利用できます。対応しているのは gpt-5.4 以降のモデルのみです。詳しくはツール検索ガイド を参照してください。
(任意)pydantic と zod を使った Function Calling 可能な限り、関数スキーマは直接定義してください。Python と JavaScript のクライアントライブラリには、pydantic と zod のオブジェクトをスキーマに変換するヘルパーもありますが、対応していない機能もあります。
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 import OpenAI from "openai";
import { z } from "zod";
import { zodFunction } from "openai/helpers/zod";
const openai = new OpenAI();
const GetWeatherParameters = z.object({
location: z.string().describe("City and country e.g. Bogotá, Colombia"),
});
const tools = [
zodFunction({ name: "getWeather", parameters: GetWeatherParameters }),
];
const messages = [
{ role: "user", content: "What's the weather like in Paris today?" },
];
const response = await openai.chat.completions.create({
model: "gpt-5.6-terra",
reasoning_effort: "none",
messages,
tools,
store: true,
});
console.log(response.choices[0].message.tool_calls); 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 from openai import OpenAI, pydantic_function_tool
from pydantic import BaseModel, Field
client = OpenAI()
class GetWeather ( BaseModel ):
location: str = Field( ... , description = "City and country e.g. Bogotá, Colombia" )
tools = [pydantic_function_tool(GetWeather)]
completion = client.chat.completions.create(
model = "gpt-5.6-terra" ,
reasoning_effort = "none" ,
messages = [{ "role" : "user" , "content" : "What's the weather like in Paris today?" }],
tools = tools,
)
print (completion.choices[ 0 ].message.tool_calls)
関数名、パラメーターの説明、指示は明確かつ具体的に記述してください。
関数の目的と各パラメーターの用途 (および形式)を明示し、出力が何を表すのかを説明してください。
各関数を使う場面と使わない場面をシステムプロンプトに記述してください。 原則として、モデルが何をすべきかを正確に 伝えます。
具体例やエッジケースを含めてください。 特に、同じ失敗を繰り返す場合に役立ちます。(注意 :例を追加すると、リーズニングモデル では性能が低下することがあります。)
読み込みを遅らせるツールでは、詳しい使い方は関数の説明に記述し、名前空間の説明は簡潔にしてください。 モデルは名前空間の説明をもとに読み込む対象を選び、関数の説明をもとに読み込んだツールを正しく使います。
ソフトウェアエンジニアリングのベストプラクティスを適用してください。
関数の動作を予測しやすく、直感的に理解できるようにしてください (驚き最小の原則 )。
列挙型を使い 、オブジェクトの構造を工夫して、無効な状態を表現できないようにしてください。たとえば、toggle_light(on: bool, off: bool) では無効な引数の組み合わせでも呼び出せてしまいます。
インターンでも使えるか確認してください。 モデルに与えた情報だけで、インターンなど初めて使う人が関数を正しく使えるでしょうか。使えない場合は、どのような質問が出るかを確認し、その答えをプロンプトに追加してください。
モデルの負担を減らし、可能な処理はコードで実行してください。
既知の引数をモデルに指定させないでください。 たとえば、前のメニューで order_id が判明している場合は、order_id パラメーターを含めず、submit_refund() を引数なしで定義し、コード内で order_id を渡してください。
常に連続して呼び出す関数はまとめてください。 たとえば、mark_location() を必ず query_location() の直後に呼び出す場合は、場所をマークする処理を検索関数に組み込みます。
精度を高めるため、最初に利用できる関数の数を絞ってください。
関数の数を変えて性能を評価してください 。
各ターンの開始時に利用できる関数は 20 個未満を目安にしてください。 これは厳密な上限ではありません。
ツール検索を使い 、すべてのツールを最初から公開するのではなく、規模の大きいツール群や使用頻度の低いツールの読み込みを遅らせてください。
OpenAI のリソースを活用してください。
内部では、モデルが学習した構文で関数定義がシステムメッセージに挿入されます。そのため、呼び出し可能な関数の定義もモデルのコンテキスト上限に算入され、入力トークンとして課金されます。トークン上限に達する場合は、最初に読み込む関数の数を減らす、可能な範囲で説明を短くする、またはツール検索 を使って必要なときだけツールを読み込むことをおすすめします。
ツールの仕様に多数の関数を定義している場合は、ファインチューニング でトークン使用量を削減することもできます。
モデルが関数を呼び出したら、アプリケーションはその関数を実行し、結果を返す必要があります。モデルの応答に含まれる呼び出しは 0 件、1 件、または複数件なので、複数件を処理できるように実装してください。
応答には tool_calls 配列が含まれます。各要素には id(後で関数の結果を送信する際に使用)と function があり、後者には name と JSON 形式にエンコードされた arguments が含まれます。
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 [
{
"id" : "call_12345xyz" ,
"type" : "function" ,
"function" : {
"name" : "get_weather" ,
"arguments" : "{ \" location \" : \" Paris, France \" }"
}
},
{
"id" : "call_67890abc" ,
"type" : "function" ,
"function" : {
"name" : "get_weather" ,
"arguments" : "{ \" location \" : \" Bogotá, Colombia \" }"
}
},
{
"id" : "call_99999def" ,
"type" : "function" ,
"function" : {
"name" : "send_email" ,
"arguments" : "{ \" to \" : \" bob@email.com \" , \" body \" : \" Hi bob \" }"
}
}
] 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 messages.push(completion.choices[0].message);
for (const toolCall of completion.choices[0].message.tool_calls ?? []) {
if (toolCall.type !== "function") continue;
const name = toolCall.function.name;
const args = JSON.parse(toolCall.function.arguments);
const result = await callFunction(name, args);
messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: result.toString(),
});
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14 messages.append(completion.choices[ 0 ].message)
for tool_call in completion.choices[ 0 ].message.tool_calls or []:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
result = call_function(name, args)
messages.append(
{
"role" : "tool" ,
"tool_call_id" : tool_call.id,
"content" : json.dumps(result),
}
) 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 messages = append(messages, completion.Choices[0].Message.ToParam())
for _, toolCall := range completion.Choices[0].Message.ToolCalls {
if toolCall.Type != "function" {
continue
}
var arguments functionArguments
if err := json.Unmarshal([]byte(toolCall.Function.Arguments), &arguments); err != nil {
panic(err)
}
result, err := callFunction(toolCall.Function.Name, arguments)
if err != nil {
panic(err)
}
messages = append(messages, openai.ToolMessage(result, toolCall.ID))
} 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 import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.core.JsonValue;
import com.openai.core.ObjectMappers;
import com.openai.models.FunctionDefinition;
import com.openai.models.FunctionParameters;
import com.openai.models.ReasoningEffort;
import com.openai.models.chat.completions.ChatCompletionCreateParams;
import com.openai.models.chat.completions.ChatCompletionToolMessageParam;
import java.util.List;
import java.util.Map;
var assistant = client.chat().completions().create(params).choices().get(0).message();
var history = params.toBuilder().addMessage(assistant);
for (var item : assistant.toolCalls().orElse(List.of())) {
var call = item.asFunction();
String output;
if (call.function().name().equals("get_weather")) {
record Coordinates(double latitude, double longitude) {}
Coordinates coordinates = call.function().arguments(Coordinates.class);
output =
ObjectMappers.jsonMapper()
.writeValueAsString(
Map.of(
"latitude", coordinates.latitude(),
"longitude", coordinates.longitude(),
"temperature_c", 18));
} else if (call.function().name().equals("send_email")) {
record Email(String to, String body) {}
Email message = call.function().arguments(Email.class);
output =
ObjectMappers.jsonMapper()
.writeValueAsString(Map.of("to", message.to(), "status", "sent"));
} else {
throw new IllegalArgumentException("Unknown function: " + call.function().name());
}
history.addMessage(
ChatCompletionToolMessageParam.builder().toolCallId(call.id()).content(output).build());
}
System.out.println(ObjectMappers.jsonMapper().writeValueAsString(history.build().messages())); 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 message = completion.choices.fetch(0).message
messages << message
Array(message.tool_calls).each do |tool_call|
next unless tool_call.is_a?(
OpenAI::Models::Chat::ChatCompletionMessageFunctionToolCall
)
name = tool_call.function.name
arguments = JSON.parse(tool_call.function.arguments)
result = call_function(name, arguments)
messages << {
role: :tool,
tool_call_id: tool_call.id,
content: JSON.generate(result)
}
end
応答の output 配列には、type の値が function_call の項目が含まれます。各項目には call_id(後で関数の結果を送信する際に使用)、name、JSON 形式にエンコードされた arguments があります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 [
{
"id" : "fc_12345xyz" ,
"call_id" : "call_12345xyz" ,
"type" : "function_call" ,
"name" : "get_weather" ,
"arguments" : "{ \" location \" : \" Paris, France \" }"
},
{
"id" : "fc_67890abc" ,
"call_id" : "call_67890abc" ,
"type" : "function_call" ,
"name" : "get_weather" ,
"arguments" : "{ \" location \" : \" Bogotá, Colombia \" }"
},
{
"id" : "fc_99999def" ,
"call_id" : "call_99999def" ,
"type" : "function_call" ,
"name" : "send_email" ,
"arguments" : "{ \" to \" : \" bob@email.com \" , \" body \" : \" Hi bob \" }"
}
] ツール検索 を使っている場合、tool_search_call と tool_search_output の項目が function_call の前に現れることもあります。関数を読み込んだ後は、ここで示した方法で関数呼び出しを処理してください。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 import { toResponseInputItems } from "openai/lib/responses/ResponseInputItems";
input.push(...toResponseInputItems(response.output));
for (const toolCall of response.output) {
if (toolCall.type !== "function_call") {
continue;
}
const name = toolCall.name;
const args = JSON.parse(toolCall.arguments);
const result = await callFunction(name, args);
input.push({
type: "function_call_output",
call_id: toolCall.call_id,
output: result.toString(),
});
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 input_messages += response.output
for tool_call in response.output:
if tool_call.type != "function_call" :
continue
name = tool_call.name
args = json.loads(tool_call.arguments)
result = call_function(name, args)
input_messages.append(
{
"type" : "function_call_output" ,
"call_id" : tool_call.call_id,
"output" : json.dumps(result),
}
) 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 input = append(input, responseOutputAsInput(response.Output)...)
for _, output := range response.Output {
if output.Type != "function_call" {
continue
}
toolCall := output.AsFunctionCall()
var arguments functionArguments
if err := json.Unmarshal([]byte(toolCall.Arguments), &arguments); err != nil {
panic(err)
}
result, err := callFunction(toolCall.Name, arguments)
if err != nil {
panic(err)
}
toolOutput := responses.ResponseInputItemParamOfFunctionCallOutput(result)
toolOutput.OfFunctionCallOutput.CallID = openai.String(toolCall.CallID)
input = append(input, toolOutput)
} 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 import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.core.JsonValue;
import com.openai.core.ObjectMappers;
import com.openai.models.responses.EasyInputMessage;
import com.openai.models.responses.FunctionTool;
import com.openai.models.responses.ResponseCreateParams;
import com.openai.models.responses.ResponseInputItem;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
response.output().stream()
.map(item -> JsonValue.from(item).convert(ResponseInputItem.class))
.forEach(input::add);
for (var item : response.output()) {
if (item.functionCall().isEmpty()) continue;
var call = item.functionCall().get();
String result;
if (call.name().equals("get_weather")) {
record Coordinates(double latitude, double longitude) {}
Coordinates coordinates = call.arguments(Coordinates.class);
result =
ObjectMappers.jsonMapper()
.writeValueAsString(
Map.of(
"latitude", coordinates.latitude(),
"longitude", coordinates.longitude(),
"temperature_c", 18));
} else if (call.name().equals("send_email")) {
record Email(String to, String body) {}
Email message = call.arguments(Email.class);
result =
ObjectMappers.jsonMapper()
.writeValueAsString(Map.of("to", message.to(), "status", "sent"));
} else {
throw new IllegalArgumentException("Unknown function: " + call.name());
}
var output =
ResponseInputItem.ofFunctionCallOutput(
ResponseInputItem.FunctionCallOutput.builder()
.callId(call.callId())
.output(result)
.build());
input.add(output);
}
System.out.println(ObjectMappers.jsonMapper().writeValueAsString(input)); 1
2
3
4
5
6
7
8
9
10
11
12
13
14 input.concat(response.output)
response.output.each do |tool_call|
next unless tool_call.is_a?(OpenAI::Models::Responses::ResponseFunctionToolCall)
arguments = JSON.parse(tool_call.arguments)
result = call_function(tool_call.name, arguments)
input << {
type: :function_call_output,
call_id: tool_call.call_id,
output: JSON.generate(result)
}
end
上の例では、call_function という関数が各呼び出しを対応する処理に振り分けると仮定しています。実装例を示します。
1
2
3
4
5
6
7
8
9 const callFunction = async (name, args) => {
if (name === "get_weather") {
return getWeather(args.latitude, args.longitude);
}
if (name === "send_email") {
return sendEmail(args.to, args.body);
}
throw new Error(`Unknown function: ${name}`);
}; 1
2
3
4
5
6 def call_function (name, args):
if name == "get_weather" :
return get_weather( ** args)
if name == "send_email" :
return send_email( ** args)
raise ValueError ( f "Unknown function: { name } " ) 1
2
3
4
5
6
7
8
9
10 func callFunction(name string, arguments functionArguments) (string, error) {
switch name {
case "get_weather":
return getWeather(arguments.Location), nil
case "send_email":
return sendEmail(arguments.To, arguments.Body), nil
default:
return "", fmt.Errorf("unknown function: %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 import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;
var argsJson = new ObjectMapper().readTree("{\"location\":\"Paris, France\"}");
System.out.println(callFunction("get_weather", argsJson));
// These local fixtures demonstrate dispatch; replace them with your application services.
// The email fixture prints its inputs and does not send a message.
static Map<String, Object> callFunction(String name, JsonNode arguments) {
return switch (name) {
case "get_weather" -> getWeather(arguments.get("location").asText());
case "send_email" -> sendEmail(arguments.get("to").asText(), arguments.get("body").asText());
default -> throw new IllegalArgumentException("Unknown function: " + name);
};
}
private static Map<String, Object> getWeather(String location) {
int temperature =
switch (location) {
case "Bogotá, Colombia" -> 18;
case "Paris, France" -> 15;
default -> 20;
};
return Map.of("location", location, "temperature_celsius", temperature);
}
private static Map<String, Object> sendEmail(String to, String body) {
System.out.println("Sending email to " + to + ": " + body);
return Map.of("status", "sent");
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 using System.Text.Json;
using JsonDocument arguments = JsonDocument.Parse("{\"location\":\"Paris, France\"}");
Console.WriteLine(FunctionDispatcher.CallFunction("get_weather", arguments.RootElement));
internal static class FunctionDispatcher
{
// These local fixtures demonstrate dispatch; replace them with your application services.
// The email fixture prints its inputs and does not send a message.
internal static string CallFunction(string name, JsonElement arguments) => name switch
{
"get_weather" => GetWeather(arguments.GetProperty("location").GetString()!),
"send_email" => SendEmail(arguments.GetProperty("to").GetString()!, arguments.GetProperty("body").GetString()!),
_ => throw new ArgumentException($"Unknown function: {name}", nameof(name))
};
private static string GetWeather(string location) => JsonSerializer.Serialize(new { location, temperature_celsius = location switch { "Bogotá, Colombia" => 18, "Paris, France" => 15, _ => 20 } });
private static string SendEmail(string to, string body)
{
Console.WriteLine($"Sending email to {to}: {body}");
return "{\"status\":\"sent\"}";
}
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 def call_function(name, arguments)
case name
when "get_weather"
FunctionCallingExample.get_weather(
arguments.fetch("latitude"),
arguments.fetch("longitude")
)
when "send_email"
FunctionCallingExample.send_email(
arguments.fetch("to"),
arguments.fetch("body")
)
else
raise ArgumentError, "Unknown function: #{name}"
end
end
function_call_output メッセージで渡す結果は通常、文字列にします。形式は JSON、エラーコード、プレーンテキストなどから自由に選べます。モデルは必要に応じてその文字列を解釈します。
画像やファイルを返す関数の場合、文字列の代わりに画像またはファイルのオブジェクトの配列 を渡せます。
戻り値のない関数(例:send_email)では、"success" など、成功または失敗を示す文字列を返してください。
結果を messages に追加し、モデルに送り返すと、最終応答を取得できます。
1
2
3
4
5
6
7 const completion = await openai.chat.completions.create({
model: "gpt-5.6-terra",
reasoning_effort: "none",
messages,
tools,
store: true,
}); 1
2
3
4
5
6 completion = client.chat.completions.create(
model = "gpt-5.6-terra" ,
reasoning_effort = "none" ,
messages = messages,
tools = chat_tools,
) 1
2
3
4
5
6
7
8
9 completion, err = client.Chat.Completions.New(context.Background(), openai.ChatCompletionNewParams{
Model: "gpt-5.6-terra",
Messages: messages,
Tools: tools,
ReasoningEffort: shared.ReasoningEffortNone,
})
if err != nil {
panic(err)
} 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 import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.core.JsonValue;
import com.openai.models.FunctionDefinition;
import com.openai.models.FunctionParameters;
import com.openai.models.ReasoningEffort;
import com.openai.models.chat.completions.ChatCompletionAssistantMessageParam;
import com.openai.models.chat.completions.ChatCompletionCreateParams;
import com.openai.models.chat.completions.ChatCompletionMessageFunctionToolCall;
import com.openai.models.chat.completions.ChatCompletionToolMessageParam;
import java.util.List;
import java.util.Map;
FunctionDefinition weather =
FunctionDefinition.builder()
.name("get_weather")
.description("Get the weather for a city.")
.parameters(
FunctionParameters.builder()
.putAdditionalProperty("type", JsonValue.from("object"))
.putAdditionalProperty(
"properties", JsonValue.from(Map.of("city", Map.of("type", "string"))))
.putAdditionalProperty("required", JsonValue.from(List.of("city")))
.putAdditionalProperty("additionalProperties", JsonValue.from(false))
.build())
.strict(true)
.build();
ChatCompletionCreateParams params =
ChatCompletionCreateParams.builder()
.model("gpt-5.6-terra")
.reasoningEffort(ReasoningEffort.NONE)
.addUserMessage("What is the weather in Paris?")
.addMessage(
ChatCompletionAssistantMessageParam.builder()
.addToolCall(
ChatCompletionMessageFunctionToolCall.builder()
.id("call_weather")
.function(
ChatCompletionMessageFunctionToolCall.Function.builder()
.name("get_weather")
.arguments("{\"city\":\"Paris\"}")
.build())
.build())
.build())
.addMessage(
ChatCompletionToolMessageParam.builder()
.toolCallId("call_weather")
.content("{\"city\":\"Paris\",\"temperature_c\":18}")
.build())
.addFunctionTool(weather)
.build();
client.chat().completions().create(params).choices().stream()
.flatMap(choice -> choice.message().content().stream())
.forEach(System.out::println); 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 require "openai"
client = OpenAI::Client.new
completion = client.chat.completions.create(
model: "gpt-5.6-terra",
reasoning_effort: :none,
messages: [
{
role: :user,
content: "What is the weather in Paris?"
},
{
role: :assistant,
tool_calls: [
{
id: "call_weather",
type: :function,
function: {
name: "get_weather",
arguments: '{"city":"Paris"}'
}
}
]
},
{
role: :tool,
tool_call_id: "call_weather",
content: '{"city":"Paris","temperature_c":18}'
}
],
tools: [
{
type: :function,
function: {
name: "get_weather",
description: "Get the weather for a city",
parameters: {
type: :object,
properties: { city: { type: :string } },
required: ["city"],
additionalProperties: false
},
strict: true
}
}
]
)
puts(completion.choices.fetch(0).message.content)
結果を input に追加し、モデルに送り返すと、最終応答を取得できます。
1
2
3
4
5 const response = await openai.responses.create({
model: "gpt-6-astra",
input,
tools,
}); 1
2
3
4
5 response = client.responses.create(
model = "gpt-6-astra" ,
input = input_messages,
tools = responses_tools,
) 1
2
3
4
5
6
7
8 response, err = client.Responses.New(context.Background(), responses.ResponseNewParams{
Model: "gpt-6-astra",
Input: responses.ResponseNewParamsInputUnion{OfInputItemList: input},
Tools: tools,
})
if err != nil {
panic(err)
} 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 import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.core.JsonValue;
import com.openai.models.responses.EasyInputMessage;
import com.openai.models.responses.FunctionTool;
import com.openai.models.responses.ResponseCreateParams;
import com.openai.models.responses.ResponseFunctionToolCall;
import com.openai.models.responses.ResponseInputItem;
import java.util.List;
import java.util.Map;
FunctionTool weather =
FunctionTool.builder()
.name("get_weather")
.description("Get the weather for a city.")
.parameters(
FunctionTool.Parameters.builder()
.putAdditionalProperty("type", JsonValue.from("object"))
.putAdditionalProperty(
"properties", JsonValue.from(Map.of("city", Map.of("type", "string"))))
.putAdditionalProperty("required", JsonValue.from(List.of("city")))
.putAdditionalProperty("additionalProperties", JsonValue.from(false))
.build())
.strict(true)
.build();
ResponseCreateParams params =
ResponseCreateParams.builder()
.model("gpt-6-astra")
.inputOfResponse(
List.of(
ResponseInputItem.ofEasyInputMessage(
EasyInputMessage.builder()
.role(EasyInputMessage.Role.USER)
.content("What is the weather like in Paris?")
.build()),
ResponseInputItem.ofFunctionCall(
ResponseFunctionToolCall.builder()
.callId("call_weather")
.name("get_weather")
.arguments("{\"city\":\"Paris\"}")
.build()),
ResponseInputItem.ofFunctionCallOutput(
ResponseInputItem.FunctionCallOutput.builder()
.callId("call_weather")
.output("{\"city\":\"Paris\",\"temperature_c\":18}")
.build())))
.addTool(weather)
.build();
client.responses().create(params).output().stream()
.flatMap(item -> item.message().stream())
.flatMap(message -> message.content().stream())
.flatMap(content -> content.outputText().stream())
.forEach(text -> System.out.println(text.text())); 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 require "openai"
client = OpenAI::Client.new
input = [
{
role: :user,
content: "What is the weather like in Paris?"
},
{
type: :function_call,
call_id: "call_weather",
name: "get_weather",
arguments: '{"city":"Paris"}'
},
{
type: :function_call_output,
call_id: "call_weather",
output: '{"city":"Paris","temperature_c":18}'
}
]
tools = [
{
type: :function,
name: "get_weather",
description: "Get the weather for a city",
parameters: {
type: :object,
properties: { city: { type: :string } },
required: ["city"],
additionalProperties: false
},
strict: true
}
]
response = client.responses.create(
model: "gpt-6-astra",
input: input,
tools: tools
)
puts(response.output_text)
"It's about 15°C in Paris, 18°C in Bogotá, and I've sent that email to Bob."
デフォルトでは、いつ、いくつのツールを使うかをモデルが判断します。tool_choice パラメーターで動作を指定できます。
自動 (デフォルト ):関数を呼び出さないか、1 つ以上呼び出します。tool_choice: "auto"
必須 :1 つ以上の関数を呼び出します。
tool_choice: "required"
関数を指定 :指定した関数だけを 1 回呼び出します。
tool_choice: {"type": "function", "name": "get_weather"}
許可するツール :モデルが呼び出せるツールを、利用可能なツールの一部に限定します。
allowed_tools の用途
allowed_tools の一覧を設定すると、モデルに渡すツール一覧は変更せずに、各リクエストで利用できるツールを一部に限定できます。これにより、プロンプトキャッシュ によるコスト削減効果を最大限に活かせます。
1 2 3 4 5 6 7 8 9 "tool_choice" : {
"type" : "allowed_tools" ,
"mode" : "auto" ,
"tools" : [
{ "type" : "function" , "name" : "get_weather" },
{ "type" : "function" , "name" : "search_docs" }
]
}
}
tool_choice を "none" に設定すると、関数を渡さなかった場合と同じ動作になります。
ツール検索を使う場合も、tool_choice はそのターンで呼び出し可能なツールに適用されます。一部のツールを読み込んだ後、モデルが使えるツールをその範囲に限定したい場合に特に便利です。
GPT-5 以降の対応モデルでは、組み込みツール も利用可能な状態で、関数を並列に呼び出せます。ただし、並列で実行する関数呼び出しのバッチに組み込みツールを含めることはできません。
モデルは 1 ターンで複数の関数を呼び出す場合があります。これを防ぐには parallel_tool_calls を false に設定してください。ツール呼び出しは 0 件または 1 件に限定されます。
注意 :現在、ファインチューニング済みのモデルが 1 ターンで複数の関数を呼び出すと、それらの呼び出しでは厳格モード が無効になります。
gpt-4.1-nano-2025-04-14 に関する注意 :この gpt-4.1-nano のスナップショットでは、並列ツール呼び出しを有効にすると、同じツールに対する呼び出しが複数含まれる場合があります。このスナップショットでは、並列ツール呼び出しを無効にすることをおすすめします。
strict を true に設定すると、関数呼び出しが確実に関数スキーマに従うようになります。厳格モードは常に有効にすることをおすすめします。
厳格モードは内部で構造化出力 を使用するため、次の要件があります。
additionalProperties は false に設定する必要があります。これは parameters 内のすべてのオブジェクトに適用されます。
properties のすべてのフィールドを required に指定する必要があります。
任意のフィールドを表すには、null を type の選択肢に追加します(下の例を参照)。
strict: true を指定してスキーマが上記の要件を満たしていない場合、リクエストは拒否され、不足している制約の詳細が返されます。strict を省略した場合の動作は API によって異なります。Responses では、可能であればスキーマを厳格モードに適合するように正規化します。適合させられない場合は、厳格モードを使わないベストエフォートの Function Calling に切り替わります。その場合、応答に含まれるツールには strict: false と表示されます。Chat Completions では、デフォルトで厳格モードは無効です。Responses で厳格モードを使わず、ベストエフォートの Function Calling を維持するには、strict: false を明示的に設定してください。
Strict mode enabled Strict mode disabled 厳格モードが有効
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 {
"type" : "function" ,
"function" : {
"name" : "get_weather" ,
"description" : "Retrieves current weather for the given location." ,
"strict" : true ,
"parameters" : {
"type" : "object" ,
"properties" : {
"location" : {
"type" : "string" ,
"description" : "City and country e.g. Bogotá, Colombia"
},
"units" : {
"type" : [ "string" , "null" ],
"enum" : [ "celsius" , "fahrenheit" ],
"description" : "Units the temperature will be returned in."
}
},
"required" : [ "location" , "units" ],
"additionalProperties" : false
}
}
} 厳格モードが無効
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 {
"type" : "function" ,
"function" : {
"name" : "get_weather" ,
"description" : "Retrieves current weather for the given location." ,
"parameters" : {
"type" : "object" ,
"properties" : {
"location" : {
"type" : "string" ,
"description" : "City and country e.g. Bogotá, Colombia"
},
"units" : {
"type" : "string" ,
"enum" : [ "celsius" , "fahrenheit" ],
"description" : "Units the temperature will be returned in."
}
},
"required" : [ "location" ],
}
}
}
Strict mode enabled Strict mode disabled 厳格モードが有効
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 {
"type" : "function" ,
"name" : "get_weather" ,
"description" : "Retrieves current weather for the given location." ,
"strict" : true ,
"parameters" : {
"type" : "object" ,
"properties" : {
"location" : {
"type" : "string" ,
"description" : "City and country e.g. Bogotá, Colombia"
},
"units" : {
"type" : [ "string" , "null" ],
"enum" : [ "celsius" , "fahrenheit" ],
"description" : "Units the temperature will be returned in."
}
},
"required" : [ "location" , "units" ],
"additionalProperties" : false
}
} 厳格モードが無効
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 {
"type" : "function" ,
"name" : "get_weather" ,
"description" : "Retrieves current weather for the given location." ,
"parameters" : {
"type" : "object" ,
"properties" : {
"location" : {
"type" : "string" ,
"description" : "City and country e.g. Bogotá, Colombia"
},
"units" : {
"type" : "string" ,
"enum" : [ "celsius" , "fahrenheit" ],
"description" : "Units the temperature will be returned in."
}
},
"required" : [ "location" ],
}
}
厳格モードを有効にすることをおすすめしますが、次の制限があります。
JSON スキーマの一部の機能には対応していません(対応するスキーマ を参照)。
ファインチューニング済みのモデルには、次の制限もあります。
スキーマは初回のリクエスト時に追加の処理を経て、キャッシュされます。リクエストごとにスキーマが異なる場合、レイテンシが増加することがあります。
スキーマは性能向上のためにキャッシュされるので、ゼロデータ保持 の対象外です。
ストリーミングを使うと、モデルが引数を生成している間に、どの関数を呼び出しているかを表示して進捗を伝えられます。引数自体もリアルタイムで表示できます。
関数呼び出しのストリーミングは、通常の応答のストリーミングと同じ仕組みです。stream を true に設定すると、delta オブジェクトを含むチャンクを受け取ります。
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 import { OpenAI } from "openai";
const openai = new OpenAI();
const tools = [
{
type: "function",
function: {
name: "get_weather",
description: "Get current temperature for a given location.",
parameters: {
type: "object",
properties: {
location: {
type: "string",
description: "City and country e.g. Bogotá, Colombia",
},
},
required: ["location"],
additionalProperties: false,
},
strict: true,
},
},
];
const stream = await openai.chat.completions.create({
model: "gpt-5.6-terra",
reasoning_effort: "none",
messages: [
{ role: "user", content: "What's the weather like in Paris today?" },
],
tools,
tool_choice: "required",
stream: true,
store: true,
});
for await (const chunk of stream) {
const delta = chunk.choices[0].delta;
console.log(delta.tool_calls);
} 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 from openai import OpenAI
client = OpenAI()
tools = [
{
"type" : "function" ,
"function" : {
"name" : "get_weather" ,
"description" : "Get current temperature for a given location." ,
"parameters" : {
"type" : "object" ,
"properties" : {
"location" : {
"type" : "string" ,
"description" : "City and country e.g. Bogotá, Colombia" ,
}
},
"required" : [ "location" ],
"additionalProperties" : False ,
},
"strict" : True ,
},
}
]
stream = client.chat.completions.create(
model = "gpt-5.6-terra" ,
reasoning_effort = "none" ,
messages = [{ "role" : "user" , "content" : "What's the weather like in Paris today?" }],
tools = tools,
stream = True ,
)
for chunk in stream:
delta = chunk.choices[ 0 ].delta
print (delta.tool_calls) 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 package main
import (
"context"
"fmt"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/shared"
)
func main() {
client := openai.NewClient()
parameters := map[string]any{
"type": "object",
"properties": map[string]any{
"location": map[string]any{"type": "string", "description": "City and country e.g. Bogotá, Colombia"},
},
"required": []string{"location"},
"additionalProperties": false,
}
tool := openai.ChatCompletionToolUnionParam{OfFunction: &openai.ChatCompletionFunctionToolParam{
Function: shared.FunctionDefinitionParam{Name: "get_weather", Parameters: parameters, Strict: openai.Bool(true)},
}}
stream := client.Chat.Completions.NewStreaming(context.Background(), openai.ChatCompletionNewParams{
Model: "gpt-5.6-terra",
Messages: []openai.ChatCompletionMessageParamUnion{
openai.UserMessage("What's the weather like in Paris today?"),
},
Tools: []openai.ChatCompletionToolUnionParam{tool},
ReasoningEffort: shared.ReasoningEffortNone,
})
for stream.Next() {
if len(stream.Current().Choices) > 0 {
fmt.Println(stream.Current().Choices[0].Delta.ToolCalls)
}
}
if err := stream.Err(); err != nil {
panic(err)
}
} 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 import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.core.JsonValue;
import com.openai.core.http.StreamResponse;
import com.openai.models.FunctionDefinition;
import com.openai.models.FunctionParameters;
import com.openai.models.ReasoningEffort;
import com.openai.models.chat.completions.ChatCompletionChunk;
import com.openai.models.chat.completions.ChatCompletionCreateParams;
import java.util.List;
import java.util.Map;
FunctionDefinition weather =
FunctionDefinition.builder()
.name("get_weather")
.description("Get the weather for a city.")
.parameters(
FunctionParameters.builder()
.putAdditionalProperty("type", JsonValue.from("object"))
.putAdditionalProperty(
"properties", JsonValue.from(Map.of("city", Map.of("type", "string"))))
.putAdditionalProperty("required", JsonValue.from(List.of("city")))
.putAdditionalProperty("additionalProperties", JsonValue.from(false))
.build())
.strict(true)
.build();
ChatCompletionCreateParams params =
ChatCompletionCreateParams.builder()
.model("gpt-5.6-terra")
.reasoningEffort(ReasoningEffort.NONE)
.addUserMessage("What is the weather in Paris?")
.addFunctionTool(weather)
.build();
try (StreamResponse<ChatCompletionChunk> stream =
client.chat().completions().createStreaming(params)) {
stream.stream()
.flatMap(chunk -> chunk.choices().stream())
.flatMap(choice -> choice.delta().toolCalls().stream())
.flatMap(List::stream)
.forEach(System.out::println);
} 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 require "openai"
client = OpenAI::Client.new
stream = client.chat.completions.stream(
model: "gpt-5.6-terra",
reasoning_effort: :none,
messages: [
{
role: :user,
content: "What is the weather in Paris?"
}
],
tools: [
{
type: :function,
function: {
name: "get_weather",
description: "Get the weather for a city",
parameters: {
type: :object,
properties: { city: { type: :string } },
required: ["city"],
additionalProperties: false
},
strict: true
}
}
]
)
stream.each do |event|
next unless event.is_a?(OpenAI::Helpers::Streaming::ChatChunkEvent)
puts(event.chunk.choices.first&.delta&.tool_calls)
end 1
2
3
4
5
6
7
8
9 [{ "index" : 0 , "id" : "call_DdmO9pD3xa9XTPNJ32zg2hcA" , "function" : { "arguments" : "" , "name" : "get_weather" }, "type" : "function" }]
[{ "index" : 0 , "id" : null , "function" : { "arguments" : "{ \" " , "name" : null }, "type" : null }]
[{ "index" : 0 , "id" : null , "function" : { "arguments" : "location" , "name" : null }, "type" : null }]
[{ "index" : 0 , "id" : null , "function" : { "arguments" : " \" : \" " , "name" : null }, "type" : null }]
[{ "index" : 0 , "id" : null , "function" : { "arguments" : "Paris" , "name" : null }, "type" : null }]
[{ "index" : 0 , "id" : null , "function" : { "arguments" : "," , "name" : null }, "type" : null }]
[{ "index" : 0 , "id" : null , "function" : { "arguments" : " France" , "name" : null }, "type" : null }]
[{ "index" : 0 , "id" : null , "function" : { "arguments" : " \" }" , "name" : null }, "type" : null }]
null ただし、チャンクを 1 つの content 文字列にまとめる代わりに、JSON オブジェクトを文字列にエンコードした arguments を組み立てます。
モデルが 1 つ以上の関数を呼び出すと、tool_calls フィールドに値が設定されます。このフィールドは各 delta にあり、各 tool_call には次のフィールドが含まれます。
フィールド
説明
index
delta がどの関数呼び出しに対応するかを識別するインデックス
id
ツール呼び出しの ID
function
関数呼び出しの差分(name と arguments)
type
tool_call の種類(関数呼び出しでは常に function)
これらのフィールドの多くは、各ツール呼び出しの最初の delta にだけ設定されます。たとえば id、function.name、type が該当します。
以下のコードは、各 delta を集約して最終的な tool_calls オブジェクトを組み立てる例です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 const finalToolCalls = {};
for await (const chunk of stream) {
const toolCalls = chunk.choices[0].delta.tool_calls || [];
for (const toolCall of toolCalls) {
const { index } = toolCall;
const accumulated = (finalToolCalls[index] ??= {
id: toolCall.id,
type: toolCall.type,
function: { name: toolCall.function?.name, arguments: "" },
});
accumulated.id ??= toolCall.id;
accumulated.type ??= toolCall.type;
accumulated.function.name ??= toolCall.function?.name;
accumulated.function.arguments += toolCall.function?.arguments ?? "";
}
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14 final_tool_calls = {}
for chunk in stream:
if not chunk.choices:
continue
for tool_call in chunk.choices[ 0 ].delta.tool_calls or []:
index = tool_call.index
if index not in final_tool_calls:
final_tool_calls[index] = tool_call
elif tool_call.function:
final_tool_calls[index].function.arguments += (
tool_call.function.arguments or ""
) 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 package main
import (
"context"
"fmt"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/shared"
)
func main() {
client := openai.NewClient()
parameters := map[string]any{
"type": "object",
"properties": map[string]any{
"location": map[string]any{"type": "string"},
},
"required": []string{"location"},
"additionalProperties": false,
}
tool := openai.ChatCompletionToolUnionParam{OfFunction: &openai.ChatCompletionFunctionToolParam{
Function: shared.FunctionDefinitionParam{Name: "get_weather", Parameters: parameters, Strict: openai.Bool(true)},
}}
stream := client.Chat.Completions.NewStreaming(context.Background(), openai.ChatCompletionNewParams{
Model: "gpt-5.6-terra",
Messages: []openai.ChatCompletionMessageParamUnion{
openai.UserMessage("What's the weather like in Paris today?"),
},
Tools: []openai.ChatCompletionToolUnionParam{tool},
ReasoningEffort: shared.ReasoningEffortNone,
})
finalToolCalls := map[int64]openai.ChatCompletionChunkChoiceDeltaToolCall{}
for stream.Next() {
chunk := stream.Current()
if len(chunk.Choices) == 0 {
continue
}
for _, toolCall := range chunk.Choices[0].Delta.ToolCalls {
finalToolCall, ok := finalToolCalls[toolCall.Index]
if !ok {
finalToolCalls[toolCall.Index] = toolCall
continue
}
finalToolCall.Function.Arguments += toolCall.Function.Arguments
finalToolCalls[toolCall.Index] = finalToolCall
}
}
if err := stream.Err(); err != nil {
panic(err)
}
fmt.Println(finalToolCalls)
} 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 import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.core.JsonValue;
import com.openai.core.http.StreamResponse;
import com.openai.models.FunctionDefinition;
import com.openai.models.FunctionParameters;
import com.openai.models.ReasoningEffort;
import com.openai.models.chat.completions.ChatCompletionChunk;
import com.openai.models.chat.completions.ChatCompletionCreateParams;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
FunctionDefinition weather =
FunctionDefinition.builder()
.name("get_weather")
.description("Get the weather for a city.")
.parameters(
FunctionParameters.builder()
.putAdditionalProperty("type", JsonValue.from("object"))
.putAdditionalProperty(
"properties", JsonValue.from(Map.of("location", Map.of("type", "string"))))
.putAdditionalProperty("required", JsonValue.from(List.of("location")))
.putAdditionalProperty("additionalProperties", JsonValue.from(false))
.build())
.strict(true)
.build();
ChatCompletionCreateParams params =
ChatCompletionCreateParams.builder()
.model("gpt-5.6-terra")
.reasoningEffort(ReasoningEffort.NONE)
.addUserMessage("What is the weather in Paris?")
.addFunctionTool(weather)
.build();
record ToolCall(String id, String type, String name, StringBuilder arguments) {}
Map<Long, ToolCall> toolCalls = new LinkedHashMap<>();
try (StreamResponse<ChatCompletionChunk> stream =
client.chat().completions().createStreaming(params)) {
stream.stream()
.flatMap(chunk -> chunk.choices().stream())
.flatMap(choice -> choice.delta().toolCalls().stream())
.flatMap(List::stream)
.forEach(
delta -> {
ToolCall toolCall =
toolCalls.computeIfAbsent(
delta.index(),
ignored ->
new ToolCall(
delta.id().orElseThrow(),
delta.type().orElseThrow().asString(),
delta.function().flatMap(function -> function.name()).orElseThrow(),
new StringBuilder()));
delta
.function()
.flatMap(function -> function.arguments())
.ifPresent(toolCall.arguments()::append);
});
}
toolCalls.values().forEach(System.out::println); 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 require "openai"
client = OpenAI::Client.new
stream = client.chat.completions.stream(
model: "gpt-5.6-terra",
reasoning_effort: :none,
messages: [
{
role: :user,
content: "What is the weather in Paris?"
}
],
tools: [
{
type: :function,
function: {
name: "get_weather",
parameters: {
type: :object,
properties: { location: { type: :string } },
required: ["location"],
additionalProperties: false
},
strict: true
}
}
]
)
tool_calls = {}
stream.each do |event|
next unless event.is_a?(OpenAI::Helpers::Streaming::ChatChunkEvent)
(event.chunk.choices.first&.delta&.tool_calls || []).each do |delta|
tool_call = tool_calls[delta.index] ||= {
id: nil,
type: nil,
function: {
name: nil,
arguments: +""
}
}
tool_call[:id] ||= delta.id
tool_call[:type] ||= delta.type
tool_call[:function][:name] ||= delta.function&.name
tool_call[:function][:arguments] << delta.function&.arguments.to_s
end
end
puts(tool_calls.sort.to_h.values) 1
2
3
4
5
6
7
8 {
"index" : 0 ,
"id" : "call_RzfkBpJgzeR0S242qfvjadNe" ,
"function" : {
"name" : "get_weather" ,
"arguments" : "{ \" location \" : \" Paris, France \" }"
}
}
ストリーミングを使うと、モデルが引数を生成している間に、どの関数を呼び出しているかを表示して進捗を伝えられます。引数自体もリアルタイムで表示できます。
関数呼び出しのストリーミングは、通常の応答のストリーミングと同じ仕組みです。stream を true に設定すると、さまざまな種類の event オブジェクトを受け取ります。
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 import { OpenAI } from "openai";
const openai = new OpenAI();
const tools = [
{
type: "function",
name: "get_weather",
description: "Get current temperature for provided coordinates in celsius.",
parameters: {
type: "object",
properties: {
latitude: { type: "number" },
longitude: { type: "number" },
},
required: ["latitude", "longitude"],
additionalProperties: false,
},
strict: true,
},
];
const stream = await openai.responses.create({
model: "gpt-6-astra",
input: [{ role: "user", content: "What's the weather like in Paris today?" }],
tools,
tool_choice: "required",
stream: true,
store: true,
});
for await (const event of stream) {
console.log(event);
} 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 from openai import OpenAI
client = OpenAI()
tools = [
{
"type" : "function" ,
"name" : "get_weather" ,
"strict" : True ,
"description" : "Get current temperature for a given location." ,
"parameters" : {
"type" : "object" ,
"properties" : {
"location" : {
"type" : "string" ,
"description" : "City and country e.g. Bogotá, Colombia" ,
}
},
"required" : [ "location" ],
"additionalProperties" : False ,
},
}
]
stream = client.responses.create(
model = "gpt-6-astra" ,
input = [{ "role" : "user" , "content" : "What's the weather like in Paris today?" }],
tools = tools,
stream = True ,
)
for event in stream:
print (event) 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 package main
import (
"context"
"fmt"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/responses"
)
func main() {
client := openai.NewClient()
parameters := map[string]any{
"type": "object",
"properties": map[string]any{
"location": map[string]any{"type": "string", "description": "City and country e.g. Bogotá, Colombia"},
},
"required": []string{"location"},
"additionalProperties": false,
}
tool := responses.ToolParamOfFunction("get_weather", parameters, true)
stream := client.Responses.NewStreaming(context.Background(), responses.ResponseNewParams{
Model: "gpt-6-astra",
Input: responses.ResponseNewParamsInputUnion{OfString: openai.String("What's the weather like in Paris today?")},
Tools: []responses.ToolUnionParam{tool},
})
for stream.Next() {
fmt.Println(stream.Current().Type)
}
if err := stream.Err(); err != nil {
panic(err)
}
} 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 import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.core.JsonValue;
import com.openai.core.http.StreamResponse;
import com.openai.models.responses.FunctionTool;
import com.openai.models.responses.ResponseCreateParams;
import com.openai.models.responses.ResponseStreamEvent;
import java.util.List;
import java.util.Map;
FunctionTool weather =
FunctionTool.builder()
.name("get_weather")
.description("Get the weather for a city.")
.parameters(
FunctionTool.Parameters.builder()
.putAdditionalProperty("type", JsonValue.from("object"))
.putAdditionalProperty(
"properties", JsonValue.from(Map.of("city", Map.of("type", "string"))))
.putAdditionalProperty("required", JsonValue.from(List.of("city")))
.putAdditionalProperty("additionalProperties", JsonValue.from(false))
.build())
.strict(true)
.build();
ResponseCreateParams params =
ResponseCreateParams.builder()
.model("gpt-6-astra")
.input("What is the weather in Paris?")
.addTool(weather)
.build();
try (StreamResponse<ResponseStreamEvent> stream = client.responses().createStreaming(params)) {
stream.stream().forEach(System.out::println);
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 require "openai"
client = OpenAI::Client.new
stream = client.responses.stream(
model: "gpt-6-astra",
input: "What is the weather in Paris?",
tools: [
{
type: :function,
name: "get_weather",
description: "Get the weather for a city",
parameters: {
type: :object,
properties: { city: { type: :string } },
required: ["city"],
additionalProperties: false
},
strict: true
}
]
)
stream.each { |event| puts(event.type) } 1
2
3
4
5
6
7
8
9
10 { "type" : "response.output_item.added" , "response_id" : "resp_1234xyz" , "output_index" : 0 , "item" :{ "type" : "function_call" , "id" : "fc_1234xyz" , "call_id" : "call_1234xyz" , "name" : "get_weather" , "arguments" : "" }}
{ "type" : "response.function_call_arguments.delta" , "response_id" : "resp_1234xyz" , "item_id" : "fc_1234xyz" , "output_index" : 0 , "delta" : "{ \" " }
{ "type" : "response.function_call_arguments.delta" , "response_id" : "resp_1234xyz" , "item_id" : "fc_1234xyz" , "output_index" : 0 , "delta" : "location" }
{ "type" : "response.function_call_arguments.delta" , "response_id" : "resp_1234xyz" , "item_id" : "fc_1234xyz" , "output_index" : 0 , "delta" : " \" : \" " }
{ "type" : "response.function_call_arguments.delta" , "response_id" : "resp_1234xyz" , "item_id" : "fc_1234xyz" , "output_index" : 0 , "delta" : "Paris" }
{ "type" : "response.function_call_arguments.delta" , "response_id" : "resp_1234xyz" , "item_id" : "fc_1234xyz" , "output_index" : 0 , "delta" : "," }
{ "type" : "response.function_call_arguments.delta" , "response_id" : "resp_1234xyz" , "item_id" : "fc_1234xyz" , "output_index" : 0 , "delta" : " France" }
{ "type" : "response.function_call_arguments.delta" , "response_id" : "resp_1234xyz" , "item_id" : "fc_1234xyz" , "output_index" : 0 , "delta" : " \" }" }
{ "type" : "response.function_call_arguments.done" , "response_id" : "resp_1234xyz" , "item_id" : "fc_1234xyz" , "output_index" : 0 , "arguments" : "{ \" location \" : \" Paris, France \" }" }
{ "type" : "response.output_item.done" , "response_id" : "resp_1234xyz" , "output_index" : 0 , "item" :{ "type" : "function_call" , "id" : "fc_1234xyz" , "call_id" : "call_1234xyz" , "name" : "get_weather" , "arguments" : "{ \" location \" : \" Paris, France \" }" }} ただし、チャンクを 1 つの content 文字列にまとめる代わりに、JSON オブジェクトを文字列にエンコードした arguments を組み立てます。
モデルが 1 つ以上の関数を呼び出すと、関数呼び出しごとに response.output_item.added 型のイベントが発行されます。このイベントには次のフィールドが含まれます。
フィールド
説明
response_id
関数呼び出しを含む応答の ID
output_index
応答内の出力項目のインデックス。応答内の個々の関数呼び出しを識別します。
item
処理中の関数呼び出しの項目。name、arguments、id フィールドを含みます。
続いて、response.function_call_arguments.delta 型のイベントを順次受け取ります。各イベントの delta には arguments フィールドの差分が入ります。イベントには次のフィールドが含まれます。
フィールド
説明
response_id
関数呼び出しを含む応答の ID
item_id
差分が属する関数呼び出し項目の ID
output_index
応答内の出力項目のインデックス。応答内の個々の関数呼び出しを識別します。
delta
arguments フィールドの差分
以下のコードは、各 delta を集約して最終的な tool_call オブジェクトを組み立てる例です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 const finalToolCalls = {};
for await (const event of stream) {
if (
event.type === "response.output_item.added" &&
event.item.type === "function_call"
) {
finalToolCalls[event.output_index] = event.item;
} else if (event.type === "response.function_call_arguments.delta") {
const index = event.output_index;
if (finalToolCalls[index]) {
finalToolCalls[index].arguments += event.delta;
}
}
} 1
2
3
4
5
6
7
8
9
10
11
12
13 final_tool_calls = {}
for event in stream:
if (
event.type == "response.output_item.added"
and event.item.type == "function_call"
):
final_tool_calls[event.output_index] = event.item
elif event.type == "response.function_call_arguments.delta" :
index = event.output_index
if final_tool_calls[index]:
final_tool_calls[index].arguments += event.delta 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 package main
import (
"context"
"fmt"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/responses"
)
func main() {
client := openai.NewClient()
parameters := map[string]any{
"type": "object",
"properties": map[string]any{
"location": map[string]any{"type": "string"},
},
"required": []string{"location"},
"additionalProperties": false,
}
tool := responses.ToolParamOfFunction("get_weather", parameters, true)
stream := client.Responses.NewStreaming(context.Background(), responses.ResponseNewParams{
Model: "gpt-6-astra",
Input: responses.ResponseNewParamsInputUnion{
OfString: openai.String("What's the weather like in Paris today?"),
},
Tools: []responses.ToolUnionParam{tool},
})
finalToolCalls := map[int64]responses.ResponseFunctionToolCall{}
for stream.Next() {
event := stream.Current()
if event.Type == "response.output_item.added" && event.Item.Type == "function_call" {
finalToolCalls[event.OutputIndex] = event.Item.AsFunctionCall()
}
if event.Type == "response.function_call_arguments.delta" {
finalToolCall, ok := finalToolCalls[event.OutputIndex]
if !ok {
continue
}
finalToolCall.Arguments += event.Delta
finalToolCalls[event.OutputIndex] = finalToolCall
}
}
if err := stream.Err(); err != nil {
panic(err)
}
fmt.Println(finalToolCalls)
} 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 import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.core.JsonValue;
import com.openai.core.http.StreamResponse;
import com.openai.models.responses.FunctionTool;
import com.openai.models.responses.ResponseCreateParams;
import com.openai.models.responses.ResponseFunctionToolCall;
import com.openai.models.responses.ResponseStreamEvent;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
FunctionTool weather =
FunctionTool.builder()
.name("get_weather")
.description("Get the weather for a city.")
.parameters(
FunctionTool.Parameters.builder()
.putAdditionalProperty("type", JsonValue.from("object"))
.putAdditionalProperty(
"properties", JsonValue.from(Map.of("location", Map.of("type", "string"))))
.putAdditionalProperty("required", JsonValue.from(List.of("location")))
.putAdditionalProperty("additionalProperties", JsonValue.from(false))
.build())
.strict(true)
.build();
ResponseCreateParams params =
ResponseCreateParams.builder()
.model("gpt-6-astra")
.input("What is the weather in Paris?")
.addTool(weather)
.build();
Map<Long, ResponseFunctionToolCall> toolCalls = new LinkedHashMap<>();
try (StreamResponse<ResponseStreamEvent> stream = client.responses().createStreaming(params)) {
stream.stream()
.forEach(
event -> {
event
.outputItemAdded()
.ifPresent(
added ->
added
.item()
.functionCall()
.ifPresent(call -> toolCalls.put(added.outputIndex(), call)));
event
.functionCallArgumentsDelta()
.ifPresent(
delta ->
toolCalls.computeIfPresent(
delta.outputIndex(),
(ignored, call) ->
call.toBuilder()
.arguments(call.arguments() + delta.delta())
.build()));
});
}
toolCalls.values().forEach(System.out::println); 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 require "openai"
client = OpenAI::Client.new
stream = client.responses.stream(
model: "gpt-6-astra",
input: "What is the weather in Paris?",
tools: [
{
type: :function,
name: "get_weather",
parameters: {
type: :object,
properties: { location: { type: :string } },
required: ["location"],
additionalProperties: false
},
strict: true
}
]
)
final_tool_calls = {}
stream.each do |event|
case event
when OpenAI::Models::Responses::ResponseOutputItemAddedEvent
item = event.item
next unless item.is_a?(OpenAI::Models::Responses::ResponseFunctionToolCall)
final_tool_calls[event.output_index] = item.to_h.merge(arguments: item.arguments.dup)
when OpenAI::Models::Responses::ResponseFunctionCallArgumentsDeltaEvent
tool_call = final_tool_calls[event.output_index]
tool_call[:arguments] << event.delta if tool_call
end
end
puts(final_tool_calls.sort.to_h.values) 1
2
3
4
5
6
7 {
"type" : "function_call" ,
"id" : "fc_1234xyz" ,
"call_id" : "call_2345abc" ,
"name" : "get_weather" ,
"arguments" : "{ \" location \" : \" Paris, France \" }"
} モデルが関数の呼び出しを終えると、response.function_call_arguments.done 型のイベントが発行されます。このイベントには、次のフィールドを含む関数呼び出し全体が格納されます。
フィールド
説明
response_id
関数呼び出しを含む応答の ID
output_index
応答内の出力項目のインデックス。応答内の個々の関数呼び出しを識別します。
item
関数呼び出しの項目。name、arguments、id フィールドを含みます。
カスタムツールの仕組みは、JSON スキーマで定義する関数ツールとほぼ同じです。ただし、ツールが必要とする入力をモデルに明示する代わりに、モデルが任意の文字列を入力としてツールに渡せます。応答を不必要に JSON で包みたくない場合や、応答に独自の文法を適用したい場合に便利です。文法については後述します。
以下は、モデルの応答として Python コードを含む文字列を受け取るカスタムツールの作成例です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 import OpenAI from "openai";
const client = new OpenAI();
const response = await client.responses.create({
model: "gpt-6-astra",
input: "Use the code_exec tool to print hello world to the console.",
tools: [
{
type: "custom",
name: "code_exec",
description: "Executes arbitrary Python code.",
},
],
});
console.log(response.output); 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model = "gpt-6-astra" ,
input = "Use the code_exec tool to print hello world to the console." ,
tools = [
{
"type" : "custom" ,
"name" : "code_exec" ,
"description" : "Executes arbitrary Python code." ,
}
],
)
print (response.output) 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 package main
import (
"context"
"fmt"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/responses"
)
func main() {
client := openai.NewClient()
tool := responses.ToolParamOfCustom("code_exec")
tool.OfCustom.Description = openai.String("Executes arbitrary Python code.")
response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{
Model: "gpt-6-astra",
Input: responses.ResponseNewParamsInputUnion{OfString: openai.String("Use the code_exec tool to print hello world to the console.")},
Tools: []responses.ToolUnionParam{tool},
})
if err != nil {
panic(err)
}
fmt.Println(response.Output)
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.responses.CustomTool;
import com.openai.models.responses.ResponseCreateParams;
ResponseCreateParams params =
ResponseCreateParams.builder()
.model("gpt-6-astra")
.input("Use code_exec to print hello world.")
.addTool(
CustomTool.builder()
.name("code_exec")
.description("Executes arbitrary Python code.")
.build())
.build();
client.responses().create(params).output().forEach(System.out::println); 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 require "openai"
client = OpenAI::Client.new
response = client.responses.create(
model: "gpt-6-astra",
input: "Use code_exec to print hello world.",
tools: [
{
type: :custom,
name: "code_exec",
description: "Executes arbitrary Python code."
}
]
)
puts(response.output)
これまでの例と同様に、output 配列にはモデルが生成したツール呼び出しが含まれます。ただし、今回はツール呼び出しの入力がプレーンテキストで渡されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [
{
"id" : "rs_6890e972fa7c819ca8bc561526b989170694874912ae0ea6" ,
"type" : "reasoning" ,
"content" : [],
"summary" : []
},
{
"id" : "ctc_6890e975e86c819c9338825b3e1994810694874912ae0ea6" ,
"type" : "custom_tool_call" ,
"status" : "completed" ,
"call_id" : "call_aGiFQkRWSWAIsMQ19fKqxUgb" ,
"input" : "print( \" hello world \" )" ,
"name" : "code_exec"
}
]
文脈自由文法 (CFG)は、指定した形式に適合するテキストの生成方法を定める規則の集合です。カスタムツールに CFG を指定すると、モデルがそのツールに渡すテキストの形式を制約できます。
カスタムツールの設定時に grammar パラメーターで独自の CFG を指定できます。現在、文法の定義には lark と regex の 2 種類の CFG 構文を使用できます。
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 import OpenAI from "openai";
const client = new OpenAI();
const grammar = `
start: expr
expr: term (SP ADD SP term)* -> add
| term
term: factor (SP MUL SP factor)* -> mul
| factor
factor: INT
SP: " "
ADD: "+"
MUL: "*"
%import common.INT
`;
const response = await client.responses.create({
model: "gpt-6-astra",
input: "Use the math_exp tool to add four plus four.",
tools: [
{
type: "custom",
name: "math_exp",
description: "Creates valid mathematical expressions",
format: {
type: "grammar",
syntax: "lark",
definition: grammar,
},
},
],
});
console.log(response.output); 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 from openai import OpenAI
client = OpenAI()
grammar = """
start: expr
expr: term (SP ADD SP term)* -> add
| term
term: factor (SP MUL SP factor)* -> mul
| factor
factor: INT
SP: " "
ADD: "+"
MUL: "*"
%i mport common.INT
"""
response = client.responses.create(
model = "gpt-6-astra" ,
input = "Use the math_exp tool to add four plus four." ,
tools = [
{
"type" : "custom" ,
"name" : "math_exp" ,
"description" : "Creates valid mathematical expressions" ,
"format" : {
"type" : "grammar" ,
"syntax" : "lark" ,
"definition" : grammar,
},
}
],
)
print (response.output) 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 package main
import (
"context"
"fmt"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/responses"
"github.com/openai/openai-go/v3/shared"
)
func main() {
client := openai.NewClient()
grammar := `start: expr
expr: term (SP ADD SP term)* -> add
| term
term: factor (SP MUL SP factor)* -> mul
| factor
factor: INT
SP: " "
ADD: "+"
MUL: "*"
%import common.INT`
tool := responses.ToolParamOfCustom("math_exp")
tool.OfCustom.Description = openai.String("Creates valid mathematical expressions")
tool.OfCustom.Format = shared.CustomToolInputFormatParamOfGrammar(grammar, "lark")
response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{
Model: "gpt-6-astra",
Input: responses.ResponseNewParamsInputUnion{OfString: openai.String("Use the math_exp tool to add four plus four.")},
Tools: []responses.ToolUnionParam{tool},
})
if err != nil {
panic(err)
}
fmt.Println(response.Output)
} 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 import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.CustomToolInputFormat;
import com.openai.models.responses.CustomTool;
import com.openai.models.responses.ResponseCreateParams;
String grammar =
"""
start: expr
expr: term (SP ADD SP term)*
term: INT
SP: " "
ADD: "+"
%import common.INT
""";
ResponseCreateParams params =
ResponseCreateParams.builder()
.model("gpt-6-astra")
.input("Use math_exp to add four plus four.")
.addTool(
CustomTool.builder()
.name("math_exp")
.description("Creates valid mathematical expressions.")
.format(
CustomToolInputFormat.Grammar.builder()
.syntax(CustomToolInputFormat.Grammar.Syntax.LARK)
.definition(grammar)
.build())
.build())
.build();
client.responses().create(params).output().forEach(System.out::println); 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 require "openai"
client = OpenAI::Client.new
grammar = <<~LARK
start: expr
expr: term (SP ADD SP term)*
term: INT
SP: " "
ADD: "+"
%import common.INT
LARK
response = client.responses.create(
model: "gpt-6-astra",
input: "Use math_exp to add four plus four.",
tools: [
{
type: :custom,
name: "math_exp",
description: "Creates valid mathematical expressions.",
format: {
type: :grammar,
syntax: :lark,
definition: grammar
}
}
]
)
puts(response.output)
ツールの出力は、定義した Lark CFG に従った形式になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [
{
"id" : "rs_6890ed2b6374819dbbff5353e6664ef103f4db9848be4829" ,
"type" : "reasoning" ,
"content" : [],
"summary" : []
},
{
"id" : "ctc_6890ed2f32e8819daa62bef772b8c15503f4db9848be4829" ,
"type" : "custom_tool_call" ,
"status" : "completed" ,
"call_id" : "call_pmlLjmvG33KJdyVdC4MVdk5N" ,
"input" : "4 + 4" ,
"name" : "math_exp"
}
]
文法の記述には Lark をベースにした構文を使用します。モデルのサンプリングは LLGuidance で制約します。Lark の次の機能には対応していません。
字句解析器の正規表現での先読み・後読み
字句解析器の正規表現での最短一致の修飾子(*?、+?、??)
終端記号の優先順位
テンプレート
インポート(組み込みの %import common を除く)
%declare 宣言
独自の文法を試すには、Lark IDE の利用をおすすめします。
文法には、ツールに必要な規則とパターンだけを含めてください。文法が複雑すぎると OpenAI API がエラーを返す場合があるため、API で使用する前に、目的の文法が対応範囲内にあることを確認してください。
Lark の文法を意図どおりに仕上げるには工夫が必要です。単純な文法ほど安定して動作します。複雑な文法では、モデルが学習時の分布から外れないように、文法の定義自体、プロンプト、ツールの説明を繰り返し調整する必要があることがよくあります。
正しい例(範囲を限定した単一の終端記号):
start: SENTENCE
SENTENCE: /[A-Za-z, ]*(the hero|a dragon|an old man|the princess)[A-Za-z, ]*(fought|saved|found|lost)[A-Za-z, ]*(a treasure|the kingdom|a secret|his way)[A-Za-z, ]*\./
次のように複数の規則や終端記号に分割しないでください。この例では、規則を使って自由形式のテキストを複数の終端記号に割り振ろうとしています。字句解析器は自由形式の部分に貪欲にマッチするため、意図した位置で区切れなくなります。
start: sentence
sentence: /[A-Za-z, ]+/ subject /[A-Za-z, ]+/ verb /[A-Za-z, ]+/ object /[A-Za-z, ]+/
入力から終端記号をどこで切り出すかは、終端記号の定義だけで決まります。小文字で記述する規則は、この処理に影響しません。「アンカー間の自由形式のテキスト」を扱う場合は、全体を 1 つの正規表現の終端記号として定義し、字句解析器が意図した構造に 1 回だけマッチするようにしてください。
Lark では、字句解析器のトークンを終端記号(慣例では UPPERCASE)で表し、構文解析器の生成規則を規則(慣例では lowercase)で表します。対応する機能の範囲内で意図どおりに動作させるには、文法を明示的に記述して不要な複雑さを避け、終端記号と規則の役割を明確に分けてください。
終端記号の正規表現には Rust の regex クレートの構文 を使用します。Python の re モジュール の構文とは異なります。
字句解析と構文解析の実行順序
CFG の規則が適用される前に、字句解析器が終端記号に貪欲にマッチします(最長一致が優先されます)。終端記号を複数の規則に分けてマッチの仕方を制御しようとしても、字句解析器の動作を決めるのは終端記号の正規表現だけです。
自由形式のテキストからパターンを切り出すには単一の終端記号を使用
任意のテキストに埋め込まれたパターン(たとえば、アンカー間に任意の文字列が入る自然言語の文)を認識するには、そのパターンを 1 つの終端記号で表現してください。自由形式のテキストにマッチする終端記号と構文解析の規則を交互に並べないでください。貪欲にマッチする字句解析器は意図した境界で止まらず、モデルが学習時の分布から外れる可能性が非常に高くなります。
独立したトークンの組み合わせには規則を使用
規則は、明確に区切られた終端記号(数値、キーワード、句読点など)を組み合わせて、より大きな構造を作るのに適しています。2 つの終端記号の間に入るテキストの制約には適していません。
終端記号は対象と範囲を絞り、単独で完結するように定義
文字クラスを明示し、範囲を限定した量指定子({0,10} など)を使ってください。上限のない * を多用するのは避けます。「ピリオドまでの任意のテキスト」が必要な場合は、/[^.\n]{0,10}*\./ のようなパターンを優先し、/.+\./ のようにマッチ範囲が際限なく広がるものは避けてください。
規則はトークンの組み合わせに使い、正規表現内部の制御には使わない
規則の適切な使用例:
start: expr
NUMBER: /[0-9]+/
PLUS: "+"
MINUS: "-"
expr: term (("+"|"-") term)*
term: NUMBER
空白の明示的な処理
範囲を限定しない %ignore ディレクティブに頼らないでください。無制限に無視するよう指定すると、文法が複雑になりすぎたり、モデルが学習時の分布から外れたりする場合があります。空白を許可する箇所には、空白を表す終端記号を明示的に組み込んでください。
文法が複雑すぎて API に拒否される場合は、規則と終端記号を単純化し、範囲を限定していない %ignore を削除してください。
カスタムツールに予期しないトークンが渡される場合は、終端記号のマッチ範囲が重複していないか、字句解析器の貪欲なマッチがどう働いているかを確認してください。
モデルの出力が学習時の分布から外れる場合(出力が極端に長くなったり、同じ内容を繰り返したりして、構文上は正しくても意味が誤っている場合)は、次の方法を試してください。
文法の制約を厳しくします。
プロンプトにフューショットの例を追加します。ツールの説明には、文法の解説と、その文法を踏まえて推論し、文法に従うよう求める指示を加えます。結果を見ながら両方を調整してください。
推論強度を上げて試します(例:medium から high に変更)。
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 import OpenAI from "openai";
const client = new OpenAI();
const grammar =
"^(?P<month>January|February|March|April|May|June|July|August|September|October|November|December)\\s+(?P<day>\\d{1,2})(?:st|nd|rd|th)?\\s+(?P<year>\\d{4})\\s+at\\s+(?P<hour>0?[1-9]|1[0-2])(?P<ampm>AM|PM)$";
const response = await client.responses.create({
model: "gpt-6-astra",
input:
"Use the timestamp tool to save a timestamp for August 7th 2025 at 10AM.",
tools: [
{
type: "custom",
name: "timestamp",
description: "Saves a timestamp in date + time in 24-hr format.",
format: {
type: "grammar",
syntax: "regex",
definition: grammar,
},
},
],
});
console.log(response.output); 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 from openai import OpenAI
client = OpenAI()
grammar = r " ^( ?P<month> January | February | March | April | May | June | July | August | September | October | November | December )\s + ( ?P<day> \d {1,2} )(?: st | nd | rd | th ) ? \s + ( ?P<year> \d {4} )\s + at \s + ( ?P<hour> 0 ? [1-9] | 1 [0-2])( ?P<ampm> AM | PM )$ "
response = client.responses.create(
model = "gpt-6-astra" ,
input = "Use the timestamp tool to save a timestamp for August 7th 2025 at 10AM." ,
tools = [
{
"type" : "custom" ,
"name" : "timestamp" ,
"description" : "Saves a timestamp in date + time in 24-hr format." ,
"format" : {
"type" : "grammar" ,
"syntax" : "regex" ,
"definition" : grammar,
},
}
],
)
print (response.output) 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 package main
import (
"context"
"fmt"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/responses"
"github.com/openai/openai-go/v3/shared"
)
func main() {
client := openai.NewClient()
grammar := `^(?P<month>January|February|March|April|May|June|July|August|September|October|November|December)\s+(?P<day>\d{1,2})(?:st|nd|rd|th)?\s+(?P<year>\d{4})\s+at\s+(?P<hour>0?[1-9]|1[0-2])(?P<ampm>AM|PM)$`
tool := responses.ToolParamOfCustom("timestamp")
tool.OfCustom.Description = openai.String("Saves a timestamp in date and time format.")
tool.OfCustom.Format = shared.CustomToolInputFormatParamOfGrammar(grammar, "regex")
response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{
Model: "gpt-6-astra",
Input: responses.ResponseNewParamsInputUnion{OfString: openai.String("Use the timestamp tool to save a timestamp for August 7th 2025 at 10AM.")},
Tools: []responses.ToolUnionParam{tool},
})
if err != nil {
panic(err)
}
fmt.Println(response.Output)
} 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 import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.CustomToolInputFormat;
import com.openai.models.responses.CustomTool;
import com.openai.models.responses.ResponseCreateParams;
String grammar =
"^(January|February|March|April|May|June|July|August|September|October|November|December) "
+ "\\d{1,2}(st|nd|rd|th)? \\d{4} at (0?[1-9]|1[0-2])(AM|PM)$";
ResponseCreateParams params =
ResponseCreateParams.builder()
.model("gpt-6-astra")
.input("Use timestamp to save August 7th 2025 at 10AM.")
.addTool(
CustomTool.builder()
.name("timestamp")
.description("Saves a timestamp in date and time format.")
.format(
CustomToolInputFormat.Grammar.builder()
.syntax(CustomToolInputFormat.Grammar.Syntax.REGEX)
.definition(grammar)
.build())
.build())
.build();
client.responses().create(params).output().forEach(System.out::println); 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 require "openai"
client = OpenAI::Client.new
grammar = "^(January|February|March|April|May|June|July|August|September|October|November|December) \\d{1,2}(st|nd|rd|th)? \\d{4} at (0?[1-9]|1[0-2])(AM|PM)$"
response = client.responses.create(
model: "gpt-6-astra",
input: "Use timestamp to save August 7th 2025 at 10AM.",
tools: [
{
type: :custom,
name: "timestamp",
description: "Saves a timestamp in date and time format.",
format: {
type: :grammar,
syntax: :regex,
definition: grammar
}
}
]
)
puts(response.output)
ツールの出力は、定義した Regex CFG に従った形式になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [
{
"id" : "rs_6894f7a3dd4c81a1823a723a00bfa8710d7962f622d1c260" ,
"type" : "reasoning" ,
"content" : [],
"summary" : []
},
{
"id" : "ctc_6894f7ad7fb881a1bffa1f377393b1a40d7962f622d1c260" ,
"type" : "custom_tool_call" ,
"status" : "completed" ,
"call_id" : "call_8m4XCnYvEmFlzHgDHbaOCFlK" ,
"input" : "August 7th 2025 at 10AM" ,
"name" : "timestamp"
}
]
Lark 構文と同様に、正規表現には Rust の regex クレートの構文 を使用します。Python の re モジュール の構文とは異なります。
正規表現の次の機能には対応していません。
先読み・後読み
最短一致の修飾子(*?、+?、??)
パターンは 1 行で記述
入力の改行にマッチさせるには、エスケープシーケンス \n を使ってください。パターンを複数行に分けて記述できる詳細モードや拡張モードは使用しないでください。
正規表現はパターン文字列をそのまま指定
パターンを // で囲まないでください。