들어가며
Claude를 사용하다 보면 “컨텍스트 윈도우(Context Window)“라는 용어를 자주 접하게 된다. 이는 Claude가 한 번에 처리할 수 있는 텍스트의 양을 의미한다.
“Claude가 책 한 권을 통째로 읽을 수 있다”는 이야기를 들어봤을 것이다. 실제로 Claude는 200K 토큰(약 300페이지 분량)을 한 번에 처리할 수 있으며, 베타 기능을 사용하면 1M 토큰까지 확장할 수 있다.
이번 차시에서는 컨텍스트 윈도우의 개념과 작동 방식을 이해하고, 긴 문서를 효율적으로 처리하는 실전 전략을 알아본다.
컨텍스트 윈도우란 무엇인가
정의
컨텍스트 윈도우는 Claude가 한 번에 “기억”할 수 있는 텍스트의 최대 길이다. 여기에는 다음이 모두 포함된다:
전체 컨텍스트 = 시스템 프롬프트
+ 이전 대화 기록
+ 현재 사용자 메시지
+ Claude 응답 (예상)
모델별 컨텍스트 윈도우
| 모델 | 표준 컨텍스트 | 베타 확장 |
|---|---|---|
| Claude Opus 4.6 | 200K 토큰 | 1M 토큰 |
| Claude Sonnet 4.5 | 200K 토큰 | 1M 토큰 |
| Claude Haiku 4.5 | 200K 토큰 | - |
200K 토큰의 실제 크기
200K 토큰이 실제로 어느 정도 분량인지 감을 잡아보자:
- 영어 기준: 약 150,000 단어
- 책으로 환산: 약 300페이지 분량
- 코드 기준: 약 50,000줄의 Python 코드
- 한국어 기준: 영어 대비 약 1.5-2배 토큰 사용 (약 75,000-100,000 단어)
예를 들어, 소설 『해리 포터와 마법사의 돌』(영문판)이 약 77,000 단어이므로, Claude는 이 책을 약 2권 정도 한 번에 읽을 수 있다.
컨텍스트 구성 요소
입력과 출력
컨텍스트 윈도우는 입력과 출력을 모두 포함한다는 점이 중요하다.
import anthropic
client = anthropic.Anthropic()
# 토큰 수 미리 계산
count = client.messages.count_tokens(
model="claude-sonnet-4-5-20250929",
system="당신은 친절한 어시스턴트다.",
messages=[
{"role": "user", "content": "긴 문서..."},
]
)
print(f"예상 입력 토큰: {count.input_tokens}")
컨텍스트 한계 상황
200K 토큰 한계를 초과하면 어떻게 될까?
from anthropic import BadRequestError
try:
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=4096,
messages=[{
"role": "user",
"content": very_long_text # 200K 토큰 초과
}]
)
except BadRequestError as e:
if "context_length_exceeded" in str(e):
print("컨텍스트 한계 초과!")
# 대응 전략 실행
오류 메시지 예시:
BadRequestError: 400 - {"type":"error","error":{"type":"invalid_request_error","message":"prompt is too long: 250000 tokens > 200000 maximum"}}
200K vs 1M 토큰
1M 토큰 베타 활성화
1M 토큰 컨텍스트는 베타 기능으로 제공된다.
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=4096,
betas=["long-context-1m"], # 1M 토큰 활성화
messages=[{
"role": "user",
"content": very_long_document # 최대 1M 토큰
}]
)
비용 차이
1M 토큰 베타를 사용하면 200K를 초과하는 부분에 대해 입력 토큰 가격이 2배가 된다.
예시: Sonnet 4.5로 300K 토큰 입력
# 표준 컨텍스트 (200K 이하)
처음 200K: 200,000 × $3 / 1,000,000 = $0.60
# 1M 베타 사용 (200K 초과)
처음 200K: 200,000 × $3 / 1,000,000 = $0.60
추가 100K: 100,000 × ($3 × 2) / 1,000,000 = $0.60
총 비용: $1.20
주의사항
1M 토큰 사용 시 고려사항:
- 응답 시간 증가: 매우 긴 컨텍스트는 처리 시간이 길어진다
- 비용 2배: 200K 초과 부분은 입력 비용이 2배
- 베타 기능: 향후 변경될 수 있음
- 실제 필요성 검토: 대부분의 경우 200K로 충분함
긴 문서 처리 전략
200K 토큰을 초과하는 문서를 처리해야 한다면 어떻게 해야 할까? 다음 세 가지 전략을 활용할 수 있다.
1. 청킹 (Chunking)
문서를 작은 조각으로 나누어 순차적으로 처리하는 방법이다.
def chunk_text(text, max_tokens=50000):
"""텍스트를 청크로 분할"""
words = text.split()
chunks = []
current_chunk = []
for word in words:
current_chunk.append(word)
# 대략적인 토큰 추정 (단어당 1.3토큰)
if len(current_chunk) * 1.3 > max_tokens:
chunks.append(" ".join(current_chunk))
current_chunk = []
if current_chunk:
chunks.append(" ".join(current_chunk))
return chunks
# 사용 예시
with open("very_long_book.txt", "r") as f:
book = f.read()
chunks = chunk_text(book, max_tokens=50000)
print(f"총 {len(chunks)}개 청크로 분할")
# 각 청크를 순차적으로 처리
summaries = []
for i, chunk in enumerate(chunks):
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1000,
messages=[{
"role": "user",
"content": f"다음 텍스트의 핵심 내용을 요약해줘:\n\n{chunk}"
}]
)
summaries.append(response.content[0].text)
print(f"청크 {i+1}/{len(chunks)} 처리 완료")
# 최종 요약 통합
final_summary = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=2000,
messages=[{
"role": "user",
"content": f"다음은 긴 문서의 각 부분 요약이다. 이를 통합하여 전체 요약을 작성해줘:\n\n" + "\n\n".join(summaries)
}]
)
print("\n최종 요약:")
print(final_summary.content[0].text)
장점:
- 컨텍스트 한계를 우회할 수 있음
- 각 부분을 독립적으로 처리 가능
단점:
- 여러 번 API 호출로 비용 증가
- 문서 전체의 맥락을 놓칠 수 있음
2. 요약 후 처리
긴 문서를 먼저 요약하고, 요약본으로 작업하는 방법이다.
# 1단계: 문서 요약
with open("long_document.txt", "r") as f:
document = f.read()
summary_response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=2000,
messages=[{
"role": "user",
"content": f"다음 문서를 핵심 내용 위주로 상세히 요약해줘:\n\n{document}"
}]
)
summary = summary_response.content[0].text
print("요약 완료!")
# 2단계: 요약본으로 질문
question_response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1000,
messages=[{
"role": "user",
"content": f"""
다음은 긴 문서의 요약이다:
{summary}
질문: 이 문서에서 가장 중요한 3가지 핵심 아이디어는 무엇인가?
"""
}]
)
print(question_response.content[0].text)
장점:
- API 호출 횟수 최소화
- 핵심 정보에 집중
단점:
- 요약 과정에서 세부 정보 손실 가능
- 정확한 인용이나 참조가 어려움
3. RAG (Retrieval-Augmented Generation)
관련 부분만 검색하여 컨텍스트에 포함하는 가장 효율적인 방법이다.
from sentence_transformers import SentenceTransformer
import numpy as np
# 1단계: 문서를 청크로 분할
def create_chunks(text, chunk_size=500):
"""문서를 작은 청크로 분할"""
sentences = text.split('. ')
chunks = []
current_chunk = []
for sentence in sentences:
current_chunk.append(sentence)
if len(' '.join(current_chunk).split()) >= chunk_size:
chunks.append('. '.join(current_chunk) + '.')
current_chunk = []
if current_chunk:
chunks.append('. '.join(current_chunk) + '.')
return chunks
# 2단계: 임베딩 생성
model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
with open("knowledge_base.txt", "r") as f:
document = f.read()
chunks = create_chunks(document)
chunk_embeddings = model.encode(chunks)
# 3단계: 질문에 관련된 청크 검색
def retrieve_relevant_chunks(query, top_k=5):
"""질문과 가장 관련 있는 청크 검색"""
query_embedding = model.encode([query])[0]
# 코사인 유사도 계산
similarities = np.dot(chunk_embeddings, query_embedding) / (
np.linalg.norm(chunk_embeddings, axis=1) * np.linalg.norm(query_embedding)
)
# 가장 유사한 top_k개 선택
top_indices = np.argsort(similarities)[-top_k:][::-1]
return [chunks[i] for i in top_indices]
# 4단계: 관련 컨텍스트만 Claude에 전달
query = "머신러닝에서 과적합을 방지하는 방법은?"
relevant_chunks = retrieve_relevant_chunks(query, top_k=5)
context = "\n\n".join(relevant_chunks)
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1000,
messages=[{
"role": "user",
"content": f"""
다음 컨텍스트를 참고하여 질문에 답해줘:
컨텍스트:
{context}
질문: {query}
"""
}]
)
print(response.content[0].text)
장점:
- 필요한 정보만 선택적으로 사용
- 토큰 사용량 최소화
- 대규모 지식 베이스에 적합
단점:
- 초기 설정이 복잡 (임베딩 모델, 벡터 DB 등)
- 검색 품질이 결과에 영향
RAG 실전 예제: Pinecone 사용
실제 프로덕션 환경에서는 전문 벡터 데이터베이스를 사용한다.
from pinecone import Pinecone, ServerlessSpec
from sentence_transformers import SentenceTransformer
# Pinecone 초기화
pc = Pinecone(api_key="your-api-key")
# 인덱스 생성
index_name = "claude-kb"
if index_name not in pc.list_indexes().names():
pc.create_index(
name=index_name,
dimension=384, # all-MiniLM-L6-v2 차원
metric="cosine",
spec=ServerlessSpec(cloud="aws", region="us-east-1")
)
index = pc.Index(index_name)
# 임베딩 모델
model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
# 문서 업로드
chunks = create_chunks(document)
for i, chunk in enumerate(chunks):
embedding = model.encode(chunk).tolist()
index.upsert(vectors=[(f"chunk-{i}", embedding, {"text": chunk})])
print(f"{len(chunks)}개 청크 업로드 완료")
# 검색 및 질문
def ask_with_rag(question):
# 질문 임베딩
query_embedding = model.encode(question).tolist()
# 유사 청크 검색
results = index.query(vector=query_embedding, top_k=5, include_metadata=True)
# 컨텍스트 구성
context = "\n\n".join([match["metadata"]["text"] for match in results["matches"]])
# Claude에 질문
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1000,
messages=[{
"role": "user",
"content": f"컨텍스트:\n{context}\n\n질문: {question}"
}]
)
return response.content[0].text
# 사용
answer = ask_with_rag("딥러닝에서 배치 정규화의 역할은?")
print(answer)
Compaction (컴팩션)
서버 측에서 컨텍스트를 자동으로 요약하여 관리하는 기능이다.
Compaction 활성화
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=long_conversation,
betas=["context-compaction"] # 자동 컴팩션 활성화
)
Compaction 동작 방식
- 자동 감지: 컨텍스트가 한계에 가까워지면 자동 발동
- 요약 수행: 이전 대화 내용을 요약하여 압축
- 핵심 유지: 중요한 정보는 유지하면서 토큰 절약
- 투명 동작: 사용자에게 투명하게 동작
예시:
messages = [
{"role": "user", "content": "Python으로 웹 스크래퍼를 만들어줘."},
{"role": "assistant", "content": "...긴 응답..."},
{"role": "user", "content": "이제 데이터베이스에 저장하는 기능을 추가해줘."},
{"role": "assistant", "content": "...긴 응답..."},
# ... 계속 대화
]
# 컨텍스트가 너무 길어지면 자동으로 초반 대화를 요약
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=2048,
messages=messages,
betas=["context-compaction"]
)
장점:
- 자동으로 컨텍스트 관리
- 긴 대화에서 유용
- 핵심 정보 유지
단점:
- 베타 기능으로 변경 가능
- 요약 과정에서 일부 정보 손실 가능
컨텍스트 최적화 전략
1. 시스템 프롬프트 최적화
불필요하게 긴 시스템 프롬프트는 매 요청마다 토큰을 낭비한다.
# ❌ 비효율적
system_prompt = """
당신은 매우 친절하고 도움이 되는 AI 어시스턴트입니다.
항상 정확한 정보를 제공하려고 최선을 다합니다.
사용자의 질문에 성실하게 답변하며,
필요하다면 추가 질문을 할 수 있습니다.
답변은 명확하고 이해하기 쉽게 작성하며,
예시를 들어 설명하는 것을 선호합니다.
...
(300 토큰)
"""
# ✅ 효율적
system_prompt = "친절한 한국어 AI 어시스턴트. 정확하고 간결하게 답변."
# (10 토큰)
절감 효과:
요청 100회 시:
- 비효율적: 300 × 100 = 30,000 토큰
- 효율적: 10 × 100 = 1,000 토큰
- 절감: 29,000 토큰 (97% 절감)
비용 (Sonnet 기준):
- 절감액: 29,000 × $3 / 1,000,000 = $0.087 (약 116원)
2. 대화 히스토리 관리
긴 대화에서는 최근 메시지만 유지한다.
MAX_HISTORY = 10
def manage_history(messages):
"""최근 N개 메시지만 유지"""
if len(messages) <= MAX_HISTORY:
return messages
# 시스템 메시지는 항상 유지
system_msgs = [m for m in messages if m.get("role") == "system"]
# 최근 메시지만 선택
recent_msgs = messages[-MAX_HISTORY:]
return system_msgs + recent_msgs
# 사용 예시
conversation = []
while True:
user_input = input("You: ")
if user_input.lower() == "quit":
break
conversation.append({"role": "user", "content": user_input})
# 히스토리 관리
managed_conversation = manage_history(conversation)
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=managed_conversation
)
assistant_msg = response.content[0].text
conversation.append({"role": "assistant", "content": assistant_msg})
print(f"Claude: {assistant_msg}")
3. 불필요한 컨텍스트 제거
이전 결과가 필요 없는 경우 새 대화를 시작한다.
# ❌ 비효율적: 이전 대화를 계속 포함
conversation = [
{"role": "user", "content": "Python의 리스트란?"},
{"role": "assistant", "content": "...긴 설명..."},
{"role": "user", "content": "이제 전혀 다른 질문: Java의 클래스란?"}
]
# ✅ 효율적: 새 대화 시작
new_conversation = [
{"role": "user", "content": "Java의 클래스란?"}
]
4. 프롬프트 캐싱 활용
반복적으로 사용하는 긴 컨텍스트는 캐싱한다.
# 긴 코드베이스를 시스템 프롬프트에 포함
codebase = read_entire_codebase() # 50,000 토큰
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=2048,
system=[{
"type": "text",
"text": f"다음 코드베이스를 참고하여 답변해줘:\n\n{codebase}",
"cache_control": {"type": "ephemeral"} # 5분간 캐싱
}],
messages=[{
"role": "user",
"content": "이 코드의 주요 버그는?"
}]
)
# 5분 내 추가 질문 시 캐시 히트 (비용 90% 절감)
response2 = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=2048,
system=[{
"type": "text",
"text": f"다음 코드베이스를 참고하여 답변해줘:\n\n{codebase}",
"cache_control": {"type": "ephemeral"}
}],
messages=[{
"role": "user",
"content": "리팩토링이 필요한 부분은?"
}]
)
비용 비교:
코드베이스: 50,000 토큰
질문 5회
# 캐싱 없이
비용 = 5 × (50,000 × $3 / 1,000,000)
= 5 × $0.15
= $0.75
# 캐싱 사용
비용 = (50,000 × $3.75 / 1,000,000) # 첫 번째 (1.25배)
+ 4 × (50,000 × $0.3 / 1,000,000) # 나머지 (0.1배)
= $0.1875 + $0.06
= $0.2475
절감: $0.75 - $0.2475 = $0.5025 (67% 절감)
실전 예제: 대규모 코드베이스 분석
실제로 긴 컨텍스트가 유용한 경우를 살펴보자.
시나리오: 전체 코드베이스 리뷰
import os
import glob
def read_codebase(directory, extensions=['.py', '.js', '.ts']):
"""지정된 디렉토리의 모든 코드 파일 읽기"""
files_content = []
for ext in extensions:
pattern = os.path.join(directory, f"**/*{ext}")
files = glob.glob(pattern, recursive=True)
for file_path in files:
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
relative_path = os.path.relpath(file_path, directory)
files_content.append(f"# {relative_path}\n```{ext[1:]}\n{content}\n```\n")
except Exception as e:
print(f"파일 읽기 실패 {file_path}: {e}")
return "\n".join(files_content)
# 코드베이스 읽기
codebase = read_codebase("./my-project", extensions=['.py'])
# 토큰 수 확인
token_count = client.messages.count_tokens(
model="claude-sonnet-4-5-20250929",
messages=[{"role": "user", "content": codebase}]
)
print(f"코드베이스 토큰 수: {token_count.input_tokens}")
# 200K 이하면 한 번에 처리
if token_count.input_tokens < 200000:
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=4096,
messages=[{
"role": "user",
"content": f"""
다음 코드베이스를 분석하고 다음 사항을 리포트해줘:
1. 아키텍처 개요
2. 주요 컴포넌트 및 의존성
3. 잠재적 버그나 보안 취약점
4. 코드 품질 개선 제안
5. 테스트 커버리지 개선 방안
코드베이스:
{codebase}
"""
}]
)
print(response.content[0].text)
else:
print("코드베이스가 너무 큽니다. RAG 전략을 사용하세요.")
시나리오: 긴 API 문서 질의응답
# API 문서 로드 (예: FastAPI 전체 문서)
with open("fastapi_docs.txt", "r") as f:
api_docs = f.read()
# 시스템 프롬프트에 문서 포함 (캐싱 사용)
system_prompt = [{
"type": "text",
"text": f"""
당신은 FastAPI 전문가다. 다음 공식 문서를 참고하여 질문에 답해줘.
{api_docs}
""",
"cache_control": {"type": "ephemeral"}
}]
# 여러 질문 처리
questions = [
"FastAPI에서 의존성 주입은 어떻게 동작하나?",
"비동기 라우트를 작성하는 방법은?",
"Pydantic 모델로 검증하는 방법은?",
"WebSocket을 구현하는 방법은?",
"백그라운드 태스크를 실행하는 방법은?"
]
for question in questions:
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1000,
system=system_prompt, # 캐시 재사용
messages=[{"role": "user", "content": question}]
)
print(f"\n질문: {question}")
print(f"답변: {response.content[0].text}")
print(f"입력 토큰: {response.usage.input_tokens}")
print(f"캐시 읽기 토큰: {response.usage.cache_read_input_tokens}")
정리
핵심 요점
- 컨텍스트 윈도우: Claude가 한 번에 처리할 수 있는 텍스트 양
- 200K 토큰: 약 300페이지 분량, 대부분의 경우 충분
- 1M 토큰 베타: 200K 초과 시 비용 2배, 신중히 사용
- 청킹: 문서를 분할하여 순차 처리
- 요약: 긴 문서를 먼저 요약 후 작업
- RAG: 관련 부분만 검색하여 사용 (가장 효율적)
- Compaction: 서버 측 자동 컨텍스트 관리
- 최적화: 시스템 프롬프트 간결화, 히스토리 관리, 캐싱 활용
전략 선택 가이드
| 상황 | 권장 전략 |
|---|---|
| 문서 < 200K 토큰 | 전체 컨텍스트 사용 |
| 문서 200K-500K 토큰 | 청킹 또는 요약 |
| 문서 > 500K 토큰 | RAG |
| 반복 질의응답 | RAG + 캐싱 |
| 긴 대화 | Compaction |
| 코드베이스 분석 | 전체 컨텍스트 또는 RAG |
비용 최적화 체크리스트
- 시스템 프롬프트를 간결하게 작성했는가?
- 대화 히스토리를 적절히 관리하는가?
- 불필요한 컨텍스트를 제거했는가?
- 반복 사용하는 긴 컨텍스트를 캐싱하는가?
- 200K를 초과할 때 대안을 고려했는가?
- RAG를 적용할 수 있는 경우인가?
다음 단계
다음 차시에서는 Claude API의 가격 체계를 상세히 알아본다. 토큰 기반 가격 정책, 모델별 가격 차이, 그리고 실제 비용을 계산하는 방법을 학습한다.