결론 먼저 — interim_results=true 없이는 음성 UX가 깨진다
| 옵션 | 설정 | 영향 |
|---|---|---|
| API 종류 | StreamingRecognize | partial result 수신 가능 |
| interim_results | true | 발화 중 실시간 텍스트 |
| single_utterance | true (음성 turn 1회) | 발화 끝 자동 감지 |
| sample rate | 16000Hz 권장 | 모바일 마이크와 호환 |
| audio chunk | 약 100ms 단위 | latency / 효율 균형 |
| language code | ko-KR (또는 해당 언어) | 명시 필수 |
“streaming + interim_results”가 음성 turn의 latency 인상을 가장 크게 바꾼다.
왜 streaming이 비-streaming보다 빠른가
비-streaming(Recognize) API는 발화 전체를 받아서 한 번에 transcript를 돌려준다. 사용자가 말 끝낸 시점부터 결과까지의 지연이 한 통째로 묶인다.
streaming(StreamingRecognize) API는 audio chunk를 흘려보내고 partial result를 받는다.
- 사용자가 말하는 동안 이미 transcript가 만들어지고 있다
- 발화 끝이 감지되면 final result만 받으면 됨
- 발화 끝과 결과 사이의 추가 지연이 작다
음성 대화 turn에서는 이 차이가 사용자 인상 자체를 가른다.
audio chunk 크기 — 너무 작아도, 너무 커도 손해
- chunk가 너무 크면 (예: 500ms): partial result 갱신 빈도가 낮아 “느리다”는 인상
- chunk가 너무 작으면 (예: 20ms): API 왕복 횟수 증가 / 일부 환경에서 throttle
- 약 100ms 단위가 일반적으로 권장됨 (Google Cloud STT 문서)
WebRTC와 결합하면 RTP 패킷 한 개의 audio payload가 보통 20ms이므로, 5개를 모아서 100ms로 보내는 패턴이 자연스럽다.
single_utterance — 발화 끝 자동 감지
음성 turn 1회를 다루는 경우 single_utterance=true가 편하다.
- 발화 끝이 감지되면 STT가 자동으로 stream 종료
- LLM 호출을 시작할 시점이 곧바로 결정됨
- 긴 받아쓰기(메모, 회의록 등)에는 false로 두고 발화 종료를 직접 감지하는 게 적절
endpointing 임계 — latency를 결정하는 숨은 변수
발화 종료 감지의 침묵 임계가 STT 지연의 가장 큰 부분 중 하나다 (4구간 latency 분해에서 다룸).
- 기본값은 보통 안전하게 길게 잡혀 있음 (수백 ms)
- 음성 상담처럼 빠른 turn-taking이 필요한 경우 짧게 (예: 약 350ms)
- 너무 짧게 두면 사용자 호흡 중 짧은 침묵을 발화 끝으로 오인
interim_results를 LLM 호출과 어떻게 결합하나
partial result가 들어오는 동안 LLM의 prompt skeleton을 미리 구성하면 단계 2(STT 결과 → LLM 입력)의 시간이 거의 0에 가까워진다.
- partial result 도착 → prompt 끝부분에 partial을 살짝 끼워 prompt skeleton 갱신
- final result 도착 → 마지막 갱신 + LLM stream 호출 즉시 시작
- LLM 첫 토큰 도착 → TTS stream 시작
partial을 prompt에 미리 넣어 LLM을 미리 호출하면 더 빠르지만, partial이 final과 어긋날 위험이 있다. 안전한 패턴은 final 시점에 호출 + prompt 구성은 미리.
개인 메모 — 처음에는 final 결과만 받았다
데모를 처음 짤 때는 StreamingRecognize의 interim_results를 false로 두고 final transcript만 받았다. 코드가 단순해 보였고, “어차피 LLM은 final로 돌리니 partial은 필요 없다”고 생각했다.
그런데 음성 turn으로 옮기고 나서 인상이 바뀌었다. final이 들어올 때까지의 침묵이 듣는 입장에서 “이 시스템이 듣고 있나?”라는 인상을 만든다. interim_results를 켜고 partial을 디버그 로그에 찍기 시작한 순간, 음성 데모의 흐름이 다른 종류로 느껴졌다 — 진짜로 “듣고 있는” 시스템이 됐다는 인상.
이 한 가지 옵션이 가장 작은 코드 변경으로 가장 큰 인상 차이를 만들었던 적이 처음이었다.
답글 남기기