긴 상담을 버티는 음성 에이전트: 동적 컨텍스트 윈도우 관리


짧은 문의는 매끄럽게 처리하던 음성 에이전트가 통화가 10분, 20분 길어지면 눈에 띄게 흔들립니다. 답이 느려지고, 앞에서 분명히 말한 내용을 다시 묻고, 어떤 구간에서는 처리 비용이 부쩍 뜁니다. 모델을 바꿔도 잘 낫지 않는데, 원인은 모델의 머리가 나빠서가 아니라 통화 내내 쌓인 대화를 '어떻게 모델에게 들려줄 것인가'를 설계하지 않았기 때문입니다. 긴 상담을 끝까지 또렷하게 끌고 가려면, 대화 이력을 매 턴 통째로 밀어 넣는 대신 동적으로 관리해야 합니다.

[시리즈] S2S·LLM 음성 에이전트 엔지니어링 63편 · 현재 4-1편

← 이전: 3-5 실시간 세션 관리 — 통화 맥락·고객 데이터 / 다음 → 4-2 WebRTC·WebSocket 초저지연 음성

전체 목차 펼치기 (63편)

Part 1 — 음성 AI가 느린 이유: 레이턴시 진단 (4편)

Part 2 — 텍스트를 넘어: End-to-End S2S의 원리 (4편)

Part 3 — 실시간 대화와 에이전트화: 인터럽트·MCP·도구 (5편)

Part 4 — 인프라·세션·보안: 프로덕션 백엔드 (5편)

Part 5 — 운영·품질·평가: 에이전틱 워크플로우와 신뢰성 (7편)

Part 6 — 음성 경험 고도화·실전 통합: 한국어·UX·전화망·엣지 (5편)

Part 7 — S2S 도입 전략·구매 의사결정 (4편)

Part 8 — LLM 콜봇 PoC 현장 (6편)

Part 9 — 상담어시스트: 상담사 화면을 바꾸는 실시간 AI (7편)

Part 10 — 직접 만든 롤플레이 챗봇: RAG 에이전트 구축기 (8편)

Part 11 — KMS 실무 — 지식관리시스템의 설계와 운영 현실 (8편)

통화가 길어지면 무슨 일이 벌어지는가

대화형 모델은 매 턴, 지금까지의 대화를 다시 입력으로 받아 다음 발화를 만듭니다. 음성 에이전트도 다르지 않습니다. 사용자 발화와 에이전트 응답이 번갈아 쌓이고, 그 누적분이 매 턴 프롬프트에 다시 실립니다. 통화가 길어진다는 것은 곧 입력 길이가 단조롭게 늘어난다는 뜻입니다.

가장 순진한 방식은 "전체 대화를 매번 다 넣는 것"입니다. 짧은 통화에서는 멀쩡히 작동하기 때문에 초기에는 문제처럼 보이지 않습니다. 하지만 대화가 길어지면 이 방식은 세 방향에서 동시에 무너집니다. 입력 토큰이 늘어 비용이 가파르게 커지고, 입력을 처리하는 데 걸리는 시간이 늘어 응답이 느려지며, 정작 중요한 정보가 긴 맥락 속에 묻혀 정확도까지 떨어집니다. 길어질수록 느려지고, 비싸지고, 흐려지는 삼중고입니다.

윈도우를 다 채우면 안 되는 세 가지 이유

첫째는 비용입니다. 대부분의 모델은 매 턴 입력 전체를 다시 처리합니다. 즉 대화가 누적될수록 같은 앞부분을 반복해서 다시 보내는 셈이고, 누적된 이력이 길어질수록 통화 한 건에 드는 처리량은 단순 합산보다 빠르게 불어납니다. 구체 단가는 모델·시점마다 다르지만, 메커니즘 자체가 "길어질수록 가팔라지는" 구조라는 점이 핵심입니다.

둘째는 지연입니다. 모델이 답을 만들기 전에 입력을 먼저 읽어 들이는 단계(prefill)가 있는데, 이 시간은 입력 길이에 비례해 늘어납니다. 텍스트 챗이라면 조금 느린 정도로 넘어가지만, 실시간 음성은 통상 1초 안팎의 응답을 자연스럽다고 느끼는 매체라(체감지연 보정은 03편 참조), 입력이 길어져 생기는 지연은 곧장 어색한 침묵으로 드러납니다.

셋째는 품질입니다. 맥락이 아주 길어지면 모델이 중간에 놓인 정보를 흘려버리는 경향이 보고됩니다. 흔히 'lost in the middle'이라 부르는 현상으로, 윈도우를 꽉 채운다고 더 똑똑해지는 게 아니라 오히려 핵심을 놓칠 수 있다는 뜻입니다. 윈도우는 채울 공간이 아니라 아껴 써야 할 예산에 가깝습니다.

핵심 슬롯: 절대 잊으면 안 되는 것부터 분리

