AI 마이크로서비스를 로컬에서 실행할 때 아무도 말하지 않는 인증 문제
요약
클라우드 없이 로컬 환경에서 실행되는 실시간 음성 AI 에이전트(AI-RTC-Agent)의 아키텍처와 인증 문제를 다룹니다. WebRTC와 FastMCP를 활용하여 지연 시간을 최소화한 오디오 파이프라인 설계 방식을 설명합니다.
핵심 포인트
- 로컬 환경의 제약(GPU, 클라우드 없음)을 극복하는 인증 프로토콜 설계
- WebRTC DataChannel을 활용한 저지연 전사(transcript) 데이터 전송
- VAD 성능을 위한 16kHz와 48kHz 이중 버퍼링 오디오 파이프라인
- Whisper 모델의 싱글톤 로드를 통한 콜드 스타트 방지
대부분의 음성 AI 튜토리얼은 당신에게 API 키와 클라우드 엔드포인트(cloud endpoint)가 있다고 가정합니다. 하지만 제가 만든 것은 제 앞에 있는 기기에서 실행되어야 했습니다. 4GB GPU, 클라우드 없음, 관리형 인증 계층(managed auth layer) 없음과 같은 조건이었죠.
이러한 제약 사항은 제가 어디에서도 본 적 없는 문제를 해결하도록 강제했습니다. 데이터베이스, 저장소(repo)에 보관된 공유 비밀키(shared secret), 또는 세션 관리자(session manager) 없이 두 개의 로컬 Python 프로세스 간의 요청을 어떻게 인증할 것인가 하는 문제였습니다.
이것은 제가 무엇을 만들었는지, 그리고 제가 처음부터 설계해야 했던 인증 프로토콜(auth protocol)에 대한 이야기입니다.
내가 만든 것
AI-RTC-Agent는 완전히 로컬에서 작동하는 실시간 음성 에이전트(real-time voice agent)입니다. 아키텍처는 네 개의 격리된 계층으로 구성됩니다:
- React 클라이언트 (React client) — 마이크 오디오를 캡처하고, 네이티브
RTCPeerConnection을 사용하여 WebRTC를 통해 스트리밍합니다. - Python WebRTC 서버 (Python WebRTC server) — 48kHz PCM 프레임을 수신하고, VAD를 실행하며, 발화(utterances)를 세그먼트(segments)화합니다.
- FastMCP 서버 (FastMCP server) — STT를 위해 Whisper small을 실행하며, 이메일, 캘린더 및 검색 도구를 제공합니다.
- 에이전트 계층 (Agent layer) — OpenAI, Gemini, 그리고 로컬 Ollama를 위한 어댑터(adapters)를 갖춘 LLM 의도 라우팅(intent routing)을 수행합니다.
데이터 흐름은 다음과 같습니다:
브라우저 마이크 → WebRTC (48kHz PCM) → VAD 세그먼테이션 → FastMCP (Whisper STT) → WebRTC DataChannel을 통해 다시 전송되는 전사(transcript)
반환 경로에는 HTTP 왕복(round-trip)이 없습니다. 전사(transcript)는 WebRTC DataChannel을 통해 직접 푸시되어 지연 시간(latency)을 낮게 유지합니다.
오디오 파이프라인 (The Audio Pipeline)
인증으로 넘어가기 전에, VAD 파이프라인을 설명할 가치가 있습니다. 왜냐하면 이것이 전체 세그먼테이션(segmentation) 설계를 주도하기 때문입니다.
브라우저는 48kHz 모노 PCM을 스트리밍합니다. 서버는 16kHz를 요구하는 webrtcvad를 실행하므로, 모든 들어오는 프레임은 이에 맞춰 데시메이션(decimated)됩니다. 하지만 중요한 점은, Whisper에 16kHz 오디오를 입력해서는 좋은 결과를 기대할 수 없다는 것입니다. 따라서 시스템은 동일한 스트림에서 두 개의 별도 버퍼(buffer)를 유지합니다:
webrtcvad에 의해 공격성(aggressiveness) 3 설정으로 300ms 슬라이딩 윈도우(sliding window) 상에서 평가되는 16kHz 버퍼- Whisper를 위한 실제 음성 프레임을 축적하는 raw 48kHz 버퍼
VAD가 2초 연속 침묵을 감지하면, 해당 발화(utterance)가 완료된 것으로 간주합니다. raw 48kHz 버퍼는 WAV 헤더와 함께 래핑(wrapped)되어 base64로 인코딩된 후, 전사(transcription)를 위해 FastMCP 서버로 전송됩니다.
Whisper는 서버 부팅 시 LoadModelService를 통해 모듈 수준의 싱글톤(singleton)으로 사전 로드(preloaded)되므로, 첫 번째 발화 시 콜드 스타트(cold-start) 페널티가 발생하지 않습니다.
인증 문제 (The Auth Problem)
여기서부터 흥미로운 지점이 나타납니다.
WebRTC 서버와 FastMCP 서버는 localhost HTTP를 통해 통신하는 두 개의 별개 프로세스입니다. 운영 환경(production)이라면 앞에 리버스 프록시(reverse proxy)를 두거나, mTLS를 사용하거나, 시크릿 매니저(secrets manager)에 비밀 값을 저장할 것입니다. 하지만 이것은 로컬 개발 워크스페이스입니다. 인프라도, 운영(ops)도, 데이터베이스도 없습니다.
가장 단순한 해결책은 .env 파일에 정적 API 키를 두는 것입니다. 문제는 정적 키는 설정 파일에 머물며, 저장소(repo)에 커밋되고, 절대 교체(rotate)되지 않는다는 점입니다. 로컬 환경이라 할지라도, 오픈 소스 블루프린트(blueprint)에 이러한 습관을 구축하는 것은 좋지 않습니다.
저는 다음과 같은 조건이 필요했습니다:
- **데이터베이스가 전혀 필요하지 않을 것 (zero database)
- 소스 파일에 정적 자격 증명(static credentials)을 남기지 않을 것
- 상태가 없을 것 (stateless) — 프로세스 간 세션 동기화가 필요 없음
- 시간 제한이 있을 것 (time-limited) — 탈취된 키를 재사용할 수 없어야 함
해결책: 결정론적 타임스탬프 기반 인증 (Deterministic Timestamp-Based Auth)
두 프로세스는 독립적으로 동일한 알고리즘을 실행합니다:
class api_key_generator:
def __init__(self, expire_time: int = 5):
self.expire_time = expire_time # 5초 슬라이딩 에포크 윈도우 (sliding epoch window)
...
접두사(prefix)와 접미사(suffix)는 타임스탬프에 대한 결정론적 함수인 math.sqrt(math.log10(timestamp))로부터 유도됩니다. 따라서 양측 모두 임의의 5초 윈도우에 대해 예상되는 키를 독립적으로 계산할 수 있습니다.
검증 방식:
- WebRTC 서버가 키를 생성하고, 이를
X-API-Key헤더로 추가하여 오디오 페이로드 (audio payload)를 전송합니다. - FastMCP 미들웨어 (middleware)가 요청을 가로채 헤더를 추출하고, 포함된 타임스탬프 (timestamp)를 파싱합니다.
- 미들웨어는 해당 타임스탬프 윈도우 (window)에 대해 예상되는 키를 독립적으로 생성합니다.
- 문자열이 일치하고 타임스탬프가 하나의 유예 기간 (grace interval, 5초) 이내라면 요청이 인증됩니다.
- 윈도우 외부에서 키가 재사용 (replay)되는 경우 — 거부됩니다.
이를 통해 얻는 이점:
- 데이터베이스 없음, 자격 증명 (credential) 저장소 없음
- 키는 5초마다 자동으로 만료됨
- 탈취된 키는 윈도우가 닫힌 후에는 무용지물임
- 두 프로세스 모두 완전히 상태 비저장 (stateless) 상태를 유지함
MCP 레이어 (The MCP Layer)
FastMCP 서버는 핵심적인 역할을 수행하는 마이크로서비스 (microservice)입니다. Whisper STT (음성-텍스트 변환) 외에도 다음과 같은 기능을 노출합니다:
- 메일 도구 (Mail tools) — 스레드 헤더 (
In-Reply-To,References)를 포함한 SMTP 송신/회신 - 캘린더 도구 (Calendar tools) — OAuth가 구성되지 않은 경우
.ics폴백 (fallback)을 지원하는 Google Calendar API - 검색 도구 (Search tools) — 토큰 버킷 (token-bucket) 속도 제한기 (rate limiter, 1.0 req/sec)를 적용한 DuckDuckGo
모든 도구의 응답은 통합 응답 파서 (response parser)인 ok(data, message), err(message, code), paginated(items, total)를 거칩니다. 레이어 전체에 걸쳐 일관된 형태를 유지하므로 테스트가 깔끔해집니다.
테스트 (Testing)
VAD 서버와 MCP 도구 레이어 모두 완전한 pytest 제품군을 갖추고 있습니다:
# FastMCP 도구 테스트
cd mcp && pytest tests/ -v
...
MCP 테스트는 전사 (transcription) 정확도, SMTP 회신 스레딩, 캘린더 파싱, 그리고 속도 제한기 (rate limiter) 동작을 다룹니다.
로컬 실행 (Running It Locally)
요구 사항: Python 3.10+, Node.js 18+, ffmpeg, 그리고 4GB GPU (또는 CPU, 속도는 더 느림).
# 1. Python 의존성 설치
pip install -r requirements.txt
...
마이크에 대고 말한 뒤 2초간 멈추면, 대시보드에 전사 내용이 나타납니다.
리포지토리 (Repo)
github.com/zkzkGamal/AI-RTC-Agent
MIT 라이선스입니다. 인증 프로토콜 (auth protocol) 또는 VAD 파이프라인 (pipeline)에 대한 이슈 (Issues), PR, 질문은 모두 환영합니다. 특히 데이터베이스가 없는 로컬 인증 문제를 해결하는 더 깔끔한 접근 방식을 본 분이 계신지 궁금합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기