카테고리 없음

3. [History+Summary] 자동 대화 요약 메모리를 탑재한 대화형 챗봇 봇(CLI)

아톨 2026. 8. 31. 16:48
반응형

단발성 테스트 스크립트를 넘어, 실제 터미널(CLI) 환경에서 사용자와 주고받는 실시간 대화 루프(while True)와 슬라이딩 윈도우 기반 요약 메모리를 결합한 완성형 챗봇 애플리케이션입니다.

 

  • #Chat_Loop (대화 루프): 사용자가 /quit을 입력하기 전까지 연속적으로 메시지를 입력받고 응답하는 무한 루프 구조입니다.
  • #CLI_Command (특수 명령어): 챗봇 대화 중 /history, /summary, /reset과 같은 명령어로 대화 이력을 조회하거나 상태를 관리하는 기능입니다.
  • #System_Prompt (시스템 프롬프트): AI에게 역할(Assistant), 답변 규칙, 요약 정보 참조 방식 등을 미리 지시하는 핵심 명령문입니다.
  • #Summary_Memory (요약 메모리): 누적된 오래된 대화를 요약하여 system 메시지 형태의 백그라운드 메모리로 변환 및 유지하는 기법입니다.
  • #Exception_Handling (예외 처리): API 호출 중 네트워크 오류 등이 생겨도 프로그램이 강제로 꺼지지 않고 /retry 또는 다음 입력으로 넘어가도록 try-except로 보호하는 구조입니다.

 

1. 환경 설정 및 시스템 상수

# ============================================================
# 1. 환경 설정
# ============================================================
load_dotenv()

# API KEY 읽기
API_KEY = os.getenv("OPENAI_API_KEY")

# 사용할 모델
MODEL_NAME = os.getenv("MODEL_NAME", "gpt-5.4-mini")

# ============================================================
# 2. History 설정
# ============================================================
# 이 개수를 초과하면 오래된 대화를 요약한다.
MAX_HISTORY_MESSAGES = 10

# 요약할 때 최근 몇 개 메시지를 그대로 유지할 것인가
KEEP_RECENT_MESSAGES = 4

# ============================================================
# 3. OpenAI Client 생성
# ============================================================
client = OpenAI(api_key = API_KEY)
  • 상수 설정: 대화 메시지가 10개를 초과하면 자동 압축 로직을 트리거하고, 압축 시에도 가장 최근 메시지 4개는 요약하지 않고 원본 그대로 유지합니다.

2. 시스템 프롬프트 정의 (SYSTEM_PROMPT)

# ============================================================
# 4. System Prompt
# ============================================================
SYSTEM_PROMPT = """
당신은 친절하고 정확한 AI Assistant입니다.

사용자와 자연스럽게 대화하세요.

이전 대화 요약이 제공되면, 그 내용을 사용자의 중요한 배경 정보와 이전 대화의 맥락으로 활용하세요.

단, 이전 요약에 없는 사실을 새롭게 만들어내지 마세요.
"""
  • AI의 기본적인 페르소나와 환각(Hallucination) 방지 지침을 담은 기본 메인 지침입니다.

3. 요약 및 압축 함수 (summarize_old_messages, compress_history)

# ============================================================
# 5. 오래된 대화 요약 함수
# ============================================================

