본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 27. · 2 Views
Context Manager 구현하기 실습 가이드
LLM 애플리케이션에서 컨텍스트를 효율적으로 관리하는 Context Manager를 직접 구현해봅니다. Progressive Disclosure와 Attention Budget 개념을 활용하여 토큰을 최적화하는 방법을 단계별로 학습합니다.
목차
- 프로젝트_개요_Context_Manager의_역할과_구조
- 단계1_기본_Context_클래스_설계
- 단계2_Progressive_Disclosure_구현
- 단계3_Attention_Budget_모니터링_추가
- 단계4_70_80퍼센트_임계값에서_압축_트리거
- 단계5_테스트_및_검증
- 실습_실제_LLM_API와_통합하기
1. 프로젝트 개요 Context Manager의 역할과 구조
최근 AI 스타트업에 입사한 김개발 씨는 첫 프로젝트로 챗봇 시스템 개발을 맡게 되었습니다. 열심히 GPT API를 연동했는데, 대화가 길어질수록 비용이 폭발적으로 증가하고 응답 품질도 떨어지는 문제가 발생했습니다.
선배 박시니어 씨가 말합니다. "Context Manager가 필요해요."
Context Manager는 LLM에 전달되는 컨텍스트를 체계적으로 관리하는 시스템입니다. 마치 도서관 사서가 필요한 책만 정확히 찾아주듯이, 모델에게 꼭 필요한 정보만 효율적으로 전달하는 역할을 합니다.
이를 통해 토큰 비용을 절감하고, 모델의 집중력을 최적화할 수 있습니다.
다음 코드를 살펴봅시다.
# Context Manager의 전체 구조 설계
from dataclasses import dataclass, field
from typing import List, Dict, Optional
from enum import Enum
class Priority(Enum):
CRITICAL = 1 # 절대 삭제 불가
HIGH = 2 # 중요한 정보
MEDIUM = 3 # 일반 정보
LOW = 4 # 필요시 압축 가능
@dataclass
class ContextItem:
content: str
priority: Priority
tokens: int
metadata: Dict = field(default_factory=dict)
김개발 씨는 입사 첫 주에 충격적인 경험을 했습니다. 자신이 만든 챗봇이 하루 만에 API 비용 100만원을 소진해버린 것입니다.
대화 내역을 전부 컨텍스트에 담아 보내다 보니, 토큰 사용량이 기하급수적으로 늘어난 탓이었습니다. 박시니어 씨가 화이트보드 앞으로 김개발 씨를 불렀습니다.
"LLM을 사용할 때 가장 중요한 건 뭘까요?" 김개발 씨가 머뭇거리자 박시니어 씨가 답했습니다. "바로 컨텍스트 관리입니다." 그렇다면 Context Manager란 정확히 무엇일까요?
쉽게 비유하자면, Context Manager는 마치 유능한 비서와 같습니다. 사장님이 회의에 들어가기 전, 비서는 수백 페이지의 보고서 중에서 핵심만 정리해서 요약본을 만들어 드립니다.
모든 내용을 다 전달하면 사장님의 집중력이 분산되고, 정작 중요한 결정을 내리기 어려워지기 때문입니다. LLM도 마찬가지입니다.
컨텍스트 윈도우에는 한계가 있고, 너무 많은 정보를 넣으면 중요한 정보가 묻혀버리는 현상이 발생합니다. 이를 "Lost in the Middle" 문제라고 부릅니다.
Context Manager가 없던 시절에는 어땠을까요? 개발자들은 대화 내역이 길어지면 단순히 오래된 메시지부터 삭제했습니다.
하지만 이 방식에는 치명적인 문제가 있었습니다. 사용자가 처음에 언급한 중요한 정보가 사라져버리는 것입니다.
예를 들어 "나는 채식주의자야"라고 처음에 말한 사용자에게, 나중에 스테이크 레시피를 추천하는 황당한 상황이 벌어집니다. 단순 시간순 삭제가 아닌, 우선순위 기반 관리가 필요한 이유입니다.
위 코드를 살펴보면, 먼저 Priority 열거형으로 네 가지 우선순위를 정의했습니다. CRITICAL은 사용자의 핵심 설정처럼 절대 삭제하면 안 되는 정보입니다.
HIGH는 현재 대화의 맥락에 중요한 정보, MEDIUM은 일반적인 대화 내용, LOW는 필요하면 압축하거나 삭제해도 되는 정보입니다. ContextItem 클래스는 하나의 컨텍스트 단위를 표현합니다.
내용과 우선순위뿐 아니라, 토큰 수와 메타데이터까지 관리합니다. 토큰 수를 미리 계산해두면 나중에 압축 결정을 내릴 때 빠르게 판단할 수 있습니다.
실제 현업에서는 이 구조를 기반으로 다양한 기능을 확장합니다. 예를 들어 고객 서비스 챗봇에서는 고객의 VIP 등급, 이전 불만 사항 등을 CRITICAL로 설정합니다.
이렇게 하면 대화가 아무리 길어져도 핵심 고객 정보는 항상 유지됩니다. 김개발 씨가 고개를 끄덕였습니다.
"아, 그래서 구조 설계가 중요하군요. 일단 뼈대를 잘 만들어 놓으면 나중에 기능 추가가 쉬워지겠네요."
실전 팁
💡 - 우선순위는 4단계가 적당합니다. 너무 세분화하면 관리가 복잡해집니다.
- 토큰 수는 생성 시점에 미리 계산해두면 런타임 성능이 향상됩니다.
- metadata를 활용하면 나중에 분석이나 디버깅에 유용합니다.
2. 단계1 기본 Context 클래스 설계
박시니어 씨가 말했습니다. "이제 본격적으로 Context Manager 클래스를 만들어볼까요?" 김개발 씨는 노트북을 펴고 코딩을 시작했습니다.
첫 단계는 컨텍스트 아이템들을 저장하고 관리할 수 있는 기본 클래스를 만드는 것이었습니다.
ContextManager 클래스는 여러 ContextItem을 담아 관리하는 컨테이너입니다. 마치 서류 캐비닛처럼, 각 서류를 체계적으로 분류하고 보관합니다.
추가, 조회, 삭제 같은 기본 연산과 함께 현재 총 토큰 수를 추적하는 기능이 필요합니다.
다음 코드를 살펴봅시다.
import tiktoken
class ContextManager:
def __init__(self, max_tokens: int = 4000):
self.max_tokens = max_tokens
self.items: List[ContextItem] = []
self.encoder = tiktoken.get_encoding("cl100k_base")
def count_tokens(self, text: str) -> int:
return len(self.encoder.encode(text))
def add(self, content: str, priority: Priority) -> ContextItem:
tokens = self.count_tokens(content)
item = ContextItem(content=content, priority=priority, tokens=tokens)
self.items.append(item)
return item
@property
def total_tokens(self) -> int:
return sum(item.tokens for item in self.items)
구조 설계를 마친 김개발 씨는 이제 본격적으로 ContextManager 클래스를 구현하기 시작했습니다. 박시니어 씨가 옆에서 조언합니다.
"가장 기본이 되는 기능부터 차근차근 만들어 가세요." 서류 캐비닛을 생각해보면 이해가 쉽습니다. 좋은 캐비닛은 서류를 쉽게 넣고 뺄 수 있어야 합니다.
또한 현재 얼마나 많은 서류가 있는지, 공간이 얼마나 남았는지 한눈에 파악할 수 있어야 합니다. ContextManager도 마찬가지입니다.
먼저 생성자를 살펴봅시다. max_tokens 파라미터로 최대 토큰 수를 지정합니다.
기본값은 4000으로 설정했는데, 이는 GPT-3.5의 컨텍스트 윈도우를 고려한 값입니다. 물론 GPT-4나 Claude처럼 더 큰 윈도우를 지원하는 모델을 사용할 때는 이 값을 늘리면 됩니다.
tiktoken 라이브러리는 OpenAI에서 제공하는 토크나이저입니다. "cl100k_base" 인코딩은 GPT-4와 GPT-3.5-turbo에서 사용하는 토큰화 방식입니다.
토큰 수를 정확히 계산하려면 실제 모델이 사용하는 토크나이저를 써야 합니다. count_tokens 메서드는 문자열을 받아서 토큰 수를 반환합니다.
이 메서드가 있으면 매번 외부 라이브러리를 직접 호출할 필요 없이, 클래스 내부에서 일관되게 토큰을 계산할 수 있습니다. add 메서드는 새로운 컨텍스트 아이템을 추가합니다.
내용과 우선순위만 전달하면, 토큰 수는 자동으로 계산됩니다. 반환값으로 생성된 아이템을 돌려주는데, 이렇게 하면 호출자가 필요할 때 참조할 수 있습니다.
total_tokens 프로퍼티는 현재 저장된 모든 아이템의 토큰 수 합계를 반환합니다. 프로퍼티로 만든 이유는 호출할 때마다 최신 값을 계산하기 위해서입니다.
캐싱을 할 수도 있지만, 단순함을 위해 매번 계산하도록 했습니다. 김개발 씨가 질문했습니다.
"그런데 tiktoken을 설치해야 하나요?" 박시니어 씨가 답합니다. "네, pip install tiktoken으로 설치하면 됩니다.
OpenAI API를 사용한다면 필수라고 할 수 있어요." 실제 프로젝트에서는 여기에 몇 가지 기능을 더 추가합니다. 예를 들어 아이템을 ID로 조회하거나, 특정 우선순위의 아이템만 필터링하는 기능이 필요할 수 있습니다.
하지만 지금은 핵심 기능에 집중하겠습니다.
실전 팁
💡 - tiktoken은 OpenAI 모델용입니다. Claude나 다른 모델을 쓴다면 해당 토크나이저를 사용하세요.
- max_tokens는 모델의 컨텍스트 윈도우보다 여유 있게 설정하세요. 응답 생성용 토큰도 남겨둬야 합니다.
- 프로퍼티로 total_tokens를 만들면 항상 최신 값을 얻을 수 있습니다.
3. 단계2 Progressive Disclosure 구현
"자, 이제 재미있는 부분이에요." 박시니어 씨가 화이트보드에 피라미드 그림을 그렸습니다. "Progressive Disclosure라고 들어봤어요?" 김개발 씨가 고개를 저었습니다.
"정보를 한꺼번에 다 주지 않고, 필요한 만큼만 점진적으로 공개하는 기법이에요."
Progressive Disclosure는 정보를 중요도에 따라 단계적으로 공개하는 전략입니다. 마치 영화 예고편이 핵심 장면만 보여주듯이, LLM에게도 처음에는 핵심 정보만 제공하고, 필요할 때 세부 정보를 추가로 공개합니다.
이를 통해 초기 토큰 사용량을 줄이고, 모델의 집중력을 높일 수 있습니다.
다음 코드를 살펴봅시다.
from typing import Generator
class ContextManager:
# ... 이전 코드 ...
def get_by_priority(self, max_priority: Priority) -> List[ContextItem]:
"""지정한 우선순위 이상의 아이템만 반환"""
return [
item for item in self.items
if item.priority.value <= max_priority.value
]
def progressive_disclosure(self) -> Generator[List[ContextItem], None, None]:
"""단계적으로 컨텍스트를 공개"""
for priority in Priority:
yield self.get_by_priority(priority)
def build_context(self, max_priority: Priority = Priority.MEDIUM) -> str:
"""지정 우선순위까지의 컨텍스트를 문자열로 조합"""
items = self.get_by_priority(max_priority)
return "\n\n".join(item.content for item in items)
박시니어 씨가 재미있는 비유를 들었습니다. "신입 사원이 첫날 출근했다고 생각해보세요.
회사의 모든 규정집을 한꺼번에 읽으라고 하면 어떨까요?" 김개발 씨가 웃으며 답했습니다. "머리가 터질 것 같겠죠.
저도 입사 첫날 두꺼운 매뉴얼을 받고 막막했던 기억이 나네요." 바로 그것입니다. Progressive Disclosure는 이런 정보 과부하를 방지하는 전략입니다.
신입 사원에게 첫날에는 출퇴근 시간과 기본 업무만 알려주고, 일주일 후에 복리후생을, 한 달 후에 평가 제도를 설명하는 것이 더 효과적입니다. LLM도 마찬가지입니다.
모든 컨텍스트를 한꺼번에 던져주면 모델이 중요한 정보와 덜 중요한 정보를 구분하기 어려워집니다. 오히려 핵심 정보에 집중하지 못하고 엉뚱한 답변을 할 수 있습니다.
위 코드의 get_by_priority 메서드를 보면, 지정한 우선순위 이상의 아이템만 필터링해서 반환합니다. Priority 열거형의 value 값을 비교하는데, CRITICAL이 1이고 LOW가 4이므로 값이 작을수록 더 중요한 정보입니다.
progressive_disclosure 메서드는 제너레이터입니다. CRITICAL부터 시작해서 점점 더 많은 정보를 포함하는 리스트를 순차적으로 반환합니다.
이렇게 하면 호출자가 필요한 수준의 상세도를 선택할 수 있습니다. 실제 사용 예를 들어보겠습니다.
고객 지원 챗봇에서 첫 응답을 생성할 때는 CRITICAL 정보만 사용합니다. 여기에는 고객 이름, VIP 여부, 현재 문의 유형 같은 핵심 정보가 들어갑니다.
만약 첫 응답이 충분하지 않으면, HIGH 레벨까지 확장합니다. 이제 최근 구매 이력이나 이전 문의 내역도 포함됩니다.
그래도 부족하면 MEDIUM, LOW까지 순차적으로 확장합니다. build_context 메서드는 필터링된 아이템들을 하나의 문자열로 조합합니다.
각 아이템 사이에 빈 줄 두 개를 넣어서 구분했습니다. 이 문자열이 실제로 LLM API에 전달되는 컨텍스트가 됩니다.
김개발 씨가 감탄했습니다. "와, 이렇게 하면 처음에 토큰을 아끼다가, 필요할 때만 더 자세한 정보를 쓸 수 있겠네요!" 박시니어 씨가 고개를 끄덕였습니다.
"맞아요. 비용 절감과 품질 향상을 동시에 달성하는 핵심 기법이에요."
실전 팁
💡 - 첫 응답은 CRITICAL과 HIGH만으로 시작하세요. 대부분의 경우 이것으로 충분합니다.
- 제너레이터를 사용하면 메모리를 효율적으로 사용할 수 있습니다.
- 우선순위 기준은 도메인에 따라 다르게 정의하세요.
4. 단계3 Attention Budget 모니터링 추가
"이제 가장 중요한 개념을 배울 차례예요." 박시니어 씨가 진지한 표정으로 말했습니다. "Attention Budget이라고 들어봤어요?" 김개발 씨는 처음 듣는 용어였습니다.
"쉽게 말해서, LLM이 집중할 수 있는 정신적 자원의 총량이에요."
Attention Budget은 LLM이 컨텍스트에 할당할 수 있는 주의력의 총량입니다. 마치 사람이 하루에 집중할 수 있는 시간이 정해져 있듯이, LLM도 토큰 수에 따라 집중력이 분산됩니다.
이 예산을 모니터링하면 언제 압축이 필요한지 정확히 판단할 수 있습니다.
다음 코드를 살펴봅시다.
from dataclasses import dataclass
@dataclass
class BudgetStatus:
used: int
total: int
usage_ratio: float
remaining: int
is_warning: bool
is_critical: bool
class ContextManager:
# ... 이전 코드 ...
def __init__(self, max_tokens: int = 4000, warning_threshold: float = 0.7):
self.max_tokens = max_tokens
self.warning_threshold = warning_threshold
self.items: List[ContextItem] = []
self.encoder = tiktoken.get_encoding("cl100k_base")
def get_budget_status(self) -> BudgetStatus:
used = self.total_tokens
ratio = used / self.max_tokens
return BudgetStatus(
used=used,
total=self.max_tokens,
usage_ratio=ratio,
remaining=self.max_tokens - used,
is_warning=ratio >= self.warning_threshold,
is_critical=ratio >= 0.9
)
박시니어 씨가 흥미로운 질문을 던졌습니다. "하루에 정말 집중해서 일할 수 있는 시간이 몇 시간인 것 같아요?" 김개발 씨가 잠시 생각했습니다.
"음, 솔직히 4-5시간 정도요? 그 이상은 효율이 뚝 떨어지는 것 같아요." "맞아요.
그게 바로 Attention Budget의 개념이에요. LLM도 마찬가지로, 컨텍스트가 길어질수록 집중력이 분산됩니다.
특히 중간에 있는 정보에 대한 주의력이 크게 떨어져요." 이것이 바로 앞서 언급한 "Lost in the Middle" 현상입니다. 연구에 따르면, LLM은 컨텍스트의 시작 부분과 끝 부분에 더 많은 주의를 기울이고, 중간 부분은 상대적으로 소홀히 합니다.
따라서 단순히 토큰 수만 관리하는 것으로는 부족합니다. 사용률을 추적하고, 일정 임계값을 넘어가면 경고를 발생시켜야 합니다.
위 코드에서 BudgetStatus 데이터클래스는 현재 예산 상태를 담습니다. used는 현재 사용 중인 토큰 수, total은 최대 허용 토큰 수입니다.
usage_ratio는 사용률로, 0에서 1 사이의 값을 가집니다. is_warning은 사용률이 70% 이상일 때 True가 됩니다.
이 시점부터 압축을 고려해야 합니다. is_critical은 90% 이상일 때 True가 됩니다.
이때는 즉시 압축을 수행해야 합니다. 왜 70%를 경고 임계값으로 설정했을까요?
여유분을 남겨두기 위해서입니다. 새로운 메시지가 갑자기 많은 토큰을 차지할 수 있고, 응답 생성에도 토큰이 필요합니다.
버퍼를 충분히 확보해두면 갑작스러운 상황에도 대응할 수 있습니다. get_budget_status 메서드는 호출할 때마다 현재 상태를 계산해서 반환합니다.
이 정보를 바탕으로 UI에 경고를 표시하거나, 자동으로 압축을 트리거할 수 있습니다. 김개발 씨가 물었습니다.
"그러면 매 메시지마다 이 상태를 체크해야 하나요?" 박시니어 씨가 답했습니다. "네, 최소한 사용자 메시지를 받을 때마다 체크하는 것이 좋아요.
비용이 거의 들지 않으니까요."
실전 팁
💡 - 경고 임계값은 70-80%, 위험 임계값은 90%가 일반적입니다.
- BudgetStatus 객체를 로깅하면 나중에 분석에 유용합니다.
- 실시간 대시보드에 사용률을 표시하면 운영이 편해집니다.
5. 단계4 70 80퍼센트 임계값에서 압축 트리거
"자, 이제 가장 핵심인 압축 로직을 구현할 차례예요." 박시니어 씨가 말했습니다. 김개발 씨는 조금 긴장했습니다.
정보를 압축한다는 게 생각보다 복잡해 보였기 때문입니다. "걱정 마세요.
단계별로 차근차근 만들어 볼게요."
압축 트리거는 Attention Budget이 임계값에 도달했을 때 자동으로 컨텍스트를 줄이는 메커니즘입니다. 마치 스마트폰의 저전력 모드처럼, 배터리가 20% 이하로 떨어지면 자동으로 불필요한 기능을 꺼서 사용 시간을 연장합니다.
Context Manager도 토큰 사용률이 높아지면 우선순위가 낮은 정보부터 압축하거나 제거합니다.
다음 코드를 살펴봅시다.
class ContextManager:
# ... 이전 코드 ...
def compress_if_needed(self) -> bool:
"""필요시 자동 압축 수행. 압축했으면 True 반환"""
status = self.get_budget_status()
if not status.is_warning:
return False
# 우선순위가 낮은 아이템부터 제거
self.items.sort(key=lambda x: x.priority.value, reverse=True)
while self.get_budget_status().usage_ratio > 0.6:
if not self.items:
break
# LOW 우선순위만 제거
if self.items[-1].priority == Priority.LOW:
removed = self.items.pop()
print(f"Removed: {removed.content[:50]}...")
else:
break
# 정렬 복원
self.items.sort(key=lambda x: x.priority.value)
return True
스마트폰 배터리가 20% 이하로 떨어지면 어떻게 되나요? 화면 밝기가 줄어들고, 백그라운드 앱이 정리되고, 위치 서비스가 제한됩니다.
핵심 기능은 유지하면서 배터리 소모를 최소화하는 것입니다. Context Manager의 압축 로직도 같은 원리입니다.
토큰 사용률이 70%를 넘어가면 경고 모드에 진입합니다. 이때부터 우선순위가 낮은 정보를 정리하기 시작합니다.
왜 70-80%일까요? 세 가지 이유가 있습니다.
첫째, 새 메시지를 위한 공간 확보입니다. 사용자가 긴 메시지를 보낼 수도 있습니다.
여유 공간이 없으면 새 메시지를 처리할 수 없게 됩니다. 둘째, 응답 생성 공간 확보입니다.
LLM의 응답도 토큰을 사용합니다. 충분한 응답을 생성하려면 공간이 필요합니다.
셋째, 성능 저하 방지입니다. 컨텍스트가 가득 차면 모델의 응답 품질이 떨어집니다.
미리 정리해두면 더 좋은 응답을 얻을 수 있습니다. 위 코드의 compress_if_needed 메서드를 살펴봅시다.
먼저 현재 예산 상태를 확인합니다. 경고 상태가 아니면 아무것도 하지 않고 False를 반환합니다.
경고 상태라면 아이템들을 우선순위 역순으로 정렬합니다. 가장 덜 중요한 아이템이 리스트 끝에 오게 됩니다.
그다음 while 루프에서 사용률이 60% 이하로 떨어질 때까지 아이템을 제거합니다. 중요한 점은 LOW 우선순위만 제거한다는 것입니다.
더 중요한 정보는 건드리지 않습니다. "그런데 왜 60%까지 낮추나요?" 김개발 씨가 물었습니다.
박시니어 씨가 설명했습니다. "70%에서 트리거해서 70%까지만 낮추면, 바로 다음 메시지에서 또 트리거될 수 있어요.
충분히 여유를 확보해야 압축이 너무 자주 일어나지 않습니다." 마지막으로 아이템들을 다시 원래 순서로 정렬합니다. 이렇게 하면 컨텍스트를 구성할 때 우선순위 순서가 유지됩니다.
실제 프로덕션 환경에서는 단순 삭제 대신 요약을 사용하기도 합니다. 예를 들어 이전 대화 10개를 삭제하는 대신, LLM에게 요약을 요청하고 그 요약본을 HIGH 우선순위로 저장하는 방식입니다.
실전 팁
💡 - 압축 후 목표 사용률은 60% 정도가 적당합니다. 너무 공격적으로 낮추면 정보 손실이 커집니다.
- 압축 로그를 남기면 나중에 디버깅에 유용합니다.
- 삭제 대신 요약을 사용하면 정보 손실을 최소화할 수 있습니다.
6. 단계5 테스트 및 검증
"코드를 다 작성했으니 이제 테스트해볼까요?" 박시니어 씨가 말했습니다. 김개발 씨는 테스트의 중요성을 잘 알고 있었습니다.
특히 Context Manager처럼 복잡한 로직은 다양한 시나리오에서 검증해야 안심할 수 있습니다.
테스트 코드는 우리가 만든 Context Manager가 정상적으로 동작하는지 검증합니다. 마치 자동차 출고 전에 다양한 테스트를 거치듯이, 코드도 여러 상황에서 예상대로 동작하는지 확인해야 합니다.
우선순위 필터링, 토큰 계산, 압축 트리거 등 핵심 기능을 모두 테스트합니다.
다음 코드를 살펴봅시다.
import unittest
class TestContextManager(unittest.TestCase):
def setUp(self):
self.cm = ContextManager(max_tokens=100)
def test_add_and_count_tokens(self):
item = self.cm.add("Hello, world!", Priority.HIGH)
self.assertEqual(item.priority, Priority.HIGH)
self.assertGreater(item.tokens, 0)
def test_priority_filtering(self):
self.cm.add("Critical info", Priority.CRITICAL)
self.cm.add("Low info", Priority.LOW)
critical_only = self.cm.get_by_priority(Priority.CRITICAL)
self.assertEqual(len(critical_only), 1)
def test_compression_trigger(self):
# 토큰 제한을 작게 설정하고 많은 데이터 추가
for i in range(10):
self.cm.add(f"Low priority message {i}" * 5, Priority.LOW)
self.assertTrue(self.cm.get_budget_status().is_warning)
김개발 씨가 코드를 완성하고 뿌듯해하고 있을 때, 박시니어 씨가 말했습니다. "좋아요, 이제 진짜 중요한 단계가 남았어요." 김개발 씨가 고개를 갸웃거렸습니다.
"뭔데요?" "바로 테스트예요." 많은 주니어 개발자들이 테스트를 귀찮은 추가 작업으로 생각합니다. 하지만 경험 많은 개발자일수록 테스트의 가치를 압니다.
테스트는 미래의 나를 위한 안전망입니다. 자동차를 생각해보세요.
아무리 멋진 디자인의 신차라도, 충돌 테스트나 브레이크 테스트를 통과하지 못하면 출시할 수 없습니다. 코드도 마찬가지입니다.
프로덕션에 배포하기 전에 다양한 상황에서 테스트해야 합니다. 위 테스트 코드를 살펴봅시다.
setUp 메서드에서 각 테스트 전에 새로운 ContextManager 인스턴스를 생성합니다. max_tokens를 100으로 작게 설정했는데, 테스트에서 압축 트리거를 쉽게 확인하기 위해서입니다.
test_add_and_count_tokens는 가장 기본적인 테스트입니다. 아이템을 추가하고, 우선순위가 올바르게 설정되었는지, 토큰 수가 0보다 큰지 확인합니다.
test_priority_filtering은 우선순위 필터링이 제대로 동작하는지 확인합니다. CRITICAL과 LOW 두 개의 아이템을 추가하고, CRITICAL만 조회했을 때 정확히 하나만 반환되는지 검증합니다.
test_compression_trigger는 압축 트리거 조건을 테스트합니다. 의도적으로 많은 LOW 우선순위 메시지를 추가해서 경고 상태가 되는지 확인합니다.
박시니어 씨가 조언했습니다. "테스트는 경계 조건을 잘 확인해야 해요.
예를 들어 토큰이 정확히 임계값일 때, 아이템이 하나도 없을 때, 모든 아이템이 CRITICAL일 때 같은 특수한 상황을 테스트해보세요." 김개발 씨가 열심히 메모했습니다. 테스트 코드를 작성하다 보면 오히려 원래 코드의 버그를 발견하기도 합니다.
"테스트 주도 개발(TDD)이라는 방법론도 있어요. 테스트를 먼저 작성하고, 그 테스트를 통과하는 코드를 나중에 작성하는 방식이에요." pytest를 사용하면 더 간결한 문법으로 테스트를 작성할 수 있습니다.
하지만 표준 라이브러리인 unittest도 충분히 강력합니다. 중요한 건 도구가 아니라 테스트하는 습관입니다.
실전 팁
💡 - 경계 조건과 예외 상황을 반드시 테스트하세요.
- 테스트 이름은 무엇을 테스트하는지 명확하게 작성하세요.
- CI/CD 파이프라인에 테스트를 포함시키면 자동으로 검증됩니다.
7. 실습 실제 LLM API와 통합하기
"드디어 마지막 단계예요!" 박시니어 씨가 말했습니다. 김개발 씨의 눈이 반짝였습니다.
지금까지 만든 Context Manager를 실제 OpenAI API와 연결해서 동작하는 모습을 볼 생각에 설레었습니다. 이론으로만 배운 것과 실제로 동작하는 것을 보는 것은 완전히 다른 경험이기 때문입니다.
LLM API 통합은 우리가 만든 Context Manager를 실제 서비스에서 사용할 수 있게 만드는 마지막 단계입니다. 마치 엔진을 자동차에 장착하는 것처럼, Context Manager를 챗봇 시스템에 연결합니다.
사용자 메시지를 받아 컨텍스트에 추가하고, 예산을 확인한 후, API를 호출하는 전체 흐름을 구현합니다.
다음 코드를 살펴봅시다.
from openai import OpenAI
class ChatBot:
def __init__(self, api_key: str):
self.client = OpenAI(api_key=api_key)
self.context = ContextManager(max_tokens=3000)
# 시스템 프롬프트는 CRITICAL로 설정
self.context.add(
"You are a helpful assistant.",
Priority.CRITICAL
)
def chat(self, user_message: str) -> str:
# 사용자 메시지 추가
self.context.add(user_message, Priority.MEDIUM)
# 필요시 압축
self.context.compress_if_needed()
# API 호출
response = self.client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": self.context.build_context()}]
)
return response.choices[0].message.content
드디어 모든 퍼즐 조각이 맞춰지는 순간입니다. 김개발 씨가 지금까지 열심히 만든 Context Manager가 실제로 동작하는 모습을 볼 시간입니다.
박시니어 씨가 화이트보드에 전체 흐름을 그렸습니다. "사용자가 메시지를 보내면 어떤 일이 일어나는지 따라가 봅시다." 첫 번째, 사용자 메시지 수신입니다.
사용자가 "안녕하세요, 오늘 날씨가 어때요?"라고 메시지를 보냅니다. 이 메시지는 MEDIUM 우선순위로 Context Manager에 추가됩니다.
두 번째, 예산 확인 및 압축입니다. compress_if_needed 메서드가 호출되어 현재 토큰 사용률을 확인합니다.
70%를 넘었다면 LOW 우선순위 아이템들이 정리됩니다. 세 번째, 컨텍스트 구성입니다.
build_context 메서드가 모든 아이템을 하나의 문자열로 조합합니다. 이때 Progressive Disclosure 전략에 따라 적절한 수준의 정보만 포함됩니다.
네 번째, API 호출입니다. 구성된 컨텍스트와 함께 OpenAI API를 호출합니다.
모델은 이 컨텍스트를 바탕으로 응답을 생성합니다. 다섯 번째, 응답 반환입니다.
생성된 응답을 사용자에게 돌려줍니다. 필요하다면 이 응답도 컨텍스트에 추가할 수 있습니다.
위 코드의 ChatBot 클래스를 살펴봅시다. 생성자에서 OpenAI 클라이언트와 ContextManager를 초기화합니다.
시스템 프롬프트는 CRITICAL 우선순위로 설정해서 절대 삭제되지 않게 합니다. chat 메서드는 실제 대화 로직을 담당합니다.
사용자 메시지를 MEDIUM으로 추가하고, 압축이 필요한지 확인한 후, API를 호출합니다. 실제 프로덕션 환경에서는 여기에 몇 가지 기능을 더 추가해야 합니다.
에러 처리, 재시도 로직, 응답 캐싱, 로깅 등이 필요합니다. 또한 AI의 응답도 컨텍스트에 추가해서 대화의 연속성을 유지해야 합니다.
김개발 씨가 직접 코드를 실행해봤습니다. "와, 정말 동작하네요!" 몇 번의 대화를 주고받자 압축이 트리거되는 것도 확인할 수 있었습니다.
콘솔에 "Removed: ..." 메시지가 출력되면서 오래된 LOW 우선순위 메시지가 정리되었습니다. 박시니어 씨가 마무리했습니다.
"이제 기본기는 다 갖췄어요. 앞으로는 이 기반 위에서 더 고급 기능들을 추가해나가면 됩니다.
요약 기능, 멀티턴 대화 관리, 사용자별 컨텍스트 분리 같은 것들이요." 김개발 씨는 뿌듯한 미소를 지었습니다. 처음에는 막막했던 Context Manager가 이제는 완전히 이해되었습니다.
무엇보다 직접 만들어보면서 배운 것이 가장 큰 수확이었습니다.
실전 팁
💡 - 시스템 프롬프트는 항상 CRITICAL로 설정하세요.
- API 호출 전에 항상 compress_if_needed를 호출하는 것이 좋습니다.
- 실제 서비스에서는 반드시 에러 처리와 로깅을 추가하세요.
- AI 응답도 컨텍스트에 추가해서 대화 연속성을 유지하세요.
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
LLM-as-a-Judge 평가 시스템 완벽 가이드
LLM을 활용하여 AI 에이전트의 출력을 체계적으로 평가하는 방법을 다룹니다. Direct Scoring, Pairwise Comparison, 편향 완화 기법부터 실제 대시보드 구현까지 실습 중심으로 설명합니다.
Agent-Optimized Tool Set 설계 실습 가이드
AI 에이전트가 효율적으로 사용할 수 있는 통합 도구 세트를 설계하는 방법을 배웁니다. FileSystem, Web, Database 도구를 MCP 프로토콜로 패키징하여 토큰 효율성과 명확성을 극대화하는 실전 기술을 다룹니다.
Knowledge Graph 메모리 시스템 완벽 가이드
AI 에이전트에 장기 기억 능력을 부여하는 Knowledge Graph 메모리 시스템을 구축합니다. Working, Short-term, Long-term 메모리 아키텍처부터 Neo4j를 활용한 Temporal Knowledge Graph 구현까지 실습합니다.
Multi-Agent Research Assistant 실습 가이드
여러 AI 에이전트가 협력하여 연구를 수행하는 시스템을 구축하는 방법을 배웁니다. Supervisor 패턴을 중심으로 Search, Analysis, Synthesis 에이전트를 설계하고 구현하는 과정을 단계별로 살펴봅니다.
Advanced Evaluation - LLM-as-a-Judge 마스터
LLM을 평가자로 활용하여 AI 시스템의 품질을 자동으로 측정하는 고급 기법을 다룹니다. Direct Scoring부터 Panel of LLMs까지, 편향을 최소화하고 신뢰도를 높이는 실전 전략을 배웁니다.