Files
watch_sceen_ai/README.md

11 KiB

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)를 잡는다. 종료 판정 시점엔 전사가 거의 끝나 있게 해, 판정 후 추가 지연을 최소화한다.
  • 디스코드 수신 특성상 지속 스트림 + 지터버퍼(수십 ms)가 붙는다 — 아래 지연 예산에 반영.

두뇌 (Brain)

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

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

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

지연 예산 (목표 ≤ 1초)

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

구간 목표
디스코드 보이스 수신 + Opus 디코드(지속 스트림·지터버퍼) ~80 ms
STT 종료 판정(VAD) + 마지막 청크 전사 ~250 ms
Brain 첫 토큰(Claude Haiku 스트리밍) ~350 ms
TTS 첫 문장 합성·재생 시작(한국어) ~250 ms
여유 ~70 ms
합계 ~1.0 s
  • Brain·TTS는 문장 단위 스트리밍이라, 응답 전체 완성이 아니라 "첫 문장"만 준비되면 소리가 난다.
  • 가장 빡빡한 구간은 **Brain 첫 토큰(네트워크 TTFT)**이다. Haiku로도 300ms대가 목표라, 안 되면 로컬 소형 LLM 선응답이나 프롬프트 워밍 등을 검토한다.
  • 나중에 눈(화면 이해)을 붙여도, 화면 이해는 별도 인지 루프에서 미리 끝나 있어 응답 시점에 비전 지연이 끼어들지 않는다.

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. 결정이 필요한 것 (사용자 확인 대기)

실제 STT/TTS 엔진을 붙이기 전에 정해야 할 것들:

  • GPU 사용 동의 — RTX 5050으로 STT/TTS를 돌리면 빠르다(규칙상 GPU는 먼저 확인 필요).
  • 파이썬 버전 — 이 venv는 3.14인데 faster-whisper·MeloTTS는 3.11대가 안전하다. 호스트에 이미 MeloTTS용 3.11 자산이 있어, 별도 3.11 venv + subprocess 연결을 제안.
  • 엔진 선택 — STT는 faster-whisper, TTS는 한국어 고정. 자연스러움 최우선이면 무거운 엔진(XTTS 계열)이 필요하지만 1초 예산과 상충 → 우선 빠른 MeloTTS로 시작할지, 처음부터 더 사람 같은 엔진을 GPU로 감수할지 결정 필요.
  • 두뇌 — Claude OAuth(Haiku 계열)로 연결(지금 코드는 API키 기반이라 OAuth 전환 필요).
  • STT 입력 경로 — 디스코드 보이스 수신(셀프봇)으로 확정. 유저 음성 Opus RTP를 받아 복호·디코드해 STT에 넣는다(로컬 마이크 아님).