~/ai-dev.md
AI_DEV

실시간 AI 세션은 왜 채팅 API가 아니라 상태 머신일까

10분 읽기·2026.06.23·출처 5·00
오늘의 질문

음성·영상 에이전트를 만들 때 왜 “메시지 하나 보내고 답 받기” 모델만으로는 부족할까?

ai-dev.md
SURVIVE.exe

실시간 AI 세션은 왜 채팅 API가 아니라 상태 머신일까

1. 왜 지금 봐야 하나

텍스트 챗봇을 만들 때는 보통 요청 하나와 응답 하나를 생각한다. 사용자가 메시지를 보내고, 서버가 모델 API를 호출하고, 응답을 저장한다. 이 구조에서는 장애도 비교적 단순하다. 요청이 실패하면 재시도하고, 응답이 늦으면 타임아웃을 걸면 된다.

하지만 실시간 음성·영상 AI는 다르다. 사용자는 말하는 중간에 끊고, 모델도 말하는 중간에 멈춰야 한다. 마이크 오디오는 계속 쌓이고, WebSocket 연결은 언젠가 끊기며, 긴 대화의 과거 토큰은 매 턴 비용으로 다시 영향을 준다. 도구 호출까지 붙이면 “모델이 말하는 상태”, “사용자가 말하는 상태”, “도구 결과를 기다리는 상태”, “연결을 복구하는 상태”가 동시에 생긴다.

Google의 Gemini Live API 문서는 이 차이를 잘 보여준다. Live API는 낮은 지연의 음성·영상 상호작용을 위해 WebSocket 기반의 지속 세션을 사용한다. 공식 best practices는 오디오를 20~40ms 단위로 보내고, 클라이언트가 과도하게 버퍼링하지 말며, 모델 응답 중 사용자가 끼어들면 클라이언트 오디오 버퍼를 즉시 버리라고 설명한다. Session management 문서는 연결 수명, GoAway, session resumption, context window compression을 별도의 운영 주제로 다룬다.

오늘의 핵심은 이것이다.

실시간 AI 앱은 “채팅 API 호출”이 아니라 입력 스트림, 모델 출력, 도구 실행, 컨텍스트 압축, 연결 복구를 관리하는 상태 머신이다.

2. 핵심 개념

일반 채팅 API와 실시간 세션 API의 가장 큰 차이는 시간이 제품 로직 안으로 들어온다는 점이다. 텍스트 요청에서는 사용자의 입력이 이미 완성된 문자열이다. 반면 음성 세션에서는 입력이 아직 끝나지 않았을 수 있다. 모델이 응답을 시작했는데 사용자가 말을 다시 시작할 수도 있다. 네트워크 연결은 유지되어야 하고, 출력 오디오는 재생 큐에 남아 있을 수 있다.

그래서 실시간 AI 세션은 최소한 다음 상태를 명시해야 한다.

  1. listening: 사용자의 실시간 입력을 받는 상태
  2. thinking/responding: 모델이 응답을 생성하고 오디오를 내보내는 상태
  3. interrupted: 사용자 발화로 인해 기존 모델 출력을 폐기해야 하는 상태
  4. tool_waiting: 모델이 도구 호출을 요청했고 앱이 실행 결과를 돌려줘야 하는 상태
  5. compressing/resuming: 컨텍스트 창 또는 연결 수명 때문에 세션을 압축하거나 재개하는 상태
  6. closed/recovered: 연결 종료 후 새 연결에서 이전 세션을 이어가는 상태

이 상태를 명시하지 않으면 버그가 사용자 경험으로 바로 드러난다. 사용자가 끊었는데 AI가 계속 말하거나, 오래 대화할수록 비용이 폭증하거나, WebSocket 재연결 후 모델이 이전 맥락을 잃는 식이다.

3. 최신 이슈와 연결

Live API 문서에서 특히 중요한 운영 포인트는 세 가지다.

첫째, 컨텍스트는 실시간으로 비용을 만든다. Best practices는 native audio token이 초당 약 25개씩 누적된다고 설명한다. 또 Live API는 세션 컨텍스트 창에 남아 있는 토큰이 턴마다 다시 비용에 영향을 주는 compounding model을 가진다고 안내한다. 즉 “긴 대화가 가능하다”는 말은 “아무것도 안 해도 싸다”는 뜻이 아니다. 긴 세션에서는 context window compression, 요약, sliding window 정책이 비용 제어 장치가 된다.

둘째, 연결과 세션은 같은 것이 아니다. Session management 문서는 연결 수명이 약 10분으로 제한될 수 있고, 서버가 종료 전에 GoAway 메시지를 보낼 수 있다고 설명한다. 세션을 여러 연결에 걸쳐 유지하려면 SessionResumptionUpdate에서 받은 handle을 저장하고, 다음 연결에서 SessionResumptionConfig.handle로 넘겨야 한다. 이 설계는 프론트엔드의 “재연결 로직”이 아니라 제품 상태 저장 문제다.

셋째, 실시간 도구 호출은 순서와 책임 소재를 복잡하게 만든다. Capabilities 문서는 모델 버전에 따라 async function calling 지원 여부와 이벤트 파트 처리 방식이 다르다고 설명한다. Gemini 3.1 Flash Live Preview는 한 서버 이벤트에 여러 content part가 함께 올 수 있으므로 모든 part를 처리해야 하고, function calling은 순차적이라고 정리한다. Tools 문서는 built-in tool은 Google 서버에서 실행되지만, custom function은 애플리케이션이 실행하고 같은 id로 결과를 돌려줘야 한다고 설명한다.

