RTZR FINALIZE로 스트리밍 STT 결과 더 빨리 받기

RTZR 스트리밍 STT의 FINALIZE로 서버의 end-point detection 대기를 줄이는 방법을 소개합니다. gRPC·WebSocket 사용법과 실험, LiveKit Agents 및 TEN Framework 연동 예시를 살펴봅니다.

RTZR FINALIZE로 스트리밍 STT 결과 더 빨리 받기

By Hazel

스트리밍 STT는 음성을 받는 동안 중간 인식 결과를 실시간으로 보냅니다. final transcript를 받으려면 서버가 현재 발화의 끝을 판단하는 end-point detection이 끝날 때까지 기다려야 합니다.

FINALIZE는 클라이언트가 판단한 발화 끝을 서버에 전달하는 요청입니다. 이를 사용하면 서버의 end-point detection을 기다리지 않고 final transcript를 받아 다음 응답 생성을 더 빠르게 시작할 수 있습니다.

이 글에서는 FINALIZE의 동작과 사용법을 살펴보고, 실험을 통해 적용 전후의 final transcript 수신 시간을 비교하겠습니다.

클라이언트는 RTZR 스트리밍 STT API에 오디오와 제어 요청을 보내는 애플리케이션을 뜻합니다. 서버는 오디오를 인식하고 결과를 반환하는 RTZR STT 서버입니다.

1. FINALIZE는 어떤 시간을 줄일까?

클라이언트가 발화 끝을 이미 알고 있는 상황에서 두 흐름을 비교해 보겠습니다.

FINALIZE 미사용

애플리케이션이 발화 끝 판단
  → 서버의 end-point detection 대기  ← FINALIZE로 줄이는 구간
  → final transcript 수신
  → 다음 응답 생성 시작


FINALIZE 사용

애플리케이션이 발화 끝 판단
  → 클라이언트가 FINALIZE 전송
  → final transcript 수신
  → 다음 응답 생성 시작

스트리밍 STT에서 final transcript가 도착하는 시점은 서버가 현재 발화의 끝을 언제 판단하느냐에 따라 달라집니다. 별도의 요청이 없다면 서버는 end-point detection으로 발화가 끝났는지 판단합니다. 따라서 사용자가 이미 말을 마쳤더라도 서버의 판단이 끝날 때까지 final transcript를 기다리는 구간이 생깁니다.

애플리케이션이 VAD나 turn detector로 발화 끝을 먼저 판단했다면, 클라이언트는 그 시점에 FINALIZE를 보내 현재 발화를 마쳤다고 서버에 알릴 수 있습니다. 서버의 end-point detection 대기를 생략하고 현재까지 입력된 음성의 인식을 마무리하도록 요청하는 방식입니다.

발화가 끝난 뒤 서버의 end-point detection을 기다리는 구간을 줄일 수 있어 final transcript를 더 일찍 받고 다음 응답 생성도 앞당길 수 있습니다.

End-point detection과 epd_time

서버가 발화 종료를 판단할 때 사용하는 설정 중 하나가 epd_time입니다. epd_time은 음성이 멈춘 뒤 서버가 현재 발화가 끝났다고 판단하기까지 기다리는 무음 길이를 의미합니다. 공식 Proto의 기본값은 0.5초이며 권장 범위는 0.5~1.0초입니다. 기본 설정에서는 약 0.5초 이내의 쉼을 같은 발화 안에 포함할 수 있도록 기다리고, 무음이 그보다 길게 이어지면 현재 발화의 결과를 확정합니다.

epd_time을 짧게 설정하면 final transcript를 더 빨리 받을 수 있지만, 문장 중간의 머뭇거림이나 짧은 쉼까지 발화 종료로 판단할 가능성이 커집니다. 반대로 길게 설정하면 자연스러운 쉼에는 안정적이지만 대기시간이 늘어날 수 있습니다. 적용하고자 하는 도메인과 발화 특성에 맞춰 적절한 값으로 설정해야 합니다.

2. FINALIZE와 EOS의 역할

발화 하나를 확정하는 것과 전체 오디오 입력을 종료하는 것은 별개의 동작입니다. FINALIZE는 현재 발화를 확정하며, 스트리밍 연결을 종료하지 않습니다.

구분 현재 발화를 처리하는 방식 이후 동작
FINALIZE 클라이언트가 현재 발화의 확정을 요청 연결을 유지하고 다음 음성 처리
WebSocket EOS 전체 오디오 입력 종료를 알림 남은 결과를 수신하고 현재 스트림을 마무리
gRPC half-close 요청 스트림의 전송 측을 닫음 응답 수신을 이어가며 현재 RPC를 마무리

WebSocket에서 EOS 텍스트 메시지를 보내면 더 이상 전송할 오디오가 없음을 알리고, 남은 인식 결과를 받은 뒤 연결을 마무리합니다. gRPC에서는 요청 스트림의 전송 측만 닫는 half-close로 전체 입력을 끝내고, 남은 응답을 수신합니다.

