10.2 대화 상태 관리: 메모리 설계
네, 아래는 「10.2 대화 상태 관리: 메모리 설계」 절에 해당하는 콘텐츠를 책의 일부분처럼 자세하고 친절하게 설명한 실제 원고입니다.
10.2 대화 상태 관리: 메모리 설계
OpenAI의 GPT 기반 API는 기본적으로 상태(stateless)입니다. 즉, 이전에 어떤 대화를 나누었든지 현재의 요청에서는 그 내용을 자동으로 알지 못합니다. 각각의 API 호출은 독립적으로 처리되며, 대화의 연속성과 문맥 유지 기능은 전적으로 개발자가 설계한 로직과 데이터 구조에 의해 구현해야 합니다. 이 절에서는 실제 애플리케이션에서 어떤 방식으로 "대화 상태", 즉 메모리(memory)를 관리할 수 있는지에 대해 구체적인 설계 전략과 코드 예제를 중심으로 학습합니다.
✅ 왜 대화 상태(memory)가 중요한가?
대화형 시스템에서 문맥 추론과 기억 유지가 없으면 자연스럽고 유용한 상호작용을 제공하기 어렵습니다. 예를 들어 사용자가 다음과 같이 말한다고 가정해 봅시다:
사용자: "안녕하세요. 오늘 서울 날씨 어때요?"
GPT: "오늘 서울은 맑고 기온은 22도입니다."
사용자: "내일은요?"
이때, 마지막 문장에서 "내일은요?"라는 질문이 자연스럽게 이해되기 위해서는 이전 대화 내용을 기억하고 "서울"이 언급된 것을 알고 있어야 합니다. 따라서 API를 호출하는 쪽에서 문맥을 저장하고 새로운 입력에 포함시켜야 합니다.
📌 1. 대화 상태 관리를 위한 설계 전략
대화 상태를 관리하는 방법에는 다양한 전략이 있으며, 크게 다음 세 가지 수준으로 구분할 수 있습니다:
① 텍스트 기반 메모리
과거 메시지를 텍스트로 저장하고 지속적으로 API에 포함
② 구조화된 메모리
메타 정보(역할, 시간, user ID)를 포함한 메시지 히스토리 관리
③ 장기/단기 메모리 분리
최근 정보는 챗 메시지로, 오래된 내용은 벡터 DB 등으로 RAG 처리를 병행
📚 2. 텍스트 기반 메모리 구현 예제
가장 기본이 되는 형태는 단순히 이전 user-assistant 메시지를 배열로 저장하는 것입니다.
예시 코드 (Python with OpenAI Python SDK):
from openai import OpenAI
client = OpenAI(api_key="sk-...")
chat_history = [
{"role": "system", "content": "당신은 여행 전문가입니다."},
{"role": "user", "content": "서울에서 가볼 만한 곳 알려줘"},
{"role": "assistant", "content": "경복궁, 남산타워, 홍대 거리를 추천합니다."},
{"role": "user", "content": "그중에서 자연을 느낄 수 있는 곳은?"}
]
response = client.chat.completions.create(
model="gpt-4",
messages=chat_history
)
print(response.choices[0].message.content)
이 구조에서는 각 메시지를 배열에 모아 순서대로 전달하므로, GPT는 이전 내용을 자연스럽게 문맥으로 이해할 수 있습니다.
그러나 이 방식에는 한계가 존재합니다:
호출마다 전체 히스토리를 포함해야 하므로 토큰 수 증가 ⟶ 비용 증가
오래된 맥락까지 계속 유지되면서 요점이 희미해질 수 있음
🧠 3. 구조화된 메모리: 대화 메시지 관리 모듈 설계
실제 서비스를 만들 때는 단순 배열보다는 사용자나 세션 단위로 대화 기록을 구조화하여 관리하는 것이 바람직합니다.
예: Redis 기반 메시지 히스토리 저장 구조
{
"user:abc123": [
{"role": "system", "content": "당신은 친절한 금융 상담사입니다."},
{"role": "user", "content": "적금 추천해 주세요"},
{"role": "assistant", "content": "목표 기간에 따라 적합한 상품이 있어요..."},
...
]
}
구현 시 고려 사항:
키 구성
user_id 또는 session_id 별로 분리
만료 시간
일정 시간 후 자동 삭제로 메모리 절약
최근 N개만 유지
sliding window 방식으로 최근 대화만 보관하여 토큰 최적화
백업/로깅
영구 저장소(DB)에 주기적 동기화
이를 활용하면 다음과 같은 코드를 구성할 수 있습니다:
def load_chat_history(user_id, limit=10):
# Redis 또는 DB에서 최근 N개 메시지 불러오기
return redis.lrange(f"user:{user_id}", -limit, -1)
def save_user_message(user_id, role, content):
message = {"role": role, "content": content}
redis.rpush(f"user:{user_id}", json.dumps(message))
이러한 메모리 관리 로직은 LangChain, LlamaIndex와 같은 프레임워크에서도 핵심 구조로 포함되어 있으며, 자체 구현 시에도 안정성과 확장성을 높일 수 있습니다.
🧠 4. 장기 메모리 vs 단기 메모리: 하이브리드 전략
실제 프로젝트에서는 "최근 대화 흐름"과 "사용자의 장기 선호 정보"를 분리해서 관리하는 것이 좋습니다.
단기 메모리: 5~10 turns의 최근 대화만 저장해 챗 API 호출 시 포함
장기 메모리: 사용자의 관심사, 과거 이력, FAQ 등은 Vector DB에 Embedding 저장
예: 하루 전 예약 내용, 선호 메뉴, 자주 묻는 질문은 long-term memory 처리
⟶ 사용자의 요청이 들어오면 RAG(Retrieval Augmented Generation) 방식으로 관련 기억을 검색하고, 응답 프롬프트에 포함
from langchain.vectorstores import FAISS
retrieved_facts = vector_db.similarity_search("user의 최근 예약")
full_context = retrieved_facts + recent_messages
response = client.chat.completions.create(
model="gpt-4",
messages=full_context
)
🛠️ 5. 상태 관리 시 고려할 점
토큰 한도
GPT-4: 8k/32k/128k context 지원 모델 선택에 따라 다름
개인정보 보호
사용자 대화 기록은 암호화 및 만료 처리 필요
맥락의 흐름 관리
요약(Summarization)을 통해 전체 히스토리를 압축할 수 있음
기억 삭제 및 수정
사용자 요청에 따라 "기억 잊기" 또는 업데이트 기능 제공 가능
✅ 요약
OpenAI의 기본 API는 stateless하므로, 대화 흐름 유지를 위해 메모리 관리가 필수
간단한 텍스트 배열 방식부터, Redis/DB 기반 구조화된 히스토리 관리까지 다양한 방식이 존재
최근 대화는 message 배열에 포함시키되, 오래된 정보는 벡터 DB와의 연동으로 효율적인 기억 구조 가능
실제 서비스에서는 session/user 단위로 메모리를 설계하고, 토큰 최적화와 개인정보 보호도 병행하여 구현해야 함
다음 절에서는 이러한 상태 관리 메커니즘과 함께 다양한 기능(도구 호출 등)을 결합하여 진짜 "복합 기능 챗봇"을 구현하는 방법에 대해 다룹니다.
Last updated