Responses API로 마이그레이션하기
Responses API로 마이그레이션하기
Chat Completions로 잘 돌아가던 코드를 새 API로 옮겨야 하는데, 어디서부터 손대야 할지 막막할 때가 있어요. OpenAI의 Responses API는 Chat Completions의 진화판으로, 단순함과 강력한 에이전트 프리미티브를 통합에 더해주는 새 API 원시 타입이에요. Chat Completions는 계속 지원되지만, 모든 신규 프로젝트에는 Responses가 권장됩니다. 이 글에서는 두 API가 어떻게 다른지, 기존 코드를 어떤 순서로 옮기면 되는지를 단계별로 안내할게요.
출처: 공식문서
Responses API에 대해
Responses API는 강력한 에이전트형 애플리케이션을 구축하기 위한 통일 인터페이스예요. 다음을 포함해요.
- 웹 검색, 파일 검색, 컴퓨터 사용, 코드 인터프리터, 원격 MCP 같은 내장 도구
- 이전 응답을 넘겨 더 정확한 추론 결과를 얻을 수 있는 매끄러운 다중 턴 상호작용
- 텍스트와 이미지를 위한 네이티브 멀티모달 지원
Responses의 장점
Responses API는 Chat Completions 대비 여러 장점이 있어요.
- 더 나은 성능: GPT-5 같은 추론 모델을 Responses와 함께 쓰면 Chat Completions보다 더 나은 모델 지능을 얻을 수 있어요. 내부 평가에서 같은 프롬프트·설정으로 SWE-bench 성능이 3% 개선됐어요.
- 기본적으로 에이전트형: Responses API는 에이전트 루프라서, 모델이 하나의 API 요청 안에서
web_search,image_generation,file_search,code_interpreter, 원격 MCP 서버, 그리고 내 커스텀 함수 같은 여러 도구를 호출할 수 있어요. - 더 낮은 비용: 캐시 활용이 개선돼 비용이 낮아져요(내부 테스트에서 Chat Completions 대비 40%~80% 개선).
- 상태 유지 컨텍스트:
store: true를 쓰면 턴마다 상태가 유지되며 추론·도구 컨텍스트가 보존돼요. - 유연한 입력: 문자열이나 메시지 목록을 입력으로 넘기고,
instructions로 시스템 수준 지시를 줄 수 있어요. - 암호화된 추론: 무상태(stateless)를 선택하면서도 고급 추론의 이점은 누릴 수 있어요.
- 미래 대비: 향후 모델을 위해 미리 설계됐어요.
두 API의 기능 비교를 요약하면, Responses API가 대부분의 기능(Chat Completions의 오디오는 "Coming soon")을 지원하고 거기에 컴퓨터 사용, 코드 인터프리터, 이미지 생성, 추론 요약 등이 더해져요.
예시
Messages vs. Items
두 API 모두 모델 출력 생성이 쉽지만, 입력과 결과의 표현이 달라요. Chat Completions 호출의 입력·결과는 Messages 배열인 반면, Responses API는 _Items_를 써요. Item은 모델 동작의 다양한 가능성을 나타내는 여러 타입의 합집합이에요. message도 Item의 한 종류고, function_call이나 function_call_output도 Item이에요. Chat Completions의 Message가 여러 관심사를 한 객체에 묶어두는 반면, Items는 서로 구분되며 모델 컨텍스트의 기본 단위를 더 잘 나타내요.
또한 Chat Completions는 n 파라미터로 choices에 여러 병렬 생성물을 반환할 수 있지만, Responses에서는 이 파라미터가 제거되어 한 번에 하나의 생성물만 남았어요.
Chat Completions 응답 예시:
{
"id": "chatcmpl-C9EDpkjH60VPPIB86j2zIhiR8kWiC",
"object": "chat.completion",
"created": 1756315657,
"model": "gpt-5.5",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Under a blanket of starlight, a sleepy unicorn tiptoed through moonlit meadows, gathering dreams like dew to tuck beneath its silver mane until morning.",
"refusal": null,
"annotations": []
},
"finish_reason": "stop"
}
],
...
}
Responses 응답 예시:
{
"id": "resp_68af4030592c81938ec0a5fbab4a3e9f05438e46b5f69a3b",
"object": "response",
"created_at": 1756315696,
"model": "gpt-5.5",
"output": [
{
"id": "rs_68af4030baa48193b0b43b4c2a176a1a05438e46b5f69a3b",
"type": "reasoning",
"content": [],
"summary": []
},
{
"id": "msg_68af40337e58819392e935fb404414d005438e46b5f69a3b",
"type": "message",
"status": "completed",
"content": [
{
"type": "output_text",
"annotations": [],
"logprobs": [],
"text": "Under a quilt of moonlight, a drowsy unicorn wandered through quiet meadows, brushing blossoms with her glowing horn so they sighed soft lullabies that carried every dreamer gently to sleep."
}
],
"role": "assistant"
}
],
...
}
Responses API에서 응답을 받으면 필드가 조금 다릅니다. message 대신 고유 id를 가진 타입화된 response 객체를 받아요. Responses는 기본적으로 저장되고, Chat Completions는 새 계정에서 기본적으로 저장돼요. 어느 API든 저장을 끄려면 store: false를 설정하세요. Chat Completions에서는 choices 배열 안의 message를 받지만, Responses에서는 output으로 불리는 Item 배열을 받아요.
추가 차이점
- Responses는 기본 저장, Chat Completions는 새 계정 기본 저장. 저장을 끄려면
store: false. - 추론 모델은 Responses API에서 개선된 도구 사용으로 더 풍부한 경험을 제공해요. GPT-5.4부터 Chat Completions는
reasoning_effort가none이 아닌 값이면 도구 호출을 지원하지 않아요. - Structured Outputs API 형태가 달라요.
response_format대신 Responses에서는text.format을 써요. 자세한 내용은 Structured Outputs 가이드를 참고하세요. - 함수 호출 API 형태가 요청의 함수 구성과 응답의 함수 호출 모두에서 달라요. 차이는 function calling 가이드에서 확인하세요.
- Responses SDK에는 Chat Completions SDK에는 없는
output_text헬퍼가 있어요. - Chat Completions에서는 컨버세이션 상태를 수동으로 관리해야 하지만, Responses API는 지속 컨버세이션을 위한 Conversations API와
previous_response_id로 응답을 쉽게 연결하는 기능을 호환성 있게 제공해요.
Chat Completions에서 마이그레이션
마이그레이션은 세 가지로 나눠서 접근해요. 요청을 /v1/responses로 보내고, 타입화된 output 배열에서 출력을 읽고, 앱이 턴 사이 상태를 어떻게 유지할지 결정하는 것.
1. 생성 엔드포인트 업데이트
생성 엔드포인트를 post /v1/chat/completions에서 post /v1/responses로 바꾸는 것부터 시작해요.
함수나 멀티모달 입력을 쓰지 않는다면, 단순 메시지 입력은 한 API에서 다른 API로 그대로 재사용할 수 있어요.
context = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"},
]
completion = client.chat.completions.create(model="gpt-6-astra", messages=context)
response = client.responses.create(model="gpt-6-astra", input=context)
Chat Completions에서는 messages 배열을 만들고 completion.choices[0].message.content에서 텍스트를 읽어요. Responses에서는 instructions와 input을 최상위에서 분리하고 response.output_text로 생성 텍스트를 읽을 수 있어요.
from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-6-astra", instructions="You are a helpful assistant.", input="Hello!"
)
print(response.output_text)
2. Messages를 Items로 매핑
Chat Completions는 입력·출력 모두 messages를 쓰지만, Responses는 타입화된 Items의 input/output 배열을 써요. message는 reasoning, function_call, function_call_output 같은 Item과 함께 있는 하나의 Item 타입이에요.
| Chat Completions 개념 | Responses 매핑 |
|---|---|
messages[] |
문자열 또는 입력 Item 배열로서의 input |
| 시스템/개발자 지시 | 최상위 instructions, 또는 기존 트랜스크립트를 보존해야 할 때 호환되는 message Item |
| 사용자 메시지 | role: "user"인 입력 message Item |
| 어시스턴트 메시지 | response.output의 출력 message Item. 수동으로 상태를 관리하는 경우 input으로 다시 넘김 |
| 도구/함수 호출 | function_call 출력 Item |
| 도구/함수 결과 | call_id로 호출과 연결된 function_call_output 입력 Item |
n으로 여러 생성물 |
Responses에서는 불가. 여러 후보 출력이 필요하면 별도 요청 |
최종 텍스트만 필요하면 SDK output_text 헬퍼를 쓰고, 추론·도구·멀티모달을 쓸 때는 response.output을 순회하며 각 Item을 type으로 처리해요.
3. 다중 턴 컨버세이션 업데이트
앱에 다중 턴 대화가 있다면 컨텍스트 로직을 업데이트해야 해요. Responses는 세 가지 일반적인 상태 관리 옵션을 제공해요.
- OpenAI가 이전 응답 컨텍스트를 관리하기를 원하면
previous_response_id를 써요. 이 파라미터는 이전 응답의 최상위instructions를 이어주지 않으므로,instructions는 요청마다 다시 보내야 해요. - 컨텍스트를 직접 관리·정리해야 한다면 이전
outputItems를 다음 요청의input으로 넘겨요. - 지속 컨버세이션 객체가 필요하면 Conversations API를 써요.
Chat Completions에서는 전체 트랜스크립트를 저장하고 누적된 messages 배열을 요청마다 보내요. Responses에서는 한 응답의 output을 다음 요청의 input으로 수동 넘길 수 있어요.
context = [{"role": "user", "content": "What is the capital of France?"}]
res1 = client.responses.create(
model="gpt-6-astra",
input=context,
)
# Append the first response's output to context
context += res1.output
# Add the next user message
context += [{"role": "user", "content": "And its population?"}]
res2 = client.responses.create(
model="gpt-6-astra",
input=context,
)
또는 previous_response_id로 이전 응답을 참조해 응답 체인이나 포크를 만들 수도 있어요.
res1 = client.responses.create(
model="gpt-6-astra", input="What is the capital of France?", store=True
)
res2 = client.responses.create(
model="gpt-6-astra",
input="And its population?",
previous_response_id=res1.id,
store=True,
)
previous_response_id를 써도 체인의 모든 이전 입력 토큰은 API에서 입력 토큰으로 과금돼요.
4. 상태 유지(statefulness) 사용 시점 결정
Responses는 기본 저장, Chat Completions는 새 계정 기본 저장이고, 저장을 끄려면 store: false예요.
Zero Data Retention(ZDR) 요구사항처럼 규정·데이터 보존 정책 때문에 상태 유지 방식으로 Responses를 쓸 수 없는 조직도 있어요. 이런 경우를 위해 OpenAI는 암호화된 추론 Items를 제공해, 워크플로는 무상태로 유지하면서 추론 Items의 이점은 누릴 수 있게 해줘요.
상태를 끄면서도 추론은 쓰려면:
- store 필드에
store: false를 설정해요. - 반환된 모든 추론 Item을 보존하고 재생(replay)해요. 응답을 만들면 각 Item에 기본적으로
encrypted_content가 포함돼요.
그러면 API가 추론 토큰의 암호화 버전을 반환하고, 이를 일반 추론 Item처럼 이후 요청에 다시 넘길 수 있어요. ZDR 조직에서는 OpenAI가 store: false를 자동으로 강제해요. encrypted_content가 포함된 요청은 메모리에서 복호화되어 다음 응답 생성에 쓰이고 안전하게 폐기돼요. 새 추론 토큰은 즉시 암호화되어 반환되므로 중간 상태가 저장되지 않아요.
5. 함수 정의와 출력 업데이트
두 API에서 함수 정의가 달라지는 작지만 중요한 차이가 두 가지 있어요.
- Chat Completions에서 함수 정의는 외부 태그(externally tagged)고, Responses에서는 내부 태그(internally tagged)예요.
- Chat Completions에서 함수는 기본적으로 비-strict(non-strict)예요. Responses에서는
strict를 생략하면 strict 모드를 시도하고, 스키마가 호환되지 않으면 비-strict의 best-effort 함수 호출로 폴백하며 해결된 도구를strict: false로 반환해요. Responses에서 확실히 비-strict로 유지하려면strict: false를 명시해요.
Chat Completions 함수 예시:
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Determine weather in my location",
"strict": true,
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"location"
]
}
}
}
Responses 함수 예시:
{
"type": "function",
"name": "get_weather",
"description": "Determine weather in my location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"location"
]
}
}
Responses에서 도구 호출과 그 출력은 call_id로 서로 연관되는 두 가지 구분된 Item 타입이에요. Responses에서 함수 호출이 어떻게 동작하는지는 function calling 문서를 참고하세요.
6. Structured Outputs 정의 업데이트
Responses API에서 Structured Outputs 정의는 response_format에서 text.format으로 옮겨졌어요.
Chat Completions 예시:
response = client.chat.completions.create(
model="gpt-6-astra",
messages=[
{
"role": "user",
"content": "Jane, 54 years old",
}
],
response_format={
"type": "json_schema",
"json_schema": {
"name": "person",
"strict": True,
"schema": {
"type": "object",
"properties": {
"name": {"type": "string", "minLength": 1},
"age": {"type": "number", "minimum": 0, "maximum": 130},
},
"required": ["name", "age"],
"additionalProperties": False,
},
},
},
reasoning_effort="medium",
)
Responses 예시:
response = client.responses.create(
model="gpt-6-astra",
input="Jane, 54 years old",
text={
"format": {
"type": "json_schema",
"name": "person",
"strict": True,
"schema": {
"type": "object",
"properties": {
"name": {"type": "string", "minLength": 1},
"age": {"type": "number", "minimum": 0, "maximum": 130},
},
"required": ["name", "age"],
"additionalProperties": False,
},
}
},
)
7. 스트리밍 소비자 업데이트
Chat Completions 스트리밍은 delta 필드가 있는 증분 청크를 반환하고, Responses 스트리밍은 타입화된 서버-전송 이벤트(SSE)를 써요. 스트림 소비자는 각 이벤트의 type으로 분기하고, UI나 오케스트레이션 레이어가 필요한 이벤트를 처리하도록 업데이트해야 해요.
텍스트 스트리밍에서는 다음 이벤트를 들어보세요.
response.createdresponse.output_text.deltaresponse.completederror
함수 호출 스트림은 response.function_call_arguments.delta나 response.function_call_arguments.done 같은 이벤트도 발생시켜요. 스트리밍 Responses 가이드와 Responses 스트리밍 이벤트 참조를 참고하세요.
8. 네이티브 도구로 업그레이드
OpenAI의 네이티브 도구 혜택을 받는 유스케이스가 있다면, 도구 호출을 OpenAI 도구를 그대로 쓰도록 업데이트할 수 있어요. Chat Completions에서는 OpenAI 호스팅 도구를 네이티브로 쓸 수 없어 직접 통합을 작성해야 하죠. (이 예시는 GPT-5.6을 쓰는데, GPT-6 Astra는 도구 호출에 Responses API가 필요하기 때문이에요.)
Responses에서는 모델이 쓰기를 원하는 도구를 지정할 수 있어요.
answer = client.responses.create(
model="gpt-6-astra",
input="Who is the current president of France?",
tools=[{"type": "web_search"}],
)
print(answer.output_text)
9. 흔한 마이그레이션 오류 체크
코드를 옮길 때 다음 문제들을 주의하세요.
choices[0].message.content를 읽는 대신response.output_text나response.output을 읽지 않는 경우- 모든
output항목을 메시지로 취급하는 것. 추론·도구·함수 호출은 별도의 Item 타입이에요. - 컨텍스트를 수동으로 다음 응답에 넘길 때 추론, 함수 호출, 함수 호출 출력 Item을 떨어뜨리는 경우
- 매칭
call_id없이 함수 결과를 보내는 경우 - Responses 요청에서
text.format대신response_format을 쓰는 경우 - 타입화된 Responses 이벤트를 처리하지 않고 Chat Completions 스트리밍 청크 핸들러를 재사용하는 경우
previous_response_id가 이전 컨텍스트의 과금을 제거한다고 가정하는 것. 응답 체인의 이전 입력 토큰은 여전히 입력 토큰으로 과금돼요.
단계적 롤아웃 체크리스트
Chat Completions는 계속 지원되므로, 사용자 플로우 하나씩 마이그레이션할 수 있어요.
- 단순 텍스트 생성 플로우부터 시작한다.
- 엔드포인트, 요청 본문, 출력 처리를 업데이트한다.
- 플로우가
previous_response_id, 수동 Item 재생, Conversations API 중 무엇을 쓸지 결정한다. - 무상태 또는 ZDR이면
store: false를 추가하고, 추론 컨텍스트가 턴을 넘어 이어져야 하면 암호화된 추론 Items를 포함한다. - 함수 정의를 마이그레이션하고 함수 호출 출력에 올바른
call_id가 포함되는지 확인한다. - Structured Outputs 스키마를
response_format에서text.format으로 옮긴다. - 스트리밍 소비자를 타입화된 Responses 이벤트 처리 방식으로 업데이트한다.
- 워크플로에 맞는 곳에 맞춤 오케스트레이션을 OpenAI 호스팅 도구로 교체한다.
- 더 많은 트래픽을 Responses로 보내기 전에 동작, 지연, 토큰 사용량, 오류를 비교한다.
시간이 지나면서 모든 플로우를 Responses API로 옮기는 걸 권장해요. 최신 OpenAI 기능과 개선을 활용할 수 있으니까요.
Assistants API
Assistants API 베타에 대한 개발자 피드백을 바탕으로, 더 유연하고 빠르고 쓰기 쉽게 만든 핵심 개선들이 Responses API에 녹아들었어요. Responses API는 OpenAI에서 에이전트를 구축하는 미래 방향을 나타내요.
Assistants API는 2026년 8월 26일에 공식적으로 종료되어 더 이상 사용할 수 없어요. 마이그레이션 가이드를 따라 통합을 Responses API로 업데이트하세요.
더 알아보기 (Learn more)
- Function calling 가이드 — Responses에서 함수 호출이 어떻게 동작하는지
- Structured Outputs —
text.format기반 구조화 출력 - Conversation state — 컨버세이션 상태 관리
- Streaming Responses — 타입화된 SSE 이벤트 다루기