Skip to content

[Week2] PostgresSaver + interrupt 기반 스킬 트리 선택 시스템#14

Open
Parkhaeil wants to merge 2 commits into
mainfrom
Parkhaeil/week2-state-memory
Open

[Week2] PostgresSaver + interrupt 기반 스킬 트리 선택 시스템#14
Parkhaeil wants to merge 2 commits into
mainfrom
Parkhaeil/week2-state-memory

Conversation

@Parkhaeil

Copy link
Copy Markdown
Collaborator

[Week 2] PostgresSaver 메모리 + interrupt 사용자 확인 흐름 추가

개요

도메인: 스타듀밸리 게임 가이드

1주차 ReAct Agent에 두 가지를 추가했습니다.

  1. PostgresSaver 체크포인터thread_id 기반 short-term memory 연결
  2. interrupt를 이용한 사용자 확인(컨펌) 흐름 추가

그래프 구조

START → agent ─[should_continue]─┬→ tools → agent (루프)
                                  ├→ commit_choice → format → END
                                  └→ format → END

interrupt_before=["commit_choice"]로 스킬 선택 대기 포인트를 명시했습니다.

주요 구현

1. PostgresSaver 체크포인터

thread_id별로 대화 상태를 Postgres에 저장합니다. 같은 thread_id로 이어 호출하면 이전 대화 맥락을 그대로 이어받고, 다른 thread_id로 호출하면 빈 상태에서 시작합니다.

2. interrupt + resume 패턴

스킬 트리 선택은 5레벨 선택이 10레벨 선택지 자체를 결정하는 비용 큰 결정입니다. LLM이 두 선택지를 비교 안내한 뒤 사용자가 직접 확인하도록 "추천 → interrupt → 컨펌" 패턴을 적용했습니다. (ASSIGNMENT.md의 "보고서 outline 생성 후 사용자 승인" 예시와 동일한 구조)

  • [AWAITING_SKILL_CHOICE] 시그널을 LLM 응답에 삽입
  • should_continue 라우터가 이를 감지하면 commit_choice로 분기
  • format 노드 출력 시 이 문자열을 제거해 사용자에게는 노출하지 않음

3. 커스텀 reducer upsert_skill_choices

스타듀밸리는 하수구에서 10,000g을 내고 직업을 재선택할 수 있습니다. 기본 add reducer(항상 추가)는 중복이 쌓이고 덮어쓰기는 이전 기록이 사라져 둘 다 맞지 않으므로, 같은 레벨이면 교체 / 없으면 추가하는 upsert를 직접 구현했습니다.

실행 방법

# Postgres 세팅 (최초 1회)
brew install postgresql@16
brew services start postgresql@16
createdb stardew_agent

# .env 설정
echo "POSTGRES_URI=postgresql://localhost:5432/stardew_agent" > assignments/Parkhaeil/week2/.env

# 노트북 실행
cd assignments/Parkhaeil/week2
jupyter notebook run.ipynb

테스트 시나리오

시나리오 1: thread_id 분리 — 같은/다른 thread의 실행 차이

호출 thread_id 입력 결과
1차 thread-A "내 이름은 민준이야. 기억해줘!" 이름 저장
2차 thread-A "내 이름이 뭐야?" "민준" — 이전 대화 이어받음
3차 thread-B "내 이름이 뭐야?" "알 수 없음" — 새 세션, skill_choices: []

Postgres에 thread별로 체크포인트가 저장되므로 thread_id가 곧 세션 키 역할을 합니다.

시나리오 2: interrupt 흐름

사용자: "농사 5렙인데 뭐 찍을지 고민이야"
봇: "목축업자(+20%) vs 경작인(+10%). 뭐할래?"  ← [AWAITING_SKILL_CHOICE] 포함
--- interrupt_before=["commit_choice"] 에서 일시정지 ---
사용자: "목축업자 할래"
봇: "저장했어요!"
skill_choices: [{"level": 5, "choice": "목축업자"}]

시나리오 3: 하수구 직업 변경 — upsert_skill_choices 검증

사용자: "사실 경작인으로 바꿨어"
→ upsert: level 5 항목을 경작인으로 교체
skill_choices: [{"level": 5, "choice": "경작인"}]  ← 리스트 길이 1 유지

실행 결과 요약

  • 시나리오 1: thread-A 2차 호출에서 "민준" 정상 기억. thread-B에서 "알 수 없음" 정상 응답, skill_choices: [] 초기화 확인.
  • 시나리오 2: snapshot.next = ('commit_choice',)로 interrupt 포인트 확인. resume 후 skill_choices: [{'level': 5, 'choice': '목축업자'}] 저장 확인.
  • 시나리오 3: 변경 후 skill_choices: [{'level': 5, 'choice': '경작인'}]으로 교체. 리스트 길이 1 유지(중복 추가 없음).

막힌 점 / 다음 주 개선할 점

막힌 점: commit_choice 노드에서 전체 대화 히스토리를 파싱하면 LLM이 이전 선택("목축업자")과 현재 선택("경작인")을 혼동해 잘못된 값을 추출하는 문제가 있었습니다. 최근 3개 메시지만 파싱하고 시스템 메시지에 "가장 최근 선택 기준"을 명시해 해결했습니다.

다음 주 개선: 스타듀밸리 위키 문서를 실제로 로드해 RAG 파이프라인으로 연결하고, 현재 mock 데이터 기반 도구를 벡터 스토어 검색으로 교체할 예정입니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants