체크포인터가 thread(대화방) 단위로 쌓는 대화 원문과, 우리 코드가 사용자 단위로 별도 테이블에 적는 기억은 서로 다른 저장소입니다. 새 대화방을 열면 원문은 따라오지 않고 기억만 system 프롬프트로 따라온다는 것을 네 개의 실습으로 한 단계씩 확인합니다. 사용자가 입력한 문장과 모델의 답, uuid 값은 (예시) 입니다.
| 대화 기록 (단기 기억) | 기억 (장기 기억) | |
|---|---|---|
| 단위 | thread (대화방) 하나. 열쇠는 config["configurable"]["thread_id"] | 사용자 한 명. 열쇠는 memories.user 열 (입력한 이름) |
| 내용 | 주고받은 메시지 원문 전체 (State 전체) | 골라낸 사실 몇 문장 |
| 저장하는 것 | 체크포인터(SqliteSaver)가 자동으로 | 우리 코드가 정한 시점에 (save_memory 도구, MemoryMiddleware) |
| 모델에게 가는 방식 | 같은 thread_id 로 invoke 하면 messages 로 다시 실려 감 | 시작할 때 읽어 system_prompt 문자열에 끼워 넣음 |
| 이 날의 저장소 | checkpoints.sqlite (기초), chat_memory.sqlite (대화 실습 셋) | memory.sqlite 의 memories (user, memory, created) |
| 새 대화방을 열면 | 따라오지 않음 (빈 방에서 시작) | 따라옴 (같은 이름이면) |
| 파일 | 체크포인터 파일 | thread_id 모양 | 기억 테이블 |
|---|---|---|---|
sqlite_chat.py | chat_memory.sqlite | chat_{이름} (이름마다 고정 → 다시 켜면 이어짐) | 쓰지 않음 |
memory_chat.py | chat_memory.sqlite | {이름}_{uuid 8자리} (실행마다 새 방) | 읽기: 시작 시 load_memories · 쓰기: save_memory 도구 |
auto_memory.py | chat_memory.sqlite | {이름}_{uuid 8자리} (실행마다 새 방) | 읽기: 시작 시 · 쓰기: MemoryMiddleware.before_model |
thread_id 모양이 달라 세 프로그램의 대화방은 섞이지 않고, 기억 테이블은 memory_chat.py 와 auto_memory.py 가 함께 씁니다. 한쪽에서 저장한 기억을 다른 쪽이 읽습니다.
테이블 하나와 함수 둘로 만든 이 기억 저장소는 7일차에 LangGraph 가 제공하는 Store 로 다시 만듭니다. 저장과 검색을 누가 대신해 주느냐가 달라질 뿐, 무엇을 언제 기억할지 정하는 숙제는 그대로 남습니다.
checkpointer_basic.py 를 다시 실행하면 user_A 의 1차 호출은 빈 대화가 아니라 지난 실행 뒤에 이어 붙습니다. 깨끗이 보려면 thread_id 를 바꾸거나 파일을 지웁니다.chat_{이름})과 그 기억을 이어받습니다. 실서비스에서는 로그인한 사용자 id 처럼 추측할 수 없는 값으로 만들어야 합니다.INSERT 라 같은 사실이 여러 번 쌓이고, 팀을 옮겼다고 해도 예전 기억은 지워지지 않습니다.mask_pii 를 거치게 하면 코드로 강제할 수 있습니다.llm.invoke 로 직접 부르는 별도 호출이라 대화 기록에는 남지 않습니다.checkpointer_basic.py: SqliteSaver 를 붙이고 같은 thread(user_A)와 다른 thread(user_B)에서 기억이 이어지는지 비교sqlite_chat.py: 이름으로 대화방(chat_{이름})을 나누는 터미널 챗. 기록은 chat_memory.sqlite 에 쌓임memory_chat.py: "기억해줘" 를 save_memory 도구로 memories 테이블에 저장하고, 실행마다 새 대화방을 열어 기억만 system 프롬프트로 불러오기auto_memory.py: MemoryMiddleware 가 사용자 말에서 기억 후보를 구조화 출력으로 뽑아 저장하고, 기억이 있으면 먼저 인사