3. gRPC와 WebSocket으로 FINALIZE 보내기

두 프로토콜에서 요청의 의미는 같습니다. 현재 발화의 오디오를 보낸 뒤, 같은 스트림에 FINALIZE 요청을 추가합니다. 결과를 수신하고 다음 발화로 이어가는 흐름은 다음과 같습니다.

현재 발화의 오디오 전송
  → FINALIZE 요청
  → final transcript 수신 확인
  → 같은 연결로 다음 발화 전송

아래 Python 코드는 인증과 연결, 인식 설정이 준비되어 있다는 전제에서 요청을 추가하는 부분만 보여줍니다. 전체 연결 코드는 gRPC 연동 예제WebSocket 연동 예제를 참고하세요.

gRPC

오디오를 보내던 요청 스트림에 제어 요청을 추가합니다. streaming_control 안에 DECODER_CMD_FINALIZE 명령을 담습니다.

# 클라이언트가 현재 발화의 끝을 감지했을 때 같은 request stream에 추가합니다.

yield pb.DecoderRequest(
    streaming_control=pb.DecoderControl(
        command=pb.DecoderControl.DECODER_CMD_FINALIZE,
    )
)

이 요청 뒤에도 다음 발화의 오디오 요청을 같은 요청 스트림으로 보낼 수 있습니다. 대화의 전체 입력을 마칠 때는 요청 전송을 끝내는 half-close를 사용합니다.

WebSocket

오디오는 바이너리 메시지로 보내고, FINALIZE는 JSON 텍스트 메시지로 보냅니다. 아래 코드는 비동기 함수 내부에서 실행합니다.

# 클라이언트가 현재 발화의 끝을 감지했을 때 같은 WebSocket connection으로 전송합니다.

await websocket.send(json.dumps({"type": "Finalize"}))

FINALIZE를 보낸 뒤 결과가 확정됐는지는 gRPC 응답의 is_final 또는 WebSocket 응답의 final 값으로 확인할 수 있습니다.

빈 발화에서 FINALIZE를 보내거나 오디오 없이 FINALIZE를 연속으로 보내면 새 결과가 생성되지 않습니다.

4. FINALIZE 적용 전후 비교

이 실험은 애플리케이션이 정확한 발화 끝을 이미 안다고 가정하고, 그 시점부터 final transcript를 받을 때까지의 시간을 비교한 실험입니다.

실험 구성

AI Hub의 감정이 태깅된 자유대화(성인)에서
발화 50개를 선정했습니다. 선정 기준은 다음과 같습니다.

  • WAV와 전사 라벨이 짝을 이루고, 라벨의 발화 시점이 실제 오디오 범위 안에 있을 것
  • 발화가 끝난 뒤 약 3초 동안 두 화자의 다른 음성이 이어지지 않을 것
  • 특정 화자나 녹음에 발화가 몰리지 않고, 짧은·중간·긴 발화가 고르게 포함될 것
  • 실험에 사용하는 원본 오디오 구간이 서로 겹치지 않을 것

또한, 원본 WAV에는 두 화자의 음성이 서로 다른 스테레오 채널에 저장되어 있습니다. 두 화자의 음성이 함께 입력되면 발화가 겹치거나 이어지는 구간에서 발화 경계가 불명확해질 수 있습니다. 이번 실험에서는 FINALIZE 적용 여부에 따른 차이만 비교하기 위해 채널을 선택해 사용했습니다.

이 기준에 따라 35개 원본 녹음에서 서로 겹치지 않는 발화 구간 50개를 선정했습니다.

실험에서는 FINALIZE를 보내지 않는 Baseline과 전사 라벨의 EndTime에 FINALIZE를 한 번 보내는 Finalize 조건을 비교했습니다.
FINALIZE 요청 여부를 제외한 설정은 다음과 같이 맞췄습니다.

항목 공통 설정
입력 오디오 두 조건에 동일한 원본 녹음 구간
오디오 형식 LINEAR16
샘플링 레이트 16kHz
전송 단위 100ms
전송 속도 실제 오디오 재생 시간과 같은 속도
모델 sommers_ko
도메인 CALL
서버 end-point detection 설정 epd_time=0.5 (0.5초)
실행 횟수 50개 발화를 조건별 1회, 총 100건

이번 실험은 두 조건 모두 epd_time=0.5로 설정했습니다. 따라서 Baseline은 이 설정에 따른 서버의 발화 종료 판단을 기다리고, Finalize 조건은 라벨 EndTime에서 해당 대기를 생략하도록 요청합니다.

epd_time을 더 짧게 설정하면 Baseline도 빨라져 FINALIZE 적용 전후의 차이가 작아질 수 있고, 더 길게 설정하면 차이가 커질 수 있습니다.
조건 발화 경계에서의 동작
Baseline 별도 요청 없이 서버의 end-point detection 대기
Finalize FINALIZE 요청을 한 번 전송

