watch_sceen_ai

디스코드에서 음성으로 대화하는 AI. 최종 목표는 화면공유(Go Live)까지 실시간으로 보면서 대화하는 것이지만, 지금은 화면공유(눈)를 잠시 미뤄두고 음성 대화 루프 STT → 두뇌 → TTS부터 완성하는 단계다.

  • 실행 위치: 이 리눅스 호스트(.9, RTX 5050 8GB / ffmpeg / node v22 있음)
  • 현재 상태: 음성 루프 뼈대 동작 — 파이프라인이 눈 없이(화면공유 없이) 돌아간다 (python -m wsai --voice). STT/TTS/두뇌는 아직 mock이며, 실제 엔진 연결이 다음 목표.
  • 보류 중: 화면공유 비디오 수신(눈). 단, STT 입력은 디스코드 보이스로 유저 음성을 수신하므로 셀프봇의 보이스 접속 자체는 지금도 쓴다(비디오만 미룸). E2EE 합류 관문은 이미 통과해 두었다(아래 8장).

1. 설계 원칙

  1. 지연이 전부다. 음성 대화형이라 "정확하지만 느린" 답은 실패다. 전 구간을 스트리밍으로 잇고, 목표는 사용자가 말 끝낸 뒤 첫 소리까지 1초 이내.
  2. 싼 것부터, 필요할 때만 비싸게. 로컬(GPU)에서 처리할 수 있는 건 로컬에서, 정말 필요한 것만 클라우드로 올린다.
  3. 부품은 교체 가능해야 한다. 귀/두뇌/입(그리고 나중의 눈)이 전부 wsai/interfaces.py의 Protocol이라, 어느 하나가 바뀌어도 나머지는 그대로 돌아간다. 실제로 눈(source/vision)은 선택 사항이라, 없으면 파이프라인이 음성 루프만 돌린다.

2. 전체 구조

두 개의 async 루프가 있고, 지금은 대화 루프만 켜서 쓴다. 인지(눈) 루프는 나중에 붙인다.

[대화 루프]  SpeechToText ──Utterance──▶  Brain(LLM) ──▶ TextToSpeech
             (귀)                          (두뇌)          (입)
                                             ▲
                                             │ (화면 맥락: 지금은 비어 있음)
[인지 루프]  FrameSource ─▶ VisionBackend ─┘   ← 보류 (source/vision = None)
             (눈)
  • 대화 루프: 사용자 발화 → 두뇌(대화이력 + 있으면 최신 화면 맥락) → 음성 응답.
  • 인지 루프: source/vision이 없으면 통째로 건너뛴다. 눈을 붙이면 최신 화면 맥락을 채워 넣어, 두뇌가 "지금 화면"을 참고할 수 있게 된다.
  • 두 루프는 비동기로 독립. 눈이 생겨도 대화 루프는 프레임 하나하나를 기다리지 않고 "가장 최근에 이해된 화면"만 읽는다.

데이터 타입/인터페이스는 wsai/interfaces.py에 정의돼 있다: Frame, ScreenObservation, Utterance, Reply / FrameSource, VisionBackend, SpeechToText, TextToSpeech, Brain, TextChannel.


3. 귀 / 두뇌 / 입 — 지금 만드는 음성 루프

귀 (STT) — 디스코드 보이스 수신

  • 로컬 마이크가 아니라 디스코드 보이스에서 유저가 말하는 음성을 수신한다. 셀프봇이 보이스 채널에 접속해(이미 통과한 DAVE/MLS E2EE 경로) 유저의 Opus 오디오 RTP를 받고, 복호 → Opus 디코드 → PCM으로 만들어 STT에 흘려보낸다.
  • faster-whisper로 실시간 부분 전사를 계속 돌리고, VAD로 발화 종료(endpointing)를 잡는다. 종료 판정 시점엔 전사가 사실상 끝나 있어, 판정 후 남는 건 VAD hangover + 짧은 마지막 디코드뿐이다. 목표 종료→텍스트 확정 ~150ms.
  • 디스코드 수신 특성상 지속 스트림 + 지터버퍼(수십 ms)가 붙는다 — 아래 지연 예산에 반영.

두뇌 (Brain)

  • Claude(OAuth, Haiku 계열)로 대화 응답 생성. 저비용·빠른 모델을 기본으로 한다.
  • 입력은 "대화이력 + (있으면) 최신 화면 맥락 1건"만 넣어 토큰을 작게 유지한다.
  • 눈이 없는 지금은 화면 맥락이 비어 있고, 두뇌는 그 사실을 솔직히 말한다.

