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