予測出力 を使うと、出力トークンの多くが事前にわかっている場合に、Chat Completions の API 応答を高速化できます。典型的な例は、テキストファイルやコードファイルに小さな変更を加えて再生成する場合です。予測内容は、Chat Completions の prediction リクエストパラメータ で指定できます。
予測出力は現在、最新の gpt-4o、gpt-4o-mini、gpt-4.1、gpt-4.1-mini、gpt-4.1-nano モデルで利用できます。ここからは、予測出力を使ってアプリケーションのレイテンシを短縮する方法を説明します。
予測出力は、テキスト文書やコードファイルに小さな変更を加えて再生成する場合に特に役立ちます。たとえば、GPT-4o モデル で JavaScript コードをリファクタリングし、User クラスの username プロパティを email に変更するとします。
1
2
3
4
5
6
7 class User {
firstName = "" ;
lastName = "" ;
username = "" ;
}
export default User;
上記の 4 行目を除き、ファイルの大部分は変わりません。コードファイルの現在の内容を予測テキストとして使うと、ファイル全体をより低いレイテンシで再生成できます。ファイルが大きくなるほど、時間の短縮効果も大きくなります。
以下は、OpenAI の SDK で prediction パラメータを使う例です。モデルの最終出力が元のコードファイルとほぼ同じになると予測し、そのファイルの内容を予測テキストとして指定します。
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 import OpenAI from "openai" ;
const code = `
class User {
firstName = "";
lastName = "";
username = "";
}
export default User;
` . trim ();
const openai = new OpenAI ();
const refactorPrompt = `
Replace the "username" property with an "email" property. Respond only
with code, and with no markdown formatting.
` ;
const completion = await openai.chat.completions. create ({
model: "gpt-4.1" ,
messages: [
{
role: "user" ,
content: refactorPrompt,
},
{
role: "user" ,
content: code,
},
],
store: true ,
prediction: {
type: "content" ,
content: code,
},
});
// Inspect returned data
console. log (completion);
console. log (completion.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 from openai import OpenAI
code = """
class User {
firstName = "";
lastName = "";
username = "";
}
export default User;
""".strip()
refactor_prompt = """
Replace the "username" property with an "email" property. Respond only
with code, and with no markdown formatting.
"""
client = OpenAI()
completion = client.chat.completions.create(
model="gpt-4.1",
messages=[
{"role": "user", "content": refactor_prompt},
{"role": "user", "content": code},
],
prediction={"type": "content", "content": code},
)
print(completion)
print(completion.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 package main
import (
"context"
"fmt"
"strings"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/shared"
)
func main() {
client := openai.NewClient()
code := strings.TrimSpace(`
class User {
firstName = "";
lastName = "";
username = "";
}
export default User;
`)
refactorPrompt := strings.TrimSpace(`
Replace the "username" property with an "email" property. Respond only
with code, and with no markdown formatting.
`)
completion, err := client.Chat.Completions.New(context.Background(), openai.ChatCompletionNewParams{
Model: shared.ChatModelGPT4_1,
Messages: []openai.ChatCompletionMessageParamUnion{
openai.UserMessage(refactorPrompt),
openai.UserMessage(code),
},
Store: openai.Bool(true),
Prediction: openai.ChatCompletionPredictionContentParam{
Content: openai.ChatCompletionPredictionContentContentUnionParam{OfString: openai.String(code)},
},
})
if err != nil {
panic(err)
}
fmt.Println(completion.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 import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.chat.completions.ChatCompletionCreateParams;
import com.openai.models.chat.completions.ChatCompletionPredictionContent;
String code =
"""
class User {
firstName: string = "";
lastName: string = "";
username: string = "";
}
export default User;
""";
String refactorPrompt =
"Replace the \"username\" property with an \"email\" property. "
+ "Respond only with code, and with no markdown formatting.";
ChatCompletionCreateParams params =
ChatCompletionCreateParams.builder()
.model("gpt-4.1")
.addUserMessage(refactorPrompt)
.addUserMessage(code)
.prediction(ChatCompletionPredictionContent.builder().content(code).build())
.store(true)
.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 using OpenAI.Chat;
#pragma warning disable OPENAI001
string key = Environment.GetEnvironmentVariable("OPENAI_API_KEY")!;
string model = "gpt-4.1";
ChatClient client = new(model, key);
string code =
"""
class User {
firstName = "";
lastName = "";
username = "";
}
export default User;
""";
ChatCompletionOptions options = new()
{
OutputPrediction = ChatOutputPrediction.CreateStaticContentPrediction(code),
};
ChatCompletion completion = await client.CompleteChatAsync(
[
new UserChatMessage(
"Replace the username property with an email property. Respond only with code, and with no markdown formatting."
),
new UserChatMessage(code),
],
options
);
Console.WriteLine(completion.Content[0].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 require "openai"
client = OpenAI::Client.new
code = <<~CODE
class User {
firstName: string = "";
lastName: string = "";
username: string = "";
}
export default User;
CODE
refactor_prompt = <<~PROMPT
Replace the "username" property with an "email" property. Respond only
with code, and with no markdown formatting.
PROMPT
completion = client.chat.completions.create(
model: "gpt-4.1",
messages: [
{
role: :user,
content: refactor_prompt
},
{
role: :user,
content: code
}
],
prediction: {
type: :content,
content: code
},
store: true
)
puts(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 curl https://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-4.1",
"messages": [
{
"role": "user",
"content": "Replace the username property with an email property. Respond only with code, and with no markdown formatting."
},
{
"role": "user",
"content": "$CODE_CONTENT_HERE"
}
],
"prediction": {
"type": "content",
"content": "$CODE_CONTENT_HERE"
}
}'
モデルの応答には、リファクタリング後のコードに加え、以下のような使用量データも含まれます。ここでは choices フィールドを省略しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 {
"id" : "chatcmpl-xxx" ,
"object" : "chat.completion" ,
"created" : 1786652188 ,
"model" : "gpt-4.1-2025-04-14" ,
"usage" : {
"prompt_tokens" : 59 ,
"completion_tokens" : 24 ,
"total_tokens" : 83 ,
"prompt_tokens_details" : { "cached_tokens" : 0 , "audio_tokens" : 0 },
"completion_tokens_details" : {
"reasoning_tokens" : 0 ,
"audio_tokens" : 0 ,
"accepted_prediction_tokens" : 14 ,
"rejected_prediction_tokens" : 2
}
},
"system_fingerprint" : "fp_6ddb4f7408"
}
usage オブジェクト内の accepted_prediction_tokens と rejected_prediction_tokens に注目してください。この例では、予測テキストのうち 14 トークンが応答の高速化に使われ、2 トークンが不採用になりました。
不採用になったトークンも、API が生成するほかの出力トークンと同様に課金されます。そのため、予測出力を使うとリクエストの費用が増える場合があります。
API 応答にストリーミングを使うと、予測出力によるレイテンシの短縮効果がさらに大きくなります。以下は、同じコードのリファクタリングを、OpenAI SDK のストリーミングで行う例です。
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 import OpenAI from "openai" ;
const code = `
class User {
firstName = "";
lastName = "";
username = "";
}
export default User;
` . trim ();
const openai = new OpenAI ();
const refactorPrompt = `
Replace the "username" property with an "email" property. Respond only
with code, and with no markdown formatting.
` ;
const completion = await openai.chat.completions. create ({
model: "gpt-4.1" ,
messages: [
{
role: "user" ,
content: refactorPrompt,
},
{
role: "user" ,
content: code,
},
],
store: true ,
prediction: {
type: "content" ,
content: code,
},
stream: true ,
});
// Inspect returned data
for await ( const chunk of completion) {
process.stdout. write (chunk.choices[ 0 ]?.delta?.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 from openai import OpenAI
code = """
class User {
firstName = "";
lastName = "";
username = "";
}
export default User;
""".strip()
refactor_prompt = """
Replace the "username" property with an "email" property. Respond only
with code, and with no markdown formatting.
"""
client = OpenAI()
stream = client.chat.completions.create(
model="gpt-4.1",
messages=[
{"role": "user", "content": refactor_prompt},
{"role": "user", "content": code},
],
prediction={"type": "content", "content": code},
stream=True,
)
for chunk in stream:
if chunk.choices[0].delta.content is not None:
print(chunk.choices[0].delta.content, end="") 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 package main
import (
"context"
"fmt"
"strings"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/shared"
)
func main() {
client := openai.NewClient()
code := strings.TrimSpace(`
class User {
firstName = "";
lastName = "";
username = "";
}
export default User;
`)
refactorPrompt := strings.TrimSpace(`
Replace the "username" property with an "email" property. Respond only
with code, and with no markdown formatting.
`)
stream := client.Chat.Completions.NewStreaming(context.Background(), openai.ChatCompletionNewParams{
Model: shared.ChatModelGPT4_1,
Messages: []openai.ChatCompletionMessageParamUnion{
openai.UserMessage(refactorPrompt),
openai.UserMessage(code),
},
Store: openai.Bool(true),
Prediction: openai.ChatCompletionPredictionContentParam{
Content: openai.ChatCompletionPredictionContentContentUnionParam{OfString: openai.String(code)},
},
})
for stream.Next() {
if len(stream.Current().Choices) > 0 {
fmt.Print(stream.Current().Choices[0].Delta.Content)
}
}
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 import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.core.http.StreamResponse;
import com.openai.models.chat.completions.ChatCompletionChunk;
import com.openai.models.chat.completions.ChatCompletionCreateParams;
import com.openai.models.chat.completions.ChatCompletionPredictionContent;
String code =
"""
class User {
firstName: string = "";
lastName: string = "";
username: string = "";
}
export default User;
""";
String refactorPrompt =
"Replace the \"username\" property with an \"email\" property. "
+ "Respond only with code, and with no markdown formatting.";
ChatCompletionCreateParams params =
ChatCompletionCreateParams.builder()
.model("gpt-4.1")
.addUserMessage(refactorPrompt)
.addUserMessage(code)
.prediction(ChatCompletionPredictionContent.builder().content(code).build())
.store(true)
.build();
try (StreamResponse<ChatCompletionChunk> stream =
client.chat().completions().createStreaming(params)) {
stream.stream()
.flatMap(chunk -> chunk.choices().stream())
.flatMap(choice -> choice.delta().content().stream())
.forEach(System.out::print);
} 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 using OpenAI.Chat;
#pragma warning disable OPENAI001
string key = Environment.GetEnvironmentVariable("OPENAI_API_KEY")!;
string model = "gpt-4.1";
ChatClient client = new(model, key);
string code =
"""
class User {
firstName = "";
lastName = "";
username = "";
}
export default User;
""";
ChatCompletionOptions options = new()
{
OutputPrediction = ChatOutputPrediction.CreateStaticContentPrediction(code),
};
await foreach (
StreamingChatCompletionUpdate update in client.CompleteChatStreamingAsync(
[
new UserChatMessage(
"Replace the username property with an email property. Respond only with code, and with no markdown formatting."
),
new UserChatMessage(code),
],
options
)
)
{
foreach (ChatMessageContentPart part in update.ContentUpdate)
{
Console.Write(part.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 require "openai"
client = OpenAI::Client.new
code = <<~CODE
class User {
firstName: string = "";
lastName: string = "";
username: string = "";
}
export default User;
CODE
refactor_prompt = <<~PROMPT
Replace the "username" property with an "email" property. Respond only
with code, and with no markdown formatting.
PROMPT
stream = client.chat.completions.stream(
model: "gpt-4.1",
messages: [
{
role: :user,
content: refactor_prompt
},
{
role: :user,
content: code
}
],
prediction: {
type: :content,
content: code
},
store: true
)
stream.text.each { |text| print(text) }
応答内の予測テキストの位置
指定した予測テキストは、生成される応答のどこに現れても、応答レイテンシの短縮に役立ちます。たとえば、以下のシンプルな Hono サーバーのコードを予測テキストに指定するとします。
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 { serve } from "@hono/node-server" ;
import { serveStatic } from "@hono/node-server/serve-static" ;
import { Hono } from "hono" ;
const app = new Hono ();
app. get ( "/api" , ( c ) => {
return c. text ( "Hello Hono!" );
});
// You will need to build the client code first: `pnpm run ui:build`.
app. use (
"/*" ,
serveStatic ({
rewriteRequestPath : ( path ) => `./dist${ path }` ,
})
);
const port = 3000 ;
console. log ( `Server is running on port ${ port }` );
serve ({
fetch: app.fetch,
port,
});
次のようなプロンプトで、モデルにファイルの再生成を依頼できます。
Add a get route to this application that responds with
the text "hello world". Generate the entire application
file again with this route added, and with no other
markdown formatting.
このプロンプトに対する応答は、たとえば次のようになります。
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 import { serve } from "@hono/node-server" ;
import { serveStatic } from "@hono/node-server/serve-static" ;
import { Hono } from "hono" ;
const app = new Hono ();
app. get ( "/api" , ( c ) => {
return c. text ( "Hello Hono!" );
});
app. get ( "/hello" , ( c ) => {
return c. text ( "hello world" );
});
// You will need to build the client code first: `pnpm run ui:build`.
app. use (
"/*" ,
serveStatic ({
rewriteRequestPath : ( path ) => `./dist${ path }` ,
})
);
const port = 3000 ;
console. log ( `Server is running on port ${ port }` );
serve ({
fetch: app.fetch,
port,
});
予測テキストが、応答に新しく追加された内容の前後に分かれて現れても、採用された予測トークンは記録されます。choices フィールドを省略したモデルの応答は、次のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 {
"id" : "chatcmpl-xxx" ,
"object" : "chat.completion" ,
"created" : 1731014771 ,
"model" : "gpt-4o-2024-08-06" ,
"usage" : {
"prompt_tokens" : 203 ,
"completion_tokens" : 159 ,
"total_tokens" : 362 ,
"prompt_tokens_details" : { "cached_tokens" : 0 , "audio_tokens" : 0 },
"completion_tokens_details" : {
"reasoning_tokens" : 0 ,
"audio_tokens" : 0 ,
"accepted_prediction_tokens" : 60 ,
"rejected_prediction_tokens" : 0
}
},
"system_fingerprint" : "fp_9ee9e968ea"
}
今回は、予測テキストとして指定したファイルの内容がすべて最終応答で使われたため、不採用になった予測トークンはありませんでした。いいですね!🔥
予測出力を使う際は、以下の点と制限事項を考慮してください。
予測出力に対応しているのは、GPT-4o、GPT-4o-mini、GPT-4.1、GPT-4.1-mini、GPT-4.1-nano シリーズのモデルのみです。
予測テキストとして指定したトークンは、最終出力に含まれなくても、出力トークンの料金で課金されます。最終応答で使われなかったトークン数は、usage オブジェクトの rejected_prediction_tokens プロパティ で確認できます。
予測出力を使う場合、以下の API パラメータ はサポートされません。
n:1 を超える値はサポート対象外
logprobs:サポート対象外
presence_penalty:0 を超える値はサポート対象外
frequency_penalty:0 を超える値はサポート対象外
audio:予測出力は音声の入力と出力 に非対応
modalities:text モダリティのみサポート
max_completion_tokens:サポート対象外
tools:予測出力では現在、Function Calling はサポート対象外