AI 음성 에이전트를 자연스럽게 만드는 것은 합성 음성 품질보다 저지연 스트리밍과 인터럽션 처리라는 공학적 도전이다. 이 튜토리얼은 Python, FastAPI, Twilio Conversation Relay, OpenAI를 사용해 토큰 단위 스트리밍과 asyncio.Task 기반 인터럽션 취소를 구현하고, 시스템 프롬프트 설계로 AI 음성의 자연스러움을 개선하는 방법을 단계별로 다룬다.
핵심 포인트- 저지연의 핵심: OpenAI 응답을 토큰 단위로 WebSocket을 통해 Twilio에 직접 스트리밍해 500ms 이내 첫 응답 달성
- 인터럽션 처리: `interrupt` 이벤트 수신 시 `active_task.cancel()`로 asyncio 태스크 즉시 취소 + Twilio에 `{type: "clear"}` 전송
- ElevenLabs TTS + Deepgram Nova-3 조합이 자연스러운 음성 출력의 기반
- 시스템 프롬프트: 2문장 이내 응답, 구어체 필러("Oh...", "Uhm..."), 마크다운/이모지 금지, 숫자 영문 발음
- SSML `<break time="200ms"/>` 태그로 LLM이 출력한 `...`을 명시적 호흡 구간으로 변환
상세 정리- FastAPI + uvicorn: POST `/voice`(TwiML 반환)와 WebSocket `/ws`(대화 처리) 엔드포인트 구성
- 각 발화 시작 시 이전 `active_task` 취소 후 `asyncio.create_task()`로 새 스트리밍 시작
- `asyncio.CancelledError`를 except로 정상 취소 처리 — 예외가 아닌 의도적 제어 흐름
- OpenAI `AsyncOpenAI` 비동기 클라이언트로 `gpt-4o` 스트리밍 API 사용
- 토큰 루프: `async for chunk in stream` → `choices[0].delta.content` 추출 → `{type: "text", token, last: false}` 전송
- 마지막 토큰에 `last: true` 플래그로 Twilio에 발화 턴 종료 신호
- TwiML 설정: `ttsProvider="ElevenLabs"`, `speechModel="nova-3-general"`, `ignoreBackchannel="true"`, `eotThreshold="0.8"`
- 테스트: ngrok으로 로컬 포트 노출 → Twilio 콘솔 웹훅에 `/voice` URL 등록
왜 읽나FastAPI와 asyncio.Task 기반 인터럽션 취소 패턴을 Python으로 구현해 AI 음성 에이전트의 자연스러움과 반응성을 500ms 이내로 끌어올리는 실전 코드 가이드다.