LLM 앱 개발 실전 #1 첫 API 호출과 개발 환경 셋업
이 시리즈는 LLM을 처음 코드로 다뤄 보는 분을 위한 입문 시리즈입니다. REST API와 데이터베이스를 다뤄 본 경험이 있다면 더 쉽게 따라올 수 있습니다. 끝까지 따라가면 단순 호출에서 시작해 RAG와 에이전트까지, 실제로 동작하는 LLM 앱을 직접 만들 수 있게 됩니다.
총 13편으로 구성됩니다.
- #1 첫 API 호출과 개발 환경 셋업 ← 이번 글
- #2 메시지와 파라미터 이해하기
- #3 스트리밍으로 응답 실시간 출력
- #4 프롬프트 엔지니어링 실무
- #5 구조화된 출력 받기
- #6 툴 콜링으로 외부 기능 연결
- #7 임베딩과 벡터 검색
- #8 RAG 파이프라인 구축
- #9 대화 메모리와 컨텍스트 관리
- #10 AI 에이전트 만들기
- #11 MCP로 도구 연결하기
- #12 비용, 평가, 관측
- #13 실전 프로젝트
코드 예제는 Python으로 작성하고, 모델은 Anthropic의 Claude를 사용합니다. 이 글에서 다루는 개념은 대부분 다른 LLM 제공자에도 그대로 적용할 수 있습니다.
LLM 앱은 일반 백엔드와 무엇이 다른가 #
코드에서 LLM을 호출하는 일은 외부 API를 호출하는 일과 구조가 비슷합니다. HTTP 요청을 보내고 응답을 받습니다. 그런데 그 안을 들여다보면 익숙한 백엔드 개발과 다른 성질이 몇 가지 있습니다. 이 차이를 먼저 알아 두어야 이후 편들이 왜 그런 방식을 쓰는지 이해할 수 있습니다.
첫째, 출력이 매번 다릅니다. 같은 입력을 보내도 응답이 똑같이 나오지 않습니다. 일반 함수는 같은 인자에 같은 결과를 돌려주지만, LLM은 그렇지 않습니다. 그래서 “정확히 이 문자열이 온다"를 전제로 코드를 짜면 깨집니다. 응답의 형태를 강제하고 검증하는 일이 중요해지는데, 이건 #5에서 다루겠습니다.
둘째, 입력과 출력이 자연어입니다. 무엇을 시킬지 함수 시그니처가 아니라 문장으로 적습니다. 이 문장을 어떻게 쓰느냐가 결과 품질을 크게 바꿉니다. 그래서 프롬프트 작성이 별도의 기술이 되고, #4에서 따로 다루겠습니다.
셋째, 비용이 토큰 단위입니다. 요청당 고정 요금이 아니라, 주고받은 글자량(정확히는 토큰)에 비례해 과금됩니다. 입력이 길수록, 출력이 길수록 비용이 올라갑니다. 그래서 컨텍스트를 얼마나 보낼지, 캐싱을 어떻게 할지가 실제 운영에서 중요한 주제가 됩니다. #12에서 다루겠습니다.
넷째, 응답이 느리고 길 수 있습니다. LLM은 답을 한 번에 내놓지 않고 토큰을 하나씩 생성합니다. 긴 답변은 수 초가 걸리기도 합니다. 그래서 생성되는 대로 화면에 흘려보내는 스트리밍이 기본 기법이 되고, #3에서 다루겠습니다.
지금은 이 네 가지를 “그런 성질이 있구나” 정도로만 기억해 두면 충분합니다. 각 편에서 하나씩 실제로 다루게 됩니다.
준비물 #
첫 호출을 하려면 세 가지가 필요합니다.
- Anthropic 계정과 API 키
- Python 3.8 이상 환경
- 약간의 사용 크레딧 (첫 호출 비용은 1원 단위입니다)
API 키는 Anthropic Console에 로그인한 뒤 API Keys 메뉴에서 발급합니다. 발급된 키는 한 번만 전체가 표시되므로 그 자리에서 복사해 둡니다.
SDK 설치와 키 설정 #
Anthropic은 Python용 공식 SDK를 제공합니다. 가상 환경을 만든 뒤 설치합니다.
python -m venv venv
source venv/bin/activate
pip install anthropic발급받은 키는 환경 변수 ANTHROPIC_API_KEY에 넣습니다. SDK는 이 이름의 환경 변수를 자동으로 읽습니다.
export ANTHROPIC_API_KEY="sk-ant-여기에-발급받은-키"이렇게 해두면 코드 어디에도 키 문자열이 등장하지 않습니다. 키를 코드에 박아 넣지 않는 습관은 처음부터 들이는 편이 좋습니다.
첫 호출 #
이제 Claude에게 한 문장을 보내고 응답을 받아 보겠습니다.
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[
{"role": "user", "content": "한 문장으로 자기소개를 해줘."}
],
)
for block in response.content:
if block.type == "text":
print(block.text)실행하면 Claude가 생성한 자기소개 한 문장이 출력됩니다. 짧지만 이 코드 안에 LLM 호출의 기본 골격이 모두 들어 있습니다. 하나씩 뜯어보겠습니다.
anthropic.Anthropic()— 클라이언트를 만듭니다. 인자를 비워 두면 환경 변수ANTHROPIC_API_KEY에서 키를 읽습니다.model— 어떤 모델로 답할지 지정합니다. 모델 선택은 바로 아래에서 다루겠습니다.max_tokens— 응답이 최대 몇 토큰까지 생성될지 상한을 정합니다. 이 값을 넘으면 응답이 중간에 잘립니다.messages— 대화 내용입니다. 지금은 사용자(user) 한 명이 한 번 말한 형태입니다. 역할 구조는 #2에서 자세히 다루겠습니다.response.content— 응답은 문자열 하나가 아니라 블록의 목록입니다. 각 블록의type을 확인하고text블록의 내용을 꺼냅니다.
마지막 부분이 처음에는 번거롭게 느껴질 수 있습니다. “그냥 텍스트만 주면 되지 왜 목록인가” 싶지만, 뒤에서 툴 콜링이나 추론 관련 블록이 함께 오기 시작하면 이 구조가 필요해집니다. 그래서 처음부터 type을 확인하고 꺼내는 습관을 들여 두는 편이 좋습니다.
세 가지 모델 중에서 고르기 #
model에 넣을 수 있는 Claude 모델은 성능과 비용에 따라 크게 세 등급으로 나뉩니다.
| 등급 | 모델 ID | 성격 | 잘 맞는 일 |
|---|---|---|---|
| 가장 강력 | claude-opus-4-8 | 가장 똑똑하고 비쌈 | 복잡한 추론, 긴 작업, 에이전트 |
| 균형 | claude-sonnet-4-6 | 속도와 지능의 균형 | 대부분의 실무 작업 |
| 가장 빠름 | claude-haiku-4-5 | 빠르고 저렴 | 분류, 요약 같은 단순 작업 |
학습 단계에서는 균형 등급인 claude-sonnet-4-6이 무난합니다. 비용 부담이 적으면서도 대부분의 예제를 충분히 소화합니다. 단순 분류나 짧은 변환처럼 가벼운 작업이라면 더 저렴한 Haiku로 충분하고, 까다로운 추론이 필요하면 Opus를 씁니다. 이 시리즈에서는 편마다 작업 성격에 맞는 모델을 골라 쓰겠습니다.
흔히 걸려 넘어지는 곳 #
첫 호출 단계에서 자주 마주치는 문제 몇 가지를 미리 정리해 두겠습니다.
AuthenticationError가 난다 — 환경 변수가 설정되지 않았거나 키가 잘못된 경우입니다.echo $ANTHROPIC_API_KEY로 값이 제대로 들어 있는지 확인합니다. 새 터미널을 열었다면export를 다시 해야 할 수 있습니다.- 응답이 중간에 끊긴다 —
max_tokens가 너무 작은 경우입니다. 응답의stop_reason이max_tokens로 나오면 상한에 걸린 것이므로 값을 늘립니다. block.text에서 오류가 난다 — 모든 블록이 텍스트는 아닙니다. 위 예제처럼block.type == "text"를 먼저 확인하고 꺼내야 안전합니다.
이 세 가지만 알아 두어도 처음 30분 동안의 막힘은 대부분 피할 수 있습니다.
마무리 #
이번 글에서는 LLM 앱이 일반 백엔드와 어떻게 다른지 짚고, API 키 발급부터 첫 응답까지를 직접 해봤습니다. 정리하면 다음과 같습니다.
- LLM 앱은 출력이 매번 다르고, 입출력이 자연어이며, 토큰 단위로 과금되고, 응답이 느릴 수 있습니다.
- API 키는 환경 변수로 다루고 코드에 박아 넣지 않습니다.
- 응답은 블록의 목록이므로
type을 확인하고 꺼냅니다. - 모델은 작업 성격에 맞춰 Opus, Sonnet, Haiku 중에 고릅니다.
다음 글인 “LLM 앱 개발 실전 #2 메시지와 파라미터 이해하기"에서는 방금 가볍게 넘어간 messages의 역할 구조와 temperature 같은 파라미터를 제대로 들여다봅니다. 이 역할 구조를 알아야 Claude에게 맥락과 지시를 정확히 전달할 수 있습니다.