본문으로 건너뛰기

OpenAI Assistants API를 Responses API로 마이그레이션하는 방법

11 분 소요API 가이드

Assistants API 마이그레이션은 URL 교체 작업이 아닙니다. 설정의 새 소유자, 세 가지 대화 상태 전략, custom function 실행 루프, File Search, 기존 Thread 처리와 rollback 기준을 먼저 정해야 합니다.

Assistant 설정, Thread 대화 상태, Run 도구 실행을 Responses 구조로 옮기고 검증 후 점진 전환하는 흐름도

OpenAI Assistants API는 2026년 8월 26일 종료 예정입니다. 하지만 Assistant → Prompt, Thread → Conversation, Run → Response를 기계적으로 바꾸면 마이그레이션이 끝나는 것은 아닙니다. 설정을 어디에 보관할지, 대화 상태를 누가 소유할지, custom function을 누가 실행할지까지 다시 정해야 합니다.

먼저 production Assistant 하나를 고르고 다음 항목을 목록화하세요: instructions, model, tools, Thread와 애플리케이션 session의 연결, vector store, streaming, background 작업, 오류 처리, 평가용 대화. 그다음 새 경로를 feature flag 뒤에 만들고, 동일한 입력으로 기능 동등성을 확인한 뒤에만 트래픽을 늘립니다.

두 종료일을 혼동하지 마세요. Assistants API의 종료 예정일은 2026-08-26입니다. reusable prompt objects는 2026-06-03에 deprecated로 발표됐고, v1/prompts와 함께 2026-11-30 종료 예정입니다. 새 장기 시스템이라면 Assistant 설정을 다시 수명이 짧은 Prompt object에 고정하지 말고, 버전 관리되는 애플리케이션 코드에서 instructions와 tool schema를 관리하세요.

이 글의 범위는 OpenAI Assistants API에서 OpenAI Responses/Conversations API로 옮기는 작업입니다. Chat Completions 마이그레이션, Azure OpenAI/Foundry, 제3자 OpenAI-compatible endpoint 전환에는 이 객체·도구·보관 계약을 그대로 적용하면 안 됩니다.

먼저 작성할 마이그레이션 매트릭스

코드를 수정하기 전에 이 표를 실제 서비스 값으로 채우세요. 빈칸은 “자동으로 해결될 항목”이 아니라 아직 owner가 없는 항목입니다.

기존 구성Responses 쪽 목적지반드시 보존할 것통과 증거
Assistant instructions와 tools애플리케이션 코드의 INSTRUCTIONS, TOOLS동작 규칙, tool schema, 버전golden prompt 결과와 tool 선택이 허용 범위 안에 있음
ThreadConversation, previous_response_id, 또는 앱 관리 history사용자/session 연결, 순서, 필요한 item두 번째 요청이 첫 요청의 필요한 맥락을 재현
Messageinput과 message itemrole, content, 지원되는 첨부텍스트·이미지·파일 케이스별 결과 일치
Runresponses.create()동기/stream/background 선택완료·실패·취소 경로가 모두 관찰됨
Run stepresponse.output의 itemmessage, reasoning, tool call, tool outputitem type과 call_id를 로그에서 추적 가능
Assistant File SearchResponses의 hosted file_searchvector store, filter, citation 기대값정답 문서가 검색되고 citation이 연결됨
Custom function앱이 실행하는 function_call loopschema, 권한, side effect, idempotency원래 call_id로 output을 돌려주고 최종 응답 도달

OpenAI의 현재 Assistants migration guide는 개념 매핑과 기존 message backfill 예제를 제공합니다. 이 표의 “통과 증거”는 그 위에 애플리케이션 계약을 추가한 것입니다. HTTP 200이나 output_text 한 줄만으로는 File Search, tool side effect, 상태 연속성이 보존됐다고 말할 수 없습니다.

같은 주문 지원 시나리오를 before/after로 비교하세요

