OpenAI SDK를 사용했는데 Claude가 응답했습니다. 그 이유는 다음과 같습니다.
요약
OpenAI SDK를 사용하여 Anthropic의 Claude 모델을 호출할 수 있는 이유와 그 이면에 숨겨진 기술적 계층을 설명합니다. SDK, 엔드포인트, API 계약, 모델 식별자의 차이를 통해 AI 애플리케이션의 구조를 이해하도록 돕습니다.
핵심 포인트
- SDK는 모델이 아닌 요청 구성 및 응답 파싱을 담당하는 클라이언트임
- Anthropic은 OpenAI 호환 엔드포인트를 통해 기존 SDK 활용을 지원함
- API 호출은 SDK, 엔드포인트, 모델 식별자 등 여러 계층으로 분리됨
- 패키지 이름(OpenAI)이 반드시 실행되는 모델을 결정하지 않음
OpenAI Python 패키지를 설치합니다.
OpenAI를 임포트(import)합니다.
client.chat.completions.create()를 호출합니다.
그리고 Claude가 응답합니다.
import os
from openai import OpenAI
...
처음 이 코드를 보면 잘못된 것처럼 보입니다.
SDK가 openai라고 되어 있다면, OpenAI 모델이 응답해야 하는 것 아닐까요?
아닙니다. 그리고 그 이유는 이 코드 샘플 하나를 넘어서는 매우 중요한 의미를 갖습니다.
10초 요약 답변
해당 코드 조각에는 세 가지 독립적인 선택 사항이 숨겨져 있습니다:
- SDK: 애플리케이션이 요청(request)을 구성하고 응답(response)을 읽는 방식을 결정합니다.
- 엔드포인트(endpoint) 및 API 계약 (API contract): 요청이 어디로 가는지, 네트워크를 통해 어떤 형태의 데이터가 오가는지를 결정합니다.
- 모델 식별자(model identifier) 및 라우팅 규칙 (routing rules): 궁극적으로 무엇이 실행될지를 결정합니다.
패키지 이름은 모델 이름이 아닙니다.
위의 예시에서는 다음과 같습니다:
OpenAI Python SDK
↓ OpenAI 스타일의 HTTP로 통신
Anthropic 호환 엔드포인트 (compatibility endpoint)
...
Anthropic은 기존 OpenAI 통합 환경에서 Claude를 테스트하고 비교할 수 있도록 이 호환 레이어(compatibility layer)를 공식적으로 제공합니다. 이는 곧 설명할 이유들로 인해, Claude를 우선적으로 사용하는 대부분의 애플리케이션에서 권장되는 프로덕션(production) 경로는 아닙니다.
하지만 그전에, 개발자들이 실수로 "AI"라는 단어 하나로 압축해버리는 계층들을 분리해 봅시다.
하나의 API 호출 뒤에 숨겨진 4가지 계층
유용한 멘탈 모델(mental model)은 "SDK → 모델"이 아닙니다. 그것은 다음과 같습니다:
┌───────────────────────────────────────────────┐
│ 1. 애플리케이션 (Application) + SDK │
│ 요청을 구축하고 응답을 파싱(parse)함 │
...
이 계층들은 하나의 프로그램, 여러 개의 컨테이너, 또는 여러 회사의 인프라(infrastructure)에 존재할 수 있습니다. 경계는 개념적이지만, 책임은 서로 다릅니다.
1. SDK는 모델이 아니라 클라이언트(client)입니다
SDK는 보통 다음과 같은 기능을 제공합니다:
- 인증(authentication) 및 기본 헤더(default headers);
- 요청(request) 및 응답(response) 타입;
- 직렬화(serialization) 및 검증(validation);
- 재시도(retries), 타임아웃(timeouts), 그리고 에러 클래스(error classes);
- 스트리밍(streaming) 도우미;
- 생(raw) HTTP보다 더 나은 인터페이스.
SDK가 없다면, 동일한 작업을 HTTP 클라이언트로 수행할 수 있습니다:
import os
import httpx
...
Anthropic의 네이티브 응답은 content[]를 사용합니다. OpenAI 스타일의 응답은 choices[]를 사용합니다.
이러한 형태(shapes)는 API 계약 (API contracts)입니다. 둘 중 어느 것도 신경망 (neural network)의 자연스러운 출력 형식이 아닙니다.
2. 엔드포인트 (endpoint)는 단순한 URL 그 이상입니다
다음 클라이언트들을 고려해 보세요:
# OpenAI
OpenAI(
api_key=os.environ["OPENAI_API_KEY"],
...
호출 방식은 거의 변하지 않지만, 요청은 신뢰 (trust), 과금 (billing), 지연 시간 (latency), 그리고 데이터 경계 (data boundaries)가 완전히 다른 세 가지 영역을 가로지릅니다.
base_url은 다음을 가리킬 수 있습니다:
- 모델 제공업체 자체;
- OpenRouter 또는 LiteLLM과 같은 멀티 제공업체 게이트웨이 (multi-provider gateway);
- 클라우드 프록시 (cloud proxy);
- vLLM과 같은 OpenAI 호환 서버;
- Ollama와 같은 로컬 런타임 (local runtime);
- 귀하의 자체 내부 정책 및 라우팅 서비스.
이것이 바로 "우리는 OpenAI SDK를 사용한다"라는 말이 아키텍트에게 프롬프트가 어디로 가는지에 대해 거의 아무것도 알려주지 않는 이유입니다.
다음 질문들은 다음과 같아야 합니다:
base_url은 무엇인가?- 해당 엔드포인트를 누가 제어하는가?
- 어떤 모델 식별자 (model identifier)가 전송되는가?
- 게이트웨이가 이를 재작성하거나 재라우팅 (reroute)할 수 있는가?
- 어떤 프로토콜 기능들이 변환 과정에서 살아남는가?
3. 서빙 계층 (serving layer)이 API 형태의 응답을 생성합니다
가장 낮은 유효 수준에서, 언어 모델 (language model)은 숫자로 작동합니다.
프롬프트는 포맷팅되고 토큰화 (tokenized)됩니다. 모델은 로짓 (logits)을 생성합니다. 생성 전략 (generation strategy)이 새로운 토큰 ID를 선택합니다. 그 ID들은 텍스트로 디코딩 (decoded)됩니다.
예를 들어, Hugging Face Transformers 문서는 generate()가 토큰 시퀀스(token sequences)를 반환하거나, 요청 시 더 풍부한 내부 ModelOutput을 반환한다고 명시합니다:
generated_ids = model.generate(**model_inputs, max_new_tokens=50)
text = tokenizer.batch_decode(
generated_ids,
...
결과물에 다음과 같은 내용이 포함되어야 한다고 요구하는 보편적인 신경망 법칙은 없습니다:
{
"choices": [],
"finish_reason": "stop",
...
해당 공개 JSON 형태는 생성 런타임 (generation runtime) 주변의 소프트웨어에 의해 조립되는 것입니다.
서빙 시스템 (serving system)은 내부 텍스트, 토큰 ID (token IDs), 종료 메타데이터 (finish metadata), 타이밍 정보 (timing information), 캐시 통계 (cache statistics), 그리고 스케줄러 상태 (scheduler state)를 생성할 수도 있습니다. 중요한 차이점은 "엔진이 텍스트만 반환한다"는 것이 아닙니다. 중요한 차이점은 다음과 같습니다:
Provider JSON은 서빙/API 레이어에 의해 생성된 네트워크 계약 (network contract)이지, 모델 가중치 (model weights)의 본질적인 속성이 아닙니다.
vLLM과 같은 프로젝트들이 이를 가시화합니다. 즉, 추론 엔진 (inference machinery)은 내부 요청 출력 (internal request outputs)을 반환하는 반면, OpenAI 호환 서버 (OpenAI-compatible server)는 OpenAI 스타일의 스키마 (schemas)를 가진 엔드포인트 (endpoints)를 노출합니다.
4. 모델은 다운스트림 (downstream)에서 선택됩니다
model 필드는 서비스에 대한 요청이지, Python 임포트 (import)가 아닙니다.
엔드포인트가 이를 어떻게 해석할지를 결정합니다.
response = client.chat.completions.create(
model="anthropic/claude-sonnet-4.6",
messages=[{"role": "user", "content": "Hello"}],
...
게이트웨이 (gateway)는 다음과 같은 작업을 수행할 수 있습니다:
- 해당 식별자 (identifier)를 Anthropic으로 매핑 (map);
- 여러 업스트림 (upstream) 리전 (regions) 중 하나를 선택;
- 동일한 오픈 모델 (open model)을 호스팅하는 다른 제공업체로 페일오버 (fail over);
- 식별자 거부;
- 조직에서 구성한 별칭 (alias) 적용.
따라서 모델 문자열조차 항상 완전한 배포 정체성 (deployment identity)인 것은 아닙니다. 프로덕션 (production) 환경에서는 플랫폼이 노출할 때마다 결정된 제공업체 (provider), 모델 버전 (model version), 요청 ID (request ID), 그리고 라우팅 결정 (routing decision)을 로그로 남기십시오.
"OpenAI 호환"이 "동일함"을 의미하지는 않습니다
이 지점이 편리한 프로토타입이 조용한 프로덕션 버그 (production bug)로 변하는 곳입니다.
두 서비스가 /v1/chat/completions를 지원하더라도 다음과 같은 사항에서 서로 다를 수 있습니다:
- 허용되는 파라미터 (parameters);
- 도구 호출 (tool-call) 보장 사항;
- 멀티모달 (multimodal) 콘텐츠;
- 구조화된 출력 (structured output) 강제 적용;
- 스트리밍 이벤트 (streaming event) 세부 정보;
- 토큰 계산 (token accounting);
- 에러 페이로드 (error payloads);
- 제공업체별 기능 (provider-specific capabilities).
Anthropic은 자사의 OpenAI 호환 레이어 (compatibility layer)에 있는 몇 가지 구체적인 제한 사항을 문서화하고 있습니다:
- 함수 호출 (function calling)을 위한
strict옵션이 무시됩니다; response_format,logprobs및 기타 여러 필드가 무시됩니다;- 이 인터페이스를 통해서는 프롬프트 캐싱 (prompt caching)을 지원하지 않습니다;
- 시스템 (system) 메시지와 개발자 (developer) 메시지가 상위로 끌어올려져(hoisted) 결합됩니다;
- Claude의 전체 기능 세트를 사용하려면 네이티브 Claude API가 필요합니다.
지원되지 않는 일부 필드는 조용히 무시됩니다.
마지막에 언급한 동작은 명확한 에러가 발생하는 것보다 더 위험합니다. 코드는 컴파일될 수 있고, 요청은 200 응답을 반환할 수 있지만, 당신의 가정이 여전히 틀릴 수 있기 때문입니다.
Ollama 역시 똑같이 주의 깊은 표현을 사용합니다. 즉, OpenAI API의 **일부 (parts)**를 지원한다고 명시합니다. vLLM은 지원되는 파라미터와 추가 파라미터 목록을 별도로 문서화하고 있습니다.
호환성은 불리언(boolean) 값이 아니라 스펙트럼(spectrum)입니다.
"base_url만 바꾸면 되는" 마이그레이션
다음과 같은 프로토타입이 작동한다고 가정해 봅시다:
client = OpenAI(
api_key=os.environ["GATEWAY_API_KEY"],
base_url=os.environ["LLM_BASE_URL"],
...
기본적인 채팅 완성 (chat completion)을 위해서는 환경 변수를 변경하는 것만으로도 충분할 수 있습니다.
하지만 운영 환경(production)에서는 다음과 같은 기능들이 추가됩니다:
- 병렬 도구 호출 (parallel tool calls);
- 엄격한 JSON 스키마 (strict JSON schemas);
- 이미지 또는 PDF;
- 프롬프트 캐싱 (prompt caching);
- 토큰 단위 스트리밍 (token-level streaming);
- 제공자별 안전 제어 (provider-specific safety controls);
- 상세한 사용량 계정 (detailed usage accounting).
이제 최저 공통 분모 (lowest common denominator)를 선택하는 방식이 비용을 발생시키기 시작합니다.
추상화는 공짜가 아니었습니다. 당신은 번역 작업을 게이트웨이(gateway)로 미루었고, 그에 따른 충실도(fidelity)의 한계를 수용한 것입니다.
어떤 접근 방식을 선택해야 할까요?
| 상황 | 합리적인 기본값 | 주요 트레이드오프 (trade-off) |
|---|---|---|
| Claude 우선 운영 앱 | 네이티브 Anthropic SDK | 최상의 Claude 기능 커버리지; 더 강력한 벤더 결합 (vendor coupling) |
| ... |
진지한 멀티 프로바이더 (multi-provider) 애플리케이션을 구축한다면, 저는 구현부에서 네이티브 제공자 SDK를 사용하는 작은 내부 인터페이스를 만드는 것을 선호합니다.
예를 들어:
from typing import Protocol
class TextModel(Protocol):
...
이는 base_url을 바꾸는 것보다 더 많은 작업이 필요하지만, 손실이 발생하는 부분(lossy parts)을 가시화해 줍니다. 당신의 도메인 코드는 당신이 정의한 계약(contract)에 의존하는 반면, 제공자별 기능은 각 어댑터(adapter) 내부에서 여전히 사용할 수 있게 됩니다.
대신 범용 게이트웨이 (universal gateway)를 선택한다면, 당신이 의존하는 모든 기능에 대해 계약 테스트 (contract tests)를 생성하세요:
✓ plain text
✓ streaming
✓ tool call arguments
...
단순히 첫 번째 "Hello" 요청이 성공하는지만 테스트하지 마세요.
유지해야 할 멘탈 모델 (mental model)
누군가가 다음과 같이 말할 때:
"우리는 OpenAI SDK를 사용합니다."
이를 다음과 같이 번역하세요:
"이 코드는 OpenAI API 형태 (shape)를 중심으로 설계된 클라이언트 (client)를 사용합니다."
이것은 자동으로 다음을 의미하지 않습니다:
- OpenAI가 엔드포인트 (endpoint)를 호스팅함;
- GPT가 답변을 생성함;
- 모든 OpenAI 기능이 지원됨;
- 프롬프트 (prompts)가 게이트웨이를 거치지 않음;
- 제공자 (provider)를 전환해도 손실이 없음.
다음의 체인을 기억하세요:
SDK → API 계약 (API contract) → 엔드포인트/라우터 (endpoint/router) → 서빙 런타임 (serving runtime) → 모델 (model)
SDK는 프로토콜 (protocol)을 말합니다.
엔드포인트는 요청을 수신하고 라우팅할 수 있습니다.
서빙 스택 (serving stack)은 생성 작업을 실행하거나 위임합니다.
모델은 토큰 확률 (token probabilities)을 생성합니다.
머릿속에서 이러한 책임들이 분리되고 나면, "OpenAI SDK가 Claude를 호출한다"는 것은 더 이상 마법처럼 보이지 않습니다. 그것은 언제나 그랬듯, 하나의 클라이언트가 Claude에 도달하는 방법을 아는 엔드포인트에 호환 가능한 네트워크 계약 (network contract)을 전달하는 과정이 됩니다.
LLM 제공자를 전환했을 때 가장 먼저 망가진 것은 무엇이었나요: 스트리밍 (streaming), 도구 호출 (tool calls), 구조화된 출력 (structured output), 아니면 사용량 계정 (usage accounting)이었나요?
실패 사례를 댓글로 공유해 주세요. 그러한 에지 케이스 (edge cases)들이 바로 "호환성"이 흥미로워지는 지점입니다.
참고 문헌 (References)
- Anthropic: OpenAI SDK compatibility
- Hugging Face Transformers: Generation
- vLLM: OpenAI-Compatible Server
- Ollama: OpenAI compatibility
고지 사항: 이 기사는 저자의 독창적인 기술 자료와 주제 관련 지식을 바탕으로 작성되었습니다. AI는 기사의 구조를 재구성하고, 영문 교정을 수행하며, 커버 이미지를 생성하는 데 도움을 주는 용도로 사용되었습니다. 기술적 주장과 인용된 출처는 발행 전 검토되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기