ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 이코에코(Eco²) Agent: Multi-turn 대화 E2E 검증 완료
    이코에코(Eco²)/Agent 2026. 1. 19. 11:29

    검증 일시: 2026-01-19 03:05 UTC
    검증 환경: k8s-master (13.209.44.249)
    관련 PR: #434 (fix: use redis_streams for ProgressNotifier and DomainEventBus)
    검증 결과: ✅ PASS


    1. 검증 개요

    1.1 검증 목적

    LangGraph 체크포인터(PostgreSQL + Redis Cache-Aside)를 통한 멀티턴 대화 상태 영속화 및 맥락 유지 검증.

    1.2 핵심 검증 항목

    항목 설명 결과
    세션 상태 영속화 동일 session_id로 여러 메시지 처리 시 상태 유지 ✅ Pass
    체크포인트 누적 turn마다 step이 연속적으로 증가 ✅ Pass
    intent_history 누적 이전 대화의 intent가 히스토리에 누적 ✅ Pass
    맥락 유지 이전 턴의 대화 내용을 참조하여 응답 ✅ Pass
    Redis Streams 이벤트 각 turn의 이벤트가 정상 발행 ✅ Pass

    2. 데이터 검증 (3 Turns)

    세션 ID: 6a87f182-9599-498a-a519-fab2002f3c6a

    2.1 Turn별 체크포인트 분포

    Turn job_id Shard min_step max_step Checkpoints
    1 199887cb-... 1 -1 6 8
    2 fdcd9d16-... 3 7 14 8
    3 ff6dc3bd-... 3 15 22 8

    총 24개 체크포인트, step이 -1 → 22까지 연속 증가

    2.2 intent_history Blob 크기 변화

    Turn Version Blob Size 내용
    1 3-4 7 bytes ["waste"]
    2 11-12 13 bytes ["waste", "waste"]
    3 19-20 19 bytes ["waste", "waste", "waste"]

    2.3 채널별 Blob 현황

    channel blob_count total_size
    __pregel_tasks 6 10,150
    disposal_rules 5 6,141
    weather_context 5 930
    __start__ 3 629
    decomposed_queries 6 214
    intent_history 6 78

    2.4 Redis Streams Shard 분포

    Shard Events Turn
    Shard 1 20 Turn 1
    Shard 3 40 Turn 2, 3

    3. 맥락 유지 검증 (5 Turns)

    세션 ID: 04d4c54c-74dd-4271-8c92-5ce781d00b6a
    검증 목적: 맥락 의존적 대화에서 이전 턴의 컨텍스트가 유지되는지 검증

    3.1 테스트 시나리오

    페트병 분리배출 주제로 맥락이 이어지는 5턴 대화:

    Turn 메시지 맥락 의존성
    1 "페트병 어떻게 버려?" 기본 질문
    2 "라벨은 어떻게 해?" 페트병 맥락 필요
    3 "뚜껑도 분리해야 해?" 페트병 맥락 유지
    4 "유색 페트병은 다르게 버려?" 페트병+색상 확장
    5 "지금까지 알려준 거 정리해줘" 전체 맥락 요약

    3.2 Turn별 대화 내용

    Turn 1: 페트병 어떻게 버려?

    User: 페트병 어떻게 버려?

    Assistant (intent: waste):

    무색 **페트병(먹는샘물/음료/주스병)**이면 **투명 페트병 전용 수거함(무색페트)**에 버리면 돼! ♻️
    
    배출은 이렇게 해줘:
    - **라벨 떼기 → 내용물 비우기 → 물로 헹구기 → 납작하게 압착 → 뚜껑 닫아서** 배출
    
    참고로 **유색 페트병**이나 **음료·생수병이 아닌 페트 용기(식품용기, 컵, 워셔액 용기 등)**는
    **플라스틱류**로 버려야 해.

    Turn 2: 라벨은 어떻게 해?

    User: 라벨은 어떻게 해?

    Assistant (intent: waste):

    라벨은 보통 **떼서 따로 버리는 게 정답**이야 ♻️
    - **종이 라벨**이면: 잘 떼어서 **종이류**로 (찢어지면 일반쓰레기인 경우도 있어)
    - **비닐/플라스틱 라벨**이면: **비닐류**로 분리배출

    💡 맥락 유지 확인: "라벨"만 언급했지만 페트병 라벨로 이해하고 응답

    Turn 3: 뚜껑도 분리해야 해?

    User: 뚜껑도 분리해야 해?

    Assistant (intent: waste):

    응, **대부분은 뚜껑도 분리하는 게 좋아!** ♻️
    보통 병(페트/유리) 몸통이랑 뚜껑 재질이 달라서
    **몸통은 해당 수거함**, **뚜껑은 플라스틱류로 따로** 배출해주면 더 좋아.

    💡 맥락 유지 확인: "뚜껑"만 언급했지만 페트병 뚜껑으로 이해

    Turn 4: 유색 페트병은 다르게 버려?

    User: 유색 페트병은 다르게 버려?

    Assistant (intent: waste):

    응, **유색 페트병(초록/갈색/파란색 등)**은 무색 투명페트병이랑 달라서
    **"플라스틱류"로 배출**하면 돼! ♻️
    무색 투명페트병만 따로 모아서 전용 수거함(무색페트병)으로 배출해야 해.

    💡 맥락 유지 확인: Turn 1에서 언급한 유색 페트병 내용과 연결

    Turn 5: 지금까지 알려준 거 정리해줘

    User: 지금까지 알려준 거 정리해줘

    Assistant (intent: general):

    좋아, 지금까지 내용 **무색 페트병(먹는샘물·음료 페트병)** 기준으로 깔끔하게 정리해줄게 ♻️
    
    - **대상:** 먹는샘물/음료/주스 같은 **무색 PET 페트병**
    - **배출 방법:** **라벨 제거 → 내용물 비우기 → 물로 헹구기 → 눌러서 압착 → 뚜껑 닫아 배출**
    - **주의(이건 무색페트병으로 X):** **유색 페트병**, 페트 재질의 다른 용기/식품용기/워셔액 용기/컵,
      먹는샘물·음료 외 플라스틱 용기는 **"플라스틱류"로 배출**해야 해
    
    원하면 사진이나 제품 이름 보내줘도 "이게 무색페트병 맞는지" 같이 확인해줄게!

    ✅ 맥락 유지 검증 완료: 이전 4개 턴의 내용(배출 방법, 라벨, 뚜껑, 유색 페트병)을 모두 포함하여 정리

    3.3 데이터 검증 (5 Turns)

    Turn job_id Shard min_step max_step Intent
    1 29fddf24-... 0 -1 6 waste
    2 dcf654da-... 1 7 14 waste
    3 345f4bb5-... 0 15 22 waste
    4 f9ec98c6-... 0 23 30 waste
    5 0e4d8084-... 1 31 38 general

    총 40개 체크포인트, step이 -1 → 38까지 연속 증가

    3.4 intent_history Blob 크기 변화 (5 Turns)

    Turn Blob Size 추정 내용
    1 7 bytes ["waste"]
    2 13 bytes ["waste", "waste"]
    3 19 bytes ["waste", "waste", "waste"]
    4 25 bytes ["waste", "waste", "waste", "waste"]
    5 33 bytes ["waste", "waste", "waste", "waste", "general"]

    4. 멀티턴 상태 흐름

    ┌─────────────────────────────────────────────────────────────────────────────┐
    │                    Multi-Turn Conversation State Flow                        │
    ├─────────────────────────────────────────────────────────────────────────────┤
    │                                                                              │
    │  Turn 1: "페트병 어떻게 버려?"                                               │
    │  ├── intent: waste, steps: -1 → 6                                           │
    │  └── PostgreSQL checkpoint saved ────────────┐                              │
    │                                               │                              │
    │                                               ▼                              │
    │  Turn 2: "라벨은 어떻게 해?"                                                 │
    │  ├── intent: waste, steps: 7 → 14                                           │
    │  ├── intent_history: ["waste", "waste"]                                     │
    │  └── 맥락: 페트병 라벨로 이해 ◄────────────────┤                            │
    │                                               │                              │
    │                                               ▼                              │
    │  Turn 3: "뚜껑도 분리해야 해?"                                               │
    │  ├── intent: waste, steps: 15 → 22                                          │
    │  └── 맥락: 페트병 뚜껑으로 이해 ◄──────────────┤                            │
    │                                               │                              │
    │                                               ▼                              │
    │  Turn 4: "유색 페트병은 다르게 버려?"                                        │
    │  ├── intent: waste, steps: 23 → 30                                          │
    │  └── 맥락: Turn 1 유색 페트병 언급과 연결 ◄────┤                            │
    │                                               │                              │
    │                                               ▼                              │
    │  Turn 5: "지금까지 알려준 거 정리해줘"                                       │
    │  ├── intent: general, steps: 31 → 38                                        │
    │  └── 맥락: Turn 1-4 전체 내용 요약 ◄───────────┘                            │
    │                                                                              │
    └─────────────────────────────────────────────────────────────────────────────┘

    5. SSE Event Flow

    5.1 Turn 5 이벤트 시퀀스

    queued (started) → intent (general) → general → aggregator → answer → done
                            ↑                ↑
                       맥락 기반 분류    이전 대화 참조하여 응답 생성

    5.2 done 이벤트 상세 (Turn 5)


     

    6. 결론

    6.1 검증 완료 항목

    항목 상태 근거
    세션 상태 영속화 ✅ Pass 동일 thread_id로 40개 체크포인트 누적
    체크포인트 연속성 ✅ Pass step이 -1 → 38까지 연속 증가
    intent_history 누적 ✅ Pass blob 크기 7 → 13 → 19 → 25 → 33 bytes 증가
    맥락 유지 ✅ Pass Turn 5에서 이전 4개 턴 내용 정확히 요약
    Redis Streams 발행 ✅ Pass 5개 turn 모두 이벤트 발행 확인

    6.2 주요 설정

    설정 위치
    CHAT_SHARD_COUNT 4 configmap.yaml
    enable_dynamic_routing True dependencies.py
    cache_ttl 86400 (24h) checkpointer.py
    PostgreSQL Checkpointer Cache-Aside Pattern checkpointer.py

    부록: 테스트 환경

    Cluster: k8s-master (13.209.44.249)
    Namespace: chat
    Deployment: chat-worker
    PostgreSQL: dev-postgresql-0 (postgres namespace)
    Redis Streams: rfr-streams-redis-0 (redis namespace)
    Redis Pub/Sub: rfr-pubsub-redis-0 (redis namespace)

    댓글

ABOUT ME

🎓 부산대학교 정보컴퓨터공학과 학사: 2017.03 - 2023.08
☁️ Rakuten Symphony Jr. Cloud Engineer: 2024.12.09 - 2025.08.31
🏆 2025 AI 새싹톤 우수상 수상: 2025.10.30 - 2025.12.02
🌏 이코에코(Eco²) 백엔드/인프라 고도화 중: 2025.12 - Present

Designed by Mango