기존 Assistant object에는 model, instructions, tools가 함께 저장됐습니다. Responses 요청에서는 이 계약을 요청에 직접 넣을 수 있습니다. 장기 운영 기준으로는 설정을 코드에 두고 배포 버전과 함께 검토·rollback할 수 있게 하세요.

python
import os from openai import OpenAI client = OpenAI() MODEL = os.environ["OPENAI_MODEL"] INSTRUCTIONS = """ 당신은 주문 지원 담당자입니다. 확인되지 않은 배송 상태를 추측하지 마세요. 주문 상태는 반드시 도구 결과만 사용해 답하세요. """.strip() assistant = client.beta.assistants.create( model=MODEL, instructions=INSTRUCTIONS, ) thread = client.beta.threads.create() client.beta.threads.messages.create( thread_id=thread.id, role="user", content="교환 절차를 두 단계로 설명해 주세요.", ) legacy_run = client.beta.threads.runs.create_and_poll( thread_id=thread.id, assistant_id=assistant.id, ) if legacy_run.status != "completed": raise RuntimeError(f"legacy run failed: {legacy_run.status}") # AFTER: 설정은 코드가, 지속 대화는 Conversation이 소유합니다. conversation = client.conversations.create( metadata={"app_session": "migration-smoke-001"} ) response = client.responses.create( model=MODEL, conversation=conversation.id, instructions=INSTRUCTIONS, input="교환 절차를 두 단계로 설명해 주세요.", ) if response.status != "completed": raise RuntimeError(f"response failed: {response.status}") print(response.output_text)

before 쪽은 Assistant가 설정을 저장하고 Thread/Run이 상태와 실행을 맡았다는 이전 대상 책임만 보여 줍니다. after 쪽은 model을 OPENAI_MODEL에서 읽고, 설정을 코드로 이동하며, Conversation과 Response ID를 애플리케이션 session에 연결할 수 있게 합니다.

이 글의 코드 조각은 독자의 project·credential에서 실행된 것이 아닙니다. 현재 OpenAI SDK를 고정한 staging 환경에서 실행하고, 사용하는 model이 Conversations와 필요한 tool/parameter를 지원하는지 검증한 뒤 production에 적용하세요. 아직 공식 project key와 Billing을 확인하지 않았다면 한국어 OpenAI API 키 발급 가이드에서 먼저 최소 Responses 호출까지 통과시키세요.

왜 Prompt object를 기본 목적지로 삼지 않는가

공식 migration guide에는 기존 Assistant에서 named prompt를 만드는 단계가 남아 있습니다. 그러나 같은 페이지는 reusable prompts deprecation을 확인하라고 경고하며, 현재 deprecations 문서는 prompt 내용을 애플리케이션 코드로 옮기라고 안내합니다.

이미 Prompt object를 사용하는 팀은 즉시 지울 필요가 없습니다. 다만 2026-11-30 이전에 다음 전환을 별도 작업으로 잡아야 합니다.

  1. Prompt의 instructions, tool schema, structured output 설정을 export합니다.
  2. 민감한 값과 환경별 값은 secret/config owner로 분리합니다.
  3. 설정을 코드 리뷰와 버전 태그가 가능한 저장소에 넣습니다.
  4. Prompt ID와 code-managed 설정으로 같은 golden set을 실행합니다.
  5. 결과가 통과하면 Prompt ID 의존성을 제거합니다.

대화 상태는 세 가지 중 하나를 의도적으로 선택하세요

Responses에는 “Thread의 새 이름 하나”만 있는 것이 아닙니다. OpenAI의 conversation state 문서는 적어도 세 가지 패턴을 지원합니다.

1. Conversation object: 여러 세션에 걸친 지속 상태

고객 지원처럼 같은 대화를 여러 요청과 worker가 이어야 한다면 Conversation ID를 애플리케이션 session에 연결합니다.