이 셋을 합치면 결론은 단순하다. 실시간 AI 앱은 모델 품질만으로 완성되지 않는다. 상태 전이, 버퍼 폐기, 세션 복구, 도구 실행 대기, 비용 상한을 애플리케이션이 책임져야 한다.

4. 개발자 관점 해석

개발자가 가장 자주 하는 실수는 실시간 AI를 “채팅 UI에 마이크만 붙인 것”으로 보는 것이다. 그러나 실제 병목은 모델 호출보다 주변 상태에 있다.

예를 들어 오디오를 1초씩 모아서 보내면 구현은 편하지만 지연이 커진다. Best practices는 20ms~100ms 수준의 작은 chunk 전송을 권장한다. 반대로 너무 작은 단위로 보내면서 네트워크와 재생 큐를 계측하지 않으면 클라이언트 CPU, WebSocket backpressure, 오디오 끊김이 생길 수 있다.

또 사용자가 모델 응답을 끊었을 때 서버가 interrupted: true를 보냈다면, 클라이언트는 이미 받은 오디오를 계속 재생하면 안 된다. 이건 UX 문제가 아니라 상태 일관성 문제다. 사용자의 새 발화가 현재 턴을 취소했는데 이전 오디오가 남아 재생되면, 사용자는 AI가 말을 듣지 않는다고 느낀다.

컨텍스트도 마찬가지다. “긴 컨텍스트 모델을 쓰면 기억 문제가 사라진다”는 생각은 실시간 세션에서는 특히 위험하다. 오디오 토큰은 빠르게 쌓이고, 세션 컨텍스트가 길어질수록 각 턴의 비용과 지연이 커질 수 있다. 따라서 오래 유지되는 음성 에이전트는 처음부터 다음 질문을 가져야 한다.

  • 어떤 정보는 그대로 유지해야 하는가?
  • 어떤 발화는 요약으로 바꿔도 되는가?
  • 어떤 도구 결과는 최종 결론만 남기고 원문을 버릴 수 있는가?
  • 압축 후에도 사용자가 “아까 말한 것”을 물었을 때 필요한 참조가 남는가?

마지막으로 도구 호출은 실시간성을 깨뜨릴 수 있다. 결제 조회, 검색, DB 업데이트 같은 custom tool은 모델 서버가 아니라 내 애플리케이션이 실행한다. 도구가 느리면 모델이 말하지 못하는 시간이 생기고, 도구 결과가 늦게 왔을 때 이미 사용자가 다음 말을 시작했을 수도 있다. 따라서 실시간 에이전트의 도구는 “호출 가능 여부”보다 “언제 호출하고, 기다리는 동안 무엇을 말하며, 늦은 결과를 어떻게 반영할지”가 더 중요하다.

5. 내 프로젝트에 적용할 체크포인트

실시간 AI 기능을 만들거나 검토한다면 아래를 먼저 확인하자.

  • 상태도: listening, responding, interrupted, tool_waiting, reconnecting 상태가 코드에 명시되어 있는가?
  • 오디오 입력: 클라이언트가 20~100ms 수준의 작은 chunk로 보내는가, 아니면 UX를 느리게 만드는 큰 버퍼를 쌓는가?
  • 중단 처리: 모델 응답 중 사용자 발화가 감지되면 재생 큐와 화면 자막을 어떻게 폐기하는가?
  • 컨텍스트 압축: long session에서 compression trigger, sliding window, 요약 정책이 있는가?
  • 세션 재개: resumption handle을 어디에 저장하고, 2시간 유효성 같은 제한을 어떻게 다루는가?
  • 도구 호출: custom function call의 id, 실행 결과, 실패, 지연을 trace로 남기는가?
  • 비용 계측: 세션 길이, 누적 오디오 초, 컨텍스트 토큰, 턴당 비용을 분리해서 볼 수 있는가?
  • 보안 경계: 브라우저에서 직접 연결한다면 일반 API key가 아니라 ephemeral token 같은 단기 인증을 쓰는가?

6. 오늘 10분 액션

오늘은 코드를 고치기보다 상태를 그려보자.

  1. 지금 만들고 있거나 상상 중인 음성 AI 기능 하나를 고른다.
  2. 종이에 listening → responding → interrupted → tool_waiting → reconnecting 상태를 그린다.
  3. 각 화살표 옆에 “누가 이벤트를 발생시키는가?”를 적는다. 예: 사용자 발화, 서버 interrupted, 도구 결과, GoAway, WebSocket close.
  4. 각 상태마다 버려야 할 것과 보존해야 할 것을 나눈다. 예: 출력 오디오 버퍼는 버림, 사용자 의도와 도구 결과 trace는 보존.
  5. 마지막으로 “이 세션이 30분 지속되면 비용과 컨텍스트는 어떻게 되는가?”를 한 줄로 적는다.

이 10분만 해도 실시간 AI를 단순 데모가 아니라 운영 가능한 제품 흐름으로 보기 시작할 수 있다.

7. 더 볼 자료

중복 회피 메모

최근 ai_dev 글은 prompt caching, LLM router의 prefix-cache-aware scheduling, MCP elicitation, guardrail checkpoint, reasoning effort, structured outputs를 다뤘다. 이번 글은 일반 채팅형 agent architecture나 서빙 라우팅을 반복하지 않고, 실시간 음성·영상 AI 세션에서 생기는 상태 전이, interrupt 처리, context compression, session resumption, 도구 대기 상태를 하나의 상태 머신으로 설계하는 관점에 집중한다.

오늘 10분 액션+5%

댓글 0

최신순 ▾
한 줄 남기기

혹시 이 글을 읽는 동료 개발자가 있다면, GitHub으로 로그인하고 한 줄 흔적을 남겨줘요. (스팸 방지용 로그인이에요)