def summarize_old_messages(old_messages):

    """오래된 대화 이력을 AI가 핵심 내용 중심으로 요약한다."""

    # --------------------------------------------------------
    # 메시지 리스트를 하나의 텍스트로 변환
    # --------------------------------------------------------

    conversation_text = "\n".join(
        f"{message['role'].upper()}: {message['content']}"
        for message in old_messages
    )

    # --------------------------------------------------------
    # 화면에 요약 대상 출력
    # --------------------------------------------------------
    print("\n")
    print("=" * 70)
    print("🧠 오래된 대화 요약 시작")
    print("=" * 70)

    print("\n[요약 대상 대화]\n")
    print(conversation_text)
    print("\n" + "-" * 70)

    # --------------------------------------------------------
    # OpenAI에게 요약 요청
    # --------------------------------------------------------
    response = client.responses.create(
        model=MODEL_NAME,
        input=[
            {
                "role": "system",
                "content": """
당신은 대화 메모리를 관리하는 시스템입니다.

아래 대화를 미래의 AI Assistant가 계속 참고할 수 있도록 압축 요약하세요.

다음 내용을 중심으로 정리하세요.
1. 사용자의 중요한 배경 정보
2. 사용자의 선호와 방향
3. 사용자의 목표
4. 이전에 논의한 중요한 내용
5. 현재 진행 중인 작업

중요하지 않은 인사말이나 반복적인 내용은 제거하세요.

가능하면 5줄 이내로 작성하세요.
"""
            },
            {
                "role": "user",
                "content": conversation_text
            },
        ],
    )

    # --------------------------------------------------------
    # 요약 결과
    # --------------------------------------------------------
    summary = response.output_text

    print("\n[새로운 요약 결과]\n")
    print(summary)
    print("=" * 70)
    print()

    return summary

# ============================================================
# 6. History 압축 함수
# ============================================================
def compress_history(messages):
    """대화가 너무 길어지면, 오래된 대화를 요약하고, 최근 대화만 유지한다."""

    # --------------------------------------------------------
    # 현재 메시지 개수 확인
    # --------------------------------------------------------
    print(
        f"\n📦 현재 History 메시지 수: "
        f"{len(messages)}"
    )

    # --------------------------------------------------------
    # 아직 길이가 짧으면 압축하지 않는다
    # --------------------------------------------------------
    if len(messages) <= MAX_HISTORY_MESSAGES:
        return messages

    # --------------------------------------------------------
    # History 압축 시작
    # --------------------------------------------------------
    print("\n⚠️ History가 너무 길어졌습니다.")
    print("🧠 오래된 대화를 요약합니다...")

    # --------------------------------------------------------
    # 최근 메시지 유지
    # --------------------------------------------------------
    recent_messages = messages[-KEEP_RECENT_MESSAGES:]

    # --------------------------------------------------------
    # 오래된 메시지 선택
    # --------------------------------------------------------
    old_messages = messages[:-KEEP_RECENT_MESSAGES]

    # --------------------------------------------------------
    # 오래된 대화 요약
    # --------------------------------------------------------
    summary = summarize_old_messages(old_messages)

    # --------------------------------------------------------
    # 요약 메시지 생성
    # --------------------------------------------------------
    summary_message = {
        "role": "system",
        "content": (
            "[이전 대화의 요약 메모]\n\n"
            f"{summary}"
        )
    }

    # --------------------------------------------------------
    # 새로운 History 생성: 요약 + 최근 대화
    # --------------------------------------------------------
    new_messages = [summary_message] + recent_messages

    print(
        f"📦 압축 후 History 메시지 수: "
        f"{len(new_messages)}"
    )

    return new_messages
  • 메시지 수가 10개를 초과하면 messages를 오래된 대화와 최근 4개 대화로 자릅니다.
  • 오래된 대화는 summarize_old_messages에 넘겨 5줄 이내의 핵심 메모리 데이터로 변환한 후 최신 4개 메시지 앞에 붙여 새로운 대화 이력으로 교체합니다.

4. 상태 출력 함수 (print_history, print_summary)

# ============================================================
# 7. 현재 History 출력
# ============================================================
def print_history(messages):
    """현재 저장된 대화 History를 출력한다."""

    print("\n")
    print("=" * 70)
    print("📚 현재 History")
    print("=" * 70)

    for index, message in enumerate(messages):
        role = message["role"]
        content = message["content"]

        print(f"\n[{index}] {role.upper()}")
        print(content)
        print("-" * 70)

    print(f"\n총 메시지 수: {len(messages)}")
    print("=" * 70)