입 (TTS) — 한국어 고정, 사람처럼

  • 한국어 전용으로, 최대한 자연스러운(사람 같은) 음성을 목표로 한다.
  • 지연을 줄이려고 첫 문장 전체를 기다리지 않고 첫 짧은 구절부터 청크 스트리밍으로 재생한다 (나머지는 재생되는 동안 이어서 합성). 목표 첫 소리 ~170ms.
  • 인터럽트: 사용자가 말하기 시작하면 재생 중인 TTS를 즉시 멈춘다(barge-in).
  • 트레이드오프: 가장 사람 같은 합성(예: XTTS 계열)은 느려서 1초 예산과 충돌한다. 그래서 빠른 한국어 엔진(MeloTTS, 이 호스트에 자산 있음)으로 첫 문장 지연부터 확보하고, 자연스러움이 부족하면 GPU + 문장 스트리밍으로 더 무거운 엔진을 검토한다(품질 vs 지연).

지연 예산 (목표 ≤ 1초)

유저가 디스코드 보이스에서 말을 멈춘 순간 → 첫 소리까지:

구간 목표
디스코드 보이스 수신 + Opus 디코드(지속 스트림·지터버퍼) ~80 ms
STT 종료 판정(VAD) + 마지막 청크 전사 ~150 ms
Brain 첫 토큰(Claude Haiku 스트리밍) ~350 ms
TTS 첫 문장 합성·재생 시작(한국어) ~170 ms
여유 ~100 ms
합계 ~0.85 s
  • Brain(머리)은 네트워크 TTFT라 우리가 못 줄인다 → 350ms로 고정. 대신 우리 GPU에서 도는 STT·TTS를 깎아 여유를 벌었다. 두 구간을 어떻게 줄이는지는 아래.
    • STT ~150ms: 발화 중 부분전사를 계속 돌려 종료 판정 시점엔 전사가 사실상 끝나 있게 한다. 남는 건 VAD hangover(~120ms) + 마지막 짧은 디코드(~30ms)뿐.
    • TTS ~170ms: 첫 문장 전체가 아니라 첫 짧은 구절만 먼저 합성해 청크 스트리밍으로 바로 재생을 시작한다(나머지는 재생되는 동안 이어서 합성).
  • Brain·TTS는 스트리밍이라, 응답 전체 완성이 아니라 "첫 소리"만 준비되면 재생된다.
  • 나중에 눈(화면 이해)을 붙여도, 화면 이해는 별도 인지 루프에서 미리 끝나 있어 응답 시점에 비전 지연이 끼어들지 않는다.

4. 눈 — 화면공유 수신 (보류, 나중에)

최종 목표엔 화면공유가 들어가지만, 지금은 의도적으로 미뤄둔 부분이다. 아래는 나중에 다시 붙일 때를 위한 기록이다. 두 트랙을 병행할 계획이었다.

트랙 A (주력) — 화면공유 스트림 직접 수신

스크린샷·브라우저 캡처가 아니라, 실제 화면공유 스트림 자체를 클라이언트처럼 수신한다. 공식 봇 API는 비디오 수신을 막으므로 유저 토큰(셀프봇) 으로 프로토콜 레벨에서 비디오 RTP를 받는다(전용 버너 계정만 사용). 파이프: join → Go Live "watch" 구독 → 암호화 RTP 수신 → DAVE(MLS E2EE) 복호 → 전송암호 복호 → VP8/H264 depayload → ffmpeg 디코드 → 프레임. DAVE는 @snazzah/davey(Rust NAPI), 수신은 discord.js-selfbot 계열.

솔직한 리스크: 완성형 라이브러리가 없어 직접 조립해야 하고, 셀프봇은 ToS 회색지대라 계정 밴 위험이 있다(→ 버너 전용). 그래서 음성 루프를 먼저 세우는 지금 순서가 안전하다.

트랙 B (폴백/개발용) — 로컬 렌더 화면 캡처

디스코드 클라이언트가 화면공유를 보고 있는 화면을 로컬에서 캡처한다. 트랙 A가 막힐 때의 안전판이자 개발용 실용 소스. poc/의 Xvfb 실험과 wsai/backends/capture_mss.py가 여기 해당.

두 트랙 모두 결과물은 동일한 Frame 스트림이라, 눈을 붙일 때 나머지는 손대지 않는다.


5. 실행

이 호스트에는 python 별칭이 없으므로 워크스페이스 venv를 쓴다(.venv/bin/python).

.venv/bin/python -m wsai --voice    # 눈 없는 음성 루프 데모 (STT→두뇌→TTS, 지금 초점)
.venv/bin/python -m wsai            # mock 데모 (눈 포함 전체 흐름, 몇 프레임 돌고 종료)
.venv/bin/python -m wsai --env      # WSAI_* 환경변수로 백엔드 조립
.venv/bin/python -m pip install pytest && .venv/bin/python -m pytest -q   # 스모크 테스트
  • WSAI_SOURCE=none WSAI_VISION=none 으로도 눈 없이(음성 루프만) 조립할 수 있다.
  • 백엔드별 추가 설치는 requirements.txt 주석 참고(faster-whisper, mss/pillow, anthropic 등).