측정값은 라벨 EndTime부터 해당 발화의 마지막 부분을 포함한 final transcript가 도착할 때까지의 시간입니다.

실험 결과

50개 발화는 두 조건에서 모두 정상 완료됐습니다. P50은 중앙값이며, P90과 P95는 각각 발화의 90%와 95%가 해당 시간 안에 결과를 받았다는 뜻입니다.

조건별 final transcript 수신 시간

조건 P50 P90 P95
Baseline 702.14ms 747.15ms 781.54ms
Finalize 24.24ms 87.38ms 101.01ms

같은 발화에서 단축된 시간

Paired 비교 지표 결과
중앙 단축 시간 636.37ms
평균 단축 시간 642.05ms
FINALIZE 조건이 더 빨랐던 발화 50/50

FINALIZE를 보낸 조건이 모든 발화에서 더 빨랐고, 중앙 단축 시간은 636.37ms였습니다.

이번 실험은 실제 VAD 대신 전사 데이터 라벨의 EndTime을 사용했습니다. 따라서 결과는 발화 끝을 정확히 안다는 조건에서 서버의 대기가 얼마나 달라지는지 보여주며, VAD의 판단 시간과 오차를 포함한 실제 음성 에이전트의 전체 지연을 의미하지 않습니다.

5. 활용 사례

FINALIZE는 애플리케이션이 자체 VAD, turn detector로 사용자 발화의 끝을 판단할 수 있을 때 유용합니다.

Callbot

예약, 주문 접수, 본인 확인처럼 대화 흐름과 예상 답변이 비교적 정해진 Callbot에서는 VAD로 사용자의 음성이 멈춘 시점을 감지하고, 업무 흐름상 답변이 끝났다고 판단되면 FINALIZE를 보낼 수 있습니다. 이를 통해 서버의 발화 종료 판단을 기다리지 않고 final transcript를 받아 다음 질문이나 업무 처리를 더 빠르게 시작할 수 있습니다.

Voice Agent

Voice Agent는 사용자의 음성을 이해해 대화의 맥락에 맞는 응답을 생성하거나 필요한 작업을 수행한 뒤 음성으로 답하는 시스템으로, 전화·상담·회의 등 다양한 환경에서 활용됩니다. 본인 확인처럼 답변 형식이 정해진 경우와 달리, 상담이나 범용 Voice Assistant의 자유로운 대화에서는 생각 중의 쉼이나 머뭇거림도 하나의 사용자 턴에 포함될 수 있습니다. 따라서 서버의 epd_time은 충분히 유지하되 VAD나 turn detector가 사용자 턴의 끝을 판단한 시점에 FINALIZE를 보내는 방식을 사용할 수 있습니다.

프레임워크 연동 예시

이 구조는 오픈소스 Voice Agent 프레임워크에서도 활용할 수 있습니다. LiveKit Agents는 실시간 오디오를 STT·LLM·TTS 파이프라인으로 처리하고 VAD, turn detection 및 interruption을 구성할 수 있는 프레임워크입니다. RTZR plugin for LiveKit Agents을 활용하면 RTZR 스트리밍 STT를 LiveKit Agents와 연동하고, VAD나 turn detector가 판단한 사용자 턴의 끝에 FINALIZE를 적용할 수 있습니다.

TEN Framework에서도 VAD와 turn detection 등의 extension을 조합해 실시간 Voice Agent를 구성할 수 있습니다. RTZR ASR extension을 사용하면 RTZR 스트리밍 STT를 TEN Agent graph에 연결하고, turn controller가 판단한 사용자 턴의 끝을 asr_finalize로 전달해 같은 WebSocket 연결에서 FINALIZE를 처리할 수 있습니다. 이를 통해 다음 턴의 연결을 유지하면서 서버의 end-point detection 대기를 줄일 수 있습니다.

6. 마무리

애플리케이션이 발화 끝을 먼저 판단할 수 있다면, FINALIZE로 대기를 줄이고 다음 응답을 앞당길 수 있습니다. Callbot이나 Voice Agent에 직접 적용해 더 빠르고 자연스러운 대화 흐름을 만들어보세요.

참고 자료

스트리밍 STT | RTZR STT OpenAPI
본 문서는 오디오 스트리밍 입력을 텍스트로 변환할 수 있는 스트리밍 STT API를 구현하는 방식에 대한 가이드를 제공합니다.
rtzr-api/protos/rtzr-stt.proto at main · rtzr/rtzr-api
Contribute to rtzr/rtzr-api development by creating an account on GitHub.

Great! You’ve successfully signed up.

Welcome back! You've successfully signed in.

You've successfully subscribed to 기업을 위한 음성 AI - 리턴제로 blog.

Success! Check your email for magic link to sign-in.

Success! Your billing info has been updated.

Your billing was not updated.