python
conversation = client.conversations.create() first = client.responses.create( model=MODEL, instructions=INSTRUCTIONS, conversation=conversation.id, input="주문 번호 A-104의 상태를 확인하고 싶어요.", ) second = client.responses.create( model=MODEL, instructions=INSTRUCTIONS, conversation=conversation.id, input="그 주문을 취소할 수 있나요?", ) print(second.output_text)

Conversation에는 message뿐 아니라 tool call과 tool output 같은 item도 들어갈 수 있습니다. 현재 문서상 Conversation item은 Response object의 기본 30일 보관과 같은 TTL을 따르지 않습니다. 그래서 “편리한 기본값”이 아니라 retention, 삭제, 사용자 분리 정책을 먼저 승인한 뒤 선택해야 합니다.

2. previous_response_id: 짧고 선형인 연속 대화

한 worker가 직전 응답을 이어가는 단순 흐름에는 previous_response_id가 작습니다.

python
first = client.responses.create( model=MODEL, instructions=INSTRUCTIONS, input="환불 규칙을 한 문장으로 요약해 주세요.", ) second = client.responses.create( model=MODEL, instructions=INSTRUCTIONS, previous_response_id=first.id, input="방금 규칙의 예외는 무엇인가요?", )

여기에는 두 가지 함정이 있습니다.

  • previous_response_idconversation은 같은 요청에서 함께 사용할 수 없습니다.
  • 이전 Response의 instructions는 다음 요청으로 자동 승계되지 않으므로 매번 명시해야 합니다.

또한 이전 입력 token도 연속 호출에서 다시 과금될 수 있습니다. 짧은 코드만 보고 비용과 보관 요구를 생략하지 마세요.

3. 애플리케이션 관리 history: 감사와 데이터 제어 우선

조직이 item 보관, 축약, 삭제 시점을 직접 통제해야 한다면 사용자 입력과 전체 response.output item을 자체 저장하고 다음 input에 넣습니다. reasoning model과 tool call이 있는 흐름에서 output_text만 저장하면 reasoning/tool item 연결이 사라질 수 있습니다.

선택 규칙은 간단합니다.

요구우선 검토할 전략중단 조건
여러 세션·worker가 같은 대화 지속Conversationretention/삭제 계약을 승인할 수 없음
짧고 선형이며 구현을 최소화previous_response_id분기, 장기 보관, 명시적 history 제어가 필요
감사·축약·삭제를 앱이 통제앱 관리 history모든 item을 정확히 저장·재생할 수 없음

도구 유형마다 실행 책임이 다릅니다

Responses의 tool을 모두 같은 loop로 처리하면 안 됩니다. OpenAI의 현재 MCP and Connectors 문서는 remote MCP를 type: "mcp"로 연결하고, mcp_list_tools, mcp_call, 필요 시 mcp_approval_request item으로 관찰하는 흐름을 설명합니다.

도구 유형실제 실행 주체애플리케이션이 소유할 책임통과 증거
Hosted/built-in tool (file_search 등)OpenAI가 Responses 안에서 실행vector store·filter·권한·모델 지원·결과 평가hosted-tool item, citation, 빈 결과와 오류가 golden test와 일치
Remote MCPResponses가 신뢰한 remote MCP server의 tool을 조회·호출server_url, 인증, allowed_tools, 승인 정책, 전송 데이터와 제3자 보관 정책 검토예상 mcp_list_tools/mcp_call, 민감 작업 승인, 오류와 거부 경로 관찰
Custom function애플리케이션 코드arguments 검증, 인증·권한, side effect, timeout, idempotency, 최대 roundfunction_call을 실행하고 같은 call_idfunction_call_output 제출

Remote MCP에 custom function용 function_call_output loop를 붙이지 마세요. 반대로 custom function을 OpenAI나 MCP server가 자동 실행했다고 가정해서도 안 됩니다.

Custom function은 여전히 애플리케이션이 실행합니다

