왜 컨텍스트 윈도우가 병목이 되는가
대화가 길어지거나 RAG로 문서를 붙일수록 프롬프트 토큰은 선형으로 늘어난다. 문제는 세 가지다. 첫째, 입력 토큰 비용이 매 요청마다 반복 청구된다. 둘째, 윈도우 상한에 걸리면 오래된 맥락이 잘려 답변 품질이 떨어진다. 셋째, 긴 입력은 TTFT(첫 토큰 지연)를 늘린다. 핵심 전략은 "필요한 것만, 재사용 가능하게 넣는다"이다. 요약으로 밀도를 높이고, 청킹으로 검색 단위를 쪼개고, 캐시로 반복 비용을 없앤다.
요약: 오래된 맥락을 압축한다
대화형 서비스라면 최근 N턴은 원문으로 유지하고, 그 이전은 롤링 요약으로 대체한다. 요약은 매 턴이 아니라 임계 토큰을 넘을 때만 트리거해 요약 호출 비용 자체를 줄인다.
def maybe_summarize(history, summary, tokenizer, limit=6000, keep=6):
recent = history[-keep:]
older = history[:-keep]
if count_tokens(tokenizer, history) < limit or not older:
return summary, recent
# older 구간만 압축해 기존 summary에 누적
prompt = f"기존 요약:\n{summary}\n\n추가 대화:\n{render(older)}\n\n300자 이내로 갱신 요약."
new_summary = llm(prompt, max_tokens=400)
return new_summary, recent
주의: 요약은 손실 압축이다. 숫자, 고유명사, 결정 사항은 원문 유지하거나 구조화(JSON)해 별도 보관하는 편이 안전하다.
청킹: 검색과 주입 단위를 설계한다
문서를 통째로 넣지 말고 의미 단위로 쪼갠 뒤 질의와 관련된 청크만 주입한다. 고정 길이 분할은 문장을 끊으므로, 오버랩을 두거나 헤딩·문단 경계를 우선한다.
def chunk(text, size=800, overlap=120):
words, out, i = text.split(), [], 0
while i < len(words):
out.append(" ".join(words[i:i + size]))
i += size - overlap # 경계 문맥 보존
return out
청크 크기는 검색 정밀도와 맥락 완결성의 트레이드오프다. 너무 작으면 문맥이 끊기고, 너무 크면 무관한 토큰이 섞여 정밀도가 떨어진다. 실측으로 400~1000 토큰 범위에서 튜닝한다.
캐시: 프롬프트 접두부를 재사용한다
시스템 프롬프트, 스키마, 고정 지침처럼 매 요청 동일한 접두부는 프롬프트 캐시로 처리한다. 변하지 않는 부분을 앞에, 변하는 사용자 입력을 뒤에 두는 것이 핵심이다. 접두부가 1토큰이라도 바뀌면 캐시가 무효화되므로 순서를 고정한다.
# 캐시가 잘 먹는 구조: [고정 접두부] + [가변 입력]
messages = [
{"role": "system", "content": SYSTEM_PROMPT}, # 캐시 대상(고정)
{"role": "user", "content": retrieved_chunks}, # 준고정: 세션 내 재사용
{"role": "user", "content": user_question}, # 매번 변동
]
검색 결과 청크도 동일 세션에서 반복 사용된다면 캐시 구간에 포함해 이득을 키운다. 반대로 매번 바뀌는 값(타임스탬프 등)을 접두부에 넣으면 캐시 적중률이 0이 된다.
세 전략의 역할 비교
| 전략 | 줄이는 것 | 주된 위험 |
|---|---|---|
| 요약 | 누적 대화 토큰 | 정보 손실 |
| 청킹 | 주입 문서 토큰 | 경계 문맥 단절 |
| 캐시 | 반복 접두부 비용/지연 | 순서 변동 시 무효화 |
실무 적용 순서와 주의점
세 가지는 배타적이지 않고 함께 쓴다. 권장 순서는 다음과 같다.
- 먼저 프롬프트 구조를 [고정→가변]으로 정렬해 캐시 이득부터 확보한다. 코드 변경이 가장 적고 효과가 크다.
- RAG가 있으면 청킹·오버랩을 튜닝해 주입 토큰을 줄인다.
- 대화가 길어지는 서비스면 롤링 요약을 임계 기반으로 추가한다.
공통 주의점은 관측이다. 요청별 입력·출력 토큰, 캐시 적중률, 요약 트리거 횟수를 로깅하지 않으면 어떤 전략이 실제로 효과가 있는지 판단할 수 없다. 그리고 어떤 압축이든 원문을 완전히 버리지 말고, 감사·재처리를 위해 원본은 별도 스토리지에 남겨두는 편이 안전하다.