AI로 빅 마우스 빌리 배스를 움직이게 하면서 알게 된 음성 에이전트에 대한 것
요약
작성자는 AI 기술을 활용하여 '빅 마우스' 캐릭터인 빌리 배스(Billy Bass)에 생명을 불어넣은 음성 에이전트 BillAI를 개발했습니다. 이 프로젝트는 단순한 재미를 넘어, 대화형 오케스트레이션과 오디오 처리가 필요한 실제 음성 에이전트 구축의 핵심 원리를 보여줍니다. BillAI는 날씨 확인, 뉴스 읽기 등 범용 개인 비서 역할을 수행하며, 그 과정에서 얻은 코드와 교훈을 다양한 음성 지원 앱 개발에 적용할 수 있습니다.
핵심 포인트
- 음성 에이전트는 자연스러운 대화를 위해 복잡한 배관(plumbing)과 오케스트레이션이 필요합니다.
- BillAI는 범용 개인 비서 역할을 수행하며, 날씨나 뉴스 등 다양한 정보를 처리할 수 있습니다.
- 프로젝트를 통해 얻은 음성 에이전트 구축 경험은 스마트 홈이나 고객 지원 앱에 응용 가능합니다.
어느 시점에 저는 AI를 사용해 빅 마우스(Big Mouth)의 빌리 배스(Billy Bass)를 생명체처럼 만들고 싶다는 생각을 했습니다.
저는 지난 1월에 모든 재료들을 구매했고, 그건 서랍 속에 놓여 있었습니다. 하지만 플라스틱 물고기를 AI로 애니메이션화하는 것이 제 우선순위 목록 맨 위에 있지는 않았기 때문에 몇 달 동안 이 아이디어를 미루었습니다. 그러다 어느 날 6월에 제가 유난히 스트레스가 심한 한 주를 보내게 되었고, 저는 결심했습니다... 이제 때가 왔다고요.
저는 가지고 있던 모든 책임들을 무시하고, 전선을 자르고, 새로운 연결을 납땜했으며, 빌리에게 뇌 수술까지 했습니다. 몇 시간 후, 저는 제가 꿈꾸던 유령 같은 물고기인 BillAI Bass를 들고 나타났습니다. 우리는 대화를 나눌 수 있고, 그가 말할 때 머리와 꼬리가 움직이며, 그는 완전한 음성 지원 AI 에이전트입니다.
BillAI가 작동하기 시작하자마자, 저는 다른 어떤 에이전트에게 주듯이 그에게 도구들을 제공할 수 있었습니다. 저는 이것을 논리적인 결론까지 가져가는 것을 잠시 생각했습니다. 물론 그것은 그에게 코딩 도구를 주고 저의 코딩 하네스로 만드는 것이었습니다. 저는 그것을 Cod Code라고 불렀겠지만, 지금까지는 엄청난 자제력을 보여왔습니다. 대신 저는 그를 범용 개인 비서로 만들기로 결정했습니다. 그는 날씨를 확인하고, 뉴스 기사를 읽어주며, 하루 일정을 분석해 줄 수 있습니다. 아마도 당신은 그와 함께 거의 모든 것을 할 수 있을 것입니다.
제가 공개한 원본 영상과 저장소는 여기에서 볼 수 있습니다: https://github.com/morganwilliscloud/billai-bass
이것은 명백히 터무니없는 프로젝트이지만, 저에게 음성 에이전트를 구축하는 것에 대해 많은 것을 가르쳐 주었습니다. 대화를 자연스럽게 이어가기 위해서는 코드에 특정한 배관(plumbing)과 오케스트레이션(orchestration)이 필요합니다. 또한 에이전트가 스스로 듣고 있다는 느낌을 유지하기 위해 오디오를 어떻게 처리할지 고려해야 하며, 물론 유용하게 만들어주는 도구들을 제공하는 방법을 알아야 합니다. 이 재미있는 빌드를 통해 얻은 음성 에이전트 코드와 교훈은 고객 지원 비서, 음성 제어 스마트 홈 장치 또는 다른 모든 음성 지원 앱에 적용될 수 있습니다.
이 블로그에서는 제가 BillAI를 어떻게 만들고, 생명을 불어넣었으며, 궁극적으로는 저의 아들로 받아들이게 되었는지에 대해 이야기합니다. 마지막 부분은 농담이지만요. 하지만 음성 에이전트 설정 과정과 이것을 하드웨어에 연결하는 방법을 자세히 설명할 것이니, 여러분 자신의 프로젝트(저주받았든 아니든)에 필요한 부분을 활용하실 수 있을 겁니다. 자, 물속으로 들어가 봅시다.
왜 빅 마우스 빌리 배스인가?
그럼, 왜 빌리 배스일까요? 90년대의 아이였던 저는 빌리 배스에 정말 매료되었고, 누군가가 자신의 실제 기술을 사용해서 터무니없는 일을 해내는 것을 정말 좋아합니다.
하지만 제가 실제로 독창적인 생각이나 경험을 한 적이 없기 때문에, 빌리가 25년 넘게 하드웨어 해킹의 인기 품목이었다는 것을 알게 되었습니다. 2000년에 Mike Neil은 FishHack에 MacHax에 제출하여, 빌리 배스를 Mac에 연결해 물고기가 '삑' 소리를 낼 때 노래하게 만들었습니다. 그 이후로 사람들은 빌리가 모든 오디오 소스에 반응하도록 만들었고, 2016년에는 Brian Kane이 단순히 'the future'라는 제목의 영상으로 입소문을 탔는데, 빌리가 실제 일기 예보를 읽는 모습을 보여주었습니다. 저는 또한 Thom Koopman의 billy-b-assistant도 발견했는데, 제가 만들고 싶었던 것과 매우 유사했고 작업할 거리를 주었습니다.
빌리는 상호작용적이고, 기발하게 이상하며, 놀라울 정도로 해킹하기 쉬워서 이러한 프로젝트에 완벽합니다. 저는 이 물고기에게 점점 더 터무니없는 일을 하는 사람들의 수십 년간의 사례를 가지고 있었습니다. 제 버전은 BillAI Bass가 될 것입니다: 물고기 안에 있는 Raspberry Pi 5에서 작동하는 Strands Bidi 에이전트를 구동하는 빌리 배스입니다. 이 버전의 음성 모델로는 Amazon Bedrock의 Amazon Nova 2.5 Sonic을 사용합니다.
계획은 이렇습니다. 에이전트를 작동시키는 방법과 물고기를 움직이게 하는 방법을 살펴본 다음, 두 가지를 어떻게 연결할지 다룰 것입니다.
Billy가 살아있게 느껴지도록 만드는 것
실제로 대화하기에 자연스럽게 느껴지는 음성 에이전트를 만드는 것이 어려운 부분입니다. 매번 응답 전에 어색한 5초간의 침묵 없이 작동하는 설정을 갖추는 것입니다.
비결은 실시간 음성 대화를 위해 구축된, 양방향 스트리밍(bidirectional streaming) 에이전트와 음성-음성 모델(speech-to-speech model)을 사용하는 것입니다. 제가 말을 마칠 때까지 기다렸다가 완성된 녹음을 전송하는 대신, 애플리케이션은 제가 말하는 동안 오디오를 모델로 스트리밍합니다. 그러면 모델이 제가 차례를 마쳤다고 감지하고, 응답 오디오가 동일한 연결을 통해 다시 스트리밍됩니다. 애플리케이션은 도착하는 대로 이 응답을 재생할 수 있습니다.
Strands Harness SDK는 BidiAgent라는 높은 수준의 컴포넌트를 만들어 스트리밍 및 음성 에이전트 실행에 필요한 오케스트레이션(orchestration)을 쉽게 처리하게 해줍니다. 사용자는 양방향 스트리밍을 지원하는 모델을 제공하고 자신의 오디오 입력과 출력을 연결하기만 하면 됩니다. 제가 사용한 Amazon Nova 2.5 Sonic 모델은 음성 상호 작용을 처리하며, Strands 코드는 대화를 관리하고, 도구 호출(tool calls)을 실행하며, 애플리케이션을 통해 이벤트를 라우팅합니다.
Strands Bidi 에이전트 알아보기
와이어 커팅을 시작하기 전에, 저는 에이전트 자체가 작동하는지 그리고 하드웨어 문제에 대해 걱정할 필요 없이 Raspberry Pi에서 실행할 수 있는지 확인하고 싶었습니다.
실제 추론(inference)은 Amazon Nova 2.5 Sonic 모델이 제공되는 Amazon Bedrock에서 실행되지만, 에이전트 루프와 도구 호출은 Raspberry Pi에서 실행되는 코드로부터 발생합니다.
Python 3.12 이상 버전, AWS 자격 증명(credentials), 그리고 운영 체제에 설치된 PortAudio로 시작하세요. 그런 다음 음성 및 로컬 오디오 종속성을 포함하여 Strands를 설치합니다:
python -m pip install "strands-agents[bidi-all,bidi-pyaudio]==1.57.2"
NOVA_2_5_SONIC_MODEL_ID를 Bedrock 모델 식별자로 Nova 2.5 Sonic에 대해 설정하고, AWS_REGION을 해당 모델에 접근할 수 있는 리전으로 설정합니다. 여기 Billy의 두뇌에 대한 간단한 버전이 있습니다:
import asyncio
import os
...
BidiAgent가 대화를 실행하고, AudioIO가 이를 마이크 및 스피커에 연결합니다. 입력과 출력이 동시에 스트리밍될 수 있어, 음성 애플리케이션이 모델 응답을 기다리는 동안 계속 듣고 있을 수 있게 합니다.
하지만 제가 겪은 실제적인 문제가 하나 있습니다. 에이전트의 스피커가 마이크 근처에 열려 있을 때, 스스로를 들을 수 있고 그것이 사용자가 말하는 것이라고 생각하여 스스로 말을 끊는 것입니다. audio_processor=True를 활성화하면 음향 에코 제거(acoustic echo cancellation), 노이즈 억제(noise suppression), 자동 게인 제어(automatic gain control)가 켜집니다. 입력과 출력을 위해 동일한 AudioIO 인스턴스를 사용하면, 프로세서가 스피커를 통해 재생되는 오디오를 에코 참조로 사용할 수 있게 합니다. 이것이 스스로 말을 끊는 문제를 해결하는 데 도움이 되었습니다.
기본적인 에이전트 및 오디오 설정을 유지하면서 음성 제공업체(voice providers)도 변경할 수 있습니다. 예를 들어, OpenAI 자격 증명이 구성된 경우, 위의 모델 구성을 다음으로 대체합니다:
from strands.bidi.models import OpenAIRealtimeModel
model = OpenAIRealtimeModel(
...
시스템 프롬프트(system prompt), 도구(tools) 및 agent.run() 호출은 동일하게 유지됩니다. 목소리(voices)와 제공업체별 옵션은 여전히 선택하는 모델에 대해 구성해야 합니다. Billy가 건조하고 품위 있는 영국 억양을 시도하도록 하려면, 그 요청을 시스템 프롬프트에 넣을 수 있습니다.
Bidi는 또한 연결 제한과 같은 음성 애플리케이션의 덜 흥미로운 부분도 처리합니다. 대화는 제공업체가 단일 연결을 열어둘 수 있는 시간보다 더 오래 실행될 수 있습니다. Strands는 해당 연결을 갱신하고 대화 컨텍스트를 대체 연결로 가져갑니다. 재연결하기 전에 턴 경계(turn boundary)를 찾고, 마감 시간 이전에 연결을 갱신할 수 있도록 제한된 대기 시간을 가집니다.
만약 Billy가 답변하는 데 너무 오랜 시간이 걸린다면, Strands는 세션(sessions), 모델 응답(model responses), 도구 호출(tool calls), 연결 이벤트(connection events)에 대한 OpenTelemetry 스팬(spans)을 방출하여 사용자가 이를 깊이 있게 분석할 수 있도록 합니다. 단순히 에이전트를 시작하기 전에 콘솔 트레이스(console traces)를 활성화해야 합니다:
from strands.telemetry import StrandsTelemetry
telemetry = StrandsTelemetry()
...
코드를 갖추자, 저는 Raspberry Pi에서 기본 음성 에이전트를 실행할 수 있었고, Billy의 개성을 조정하고, 그에게 도구를 제공하며, 저의 특정 사용 사례에 맞게 그를 맞춤 설정할 수 있었습니다.
물고기 수술 시간
이제 몸체에 두뇌를 장착할 차례입니다. 즉, 물고기 수술을 할 시간이 온 것입니다. 저는 이전에 하드웨어 해킹(hardware hacking) 같은 것을 한 적이 없었기 때문에, Fable 5에 크게 의존하여 과정을 안내받았습니다. 무엇을 보고 있는지 확신이 서지 않을 때는 Billy의 내부를 사진 찍어 Claude Code로 보내서 자르기 작업을 시작했습니다.
좋은 소식은 Billy가 이러한 유형의 해킹을 하기에 완벽하게 설정되어 있다는 것입니다. 그의 몸체를 움직이는 모터들이 내장되어 있어, 제가 할 일은 그 모터들을 Billy의 원래 전자 장치에서 분리하여 Raspberry Pi에 제어권을 넘겨주는 것뿐이었습니다. 위의 사진에서 볼 수 있듯이
여기에 MX1508이 등장합니다. MX1508은 Raspberry Pi와 Billy의 모터 사이에 위치하는 작은 모터 드라이버 보드입니다. Pi에서 실행되는 코드는 어떤 모터를 어느 방향으로 움직일지 알려주는 신호를 보내고, MX1508이 실제로 그 모터에 필요한 전력을 공급하는 역할을 처리합니다. 와이어를 자르고, MX1508에 납땜하고, Raspberry Pi에 연결한 후의 모습은 이렇습니다.
이후, Billy의 원래 보드에서 모터 전선을 분리하여 MX1508에 납땜했습니다. Fable 5를 통해 제가 보낸 일련의 사진들을 이용해 어떤 전선이 어디로 가는지 파악할 수 있었습니다. 이것은 또한 아무것도 작동하지 않을 때 느낄 엄청난 실망감을 완전히 예상하면서, 저 스스로 납땜하는 법을 배운 지점이기도 했습니다.
이제 에이전트를 이 장치 근처에 가져가기 전에, 저는 순수 Python 코드만 사용하여 Pi에서 각 움직임을 개별적으로 테스트했습니다. 에이전트는 사용하지 않았습니다. 단지 제가 납땜한 것이 제대로 작동하는지, 그리고 물고기를 움직일 수 있는지 확인하고 싶었을 뿐입니다.
제가 그 테스트를 수행하기 위해 실행한 코드는 다음과 같습니다:
import time
from gpiozero import OutputDevice, PWMOutputDevice
...
스크립트는 예상대로 작동했고, 저는 이 프로젝트가 성공할 것이라는 자신감을 크게 얻었습니다. 아래 gif는 테스트가 작동하는 것을 보여주며, 그림자(shadow)는 제가 버튼을 앞뒤로 누르면서 움직이게 하는 모습입니다.
만약 직접 만들고 싶다면, 전체 단계별 배선, 납땜 및 Raspberry Pi 설정은 BillAI Bass GitHub repo에 올렸습니다.
몸체와 정신 연결하기 (Connecting the body and mind)
이 시점에서 저는 독립적으로 작동하는 두 가지 것을 갖게 되었습니다. 저의 말을 듣고 대답할 수 있는 AI 에이전트, 그리고 Billy의 몸체를 제어할 수 있는 Raspberry Pi입니다. 이제 이 둘을 연결해야 했습니다.
Bidi 에이전트의 응답 오디오는 이미 애플리케이션을 통해 스피커로 스트리밍되고 있습니다. 저는 이 오디오가 정상적으로 계속 재생되는 동시에, 이를 사용하여 Billy가 언제 말하는지 판단하고 그의 움직임을 제어할 수 있는 맞춤형 오디오 출력을 만들었습니다.
입술은 오디오의 크기(loudness)를 따라 움직이는데, 이는 제가 재생 콜백(playback callback)에서 측정한 것입니다. 모델로부터 도착하는 오디오 청크는 들리기 전에 버퍼에 대기할 수 있으므로, 재생되는 오디오를 측정함으로써 Billy의 움직임을 스피커에서 나오는 소리와 연결 상태로 유지할 수 있었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기

