백그라운드 모드
백그라운드 모드 (Background mode)
Codex나 Deep Research 같은 에이전트들은 추론 모델이 복잡한 문제를 푸는 데 몇 분씩 걸릴 수 있다는 걸 보여줘요. 백그라운드 모드를 쓰면 GPT-5.2, GPT-5.2 Pro 같은 모델에서 오래 걸리는 작업을 타임아웃이나 연결 문제 걱정 없이 안정적으로 실행할 수 있어요.
백그라운드 모드는 이런 작업을 비동기로 시작하고, 개발자가 응답 객체를 폴링해 시간에 따른 상태를 확인하게 해 줘요. 응답 생성을 백그라운드에서 시작하려면 background를 true로 설정해 API 요청을 보내면 돼요.
Zero Data Retention(ZDR) 프로젝트의 백그라운드 요청은 store=false로 실행돼요. 비동기 실행과 폴링을 지원하기 위해 응답 데이터는 디스크에 약 10분 동안 임시 저장돼요.
수정된 남용 모니터링(강화형 포함)을 사용하는 프로젝트에서는, store를 생략하거나 true로 설정한 포그라운드 요청이 표준 보존을 따릅니다. 백그라운드 응답은 store=true를 명시적으로 제공한 경우에만 폴링 기간 이후에도 보존돼요. 백그라운드 요청에서 store를 생략하거나 false로 설정하면 응답은 약 10분 후 삭제돼요.
출처: 문서
본문
백그라운드에서 응답 생성하기
우주에서 수달에 관한 아주 긴 소설을 쓰는 예시를 볼게요.
curl https://api.openai.com/v1/responses \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{
"model": "gpt-6-astra",
"input": "Write a very long novel about otters in space.",
"background": true
}'
import OpenAI from "openai";
const client = new OpenAI();
const resp = await client.responses.create({
model: "gpt-6-astra",
input: "Write a very long novel about otters in space.",
background: true,
});
console.log(resp.status);
from openai import OpenAI
client = OpenAI()
resp = client.responses.create(
model="gpt-6-astra",
input="Write a very long novel about otters in space.",
background=True,
)
print(resp.status)
package main
import (
"context"
"fmt"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/responses"
)
func main() {
client := openai.NewClient()
response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{
Model: "gpt-6-astra",
Background: openai.Bool(true),
Input: responses.ResponseNewParamsInputUnion{
OfString: openai.String("Write a very long novel about otters in space."),
},
})
if err != nil {
panic(err)
}
fmt.Println(response.Status)
}
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.responses.ResponseCreateParams;
ResponseCreateParams params =
ResponseCreateParams.builder()
.model("gpt-6-astra")
.input("Write a detailed market analysis.")
.background(true)
.build();
var response = client.responses().create(params);
System.out.println(response.status().orElseThrow());
using OpenAI.Responses;
#pragma warning disable OPENAI001
string key = Environment.GetEnvironmentVariable("OPENAI_API_KEY")!;
ResponsesClient client = new(key);
CreateResponseOptions options = new()
{
Model = "gpt-6-astra",
BackgroundModeEnabled = true,
};
options.InputItems.Add(
ResponseItem.CreateUserMessageItem("Write a very long novel about otters in space.")
);
ResponseResult response = await client.CreateResponseAsync(options);
Console.WriteLine(response.Status);
require "openai"
client = OpenAI::Client.new
response = client.responses.create(
model: "gpt-6-astra",
input: "Write a detailed market analysis.",
background: true
)
puts(response.status)
백그라운드 응답 폴링하기
백그라운드 요청의 상태를 확인하려면 Responses의 GET 엔드포인트를 사용해요. 요청이 queued 또는 in_progress 상태인 동안 계속 폴링하고, 그 상태를 벗어나면 최종(terminal) 상태에 도달한 거예요.
백그라운드에서 실행 중인 응답 조회하는 예시를 볼게요.
curl https://api.openai.com/v1/responses/resp_123 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***"
import OpenAI from "openai";
const client = new OpenAI();
let resp = await client.responses.create({
model: "gpt-6-astra",
input: "Write a very long novel about otters in space.",
background: true,
});
while (resp.status === "queued" || resp.status === "in_progress") {
console.log("Current status: " + resp.status);
await new Promise((resolve) => setTimeout(resolve, 2000)); // wait 2 seconds
resp = await client.responses.retrieve(resp.id);
}
console.log("Final status: " + resp.status + "\nOutput:\n" + resp.output_text);
from openai import OpenAI
from time import sleep
client = OpenAI()
resp = client.responses.create(
model="gpt-6-astra",
input="Write a very long novel about otters in space.",
background=True,
)
while resp.status in {"queued", "in_progress"}:
print(f"Current status: {resp.status}")
sleep(2)
resp = client.responses.retrieve(resp.id)
print(f"Final status: {resp.status}\nOutput:\n{resp.output_text}")
package main
import (
"context"
"fmt"
"time"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/responses"
)
func main() {
client := openai.NewClient()
response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{
Model: "gpt-6-astra",
Background: openai.Bool(true),
Input: responses.ResponseNewParamsInputUnion{
OfString: openai.String("Write a very long novel about otters in space."),
},
})
if err != nil {
panic(err)
}
for response.Status == "queued" || response.Status == "in_progress" {
fmt.Println("Current status:", response.Status)
time.Sleep(2 * time.Second)
response, err = client.Responses.Get(context.Background(), response.ID, responses.ResponseGetParams{})
if err != nil {
panic(err)
}
}
fmt.Printf("Final status: %s\nOutput:\n%s\n", response.Status, response.OutputText())
}
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.responses.ResponseCreateParams;
import com.openai.models.responses.ResponseStatus;
ResponseCreateParams params =
ResponseCreateParams.builder()
.model("gpt-6-astra")
.input("Write a very long novel about otters in space.")
.background(true)
.build();
var response = client.responses().create(params);
while (response.status().filter(ResponseStatus.QUEUED::equals).isPresent()
|| response.status().filter(ResponseStatus.IN_PROGRESS::equals).isPresent()) {
System.out.println("Current status: " + response.status().orElseThrow());
Thread.sleep(1000);
response = client.responses().retrieve(response.id());
}
System.out.println("Final status: " + response.status().orElseThrow());
response.output().stream()
.flatMap(item -> item.message().stream())
.flatMap(message -> message.content().stream())
.flatMap(content -> content.outputText().stream())
.forEach(text -> System.out.println(text.text()));
using OpenAI.Responses;
#pragma warning disable OPENAI001
string key = Environment.GetEnvironmentVariable("OPENAI_API_KEY")!;
ResponsesClient client = new(key);
CreateResponseOptions options = new()
{
Model = "gpt-6-astra",
BackgroundModeEnabled = true,
};
options.InputItems.Add(
ResponseItem.CreateUserMessageItem("Write a very long novel about otters in space.")
);
ResponseResult created = await client.CreateResponseAsync(options);
ResponseResult response = await client.GetResponseAsync(created.Id);
while (response.Status is ResponseStatus.Queued or ResponseStatus.InProgress)
{
await Task.Delay(TimeSpan.FromSeconds(1));
response = await client.GetResponseAsync(response.Id);
}
if (response.Status != ResponseStatus.Completed)
{
throw new InvalidOperationException($"Background response ended with status: {response.Status}");
}
Console.WriteLine($"Status: {response.Status}");
Console.WriteLine(response.GetOutputText());
require "openai"
client = OpenAI::Client.new
response = client.responses.create(
model: "gpt-6-astra",
input: "Write a very long novel about otters in space.",
background: true
)
while [:queued, :in_progress].include?(response.status)
puts("Current status: #{response.status}")
sleep(2)
response = client.responses.retrieve(response.id)
end
puts("Final status: #{response.status}")
puts(response.output_text)
백그라운드 응답 취소하기
진행 중인 응답을 이렇게 취소할 수도 있어요.
curl -X POST https://api.openai.com/v1/responses/resp_123/cancel \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***"
import OpenAI from "openai";
const client = new OpenAI();
const resp = await client.responses.cancel("resp_123");
console.log(resp.status);
import os
from openai import OpenAI
response_id = os.environ["OPENAI_RESPONSE_ID"]
client = OpenAI()
resp = client.responses.cancel(response_id)
print(resp.status)
package main
import (
"context"
"fmt"
"github.com/openai/openai-go/v3"
)
func main() {
client := openai.NewClient()
canceled, err := client.Responses.Cancel(context.Background(), "resp_123")
if err != nil {
panic(err)
}
fmt.Println(canceled.Status)
}
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
String responseId = "resp_123";
var response = client.responses().cancel(responseId);
System.out.println(response.status());
using OpenAI.Responses;
#pragma warning disable OPENAI001
string key = Environment.GetEnvironmentVariable("OPENAI_API_KEY")!;
ResponsesClient client = new(key);
// Replace this illustrative ID with the background response to cancel.
string responseId = "resp_123";
ResponseResult response = await client.CancelResponseAsync(responseId);
Console.WriteLine(response.Status);
require "openai"
client = OpenAI::Client.new
response = client.responses.cancel("resp_123")
puts(response.status)
취소를 두 번 하는 것은 멱등(idempotent)이에요. 이후 호출은 그냥 최종 Response 객체를 돌려줘요.
백그라운드 응답 스트리밍하기
백그라운드 Response를 만들고 바로 스트리밍 이벤트를 받기 시작할 수 있어요. 클라이언트가 스트림을 끊을 것으로 예상되지만 나중에 다시 이어받고 싶을 때 유용해요. 이렇게 하려면 background와 stream을 모두 true로 설정해 Response를 만들면 돼요. 각 스트리밍 이벤트에서 받는 sequence_number에 해당하는 "커서(cursor)"를 추적해 두는 편이 좋아요.
현재 백그라운드 응답에서 첫 토큰을 받는 데 걸리는 시간은 동기식보다 길어요. 이 지연 격차를 줄이기 위해 작업 중이에요.
백그라운드 응답을 생성하고 스트리밍하는 예시를 볼게요.
curl https://api.openai.com/v1/responses \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{
"model": "gpt-6-astra",
"input": "Write a very long novel about otters in space.",
"background": true,
"stream": true
}'
// To resume:
curl "https://api.openai.com/v1/responses/resp_123?stream=true&starting_after=42" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***"
import OpenAI from "openai";
const client = new OpenAI();
const stream = await client.responses.create({
model: "gpt-6-astra",
input: "Write a very long novel about otters in space.",
background: true,
stream: true,
});
let cursor = null;
for await (const event of stream) {
console.log(event);
cursor = event.sequence_number;
}
// If the connection drops, you can resume streaming from the last cursor (SDK support coming soon):
// const resumedStream = await client.responses.stream(resp.id, { starting_after: cursor });
// for await (const event of resumedStream) { ... }
from openai import OpenAI
client = OpenAI()
# Fire off an async response but also start streaming immediately
stream = client.responses.create(
model="gpt-6-astra",
input="Write a very long novel about otters in space.",
background=True,
stream=True,
)
cursor = None
for event in stream:
print(event)
cursor = event.sequence_number
# If your connection drops, the response continues running and you can reconnect:
# SDK support for resuming the stream is coming soon.
# for event in client.responses.stream(resp.id, starting_after=cursor):
# print(event)
package main
import (
"context"
"fmt"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/responses"
)
func main() {
client := openai.NewClient()
stream := client.Responses.NewStreaming(context.Background(), responses.ResponseNewParams{
Model: "gpt-6-astra",
Background: openai.Bool(true),
Input: responses.ResponseNewParamsInputUnion{
OfString: openai.String("Write a very long novel about otters in space."),
},
})
var cursor int64
var responseID string
for stream.Next() {
event := stream.Current()
fmt.Println(event.Type)
cursor = event.SequenceNumber
if event.Response.ID != "" {
responseID = event.Response.ID
}
}
if err := stream.Err(); err != nil {
panic(err)
}
fmt.Printf("response %s last cursor %d\n", responseID, cursor)
// If the connection drops, resume streaming from the last cursor:
// resumed := client.Responses.GetStreaming(
// context.Background(),
// responseID,
// responses.ResponseGetParams{StartingAfter: openai.Int(cursor)},
// )
// for resumed.Next() {
// fmt.Println(resumed.Current().Type)
// }
}
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.core.http.StreamResponse;
import com.openai.models.responses.ResponseCreateParams;
import com.openai.models.responses.ResponseRetrieveParams;
import com.openai.models.responses.ResponseStreamEvent;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
ResponseCreateParams params =
ResponseCreateParams.builder()
.model("gpt-6-astra")
.input("Write a very long novel about otters in space.")
.background(true)
.build();
AtomicLong lastSequenceNumber = new AtomicLong(-1);
AtomicReference<String> responseId = new AtomicReference<>("");
AtomicBoolean streamCompleted = new AtomicBoolean(false);
JsonMapper json = new JsonMapper();
try (StreamResponse<ResponseStreamEvent> stream = client.responses().createStreaming(params)) {
stream.stream()
.forEach(
event -> {
lastSequenceNumber.set(json.valueToTree(event).path("sequence_number").asLong());
event
.created()
.ifPresent(
created -> {
responseId.set(created.response().id());
System.out.println("response.created");
});
event
.outputTextDelta()
.ifPresent(
delta -> {
System.out.println("response.output_text.delta");
});
event
.completed()
.ifPresent(
completed -> {
streamCompleted.set(true);
System.out.println("response.completed");
});
});
}
System.out.println(
"Response " + responseId.get() + "; last sequence number " + lastSequenceNumber.get());
if (!streamCompleted.get()) {
try (StreamResponse<ResponseStreamEvent> resumed =
client
.responses()
.retrieveStreaming(
ResponseRetrieveParams.builder()
.responseId(responseId.get())
.startingAfter(lastSequenceNumber.get())
.build())) {
resumed.stream()
.forEach(
event ->
event.outputTextDelta().ifPresent(delta -> System.out.println(delta.delta())));
}
}
using OpenAI.Responses;
#pragma warning disable OPENAI001
string key = Environment.GetEnvironmentVariable("OPENAI_API_KEY")!;
ResponsesClient client = new(key);
CreateResponseOptions options = new()
{
Model = "gpt-6-astra",
BackgroundModeEnabled = true,
StreamingEnabled = true,
};
options.InputItems.Add(
ResponseItem.CreateUserMessageItem("Write a very long novel about otters in space.")
);
string? responseId = null;
int lastSequenceNumber = -1;
bool completed = false;
void HandleUpdate(StreamingResponseUpdate update)
{
lastSequenceNumber = update.SequenceNumber;
switch (update)
{
case StreamingResponseCreatedUpdate created:
responseId = created.Response.Id;
break;
case StreamingResponseOutputTextDeltaUpdate text:
Console.Write(text.Delta);
break;
case StreamingResponseCompletedUpdate:
completed = true;
break;
case StreamingResponseFailedUpdate:
throw new InvalidOperationException("The background response failed.");
case StreamingResponseIncompleteUpdate:
throw new InvalidOperationException("The background response was incomplete.");
case StreamingResponseErrorUpdate error:
throw new InvalidOperationException($"The response stream failed: {error.Message}");
}
}
try
{
await foreach (
StreamingResponseUpdate update in client.CreateResponseStreamingAsync(options)
)
{
HandleUpdate(update);
}
}
catch (Exception error)
when (error is HttpRequestException or IOException && responseId is not null)
{
// The background response continues after its streaming connection is interrupted.
}
if (!completed)
{
if (responseId is null)
{
throw new InvalidOperationException("The response stream ended before providing its ID.");
}
GetResponseOptions resumeOptions = new(responseId)
{
StartingAfter = lastSequenceNumber,
StreamingEnabled = true,
};
await foreach (StreamingResponseUpdate update in client.GetResponseStreamingAsync(resumeOptions))
{
HandleUpdate(update);
}
if (!completed)
{
throw new InvalidOperationException(
"The resumed response stream ended before the background response completed."
);
}
}
require "openai"
client = OpenAI::Client.new
stream = client.responses.stream(
model: "gpt-6-astra",
input: "Write a very long novel about otters in space.",
background: true
)
last_sequence_number = -1
response_id = ""
stream.each do |event|
puts(event.type)
last_sequence_number = event.sequence_number || last_sequence_number
if event.is_a?(OpenAI::Models::Responses::ResponseCreatedEvent)
response_id = event.response.id
end
end
puts("Response #{response_id}; last sequence number #{last_sequence_number}")
# If the connection drops, resume from the last sequence number:
# client.responses.stream(response_id: response_id, starting_after: last_sequence_number).each do |event|
# puts(event.type)
# end
제한 사항
- 백그라운드 요청은
store=false를 사용할 수 있지만, 비동기 실행과 폴링을 지원하기 위해 응답 데이터는 임시 저장돼요. - 동기 응답을 취소하려면 연결을 끊으면 돼요.
stream=true로 만든 백그라운드 응답에서만 새 스트림을 시작할 수 있어요.