📚 전체 지도 보기
API로 LLM을 부른다는 게 기술적으로 무엇인가
“API로 Claude를 부른다.” “OpenAI API를 붙였다.”
이 문장을 매일 듣긴 하는데, 정작 그 안에서 진짜로 뭐가 일어나는지 물어보면 답이 애매해지는 분들이 꽤 많습니다.
SDK 한 줄 깔고 client.messages.create(...) 찍어서 답이 돌아오면 “된다”는 건 알아요. 그런데 그 한 줄이 네트워크 레벨에서 어떤 HTTP 요청이고, 왜 어떤 경우엔 타입을 치자마자 글자가 흘러나오고 어떤 경우엔 3초 기다려야 하며, 왜 “모델이 함수를 실행했다”는 말이 기술적으로 거짓말에 가까운지 — 이걸 한 번 제대로 훑고 가면 이후에 나오는 agent·streaming·tool use 얘기가 전부 다르게 읽힙니다.
이 글은 6,000~7,500자 안에서 이걸 다 훑습니다.
- Anthropic Messages vs OpenAI Chat Completions의 JSON 구조
- Streaming이 실제로는 SSE 프로토콜 위에서 도는 얘기
- Tool Use 라운드트립이 4단계라는 얘기 (그리고 모델은 함수를 실행하지 않는다는 얘기)
- 토큰 경제학 — 가격표·배치·캐싱·Opus 4.7 토크나이저 +35% 함정
- Rate limit이 5축(RPM/TPM/RPD/TPD/IPM)이라는 얘기
- SDK vs Raw HTTP, 언제 뭘 쓰나
하나씩 가보죠.
1. POST 요청 한 번이 전부다
먼저 이 문장부터 머리에 박고 시작합시다.
LLM API를 부른다 = https://api.anthropic.com/v1/messages 에 HTTPS POST를 한 번 쏘는 것.
끝입니다. 진짜로요.
Anthropic이든 OpenAI든, SDK든 Python이든 Node든 Go든, 최종적으로 네트워크 레벨에서 일어나는 건 HTTPS 위에 JSON 바디를 얹은 POST 한 발입니다. 나머지 전부(SDK, 스트리밍, tool use)는 이 POST 한 발을 어떻게 감싸고 어떻게 응답을 읽느냐의 문제일 뿐이에요.
curl로 직접 쏘면 이렇게 생겼습니다.
POST /v1/messages HTTP/1.1
Host: api.anthropic.com
x-api-key: sk-ant-...
anthropic-version: 2023-06-01
content-type: application/json
{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"system": "You are a helpful assistant.",
"messages": [
{"role": "user", "content": "LLM API가 뭐예요?"}
]
}
이게 전부예요. x-api-key 헤더에 키를 박고, JSON 바디를 던지면, 응답으로 JSON이 돌아옵니다.
돌아오는 응답도 생각보다 단순합니다.
{
"id": "msg_01XYZ...",
"type": "message",
"role": "assistant",
"model": "claude-opus-4-7",
"content": [
{"type": "text", "text": "LLM API라는 건..."}
],
"stop_reason": "end_turn",
"usage": {"input_tokens": 18, "output_tokens": 142}
}
content에 답변 텍스트usage에 이번 호출에서 쓴 토큰 수 (이게 과금 기준)stop_reason에 왜 끝났는지 (end_turn,max_tokens,stop_sequence,tool_use중 하나)
SDK를 쓰든 curl을 쓰든, 이 구조 위에서 움직입니다. 그래서 디버깅이 안 풀릴 때는 raw HTTP로 내려가서 보는 게 제일 빠르다는 말이 나오는 겁니다. SDK가 뭘 감추고 있는지를 벗기고 나면, 그 밑은 이 JSON 두 덩어리밖에 없어요.
2. Anthropic Messages vs OpenAI Chat — JSON 구조는 쌍둥이처럼 다르다
두 회사 다 “메시지 리스트를 던지면 답이 온다”는 기본 꼴은 같습니다. 그런데 들여다보면 작은 차이들이 계속 발목을 잡아요. 그중 가장 큰 차이 두 개만 봅시다.
차이 1 — system의 위치.
Anthropic Messages API는 system이 messages 바깥에 있습니다.
{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"system": "You are a senior Python engineer.",
"messages": [
{"role": "user", "content": "..."}
]
}
OpenAI Chat Completions는 system이 messages 안의 첫 원소로 들어갑니다.
{
"model": "gpt-5",
"messages": [
{"role": "system", "content": "You are a senior Python engineer."},
{"role": "user", "content": "..."}
]
}
같은 개념인데 위치가 다르다는 것. 이게 OpenAI 코드를 Claude로 포팅할 때 제일 자주 버그가 생기는 지점이에요. SDK가 감춰줘서 티가 안 날 뿐입니다.
차이 2 — max_tokens의 지위.
Anthropic은 max_tokens가 필수입니다. 빼면 400 에러가 나요. 이게 “출력 길이 상한을 항상 명시하라”는 설계 철학입니다.
OpenAI는 max_tokens(또는 max_completion_tokens)가 선택입니다. 안 쓰면 모델 최대치까지 돌려줍니다.
작은 차이 같지만 실무에선 큽니다. Claude는 토큰 예산을 미리 고정하니까 비용 예측이 쉬워요. OpenAI는 안 쓰면 풀로 때려버려서 예상 밖 고지서가 날아올 수 있습니다.
공통점은 이겁니다.
messages는{role, content}객체의 배열role은user,assistant,system(OpenAI는 추가로tool, Anthropic은tool_use/tool_result블록으로 처리)stream: true를 넣으면 Server-Sent Events로 전환tools배열에 함수 스키마를 넣으면 tool use 모드
2026년 4월 기준, Anthropic의 주력은 Opus 4.7 · Sonnet 4.6 · Haiku 4.5 3장, OpenAI는 GPT-5 · GPT-5.4가 플래그십입니다. 모델 이름만 바꾸고 나머진 거의 그대로 도는 구조예요.
3. Streaming = SSE 프로토콜 위에서 도는 얘기
ChatGPT에서 답이 한 글자씩 흘러나오는 것, 보셨죠? 그게 스트리밍이고, 네트워크 레벨에선 Server-Sent Events(SSE) 라는 아주 오래된 웹 표준 위에서 돕니다.
WebSocket이 아닙니다. HTTP/1.1 커넥션을 열어둔 채로, 서버가 data: {...}\n\n 형식의 텍스트 덩어리를 연속으로 밀어넣는 방식. 단방향 푸시라 구현도 훨씬 단순합니다.
요청에서 stream: true만 넣으면 응답이 통째로 돌아오지 않고, 이벤트 시퀀스로 쪼개져서 날아옵니다.
Anthropic의 경우, 이벤트 순서가 꽤 구조적입니다.
event: message_start
data: {"type":"message_start","message":{"id":"msg_01...","role":"assistant",...}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"LLM"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" API"}}
... (수십~수백 개의 delta) ...
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":142}}
event: message_stop
data: {"type":"message_stop"}
정리하면 message_start → content_block_start → content_block_delta × N → content_block_stop → message_delta → message_stop 이 한 셋입니다.
OpenAI는 좀 더 플랫합니다. choices[0].delta.content에 토큰 조각이 계속 들어오고, 끝나면 finish_reason이 박힌 마지막 청크 뒤에 data: [DONE] 한 줄이 찍히고 커넥션이 닫힙니다.
data: {"choices":[{"delta":{"content":"LLM"},"finish_reason":null}]}
data: {"choices":[{"delta":{"content":" API"},"finish_reason":null}]}
...
data: {"choices":[{"delta":{},"finish_reason":"stop"}]}
data: [DONE]
왜 이렇게 하느냐.
LLM은 앞에서 본 대로 한 토큰씩 순차적으로 생성합니다. 답이 완성될 때까지 기다렸다가 통째로 주면, 500 토큰짜리 답변은 체감 5~10초를 멀뚱하게 기다려야 해요. 스트리밍은 첫 토큰이 나오는 순간 바로 클라이언트로 밀어넣기 때문에, TTFT(Time To First Token) 가 1초 이내로 내려옵니다. ChatGPT 같은 “타이핑되는” UX가 가능한 이유가 이겁니다.
◀── event: message_start
◀── event: content_block_start
◀── event: content_block_delta (TTFT ≈ 0.5~1.5s)
◀── event: content_block_delta × N
◀── event: content_block_stop
◀── event: message_delta (usage)
◀── event: message_stop
SDK를 쓰면 이 SSE 파싱을 자동으로 해줘서 for chunk in response 루프로 쓰면 되지만, 디버깅할 땐 curl에 --no-buffer 붙여서 원본 청크를 직접 보는 게 가장 빠릅니다.
4. Tool Use 라운드트립 — 모델은 함수를 실행하지 않는다
여기서 오해가 제일 많이 생깁니다.
“Claude가 날씨 API를 호출했다.” “GPT가 내 DB를 조회했다.”
틀렸습니다. 모델은 함수를 실행한 적이 없습니다. 모델은 “이 함수를 이 인자로 부르라”는 JSON을 뱉었을 뿐이고, 실제 실행은 당신 코드가 한 겁니다.
이걸 정확히 이해하면 tool use 관련 버그의 90%가 풀립니다.
흐름은 이렇게 4단계입니다.
[1] 클라이언트 → 모델: tools 스키마와 함께 요청을 보낸다.
{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"tools": [{
"name": "get_weather",
"description": "Get current weather for a city",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}],
"messages": [
{"role": "user", "content": "서울 날씨 어때?"}
]
}
[2] 모델 → 클라이언트: stop_reason: "tool_use"와 함께 함수 호출 의도를 JSON으로 돌려준다.
{
"stop_reason": "tool_use",
"content": [
{"type": "text", "text": "날씨를 확인해 드릴게요."},
{"type": "tool_use", "id": "toolu_01ABC",
"name": "get_weather",
"input": {"city": "Seoul"}}
]
}
OpenAI에선 이게 tool_calls 배열로 돌아옵니다. 이름만 다르고 개념은 같아요.
[3] 클라이언트가 실제로 함수를 실행한다. 여기서 모델은 관여하지 않습니다. get_weather("Seoul")을 실제 API든 내 DB든 불러서 결과를 받아오는 건 전적으로 당신 코드의 책임입니다.
[4] 클라이언트 → 모델: 실행 결과를 tool_result로 messages에 추가해서 같은 대화 컨텍스트로 다시 요청한다.
{
"messages": [
{"role": "user", "content": "서울 날씨 어때?"},
{"role": "assistant", "content": [
{"type": "text", "text": "날씨를 확인해 드릴게요."},
{"type": "tool_use", "id": "toolu_01ABC",
"name": "get_weather", "input": {"city": "Seoul"}}
]},
{"role": "user", "content": [
{"type": "tool_result", "tool_use_id": "toolu_01ABC",
"content": "21°C, 맑음"}
]}
]
}
그제서야 모델은 “서울은 지금 21도에 맑네요” 같은 최종 답변을 텍스트로 내놓습니다.
messages + tools[ {name, input_schema} ]
stop_reason:”tool_use” + { name, input } (JSON)
※ 모델은 실행 안 함. “불러라”만 말함.
실제 get_weather(“Seoul”) 실행 → “21°C, 맑음”
messages에 tool_result 추가 → 재요청 → 최종 답변
이 구조의 귀결이 중요합니다.
- 모델이 위험한 쿼리(예:
DROP TABLE users)를 “실행”할 수 없습니다. 실행은 당신 코드예요. 당신이 막으면 됩니다. - 한 턴에 tool이 여러 번 도는 것도 가능합니다. tool_use → execute → tool_result → tool_use → execute → tool_result → … agent loop가 이 위에서 굴러갑니다.
- 그래서 agent 개발은 “모델이 똑똑한가”보다 “tool_result를 얼마나 잘 정제해서 다시 넣느냐“가 생산성을 결정합니다.
5. 토큰 경제학 — 가격표와 Opus 4.7 +35% 함정
API를 프로덕션에 붙이기 전에 반드시 한 번은 만나야 하는 게 과금 구조입니다.
2026년 4월 기준, 주요 모델 가격을 정리하면 이렇습니다. 모두 1M 토큰당 USD.
| 모델 | Input | Output |
| Claude Opus 4.7 | $5 | $25 |
| Claude Sonnet 4.6 | $3 | $15 |
| Claude Haiku 4.5 | $1 | $5 |
| GPT-5 | $1.25 | $10 |
| GPT-5.4 | $2.50 | $15 |
| GPT-4.1 nano | $0.10 | $0.40 |
세 가지 포인트.
포인트 1 — 출력이 입력보다 4~5배 비싸다.
거의 모든 모델에서 output 가격이 input의 4~5배예요. 그래서 비용 최적화의 1순위는 출력 길이 줄이기입니다. max_tokens를 타이트하게 걸고, “짧게 답해”라고 system에 박고, 필요 없는 경우 JSON 모드로 구조화 출력만 받는 식.
포인트 2 — 할인 카드 두 장이 있다.
- Batch API: -50%. 실시간이 필요 없는 대량 작업(데이터 분류, 대량 번역 등)은 Batch로 돌리면 반값입니다. 대신 24시간 안에 결과 돌려준다는 SLA.
- 프롬프트 캐싱: 캐시 히트 부분 최대 -90%. 같은 system prompt나 긴 문서를 반복해서 넣는 케이스에서 큰 폭 할인. Anthropic은
cache_control블록을 명시적으로 지정하는 방식이고, OpenAI는 일정 조건에서 자동으로 걸립니다.
RAG나 긴 system prompt가 고정인 애플리케이션에서 캐싱은 거의 필수라고 봐야 해요. 안 걸고 돌리면 돈이 녹습니다.
포인트 3 — Opus 4.7의 “같은 텍스트 +35% 토큰” 함정.
Anthropic 공식 발표에 따르면 Opus 4.7은 새 토크나이저를 씁니다. 그 결과 같은 입력 텍스트가 이전 버전 대비 약 +35% 더 많은 토큰으로 카운트됩니다.
무슨 뜻이냐면, 단가가 똑같아 보여도 실질 비용이 1.35배로 오른 것과 유사하다는 겁니다. Sonnet 4.6 → Opus 4.7 갈아탈 때, 카드 단가만 보고 “$15 → $25, 1.67배”로 계산하면 안 되고, 토큰 수 증가분까지 곱해서 약 2.25배로 잡아야 맞습니다.
공식 문서에 “주의하라”고 명시된 함정이라 이건 추정이 아닙니다. 모델 전환 전에 실제 토큰 카운트를 한 번 찍어보는 게 안전합니다.
6. Rate Limit 5축 — RPM만 보면 당한다
개발자가 제일 많이 부딪히는 게 429 Too Many Requests입니다. 이때 많은 분들이 RPM(분당 요청 수)만 올리려고 하는데, 레이트 리밋은 그것보다 복잡해요.
OpenAI 공식 문서 기준으로 리밋은 5개 축 위에서 걸립니다.
- RPM (Requests Per Minute) — 분당 요청 건수
- TPM (Tokens Per Minute) — 분당 처리 토큰 합 (입력+출력)
- RPD (Requests Per Day) — 일일 요청 건수
- TPD (Tokens Per Day) — 일일 토큰 합
- IPM (Images Per Minute) — 비전/이미지 입력 전용 (멀티모달 케이스)
이 중 어느 하나라도 히트하면 429가 떨어집니다. 그래서 “요청 빈도는 낮은데 왜 막히지?” → 답은 대개 긴 prompt로 TPM을 태워서인 경우가 많습니다.
OpenAI의 Tier 구조를 예로 들면,
- Tier 1 (가입 직후): 수만 TPM 수준
- Tier 2~3 (스타트업 초기, 누적 $50~$500 결제): 2~4M TPM
- Tier 5 (누적 $1,000+ 결제, 30일 이상): 30M TPM 수준까지 개방
Tier는 누적 결제액과 계정 연령으로 자동 상승합니다. “API 많이 쓰면 알아서 풀린다”고 생각하면 됩니다.
Anthropic도 구조는 비슷하고, Usage 대시보드에서 현재 tier와 한도를 확인할 수 있습니다.
429가 났을 때 표준 대응은 지수 백오프(exponential backoff) + jitter입니다.
for attempt in range(5):
try:
return client.messages.create(...)
except RateLimitError:
wait = (2 ** attempt) + random.random()
time.sleep(wait) # 1s, 2s, 4s, 8s, 16s (+랜덤 jitter)
SDK 대부분이 이걸 내장해서 자동 재시도를 해줍니다. 그래서 SDK를 쓰면 어지간한 429는 소리 없이 복구되는 이유가 이거예요. Raw HTTP로 내려가면 이 로직을 직접 짜야 합니다.
7. SDK vs Raw HTTP — 언제 뭘 쓰나
결론부터 가죠.
- 프로덕션 = SDK
- 디버깅 = Raw HTTP
SDK가 안 감추고 있는 척하지만 사실 많이 감추고 있어요. SSE 파싱, 재시도, 타임아웃, 토큰 카운트 집계, 타입 체크(TypeScript/Pydantic) 전부 SDK가 처리합니다. 프로덕션에서 이걸 직접 구현할 이유는 거의 없습니다.
다만 뭔가 이상한 에러가 떨어지거나, SDK 버전과 API 버전이 어긋나서 새 기능이 안 먹을 때는 raw HTTP로 내려가는 게 제일 빠릅니다. curl에 -v(verbose) 붙여서 실제 헤더·바디·스테이터스를 직접 확인하고 나면, 보통 범인이 드러납니다.
SDK를 쓰든 curl을 쓰든, 아까 처음에 본 그 구조 — JSON 바디를 얹은 HTTPS POST 한 발 — 위에서 도는 건 똑같습니다. 이걸 한 번 체화해 두면, 새 API(예: Gemini, Mistral)를 처음 봐도 1~2시간이면 감을 잡을 수 있습니다.
8. 한 줄로 묶으면
- LLM API 호출 = JSON 바디 얹은 HTTPS POST 한 발 + 응답 JSON 파싱.
- Anthropic과 OpenAI는
system위치와max_tokens필수 여부에서 갈린다. - Streaming은 SSE 프로토콜. 이벤트 시퀀스로 쪼개져 들어온다.
- Tool use는 4단계 라운드트립이고, 모델은 함수를 실행하지 않는다. 실행은 당신 코드.
- 토큰 경제학은 Output이 Input의 4~5배, Batch -50%·캐싱 -90% 할인 있음, Opus 4.7은 같은 텍스트 +35% 토큰 함정.
- Rate limit은 RPM·TPM·RPD·TPD·IPM 5축. 429 나면 지수 백오프.
- SDK는 프로덕션용, Raw HTTP는 디버깅용. 밑바닥은 똑같다.
이 7가지만 머리에 있으면, agent 프레임워크 코드를 읽을 때 뭐가 모델 얘기고 뭐가 포장 얘기인지가 선명해집니다.
FAQ
Q1. SDK 안 쓰고 curl로 직접 쏴도 되나요?
됩니다. 프로덕션에서도 기술적으론 가능해요. 다만 SSE 파싱, 지수 백오프, 타임아웃 관리, 스트리밍 중 커넥션 끊김 처리 같은 걸 전부 직접 짜야 해서 품이 큽니다. 학습·디버깅용으론 curl -v --no-buffer가 최고이고, 서비스 배포는 공식 SDK를 권장합니다. 어차피 SDK 내부도 같은 HTTPS POST예요.
Q2. 모델이 함수를 실행하지 않는다면 “Claude Code가 파일 편집했다” 같은 건 거짓말인가요?
아닙니다. 정확히는 Claude Code(harness)가 tool_use 블록을 받아서 실제로 파일 시스템 API를 호출한 거예요. 모델은 “이 파일에 이 내용 써라”라는 JSON만 뱉었고, 실행은 harness가 합니다. 사용자 입장에선 한 몸처럼 보이지만 기술적으론 확실하게 분리돼 있어요. 이게 agent 보안 설계의 출발점이기도 합니다 — harness가 권한을 거르면 모델이 아무리 위험한 tool_use를 뱉어도 실제로는 막히거든요.
Q3. Opus 4.7을 쓰려다가 비용이 예상보다 훨씬 나왔어요. 왜죠?
가능성이 세 개입니다. (1) 출력 토큰이 input의 5배 단가라는 걸 놓쳤거나, (2) Opus 4.7의 새 토크나이저로 같은 텍스트가 ~35% 더 많은 토큰으로 계산돼서 실질 단가가 올랐거나, (3) 프롬프트 캐싱을 안 걸어서 같은 system prompt를 매번 풀 비용으로 재전송하고 있거나. 셋 다 확인해 보시면 대개 원인이 잡힙니다.
다음 편 안내
P3에서는 이 API 호출이라는 개념이 클라우드(Anthropic·OpenAI) 와 오픈소스(Llama·Mistral 로컬 실행) 에서 각각 어떻게 갈라지는지를 다룹니다. 같은 HTTP POST처럼 생겼지만, 뒤에서 벌어지는 일이 완전히 다르거든요.
소스 리스트
- Anthropic — Messages API 레퍼런스: https://docs.anthropic.com/en/api/messages
- Anthropic — Pricing: https://platform.claude.com/docs/en/about-claude/pricing
- Anthropic — Tool Use Overview: https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview
- Anthropic — Implementing Tool Use: https://platform.claude.com/docs/en/agents-and-tools/tool-use/implement-tool-use
- Anthropic Engineering — Advanced Tool Use: https://www.anthropic.com/engineering/advanced-tool-use
- OpenAI — API Reference Overview: https://developers.openai.com/api/reference/overview
- OpenAI — Streaming Responses Guide: https://developers.openai.com/api/docs/guides/streaming-responses
- OpenAI — Chat Completions Streaming Events: https://developers.openai.com/api/reference/resources/chat/subresources/completions/streaming-events
- OpenAI — Pricing: https://developers.openai.com/api/docs/pricing
- OpenAI — Rate Limits Guide: https://developers.openai.com/api/docs/guides/rate-limits
- ◀ 앞 편: P1. Claude family의 기술적 차이와 4.7 업그레이드
- 지금 편: P2. API로 LLM을 부른다는 게 기술적으로 뭔가
- ▶ 다음 편: P3. 클라우드 vs 오픈소스 LLM (준비 중)
회원가입(무료)으로 매주 월요일 아침 AI 공부 지도 뉴스레터를 받아보세요 → 회원가입하기




