본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 16. 03:56

LLM 컨텍스트 윈도우 관리: 긴 문서를 처리하기 위한 기술

요약

LLM의 컨텍스트 윈도우 제한은 단일 요청에서 처리할 수 있는 최대 토큰 수를 의미하며, 아무리 큰 모델이라도 이 한계가 존재합니다. 본 글에서는 Claude 3.5 Sonnet이나 GPT-4 Turbo 등 주요 LLM의 컨텍스트 크기를 비교하고, 이를 효율적으로 관리하는 두 가지 핵심 기술을 소개합니다. 첫째는 의미 단위로 문서를 분할하는 '시맨틱 청킹'이며, 둘째는 필요한 정보만 검색하여 사용하는 'RAG(검색 증강 생성)' 기법입니다.

핵심 포인트

  • LLM의 컨텍스트 윈도우는 모델별로 제한적이며, 이를 초과하면 오류가 발생하거나 비용 낭비가 생길 수 있습니다.
  • 시맨틱 청킹은 단순히 글자 수를 자르는 것이 아니라 의미 단위(문단)를 고려하여 문서를 분할하는 기술입니다.
  • RAG(Retrieval-Augmented Generation)는 모든 정보를 프롬프트에 넣지 않고, 사용자 쿼리와 가장 관련 있는 정보만 검색하여 사용하는 효율적인 방법입니다.
  • 제공된 Python 코드는 토큰 추정 및 시맨틱 청킹 로직을 구현하여 실제 애플리케이션 개발에 활용할 수 있도록 합니다.

모든 LLM에는 컨텍스트 윈도우 (Context Window) 제한이 있습니다. 이는 단일 요청에서 전달할 수 있는 최대 토큰 (Token) 수입니다. Claude 3.5 Sonnet은 200K 토큰을 제공하지만, 이 역시 유한합니다. 여기서는 프로덕션 AI 애플리케이션을 위해 컨텍스트를 효율적으로 관리하는 방법을 소개합니다.

컨텍스트 윈도우 제한 이해하기

모델컨텍스트 윈도우대략적인 페이지 수
Claude 3.5 Sonnet200K tokens~500 pages
GPT-4 Turbo128K tokens~300 pages
Claude 3 Opus200K tokens~500 pages

제한을 초과하면 에러가 발생합니다. 제한에 근접하면 가치를 더하지 못하는 토큰에 비용을 낭비하게 됩니다.

토큰 추정 (Token Estimation)

import re

def estimate_tokens(text: str) -> int:
    """
    대략적인 토큰 추정. 
    영어 텍스트의 경우 토큰당 약 4자.
    """
    return len(text) // 4

def estimatetokensprecise(text: str) -> int:
    """
    단어 수를 사용한 더 정밀한 추정.
    평균적인 영어 단어는 약 1.3 토큰.
    """
    words = len(re.findall(r'\w+', text))
    return int(words * 1.3)

기술 1: 시맨틱 청킹 (Semantic Chunking)

글자 수가 아닌 의미 단위로 문서를 분할합니다:

import re

def semanticchunk(text: str, maxtokens: int = 4000, overlap: int = 200) -> list[str]:
    """
    텍스트를 시맨틱 청크(문단)로 분할합니다.
    """
    # 이중 줄바꿈(문단)으로 분할
    paragraphs = re.split(r'\n\n+', text)
    chunks = []
    current_chunk = []
    current_tokens = 0

    for para in paragraphs:
        para_tokens = estimate_tokens(para)
        if current_tokens + para_tokens > max_tokens:
            # 현재 청크 저장
            if current_chunk:
                chunks.append('\n\n'.join(current_chunk))
            
            # 오버랩(Overlap)을 포함하여 새 청크 시작
            overlap_paras = []
            overlap_tokens = 0
            for p in reversed(current_chunk):
                t = estimate_tokens(p)
                if overlap_tokens + t <= overlap:
                    overlap_paras.insert(0, p)
                    overlap_tokens += t
                else:
                    break
            current_chunk = overlap_paras + [para]
            current_tokens = overlap_tokens + para_tokens
        else:
            current_chunk.append(para)
            current_tokens += para_tokens

    if current_chunk:
        chunks.append('\n\n'.join(current_chunk))
    return chunks

기술 2: RAG — 검색 증강 생성 (Retrieval-Augmented Generation)

모든 내용을 프롬프트 (Prompt)에 넣지 마세요.

관련된 내용만 검색하세요: `python
class SimpleRAG:
def init(self, documents: list[str], chunk_size: int = 1000):
self.chunks = self.create_chunks(documents, chunk_size)
self.embeddings = self.create_embeddings(self.chunks)

def retrieve(self, query: str, top_k: int = 3) -> list[str]:
    """쿼리에 가장 관련 있는 청크(Chunk)를 찾습니다."""
    query_embedding = self._embed(query)
    scores = [
        self.cosine_similarity(query_embedding, e)
        for e in self.embeddings
    ]
    top_indices = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)[:top_k]
    return [self.chunks[i] for i in top_indices]

def create_chunks(self, documents: list[str], chunk_size: int) -> list[str]:
    chunks = []
    for doc in documents:
        chunks.extend(semantic_chunk(doc, max_tokens=chunk_size))
    return chunks

def _embed(self, text: str) -> list[float]:
    # 프로덕션 환경에서는 OpenAI 또는 cohere.ai 임베딩을 사용하세요
    pass

`

