OpenAI API Parity — Anthropic 코드를 OpenAI로 옮기기
Anthropic Messages API로 짠 코드를 OpenAI로 포팅할 때, 필드 이름·구조·기본값이 어떻게 어긋나는지 찾아보는 대응표.
Anthropic 쪽 값은 이 위키의 §7 first-party 사실이 최종 권위다. OpenAI 쪽은 developers.openai.com 1차 확인(HIGH)과 미확인 항목을 구분해 표기했다. openai.com 본체는 403으로 미확인.
먼저: 두 API 중 어느 것으로 옮기나 🧭
OpenAI에는 API가 둘 있다. Anthropic의 단일 Messages API와 달리 갈래를 골라야 한다.
| Responses API | Chat Completions | |
|---|---|---|
| 엔드포인트 | POST /v1/responses |
POST /v1/chat/completions |
| OpenAI 권장 | 신규 프로젝트 전부 이쪽 | 계속 지원(deprecated 아님) |
| 상태 | stateful (store, previous_response_id) |
stateless |
| 서버측 내장툴 | web search·file search·code interpreter·MCP | 없음 |
| 출력 | typed output[] / output_text |
flat choices[0].message.content |
- OpenAI 공식 문구: "While Chat Completions remains supported, Responses is recommended for all new projects."
- Anthropic Messages는 무상태(매 요청 전체 히스토리 전송)라서, 구조가 더 가까운 건 Chat Completions다. 하지만 신규 코드라면 Responses가 권장 대상이다 — 상태 유지 모델이 다르니 포팅 시 가장 큰 함정(아래 참조).
- 참고로 Assistants API는 2026-08-26 sunset (verified 2026-07) — 여기로는 옮기지 마라. 기능은 Responses로 흡수됐다.
필드 대응표 (Anthropic → OpenAI)
| 항목 | Anthropic Messages | OpenAI Chat Completions | OpenAI Responses |
|---|---|---|---|
| 시스템 프롬프트 | top-level system |
messages[] 안 system role |
top-level instructions |
| 입력 | messages[] (role+content) |
messages[] |
input (+ typed Items) |
| 툴 정의 | tools[], strict: true(top-level) |
tools[] 함수 스키마, strict: true |
tools[] flat {type,name,description,parameters,strict} |
| 툴 결과 반환 | user 턴에 tool_result 블록(tool_use_id) |
{role:"tool", tool_call_id, content} |
{type:"function_call_output", call_id, output} |
| 병렬 툴 끄기 | disable_parallel_tool_use |
parallel_tool_calls: false |
동일 |
| 구조화 출력 | output_config.format.json_schema |
response_format.json_schema |
text.format.json_schema |
| 추론 강도 | output_config.effort (기본 high) |
reasoning.effort (기본 medium) |
reasoning.effort (기본 medium) |
| 응답 접근 | content[] typed 블록 |
choices[0].message.content |
output_text / output[] |
세 API 모두 구조화 출력은 json_schema + strict/additionalProperties:false로 같은 개념이지만, 필드가 얹히는 위치가 셋 다 다르다 — 여기서 제일 많이 넘어진다.
툴/함수 호출 옮길 때
- 스키마 본문(
name/description/parameters)은 세 API가 동일. 옮기기 쉬운 부분. strict: true요구조건도 사실상 같다:"additionalProperties": false+ 모든 필드required+ 선택 필드는type: ["string","null"]. Anthropic의 strict tool use 규칙과 그대로 겹친다.- 툴 결과 반환 형식이 셋 다 다르다 (위 표). Anthropic은 user 턴 안의
tool_result블록, Chat은role:"tool"메시지, Responses는function_call_output아이템. 여기가 포팅의 핵심 수작업 지점. tool_choice: OpenAI는auto(기본)/required/특정 함수. Responses는allowed_tools도 있다.- 함수콜 스트리밍 이벤트: Responses는
response.function_call_arguments.delta/.done(verified 2026-07).
구조화 출력 옮길 때
# Anthropic (§7)
client.messages.parse(output_format=MyPydanticModel, ...) # output_config.format 로 내려감
# OpenAI Responses
client.responses.parse(text_format=MyPydanticModel, ...) # text.format.json_schema
# OpenAI Chat Completions
client.chat.completions.parse(response_format=MyPydanticModel, ...) # response_format.json_schema
- Python은 Pydantic, TS는 Zod(
zodTextFormat()/zodResponseFormat())로 스키마 변환·파싱이 자동. Anthropic의.parse()습관을 그대로 옮길 수 있다. - 두 프로바이더 모두 안전 거부(refusal)를 프로그램적으로 감지 가능한 필드로 노출한다. Anthropic은
stop_reason: "refusal", OpenAI는 별도 refusal 필드.
추론/thinking 노브 옮길 때
| Anthropic | OpenAI | |
|---|---|---|
| 켜는 법 | thinking:{type:"adaptive"} + output_config.effort |
reasoning.effort |
| effort 기본 | high |
medium |
| effort 값 | low·medium·high·xhigh·max | none·minimal·low·medium·high·xhigh·max (재확인 필요) |
| 추론 요약 | thinking.display:"summarized" (기본 omitted) |
summary: auto|concise |
| raw CoT | 절대 미반환 | 미노출(요약만) |
| 무상태 연속성 | 매 요청 전체 히스토리 | encrypted_content 재생 (재확인 필요) |
- 기본 effort가 다르다: Anthropic은
high, OpenAI는medium. 그대로 옮기면 OpenAI 쪽이 덜 생각한다 — 비용/품질 튜닝 시 유의. - 두 프로바이더 다 raw chain-of-thought는 안 준다. 요약만.
- Anthropic
effort는output_config안, OpenAIeffort는reasoning안. 컨테이너가 다르니 단순 치환 안 됨.
캐싱 대응
- Anthropic은 명시적:
cache_controlbreakpoint를 직접 찍는다. 캐시 쓰기 1.25배(5분)/2배(1시간), 읽기 0.1배. 최소 길이는 모델마다 다름(자세히는 prompt-caching). - OpenAI는 자동: 프리픽스 캐싱이 알아서 걸리고, 캐시 입력가는 표준 입력의 10%로 고정(예:
gpt-5.6-sol$0.50 vs $5.00, verified 2026-07). breakpoint 개념 없음. - 포팅 함정: Anthropic에서 정성껏 배치한
cache_control블록은 OpenAI로 옮길 때 버려라 — OpenAI에선 프롬프트 앞부분을 안정적으로 유지하는 것만이 레버다.
마이그레이션 함정 ⚠️
- 상태 모델 역전: Anthropic은 무상태라 코드가 매 턴 전체 히스토리를 보낸다. Responses로 옮기면서 그 습관을 유지하면
store/previous_response_id의 이점이 죽는다. 반대로 서버 상태에 의존하도록 짜면 무상태 재시도·리플레이가 깨진다. 정확한 stateful 필드셋은 (재확인 필요). - 구조화 출력 필드 위치 3종:
output_config.format(Anthropic) →response_format(Chat) →text.format(Responses). 이름만 바꾸면 안 되고 중첩 위치가 다르다. - 툴 결과 3종 포맷: 위 참조. 자동 변환 안 됨.
- effort 기본값 차이(high→medium)로 옮긴 직후 품질이 떨어져 보일 수 있다.
- 샘플링 파라미터: Anthropic 최신 모델은
temperature/top_p/top_k가 400 에러라 프롬프팅으로 대체해왔을 것이다. OpenAI로 옮기면 이 노브가 다시 살아나니, 재현성 때문에 명시하고 싶으면 OpenAI 쪽에서 설정. - 텍스트 스트리밍 이벤트명: Responses의 텍스트 델타 이벤트명(
response.output_text.delta추정)은 1차 미확인 (재확인 필요). 함수콜 델타 이벤트만 확인됨. - 롱컨텍스트 과금 경계: GPT-5.6은 입력 >272K 토큰이면 요청 전체가 입력 2배·출력 1.5배 (verified 2026-07). Anthropic 1M 컨텍스트 감각으로 대용량 프롬프트를 던지면 과금이 튄다.
모델 매핑 (기본값 감각)
| Anthropic 기본 | OpenAI 대응(가격대 기준) |
|---|---|
claude-opus-5 (플래그십) |
gpt-5.6-sol — 입력 $5 / 출력 $30 (verified 2026-07) |
| 중간 티어 | gpt-5.6-terra — $2.50 / $15 (verified 2026-07) |
claude-haiku-4-5 (저가) |
gpt-5.6-luna — $1.00 / $6 (verified 2026-07) |
정확한 모델 ID·티어는 openai-gpt-family에. 벤치 점수 비교는 넣지 않았다(빨리 썩고 검증 불가).
참고
- anthropic-messages-api — 옮겨오는 쪽 원본 API의 필드·구조
- openai-gpt-family — GPT-5.6 Sol/Terra/Luna 모델 ID·컨텍스트·가격 상세
- structured-output —
json_schema/strict강제 출력, Pydantic·Zod 헬퍼 - reasoning-models — effort/thinking 노브의 개념과 언제 켜나
- prompt-caching — Anthropic 명시적 캐싱 vs OpenAI 자동 캐싱
- models-overview — 프로바이더 횡단 모델 선택
출처: OpenAI 쪽은 developers.openai.com 1차 확인(HIGH)과 미확인 구분, Anthropic 쪽은 이 위키 §7 first-party. openai.com 본체는 403 미확인, stateful 필드셋·텍스트 스트리밍 이벤트명은 재확인 필요. 원본 → 2026-07-25-openai-gpt-deep