← 소프트웨어와 제품
심층 리포트DEEPER INTO TECHNOLOGY

Spring AI 세션 메모리: 대화는 남기고 문맥만 줄이는 법

대화 원본을 보존하면서 AI에 보낼 문맥만 줄이려면 무엇을 저장하고 어떤 턴을 남겨야 할까?

댓글 남기기 ↓
먼저 읽는 핵심

대화가 길어지면 과거 기록을 저장하는 일과 다음 답변에 넣는 일이 갈라진다. Spring AI의 새 세션 메모리는 원본 이벤트를 남기고, 그중 모델이 읽을 활성 뷰를 바꾼다. 이 구조를 이해하면 도구 호출과 결과를 함께 보존하면서도 오래된 대화를 줄일 수 있다. 다만 요약에서 빠진 조건과 한 턴에 몰린 거대한 결과는 별도로 다뤄야 한다.

저장한 대화를 왜 다시 잊을까

사내 네트워크 장애를 돕는 챗봇을 만든다고 하자. 사용자는 처음에 “회의 중이라 공유기를 재부팅하면 안 된다”고 말했다. 이후 연결 상태를 여러 번 조회하고 로그를 붙여 넣었다. 한참 뒤 “이제 뭘 하면 되지?”라고 물었을 때, 챗봇이 재부팅을 권한다면 어디부터 봐야 할까.

데이터베이스에 첫 대화가 남아 있다는 사실만으로는 답할 수 없다. 다음 모델 호출에 그 조건이 들어갔는지, 요약에서는 어떻게 표현됐는지 확인해야 한다. 원본 보관과 현재 입력을 구별해야 “저장은 됐는데 기억하지 못하는” 현상을 설명할 수 있다.

Spring은 2026년 10월 23일 Spring AI 2.1.0-RC1과 2.0.2를 발표했다. RC에는 세션 메모리가 들어갔고, 기존 ChatMemory 계열에는 3.0.0에서 제거할 예정이라는 사용 중단 권고가 붙었다. 당장 삭제됐다는 뜻은 아니다. 발표는 일부 저장소의 대체 구현이 아직 예정 상태이고, 의미 검색을 이용하는 VectorStoreChatMemoryAdvisor는 유지한다고 구분한다. 공식 발표

이 글은 10월 25일 확인한 2.1.0-RC1 문서를 기준으로 한 설계 분석이다. 릴리스 커밋은 7782e72d4b8e4394e0a5cb6ee17315f24386f5aa다. 공식 문서가 안정판으로 안내하는 버전은 2.0.2이며, RC는 아직 안정 버전이 아니다. 아래 대화·요약·예산은 직접 작성한 가상 사례이고 Spring이나 모델을 실행해 얻은 결과가 아니다. 릴리스 · 세션 메모리 문서

로그와 뷰를 분리하면 바뀌는 것

새 세션 메모리에서 로그(log)는 추가되는 이벤트의 기록이다. 활성 뷰(active view)는 다음 문맥 구성에 사용할 기록의 투영이다. 압축은 과거 행을 지우고 요약으로 덮는 대신, 이후 뷰를 정의할 COMPACTION 이벤트를 추가한다. 원본에 남는 것과 모델에 보여 줄 것을 따로 관리하는 구조다. 세션 개념

예를 들어 1번부터 6번까지 대화가 있다면, 원본은 그대로 두고 현재 뷰만 “초반 요약 + 5·6번 대화”로 만들 수 있다. 나중에 요약이 잘못됐다고 의심하면 원본과 대조할 자료가 남는다. 그렇다고 요약 밖의 모든 사실을 모델이 계속 알고 있는 것은 아니다.

가상 여섯 턴의 원본 로그와 압축 후 활성 뷰. 원본의 1부터 6턴은 남고, 활성 뷰는 1부터 4턴의 설명용 요약과 5·6턴으로 구성된다. 재부팅 금지 조건이 요약에 남아야 다음 답변에 전달된다.
직접 그린 설명도. 실제 저장소 화면이 아니다. 요약과 보존 범위는 아래 사례를 위해 정했다.