해법의 출발점은 분리입니다. 통화 내내 흐르는 자연어 대화와, 끝까지 정확히 유지돼야 하는 사실을 같은 그릇에 담지 않는 것입니다. 후자를 구조화된 '핵심 슬롯'으로 따로 빼서 프롬프트 상단에 항상 고정합니다.

핵심 슬롯에는 통화의 정체성과 진행 상태가 들어갑니다. 예컨대 누구와 이야기하고 있는지(식별·검증 상태), 지금 처리 중인 작업이 무엇인지, 이미 확정된 사실은 무엇인지(선택한 옵션, 합의된 날짜, 동의 여부) 같은 항목입니다. 이것들은 대화가 아무리 길어져도 요약으로 뭉개지면 안 되는 값들입니다. 슬롯은 자연어 서술이 아니라 키-값 형태로 또렷하게 유지하는 편이 안전합니다.

[핵심 슬롯 — 항상 프롬프트 상단 고정]
- 식별/검증: 확인 완료
- 진행 중 작업: 예약 일정 변경
- 확정 사실: 변경 희망일=다음 주 화요일 오후, 본인 동의=받음
- 미해결: 변경 후 안내 문자 발송 여부

이렇게 빼 두면 두 가지 이득이 있습니다. 중요한 값이 긴 맥락에 묻히지 않고, 뒤에서 대화 본문을 압축하더라도 잃어선 안 될 정보가 보호됩니다.

이전 대화 요약(Summarization): 오래된 턴을 압축

핵심 슬롯을 분리했다면, 남은 자연어 대화는 통째로 들고 갈 필요가 없습니다. 최근 몇 턴은 원문 그대로가 중요하지만, 오래된 앞부분은 요점만 남기면 됩니다. 여기서 쓰는 기법이 슬라이딩 윈도우와 롤링 요약의 결합입니다.

작동은 단순합니다. 최근 N턴은 원문으로 유지하고(슬라이딩 윈도우), 그보다 오래된 대화는 짧은 요약본으로 접습니다. 대화가 더 진행돼 임계치를 넘으면, 윈도우 밖으로 밀려난 턴을 기존 요약본에 흡수시켜 요약을 갱신합니다(롤링). 결과적으로 프롬프트는 "오래된 것은 요약, 최근 것은 원문"이라는 일정한 형태를 유지하며, 통화가 길어져도 입력 길이가 무한정 늘지 않습니다.

다만 요약에는 대가가 따릅니다. 요약을 만드는 호출 자체가 비용과 시간을 쓰고, 압축 과정에서 정보가 일부 손실됩니다. 너무 자주 요약하면 배보다 배꼽이 커지고, 너무 늦게 하면 윈도우가 먼저 터집니다. 임계치(예: 원문 턴 수나 누적 토큰 기준)를 정해 그 선을 넘을 때만 요약을 돌리는 균형점 설계가 실무의 핵심입니다. 외부 지식을 검색해 끼워 넣는 RAG와는 다른 축이라는 점도 분명히 해 둘 필요가 있습니다(검색 결과 주입은 04편 참조). 여기서 다루는 것은 외부 지식이 아니라 대화 자체의 누적 이력입니다.

동적 프롬프트 조립: 매 턴 무엇을 넣을지 결정

지금까지의 조각을 매 턴 하나의 프롬프트로 합치는 단계가 동적 조립입니다. 매번 같은 자리에 같은 종류를 끼워 넣는 고정된 레시피를 두면, 모델 입장에서도 일관되고 운영 입장에서도 측정·캐싱이 쉬워집니다.

프롬프트 = [
  시스템 규칙 (역할·말투·제약)        # 고정
  + 핵심 슬롯 (구조화 상태)            # 매 턴 갱신, 항상 상단
  + 대화 요약본 (오래된 턴 압축)       # 임계치 초과 시 갱신
  + 최근 N턴 원문 (슬라이딩 윈도우)    # 매 턴 이동
  + (필요 시) 검색·외부 조회 결과      # 해당 턴에만
]

조립 순서에도 의도가 있습니다. 시스템 규칙과 핵심 슬롯처럼 잘 바뀌지 않는 부분을 앞쪽 고정 자리에 두면, 뒤에서 다룰 프롬프트 캐싱과 맞물려 매 턴 같은 앞부분을 다시 계산하는 낭비를 줄일 수 있습니다. 반대로 매 턴 바뀌는 최근 원문은 뒤쪽에 배치합니다.

그렇다면 컨텍스트를 줄이는 싸움은 결국 어디로 이어질까요? 따라가 보면 "한 에이전트가 떠안는 지식의 양"을 줄이는 싸움과 만납니다. 단일 거대 에이전트 하나에 모든 상담 지식(요금·해지·기술지원·결제…)을 다 담으면, 시스템 규칙과 슬롯·도구 설명만으로도 고정부가 비대해져 매 턴 부담이 커지고 운영도 어려워집니다. 그래서 도메인별 서브 에이전트로 쪼갠 멀티 에이전트 구조로 가는 것이 자연스러운 귀결입니다. 앞단의 오케스트레이터가 의도 분류 결과를 받아 어느 서브 에이전트로 보낼지 라우팅하고, 선택된 서브 에이전트는 자기 도메인에 필요한 규칙·도구·지식만 좁게 들고 들어갑니다. 같은 통화라도 호출되는 컨텍스트가 도메인 단위로 작게 유지되는 셈입니다.

