
Claude API 시작하기: Messages, Streaming, 그리고 Tool Use
요약
Claude API를 활용하여 프로덕션 수준의 애플리케이션을 구축하기 위한 핵심 패턴을 다룹니다. 모델 선택 전략, 스트리밍 구현, 도구 사용(Tool Use)의 왕복 과정, 그리고 비용 절감을 위한 프롬프트 캐싱 방법을 설명합니다.
핵심 포인트
- 사용 사례에 따라 Opus, Sonnet, Haiku 모델을 적절히 선택해야 합니다.
- UX 개선과 타임아웃 방지를 위해 스트리밍(Streaming) 방식을 권장합니다.
- 도구 사용은 Claude의 호출 신호와 코드 실행 결과 반환의 2단계 과정입니다.
- 프롬프트 캐싱을 통해 입력 토큰 비용을 최대 90%까지 절감할 수 있습니다.
- 클라이언트는 커넥션 풀 유지를 위해 매번 생성하지 말고 한 번만 초기화해야 합니다.
핵심 포인트
anthropic을 설치하고ANTHROPIC_API_KEY를 설정하면, 단 5줄의 Python 코드로 작동하는 LLM API를 가질 수 있습니다.- 세 가지 모델이 사용 사례의 95%를 커버합니다: 복잡한 추론을 위한 Opus, 대부분의 프로덕션 작업을 위한 Sonnet, 저렴하고 대량의 분류 작업을 위한 Haiku.
- 긴 형태의 응답은 항상 스트리밍 (Streaming) 하세요 — 타임아웃과 나쁜 UX는 전체 응답이 완료될 때까지 차단 (Blocking)되는 과정에서 발생합니다.
- 도구 사용 (Tool use)은 두 번의 턴(two-turn) 왕복 과정이 필요합니다: Claude가 함수 호출을 신호하면, 귀하의 코드가 이를 실행하고, 그 결과를 다시 반환해야 합니다.
- 프롬프트 캐싱 (Prompt caching)은 매 요청마다 동일한 시스템 프롬프트를 보내는 애플리케이션의 경우 입력 토큰 비용을 최대 90%까지 절감합니다.
서론
대부분의 LLM API 튜토리얼은 "메시지를 보내고 응답을 출력한다" 수준에서 멈춥니다. 그것은 데모를 만드는 데는 충분하지만, 프로덕션 애플리케이션을 만드는 데는 부족합니다. 저는 Claude API를 고객 대상 애플리케이션과 내부 자동화 파이프라인에 통합해 왔으며, 팀이 프로토타입에서 프로덕션으로 넘어갈 때마다 항상 동일한 세 가지 격차가 나타나는 것을 보았습니다: 스트리밍을 하지 않고, 도구 사용을 왕복 과정으로 처리하지 않으며, 첫 번째 결제 주기가 지나고 나서야 비용을 생각한다는 점입니다. Claude API에는 스트리밍, 도구 사용, 비용 제어를 위한 특정 패턴이 있으며, 이는 기초적인 예제만으로는 명확히 알기 어렵습니다. 이를 잘못 구현하면 타임아웃, 비용 두 배 증가, 또는 Claude가 함수를 호출하려 할 때 코드가 이를 무시하여 발생하는 무음 논리 오류 (silent logic errors)가 발생할 수 있습니다.
이 기사는 전체적인 그림을 다룹니다: Messages API가 어떻게 작동하는지, 적절한 모델을 선택하는 방법, 올바르게 스트리밍하는 방법, 도구 사용을 완전한 왕복 과정으로 구현하는 방법, 그리고 규모가 커져도 비용을 예측 가능하게 유지하는 방법을 다룹니다. 모든 코드는 Python 3.11+ 기준입니다. ai-integration/claude-api-quickstart/src/main.py에 있는 동반 코드는 이 네 가지 패턴을 모두 보여주는 실행 가능한 ClaudeClient 클래스입니다.
설정
SDK와 의존성을 설치하세요:
pip install anthropic python-dotenv
API 키를 환경 변수로 저장하세요 — 절대 코드에 직접 입력(hardcode)하지 마세요:
# .env
ANTHROPIC_API_KEY=sk-ant-...
Anthropic SDK는 ANTHROPIC_API_KEY를 자동으로 읽어옵니다. 모듈 수준에서 클라이언트를 한 번만 초기화하세요:
import os
from anthropic import Anthropic
from dotenv import load_dotenv
...
클라이언트를 한 번만 생성하세요. 흔히 하는 실수는 요청 핸들러(request handler) 내부에서 Anthropic()을 인스턴스화하는 것입니다. 클라이언트는 HTTP 커넥션 풀 (connection pool)을 유지합니다. 매 호출마다 클라이언트를 다시 생성하면 연결 설정에 약 50ms가 낭비되며 불필요한 오버헤드가 추가됩니다.
Messages API 기초
Claude와의 모든 상호작용은 client.messages.create()를 통해 이루어집니다. API는 세 가지 메시지 역할 (role)을 사용합니다:
system— 전체 대화의 컨텍스트 (context)와 규칙을 설정합니다. 대화 기록에 포함되는 메시지는 아닙니다.user— 인간(또는 귀하의 애플리케이션)으로부터의 입력입니다.assistant— Claude의 이전 응답입니다. 멀티턴 (multi-turn) 대화를 이어가려면 이 응답들을 포함해야 합니다.
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
...
주요 파라미터 (parameter):
| 파라미터 | 타입 | 비고 |
|---|---|---|
model | string | 필수. 아래의 모델 선택 섹션을 참조하세요. |
| ... |
응답의 stop_reason은 Claude가 멈춘 이유를 알려줍니다:
end_turn— Claude가 자연스럽게 대화를 마쳤습니다.max_tokens—max_tokens제한에 도달했습니다. 응답이 잘렸을 수 있습니다.tool_use— Claude가 함수 호출을 원합니다. 이를 명시적으로 처리해야 합니다.stop_sequence— 설정한 중단 시퀀스 (stop sequence) 중 하나에 도달했습니다.
항상 stop_reason을 확인하세요. 만약 tool_use를 받았는데 이를 무시한다면, Claude는 귀하의 함수 결과값을 기다리고 있는 상태입니다. 즉, 질문에 대한 답변을 완료한 것이 아닙니다.
모델 선택
세 가지 프로덕션 모델이 거의 모든 사용 사례를 커버합니다. 작업의 복잡도와 비용 허용 범위에 따라 선택하세요:
| 모델 (Model) | ID | 최적 용도 (Best For) | 부적합한 용도 (Not For) |
|---|---|---|---|
| Claude Opus 4 | claude-opus-4-8 | 복잡한 다단계 추론 (Complex multi-step reasoning), 모호한 사양으로부터의 코드 생성 (code generation from ambiguous specs), 연구 종합 (research synthesis) | 대량 작업 (비용 문제), 단순 분류 (simple classification) |
| ... |
새로운 기능에는 기본적으로 claude-sonnet-4-6을 사용하세요. 저는 모든 통합 작업을 Sonnet으로 시작하며, 실제 프로덕션 입력 샘플을 두 모델 모두에 실행하여 출력을 비교한 후에만 Haiku로 전환합니다. 벤치마크가 아니라, 귀하의 특정 작업에 대한 실제 출력 (actual outputs)을 비교해야 합니다. 해당 특정 작업에 대해 출력 품질이 수용 가능한 수준임을 확인한 후에만 Haiku로 마이그레이션하세요.
스트리밍 응답 (Streaming Responses)
한 단락보다 긴 응답의 경우, 스트리밍 (stream)을 사용하세요. 2,000 토큰 응답에 대해 스트리밍을 사용하지 않는 호출은 사용자에게 아무것도 반환하기 전까지 10~30초를 기다려야 합니다. 스트리밍은 약 200ms 내에 토큰을 반환하기 시작합니다.
def stream_response(system: str, prompt: str) -> None:
with client.messages.stream(
model="claude-sonnet-4-6",
...
with client.messages.stream() 컨텍스트 매니저 (context manager)가 연결 정리 (connection cleanup)를 처리합니다. 블록 내부에서 stream.text_stream은 Claude가 생성하는 각 텍스트 델타 (text delta)를 생성 (yield)합니다.
스트리밍을 사용해야 하는 경우:
- 약 100 토큰보다 긴 모든 사용자 대상 응답.
- 문서 생성 (Document generation), 코드 생성 (code generation), 긴 설명.
스트리밍을 사용하지 말아야 하는 경우:
- 무언가를 수행하기 전에 완전한 레이블 (complete label)이 필요한 분류 작업 (Classification tasks).
- 도구 사용 (Tool use) — 부분적인 도구 입력 JSON (partial tool input JSON)을 처리할 수 있는 경우에만 전체 라운드 트립 (round trip)을 스트리밍하세요.
- 개별 응답 지연 시간 (latency)이 중요하지 않은 배치 처리 (Batch processing) 작업.
실제로 저는 사용자에게 직접 보여지는 모든 기능에는 스트리밍 (streaming)을 기본값으로 사용하고, 백그라운드 작업 (background job)에서 실행되는 모든 기능에는 비스트리밍 (non-streaming)을 기본값으로 사용합니다. 이 구분이 중요한 이유는 명확한 규칙 없이 동일한 코드베이스 내에서 두 방식을 혼용할 경우, 누군가가 지연 시간 (latency)에 민감한 상황에서 비스트리밍 헬퍼 (non-streaming helper)를 재사용할 때 미묘한 버그가 발생할 수 있기 때문입니다.
도구 사용 (Tool Use / Function Calling)
도구 사용 (Tool use)은 두 단계의 교환 과정으로 이루어집니다. 첫 번째 단계에서 Claude는 어떤 함수를 어떤 인자 (arguments)와 함께 호출할지 신호를 보냅니다. 그러면 귀하의 애플리케이션이 해당 함수를 실행합니다. 두 번째 단계에서는 그 결과를 다시 보내며, Claude는 이를 최종 답변에 포함시킵니다.
도구 정의하기
CALCULATOR_TOOLS = [
{
"name": "calculator",
...
왕복 과정 (Round trip) 처리하기
import json
def use_tool(prompt: str) -> str:
...
핵심적인 세부 사항: messages.append({"role": "assistant", "content": response.content}) — 도구 결과 (tool result)를 보내기 전에 Claude의 전체 응답 객체 (도구 사용 블록 포함)를 히스토리 (history)에 반드시 포함해야 합니다. 텍스트 내용만 보내면 대화 흐름이 끊어집니다. 저는 개발자가 어시스턴트 (assistant) 응답에서 텍스트만 추출하고 도구 사용 블록을 버린 채 추가했다가, 운영 환경에서 400 BadRequestError가 발생하는 것을 본 적이 있습니다. API는 결과적으로 생성된 메시지 히스토리가 구조적으로 유효하지 않다고 판단하여 거부하며, 에러 메시지는 정확히 어떤 필드가 문제인지 가리키지 않습니다.
에러 처리 및 재시도 (Error Handling and Retries)
유의미한 규모의 서비스에서는 속도 제한 에러 (Rate limit errors)와 일시적인 서버 에러 (transient server errors)가 불가피하게 발생합니다. 저는 철저히 테스트된 통합 시스템이 출시 일주일 만에 RateLimitError를 받기 시작하는 것을 본 적이 있는데, 단순히 새로운 기능이 요청량을 3배로 늘렸기 때문이었습니다. 해당 앱에는 재시도 로직 (retry logic)이 없었고, 모든 에러가 사용자에게 500 에러로 직접 노출되었습니다. 첫날부터 지수 백오프 (exponential backoff)를 구현하세요:
import time
import anthropic
...
일반적인 예외 (Common exceptions):
| 예외 (Exception) | 원인 (Cause) | 조치 (Action) |
|---|---|---|
anthropic.RateLimitError | 요청 과다 (Too many requests) | 지수 백오프 (Exponential backoff) |
| ... |
컨텍스트 오버플로 (context overflow)로 인한 BadRequestError를 방지하려면, 긴 대화를 보내기 전에 토큰 수 (token count)를 확인하세요:
token_count = client.messages.count_tokens(
model="claude-sonnet-4-6",
messages=messages,
...
비용 관리 및 프롬프트 캐싱 (Cost Management and Prompt Caching)
Claude는 토큰 (token) 단위로 비용을 청구합니다: 입력 토큰 (input tokens, 사용자가 보내는 것)과 출력 토큰 (output tokens, Claude가 생성하는 것). 출력 토큰은 입력 토큰보다 토큰당 비용이 3~5배 더 높습니다. 사용량 내역은 모든 응답에 포함됩니다:
print(response.usage.input_tokens) # 사용자가 보낸 토큰
print(response.usage.output_tokens) # Claude가 생성한 토큰
프롬프트 캐싱 (Prompt caching)
애플리케이션이 매 요청마다 동일한 시스템 프롬프트 (system prompt)를 보낸다면 (흔한 패턴입니다), 프롬프트 캐싱 (prompt caching)을 통해 입력 비용을 60~90%까지 절감할 수 있습니다. 캐싱 가능한 접두사 (prefix)에 cache_control을 표시하세요:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
...
첫 번째 호출 시, Anthropic은 표시된 접두사를 캐싱합니다. 캐시 TTL (Time To Live) 내의 후속 호출은 캐시된 토큰에 대해 일반 입력 가격의 약 10%만 지불합니다. 응답에는 캐시 통계가 포함됩니다:
print(response.usage.cache_creation_input_tokens) # 캐시에 기록된 토큰
print(response.usage.cache_read_input_tokens) # 캐시에서 읽은 토큰 (저렴함)
캐싱이 효과를 발휘하는 경우: 하루에 몇 번 이상의 호출이 발생하며 시스템 프롬프트 (system prompts)가 약 1,000 토큰을 초과할 때입니다. 저는 500 토큰보다 긴 고정된 시스템 프롬프트를 사용하는 모든 앱에서 기본적으로 캐싱을 활성화합니다. 구현 비용은 단 두 줄에 불과하며, 일일 활성 사용자(DAU)가 수백 명을 넘어서면 절감 효과가 빠르게 복리로 쌓입니다. 더 짧은 프롬프트나 사용량이 적은 애플리케이션의 경우, 오버헤드는 무시할 수 있는 수준입니다.
흔한 실수
-
긴 형식의 응답을 스트리밍 (streaming)하지 않는 것. 2,000 토큰 분량의 응답을 위해 블로킹 호출 (blocking call)을 사용하면 일부 HTTP 클라이언트에서 타임아웃이 발생하며, 항상 좋지 않은 사용자 경험을 제공합니다. 약 1초 이상 걸리는 모든 응답에는
client.messages.stream()을 사용하세요. -
stop_reason을 무시하는 것. 만약stop_reason이tool_use인데response.content[0].text를 반환한다면, 당신은 부분적이거나 빈 문자열을 반환하고 있는 것입니다. Claude는 도구 (tool)를 실행한 후 실제 답변을 주기 위해 기다리고 있었던 것입니다. 저는 이것이 버그 상태로 프로덕션에 배포되어, 특정 질문(우연히 도구 사용을 트리거하는 질문들)에 대해 API가 "무작위로" 빈 응답을 반환하는 현상을 본 적이 있습니다. 텍스트를 추출하기 전에 항상stop_reason을 확인하세요. -
max_tokens를 너무 낮게 설정하는 것. 만약max_tokens가 전체 응답보다 작으면, Claude는 문장 중간에 멈추고stop_reason은max_tokens를 반환합니다. 응답은 유효한 JSON이지만 내용은 잘려 있습니다.max_tokens를 넉넉하게 설정하세요. 사용하지 않은 여유 공간에 대해서는 비용이 청구되지 않습니다. -
매 요청마다
Anthropic()클라이언트를 새로 생성하는 것. 클라이언트는 내부적으로 HTTP 커넥션 풀 (connection pool)을 유지합니다. 요청마다 인스턴스를 생성하면 커넥션 설정 시간을 낭비하고 풀을 우회하게 됩니다. 모듈 수준에서 하나의 인스턴스를 생성하고 이를 재사용하세요. -
제한 없는 대화 기록을 보내는 것. 이전의 모든 턴 (turn)은 입력 토큰 비용에 포함됩니다. 긴 대화의 경우, 윈도잉 전략 (windowing strategy)을 구현하세요: 시스템 프롬프트와 마지막 N개의 턴은 유지하고 중간 부분은 잘라냅니다. 매 전송 전에
client.messages.count_tokens()를 사용하여 토큰 초과를 조기에 감지하세요.
전체 예제
위에서 설명한 모든 패턴은 ClaudeClient 클래스로 ai-integration/claude-api-quickstart/src/main.py에 있는 보조 코드에 구현되어 있습니다:
from dotenv import load_dotenv
from src.main import ClaudeClient
...
설정 방법:
cd ai-integration/claude-api-quickstart
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
...
전체 소스 코드: GitHub 링크
상태(State): 대화 턴 모델
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기