이 구분은 조회 API에도 드러난다. EventFilter.all()은 원본 전체를 읽는 쪽이고 EventFilter.active()는 활성 뷰를 읽는 쪽이다. 읽기 필터에서 제외한 이벤트는 원본에서 삭제되지 않는다. 따라서 관리자에게 보여 줄 전체 이력과 다음 모델 호출에 넣을 대화를 같은 조회로 처리할 필요가 없다. 이벤트 필터

세션에는 메시지 외의 사용자 정의 이벤트도 넣을 수 있다. 하지만 사용자 정의 상태를 기록했다고 자동으로 모델 메시지가 되지는 않는다. 예컨대 앱이 SUPPORT.CHECK_STARTED를 남기는 것과 모델에 “연결 검사를 시작했다”고 전달하는 것은 별도 설계다. 공식 문서는 메시지 이벤트가 문맥 구성·압축에 참여한다고 설명한다. 이벤트 유형

마지막 몇 줄 대신, 끝난 대화 단위로 자른다

여기서 턴(turn)은 사용자 메시지 하나와 다음 사용자 메시지 직전까지의 응답·도구 호출·결과를 묶은 단위다. 사용자가 한 번 물었어도 그 안에서 모델과 도구가 여러 차례 왕복할 수 있다. 턴 정의

아래는 가상 장애 지원 대화다. U는 사용자, A는 모델, T는 도구 결과이며 한 칸은 메시지 하나다. 실제 장비를 검사하거나 재설정하지 않았다.

턴 작성한 메시지 흐름 다음 답변에 필요한 정보
1 U: 회의 중, 재부팅 금지 → A: 조건 확인 회의 종료 전 재부팅하지 않는 제약
2 U: 유선은 되는가 → A: 상태 조회 → T: 유선 정상 → A: 결과 설명 유선 정상이라는 과거 관측
3 U: 무선은 끊긴다 → A: 추가 질문 무선에서만 보인다는 사용자 진술
4 U: 새 설정은 바꾼 적 없다 → A: 확인 변경 이력에 대한 사용자 진술
5 U: 최근 오류를 확인해 줘 → A: 로그 조회 → T: 인증 실패 → A: 결과 설명 도구로 읽은 오류와 확인 시점
6 U: 이제 뭘 하면 되지 → A: 다음 점검 제안 현재 질문과 답변

이 기록의 마지막 5개 메시지를 기계적으로 자르면 5턴의 도구 호출부터 시작한다. 사용자 질문은 빠진 채 호출·결과·응답과 6턴이 남는다. 호출과 결과가 둘 다 남더라도, 어떤 질문을 해결하려던 흐름인지 잃는 예다.

문서에 나온 WindowCompactionStrategy는 이런 중간 절단을 피하도록 턴 경계에서 자른다. 위와 같은 위치에 lastEvents(5)의 절단점이 걸리면 다음 턴 시작으로 이동하므로 6턴만 남기는 결과를 예상할 수 있다. 반면 lastTurns(2)라는 설계 선택은 5·6턴을 함께 남긴다. 이는 문서 규칙을 이 가상 배열에 적용한 설명이며 RC 실행 결과가 아니다. 압축의 절단 규칙

여기서 더 중요한 질문이 나온다. 5·6턴을 온전히 남겨도 1턴의 재부팅 금지 조건은 빠진다. 형식적으로 완결된 대화와 업무에 충분한 문맥은 별개다. 턴 보존은 도구 왕복의 형태를 지켜 주지만, 오래된 제약의 중요성을 대신 판단하지 않는다.

요약에는 결론보다 제약과 미해결 상태를 남긴다

이 사례에서 “무선 문제를 조사 중”만 남긴 요약은 부족하다. 다음처럼 읽을 수 있어야 이후 제안이 앞선 조건을 거스르지 않는다. 아래는 설명을 위해 작성한 요약 예시이며, 제품 기본 프롬프트나 Spring의 요약기를 실행해 얻은 출력이 아니다.