6. 파일 / 디렉터리

경로 역할
wsai/interfaces.py 데이터 타입 + 컴포넌트 Protocol
wsai/pipeline.py 오케스트레이터(대화 루프 + 선택적 인지 루프)
wsai/state.py 최신 화면 맥락 공유 저장소(눈이 생기면 사용)
wsai/config.py / factory.py 설정 → 백엔드 조립. voice() = 눈 없는 음성 프리셋
wsai/backends/mock.py 무의존성 mock 전 계열
wsai/backends/capture_mss.py 로컬 화면 캡처(트랙 B, 보류)
wsai/backends/claude.py Claude 비전 + 두뇌
dave/gate.mjs DAVE/MLS E2EE 합류 관문 검증(통과 증거, 눈 재개 시 재사용)
poc/ Xvfb+Chromium 캡처 실험(트랙 B 참고)
PLAN.md 단계별 착수 계획
tests/test_pipeline.py 파이프라인 스모크 테스트

7. 구현 순서 (검증 가능한 체크포인트)

음성 루프 먼저, 눈은 나중에.

  • 파이프라인 스켈레톤 — mock 모드로 전 흐름 동작.
  • 눈 없는 음성 루프. source/vision 없이 STT→Brain→TTS만 돌아간다(--voice). · 검증: python -m wsai --voice가 화면 없이 발화마다 응답을 낸다(테스트 포함).
  • V1 · 진짜 TTS(입). MeloTTS로 두뇌 응답을 실제 음성으로 합성. · 검증: 응답 텍스트가 .wav로 합성돼 들린다.
  • V2 · 진짜 STT(귀). faster-whisper로 오디오를 텍스트로 전사. · 검증: 오디오 파일 입력이 텍스트로 나온다(이후 디스코드 보이스 수신 오디오로 연결).
  • V3 · 진짜 두뇌. Claude OAuth(Haiku)로 대화 응답 생성. · 검증: 실제 발화에 자연스러운 답이 나온다.
  • V4 · 음성 왕복 + barge-in. 디스코드 보이스 수신→STT→Brain→TTS 스트리밍, 말 끊기, 지연 측정. · 검증: 디스코드에서 말로 묻고 말로 답을 듣는다, 첫 소리까지 1초 이내.
  • (나중) 눈 재개. 트랙 A/B로 화면공유 수신 → 화면 맥락을 두뇌에 연결.

8. 확정된 결정 (2026-08-11) + 배포 목표

  • GPU 사용 — OK. RTX 5050(.9)으로 STT/TTS를 돌린다. .9 로컬 도커는 NVIDIA CDI로 GPU 사용 가능.
  • 파이썬 버전 — STT/TTS는 3.11(faster-whisper·TTS 자산 호환), 오케스트레이터는 기존 코드 유지.
  • TTS 엔진 — 최대한 사람 같은 음성으로 하되, 전체 지연 1초 초과 금지가 상한. 후보(XTTS 등)의 첫 소리 지연을 GPU에서 실측해, 1초 예산에 맞는 가장 자연스러운 엔진을 채택한다(측정 기반 선택).
  • 두뇌 — Claude OAuth, 이 호스트에 연결된 것과 동일 크리덴셜 공유 (/home/claude/EJClaw/data/claude/.credentials.json). API키 아님.
  • STT 입력 경로 — 디스코드 보이스 수신(셀프봇). 유저 음성 Opus RTP를 받아 DAVE 복호·디코드해 STT로.

배포/테스트 목표

  • 대상: 디스코드 서버 1352269198297923648의 보이스 채널 1352269198914621465.
  • 유저봇(셀프봇) 토큰: .envDISCORD_SELFBOT_TOKEN(버너 계정, 밴 리스크 수용).
  • .9 로컬 GPU 도커 이미지로 올려, 그 채널에 접속한 뒤 사람이 말하면 대화하도록 한다.
  • 첫 로딩 워밍업: 시작 시 모델 프리로드 + 더미 추론(CUDA 워밍) + 보이스 미리 접속 → 첫 대화도 지연 최소.

구현 마일스톤

  • M1 셀프봇이 대상 보이스 채널에 상주 접속(DAVE 통과) + 발화자(SSRC) 감지 ← 지금
  • M2 유저 음성 Opus RTP 수신 → DAVE 복호 → PCM (라이브 발화자 필요, 최고 난이도)
  • M3 faster-whisper STT(부분전사+VAD) → M4 Claude OAuth(Haiku) 두뇌
  • M5 한국어 TTS 첫 구절 청크를 보이스로 송신(DAVE 암호화) + barge-in
  • M6 통합 + 워밍업 + .9 GPU 도커 이미지화, 채널 라이브 테스트
Description
No description provided
Readme 389 KiB
Languages
Python 78.6%
JavaScript 19.7%
Dockerfile 0.9%
HTML 0.4%
Shell 0.4%