실무 Multi-Agent 오케스트레이션 16장 · 멀티턴 대화와 세션 관리 4 / 7 ← 이전목차다음 → TechLead Cro

16장. 멀티턴 대화와 세션 관리

기억의 비용과 trim — 이력은 매번 다시 나간다

한 줄 요약

이력을 매번 다시 보내는 방식에는 피할 수 없는 비용이 있습니다. 대화가 길어질수록 입력 토큰이 커집니다. 가장 단순한 대책은 오래된 턴부터 버리는 trim입니다. 자를 때는 턴이 시작되는 자리에서 자릅니다.


1. 새 문의는 한 문장인데 입력은 계속 커진다

매 턴의 입력은 "이력 전체 + 새 문의"입니다. 고객이 한 문장만 보내도, 모델에게는 그 앞의 대화가 전부 다시 들어갑니다.

턴 고객이 보낸 것 모델에게 실제로 들어간 것
1 한 문장 시스템 프롬프트 + 도구 설명 + 한 문장
2 한 문장 위 전부 + 1턴의 네 메시지 + 한 문장
3 한 문장 위 전부 + 2턴의 메시지들 + 한 문장

2장에서 본 대로 요금은 토큰 수에 비례합니다. 그러니 대화의 뒤로 갈수록 한 마디의 값이 비싸집니다. 「따라하기」에서 이 증가를 턴마다 숫자로 확인합니다.

비용 말고 벽이 하나 더 있습니다. 모델이 한 번에 받을 수 있는 입력의 상한인 컨텍스트 윈도우(context window) 입니다(2장). 요즘 모델의 윈도우는 매우 크지만 끝이 없지는 않고, 거기에 닿기 한참 전부터 비용과 응답 속도가 먼저 나빠집니다.


2. trim — 오래된 턴부터 버린다

trim(잘라내기) 은 이력이 정해 둔 길이를 넘으면 오래된 턴부터 버리는 방법입니다. ChatSession은 최근 max_turns개 턴만 남깁니다.

여기서 턴은 고객 문의 하나와 그 처리 전체입니다. 도구를 쓴 턴은 메시지 네 개, 도구 없이 답한 턴은 메시지 두 개입니다. 그래서 메시지 개수가 아니라 턴 개수로 셉니다.


3. 턴이 시작되는 자리에서 자른다

도구 호출(function_call)과 도구 결과(function_response)는 짝입니다. API는 도구 결과가 도구 호출 바로 뒤에 오는 이력만 받습니다. 그래서 자르는 자리는 반드시 턴이 시작되는 곳, 곧 고객이 직접 말한 메시지로 잡습니다. 그러면 짝이 갈라질 일이 없습니다.

"고객이 말한 메시지"는 role == "user"에 더해 글(text)이 들어 있는지까지 보고 찾습니다. 앞 절에서 본 대로 도구 결과도 role이 user이기 때문입니다.

@staticmethod
def _is_turn_start(content: types.Content) -> bool:
    return content.role == "user" and any(p.text for p in (content.parts or []))

def _trim(self):
    starts = [i for i, c in enumerate(self.history) if self._is_turn_start(c)]
    if len(starts) <= self.max_turns:
        return
    self.history = self.history[starts[-self.max_turns]:]
줄 하는 일
_is_turn_start role이 user이고 글이 들어 있는 메시지만 턴의 시작으로 봅니다. 도구 결과는 걸러집니다
starts = [...] 이력에서 턴이 시작되는 자리의 번호를 모읍니다
len(starts) <= self.max_turns 턴 수가 한도 이하면 아무것도 하지 않습니다
starts[-self.max_turns] 뒤에서 max_turns번째 턴이 시작되는 자리. 거기서부터만 남깁니다

언제 자를지, 몇 턴을 남길지는 개발자가 정한 규칙입니다. 모델은 자기에게 오지 않은 턴이 있었다는 사실조차 모릅니다.


4. max_turns는 맞바꿈이다

trim은 비용이 끝없이 커지는 것을 막아 줍니다. 대신 남겨 둔 턴 바깥의 내용은 모델에게 가지 않습니다. max_turns는 이 둘을 맞바꾸는 손잡이입니다.

얻는 것 내주는 것
max_turns를 크게 더 먼 앞 턴까지의 문맥 턴마다 커지는 비용, 느려지는 응답
max_turns를 작게 일정한 비용 남긴 턴보다 오래된 내용

값을 얼마로 둘지는 개발자가 상담 한 건이 보통 몇 턴인지와 비용을 보고 정합니다. 실습문제 2에서 trim이 일하는 동안에도 최근 문맥이 이어지는 것을 확인합니다. 오래된 턴을 버리는 대신 줄여서 남기는 방법은 17장에서 다룹니다.


핵심 정리

  • 매 턴의 입력은 이력 전체 + 새 문의라서, 대화가 길어질수록 입력 토큰이 커집니다.
  • trim은 최근 몇 턴만 남기고 오래된 턴을 버립니다. 턴은 메시지 수가 아니라 고객 문의 단위로 셉니다.
  • 도구 호출과 도구 결과는 짝이므로, 반드시 턴이 시작되는 자리에서 자릅니다.
  • 도구 결과도 role이 user이므로, 턴의 시작은 role과 글의 유무를 함께 보고 찾습니다.
  • max_turns는 문맥의 길이와 비용을 맞바꾸는 값이고, 개발자가 정합니다.
← 이전 절세션과 스레드 — 누구의, 어느 대화인가다음 절 →따라하기 — 세 턴짜리 멀티턴 상담
오명운 · macro@prag-ai.com