Responses API가 function을 대신 호출해 업무 시스템을 변경해 주는 것은 아닙니다. 공식 function calling 문서의 순서는 모델 요청 → function_call 수신 → 앱 코드 실행 → 같은 call_idfunction_call_output 제출 → 최종 응답 또는 추가 호출입니다.

다음 예제는 tool schema, 실제 실행, reasoning/tool item 보존을 한 루프로 보여 줍니다.

python
import json import os from openai import OpenAI client = OpenAI() MODEL = os.environ["OPENAI_MODEL"] TOOLS = [{ "type": "function", "name": "get_order_status", "description": "주문 ID로 현재 처리 상태를 조회합니다.", "parameters": { "type": "object", "properties": { "order_id": { "type": "string", "description": "예: A-104", } }, "required": ["order_id"], "additionalProperties": False, }, "strict": True, }] def get_order_status(order_id: str) -> dict: # 실제 서비스에서는 인증된 내부 API를 호출합니다. demo = {"A-104": {"status": "packing", "cancelable": True}} return demo.get(order_id, {"status": "not_found", "cancelable": False}) items = [{"role": "user", "content": "A-104 주문을 지금 취소할 수 있나요?"}] MAX_TOOL_ROUNDS = 6 for round_index in range(MAX_TOOL_ROUNDS): response = client.responses.create( model=MODEL, instructions=( "주문 상태는 get_order_status 결과만 사용하세요. " "not_found이면 확인 불가라고 답하세요." ), tools=TOOLS, input=items, ) items.extend(response.output) calls = [item for item in response.output if item.type == "function_call"] if not calls: print(response.output_text) break for call in calls: if call.name != "get_order_status": raise RuntimeError(f"허용하지 않은 도구: {call.name}") args = json.loads(call.arguments) result = get_order_status(**args) items.append({ "type": "function_call_output", "call_id": call.call_id, "output": json.dumps(result, ensure_ascii=False), }) else: raise RuntimeError( f"tool loop exhausted after {MAX_TOOL_ROUNDS} rounds; " f"last_response_id={response.id}" )

Production에서는 여기에 timeout, idempotency key, 권한 확인, side-effect 승인, unknown tool 차단을 추가하세요. 모델이 여러 function을 한 번에 요청할 수 있으므로 하나만 온다고 가정하지 않습니다. round가 소진되면 response ID가 포함된 관찰 가능한 오류를 남기고 중단합니다.

File Search는 “응답 성공”이 아니라 검색 품질로 검증하세요

file_search는 OpenAI가 실행하는 hosted tool이라 custom function loop와 다릅니다. 기존 vector store ID를 재사용할 수 있는지는 대상 project에서 직접 확인하고, 파일 처리 완료와 filter도 함께 검증해야 합니다.

python
import os from openai import OpenAI client = OpenAI() MODEL = os.environ["OPENAI_MODEL"] VECTOR_STORE_ID = os.environ["OPENAI_VECTOR_STORE_ID"] response = client.responses.create( model=MODEL, input="반품 가능 기간과 예외를 근거와 함께 알려 주세요.", tools=[{ "type": "file_search", "vector_store_ids": [VECTOR_STORE_ID], }], include=["file_search_call.results"], ) for item in response.output: print(item.type) print(response.output_text)

OpenAI의 File Search 문서에 따르면 검색 결과는 기본 응답에 자동 포함되지 않으며 include=["file_search_call.results"]로 요청할 수 있습니다. 검증에서는 다음을 모두 봅니다.

  • 기대한 문서가 result에 나타나는가.
  • 최종 message의 citation/annotation이 실제 source와 연결되는가.
  • 없는 규정을 만들어내지 않는가.
  • 권한이 다른 문서가 섞이지 않는가.
  • 기존 Assistant와 같은 질문에서 허용 가능한 품질 차이인가.

기존 Thread는 전부 복사하지 마세요