작업: 회의 중 발생한 무선 연결 장애의 비파괴 점검
유효한 제약: 회의 종료 확인 전 공유기 재부팅 금지 (턴1)
확인한 사실: 당시 상태 조회에서 유선 정상 (턴2)
사용자 진술: 무선 연결 끊김, 최근 설정 변경 없음 (턴3·4)
아직 모르는 것: 회의 종료 여부, 인증 실패의 원인
다음 질문: 회의가 끝났는지 확인하고 현재 상태를 다시 점검

“확인한 사실”과 “사용자 진술”을 나누면 요약이 원래 근거보다 강한 결론으로 바뀌는 일을 줄일 수 있다. 유선 정상도 영구 상태가 아니므로 “당시”라는 시점을 붙였다. 재부팅 금지에는 해제 조건을 붙여, 다음날에도 무조건 금지하는 낡은 기억으로 남지 않게 했다.

이 양식을 모든 챗봇에 고정할 필요는 없다. 글쓰기 도우미라면 문체 선택과 미결정 문단이 중요하고, 장바구니 도우미라면 옵션·수량·아직 확인하지 않은 배송 조건이 중요하다. 다음 행동을 바꿀 수 있는 과거 정보를 찾아 요약의 인수 기준으로 삼으면 된다.

검사는 “짧게 줄었는가”보다 반례를 넣는 편이 유용하다. 같은 대화 뒤에 “바로 재부팅해도 돼?”를 묻는다면 회의 종료가 확인되지 않았음을 짚어야 한다. 다음 입력이 “회의는 끝났어”라면 제약의 해제 근거를 갱신해야 한다. 이 두 답을 실제 모델에서 관측하기 전에는 요약 품질이 검증됐다고 말할 수 없다.

토큰 예산이 있어도 최신 턴은 넘칠 수 있다

압축에는 언제 실행할지 정하는 trigger와 무엇을 남길지 정하는 strategy가 있다. 최근 턴 수, 이벤트 수, 추정 토큰 예산은 서로 다른 기준이다. 요약기를 붙일 수도 있지만 단순 창만 사용하면 요약은 자동 생성되지 않는다. 압축 설정

특히 문서는 최신 턴을 보존하며, 그 턴 하나가 창보다 커도 남긴다고 설명한다. 따라서 tokenBudget을 모든 요청의 강제 입력 상한으로 읽으면 안 된다. ChatClient 통합의 압축도 호출이 돌아온 뒤 수행되므로, 한 번의 긴 도구 반복 안에서 이미 생긴 거대 입력을 미리 막는 장치로 볼 수 없다. 압축 예외 · 호출과 압축 순서

다음 숫자는 토크나이저 측정이 아닌 가상 입력 예산이다. 공급자의 요금이나 토큰 한도가 아니다.

구성 가정한 크기 해석
앱의 지시·도구 정의·이번 질문 등 2,000 과거 대화 외에 예약할 입력
초반 요약 600 요약도 입력 공간을 사용
보존할 최근 두 턴 2,400 과거 입력을 줄인 뒤 남길 대화
합계 5,000 전체 목표 8,000보다 3,000 작음

이번 질문의 응답 후, 새 최신 턴 안에 도구 로그 9,000단위가 추가됐다고 가정하자. 다른 과거 대화를 모두 빼도 최신 턴만으로 과거 문맥 예산 6,000을 넘는다. 이때 필요한 것은 압축 횟수를 더 늘리는 일만이 아니다. 로그 도구가 관련 구간과 참조 ID를 반환하도록 바꾸거나, 필요한 범위를 나눠 조회하는 등 도구 출력부터 줄여야 한다.

실제 예산을 잡을 때는 모델별 토크나이저와 출력 공간, 도구 정의, 요약 길이까지 확인해야 한다. 이 표의 5,000이 실제 API에서 같은 토큰 수로 청구된다거나 응답 시간이 줄었다는 뜻은 아니다. 여기서 확인한 것은 합산과 초과 조건뿐이다.

