개념
컨텍스트 창은 희소 자원이다
컨텍스트 창(context window)을 디스크가 아니라 RAM이라고 생각하세요. 빠르고 직접 접근할 수 있지만 용량이 제한적입니다. 모든 것을 다 넣을 수 없으므로 선택이 필요합니다.
graph TD
subgraph Window["Context Window (128K tokens)"]
direction TB
S["System Prompt\n~500 tokens"] --> T["Tool Definitions\n~2K-8K tokens"]
T --> R["Retrieved Context\n~2K-10K tokens"]
R --> H["Conversation History\n~2K-20K tokens"]
H --> F["Few-shot Examples\n~1K-3K tokens"]
F --> Q["User Query\n~100-500 tokens"]
Q --> G["Generation Budget\n~2K-8K tokens"]
end
style S fill:#1a1a2e,stroke:#e94560,color:#fff
style T fill:#1a1a2e,stroke:#0f3460,color:#fff
style R fill:#1a1a2e,stroke:#ffa500,color:#fff
style H fill:#1a1a2e,stroke:#51cf66,color:#fff
style F fill:#1a1a2e,stroke:#9b59b6,color:#fff
style Q fill:#1a1a2e,stroke:#e94560,color:#fff
style G fill:#1a1a2e,stroke:#0f3460,color:#fff
각 구성 요소는 공간을 두고 서로 경쟁합니다. 도구 정의를 더 많이 넣으면 대화 기록을 둘 공간이 줄어듭니다. 검색된 컨텍스트를 더 많이 넣으면 퓨샷 예시를 둘 공간이 줄어듭니다. 컨텍스트 엔지니어링은 이 예산을 작업 성능(task performance)이 최대가 되도록 할당하는 기술입니다.
중간에서 잃어버림(Lost-in-the-Middle)
컨텍스트 엔지니어링에서 가장 중요한 경험적 발견입니다. 모델은 컨텍스트의 시작과 끝에 있는 정보에 더 잘 주의를 기울입니다. 중간에 있는 정보는 어텐션 점수(attention score)가 낮아지고 무시될 가능성이 큽니다.
Liu et al. (2023)은 이 현상을 체계적으로 테스트했습니다. 관련 문서 하나를 관련 없는 문서 20개 사이의 다양한 위치에 배치하고 응답 정확도(answer accuracy)를 측정했습니다. 관련 문서가 첫 번째나 마지막에 있을 때 정확도는 85-90%였습니다. 중간(20개 중 10번째 위치)에 있을 때 정확도는 60-70%로 떨어졌습니다.
이는 직접적인 엔지니어링 시사점을 가집니다.
- 가장 중요한 정보는 맨 앞에 둡니다(시스템 프롬프트, 핵심 지시사항).
- 현재 질의와 가장 관련 있는 컨텍스트는 마지막에 둡니다. 최신성 편향(recency bias)이 도움이 됩니다.
- 컨텍스트의 중간은 가장 낮은 우선순위 영역으로 취급합니다.
- 반드시 중간에 정보를 넣어야 한다면 핵심 요점을 끝에도 다시 한 번 반복합니다.
graph LR
subgraph Attention["Attention Distribution Across Context"]
direction LR
P1["Position 0-20%\nHIGH attention\n(system prompt)"]
P2["Position 20-40%\nMODERATE"]
P3["Position 40-70%\nLOW attention\n(lost in middle)"]
P4["Position 70-90%\nMODERATE"]
P5["Position 90-100%\nHIGH attention\n(current query)"]
end
style P1 fill:#51cf66,color:#000
style P2 fill:#ffa500,color:#000
style P3 fill:#ff6b6b,color:#fff
style P4 fill:#ffa500,color:#000
style P5 fill:#51cf66,color:#000
컨텍스트 구성 요소
시스템 프롬프트(System prompt): 페르소나(persona), 제약 사항, 행동 규칙을 설정합니다. 맨 앞에 들어가며 여러 턴에 걸쳐 일정하게 유지됩니다. Claude Code는 도구 정의와 행동 지침을 포함해 대략 6,000 토큰을 시스템 프롬프트에 사용합니다. 짧고 밀도 있게 유지하세요. 시스템 프롬프트의 모든 단어는 API 호출마다 반복됩니다.
도구 정의(Tool definitions): 각 도구는 50-200 토큰을 차지합니다(이름, 설명, 매개변수 스키마). 도구 50개가 각각 150 토큰씩이면 대화가 시작되기도 전에 7,500 토큰이 됩니다. 동적 도구 선택(dynamic tool selection), 즉 현재 질의와 관련 있는 도구만 포함하면 이 비용을 60-80% 줄일 수 있습니다.
검색된 컨텍스트(Retrieved context): 벡터 데이터베이스(vector database)에서 가져온 문서, 검색 결과, 파일 내용입니다. 검색의 품질이 응답의 품질을 직접 결정합니다. 나쁜 검색은 검색을 아예 하지 않는 것보다 더 나쁩니다. 창을 잡음(noise)으로 채우고 모델을 능동적으로 잘못된 방향으로 이끌기 때문입니다.
대화 기록(Conversation history): 이전의 모든 사용자 메시지와 어시스턴트(assistant) 응답입니다. 대화 길이에 따라 선형으로 증가합니다. 턴당 200 토큰인 50턴 대화는 기록만 10,000 토큰입니다. 그 대부분은 현재 질의와 무관합니다.
퓨샷 예시(Few-shot examples): 원하는 동작을 보여주는 입력/출력 쌍입니다. 잘 고른 두세 개의 예시는 수천 토큰의 지시문보다 출력 품질을 더 많이 개선하는 경우가 많습니다. 하지만 공간을 차지합니다.
생성 예산(Generation budget): 모델의 응답을 위해 예약해 둔 토큰입니다. 창을 가득 채워버리면 모델이 답할 공간이 없습니다. 최소한 2,000-4,000 토큰은 생성용으로 남겨 두세요.
컨텍스트 압축 전략
기록 요약(History summarization): 이전의 모든 턴을 그대로 보존하는 대신 주기적으로 대화를 요약합니다. "X를 논의했고, Y를 결정했으며, 사용자는 Z를 원합니다" 같은 100 토큰 요약이 2,000 토큰짜리 10개 턴을 대체합니다. 기록이 임계값(예: 5,000 토큰)을 넘으면 요약을 실행합니다.
관련도 필터링(Relevance filtering): 검색된 각 문서를 현재 질의와 비교해 점수를 매기고, 임계값 아래의 문서는 버립니다. 10개의 조각(chunk)을 가져왔지만 3개만 관련이 있다면 나머지 7개는 버립니다. 평범한 10개보다 매우 관련 있는 3개가 낫습니다.
도구 가지치기(Tool pruning): 사용자 질의의 의도(intent)를 분류하고 그 의도에 맞는 도구만 포함합니다. 코드 질문에는 캘린더 도구가 필요 없습니다. 일정 잡기 질문에는 파일 시스템 도구가 필요 없습니다. 이 전략은 도구 정의를 8,000 토큰에서 1,000 토큰까지 줄일 수 있습니다.
재귀 요약(Recursive summarization): 매우 긴 문서는 단계적으로 요약합니다. 먼저 각 절(section)을 요약하고, 그 요약들을 다시 요약합니다. 50쪽짜리 문서가 핵심 내용을 담은 500 토큰 다이제스트(digest)가 됩니다.
기억 시스템(Memory Systems)
컨텍스트 엔지니어링은 세 가지 시간 지평(time horizon)을 다룹니다.
단기 기억(Short-term memory): 현재 대화입니다. 컨텍스트 창에 직접 저장됩니다. 턴이 진행될수록 커지며, 요약과 자르기로 관리합니다.
장기 기억(Long-term memory): 대화를 넘어 지속되는 사실과 선호 정보입니다. 예: "사용자는 TypeScript를 선호합니다.", "이 프로젝트는 PostgreSQL을 사용합니다." 데이터베이스에 저장하고 세션 시작 시 검색합니다. Claude Code는 이를 CLAUDE.md 파일에 저장합니다. ChatGPT는 메모리(memory) 기능에 저장합니다.
일화 기억(Episodic memory): 관련 있을 수 있는 특정 과거 상호작용입니다. 예: "지난 화요일에 인증(auth) 모듈에서 비슷한 문제를 디버깅했습니다." 임베딩(embedding)으로 저장해 두고, 현재 대화가 과거의 에피소드와 유사할 때 검색해 옵니다.
graph TD
subgraph Memory["Memory Architecture"]
direction TB
STM["Short-term Memory\n(current conversation)\nDirect in context window"]
LTM["Long-term Memory\n(facts, preferences)\nDB -> retrieved on session start"]
EM["Episodic Memory\n(past interactions)\nEmbeddings -> retrieved on similarity"]
end
Q["Current Query"] --> STM
Q --> LTM
Q --> EM
STM --> CW["Context Window"]
LTM --> CW
EM --> CW
style STM fill:#1a1a2e,stroke:#51cf66,color:#fff
style LTM fill:#1a1a2e,stroke:#0f3460,color:#fff
style EM fill:#1a1a2e,stroke:#e94560,color:#fff
style CW fill:#1a1a2e,stroke:#ffa500,color:#fff
동적 컨텍스트 조립(Dynamic Context Assembly)
핵심 통찰은 이렇습니다. 질의마다 필요한 컨텍스트가 다릅니다. 정적인 시스템 프롬프트, 정적인 도구 집합, 정적인 기록 조합은 낭비입니다. 최고의 시스템은 질의마다 컨텍스트를 동적으로 조립합니다.
- 질의의 의도를 분류합니다.
- 관련 있는 도구만 선택합니다(모든 도구를 넣지 않습니다).
- 관련 있는 문서를 검색합니다(고정된 집합을 사용하지 않습니다).
- 관련 있는 기록 턴만 포함합니다(모든 기록을 넣지 않습니다).
- 작업 유형(task type)에 맞는 퓨샷 예시를 추가합니다.
- 중요도 순으로 정렬합니다. 가장 중요한 것은 처음에, 그다음으로 중요한 것은 마지막에, 부수적인 것은 중간에 둡니다.
이것이 그저 그런 AI 애플리케이션과 훌륭한 AI 애플리케이션을 가르는 차이입니다. 모델은 동일합니다. 차별점은 컨텍스트입니다.
직접 만들기
Step 1: 토큰 계산기(Token Counter)
측정할 수 없는 것은 예산화할 수 없습니다. 간단한 토큰 계산기를 만듭니다. 정확한 토큰 수는 토크나이저(tokenizer)에 따라 달라지므로, 공백 분할(whitespace splitting)에 기반한 근사치를 사용합니다.
import json
from collections import OrderedDict
def count_tokens(text):
if not text:
return 0
return int(len(text.split()) * 1.3)
def count_tokens_json(obj):
return count_tokens(json.dumps(obj))
Step 2: 컨텍스트 예산 관리자(Context Budget Manager)
핵심 추상화입니다. 예산 관리자는 각 구성 요소가 몇 토큰을 쓰는지 추적하고 제한을 강제합니다.
class ContextBudget:
def __init__(self, max_tokens=128000, generation_reserve=4000):
self.max_tokens = max_tokens
self.generation_reserve = generation_reserve
self.available = max_tokens - generation_reserve
self.allocations = OrderedDict()
def allocate(self, component, content, max_tokens=None):
tokens = count_tokens(content)
if max_tokens and tokens > max_tokens:
words = content.split()
target_words = int(max_tokens / 1.3)
content = " ".join(words[:target_words])
tokens = count_tokens(content)
used = sum(self.allocations.values())
if used + tokens > self.available:
allowed = self.available - used
if allowed <= 0:
return None, 0
words = content.split()
target_words = int(allowed / 1.3)
content = " ".join(words[:target_words])
tokens = count_tokens(content)
self.allocations[component] = tokens
return content, tokens
def remaining(self):
used = sum(self.allocations.values())
return self.available - used
def utilization(self):
used = sum(self.allocations.values())
return used / self.max_tokens
def report(self):
total_used = sum(self.allocations.values())
lines = []
lines.append(f"컨텍스트 예산 보고서({self.max_tokens:,} 토큰 창)")
lines.append("-" * 50)
for component, tokens in self.allocations.items():
pct = tokens / self.max_tokens * 100
bar = "#" * int(pct / 2)
lines.append(f" {component:<25} {tokens:>6} 토큰 ({pct:>5.1f}%) {bar}")
lines.append("-" * 50)
lines.append(f" {'사용량':<25} {total_used:>6} 토큰 ({total_used/self.max_tokens*100:.1f}%)")
lines.append(f" {'생성 예약분':<25} {self.generation_reserve:>6} 토큰")
lines.append(f" {'남은 예산':<25} {self.remaining():>6} 토큰")
return "\n".join(lines)
Step 3: 중간 정보 손실(Lost-in-the-Middle) 재정렬
가장 중요한 항목은 처음과 마지막에, 덜 중요한 항목은 중간에 배치하는 전략을 구현합니다.
def reorder_lost_in_middle(items, scores):
paired = sorted(zip(scores, items), reverse=True)
sorted_items = [item for _, item in paired]
if len(sorted_items) <= 2:
return sorted_items
first_half = sorted_items[::2]
second_half = sorted_items[1::2]
second_half.reverse()
return first_half + second_half
def score_relevance(query, documents):
query_words = set(query.lower().split())
scores = []
for doc in documents:
doc_words = set(doc.lower().split())
if not query_words:
scores.append(0.0)
continue
overlap = len(query_words & doc_words) / len(query_words)
scores.append(round(overlap, 3))
return scores
Step 4: 대화 기록 압축기(Conversation History Compressor)
오래된 대화 턴을 요약해 토큰 예산을 회수합니다.
class ConversationManager:
def __init__(self, max_history_tokens=5000):
self.turns = []
self.summaries = []
self.max_history_tokens = max_history_tokens
def add_turn(self, role, content):
self.turns.append({"role": role, "content": content})
self._compress_if_needed()
def _compress_if_needed(self):
total = sum(count_tokens(t["content"]) for t in self.turns)
if total <= self.max_history_tokens:
return
while total > self.max_history_tokens and len(self.turns) > 4:
old_turns = self.turns[:2]
summary = self._summarize_turns(old_turns)
self.summaries.append(summary)
self.turns = self.turns[2:]
total = sum(count_tokens(t["content"]) for t in self.turns)
def _summarize_turns(self, turns):
parts = []
for t in turns:
content = t["content"]
if len(content) > 100:
content = content[:100] + "..."
parts.append(f"{t['role']}: {content}")
return "이전 대화: " + " | ".join(parts)
def get_context(self):
parts = []
if self.summaries:
parts.append("[대화 요약]")
for s in self.summaries:
parts.append(s)
parts.append("[최근 대화]")
for t in self.turns:
parts.append(f"{t['role']}: {t['content']}")
return "\n".join(parts)
def token_count(self):
return count_tokens(self.get_context())
현재 질의와 관련 있는 도구만 포함합니다. 의도를 분류한 다음 필터링합니다.
TOOL_REGISTRY = {
"read_file": {
"description": "파일 내용을 읽습니다",
"tokens": 120,
"categories": ["code", "files"],
},
"write_file": {
"description": "파일에 내용을 씁니다",
"tokens": 150,
"categories": ["code", "files"],
},
"search_code": {
"description": "코드베이스에서 패턴을 검색합니다",
"tokens": 130,
"categories": ["code"],
},
"run_command": {
"description": "셸 명령을 실행합니다",
"tokens": 140,
"categories": ["code", "system"],
},
"create_calendar_event": {
"description": "새 캘린더 일정을 만듭니다",
"tokens": 180,
"categories": ["calendar"],
},
"list_emails": {
"description": "최근 이메일 목록을 가져옵니다",
"tokens": 160,
"categories": ["email"],
},
"send_email": {
"description": "이메일 메시지를 보냅니다",
"tokens": 200,
"categories": ["email"],
},
"web_search": {
"description": "정보를 찾기 위해 웹을 검색합니다",
"tokens": 140,
"categories": ["research"],
},
"query_database": {
"description": "데이터베이스에 SQL 쿼리를 실행합니다",
"tokens": 170,
"categories": ["code", "data"],
},
"generate_chart": {
"description": "데이터로부터 차트를 생성합니다",
"tokens": 190,
"categories": ["data", "visualization"],
},
}
def classify_intent(query):
query_lower = query.lower()
intent_keywords = {
"code": ["code", "function", "bug", "error", "file", "implement", "refactor", "debug", "test"],
"calendar": ["meeting", "schedule", "calendar", "appointment", "event"],
"email": ["email", "mail", "send", "inbox", "message"],
"research": ["search", "find", "what is", "how does", "explain", "look up"],
"data": ["data", "query", "database", "chart", "graph", "analytics", "sql"],
}
scores = {}
for intent, keywords in intent_keywords.items():
score = sum(1 for kw in keywords if kw in query_lower)
if score > 0:
scores[intent] = score
if not scores:
return ["code"]
max_score = max(scores.values())
return [intent for intent, score in scores.items() if score >= max_score * 0.5]
def select_tools(query, token_budget=2000):
intents = classify_intent(query)
relevant = {}
total_tokens = 0
for name, tool in TOOL_REGISTRY.items():
if any(cat in intents for cat in tool["categories"]):
if total_tokens + tool["tokens"] <= token_budget:
relevant[name] = tool
total_tokens += tool["tokens"]
return relevant, total_tokens
Step 6: 전체 컨텍스트 조립 파이프라인(Full Context Assembly Pipeline)
모든 것을 한데 묶습니다. 질의가 주어지면 최적의 컨텍스트를 동적으로 조립합니다.
class ContextEngine:
def __init__(self, max_tokens=128000, generation_reserve=4000):
self.budget = ContextBudget(max_tokens, generation_reserve)
self.conversation = ConversationManager(max_history_tokens=5000)
self.system_prompt = (
"당신은 도움이 되는 AI 어시스턴트입니다. 코드 편집, 파일 관리, "
"웹 검색, 데이터 분석을 위한 도구를 사용할 수 있습니다. 각 작업에 "
"적절한 도구를 사용하고, 간결하고 정확하게 답하세요."
)
self.knowledge_base = [
"Python 3.12는 대괄호 표기를 사용하는 제네릭 클래스용 타입 매개변수 문법을 도입했습니다.",
"이 프로젝트는 임베딩(embedding) 저장소로 PostgreSQL 16과 pgvector를 사용합니다.",
"인증(authentication)은 Supabase Auth와 JWT 토큰으로 처리합니다.",
"프론트엔드(frontend)는 App Router를 사용하는 Next.js 15로 구성합니다.",
"API 요청 빈도 제한은 사용자당 분당 100회로 설정합니다.",
"배포 파이프라인은 GitHub Actions와 Docker 멀티 스테이지 빌드를 사용합니다.",
"새 모듈의 테스트 커버리지는 80% 이상이어야 합니다.",
"이 코드베이스는 데이터 접근에 리포지터리 패턴(repository pattern)을 따릅니다.",
]
def assemble(self, query):
self.budget = ContextBudget(self.budget.max_tokens, self.budget.generation_reserve)
system_content, _ = self.budget.allocate("system_prompt", self.system_prompt, max_tokens=1000)
tools, tool_tokens = select_tools(query, token_budget=2000)
tool_text = json.dumps(list(tools.keys()))
tool_content, _ = self.budget.allocate("tools", tool_text, max_tokens=2000)
relevance = score_relevance(query, self.knowledge_base)
threshold = 0.1
relevant_docs = [
doc for doc, score in zip(self.knowledge_base, relevance)
if score >= threshold
]
if relevant_docs:
doc_scores = [s for s in relevance if s >= threshold]
reordered = reorder_lost_in_middle(relevant_docs, doc_scores)
doc_text = "\n".join(reordered)
doc_content, _ = self.budget.allocate("retrieved_context", doc_text, max_tokens=3000)
history_text = self.conversation.get_context()
if history_text.strip():
history_content, _ = self.budget.allocate("conversation_history", history_text, max_tokens=5000)
query_content, _ = self.budget.allocate("user_query", query, max_tokens=500)
return self.budget
def chat(self, query):
self.conversation.add_turn("user", query)
budget = self.assemble(query)
response = f"[다음 질의에 대한 응답: {query[:50]}...]"
self.conversation.add_turn("assistant", response)
return budget
def run_demo():
print("=" * 60)
print(" 컨텍스트 엔지니어링 파이프라인 데모")
print("=" * 60)
engine = ContextEngine(max_tokens=128000, generation_reserve=4000)
print("\n--- 질의 1: 코드 작업 ---")
budget = engine.chat("인증 모듈에서 JWT 토큰이 너무 빨리 만료되는 버그를 수정하세요")
print(budget.report())
print("\n--- 질의 2: 조사 작업 ---")
budget = engine.chat("PostgreSQL로 벡터 검색을 구현하는 가장 좋은 접근은 무엇인가요?")
print(budget.report())
print("\n--- 질의 3: 대화 기록이 쌓인 뒤 ---")
for i in range(8):
engine.conversation.add_turn("user", f"시스템 구현 세부 사항에 관한 후속 질문 {i+1}번")
engine.conversation.add_turn("assistant", f"아키텍처 기술 세부 사항을 다루는 후속 응답 {i+1}번")
budget = engine.chat("이제 우리가 논의한 변경 사항을 구현하세요")
print(budget.report())
print("\n--- 도구 선택 예시 ---")
test_queries = [
"auth.py의 버그를 수정하세요",
"화요일에 팀 회의를 잡으세요",
"데이터베이스 쿼리 성능 통계를 보여주세요",
"에러 처리 모범 사례를 검색하세요",
]
for q in test_queries:
tools, tokens = select_tools(q)
intents = classify_intent(q)
print(f"\n 질의: {q}")
print(f" 의도: {intents}")
print(f" 도구: {list(tools.keys())} ({tokens} 토큰)")
print("\n--- 중간 정보 손실(Lost-in-the-Middle) 재정렬 ---")
docs = ["문서 A (가장 관련 있음)", "문서 B (어느 정도 관련 있음)", "문서 C (가장 관련 없음)",
"문서 D (관련 있음)", "문서 E (약간 관련 있음)"]
scores = [0.95, 0.60, 0.20, 0.80, 0.50]
reordered = reorder_lost_in_middle(docs, scores)
print(f" 원래 순서: {docs}")
print(f" 점수: {scores}")
print(f" 재정렬: {reordered}")
print(f" (가장 관련 있는 항목은 시작과 끝, 가장 덜 관련 있는 항목은 중간)")
사용해보기
Claude Code의 컨텍스트 전략
Claude Code는 계층화된 접근(layered approach)으로 컨텍스트를 관리합니다. 시스템 프롬프트에는 행동 규칙과 도구 정의가 들어갑니다(약 6K 토큰). 파일을 열면 그 내용이 컨텍스트로 주입됩니다. 검색을 하면 그 결과가 추가됩니다. 오래된 대화 턴은 요약됩니다. CLAUDE.md는 세션을 넘어 지속되는 장기 기억을 제공합니다.
핵심 엔지니어링 결정은 이것입니다. Claude Code는 전체 코드베이스를 컨텍스트에 통째로 쏟아붓지 않습니다. 필요할 때 관련 파일만 검색해 옵니다. 이것이 실전의 컨텍스트 엔지니어링입니다.
Cursor의 동적 컨텍스트 로딩
Cursor는 전체 코드베이스를 임베딩으로 색인합니다. 사용자가 질의를 입력하면 벡터 유사도(vector similarity)로 가장 관련 있는 파일과 코드 블록을 검색해 옵니다. 그 조각들만 컨텍스트 창에 들어갑니다. 50만 줄짜리 코드베이스가 가장 관련 있는 코드 블록 5-10개로 압축됩니다.
패턴은 명확합니다. 모든 것을 임베딩으로 만들고, 필요할 때 검색하고, 중요한 것만 포함합니다.
ChatGPT Memory
ChatGPT는 사용자 선호와 사실을 장기 기억으로 저장합니다. 각 대화가 시작될 때 관련 기억을 검색해 시스템 프롬프트에 포함합니다. "The user prefers Python"이라는 한 줄은 5 토큰을 쓰지만, 대화마다 반복되는 수백 토큰 분량의 지시문을 절약해 줍니다.
컨텍스트 엔지니어링으로서의 검색 증강 생성(RAG)
검색 증강 생성(Retrieval-Augmented Generation; RAG)은 정식화된 컨텍스트 엔지니어링입니다. 지식을 모델 가중치(weights, 학습 단계)나 시스템 프롬프트(정적 컨텍스트)에 박아 넣는 대신, 질의 시점에 관련 문서를 검색해 컨텍스트 창에 주입합니다. 전체 RAG 파이프라인, 즉 분할(chunking), 임베딩(embedding), 검색(retrieval), 재순위화(reranking)는 단 하나의 문제를 해결하기 위해 존재합니다. 바로 올바른 정보를 컨텍스트 창에 넣는 일입니다.
산출물 만들기
이 강의는 outputs/prompt-context-optimizer.md를 산출합니다. 이는 컨텍스트 조립 전략을 감사(audit)하고 최적화를 권고하는 재사용 가능한 프롬프트입니다. 시스템 프롬프트, 도구 개수, 평균 기록 길이, 검색 전략을 입력으로 넣으면 토큰 낭비를 찾아내고 개선안을 제안합니다.
또한 outputs/skill-context-engineering.md도 산출합니다. 이는 작업 유형, 컨텍스트 창 크기, 지연 예산(latency budget)에 따라 컨텍스트 조립 파이프라인을 설계하는 의사결정 프레임워크입니다.
연습문제
-
(쉬움) ContextBudget 클래스에 "토큰 낭비 감지기(token waste detector)"를 추가하세요. 예산의 30% 이상을 사용하는 구성 요소를 표시하고, 구성 요소 유형별로 특화된 압축 전략(기록 요약, 도구 가지치기, 문서 재순위화 등)을 제안해야 합니다.
-
(중간) 검색된 컨텍스트에 의미 기반 중복 제거(semantic deduplication)를 구현하세요. 두 검색 문서의 유사도가 80% 이상(단어 겹침 또는 임베딩 코사인 유사도 기준)이라면 점수가 더 높은 쪽만 유지합니다. 이를 통해 회수한 토큰 예산이 얼마인지 측정하세요.
-
(중간) "컨텍스트 재현(context replay)" 도구를 만드세요. 대화 기록(transcript)이 주어지면 그것을 ContextEngine을 통해 재현하고, 턴마다 예산 할당이 어떻게 바뀌는지 시각화합니다. 구성 요소별 토큰 사용량을 시간에 따라 그래프로 그리고, 컨텍스트 압축이 시작되는 턴을 식별하세요.
-
(어려움) 우선순위 기반 도구 선택기를 구현하세요. 포함/제외라는 이진 판단 대신, 각 도구에 현재 질의 관련도 점수를 부여합니다. 도구 예산이 소진될 때까지 관련도가 높은 순서대로 도구를 포함합니다. 도구를 5개, 10개, 20개, 50개 포함했을 때의 작업 성능을 비교하세요.
-
(어려움) 여러 전략을 결합한 컨텍스트 압축기를 만드세요. 세 가지 압축 전략(자르기, 요약, 핵심 문장 추출)을 구현하고, 20개의 문서 집합에 대해 벤치마크하세요. 압축률(compression ratio)과 정보 보존(information retention) 사이의 절충, 즉 압축된 버전이 질의에 대한 답을 여전히 담고 있는지를 측정하세요.
핵심 용어
| 용어 | 흔한 설명 | 실제 의미 |
|---|
| 컨텍스트 창(Context window) | "모델이 읽을 수 있는 양" | 모델이 단일 순전파(forward pass)에서 처리하는 최대 토큰 수(입력 + 출력). GPT-5는 400K, Claude Opus 4.7은 200K(베타 1M), Gemini 3 Pro는 2M이다. |
| 컨텍스트 엔지니어링(Context engineering) | "고급 프롬프트 엔지니어링" | 컨텍스트 창에 무엇을, 어떤 순서와 우선순위로 넣을지 결정하는 분야. 검색, 압축, 도구 선택, 기억 관리 전체를 포함한다. |
| 중간 정보 손실(Lost-in-the-middle) | "모델이 중간을 잊는다" | LLM이 컨텍스트의 시작과 끝에 더 잘 주의를 기울이고, 중간에 놓인 정보에서는 정확도가 10-20% 떨어진다는 경험적 발견. |
| 토큰 예산(Token budget) | "남은 토큰 수" | 구성 요소별 한도를 가지고 컨텍스트 창 용량을 명시적으로 할당하는 방식(시스템 프롬프트, 도구, 기록, 검색, 생성). |
| 동적 컨텍스트(Dynamic context) | "필요할 때 불러오기" | 의도 분류, 관련 도구 선택, 검색 결과를 바탕으로 질의마다 컨텍스트 창을 다르게 조립하는 방식. |
| 기록 요약(History summarization) | "대화 압축" | 오래된 대화 턴을 그대로 두는 대신 간결한 요약으로 치환해, 핵심 정보는 보존하면서 토큰 비용을 줄이는 기법. |
| 도구 가지치기(Tool pruning) | "관련 도구만 포함" | 질의 의도를 분류하고 그에 맞는 도구 정의만 포함해 도구 토큰 비용을 60-80% 줄이는 기법. |
| 장기 기억(Long-term memory) | "세션을 넘어 기억하기" | 데이터베이스에 저장해 두고 세션 시작 시 검색해 오는 사실과 선호 정보. CLAUDE.md, ChatGPT Memory 등이 이에 해당한다. |
| 일화 기억(Episodic memory) | "특정 과거 사건 기억" | 과거의 상호작용을 임베딩으로 저장해 두고, 현재 질의가 과거 대화와 유사할 때 검색해 오는 방식. |
| 생성 예산(Generation budget) | "답변을 위한 공간" | 모델 출력용으로 예약해 둔 토큰. 컨텍스트가 창을 가득 채우면 모델이 응답할 공간이 없다. |
더 읽을거리