Open-LLM-VTuber는 음성으로 대화하는 AI 컴패니언입니다. 마이크에 말을 걸면, ① 그 음성을 텍스트로 받아쓰고(ASR), ② AI 두뇌(LLM)가 답을 생각하고, ③ 그 답을 다시 목소리로 합성하고(TTS), ④ 화면 속 Live2D 캐릭터가 그 목소리에 맞춰 입을 움직이고 표정까지 짓습니다. 이 모든 걸 로컬 모델만으로 인터넷 없이 돌릴 수 있다는 게 핵심입니다.
웹 브라우저로 쓰는 버전과, Electron 데스크톱 앱(투명 배경으로 화면 한구석에 캐릭터를 띄우는 '펫 모드')을 모두 제공하고, Windows·macOS·Linux를 전부 지원합니다. 처음 목표는 폐쇄형 상용 AI VTuber인 neuro-sama를 "오프라인 오픈소스로 재현하기"였고, 그래서 이름이 'Companion'이나 'Waifu'가 아니라 Open-LLM-VTuber입니다.
완제품 AI 스피커는 두뇌·귀·입이 한 통에 붙어 있어 바꿀 수 없습니다. Open-LLM-VTuber는 조립 PC에 가깝습니다. "귀(ASR)"는 sherpa-onnx, "두뇌(LLM)"는 Ollama, "입(TTS)"은 Edge TTS… 이렇게 부품마다 원하는 걸 끼우는데, 코드를 건드리지 않고 설정 파일(conf.yaml) 한 줄만 바꾸면 됩니다. 게다가 이 PC는 인터넷 선을 뽑아도 돌아갑니다 — 모든 부품을 내 컴퓨터 안에 둘 수 있으니까요.
음성 챗봇은 많지만, 대부분 클라우드에 의존하거나(대화가 회사 서버로 전송됨), 특정 모델에 묶여 있습니다. Open-LLM-VTuber는 "완전 오프라인 + 부품을 마음대로 교체 + 살아 있는 캐릭터" 세 가지를 동시에 만족시켜 약 9,000개의 별을 모았습니다.
LLM·ASR·TTS·번역을 전부 로컬 모델로 돌릴 수 있어, 인터넷 선을 뽑아도 동작합니다. 클라우드 AI에 사적인 대화를 넘기고 싶지 않은 사람에게 결정적인 장점이죠. 물론 원하면 OpenAI·Claude 같은 클라우드 API로 갈아끼울 수도 있습니다 — 선택은 사용자 몫입니다.
보통 음성 봇은 자기 목소리를 마이크로 다시 들어 혼란을 일으키거나(에코), 답을 끝까지 들어야 다음 말을 할 수 있습니다. 이 프로젝트는 AI가 말하는 중에도 사용자가 끼어들면 즉시 멈추고 새 입력을 받습니다. 헤드폰 없이 스피커로 써도 자연스럽게 대화가 됩니다.
LLM은 Ollama·OpenAI·Claude·Gemini·DeepSeek·llama.cpp·vLLM 등, TTS는 Edge TTS·GPT-SoVITS·ElevenLabs·CosyVoice 등 수십 가지를 지원합니다. 새 백엔드도 정해진 인터페이스만 구현하면 추가됩니다. "어떤 모델을 쓸지"가 코드가 아니라 설정의 문제가 된 점이 개발자들을 끌어들였습니다.
다른 음성 AI가 "정해진 메뉴만 파는 프랜차이즈"라면, Open-LLM-VTuber는 "재료를 골라 담는 샐러드 바"입니다. 두뇌·귀·입·기억·표정을 각각 취향대로 고르고, 심지어 가게(서버)를 내 집(로컬)에 차릴 수 있습니다. 자유도와 프라이버시, 그리고 '살아 있는 캐릭터'라는 감성이 트렌딩의 진짜 이유입니다.
README 상단에 v2.0 대규모 재작성이 기획 중이라는 공지가 있습니다. 지금 분석하는 건 v1 계열(v1.2.1)이며, 구조가 향후 크게 바뀔 수 있습니다. 공부 목적으로는 v1의 설계 패턴(아래)을 익히는 게 여전히 가치 있지만, "최신 코드와 다를 수 있다"는 점은 염두에 두세요.
이 프로젝트는 두 개의 저장소로 되어 있습니다. 두뇌·귀·입이 도는 백엔드(Python)가 본체이고, 화면과 캐릭터를 그리는 프론트엔드는 별도 레포(Open-LLM-VTuber-Web)를 Git 서브모듈로 끌어옵니다(그래서 클론할 때 --recursive가 필요합니다).
| 부품 | 역할 |
|---|---|
| FastAPI + uvicorn | 웹 서버. 통신은 대부분 WebSocket(실시간 양방향)으로 |
| numpy·scipy·soundfile·pydub | 오디오 처리(샘플 다루기·변환). 외부로 FFmpeg 필수 |
| onnxruntime·torch·sherpa-onnx | 모델 추론 런타임(ASR/VAD 등을 CPU·GPU에서 실행) |
| anthropic·openai·groq | 클라우드 LLM 공식 SDK(원할 때만 사용) |
| letta-client | Letta(=MemGPT) 기반 장기 기억 옵션 |
| mcp[cli]·duckduckgo-mcp-server | MCP 도구 호출(시간 조회·웹 검색 등) |
| pydantic·pyyaml·loguru | 설정 타입 검증 · YAML 설정 · 로깅 |
| uv | 패키지/가상환경 관리 도구(권장). pip도 지원 |
Open-LLM-VTuber-Web (Electron · React)| 부품 | 역할 |
|---|---|
| Electron 31 + electron-vite | 웹 기술로 데스크톱 앱 제작. 창 모드 + '펫 모드' 빌드(win/mac/linux) |
| React 18.3 + TypeScript 5.5 + Vite 5 | UI 골격과 빌드 |
| Chakra UI 3 + framer-motion | UI 컴포넌트 · 애니메이션 |
| zustand · rxjs | 상태 관리 · 반응형 스트림(오디오 흐름) |
| @ricky0123/vad-web + onnxruntime-web | 브라우저에서 음성 활동 감지(Silero VAD를 웹에서 실행) |
| Live2D (PIXI) | 캐릭터 렌더링. 모델 정의는 백엔드 model_dict.json에서 받아 표시 |
| i18next | 다국어(언어 자동 감지) |
conf.yaml 한 줄로 갈아끼우는 부품 카탈로그입니다. 종류가 워낙 많아 대표만 추립니다.
| 구분 | 지원 옵션(대표) |
|---|---|
| LLM(두뇌) | Ollama, OpenAI 호환, Claude, Gemini, DeepSeek, Zhipu, Groq, LM Studio, llama.cpp(GGUF), vLLM |
| ASR(귀) | sherpa-onnx(기본·SenseVoiceSmall), FunASR, Faster-Whisper, Whisper.cpp, OpenAI/Groq/Azure |
| TTS(입) | Edge TTS(기본), GPT-SoVITS, CosyVoice, MeloTTS, Piper, Bark, Fish Audio, ElevenLabs, Azure/OpenAI |
| VAD | Silero VAD(확률·dB 임계값으로 발화/침묵 판정) |
| 에이전트/기억 | basic_memory_agent(기본), Letta(MemGPT, 장기 기억), HumeAI EVI, Mem0 |
| 번역 | DeepLX, Tencent (예: 영어로 말하고 일본어 음성으로 답하기) |
먼저 전체 그림입니다. 사용자의 목소리가 왼쪽에서 들어와 여러 부품을 거쳐 오른쪽 캐릭터의 입과 표정으로 나옵니다. 브라우저(프론트)와 파이썬 서버(백)가 WebSocket이라는 한 통로로 연결돼 있습니다.
여러 사람이 동시에 접속해도 서로 섞이지 않도록, 접속할 때마다 기본 설정을 통째로 복제(deep-copy)해 그 세션만의 ASR/TTS/VAD/Agent 인스턴스 묶음을 만듭니다. 이걸 ServiceContext라 부르고, client_uid(접속 고유 번호)로 연결·오디오 버퍼·진행 중 대화를 딕셔너리로 관리합니다. "손님마다 전용 테이블 세트를 새로 깔아 주는" 식이죠.
WebSocket으로 오는 모든 메시지는 type 필드를 보고 갈래를 정합니다(_message_handlers 매핑). 주요 타입은 mic-audio-data/mic-audio-end(음성), text-input(타이핑), interrupt-signal(끼어들기), ai-speak-signal(AI가 먼저 말 걸기), fetch/switch-config(설정 핫스왑) 등입니다. 설정을 껐다 켜지 않고 바꾸는 '핫스왑'이 이 라우팅 덕에 가능합니다.
VAD가 사용자의 말소리를 감지하면, 백엔드가 {"type":"control","text":"interrupt"}를 보내 진행 중인 대화 task를 즉시 cancel() 합니다. 이때 영리한 디테일이 있습니다 — AI가 "들은 데까지"의 응답(heard_response)만 기억에 남깁니다. 그래야 다음 대화에서 "아까 하다 만 말"이 자연스럽게 이어지죠.
# 흐름 요약 (개념): 끼어들면 진행 task를 취소하고, 들은 만큼만 기억 on "interrupt-signal": current_task.cancel() # 말하던 거 멈춤 memory.append(heard_response_so_far) # 들린 데까지만 저장 start_new_conversation(new_input) # 새 입력 처리 시작
LLM 시스템 프롬프트에 "기쁘면 [joy], 화나면 [anger]처럼 감정 키워드를 붙여 말하라"고 지시합니다. 그러면 TTS 단계에서 그 키워드를 뽑아, model_dict.json의 emotionMap(예: joy→3, anger→2)으로 Live2D 모션 인덱스를 골라 표정을 바꿉니다. "텍스트 속 감정 태그 → 숫자 → 캐릭터 표정"으로 이어지는 깔끔한 변환입니다.
ASR·TTS·LLM·Agent 모두 *_interface.py(공통 약속) + *_factory.py(설정 보고 알맞은 구현을 만들어 주는 공장) 패턴을 씁니다. 그래서 새 백엔드를 추가하려면 인터페이스만 구현하면 끝이고, 사용자는 conf.yaml에서 이름만 바꾸면 됩니다. 3장에서 말한 "부품 자유 교체"가 코드 레벨에서 어떻게 가능한지의 답이 바로 이 패턴입니다.
이 코드베이스에서 xxx_interface.py와 xxx_factory.py가 짝으로 보이면 "아, 갈아끼울 수 있는 부품이구나"로 읽으면 됩니다. 그리고 conf.yaml의 키 이름이 곧 폴더/클래스 이름과 연결됩니다. 이 두 가지 관례만 알면 낯선 폴더도 금방 지도가 그려집니다.
src/open_llm_vtuber/ — 그 안에서도 websocket_handler.py가 심장최상위에는 실행·설정 파일이, 진짜 로직은 src/open_llm_vtuber/ 패키지에 들어 있습니다. 프론트엔드는 frontend/가 서브모듈로 링크돼 있어, 그 폴더는 별도 레포의 내용입니다.
| 파일 | 역할 |
|---|---|
| websocket_handler.py | 실시간 멀티클라이언트 음성 파이프라인의 심장. 끼어들기·그룹챗까지 — 가장 먼저 읽을 파일 |
| service_context.py | 세션마다 ASR/TTS/VAD/Agent를 묶어 격리하는 구조 |
| conf.default.yaml | 설정으로 모듈을 갈아끼우는 '설계 철학'이 그대로 드러나는 파일 |
| asr/ tts/ agent/ 의 *_factory.py | 설정 이름을 보고 알맞은 구현을 만들어 주는 공장 — 확장 지점 |
| model_dict.json | Live2D 모델과 감정→표정 인덱스(emotionMap) 매핑 |
오디오 청크·제어 신호·자막이 끊김 없이 양방향으로 흐릅니다. websocket_handler.py는 접속 고유 번호로 다중 클라이언트를 관리하고, 메시지 type으로 분기하는 실전 패턴의 좋은 교본입니다. 실시간 협업·채팅·스트리밍 앱을 만들 때 그대로 응용됩니다.
마이크 입력 → 텍스트 → 화면 표시까지만 잇는 최소 WebSocket 앱을 만들어 보세요. 거기에 "AI가 말하는 중 끼어들면 멈추기"(task cancel)를 더하면 이 프로젝트의 핵심 난제를 직접 풀어 보게 됩니다.
음성 인터페이스의 4대 부품이 어떻게 맞물리는지를 실제로 도는 코드로 볼 수 있습니다. 특히 "첫 콤마에서 바로 발화 시작"(faster_first_response)처럼 체감 지연을 줄이는 트릭은, 어떤 실시간 음성 서비스에도 통하는 노하우입니다.
"기능을 코드가 아니라 설정으로 고른다"는 확장 가능한 설계의 정석입니다. asr/asr_interface.py → asr_factory.py → 구체 구현 순으로 읽으면, 새 기능을 추가해도 기존 코드를 안 건드리는 구조(개방-폐쇄 원칙)가 손에 잡힙니다.
onnxruntime으로 가벼운 모델을 CPU/GPU에서 돌리고, Ollama로 로컬 LLM을 띄우고, VRAM에 맞춰 모델 크기를 고르는 실전 로컬 AI 운용 감각을 기를 수 있습니다. 클라우드 비용 없이 AI를 다뤄 보는 가장 좋은 놀이터 중 하나입니다.
투명 배경·항상 위·클릭 통과(click-through)로 캐릭터를 화면에 띄우는 '펫 모드'는, Electron의 창 제어를 깊이 다루는 흥미로운 예제입니다. 웹 기술로 네이티브 데스크톱 경험을 만드는 법을 배울 수 있습니다.
핵심은 두 갈래입니다. 모든 부품을 클라우드 API로 돌리면 라즈베리파이로도 가능하고, 로컬 모델로 돌리면 GPU 성능이 지연을 결정합니다. 공통으로 FFmpeg는 반드시 설치해야 합니다(없으면 오디오 에러).
| 항목 | 요구사항 |
|---|---|
| 공통 필수 | Git, FFmpeg, Python 3.10~3.12. 클론은 --recursive(프론트 서브모듈) |
| 최소(All-API 모드) | 일반 컴퓨터면 충분. ASR/LLM/TTS를 전부 API로 → 모델 다운로드·GPU 불필요 |
| 로컬 권장 | Apple M 시리즈 Mac, NVIDIA GPU(CUDA 11.8+/cuDNN), 또는 강력한 CPU |
| VRAM 주의 | LLM이 VRAM 초과하면 CPU로 강제돼 매우 느려짐 → VRAM에 맞는 모델 크기 선택 |
| 원격 마이크 | 다른 기기(폰)에서 마이크 쓰려면 HTTPS 필수(브라우저 보안 정책). 리버스 프록시 구성 |
| 모델 캐시 | ./models에 저장. 번들 Live2D 샘플은 Live2D Inc. 별도 라이선스(상업 사용 주의) |
# 1) 서브모듈까지 함께 클론 git clone https://github.com/Open-LLM-VTuber/Open-LLM-VTuber --recursive cd Open-LLM-VTuber # 2) 가상환경 + 의존성 (uv 권장 / pip도 가능) uv sync # 3) 1회 실행해 conf.yaml 생성 → Ctrl+C → 템플릿 복사해 편집 uv run run_server.py # 4) 다시 기동 → 브라우저 http://localhost:12393 uv run run_server.py # 기본 스택 = Ollama(LLM) + sherpa-onnx(ASR) + edge_tts(TTS)
위 절차대로 띄우고, 브라우저에서 마이크로 말을 걸어 보세요. AI가 말하는 도중 끼어들면 정말 멈추는지, 자막과 캐릭터 입 모양이 맞는지 확인합니다. FFmpeg가 빠지면 어떤 에러가 나는지도 일부러 한 번 겪어 두면 좋습니다.
conf.yaml에서 TTS를 Edge TTS의 다른 한국어 보이스로 바꾸거나, ASR을 Faster-Whisper로 교체해 보세요. 코드를 한 줄도 안 고치고 설정만으로 목소리·받아쓰기 품질이 달라지는 걸 체감하는 게 목표입니다.
characters/에 새 설정을 추가하고 시스템 프롬프트로 성격·말투를 정의해 보세요. prompts/의 감정 표현 프롬프트와 model_dict.json의 emotionMap을 손봐, 특정 감정에서 원하는 표정이 나오도록 조정합니다.
LLM은 Ollama 로컬 모델, ASR은 sherpa-onnx, TTS는 로컬 엔진으로 맞춰 인터넷 없이 대화가 되게 구성해 보세요. VRAM 한계 안에서 가장 빠른 모델 조합을 찾는 과정이 곧 로컬 AI 튜닝 경험입니다.
tts/tts_interface.py를 구현한 새 클래스를 만들고 tts_factory.py에 등록해, 아직 지원되지 않는 음성 엔진을 붙여 보세요. 인터페이스+팩토리 패턴을 손으로 따라 하면 이 프로젝트의 확장 메커니즘을 완전히 체득하게 됩니다.
| 주차 | 주제 | 할 일 |
|---|---|---|
| 1주차 | WebSocket 실시간 통신 | 연결·메시지 type 라우팅·브로드캐스트. websocket_handler.py 정독 + 미니 음성 에코 앱 |
| 2주차 | 음성 AI 파이프라인 | VAD/ASR/TTS 개념, 끼어들기(task cancel), 저지연 트릭. conversations/ 분석 |
| 3주차 | 플러그형 아키텍처 | 인터페이스+팩토리, pydantic 설정 스키마. asr/·tts/ 구조로 직접 백엔드 추가 |
| 4주차 | 로컬 AI + 데스크톱 | Ollama·ONNX 로컬 추론, Live2D emotionMap, Electron 펫 모드 빌드 |
이 레포는 "실시간 시스템 + AI + 데스크톱 앱"이 한곳에 모인 보기 드문 풀패키지입니다. 1~2주차(WebSocket·음성 파이프라인)는 거의 모든 실시간 AI 서비스에 직결되고, 3~4주차(플러그형 설계·로컬 모델)는 "확장 가능한 코드"와 "AI 엔지니어링" 감각을 동시에 길러 줍니다. v2 재작성이 예고된 만큼, 지금은 설계 원리 위주로 익히는 게 가성비가 좋습니다.
| 용어 | 의미 |
|---|---|
| ASR | 음성→텍스트 변환(Automatic Speech Recognition). 사용자 말을 받아쓰는 '귀' |
| TTS | 텍스트→음성 합성(Text-to-Speech). AI 답을 목소리로 내는 '입' |
| VAD | 오디오에서 '사람이 말하는 구간'을 감지(Voice Activity Detection). 발화 시작/끝·끼어들기 판정 |
| LLM | 대화를 생성하는 대규모 언어 모델. 이 앱의 '두뇌'(Ollama/Claude 등 교체 가능) |
| Live2D | 2D 일러스트를 부위별로 움직여 살아 있게 보이게 하는 기술(Cubism) |
| 립싱크(lip-sync) | TTS 오디오에 맞춰 캐릭터 입 모양을 움직이는 것 |
| WebSocket | 브라우저↔서버 양방향 실시간 통신. 오디오·제어 신호·자막을 끊김 없이 주고받음 |
| Electron | 웹 기술(HTML/JS)로 데스크톱 앱을 만드는 프레임워크. 창 모드 + 펫 모드 |
| Ollama | 로컬에서 오픈 LLM을 손쉽게 돌리는 런타임. 기본 LLM 백엔드 |
| MCP | LLM이 외부 도구(시간·검색)를 표준 방식으로 호출하게 하는 프로토콜(Model Context Protocol) |
| ONNX / onnxruntime | 모델을 프레임워크 독립 포맷으로 변환해 CPU/GPU에서 가볍게 추론하는 런타임 |
| Letta(MemGPT) | LLM에 장기 기억을 부여하는 에이전트 프레임워크(옵션) |
| 인터페이스+팩토리 | 공통 약속(인터페이스)과 생성 공장(팩토리)으로 부품을 설정만으로 갈아끼우는 설계 패턴 |
| 서브모듈(submodule) | 한 Git 저장소 안에 다른 저장소를 링크해 넣는 방식. 프론트엔드가 별도 레포 |
| 펫 모드 | 투명 배경·항상 위·클릭 통과로 캐릭터를 화면 한구석에 띄우는 데스크톱 모드 |