앱에 붙일 때의 책임은 어디에 있을까

Spring의 통합은 두 advisor를 묶는다. SessionMemoryAdvisor는 도구 반복 내부의 모델 호출에서 이력을 읽고 메시지를 저장한다. SessionTurnAdvisor는 바깥에서 턴이 끝난 뒤의 처리와 압축을 맡는다. 문서는 전자만 붙이면 압축이 없고 returnDirect 도구 결과도 저장하지 않는다고 구분한다. 필요한 전체 통합은 SessionAdvisors를 기준으로 확인할 수 있다. ChatClient 통합

매 호출에는 세션 ID가 필요하다. 기본 세션으로 빠지는 동작은 없으며, 처음 사용하는 ID의 세션을 생성하는 경로가 있다. 저장소는 테스트용 메모리 구현과 JDBC 구현이 구분된다. 메모리 구현은 재시작 때 사라지고 인스턴스 사이에 공유되지 않는다. 시작 구성과 세션 ID

여기서 앱이 정할 것은 “어떤 대화가 같은 세션인가”다. 같은 사용자의 네트워크 장애 상담과 휴가 일정 상담을 무조건 합칠 이유는 없다. 반대로 브라우저를 새로 열었다고 해결하지 못한 장애 맥락까지 잃어서는 안 된다. 로그인 사용자에게 허용된 상담 ID를 조회해 연결하는 정책은 애플리케이션에서 설계해야 한다. ID가 있다는 것 자체를 접근 권한 검사로 취급하지 않는다.

현재 문서의 JDBC 기본 지원은 PostgreSQL·MySQL·MariaDB·H2다. 이벤트 ID에 기반한 중복 추가 방지와 버전을 확인하는 압축 쓰기를 설명하지만, 이것을 “도구가 업무를 정확히 한 번 실행한다”는 보장으로 확대할 수는 없다. 저장을 재시도하는 일과 외부 시스템에서 재설정·예약·발송을 실행하는 일은 다른 작업이다. JDBC 저장소

네트워크 상담 사례라면 로그 저장 성공과 실제 장비 변경 성공을 별도로 남겨야 한다. 이후 변경 작업까지 붙일 때의 전달·중복 문제는 트랜잭셔널 아웃박스 해설로 이어서 볼 수 있다.

기존 ChatMemory를 언제 옮길까

새 API의 이름만 바꾸기 전에 현재 앱이 필요한 성질을 적어 보자. 다음은 공식 호환성 보증이 아닌 이 글의 이전 판단 기준이다.

현재 앱 먼저 확인할 것 판단
짧은 질의응답, 안정판 운영 우선 기존 메모리로 실패하는 구체적 사례가 있는가 RC 도입을 서두를 이유가 약함
긴 상담과 여러 도구 왕복 원본·활성 뷰를 따로 읽고 재현할 필요가 있는가 새 구조를 격리 환경에서 평가할 가치가 큼
Redis·MongoDB 등 기존 저장소 사용 현재 RC에 대응 저장소가 있는가, 직접 구현 범위는 얼마인가 발표의 이전 계획만으로 교체하지 않음
과거 상담의 의미 검색이 핵심 현재 대화 압축과 세션 간 검색을 어떻게 조합할까 세션 API만으로 검색 기능을 대체하지 않음

평가할 때는 위의 가상 대화를 자기 업무에 맞게 바꿔 쓸 수 있다. 압축 전후에 같은 원본 이벤트가 남는지, 제약이 요약에 유지되는지, 도구 왕복 중간에서 뷰가 시작하지 않는지 확인한다. 이어 최신 턴 하나에 큰 도구 결과를 넣고 입력이 어떻게 달라지는지 관측한다. 재시작 뒤 같은 세션을 읽는 시험은 실제 사용할 저장소에서 별도로 한다. 모두 도입 전에 수행할 검사 제안이며 이 글에서 통과한 실행 결과가 아니다.

