ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 로컬 챗봇 시리즈 #1 — 메시지 편집은 왜 그렇게 단순해야 하나: 컨텍스트 엔지니어링 관점에서
    IT 2026. 5. 8. 21:00
    로컬 챗봇 시리즈 #1 — 메시지 편집은 왜 그렇게 단순해야 하나: 컨텍스트 엔지니어링 관점에서

    들어가며 — 메시지 편집이 가장 재미있는 기능이었다

    집에 띄워둔 로컬 LLM 챗봇에 ChatGPT/Claude.ai 수준 UX를 한 묶음으로 채워넣었다. 풀텍스트 검색, Custom Instructions, Cmd-K 팔레트, 대화 export. 다 들어가니 챗봇이 "쓸 만한" 인상을 풍긴다.

    그중에서 가장 단순해 보이는데 가장 재미있었던 것이 메시지 편집이다. 사용자가 과거 자기 메시지의 ✎를 누르고 한 줄 고쳐서 다시 보내는 그것. 막상 만들어보니 "챗봇의 기억이란 무엇인가"라는 본질적 질문에 닿았다. 이번 글은 그 메서드 한 개와 그 너머의 의미를 풀어 쓴다.


    1. 사용자가 ✎를 누를 때 무엇이 일어나야 하나

    요구사항은 직관적이다.

    • 과거 user 메시지에 마우스 올리면 ✎ 버튼 노출
    • 클릭 → 그 자리에서 textarea로 변형, 텍스트 수정 가능
    • 저장 → 그 이후 모든 메시지를 컨텍스트에서 잘라내고 새 user 메시지로 재추론

    마지막 줄이 핵심이다. "그 이후 모든 메시지"가 정확히 무엇을 뜻하는가. 그리고 그것을 누가 어떻게 잘라내는가.


    2. 구현 핵심 — 메서드 한 개의 의미

    memory-store(메시지 본문이 사는 곳)에 메서드를 하나 추가했다. 정말 그것뿐이다.

    # short_term.py
    def truncate_after(self, index: int) -> int:
        if not self._current_session or index < 0:
            return 0
        msgs = self._current_session.messages
        if index >= len(msgs):
            return 0
        removed = len(msgs) - index
        self._current_session.messages = msgs[:index]
        return removed
    

    코드 자체는 6줄. 하지만 이 메서드의 의미는 한 줄로 요약된다 — "index 위치에서부터 끝까지 메시지 리스트를 잘라낸다. 그게 전부다."

    여기서 의도적으로 "안 한" 것들이 더 중요하다.

    • 새 user 메시지를 추가하지 않는다
    • LLM 호출을 트리거하지 않는다
    • 잘라낸 메시지에서 파생된 메모리·RAG·도구 호출 결과를 정리하지 않는다
    • 되돌리기 위한 backup을 두지 않는다

    왜 이렇게 옹졸하게 만들었는가? 편집 엔드포인트가 "잘라내기 한 가지"만 책임지면, 새 user 메시지를 받는 일은 평소 채팅 흐름이 그대로 한다. 즉 클라이언트 입장에서는 다음 두 단계로 이뤄진다.

    1. POST /api/sessions/{id}/edit-and-resubmit — 잘라내기만
    2. POST /v1/chat/completions — 평상시처럼 새 메시지로 호출

    "메시지 편집"이라는 한 줄짜리 사용자 의도가 백엔드에서는 이미 존재하는 두 코드 경로의 조합으로 끝난다. 새 코드 경로를 만들지 않는다는 것 — 이게 이 메서드의 진짜 가치다.


    3. 컨텍스트 엔지니어링 관점에서 본 이 메서드

    여기서 한 발 물러나 본다. LLM은 stateless다. 챗봇이 "기억한다"고 느껴지는 것은 매 요청마다 messages 배열을 통째로 다시 보내기 때문이지, 모델 안에 무엇이 남아있어서가 아니다.

    그러니까 챗봇 UX의 거의 모든 "기억" 관련 행동은 사실 "이번 요청의 messages 배열을 어떻게 조립할까"의 문제다. 새 대화는 빈 배열로 시작, 평소 채팅은 누적, 재생성은 마지막 assistant만 빼고 다시, 그리고 메시지 편집은 — 특정 인덱스부터 잘라낸 뒤 새 user 메시지로 이어 보내기.

    diagram

    같은 사용자, 같은 시작점, 다른 분기 — Git의 force-push와 비슷한 멘탈 모델이다. 어떤 분기를 모델에 보여주느냐에 따라 모델의 "현재 사고"가 달라진다.

    이런 관점에서 보면 편집 엔드포인트의 단일 책임이 왜 그렇게 중요한지가 드러난다. 만약 편집이 "메시지 자르기 + 새 user 메시지 추가 + LLM 호출"을 한 번에 했다면, 컨텍스트 조립 로직이 두 군데(평상 채팅 / 편집)에 중복된다. 두 곳 중 한쪽만 도구 호출 컨텍스트(set_search_context)나 프로젝트 시스템 프롬프트를 잊으면 모델은 다른 컨텍스트를 본다. 디버깅 지옥.

    "잘라내기"만 분리하면 그 다음은 평상시 코드가 알아서 한다. 컨텍스트 엔지니어링은 "조립 책임을 한 곳으로 모으기"가 절반이다.


    4. 트레이드오프 — 단순함의 비용

    4-1. 첫 응답이 살짝 느려진다 (vLLM prefix caching 무효화)

    vLLM은 매 요청마다 system prompt + 첫 N개 메시지를 처리한 결과(KV cache라고 부르는 중간 텐서)를 메모리에 보관한다. 다음 요청이 같은 prefix를 보내면 그 부분의 계산을 스킵하고 새 토큰부터 처리한다 — 이걸 prefix caching이라 한다.

    diagram

    편집 후엔 prefix(system + 잘라내지 않은 앞부분 메시지들)는 그대로지만 잘라내기 시점부터 끝까지 새 시퀀스가 들어가므로, 그 새 부분에 한해 다시 계산해야 한다. 사용자 체감으로는 "편집 후 첫 응답만 평소보다 1~2초 늦게 시작" 정도. 그 다음 메시지부터는 새 분기의 prefix가 캐시에 다시 쌓여 평소 속도.

    4-2. 편집은 되돌릴 수 없다

    잘려나간 메시지들은 영구 손실된다. undo 버튼은 없다. 사용자가 "어, 그 답이 사실 더 좋았는데" 했을 때 곤란하다.

    이걸 완화하는 두 가지 방안:

    • 대화 export 기능과의 짝꿍 — 편집 전에 마크다운/JSON 다운로드를 권장. 같은 페이지에 노출.
    • "재생성" 버튼은 다른 멘탈 모델 — 사용자가 직전 답만 마음에 안 들 때는 ✎ 편집 대신 ↻ 재생성을 쓰면 컨텍스트는 그대로 두고 마지막 assistant만 다시 만든다. UI에서 두 버튼이 가깝게 있으니 선택을 안내.

    4-3. "분기"인데 시간선은 단일하다

    Git에서는 force-push 하더라도 reflog가 남아 옛 분기를 되찾을 수 있다. 이 챗봇은 그렇지 않다. "하나의 세션 = 하나의 시간선"이라는 단순한 모델을 유지하기 위해서다.

    두 분기를 나란히 보고 비교하고 싶다면? 더 좋은 도구는 "세션 복제 후 편집"이다 (Phase 1 묶음에는 미포함, 후속 작업). 그러면 원본도 살아있고 새 분기도 따로. 다만 멘탈 모델이 복잡해져서 일반 사용자에겐 ✎ 한 버튼이 직관적이라 우선 단일 시간선으로 시작했다.


    5. 마무리 — 단순함이 컨텍스트 엔지니어링이다

    처음에는 "메시지 편집" 엔드포인트에 자르기·새 메시지 추가·LLM 호출까지 다 넣을 뻔했다. 그러면 평상 채팅 흐름의 코드가 두 곳으로 갈라지고, 도구 컨텍스트(set_search_context)·프로젝트 시스템 프롬프트·메모리 회상 같은 장치가 어느 한쪽에서 빠질 가능성이 생긴다.

    "잘라내기 한 가지만 한다"로 책임을 줄이니, 평상시 만들어둔 컨텍스트 조립 로직이 그대로 재사용된다. 새로 추가된 코드는 6줄짜리 메서드 하나. 그런데 이 6줄이 챗봇이 사용자의 "한 번 더 해보자"에 응답하는 핵심 primitive가 됐다.

    모델이 stateless라는 사실을 진지하게 받아들이면, 챗봇 UX는 결국 "이번 요청에 보낼 messages 배열을 어떻게 만들 것인가"의 디자인이다. 메시지 편집은 그 시간선을 사용자가 명시적으로 분기시키는 도구다 — 그 도구는 단순할수록 안전하고, 컨텍스트 조립 책임이 한 곳에 모일수록 신뢰할 만하다.

    다음 편에서는 세션을 묶는 Project(Workspace) 계층을 다룬다. 같은 모델이 작업 맥락을 알아채는 첫 번째 장치다.


    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.