전화가 걸려오는 순간부터 AI 목소리가 응답하기까지, 그 사이 전부를 대신 조립해주는 물건.
전화 걸기·받기(통신사 연동), 사람 말을 텍스트로 바꾸기(음성인식), 텍스트를 사람 목소리로 바꾸기(음성합성), "이 사람이 말을 끝냈나?" 판단하기(발화 감지), LLM에게 무슨 말을 할지 물어보기 — 이 다섯 가지를 처음부터 각각 만들려면 수개월이 걸린다. Rapida는 이 다섯 조각을 이미 배선해 두고, 어떤 회사 제품을 쓸지만 설정 파일로 고르게 해준다.
Rapida는 voice AI 오케스트레이션 플랫폼이다. "오케스트레이션(orchestration)"이란 여러 개의 독립된 부품(통신 채널·음성인식·음성합성·LLM 등)이 정확한 순서로, 정확한 타이밍에 서로 데이터를 주고받도록 지휘하는 일을 말한다. 오케스트라 지휘자가 바이올린과 트럼펫에게 각각 언제 소리를 낼지 신호를 주듯, Rapida의 파이프라인은 "지금은 마이크 소리를 들을 차례" "지금은 AI가 답할 차례"를 관리한다.
이 레포가 특별한 이유는 "이걸 자체 서버에서, 자기 데이터로, 자기 브랜드로 돌릴 수 있게" 오픈소스로 공개했다는 점이다. README 첫 문장이 이를 명확히 한다 — Ownership(소유권) · Control(통제권) · Scale(확장성). 관리형 클라우드 서비스로 편하게 시작할 수도 있고, Docker Compose 하나로 자체 서버에 통째로 올릴 수도 있다.
LiveKit·Vapi·Pipecat이 이미 있는데, 왜 또 하나가 뜨는가.
음성 AI 오케스트레이션은 이미 붐비는 시장이다. LiveKit Agents(WebRTC 미디어 서버 기반, Python/Node SDK), Vapi(완전 관리형 SaaS, 코드 없이 대시보드로 설정), Pipecat(Daily.co가 만든 Python 파이프라인 프레임워크)가 대표적이다. Rapida가 트렌딩에 오른 이유는 이들과 겹치지 않는 지점을 정확히 찌르기 때문이다.
| 비교 대상 | 일반적 접근 | Rapida의 접근 |
|---|---|---|
| 배포 형태 | Vapi처럼 완전 관리형 SaaS이거나, Pipecat처럼 코드 프레임워크만 제공(직접 서버·DB·관제 구축 필요) | 플랫폼 + 프레임워크 동시 제공 — Docker Compose로 관리 콘솔(UI)·DB·다중 서비스까지 한 번에 자체 호스팅 |
| 전화 채널 | 대개 Twilio 하나만 1급 지원, 나머지는 직접 연동 | Twilio·Vonage·Exotel·Telnyx·Asterisk·순수 SIP 6종을 코드베이스에 이미 내장 |
| 공급자 전환 | 공급자 교체 시 파이프라인 코드를 다시 짜야 하는 경우가 많음 | STT/TTS 14개, VAD 3개, EOS(발화종료) 3개를 설정 값 하나로 스위칭(팩토리 패턴) |
| 관찰가능성 | 별도 APM 도구를 직접 연결 | Datadog·OpenTelemetry·AWS X-Ray·Azure Monitor·OpenSearch·Google Cloud를 코드에 이미 내장, 토큰 사용량·통화 로그·툴 실행 로그까지 1급 개체로 기록 |
음성 AI 콜센터를 자체 구축하려면 통신 프로토콜(SIP)·실시간 오디오 스트리밍·여러 AI 공급자 API·대화 상태 관리·관측 인프라까지 전부 손대야 한다. 반대로 완전 관리형 SaaS는 빠르지만 내 데이터가 어디 저장되는지, 어떤 모델이 실제로 호출되는지 통제하기 어렵고, 대량 트래픽에서는 비용도 커진다.
소스 전체가 공개돼 있어 무엇이 실제로 일어나는지 코드로 확인할 수 있고, 자체 인프라에 배포해 데이터를 직접 통제하며, 공급자(모델·STT·TTS)를 언제든 설정만으로 바꿀 수 있다. GPL-2.0이라 상업적 화이트라벨(로고 제거)이 필요하면 별도 상용 라이선스로 전환하는 구조다.
Go 백엔드 4개 서비스 + Python 문서 서비스 + React 관리 콘솔 + 20종 넘는 AI 공급자 통합.
go.mod 기준 모듈명은 github.com/rapidaai, Go 1.25다. GitHub 언어 통계로는 Go 약 63%·TypeScript 약 26%·Python 약 9%이며, 실행 파일 4개(cmd/web·cmd/assistant·cmd/endpoint·cmd/integration)가 각각 독립 서비스로 뜬다.
| 서비스 | 기본 포트 | 역할 |
|---|---|---|
| web-api | 9001(내부) | 조직·프로젝트·유저·자격증명(Vault) 관리 — 관리 콘솔의 백엔드 |
| assistant-api | 9007 | 핵심 — 실시간 통화 파이프라인. 전화 수신부터 STT/TTS/VAD/LLM/관측까지 전부 여기 |
| endpoint-api | 9005 | 텍스트 기반 LLM 엔드포인트(음성 없는 챗봇/API 형태) 관리 |
| integration-api | 9004 | 외부 LLM/임베딩 공급자(OpenAI·Anthropic·Gemini·Cohere·xAI 등) 자격증명 검증·호출 중계 |
| document-api (Python) | 9010 | 지식베이스 문서 처리 — 청킹·임베딩·RAG 인덱싱(선택 실행, --with-knowledge) |
| 영역 | 라이브러리 | 역할 |
|---|---|---|
| 웹 프레임워크 | Gin + gRPC + grpc-web | HTTP 라우팅과 서비스 간 고성능 통신을 겸용 |
| 실시간 오디오 | Pion WebRTC v4(dtls·rtcp·rtp·interceptor) | 브라우저 위젯에서 오는 실시간 오디오 스트림 처리 |
| SIP 전화 | sipgo(emiago) | 순수 SIP 프로토콜 스택 — 통신사 없이 직접 SIP 트렁크 연결 |
| ORM/DB | GORM + PostgreSQL + golang-migrate | 스키마 마이그레이션과 데이터 접근 |
| 캐시/큐 | Redis (go-redis) | 세션 상태·속도 제한 |
| LLM SDK | anthropic-sdk-go · openai-go(v1/v3) · OpenRouter · Cohere · mark3labs/mcp-go | 다양한 LLM/툴 공급자와 직접 통신, MCP(Model Context Protocol) 지원 포함 |
| 관측(Observability) | OpenTelemetry(otel) · OTLP gRPC/HTTP exporter | 표준 텔레메트리 파이프라인, 여러 백엔드로 팬아웃 |
| 오디오 코덱 | g711(zaf) · go-audio-resampler(soxr 포함) | 전화망 표준 코덱(G.711 μ-law/A-law) 인코딩·리샘플링 |
React 18.2 + TypeScript 4.6, 빌드는 CRA를 감싼 craco, 스타일은 Tailwind CSS 4. 상태 관리·폼·데이터 테이블 등을 자체 컴포넌트 라이브러리(components/carbon/ — IBM Carbon Design System 스타일 네이밍)로 구성했다. 대화 로그 뷰어, 텔레메트리 모달, 프롬프트 에디터, 실시간 미리보기 음성 에이전트 화면까지 포함된 대형 SPA다.
| 도구 | 정체 | 역할 |
|---|---|---|
| Docker Compose | docker-compose.yml + docker-compose.knowledge.yml | postgres·redis·nginx·ui·4개 API·(선택)opensearch+document-api를 한 번에 기동 |
| Makefile | 28KB, make help로 전체 목록 | up-all·build-all·logs-*·rebuild-* 등 배포 전 과정을 명령 하나로 |
| 기본 이미지 6종 | docker/base/rapida-*.Dockerfile | golang(bookworm/alpine)·alpine·debian-slim·node-alpine·python — 서비스별 최적화된 베이스 이미지를 직접 빌드해 재사용 |
| nginx | API 게이트웨이 | 외부에서 들어오는 요청을 각 내부 서비스로 라우팅(포트 8080 진입점) |
| gRPC + Protobuf | protos/, buf.yaml | 서비스 간 계약을 .proto로 고정, talk-api·assistant-api·vault-api 등 다수의 내부 API 정의 |
mark3labs/mcp-go SDK로 이를 지원해, 어시스턴트가 외부 MCP 서버의 도구를 호출할 수 있게 한다. UI에도 tools/mcp/ 전용 설정 화면이 있다.우선순위 채널 기반 이벤트 파이프라인 — "패킷"이라는 작은 이벤트가 파이프를 타고 흐른다.
가장 먼저 전체 그림을 보자. 전화(또는 브라우저 마이크)에서 시작해 assistant-api 서비스 하나가 핵심 실시간 처리를 전담한다. 외부 전화망 → 텔레포니 채널 → 이벤트 파이프라인 → (VAD·발화종료·STT·LLM·TTS를 순서대로 거쳐) → 다시 전화망으로 음성이 나간다.
코드 상에서 이 흐름은 단계별 파이프라인 구조체로 못 박혀 있다. channel/pipeline/types.go를 보면 통화 하나가 CallReceivedPipeline → WebhookParsedPipeline → AssistantResolvedPipeline → ConversationCreatedPipeline → ProviderAnsweringPipeline → SessionConnectedPipeline 순서로 상태를 넘겨받는다. 각 단계는 Validate() 메서드로 "이 단계에 꼭 있어야 할 값이 다 채워졌는지"를 스스로 검증한다 — 앞 단계가 부실하면 다음 단계로 못 넘어가게 코드 레벨에서 강제하는 것이다.
전화가 연결된 이후, 사용자가 한 마디 말하고 AI가 답하는 "한 턴"이 실제로 어떻게 도는지 손으로 따라가 보자. 해피패스(정상 경로)만 본다 — 에러 처리·재시도는 생략.
이 8단계 각각이 "Packet(패킷)"이라는 작고 이름 붙은 이벤트로 표현된다는 게 이 레포의 핵심 설계다. internal_type.Packet은 인터페이스일 뿐이고(ContextId()·PacketName() 두 메서드만 요구), UserAudioReceivedPacket·SpeechToTextPacket·EndOfSpeechPacket 같은 60개 넘는 구체 타입이 이를 구현한다. 코드 주석에 명명 규칙이 명시돼 있다 — 커맨드(동사로 시작, 예: ExecuteLLM·SpeakText)는 "무언가를 하라"는 지시, 이벤트(과거형/명사, 예: SpeechToText·EndOfSpeech)는 "무언가 일어났다"는 보고다.
adapter_router.Classify()가 6개 채널(Control·Bootstrap·Ingress·Egress·Data·Background) 중 하나로 분류하고, 각 채널은 자기 전용 goroutine에서 순서대로 처리된다. "통화 종료" 같은 긴급 제어 신호가 TTS 오디오 재생 같은 대량 데이터 처리에 밀려 늦게 처리되는 사고를 막는다 — 병원 응급실이 중증도별로 대기줄을 나누는 것과 같은 이치.New(opts ...Option) (Interface, error) 팩토리 함수를 가진다. 내부에서 문자열 하나(microphone.vad.provider 같은 설정 키)로 switch해 실제 구현체를 고른다. 새 공급자를 추가해도 파이프라인 코드는 건드릴 필요가 없다 — 이 인터페이스만 만족하면 끼워 넣을 수 있다.서비스 4개 + 공유 패키지 + SDK/예제까지, 큰 그림에서 작은 그림으로.
| 파일/폴더 | 역할 |
|---|---|
api/assistant-api/internal/adapters/internal/dispatch.go | 패킷을 우선순위 채널로 분류·라우팅하는 핵심 진입점(99줄, 작지만 전체 흐름의 중심) |
api/assistant-api/internal/type/packet.go | 60개 넘는 패킷 타입이 정의된 이벤트 어휘집 — 이 레포의 "동사·명사 사전" |
api/assistant-api/internal/channel/pipeline/ | 통화 단계별 상태 구조체(types.go)와 실제 실행 로직(dispatcher/session) |
api/assistant-api/internal/transformer/transformer.go | STT/TTS 공급자 팩토리 — 새 음성 공급자를 추가할 때 가장 먼저 볼 파일 |
AGENTS.md / CLAUDE.md | AI 코딩 에이전트가 이 레포에서 작업할 때 지켜야 할 파일 경계·테스트 규칙을 정의 |
.claude/skills/와 .codex/skills/ 밑에 stt-integration·tts-integration·vad-integration·telephony-integration·system-understanding 등 9개 "스킬" 폴더가 있다. 각 스킬은 "새 STT 공급자를 추가하려면 어느 파일을 봐야 하는지"를 명시한 매뉴얼이다. 사람 신입 개발자용 온보딩 문서를 AI 코딩 에이전트(Claude Code·Codex)가 그대로 읽고 따라가도록 설계한 것 — 이 레포를 딥다이브하는 이 문서도 실제로 그 스킬 파일들을 참고해 작성됐다.이 레포에서 뽑아 갈 수 있는 실전 기술 6가지.
패킷 하나가 ContextId()·PacketName() 두 메서드만 요구하는 얇은 인터페이스라는 점이 핵심이다. 새 이벤트 타입을 추가해도 기존 라우팅 코드는 건드릴 필요가 없다(개방-폐쇄 원칙). api/assistant-api/internal/type/packet.go를 정독하면 "실무에서 이벤트 이름을 어떻게 짓는가"(커맨드=동사, 이벤트=과거형)까지 배울 수 있다.
transformer.go·vad.go·end_of_speech.go 세 파일 모두 같은 뼈대를 쓴다 — 옵션 구조체 + 함수형 옵션(functional options) + switch 팩토리. 아래는 VAD 팩토리를 단순화한 형태다.
// 함수형 옵션 패턴: New(옵션...) 형태로 유연하게 구성
func New(opts ...Option) (internal_type.VoiceActivityDetectorExecutor, error) {
options := &options{ctx: context.Background()}
for _, opt := range opts {
opt(options)
}
provider, _ := options.options.GetString("microphone.vad.provider")
switch VADIdentifier(provider) {
case FIRERED_VAD:
return internal_vad_firered.New(...)
case TEN_VAD:
return internal_vad_ten.New(...)
case SILERO_VAD:
return internal_vad_silero.New(...)
default:
return internal_vad_silero.New(...) // 기본값도 명시적으로 정함
}
}
channel/pipeline/types.go의 각 파이프라인 구조체(CallReceivedPipeline 등)는 자체 Validate() bool을 갖고 있다. 통화 처리 도중 "이 단계에 필요한 값이 비어 있다"는 사고를 컴파일 타임이 아니라 런타임 초입에서 빠르게 잡아낸다 — 상태 기계(state machine)를 타입으로 표현하는 실전 예제.
channel/telephony/internal/ 밑에 asterisk·exotel·sip·telnyx·twilio·vonage가 각자 폴더를 갖고 있고, 순수 SIP는 sip/에 자체 SIP 스택(inbound/outbound/registration/middleware)까지 구현돼 있다. 전화 시스템(SIP·RTP·오디오 코덱 G.711)을 실무 코드로 배우기 좋은 자료다.
observability.Recorder는 RecordEvent·RecordLog·RecordWebhook·RecordMetadata 같은 구조화된 레코드 타입을 받아, 등록된 여러 collectors(요청 로그·툴 로그·webhook·telemetry·billing 등)로 팬아웃한다. "일단 기능부터 만들고 로그는 나중에"가 아니라, 파이프라인 설계 단계부터 관측을 끼워 넣은 구조를 볼 수 있다.
Go(성능이 중요한 실시간 오디오/네트워크)·Python(document-api, RAG·임베딩 생태계가 Python에 풍부)·TypeScript(관리 콘솔 UI)로 역할을 명확히 나눴다. "왜 이 서비스는 이 언어로 짰을까"를 각 서비스의 책임과 대조해보면 언어 선택 기준을 배울 수 있다.
실습 아이디어: api/assistant-api/internal/vad/vad.go의 팩토리 패턴을 복사해, 내가 좋아하는 언어로 "가짜 STT 공급자 3개를 설정 값으로 스위칭하는" 미니 스켈레톤을 만들어 보라.
실시간 음성이라 지연시간과 리소스 여유가 중요하다.
| 항목 | 요구사항 |
|---|---|
| 필수 도구 | Docker & Docker Compose (README 1순위 요구사항) |
| 메모리 | 16GB+ RAM 권장 — postgres·redis·nginx·ui·4개 Go 서비스를 동시에 띄우기 때문(지식베이스까지 켜면 OpenSearch도 추가) |
| Go 버전(로컬 빌드 시) | Go 1.25 이상 (go.mod 명시) |
| Node/UI 빌드 시 | Node.js 14+ (package.json engines), yarn |
| DB/캐시(Docker 없이 로컬 실행 시) | PostgreSQL, Redis, (선택)OpenSearch를 별도로 준비해야 함 |
| 네트워크 | 텔레포니 공급자(Twilio 등) 웹훅을 받으려면 외부에서 접근 가능한 URL 필요(ngrok 등으로 로컬 테스트 가능) |
| 지연시간(운영 시 고려) | 실시간 대화형 음성은 체감 지연(latency)이 수백 ms를 넘으면 부자연스럽다 — STT/LLM/TTS 각 단계의 응답 속도, 선택한 공급자의 리전이 실사용 품질에 직결 |
4개 API 서비스 + DB + 캐시 + 리버스프록시 + React 빌드까지 한 번에 올라가므로, 저사양 노트북에서는 make up-all이 오래 걸리거나 메모리 부족으로 일부 서비스가 죽을 수 있다. 처음에는 make up-db → make up-assistant처럼 서비스를 하나씩 켜보는 편이 디버깅에 유리하다.
Docker로 띄워보는 것부터 파이프라인에 새 부품을 끼우는 것까지.
git clone → make setup-local && make build-all → make up-all 순서로 실행하고, http://localhost:3000에서 관리 콘솔이 뜨는지 확인한다. docker compose ps로 어떤 서비스가 몇 개 컨테이너로 떴는지 살펴보라. 목표: 4개 API + DB + 캐시 + UI가 실제로 몇 개의 프로세스로 나뉘는지 눈으로 확인.
API 키가 없어도 코드는 읽을 수 있다. transformer.go의 GetTextToSpeechTransformer()를 열어 지원 공급자 14개를 나열해보고, 그중 하나(예: internal_transformer_deepgram 폴더)를 열어 실제 HTTP/WebSocket 호출이 어디서 일어나는지 찾는다. 목표: "설정 문자열 하나가 실제 코드의 어느 지점으로 연결되는지" 추적하는 연습.
로컬에서 assistant-api를 빌드해 실행하고(go build -o bin/assistant ./cmd/assistant), adapters/internal/dispatch.go의 dispatch() 함수에 log.Printf("packet: %s", p.PacketName()) 한 줄을 추가해본다. 텍스트 기반 endpoint-api로 간단한 요청을 보내며 어떤 순서로 패킷이 찍히는지 관찰한다. 목표: §4에서 설명한 8단계 흐름이 실제 로그에서도 그대로 보이는지 검증.
api/assistant-api/internal/vad/internal/silero_vad/ 폴더 구조를 참고해, 실제 모델 없이 "3초에 한 번씩 무조건 말했다고 판정하는" 더미 VAD 구현체를 만들고 vad.go의 switch문에 새 케이스로 등록해본다. .claude/skills/vad-integration/ 스킬 문서를 함께 참고하면 이 레포가 정의한 "정석 추가 절차"를 그대로 따라갈 수 있다. 목표: 팩토리 확장점을 손으로 직접 넓혀보기.
api/assistant-api/sip/의 inbound/outbound/registration 코드를 읽고, 무료 SIP 트렁크 테스트 서비스(또는 로컬 Asterisk/FreeSWITCH)로 실제 통화를 한 번 걸어본다. sip/testdata/freeswitch/에 테스트용 설정 예시가 있다. 목표: 전화망 신호(SIP INVITE 등)가 어떻게 Rapida 내부 이벤트로 번역되는지 끝까지 추적.
음성 AI 오케스트레이션을 처음부터 체계적으로 익히는 5주 코스.
| 주차 | 주제 | 학습 자료 |
|---|---|---|
| 1주차 | 음성 AI 기초 개념(STT/TTS/VAD/EOS) + Rapida 로컬 실행 | 이 문서 §1·§4 + README + make up-all 실습 |
| 2주차 | Go 이벤트 기반 아키텍처 + 함수형 옵션 패턴 | packet.go·vad.go·transformer.go 정독, Go 공식 문서의 "Functional Options" 패턴 |
| 3주차 | 실시간 통신 프로토콜(SIP·RTP·WebRTC) | sip/·channel/webrtc/ 코드 + Pion WebRTC 공식 예제 |
| 4주차 | LLM 오케스트레이션 + MCP + 대화 상태 관리 | internal/llm/·internal/agent/ 코드 + modelcontextprotocol.io 문서 |
| 5주차 | 관측가능성 설계(OpenTelemetry) + 나만의 미니 오케스트레이터 만들기 | observability/ 코드 + OpenTelemetry Go 공식 튜토리얼로 축소판 구현 |
본문에 나온 용어 빠른 참조.
| 용어 | 의미 |
|---|---|
| STT (Speech-to-Text) | 음성인식. 사람 목소리 → 텍스트. Deepgram·Azure·Google 등 14개 공급자를 팩토리로 스위칭. |
| TTS (Text-to-Speech) | 음성합성. 텍스트 → 사람 목소리. ElevenLabs·Cartesia·Rime 등 지원. |
| VAD (Voice Activity Detection) | 발화 감지. 오디오에서 "말하는 구간"을 찾아낸다. silero/ten/firered 3개 엔진 내장. |
| EOS (End of Speech, 발화종료 감지) | "사용자가 말을 다 끝냈다"를 판정하는 로직. 너무 빠르면 말 끊고, 너무 느리면 답변이 늦다 — 침묵 기반·LiveKit·Pipecat Smart-Turn 3방식 지원. |
| 턴 테이킹 (Turn-taking) | 대화에서 "누가 말할 차례인가"를 주고받는 과정. VAD+EOS가 함께 이를 판정한다. |
| 바지 인 (Barge-in) | AI가 말하는 도중에 사용자가 끼어들어 말하는 상황. InterruptionDetectedPacket·TextToSpeechInterruptPacket으로 처리. |
| 오케스트레이션 (Orchestration) | 여러 독립 부품(통신·STT·TTS·LLM)이 정확한 순서·타이밍으로 협력하도록 지휘하는 일. Rapida의 핵심 정체성. |
| 패킷 (Packet) | 이 레포에서 파이프라인을 흐르는 최소 이벤트 단위. ContextId()·PacketName()만 요구하는 얇은 인터페이스. |
| 팩토리 패턴 (Factory Pattern) | 설정 값 하나로 실제 구현체(STT 공급자 등)를 골라 생성하는 구조. 이 레포 전역에서 반복되는 핵심 설계. |
| 함수형 옵션 패턴 (Functional Options) | New(WithLogger(l), WithContext(ctx))처럼 옵션 함수를 가변 인자로 받아 구조체를 유연하게 구성하는 Go 관용구. |
| MCP (Model Context Protocol) | AI가 외부 도구를 표준화된 방식으로 호출하는 프로토콜. mark3labs/mcp-go로 지원. |
| Pion WebRTC | Go로 작성된 WebRTC 구현체. 브라우저 위젯의 실시간 오디오를 서버가 직접 받는 데 사용. |
| sipgo | Go SIP 프로토콜 스택. 통신사 API 없이도 순수 SIP로 전화를 걸고 받을 수 있게 해준다. |
| 우선순위 채널 라우팅 | 패킷을 Control/Bootstrap/Ingress/Egress/Data/Background 6개 goroutine 채널로 나눠 긴급도별로 처리하는 방식. |