다중 턴 대화에서 왜 이력을 그냥 밀어 넣으면 안 되나
← 전체 목차 · 이전: 컨텍스트 윈도우는 창고가 아니라 주의력 예산이다 · 다음: 접두사 캐시와 시맨틱 캐시의 차이
"컨텍스트 윈도우가 부족해서"는 한 겹만 답한 것이다. 진짜 이유는 정보 품질 문제다 — 윈도우에 담긴다 해도 정보 품질이 모델을 끌어내린다.
대화가 길어질수록 이력에는 세 종류의 쓰레기가 쌓인다.
| 축적되는 노이즈 | 예시 |
|---|
| 만료된 정보 | 사용자가 앞서 말한 예산·시간·선호가 이미 바뀌었다 |
| 무관한 디테일 | 잡담, 인사, 반복 확인 |
| 초기의 오류 | 이미 뒤집히거나 폐기된 방안 |
결과: 모델의 판단을 심하게 교란하며, 구체적으로는 환각 증가, 논리 혼란, 주제 이탈로 나타난다.
결론: 기계적 접합이 아니라 이력 압축과 상태 추출
- 보존해야 할 것: 사용자가 말한 선호, 제약, 이미 확인된 사실.
- 다음 라운드 프롬프트에 반드시 들어갈 필요가 없는 것: 잡담, 반복 확인, 이미 폐기된 방안.
- 걸러내지 않을 때의 대가: 모델이 낡은 정보 더미 속에서 길을 잃는다.
핵심 방안: 이력을 3층으로
| 계층 | 내용 | 처리 방식 | 목적 |
|---|
| 1층 | 현재 턴의 컨텍스트 | 원문 보존, 압축하지 않음 | 이번 턴 지시의 정확성 보장 |
| 2층 | 장기 유효 상태 | 구조화 추출: 사용자 목표, 제약 조건, 선택된 방안 | 턴을 넘어 지속 유효한 "사실 기반" |
| 3층 | 이력 요약 | 요약으로 압축 | 배경을 남기되 token을 많이 쓰지 않는다 |
이 구조가 전량 이력보다 명확하다.
충돌 정보 처리: 가장 간과되는 한 고리
- 사용자가 앞에서 예산: 10,000이라 했다가 뒤에서 예산: 20,000으로 바꿨다.
- 잘못된 방식: 두 숫자를 모두 모델에게 주고 어느 것이 유효한지 스스로 추측하게 한다.
- 올바른 방식: 가장 최근에 확인된 상태를 명시적으로 적고(
예산: 20,000 [최신 확인]), 옛 값은 실효로 표기한다(예산: 10,000 [실효], 취소선).
- 결과: 모델이 상태를 정확히 이해할 수 있다.
이 일련의 방식을 대화 상태 관리라고 부른다.
답변 템플릿
다중 턴 대화는 이력 전체를 계속 이어 붙이면 안 되고, 정보 선별·요약 압축·상태 추출·충돌 갱신을 해야 한다.
모델이 필요로 하는 것은 현재 유효한 컨텍스트이지 완전한 채팅 흐름 기록이 아니다.
구현 절차
매 라운드 대화 시작, prompt 구성 이전에:
① 정보 선별(Filter)
보존: 선호 / 제약 / 이미 확인된 사실
폐기: 잡담 / 반복 확인 / 이미 폐기된 방안
② 충돌 갱신(Reconcile)
상태 필드가 사용자에 의해 다시 쓰였는지 스캔
다시 쓰였다면 → 새 값에 [최신 확인], 옛 값에 [실효] 표기
금지: 충돌하는 두 값을 함께 모델에 보내는 것
③ 계층 조립(Compose)
┌──────────────────────────────────┐
│ 3층: 이력 요약(압축, token 절약) │ ← 배경
├──────────────────────────────────┤
│ 2층: 장기 유효 상태(구조화) │ ← 사용자 목표 / 제약 조건 / 선택된 방안
├──────────────────────────────────┤
│ 1층: 현재 턴 컨텍스트(원문 보존) │ ← 이번 턴 지시, 압축 금지
└──────────────────────────────────┘
④ 모델에 투입
전달하는 것은 「현재 유효한 컨텍스트」이지 「완전한 채팅 흐름」이 아니다
핵심 통찰
- "담긴다"와 "담아야 한다"는 다르다 — 컨텍스트 윈도우는 용량 제약이고 정보 간섭은 품질 제약이다. 전자는 롱컨텍스트 모델로 완화할 수 있지만 후자는 선별하지 않으면 영원히 존재한다. 둘을 뭉뚱그리는 것이 이 문제의 가장 흔한 실점 지점이다.
- 이력은 "로그"가 아니라 "상태"로 다뤄야 한다 — 채팅 기록은 흐름(append-only log)이고 모델이 필요로 하는 것은 현재 유효 상태(materialized state)다. 둘 사이에는 명시적인 "투영/축약" 과정이 있어야 하며, 그 과정이 곧 대화 상태 관리다.
- 계층화의 본질은 "감쇠 속도"별 버킷 분리다 — 현재 턴 컨텍스트는 변화가 가장 빠르고(정확해야 하므로 원문 보존), 장기 상태는 가장 느리며(구조화 저장), 배경 정보는 거의 변하지 않는다(요약). 시간 순서가 아니라 정보의 유효 기간으로 이력을 조직하는 것은 Agent 기억, RAG 세션, 장시간 작업 재개에 그대로 이식되는 범용 방법이다.
- 모순된 정보가 누락된 정보보다 위험하다 — 정보가 없으면 모델은 추가 질문을 하지만, 모순이 있으면 모델은 스스로 추측하고 게다가 확신에 차 있다. 따라서 시스템이 재정권을 떠맡아야 하며 충돌을 모델에게 떠넘겨서는 안 된다.
- "실효로 표기"가 "그냥 삭제"보다 낫다 — 옛 값을 남기고 실효를 명시하면 모델의 오용을 막으면서 변경 궤적도 보존한다(사용자가 "내가 아까 말한 게 얼마였죠?"라고 물어도 답할 수 있다).
- Token 절약은 계층 압축의 직접적 수익이지만 첫 번째 동기는 아니다 — 3층 요약이 "배경은 남기되 token을 많이 쓰지 않는" 것은 사실이지만, 이 설계의 첫 목적은 노이즈 제거다. 노이즈 제거를 부산물로 여기는 팀은 돈을 아끼려고 1층까지 압축해 오히려 더 나빠지곤 한다.
- 트러블슈팅 차원이 계층 구조에서 그대로 도출된다 — "이력이 너무 길다 / 요약이 부정확하다 / 상태가 갱신되지 않았다"는 세 진단이 정확히 3층의 세 가지 실효 모드에 대응한다. 좋은 추상은 관측 가능성의 구획을 자체적으로 갖는다.
엔지니어링 실전 Tips
- 장기 상태를 명시적 스키마로 만들 것(예:
{목표, 제약 조건, 선택된 방안, 예산, 마감일}): 모델이 매 턴 자연어에서 다시 읽어내게 하지 말 것. 구조화 필드라야 충돌 감지를 신뢰성 있게 할 수 있다.
- 요약에 트리거 임계값을 둘 것: 매 턴 다시 요약하지 말 것(비싸고 요약 드리프트가 생긴다). 이력이 N턴 또는 M token을 넘으면 한 번 증분 요약하는 것이 일반적이다.
- 요약의 부정확은 관측 가능해야 한다: 요약 내용을 보관해 두면 문제가 생겼을 때 "요약이 핵심 제약을 잃었는지"인지 "상태가 갱신되지 않았는지"인지 특정할 수 있다.
- 1층은 절대 압축하지 말 것: 이번 턴의 사용자 지시가 요약되면 지시 대상("위의 그것", "아까 말한 두 번째")이 즉시 무효가 된다.
- 충돌 재정 규칙은 코드에 하드코딩할 것: 프롬프트로 모델에게 누가 새것인지 판단하게 하지 말 것. 모델의 시간 선후 판단은 신뢰할 수 없다.
- 접두사 캐시와의 충돌에 주의할 것: 3층 구조에서 3층 요약과 2층 상태는 자주 변한다. 이것들을 프롬프트 가장 앞에 두면 접두사 캐시가 깨지므로, 안정적인 시스템 프롬프트가 가장 앞에 와야 한다(문서 11 참조).