로봇 음성 스택을 플러그인으로 분리하기: STT/TTS 교체 비용 줄이는 아키텍처
로봇 음성 인터랙션은 모델 하나 잘 붙인다고 끝나지 않는다. 마이크, 스피커, 네트워크, 엔진 교체, 현장 노이즈까지 모두 고려해야 한다.
이 글은 STT/TTS 기반 로봇 어시스턴트를 플러그인 구조로 설계한 이유와 효과를 정리한다.
문제 정의
- 음성 엔진 공급자/버전 교체가 자주 발생
- 장치 제어와 대화 로직이 강결합되면 장애 전파가 큼
- 현장별 환경 차이로 단일 구현이 쉽게 깨짐
선택
핵심 컴포넌트를 분리했다.
- 음성 I/O 처리
- STT/TTS 엔진 어댑터
- 대화 흐름 제어
- 우선순위/규칙 모델
이 분리는 기능 확장보다 운영 안정성을 위한 선택이었다.
플러그인 인터페이스
class STTProvider:
def transcribe(self, audio_chunk: bytes) -> str: ...
class TTSProvider:
def synthesize(self, text: str) -> bytes: ...
엔진별 포맷 차이, 인증 방식, timeout 정책은 어댑터 내부에 격리했다. 상위 레이어에서 공급자별 분기문이 늘어나지 않게 한 것이 핵심이다.
효과
- 엔진 교체 비용 감소
- 장애 영향 범위 축소
- 현장별 커스터마이징 단순화
운영 체크리스트
- 엔진별 timeout/retry 정책 분리
- fallback 엔진 순서와 강등 조건 명시
- 세션 ID 기반 로그 통합
- WER/latency/interrupt 지표를 공급자별로 분리 수집
장애 격리 전략
- STT 실패: 텍스트 입력 fallback
- TTS 실패: 텍스트 응답 fallback
- 오디오 장치 실패: 세션 재협상 후 재시도
플러그인 경계가 명확하면 음성 엔진 한 곳의 장애가 대화 시스템 전체 중단으로 번지는 것을 막을 수 있다.
오디오 포맷 표준화
엔진마다 입력 포맷이 달라서, 포맷 변환을 호출부에서 처리하면 오류가 반복됐다.
그래서 내부 표준 포맷(예: PCM 16kHz mono)을 고정하고, 각 엔진 어댑터에서만 변환하도록 경계를 통일했다. 이 결정이 디버깅 시간을 가장 크게 줄였다.
배포 전략
플러그인 단위 버전 배포를 지원해, 엔진 교체를 전체 앱 배포와 분리했다.
덕분에 특정 공급자 장애가 발생해도 핵심 대화 플로우를 건드리지 않고 부분 롤백이 가능해졌다.
참고 및 인용
참고: ROS pluginlib은 런타임 플러그인 로딩으로 구현 결합도를 낮추는 표준 메커니즘을 제공한다. pluginlib
참고: OpenAI Speech-to-Text 가이드는 플러그인 공급자 구현 예시 중 하나로, 입력 포맷/지연/정확도 트레이드오프를 설명한다. Speech to text
참고: W3C SSML 1.1은 엔진별 차이를 줄이기 위한 음성 합성 마크업 표준으로, TTS 공급자 교체 시 호환 기준으로 활용된다. Speech Synthesis Markup Language (SSML) Version 1.1