# ============================================================
# 8. 현재 요약 출력
# ============================================================
def print_summary(messages):
    """History 안에 있는 요약 메모를 찾아 출력한다."""
    found = False
    for message in messages:
        if (
            message["role"] == "system"
            and "[이전 대화의 요약 메모]"
            in message["content"]
        ):

            print("\n")
            print("=" * 70)
            print("🧠 현재 대화 요약")
            print("=" * 70)

            print(message["content"])
            print("=" * 70)
            found = True

    if not found:
        print("\n📝 아직 생성된 대화 요약이 없습니다.")
  • 특수 명령어가 입력되었을 때, 현재 메모리에 남아있는 대화 이력이나 압축되어 저장된 요약 메모리를 콘솔 화면에 시각적으로 보여줍니다.

5. AI 답변 생성 함수 (chat_with_ai)

# ============================================================
# 9. AI에게 메시지 보내기
# ============================================================
def chat_with_ai(messages):
    """현재 History 전체를 기반으로 AI의 답변을 생성한다."""

    # --------------------------------------------------------
    # System Prompt
    # --------------------------------------------------------
    input_messages = [
        {
            "role": "system",
            "content": SYSTEM_PROMPT
        }
    ]

    # --------------------------------------------------------
    # 현재 대화 History 추가
    # --------------------------------------------------------
    input_messages.extend(messages)

    # --------------------------------------------------------
    # OpenAI 호출
    # --------------------------------------------------------
    response = client.responses.create(
        model=MODEL_NAME,
        input=input_messages
    )

    # --------------------------------------------------------
    # 답변 반환
    # --------------------------------------------------------
    return response.output_text
  • 기본 SYSTEM_PROMPT를 최상단에 두고, 뒤이어 요약 메모리 및 최근 대화 이력이 담긴 messages를 덧붙여(extend) 모델에 전달합니다.

6. 메인 대화 루프 (main)

# ============================================================
# 10. Chat Loop
# ============================================================
def main():
    """실제 사용자와 계속 대화하는 Chat Loop"""

    # --------------------------------------------------------
    # 대화 History
    # --------------------------------------------------------
    messages = []

    # --------------------------------------------------------
    # 시작 화면
    # --------------------------------------------------------
    print("\n")
    print("=" * 70)
    print("🤖 Summary Memory Chat")
    print("=" * 70)

    print("\n대화를 시작합니다.")
    print("\n사용 가능한 명령어")
    print("/history  → 현재 대화 이력 보기")
    print("/summary  → 현재 요약 보기")
    print("/reset    → 대화 초기화")
    print("/quit     → 종료")

    print("\n" + "=" * 70)

    # --------------------------------------------------------
    # 무한 대화 Loop
    # --------------------------------------------------------
    while True:
        # ----------------------------------------------------
        # 사용자 입력
        # ----------------------------------------------------
        user_input = input("\n👤 YOU: ").strip()

        # ----------------------------------------------------
        # 빈 입력 처리
        # ----------------------------------------------------
        if not user_input:
            continue

        # ----------------------------------------------------
        # 종료
        # ----------------------------------------------------
        if user_input.lower() == "/quit":
            print("\n👋 대화를 종료합니다.")
            break

        # ----------------------------------------------------
        # History 출력
        # ----------------------------------------------------
        if user_input.lower() == "/history":
            print_history(messages)
            continue

        # ----------------------------------------------------
        # Summary 출력
        # ----------------------------------------------------
        if user_input.lower() == "/summary":
            print_summary(messages)
            continue

        # ----------------------------------------------------
        # Reset
        # ----------------------------------------------------
        if user_input.lower() == "/reset":
            messages = []
            print("\n🔄 모든 대화 History를 초기화했습니다.")
            continue

        # ----------------------------------------------------
        # USER 메시지 저장
        # ----------------------------------------------------
        messages.append(
            {
                "role": "user",
                "content": user_input
            }
        )

        # ----------------------------------------------------
        # History 길이 확인 및 압축
        # ----------------------------------------------------
        messages = compress_history(messages)

        # ----------------------------------------------------
        # AI 호출
        # ----------------------------------------------------
        try:
            print("\n🤖 AI가 생각하고 있습니다...")
            answer = chat_with_ai(messages)
        except Exception as error:
            print(f"\n❌ 오류 발생: {error}")
            continue

        # ----------------------------------------------------
        # AI 답변 출력
        # ----------------------------------------------------
        print(f"\n🤖 AI: {answer}")

        # ----------------------------------------------------
        # ASSISTANT 메시지 저장
        # ----------------------------------------------------
        messages.append(
            {
                "role": "assistant",
                "content": answer
            }
        )

        # ----------------------------------------------------
        # 현재 History 상태 출력
        # ----------------------------------------------------
        print(
            f"\n📦 현재 저장된 대화 수: "
            f"{len(messages)}"
        )