이렇게 쪼개 두면 모델 선택에서도 여지가 생깁니다. 서브마다 다른 크기·종류의 모델을 섞어 쓸 수 있어, 단순 분류·확인성 응대는 작고 빠른 모델로, 복잡한 추론이 필요한 도메인만 큰 모델로 배정하는 식의 절충이 가능합니다. 다만 라우팅이 한 겹 끼는 만큼 의도 분류가 틀리면 엉뚱한 서브로 새고, 도메인 사이를 오가는 통화에서는 슬롯 같은 공유 상태를 어떻게 넘겨줄지가 새로운 설계 포인트가 됩니다. 컨텍스트 윈도우를 아끼는 문제가 어느 선을 넘으면 "프롬프트를 어떻게 접느냐"에서 "에이전트를 어떻게 나누느냐"로 옮겨간다는 점은 미리 염두에 둘 만합니다.

구분전체 이력 통째로동적 컨텍스트 관리
입력 길이통화 길이에 비례해 무한 증가요약+최근 N턴으로 상한 유지
비용 추이길어질수록 가파르게 상승완만하게 안정화
핵심 정보긴 맥락에 묻힐 위험슬롯으로 상단 고정·보호
응답 지연prefill 증가로 악화입력 상한으로 통제
구현 부담거의 없음(초기엔 편함)요약·임계치 설계 필요

실시간 음성에서의 추가 제약

텍스트 챗이라면 요약을 응답 직전에 잠깐 돌려도 됩니다. 하지만 실시간 음성에서는 사용자가 침묵을 못 견디므로, 무거운 요약 호출이 응답 경로 한가운데에 끼면 그 시간이 그대로 어색한 공백이 됩니다. 그래서 요약은 응답을 막지 않는 비동기·백그라운드 작업으로 돌리는 것이 정석입니다. 사용자에게 답하는 흐름은 기존 요약본과 최근 원문으로 즉시 진행하고, 요약 갱신은 그 뒤(또는 사용자가 말하는 동안)에 조용히 처리해 다음 턴에 반영하는 식입니다.

또 하나의 지렛대는 프롬프트 캐싱입니다. 시스템 규칙처럼 매 턴 동일한 앞부분은 모델이 다시 계산하지 않도록 캐시할 수 있어, 반복 처리 비용과 prefill 지연을 함께 줄일 수 있습니다. 동적 조립에서 고정부를 앞에 모아 둔 설계가 여기서 빛을 발합니다. 정리하면, 긴 통화의 컨텍스트 관리는 "무엇을 남기고 무엇을 접느냐"라는 메모리 설계와, "그 작업을 응답 경로 밖으로 빼느냐"라는 실시간 설계가 함께 가야 완성됩니다.

세 전략을 한눈에

지금까지의 세 축을 한 표로 정리하면, 각 전략이 무엇을 얻고 무엇을 감수하는지가 또렷해집니다.

전략장점한계
핵심 슬롯 고정확정 사실을 상단에 보호해 긴 맥락에도 안 묻힘, 정정·추적이 쉬움무엇을 슬롯으로 뺄지 사전 설계가 필요하고, 잘못 넣은 값은 통화 내내 따라옴
슬라이딩 윈도우 + 롤링 요약오래된 턴을 압축해 입력 길이에 상한을 두고, 통화가 길어져도 비용·지연이 완만해짐요약 호출 자체가 비용·시간을 쓰고 손실 압축이라 디테일이 빠질 수 있음, 임계치 튜닝 필요
고정부 프롬프트 캐싱매 턴 같은 앞부분을 다시 계산하지 않아 반복 처리 비용과 prefill 지연을 함께 줄임고정된 앞부분에만 효과가 크고 매 턴 바뀌는 최근 대화에는 이득이 제한적

세 전략은 서로를 대체하지 않고 겹쳐 써야 제값을 하며, 긴 통화의 품질은 결국 무엇을 남기고 무엇을 접을지 매 턴 설계하는 메모리 관리에서 갈립니다. 여기 정리한 접근은 2026년 6월 기준의 이야기이고 세부 구현은 환경마다 다를 수 있으니, 각자의 시스템에 맞는 출발점으로 삼아 주시면 좋겠습니다.

댓글

이 블로그의 인기 게시물

STT 인식률 93%의 함정 — 왜 그 숫자는 콜봇 목표가 될 수 없는가

AI 보이스봇이 답답한 이유: Cascaded 구조 레이턴시 완전 분석

음성 토큰(Audio Token)이란? LLM이 소리를 이해하는 원리