기술 3: 대화 요약 (Conversation Summary)
문맥 (Context)을 보존하기 위해 오래된 메시지를 요약합니다:
`python
class SummarizingConversation:
def init(self, max_tokens: int = 16000):
self.max_tokens = max_tokens
self.messages = []
self.summary = ""

def add_message(self, role: str, content: str):
    self.messages.append({"role": role, "content": content})
    self.maybe_summarize()

def maybe_summarize(self):
    total_tokens = sum(estimate_tokens(m["content"]) for m in self.messages)
    if total_tokens > self.max_tokens:
        # 오래된 메시지를 요약합니다
        older_messages = self.messages[:-5]  # 마지막 5개는 유지
        recent = self.messages[-5:]
        summary_prompt = f"""
        다음 대화의 핵심 정보를 보존하면서 간결하게 요약하세요:
        {
            chr(10).join(f'{m["role"]}: {m["content"]}' for m in older_messages)
        }
        """
        # 요약을 위해 LLM 호출 (의사 코드)
        self.summary = call_llm_summarize(summary_prompt)
        self.messages = [{"role": "system", "content": f"이전 문맥: {self.summary}"}] + recent

def get_messages(self) -> list[dict]:
    return self.messages

`

기술 4: 시스템 프롬프트 최적화 (System Prompt Optimization)
시스템 프롬프트를 간결하게 유지하세요:
`python
❌ 장황한 시스템 프롬프트 (토큰 낭비)
verbose_system = """
당신은 도움이 되는 AI입니다.
"""

assistant. 당신은 정중하고 전문적이며 도움이 되도록 설계되었습니다. 정확한 정보를 제공해야 하며, 모르는 것이 있을 때는 정직해야 합니다. 당신은 ... [200개 단어 더 있음] """ ✅ 간결한 시스템 프롬프트 (효과적) lean_system = """ 역할: 도움이 되는 AI 어시스턴트 목표: 정확하고 간결한 답변 제공 불확실할 때: "모릅니다"라고 말하기 """ `

기법 5: 토큰 추적을 포함한 스트리밍 (Streaming with Token Tracking)

`python
class StreamingTokenTracker:
def init(self, model: str = "claude-3-5-sonnet-20241022"):
self.model = model
self.totalinputtokens = 0
self.totaloutputtokens = 0

async def stream_chat(self, messages: list[dict]) -> str:
    """토큰 사용량을 추적하면서 응답을 스트리밍합니다."""
    response = await fetch(' https://api.ofox.ai/v1/chat/completions ', {
        method: 'POST',
        headers: {
            'Authorization': f'Bearer {API_KEY}',
            'Content-Type': 'application/json'
        },
        body: json.dumps({
            'model': self.model,
            'messages': messages,
            'stream': True
        })
    })
    reader = response.body.getReader()
    decoder = TextDecoder()
    full_response = []
    while True:
        chunk = await reader.read()
        if chunk.done:
            break
        data = decoder.decode(chunk.value)
        for line in data.split('\n'):
            if line.startswith('data: '):
                delta = json.loads(line[6:]).get('choices', [{}])[0].get('delta', {})
                if content := delta.get('content'):
                    self.totaloutputtokens += estimate_tokens(content)
                    yield content
    
    # 입력 토큰 추적
    self.totalinputtokens = sum(
        estimate_tokens(m['content']) for m in messages
    )

def getcost(self, inputcostper1k=0.003, outputcostper_1k=0.015):
    inputcost = (self.totalinputtokens / 1000) * inputcostper1k
    outputcost = (self.totaloutputtokens / 1000) * outputcostper_1k
    return inputcost + outputcost

`

실용적인 경험 법칙 (Practical Rule of Thumb)
프롬프트를 컨텍스트 윈도우 (Context Window)의 50% 미만으로 유지하세요. 이렇게 하면 다음과 같은 여유 공간을 확보할 수 있습니다:

  • 사용자 입력의 변동성
  • 모델의 추론 (Reasoning)
  • 예상치 못한 응답 길이

시작하기
ofox.ai를 통해 토큰 효율적인 AI 애플리케이션을 구축해 보세요. — 이들의 OpenAI 호환 API를 사용하면 경쟁력 있는 가격으로 넉넉한 컨텍스트 윈도우를 가진 Claude에 접근할 수 있습니다.

👉 ofox.ai로 시작하기 이 기사에는 제휴 링크가 포함되어 있습니다. 태그: llm, artificial-intelligence, programming, developer, performance Canonical URL: https://dev.to/zny10289

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0