# ============================================================
# 11. 프로그램 시작
# ============================================================
if __name__ == "__main__":
    main()
  • CLI 챗봇의 전체 흐름을 담당합니다.
  • 사용자의 메시지가 들어올 때마다 [입력 저장 → 대화 수 검사 및 필요시 압축 → AI 답변 생성 → 답변 저장] 순서로 안전하게 동작합니다.

실행 방법

터미널에서:

python summary_chat.py
 

실행하면:

======================================================================
🤖 Summary Memory Chat
======================================================================

대화를 시작합니다.

사용 가능한 명령어

/history  → 현재 대화 이력 보기
/summary  → 현재 요약 보기
/reset    → 대화 초기화
/quit     → 종료
 

이렇게 나올 것입니다.


추천 테스트 방법

처음부터 어려운 질문을 하지 말고, AI가 기억해야 할 정보를 의도적으로 입력해 보세요.

예를 들어:

👤 YOU: 나는 김현정이고 AI Agent를 공부하고 있어.

👤 YOU: Python을 중심으로 공부하고 있어.

👤 YOU: 나는 코드 설명을 아주 자세하게 듣는 것을 좋아해.

👤 YOU: 특히 전체 흐름을 먼저 설명해주면 좋아.

👤 YOU: 앞으로 실제 업무에 사용할 AI Agent를 만들고 싶어.

👤 YOU: Tool Calling도 중요하게 생각하고 있어.
 

계속 입력하면 메시지가 10개를 넘게 되고 자동으로:

⚠️ History가 너무 길어졌습니다.
🧠 오래된 대화를 요약합니다...
 

가 나타납니다.

그리고 AI가 대략 이런 식으로 요약합니다.

사용자는 Python 기반 AI Agent를 학습 중이다.
코드 설명은 전체 흐름부터 자세하고 친절하게 듣는 것을 선호한다.
실제 업무에 사용할 AI Agent를 만들고 있으며 Tool Calling에 관심이 많다.
 

그 다음:

/summary
 

를 입력하면 현재 Agent가 기억하고 있는 압축된 과거 기억을 직접 확인할 수 있습니다.


여기서 아주 중요한 실험을 해보세요

대화가 요약된 후 다음 질문을 해보세요.

👤 YOU: 내가 코드를 설명받을 때 어떤 방식을 좋아한다고 했지?
 

AI가:

이전 대화에서 전체 흐름을 먼저 이해하고,
그 다음 각 코드의 역할을 자세하고 친절하게 설명받는 방식을
선호한다고 말씀하셨습니다.
 

라고 대답한다면 성공입니다.

즉:

원래 대화 삭제
        ↓
하지만
        ↓
핵심 정보는 요약
        ↓
AI가 계속 기억
 

하는 것이 바로 Summary Memory의 핵심입니다.


한 가지 중요한 관찰 포인트

현재 테스트 버전은 messages가 10개를 넘으면 압축합니다.

그런데 실제 실행 중에는 이런 흐름이 됩니다.

USER 1
AI 1

USER 2
AI 2

USER 3
AI 3

USER 4
AI 4

USER 5
AI 5

USER 6
        ↓
11개 메시지
        ↓
압축 발생
 

압축 후에는:

SYSTEM: 이전 대화 요약

USER
AI
USER
AI
 

처럼 메시지가 줄어듭니다.

반응형