예측 출력 기능을 사용하면 출력 토큰의 상당 부분을 미리 알 수 있을 때 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 코드의 리팩터링을 요청한다고 가정해 보겠습니다. 변경할 속성은 username이며, User 클래스에서 이 속성의 이름을 email로 바꾸려고 합니다.
1
2
3
4
5
6
7class User {
firstName = "";
lastName = "";
username = "";
}
export default User;
위 코드에서 4번째 줄을 제외한 나머지 내용은 그대로 유지됩니다. 현재 코드 파일의 내용을 예측 텍스트로 전달하면 파일 전체를 다시 생성하는 데 걸리는 시간을 줄일 수 있습니다. 파일이 클수록 절약되는 시간도 빠르게 늘어납니다.
다음 예제에서는 모델의 최종 출력이 원본 코드 파일과 거의 같을 것으로 예상하고, 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
41import 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
30from 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
42package 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
31import 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
32using 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
36require "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
20curl 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 필드를 생략한 응답 예시에서 사용량 데이터를 확인할 수 있습니다.
12345678910111213141516171819{
"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"
}
accepted_prediction_tokens와 rejected_prediction_tokens는 usage 객체에서 확인할 수 있습니다. 이 예제에서는 예측 토큰 중 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
43import 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
32from 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
46package 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
37import 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
39using 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
36require "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
25import { 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,
});
다음과 같은 프롬프트로 모델에 파일을 다시 생성하도록 요청할 수 있습니다.
이 애플리케이션에 "hello world"라는 텍스트를 반환하는 GET 라우트를 추가하세요.
이 라우트를 포함한 애플리케이션 파일 전체를 다시 생성하세요.
별도의 마크다운 서식은 추가하지 마세요.
이 프롬프트에 대한 응답은 다음과 같을 수 있습니다.
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
29import { 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 필드를 생략한 모델 응답에서도 채택된 예측 토큰 수를 확인할 수 있습니다. 예측 텍스트가 응답에 새로 추가된 코드 앞뒤로 나뉘어 있어도 마찬가지입니다.
12345678910111213141516171819{
"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 계열 모델에서만 지원됩니다.
- 예측 텍스트로 전달한 토큰은 최종 출력에 포함되지 않아도 출력 토큰 요금이 부과됩니다.
rejected_prediction_tokens 속성(usage 객체)에서 최종 응답에 사용되지 않은 토큰 수를 확인하세요.
- 예측 출력을 사용할 때는 다음 API 매개변수에 제한이 있습니다.
n: 1보다 큰 값은 지원되지 않습니다.
logprobs: 지원되지 않습니다.
presence_penalty: 0보다 큰 값은 지원되지 않습니다.
frequency_penalty: 0보다 큰 값은 지원되지 않습니다.
audio: 예측 출력은 오디오 입력 및 출력과 호환되지 않습니다.
modalities: text 모달리티만 지원됩니다.
max_completion_tokens: 지원되지 않습니다.
tools: 예측 출력에서는 현재 함수 호출을 지원하지 않습니다.