OpenAI는 자동 Thread→Conversation 변환 도구를 제공하지 않습니다. 권장 출발점은 새 채팅을 먼저 Responses로 보내고, 오래된 Thread는 업무상 필요한 경우에만 backfill하는 것입니다.

backfill 후보를 세 그룹으로 나누세요.

  1. 진행 중이며 반드시 이어야 하는 대화: 지원 티켓이나 미완료 workflow와 연결해 우선 변환.
  2. 조회만 필요한 기록: 원본 저장소를 read-only archive로 유지하고 새 Conversation에 모두 복사하지 않음.
  3. 보존 근거가 없거나 오래된 기록: retention 정책에 따라 삭제 또는 익명화.

공식 예제는 text와 지원되는 message content를 item으로 변환합니다. 실제 Thread에 attachment, tool event, metadata, unsupported legacy content가 있다면 별도 mapping과 손실 보고가 필요합니다. 변환 개수와 HTTP 성공률이 아니라, 이어진 질문에서 필요한 사실과 tool state가 재현되는지를 확인하세요.

Run polling은 목적에 따라 다시 설계하세요

일반 Responses 호출은 동기 또는 stream으로 처리할 수 있습니다. 오래 걸리는 작업만 background=True를 검토합니다. OpenAI의 background mode 문서에 따르면 background response는 queued 또는 in_progress 동안 조회할 수 있고 취소도 가능합니다.

기존 Run에 polling 코드가 있었다는 이유만으로 모든 요청을 background로 옮기지 마세요. 다음 질문에 답한 뒤 선택합니다.

  • 사용자가 첫 token을 기다리는 대화인가, 완료 이벤트만 필요한 batch 작업인가.
  • 중간 stream event가 UI에 필요한가.
  • timeout 뒤 작업을 계속할 것인가, 취소할 것인가.
  • background 저장과 조직의 data policy가 맞는가.

Cutover는 일곱 개 증거가 모였을 때만 진행하세요

staging에서 old/new 경로에 같은 golden set을 보내고 다음 결과를 나란히 저장합니다.

Gate통과 기준실패 시 행동
Instructions금지·필수 행동이 모두 유지설정 owner와 prompt diff 수정
Conversation두 번째 turn과 재접속 후 맥락 유지상태 전략 또는 session mapping 수정
Custom tools예상 tool, arguments, call_id, output 연결schema/dispatcher/idempotency 수정
File Search목표 문서, citation, 거부 사례 통과vector store/filter/readiness 재검사
Streaming/background완료·실패·취소 event 처리transport/state machine 수정
Quality·latency·cost사전에 정한 guardrail 이내model/config 또는 범위 조정
Rollbackflag 한 번으로 기존 경로 복귀production 전환 중단

권장 순서는 내부 tester → 낮은 위험의 신규 session → 작은 비율의 실제 트래픽 → 전체 신규 session입니다. 기존 Thread backfill은 별도 batch로 유지합니다. old Assistant object와 코드는 rollback 기간이 끝나기 전에 삭제하지 않습니다.

다음 상황에서는 전환을 중단합니다.

  • tool side effect가 중복되거나 call_id 추적이 끊긴다.
  • File Search가 잘못된 문서를 인용하거나 기대 문서를 찾지 못한다.
  • Conversation이 다른 사용자 session과 섞인다.
  • error/cancel 경로가 관찰되지 않는다.
  • 비용 또는 p95 latency가 승인한 guardrail을 넘는다.
  • rollback이 데이터 삭제나 수동 hotfix에 의존한다.

마이그레이션 완료의 증거는 “새 endpoint가 답했다”가 아닙니다. 설정, 상태, tool output, 검색 근거, 비동기 동작과 rollback이 모두 관찰 가능한 계약으로 바뀌었을 때 완료입니다.

#OpenAI Assistants API#Responses API#Conversations API#도구 호출#API 마이그레이션
Share: