API 배포 체크리스트
API 배포 체크리스트 (API deployment checklist)
프로덕션에 OpenAI API를 배포할 때 품질, 비용, 지연 시간, 신뢰성을 끌어올리기 위한 항목들을 한눈에 정리했어요. 각 항목이 어떤 부분에 영향을 주는지부터 차근차근 살펴볼게요.
출처: 문서
본문
| 내용 | 기대 효과 |
|---|---|
| Responses API 사용하기 | 품질, 비용, 지연 시간, 신뢰성 |
| 워크로드에 맞는 모델 선택 | 품질, 비용, 지연 시간 |
reasoning.effort 설정 |
품질, 비용, 지연 시간 |
| 대화 중 reasoning effort 변경 | 품질, 비용, 지연 시간 |
text.verbosity 설정 |
품질, 비용, 지연 시간 |
어시스턴트 phase 파라미터 설정 |
품질, 비용 |
tool_search 사용 |
비용, 지연 시간 |
| 프로그래매틱 도구 호출 사용 | 품질, 비용, 지연 시간 |
| 병렬 작업에 Multi-agent 사용 | 품질, 비용, 지연 시간 |
| 비동기 도구 호출 사용 | 지연 시간 |
| 내장 도구 활용 | 품질 |
| Compaction 활용 | 비용 |
| 프롬프트 캐싱 최적화 | 지연 시간, 비용 |
reasoning.encrypted_content 사용 |
품질, 지연 시간 |
| 이미지 디테일 의도적으로 설정 | 품질, 비용, 지연 시간 |
| 안전성 식별자 보내기 | 안전성, 신뢰성 |
| 정렬 오류 모니터링 처리 | 안전성, 신뢰성 |
| 급격한 트래픽 증가와 모델 과부하 처리 | 신뢰성 |
background=True 사용 |
작업 연속성 |
| WebSocket 모드 사용 | 지연 시간 |
| 턴 중간 스티어링 사용 | 품질 |
Responses API 사용하기
항상 Responses API로 시작하세요. OpenAI의 대표 API이고, 최신 모델 동작, 내장 도구, 상태 유지 워크플로, 에이전트 기능에 접근하기 가장 좋은 자리예요.
워크로드에 맞는 모델 선택
워크로드에 GPT-6 모델 계열을 평가하세요. 최고 성능에는 gpt-6-astra, 까다로운 추론·코딩에는 gpt-6-sol, 효율적이고 반복적인 작업에는 gpt-6-luna를 쓰세요. 모든 요청을 가장 강력한 모델로 보내기보다 대표 작업에서 잘 수행하는 모델을 고르세요.
GPT-6으로 마이그레이션할 때는 현재 모델의 워크로드 역할과 (지원되는 경우) 유효 reasoning effort를 보존하세요. 도구와 함께 reasoning을 하려면 Responses API를 사용하세요. GPT-6 Astra는 도구 호출에 Responses가 필요하고, GPT-6 Sol·Luna는 reasoning_effort: "none"일 때만 Chat Completions에서 함수 호출을 지원해요. reasoning effort가 none이 아니면 temperature, top_p, top_logprobs를 제거하고, Chat Completions 요청에서 logprobs, Responses include 배열에서 message.output_text.logprobs도 제거하세요. EU 데이터 레지던시에서는 세 모델 모두 Standard 처리를 사용하세요. 다른 호환성 확인은 모델 마이그레이션 지침을 참고하세요. 프롬프트를 바꾸거나 새 기능을 추가하기 전에 대표 evals를 실행하세요. 작업 성공, 지연 시간, 입력·출력·reasoning·캐시 쓰기 토큰, 성공 작업당 비용을 비교하세요.
reasoning.effort 설정
모델이 답하기 전에 얼마나 생각할지를 reasoning.effort로 정해요.
GPT-6 Astra, Sol, Luna는 low, medium, high, xhigh, max를 지원해요. Sol과 Luna는 none도 지원하지만 Astra는 지원하지 않아요. 낮은 effort는 더 빠르고 reasoning 토큰을 덜 써요. 높은 effort는 계획, 디버깅, 종합, 다단계 트레이드오프에 더 많은 시간을 줘요.
추출·라우팅·분류·일상적 재작성 같은 작업은 low를 쓰세요. 문제 진단, 옵션 비교, 계획 작성, 코드 추론이 필요하면 medium이나 high를 쓰세요. xhigh나 max는 대표 evals가 추가 latency·비용을 정당화할 만큼 품질 향상을 보여줄 때만 쓰세요. minimal에서, 또는 none에서 GPT-6 Astra로 마이그레이션할 때는 low로 시작해 결과를 비교하세요. 그 외에는 현재 유효 effort를 보존하고 품질·지연·비용 목표 대비 변경을 테스트하세요.
가장 어려운 품질 우선 워크로드에서는 같은 effort의 표준 모드와 reasoning.mode: "pro"도 비교하세요. Reasoning 모드와 effort는 독립적이에요. Pro 모드는 단일 최종 답변을 반환하기 전에 더 많은 모델 작업을 적용해 신뢰성을 높일 수 있지만, 지연 시간과 토큰 사용을 늘려요.
작업에 맞게 reasoning effort 조정하기
import OpenAI from "openai";
const openai = new OpenAI();
const prompt = [
"Our CI job started failing after a dependency bump.",
"",
"Error:",
"TypeError: Timeout.__init__() got an unexpected keyword argument 'connect'",
"",
"Identify the likeliest root cause and the smallest safe fix.",
].join("\n");
const response = await openai.responses.create({
model: "gpt-6-astra",
reasoning: { effort: "xhigh", mode: "pro" },
input: prompt,
});
console.log(response.output_text);
from openai import OpenAI
client = OpenAI()
prompt = """
Our CI job started failing after a dependency bump.
Error:
TypeError: Timeout.__init__() got an unexpected keyword argument 'connect'
Identify the likeliest root cause and the smallest safe fix.
"""
response = client.responses.create(
model="gpt-6-astra",
reasoning={"effort": "xhigh", "mode": "pro"},
input=prompt,
)
print(response.output_text)
package main
import (
"context"
"fmt"
"strings"
"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()
prompt := strings.Join([]string{
"Our CI job started failing after a dependency bump.",
"",
"Error:",
"TypeError: Timeout.__init__() got an unexpected keyword argument 'connect'",
"",
"Identify the likeliest root cause and the smallest safe fix.",
}, "\n")
reasoning := shared.ReasoningParam{Effort: shared.ReasoningEffortXhigh}
reasoning.SetExtraFields(map[string]any{"mode": "pro"})
response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{
Model: "gpt-6-astra",
Reasoning: reasoning,
Input: responses.ResponseNewParamsInputUnion{OfString: openai.String(prompt)},
})
if err != nil {
panic(err)
}
fmt.Println(response.OutputText())
}
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.core.JsonValue;
import com.openai.models.Reasoning;
import com.openai.models.ReasoningEffort;
import com.openai.models.responses.ResponseCreateParams;
ResponseCreateParams params =
ResponseCreateParams.builder()
.model("gpt-6-astra")
.input(
"Our CI job started failing after a dependency bump. Error: TypeError: Timeout.__init__() got an unexpected keyword argument 'connect'. Identify the likeliest root cause and the smallest safe fix.")
.reasoning(
Reasoning.builder()
.effort(ReasoningEffort.XHIGH)
.putAdditionalProperty("mode", JsonValue.from("pro"))
.build())
.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()));
require "openai"
client = OpenAI::Client.new
prompt = <<~PROMPT
Our CI job started failing after a dependency bump.
Error:
TypeError: Timeout.__init__() got an unexpected keyword argument 'connect'
Identify the likeliest root cause and the smallest safe fix.
PROMPT
response = client.responses.create(
model: "gpt-6-astra",
reasoning: {
effort: :xhigh,
mode: :pro
},
input: prompt
)
puts(response.output_text)
대화 중 reasoning effort 변경
표준 단일 에이전트 모드의 GPT-6 모델에서는 다음 사용자 메시지 앞에 configuration_update 입력 항목을 추가해 응답 간 effort를 변경할 수 있어요. 요청 수준의 reasoning.effort는 원래 프롬프트 접두사가 캐싱 대상으로 남도록 바꾸지 마세요. 업데이트는 다음 응답에 적용되고 다른 업데이트가 덮어쓸 때까지 계속돼요. Configuration updates는 자동 compaction이나 truncation과 결합할 수 없고, /responses/compact는 그런 업데이트를 포함한 히스토리를 거부해요. 히스토리를 압축하려면 compaction_trigger 항목을 포함하고 그 뒤에 새 업데이트를 추가하세요.
text.verbosity 설정
text.verbosity는 간결함과 완전성의 균형을 조절하는 주요 레버예요. 제품이 빠르고 간결한 답변을 필요로 하면 낮게, 더 풍부한 설명·명확한 구조·완전한 컨텍스트가 필요하면 높게 설정하세요. 낮은 verbosity는 출력 토큰을 줄여 모델이 덜 생성하고 더 빨리 반환해요.
코딩에서는 medium과 high가 더 길고 조직적인 출력을 만들어요. low는 답변을 더 타이트하고 최소화해요.
마이그레이션할 때 "Be concise" 같은 광범위한 지시가 여전히 도움 되는지 확인하세요. 기본 디테일 수준은 text.verbosity로 제어하고, 필요한 내용·구조·길이는 프롬프트로 지정하는 게 좋아요.
프롬프트도 품질·토큰 사용·비용·지연 시간에 영향을 줘요. verbosity 설정과 함께 최신 모델 프롬프팅 모범 사례를 검토하세요. 여기엔 코딩 에이전트를 위한 테스트·검증 지침도 포함돼 있어요.
간결한 출력을 위해 낮은 verbosity 설정
import OpenAI from "openai";
const openai = new OpenAI();
const incident = [
"Summarize this incident for the next on-call engineer.",
"- checkout latency spiked from 220 ms to 4.8 s",
"- only us-east-1 was affected",
"- rollback is complete",
"- likely trigger: cache stampede after deploy",
].join("\n");
const response = await openai.responses.create({
model: "gpt-6-astra",
text: { verbosity: "low" },
input: incident,
});
console.log(response.output_text);
from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-6-astra",
text={"verbosity": "low"},
input="""
Summarize this incident for the next on-call engineer.
- checkout latency spiked from 220 ms to 4.8 s
- only us-east-1 was affected
- rollback is complete
- likely trigger: cache stampede after deploy
""",
)
print(response.output_text)
package main
import (
"context"
"fmt"
"strings"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/responses"
)
func main() {
client := openai.NewClient()
incident := strings.Join([]string{
"Summarize this incident for the next on-call engineer.",
"- checkout latency spiked from 220 ms to 4.8 s",
"- only us-east-1 was affected",
"- rollback is complete",
"- likely trigger: cache stampede after deploy",
}, "\n")
response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{
Model: "gpt-6-astra",
Text: responses.ResponseTextConfigParam{Verbosity: "low"},
Input: responses.ResponseNewParamsInputUnion{OfString: openai.String(incident)},
})
if err != nil {
panic(err)
}
fmt.Println(response.OutputText())
}
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.responses.ResponseCreateParams;
import com.openai.models.responses.ResponseTextConfig;
ResponseCreateParams params =
ResponseCreateParams.builder()
.model("gpt-6-astra")
.input(
"Summarize this incident for the next on-call engineer: checkout latency spiked from 220 ms to 4.8 s, only us-east-1 was affected, rollback is complete, and the likely trigger was a cache stampede.")
.text(ResponseTextConfig.builder().verbosity(ResponseTextConfig.Verbosity.LOW).build())
.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()));
require "openai"
client = OpenAI::Client.new
incident = <<~INCIDENT
Summarize this incident for the next on-call engineer.
- checkout latency spiked from 220 ms to 4.8 s
- only us-east-1 was affected
- rollback is complete
- likely trigger: cache stampede after deploy
INCIDENT
response = client.responses.create(
model: "gpt-6-astra",
text: { verbosity: :low },
input: incident
)
puts(response.output_text)
어시스턴트 phase 파라미터 설정
phase는 대화 히스토리의 어시스턴트 메시지에 붙는 라벨이에요. 이전 어시스턴트 메시지가 중간 작업 주석인지 최종 답변인지를 모델에 알려줘요. 진행 업데이트·도구 호출 전 메모·그 외 중간 메시지에는 phase: "commentary"를, 완료된 응답에는 phase: "final_answer"를 쓰세요.
어시스턴트는 이런 식으로 말할 수 있어요.
어시스턴트 주석 메시지
{
"role": "assistant",
"phase": "commentary",
"content": "I'm checking the logs and comparing them to the last successful deploy."
}
그것은 답변이 아니라 진행 메모예요. 나중에 어시스턴트는 이렇게 말할 수 있어요.
어시스턴트 최종 답변 메시지
{
"role": "assistant",
"phase": "final_answer",
"content": "The deploy failed because the migration referenced a column that does not exist in production."
}
이것은 어시스턴트가 완료 전에 눈에 보이는 진행 업데이트를 만들 수 있는 장기 실행이나 도구 중심 워크플로에서 유용해요. gpt-5.3-codex 이후 모델의 후속 요청에 그 히스토리를 다시 보낼 때는 phase를 보존해 다시 보내서 모델이 진행 업데이트와 최종 결과를 구분하게 하세요. 이는 조기 중단(early stopping)을 줄여 에이전트가 최종 답변에 도달할 때까지 계속할 가능성을 높여줘요.
tool_search 사용
매 요청에 전체 도구 카탈로그를 로드하는 대신 tool search를 쓰세요. {"type": "tool_search"}를 추가하고 비싼 도구 정의에는 defer_loading: true를 표시해요. 그러면 모델이 런타임에 필요한 하위 집합만 로드할 수 있어요. 요청 시작 시 모델은 검색 도구 이름과 설명만 봐요. 모델이 지연된 도구가 필요하다고 판단하면 tool search를 실행하고, 그때서야 지연된 도구 정의가 컨텍스트에 로드돼요. 그 후에야 모델이 그것들을 호출해요. 이렇게 하면 토큰을 절약하고 캐시 성능을 보존해요.
Tool search에는 두 가지 모드가 있어요.
- Hosted tool search는 더 단순한 옵션이에요. 요청에 어떤 도구가 가능할지 이미 알고 있을 때 사용하세요.
- Client-executed tool search는 앱이 사용자 테넌트, 프로젝트, 권한, 내부 레지스트리처럼 어떤 도구가 가능한지 결정해야 하는 경우용이에요.
앱이 정말로 발견(discovery)을 직접 제어해야 하는 게 아니라면 hosted tool search로 시작하세요.
도구를 사용자 의도별로 그룹화하세요. 가능하면 네임스페이스나 MCP 서버를 사용하세요. 모델은 긴 평면 함수 목록보다 몇 개의 명확한 그룹 중에서 고르기가 더 쉬워요. 토큰 효율과 모델 성능을 위해 각 네임스페이스를 약 10개 함수 이하로 유지하는 것을 권장해요.
네임스페이스 설명은 짧고 판별적이게 하세요. 상세 지시는 지연된 도구 정의 안에 넣어요. 모든 것을 위한 거대한 네임스페이스 하나는 피하세요.
hosted tool search와 지연 도구 사용
import OpenAI from "openai";
const openai = new OpenAI();
const billingNamespace = {
type: "namespace",
name: "billing",
description: "Billing tools for invoices, payments, taxes, and credits.",
tools: [
{
type: "function",
name: "lookup_invoice",
description:
"Look up invoice state, taxes, credits, and payment attempts.",
parameters: {
type: "object",
properties: {
invoice_id: { type: "string" },
},
required: ["invoice_id"],
additionalProperties: false,
},
strict: true,
defer_loading: true,
},
],
};
const crmNamespace = {
type: "namespace",
name: "crm",
description:
"CRM tools for account ownership, plans, health, and payment history.",
tools: [
{
type: "function",
name: "get_account",
description: "Fetch account owner, plan, health, and payment history.",
parameters: {
type: "object",
properties: {
account_id: { type: "string" },
},
required: ["account_id"],
additionalProperties: false,
},
strict: true,
defer_loading: true,
},
],
};
const response = await openai.responses.create({
model: "gpt-6-astra",
input:
"Find the right billing tool and explain why invoice INV-1043 still " +
"shows overdue after a payment yesterday.",
tools: [billingNamespace, crmNamespace, { type: "tool_search" }],
});
console.log(response.output);
from openai import OpenAI
client = OpenAI()
billing_namespace = {
"type": "namespace",
"name": "billing",
"description": "Billing tools for invoices, payments, taxes, and credits.",
"tools": [
{
"type": "function",
"name": "lookup_invoice",
"description": "Look up invoice state, taxes, credits, and payment attempts.",
"parameters": {
"type": "object",
"properties": {
"invoice_id": {"type": "string"},
},
"required": ["invoice_id"],
"additionalProperties": False,
},
"strict": True,
"defer_loading": True,
}
],
}
crm_namespace = {
"type": "namespace",
"name": "crm",
"description": "CRM tools for account ownership, plans, health, and payment history.",
"tools": [
{
"type": "function",
"name": "get_account",
"description": "Fetch account owner, plan, health, and payment history.",
"parameters": {
"type": "object",
"properties": {
"account_id": {"type": "string"},
},
"required": ["account_id"],
"additionalProperties": False,
},
"strict": True,
"defer_loading": True,
}
],
}
response = client.responses.create(
model="gpt-6-astra",
input=(
"Find the right billing tool and explain why invoice INV-1043 still "
"shows overdue after a payment yesterday."
),
tools=[billing_namespace, crm_namespace, {"type": "tool_search"}],
)
print(response.output)
(Go, Java, Ruby 예시도 동일한 패턴입니다: 네임스페이스 도구 정의, defer_loading: true, tool_search 도구를 함께 전달해요.)
프로그래매틱 도구 호출 사용
프로그래매틱 도구 호출(Programmatic Tool Calling)은 지원 모델이 적격 도구를 호출하는 JavaScript를 작성하고 호스팅 런타임 안에서 그 중간 결과를 줄이게 해요. 코드가 필터·조인·순위·중복 제거·결합·검사를 수행해 더 작은 구조화 결과를 모델에 반환하기 전에 큰 도구 결과를 다룰 수 있는 제한된 단계에 사용하세요.
programmatic_tool_calling 도구를 추가하고 각 적격 도구에 옵트인하세요. 프로그램 전용 도구는 allowed_callers: ["programmatic"]를, 모델이 도구를 직접 호출할 수도 있게 하려면 allowed_callers: ["direct", "programmatic"]를 사용하세요. 각 결과가 모델의 다음 결정을 바꿀 수 있거나, 행동에 승인이 필요하거나, 최종 답변이 인용이나 네이티브 산출물을 보존해야 할 때는 직접 호출을 유지하세요. 모델이 결과를 먼저 검사하지 않고 올바른 프로그램을 작성할 수 있도록 도구 반환 필드와 오류 동작을 문서화하세요.
도구 루프는 program·program_output 항목과 프로그램이 발행한 function_call·function_call_output 항목을 처리해야 해요. 각 call_id를 보존하고 함수 호출의 caller를 그 출력에 복사해서 서비스가 올바른 프로그램을 재개하게 하세요.
program_output과 최종 어시스턴트 메시지를 모두 테스트하세요. 올바른 프로그램 결과도 불완전한 최종 답변이 될 수 있어요. 직접 도구 호출을 쓰는 같은 워크플로와 작업 성공, 필요 증거, 총 토큰, 지연 시간, 비용을 비교하세요.
병렬 작업에 Multi-agent 사용
Multi-agent는 GPT-6 모델을 포함한 지원 모델이 독립 작업 스트림을 서브에이전트에 위임하고 결과를 종합하게 해요. 리서치·분석·구현을 별도 컨텍스트를 사용하고 병렬로 실행되는 구체적 제한 작업으로 쪼갤 수 있을 때 사용하세요.
요청에서 multi_agent.enabled를 true로 설정하세요. HTTP는 client.beta.responses로 beta Responses SDK를 쓰고 betas에 responses_multi_agent=v1을 전달하세요. 원시 HTTP나 WebSocket 연결은 OpenAI-Beta: responses_multi_agent=v1을 보내세요. Multi-agent가 beta인 동안 항목 스키마는 바뀔 수 있어요.
짧은 작업, 각 단계가 이전 단계에 의존하는 순서 체인, 같은 변경 가능한 리소스에 쓰는 작업에는 에이전트 하나를 선호하세요. 서브에이전트는 토큰 사용을 늘릴 수 있으니 기본 max_concurrent_subagents 값 3으로 시작하고 종단 간 품질·지연·비용을 측정하세요. 도구 중심이나 장기 실행 Multi-agent 워크플로에서는 WebSocket 모드가 연속 작업 오버헤드를 줄일 수 있어요.
Multi-agent를 활성화하기 전에 현재 제한 사항을 고려하세요. /responses/compact, reasoning.summary, max_tool_calls는 지원되지 않아요. 서버가 루트 컨텍스트와 모든 서브에이전트 컨텍스트를 자동으로 압축해요.
비동기 도구 호출 사용
GPT-6 모델에서 함수나 사용자 지정 도구에 async: true를 설정하면 애플리케이션이 도구를 실행하는 동안 모델이 계속 작업할 수 있어요. 느린 도구 호출을 일찍 시작하고 독립 작업을 모델이 처리하게 하세요. 애플리케이션은 여전히 호출을 실행·추적하고, 원래 call_id와 함께 나중 Responses 요청에서 결과를 반환해요. 비동기 실행은 내장 도구나 프로그래매틱 도구 호출에는 적용되지 않아요. Multi-agent 모드에서는 비동기 도구와 병렬 도구 호출을 결합하지 마세요. 전체 흐름은 비동기 도구 호출을 참고하세요.
내장 도구 활용
내장 도구(Built-in tools)는 API의 네이티브 기능이에요. 모든 도구를 직접 만들지 않고 Responses API 안에서 이미 작동하는 도구에 모델이 접근하게 할 수 있어요. 모델이 언제 쓸지 스스로 결정해요.
OpenAI는 네이티브 도구를 계속 추가하므로, 워크플로에 맞으면 내장 도구로 시작하세요. 네이티브 옵션이 작업을 다루지 못할 때 사용자 지정 도구를 만드세요. 현재 내장 도구와 관련 옵션에는 다음이 포함돼요.
- Web search: 최신 정보를 위한 웹 검색
- File search: 업로드된 파일이나 벡터 저장소 검색
- Code interpreter: 분석·수학·차트·파일 처리를 위한 Python 실행
- Shell: 호스팅 컨테이너나 자체 런타임에서 셸 명령 실행
- Computer use: 스크린샷·클릭·타이핑·스크롤로 UI 조작
- Image generation: 이미지 생성 또는 편집
- MCP/connectors: 외부 서비스와 도구에 모델 연결
- Skills: 재사용 가능한 지시 번들과 워크플로 파일 연결
- Apply patch: 구조화된 코드 편집
모델 품질도 내장 도구를 선호할 이유예요. 내장 도구는 post-training의 분포 안에 있어서 모델이 이런 도구 형태·동작·출력에 맞춰 학습·평가돼요. 내장 도구를 쓰면 새 도구보다 더 나은 도구 선택, 더 깨끗한 실행, 더 적은 실패를 지원해요.
Compaction 활용
Compaction은 컨텍스트 엔지니어링 도구예요. 많은 턴에 걸쳐 모델이 무엇을 앞으로 가져갈지 결정해요. 장기 실행 에이전트에서 문제는 "컨텍스트 한도에 닿을까?"만이 아니에요. 오래된 메시지, 도구 로그, 재시도, 낡은 세부 사항이 모델이 필요한 상태를 밀어내는 것이 문제예요.
Compaction은 후속 턴에 필요한 상태를 보존하면서 컨텍스트 크기를 줄이는 통제된 방법을 제공해요. 디버깅 단계를 끝내거나 근본 원인을 좁히는 것 같은 의미 있는 이정표 뒤에 이전 창을 압축하고 압축된 출력에서 계속할 수 있어요. 이렇게 하면 다음 턴이 모든 중간 추론·실패한 명령·낡은 추론 분기가 아니라 중요한 상태를 중심으로 만들어져 모델이 날카로워져요.
Compaction은 두 가지 방식으로 쓸 수 있어요.
- 서버에 맡기기:
previous_response_id를 쓰면context_management을 켜고compact_threshold를 설정하세요. 컨텍스트가 너무 커지면 서버가 자동으로 대화를 압축해요. 최신 사용자 메시지만 계속 보내면 돼요. - 직접 하기: 전체 입력 배열을 직접 관리한다면
client.responses.compact()를 호출하세요. 더 작은 컨텍스트 창을 돌려줘요. 그 반환 출력을 다음responses.create()호출에 그대로 쓰세요.
압축된 출력을 편집하지 마세요. 그것은 인간 요약이 아니라 모델이 계속할 수 있게 돕는 머신 상태예요. 그대로 앞으로 전달하고 다음 사용자 메시지를 추가하세요.
압축된 응답 상태에서 계속하기
import OpenAI from "openai";
import { toResponseInputItems } from "openai/lib/responses/ResponseInputItems";
const openai = new OpenAI();
// Full window collected from a long debugging session:
// user messages, assistant outputs, tool calls, and tool outputs.
const longWindow = sessionItems;
const compacted = await openai.responses.compact({
model: "gpt-6-astra",
input: longWindow,
});
const nextResponse = await openai.responses.create({
model: "gpt-6-astra",
store: false,
input: [
// Preserve replayable compacted items.
...toResponseInputItems(compacted.output),
{
type: "message",
role: "user",
content:
"We found the bad cache invalidation path. Write the fix plan " +
"and the verification checklist.",
},
],
});
console.log(nextResponse.output_text);
from openai import OpenAI
client = OpenAI()
# Full window collected from a long debugging session:
# user messages, assistant outputs, tool calls, and tool outputs.
long_window = session_items
compacted = client.responses.compact(
model="gpt-6-astra",
input=long_window,
)
next_response = client.responses.create(
model="gpt-6-astra",
store=False,
input=[
*compacted.output, # Use compact output as-is.
{
"type": "message",
"role": "user",
"content": (
"We found the bad cache invalidation path. Write the fix plan "
"and the verification checklist."
),
},
],
)
print(next_response.output_text)
(Go, Java, Ruby 예시도 같은 패턴입니다: responses.compact()로 압축하고, 압축된 output을 그대로 다음 responses.create()의 입력에 넣어요.)
프롬프트 캐싱 최적화
프롬프트 캐싱은 요청이 같은 긴 접두사를 재사용할 때 지연 시간과 비용을 자동으로 줄여요. 안정적인 지시·예시·참고 자료를 먼저 두고, 그 뒤에 동적인 사용자별 콘텐츠를 두세요. 도구 정의와 순서를 안정적으로 유지하고, 이전 컨텍스트를 다시 쓰지 않고 새 대화 턴을 추가하세요.
GPT-5.6이 명시적 프롬프트 캐싱을 도입했어요. 암시적 캐싱이 여전히 기본이지만, GPT-5.6 모델과 이후 모델 계열은 명시적 캐시 브레이크포인트와 요청 전체 캐시 정책도 지원해요. 안정적인 접두사 뒤에 바뀌는 접미사가 온다면 재사용 가능한 경계에 명시적 prompt_cache_breakpoint를 추가하세요. 요청이 제공한 브레이크포인트만 사용하고 암시적 브레이크포인트는 사용하지 않아야 할 때만 prompt_cache_options.mode를 explicit으로 설정하세요. 이전 모델은 자동 프롬프트 캐싱만 계속 사용해요.
GPT-5.5 이하에서 마이그레이션할 때는 prompt_cache_retention을 prompt_cache_options.ttl: "30m"으로 바꾸세요. 캐시 설정을 바꾸기 전에 프롬프트 캐싱 모델 차이를 확인하세요.
GPT-5.6 모델과 이후 모델 계열에서는 캐시 쓰기가 미캐시 입력 토큰 요율의 1.25배로 비용이 들어요. cached_tokens와 cache_write_tokens를 로그하고, 이후 캐시 읽기와 쓰기량을 비교해 순비용을 측정하고 브레이크포인트 배치를 조정하세요.
재사용 가능한 접두사를 공유하는 요청에는 안정적인 prompt_cache_key를 사용해 관련 요청을 같은 캐시로 라우팅하고 GPT-5.6 이전 모델에서 캐시 히트율을 최적화하세요. 바쁜 그룹은 더 많은 키에 트래픽 분산 지침을 따르세요.
GPT-5.6 이상에서는 prompt_cache_key가 선택사항이에요. 없이도 최적의 캐시 히트율을 얻을 수 있어요. 고객·사용자·워크스페이스별로 별도 캐시 회계를 유지하는 데 쓰면 그룹별 캐시 토큰 사용·청구를 설명하기 쉬워져요. 각 고객에 고유한 키를 할당하고 그 고객의 관련 요청 간에 안정적으로 유지하세요. 키를 분리하면 고객 간 캐시 히트 프로빙도 방지해요. 키로 별도 캐시 회계 유지를 참고하세요.
고객별 별도 캐시 회계 유지
import OpenAI from "openai";
const openai = new OpenAI();
const instructions = [
"You are the support agent for Acme.",
"Follow the Acme support policy and escalation rubric.",
"Use the same tone, safety rules, and tool plan for each ticket.",
].join("\n");
const response = await openai.responses.create({
model: "gpt-6-astra",
prompt_cache_key: "tenant-acme-support-agent",
instructions,
input: "Summarize the current escalation for the on-call lead.",
});
console.log(response.output_text);
from openai import OpenAI
client = OpenAI()
instructions = """
You are the support agent for Acme.
Follow the Acme support policy and escalation rubric.
Use the same tone, safety rules, and tool plan for each ticket.
"""
response = client.responses.create(
model="gpt-6-astra",
prompt_cache_key="tenant-acme-support-agent",
instructions=instructions,
input="Summarize the current escalation for the on-call lead.",
)
print(response.output_text)
(Go, Java, C#, Ruby 예시도 prompt_cache_key를 설정하는 같은 패턴입니다.)
reasoning.encrypted_content 사용
GPT-6 모델을 포함한 지원 모델은 호출 간 reasoning 보존이 가능해요. 작업의 목표·가정·우선순위가 안정적이면 reasoning.context: "all_turns"를, 이전 reasoning이 더 이상 관련 없고 모델을 낡은 접근에 고정시킬 수 있으면 current_turn을 쓰세요. reasoning.context를 생략하거나 auto로 설정하면 응답의 reasoning.context 필드를 검사해 실제 모드를 확인하세요.
지속된 reasoning은 이전 reasoning 항목을 사용할 수 있을 때만 작동해요. 저장된 응답에는 previous_response_id를 쓰세요. Zero Data Retention (ZDR) 요구사항이 응답 데이터 저장을 허용하지 않으면 암호화된 reasoning 콘텐츠가 상태 없는(state-less) 핸드오프를 가능하게 해요.
응답 출력의 reasoning 항목은 기본적으로 암호화된 reasoning 콘텐츠를 포함해요. 각 reasoning 항목의 encrypted_content 속성에서 접근할 수 있어요. 앱이 그 값을 이해할 필요는 없어요. 각 reasoning 항목을 반환된 대로 그대로 유지하고 다음 턴에 다시 보내면 모델이 워크플로를 계속하는 데 사용해요.
상태 없는 턴 사이 암호화 reasoning 전달
import OpenAI from "openai";
import { toResponseInputItems } from "openai/lib/responses/ResponseInputItems";
const openai = new OpenAI();
const history = [
{
role: "user",
content: "Investigate why invoice INV-1043 has mismatched tax totals.",
},
];
const first = await openai.responses.create({
model: "gpt-6-astra",
store: false,
reasoning: { effort: "medium", context: "current_turn" },
input: history,
});
history.push(...toResponseInputItems(first.output));
history.push({
role: "user",
content: "Now write the customer-facing explanation in plain English.",
});
const second = await openai.responses.create({
model: "gpt-6-astra",
store: false,
reasoning: { effort: "medium", context: "all_turns" },
input: history,
});
console.log(second.output_text);
from openai import OpenAI
client = OpenAI()
history = [
{
"role": "user",
"content": "Investigate why invoice INV-1043 has mismatched tax totals.",
}
]
first = client.responses.create(
model="gpt-6-astra",
store=False,
reasoning={"effort": "medium", "context": "current_turn"},
input=history,
)
history.extend(item.model_dump(exclude={"status"}) for item in first.output)
history.append(
{
"role": "user",
"content": "Now write the customer-facing explanation in plain English.",
}
)
second = client.responses.create(
model="gpt-6-astra",
store=False,
reasoning={"effort": "medium", "context": "all_turns"},
input=history,
)
print(second.output_text)
(Go, Java, Ruby 예시도 reasoning 항목을 그대로 다음 요청에 되돌려 보내는 같은 패턴입니다.)
이미지 디테일 의도적으로 설정
이미지 detail은 기본값이 auto이고 크기 조정 동작은 모델에 따라 달라져요. 큰 이미지는 입력 토큰을 더 쓰고 지연 시간을 추가할 수 있어요. 나열된 모델의 크기 조정 표를 확인하고, 배포 전에 선택한 모델로 이미지 토큰 사용과 한도를 측정하세요.
작업에 맞는 detail을 선택하세요. 이미지를 리사이즈하고, 미세한 시각 디테일이 중요하지 않으면 low, 표준 고충실도 이미지 이해에는 high를 쓰세요. 크고 밀집된, 좌표에 민감한, OCR, 로컬라이제이션, 시각 검사 작업처럼 추가 디테일이 품질을 높이는 곳에서는 지원되는 경우 original을 쓰세요. 배포 전에 최악의 경우 이미지 토큰과 지연 시간을 측정하세요.
안전성 식별자 보내기
애플리케이션이 개별 최종 사용자에게 서비스한다면 각 요청에 안정적이고 프라이버시를 보존하는 safety_identifier를 보내세요. OpenAI가 오용을 감지하고 팀이 정책 위반을 안정적으로 추적할 방법을 제공해요. 또한 한 사용자의 오용이 더 넓은 조직의 접근을 방해할 가능성을 줄여요.
식별 정보를 보내는 대신 사용자 이름이나 이메일 주소를 해시하세요. 로그아웃 경험에는 안정적인 세션 ID를 쓰세요.
정렬 오류 모니터링 처리
GPT-6 Astra 에이전트 워크플로에서는 정렬 오류 모니터링(misalignment monitoring)을 계획하세요. 요청이 403과 함께 misalignment_policy_violation을 반환하면 그 대화에 대한 액션 디스패치를 멈추고 차단된 워크플로를 자동 재시도하지 마세요. 스트리밍 중 오류도 처리하고 이미 실행됐을 수 있는 액션을 검토하세요. 팀이 프로젝트 알림이 필요하면 safety.alert.created에 구독하세요. 웹훅은 요청 오류 처리를 대체하지 않아요. 어떤 Responses 요청이 자동으로 멈출 수 있는지는 가이드를 확인하세요.
급격한 트래픽 증가와 모델 과부하 처리
복구 액션을 고르기 전에 HTTP 상태와 error.code를 확인하세요. 429와 slow_down은 요청 비율이 너무 빨리 증가했다는 뜻이에요. Retry-After가 있으면 따르고 트래픽을 줄인 뒤 점진적으로 다시 올리세요. 503과 server_is_overloaded는 요청한 모델이 일시적으로 과부하라는 뜻이에요. Retry-After가 있으면 따르고 재시도하세요. 헤더가 없으면 재시도 지연을 지터와 함께 지수적으로 늘리고 재시도를 제한하세요. 청구·지출·할당량 오류는 재시도 전에 조치가 필요해요. 모든 429를 일시적 속도 제한으로 취급하지 마세요. 속도 제한과 오류 코드를 참고하세요.
background=True 사용
시간이 오래 걸릴 수 있는 요청에는 background=True를 쓰세요. 클라이언트 연결을 열어두는 대신 API가 작업을 시작하고 ID를 반환해요. 앱은 그 작업이 끝나거나 실패하거나 취소될 때까지 폴링할 수 있어요. 큰 분석, 긴 도구 실행, 상태와 재시도 동작이 필요한 작업에 사용하세요.
백그라운드 응답 실행·폴링
// Replace the illustrative IDs and URLs below with your own resource values.
import OpenAI from "openai";
const openai = new OpenAI();
const logBundleFileId = "file_123";
let job = await openai.responses.create({
model: "gpt-6-astra",
background: true,
store: false,
input: "Analyze this large log bundle and cluster the primary failure modes.",
tools: [
{
type: "code_interpreter",
container: {
type: "auto",
file_ids: [logBundleFileId],
},
},
],
});
while (["queued", "in_progress"].includes(job.status)) {
await new Promise((resolve) => setTimeout(resolve, 2000));
job = await openai.responses.retrieve(job.id);
}
console.log(job.output_text);
# Replace the illustrative IDs and URLs below with your own resource values.
from openai import OpenAI
import time
client = OpenAI()
log_bundle_file_id = "file_123"
job = client.responses.create(
model="gpt-6-astra",
background=True,
store=False,
input="Analyze this large log bundle and cluster the primary failure modes.",
tools=[
{
"type": "code_interpreter",
"container": {
"type": "auto",
"file_ids": [log_bundle_file_id],
},
}
],
)
while job.status in {"queued", "in_progress"}:
time.sleep(2)
job = client.responses.retrieve(job.id)
print(job.output_text)
(Go, Java, Ruby 예시도 background: true로 작업을 시작하고 상태가 끝날 때까지 폴링하는 같은 패턴입니다.)
stream=True와 결합해 진행 이벤트를 받을 수 있지만, 첫 이벤트는 일반 요청보다 오래 걸릴 수 있어요.
UI 관점에서 백그라운드 모드는 "실행 중입니다. 상태는 이렇고, 결과는 준비되면 여기 나타납니다."라고 말하는 것과 같아요.
WebSocket 모드 사용
WebSocket 모드는 영구 연결을 열어두고 새 입력 항목과 previous_response_id만 보내며 계속하는, 장기 실행되고 도구 호출이 많은 워크플로를 위해 만들어졌어요. 도구 호출이 20개 이상인 워크플로에서는 종단 간 실행이 최대 약 40% 더 빨라진 것을 확인했어요.
작동 방식: 첫 메시지는 일반 Responses 요청처럼 보일 거예요. model, instructions, tools, 사용자 입력. 서버가 이벤트를 스트리밍으로 돌려보내요. 모델이 도구를 요청하면 앱이 그 도구를 실행해요. 그런 다음 새 HTTP 요청을 보내는 대신, 같은 소켓에 이전 previous_response_id와 새 항목으로 또 다른 response.create 이벤트를 보내요. 여기서 지연 시간 이점이 나와요. 일반 HTTP는 모든 후속이 새 요청이에요. WebSocket 모드에서는 연결이 열려 있고 가장 최근 응답 상태가 해당 연결의 메모리에 따뜻하게 유지돼요. 다음 턴이 그 응답에서 계속되면 서비스가 덜 준비 작업을 해요.
워크플로가 요청 하나-답변 하나라면 HTTP를 유지하세요. 워크플로가 장기 실행 에이전트처럼 행동한다면 WebSocket 모드를 시도하세요.
하나의 연결에서 병렬 대화에는 다른 stream_id 값을 쓰고, 인터리브된 이벤트를 stream_id로 라우팅하세요. 연결은 최대 16개의 활성 응답을 지원하고 같은 스트림의 요청은 순서대로 실행돼요. 연결은 최대 60분 지속돼요. 연속 작업은 HTTP 모드와 같은 previous_response_id 의미를 사용하며 각 스트림의 최신 응답에 대한 연결 로컬 캐시를 가져요.
참고: WebSocket 모드는 데이터가 디스크가 아니라 메모리에만 저장되므로 ZDR과 함께 작동해요.
Python 샘플은 pip install "openai[realtime]>=3.8.0", JavaScript 샘플은 npm install openai@^7.10.0 ws, Ruby 샘플은 gem install openai async-websocket을 사용해요.
Responses API WebSocket 세션 시작
import OpenAI from "openai";
import { ResponsesWS } from "openai/resources/responses/ws";
const openai = new OpenAI();
const ws = new ResponsesWS(openai);
ws.on("event", (event) => {
console.log(event.type);
if (
event.type === "response.completed" ||
event.type === "response.failed" ||
event.type === "response.incomplete"
) {
ws.close();
}
});
ws.on("error", (error) => {
console.error(error);
ws.close();
});
ws.send({
type: "response.create",
model: "gpt-6-astra",
store: false,
input: [
{
type: "message",
role: "user",
content: [
{
type: "input_text",
text:
"Find the flaky test in this run, call the tools you need, " +
"and keep going until you can explain the root cause.",
},
],
},
],
tools: [testLogTool, codeSearchTool],
});
from openai import OpenAI
client = OpenAI()
with client.responses.connect() as connection:
# Use the same typed parameters as client.responses.create(...).
connection.response.create(
model="gpt-6-astra",
store=False,
input=[
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": (
"Find the flaky test in this run, call the tools "
"you need, and keep going until you can explain "
"the root cause."
),
}
],
}
],
tools=[test_log_tool, code_search_tool],
)
first_event = connection.recv()
print(first_event.type)
require "async"
require "openai"
require "json"
def wait_for_response(connection)
while (event = connection.receive)
case event.type.to_s
when "response.completed" then return event.response
when "response.failed", "response.incomplete", "error"
raise "Response failed: #{event.to_json}"
end
end
raise "Connection closed before the response finished"
end
test_log_tool = {
type: "function",
name: "search_test_logs",
description: "Search test logs.",
parameters: {
type: "object",
properties: { query: { type: "string" } },
required: ["query"],
additionalProperties: false
},
strict: true
}
code_search_tool = {
type: "function",
name: "search_code",
description: "Search source code.",
parameters: {
type: "object",
properties: { query: { type: "string" } },
required: ["query"],
additionalProperties: false
},
strict: true
}
client = OpenAI::Client.new
Sync do |task|
task.with_timeout(120) do
client.responses.connect(request_options: { timeout: 10 }) do |connection|
connection.response.create(
stream_id: "main", model: "gpt-6-astra", store: false,
input: [
{
role: "user",
content: "Find the flaky test in this run, call the tools you need, and keep going until you can explain the root cause."
}
],
tools: [test_log_tool, code_search_tool]
)
puts(JSON.pretty_generate(wait_for_response(connection).output.map(&:to_h)))
end
end
end
턴 중간 스티어링 사용
사용자가 GPT-6 모델이 작업하는 동안 요구사항을 추가할 수 있다면 Responses API에 WebSocket 연결을 쓰세요. 활성 응답 ID를 previous_response_id로 하는 response.steer와 새 사용자 입력을 보내세요. 연속을 위해 계속 이벤트를 읽어요. response.steer.accepted는 업데이트가 큐에 들어갔다는 뜻이에요. 스티어링은 이미 애플리케이션에 보낸 출력을 바꾸지 않고 시작된 도구를 되돌리지 않아요. 이벤트 흐름과 도구 결과 처리는 턴 중간 스티어링을 참고하세요.
최종 정리
Responses API는 더 똑똑하고 강력한 OpenAI 애플리케이션을 구축하기 위한 기반이에요. 진짜 장점은 개발자가 일회성 프롬프트에서 작업 복잡성에 적응할 수 있는, 지속적이고 도구를 사용하며 컨텍스트를 인식하는 워크플로로 나아가게 한다는 점이에요. 이 가이드를 따르면 실제 배포에서 더 높은 성능을 볼 수 있을 거예요.
더 알아보기 (Learn more)
관련 문서: Responses API로 마이그레이션, 모델 선택, 프롬프트 캐싱, 속도 제한, 오류 코드 가이드를 함께 보면 좋아요.