Skip to content

컨텍스트 윈도우 이해

Published: at 05:52 PM

들어가며

Claude를 사용하다 보면 “컨텍스트 윈도우(Context Window)“라는 용어를 자주 접하게 된다. 이는 Claude가 한 번에 처리할 수 있는 텍스트의 양을 의미한다.

“Claude가 책 한 권을 통째로 읽을 수 있다”는 이야기를 들어봤을 것이다. 실제로 Claude는 200K 토큰(약 300페이지 분량)을 한 번에 처리할 수 있으며, 베타 기능을 사용하면 1M 토큰까지 확장할 수 있다.

이번 차시에서는 컨텍스트 윈도우의 개념과 작동 방식을 이해하고, 긴 문서를 효율적으로 처리하는 실전 전략을 알아본다.

컨텍스트 윈도우란 무엇인가

정의

컨텍스트 윈도우는 Claude가 한 번에 “기억”할 수 있는 텍스트의 최대 길이다. 여기에는 다음이 모두 포함된다:

전체 컨텍스트 = 시스템 프롬프트
              + 이전 대화 기록
              + 현재 사용자 메시지
              + Claude 응답 (예상)

모델별 컨텍스트 윈도우

모델표준 컨텍스트베타 확장
Claude Opus 4.6200K 토큰1M 토큰
Claude Sonnet 4.5200K 토큰1M 토큰
Claude Haiku 4.5200K 토큰-

200K 토큰의 실제 크기

200K 토큰이 실제로 어느 정도 분량인지 감을 잡아보자:

예를 들어, 소설 『해리 포터와 마법사의 돌』(영문판)이 약 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 토큰 사용 시 고려사항:

  1. 응답 시간 증가: 매우 긴 컨텍스트는 처리 시간이 길어진다
  2. 비용 2배: 200K 초과 부분은 입력 비용이 2배
  3. 베타 기능: 향후 변경될 수 있음
  4. 실제 필요성 검토: 대부분의 경우 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)

장점:

단점:

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)

장점:

단점:

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)

장점:

단점:

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 동작 방식

  1. 자동 감지: 컨텍스트가 한계에 가까워지면 자동 발동
  2. 요약 수행: 이전 대화 내용을 요약하여 압축
  3. 핵심 유지: 중요한 정보는 유지하면서 토큰 절약
  4. 투명 동작: 사용자에게 투명하게 동작

예시:

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}")

정리

핵심 요점

  1. 컨텍스트 윈도우: Claude가 한 번에 처리할 수 있는 텍스트 양
  2. 200K 토큰: 약 300페이지 분량, 대부분의 경우 충분
  3. 1M 토큰 베타: 200K 초과 시 비용 2배, 신중히 사용
  4. 청킹: 문서를 분할하여 순차 처리
  5. 요약: 긴 문서를 먼저 요약 후 작업
  6. RAG: 관련 부분만 검색하여 사용 (가장 효율적)
  7. Compaction: 서버 측 자동 컨텍스트 관리
  8. 최적화: 시스템 프롬프트 간결화, 히스토리 관리, 캐싱 활용

전략 선택 가이드

상황권장 전략
문서 < 200K 토큰전체 컨텍스트 사용
문서 200K-500K 토큰청킹 또는 요약
문서 > 500K 토큰RAG
반복 질의응답RAG + 캐싱
긴 대화Compaction
코드베이스 분석전체 컨텍스트 또는 RAG

비용 최적화 체크리스트

다음 단계

다음 차시에서는 Claude API의 가격 체계를 상세히 알아본다. 토큰 기반 가격 정책, 모델별 가격 차이, 그리고 실제 비용을 계산하는 방법을 학습한다.

참고 자료


Next Post
Claude 모델 패밀리