세션 압축의 결과물은 줄어든 입력만이 아니다. 왜 어떤 과거 정보가 다음 답변에 전달됐는지 설명할 수 있는 구조도 남는다. 원본 로그, 현재 뷰, 수동으로 정한 필수 조건을 함께 비교하면 어느 단계에서 정보가 사라졌는지 찾을 수 있다. 외부 문서 검색까지 함께 쓴다면 긴 문맥과 RAG의 역할 구분을 이어 읽으면 된다. 이 글을 저장해 실제 대화의 압축 전후를 대조하는 기준으로 활용할 수 있다.

다시 읽을 때의 기준
  • 원본 로그는 있었던 일을 보관하고 활성 뷰는 다음 모델 입력을 구성한다. 압축으로 뷰에서 빠진 내용도 로그에는 남지만 자동으로 다시 읽히지는 않는다.
  • 턴은 사용자 질문부터 도구 왕복과 답변까지 묶는다. 최신 턴이 혼자 예산을 넘으면 보존되므로, 압축 설정만으로 모든 입력 크기를 제한할 수 없다.
  • 2.1.0-RC1은 안정판이 아니다. ChatMemory의 사용 중단 권고와 실제 제거 시점을 구분하고, 저장소 지원·요약 누락·재시작 동작을 따로 확인한다.
SOURCES & CONTEXT

근거와 원자료

자료의 발행 시점과 이번 글에서 참고한 범위를 함께 기록합니다.

  1. Spring AI 2.1.0-RC1 and 2.0.2 Available Now ↗Spring / spring-projects · 2026

    10-23 RC와2.0.2 발표; ChatMemory deprecation/removal 계획과 아직 없는 대체 저장소. 직접 런타임 실행 아님.

  2. Session Memory ↗Spring / spring-projects · 2026

    2.1.0-RC1 preview; log/view, 필수 세션ID, 메모리 저장소 한계.

  3. Session Concepts ↗Spring / spring-projects · 2026

    이벤트·턴·활성 뷰와 삭제 구별. 업무 승인/정확히 한 번 실행 보장 아님.

  4. Compaction ↗Spring / spring-projects · 2026

    trigger/strategy, 경계 절단·최신 턴 초과 예외·요약. RC 문서; 실행 결과가 아님.

  5. Event Filtering ↗Spring / spring-projects · 2026

    전체/활성 뷰 조회와 읽기 필터. compaction 절단과 lastN 읽기의 차이를 구분.

  6. JDBC Repository ↗Spring / spring-projects · 2026

    네 DB 지원, event ID 기반 멱등 append와 version checked append. 외부 부작용 보장 아님.

  7. ChatClient Integration ↗Spring / spring-projects · 2026

    두 advisor의 역할, returnDirect 보존, 호출 종료 뒤 압축.

  8. Spring AI v2.1.0-RC1 release ↗Spring / spring-projects · 2026

    10-23 prerelease; commit7782e72d4b8e4394e0a5cb6ee17315f24386f5aa. 라이브 문서가 앞으로 고정된다는 뜻 아님.

갱신 기록

첫 원고. 공식 RC 문서와 릴리스 커밋을 대조하고 가상 대화의 턴 선택·예산 산술을 검사했다. Spring·JDBC·모델 실행과 실제 비용 효과는 검증하지 않았다.

오류를 발견했다면 댓글로 알려 주세요 →
S
Starhunter

테크독해 운영·편집. AI·IT의 개념과 기술을 연결해 읽습니다.

QUESTIONS & DISCUSSION

이 글에 댓글 남기기

댓글 공개 방식을 확인하고 있습니다. 개인정보나 비밀 값은 적지 마세요.

댓글을 불러오는 중입니다.

    새 댓글

    8자 이상. 비밀번호를 잊으면 직접 수정·삭제할 수 없습니다. 다른 서비스와 다른 비밀번호를 사용해 주세요.

    내 댓글 확인·수정·삭제

    등록 후 안내된 댓글 번호와 비밀번호를 입력하세요. 승인 대기 중인 댓글도 확인할 수 있습니다.