# Podcast 시스템 설계 문서
> 최종 업데이트: 2026-03-21

---

## 아키텍처 개요

```
[대시보드 index.html]
   └─ /api/podcast/stream      → 주제 기반 전체 에피소드 스트리밍
   └─ /api/podcast/interrupt   → 청취자 질문 인터럽트 (line-by-line SSE)
   └─ /api/podcast/ask         → Interactive 탭 질문 응답 파이프라인
   └─ /api/podcast/auto-topic  → 자동 주제 전환 엔진
   └─ /api/podcast/ticker      → OBS 헤드라인 티커 HTML
```

---

## 1. 에피소드 스트리밍 (`/stream`)

**파일**: `butler/api/podcast.js`
**LLM**: Qwen3.5-35B-A3B (port 8081, `/v1/chat/completions`, stream=true)
**포맷**: JSON 배열 → 줄마다 파싱 후 SSE emit

```
fetchKnowledge(topic)          ← knowledge_entries WHERE category='crawl'
    ↓ 최신 크롤 기사 최대 5개
LLM (SYSTEM_PROMPT + context)  ← 잇섭/시아 캐릭터 + 기사 컨텍스트
    ↓ JSON 배열 스트리밍
parseUtterances() → SSE        ← {seq, speaker, text, pause_after}
```

**캐릭터**
- 잇섭 (남): 익살맞고 가벼운 성격, 감탄사 많음
- 시아 (여): 차분하고 분석적인 전문가

---

## 2. 인터럽트 (`/interrupt`)

**파일**: `butler/api/podcast.js`
**포맷**: 줄 단위 (`잇섭: 텍스트`, `시아: 텍스트`) — JSON 금지
**TTS**: edge-tts (fallback) 또는 Chatterbox (port 8001)

```
세션 히스토리 (최근 6줄) + 질문
    ↓
INTERRUPT_SYS 프롬프트 (4줄 대화, /no_think)
    ↓ 스트리밍
parseLinesAndSend() → SSE     ← 줄 완성 즉시 emit
```

---

## 3. Interactive Ask (`/ask`) — 현재 구조

**파일**: `butler/api/podcast-interactive.js`
**LLM**: EXAONE-7.8B (port 8081, `/completion`, stream=false)
**TTS**: faster-qwen3-tts 1.7B (WSL port 7890)

```
질문 입력
    ├─ 룰기반 브릿지 텍스트 선택 (즉시)  ← BRIDGES_KO/EN 배열
    ├─ LLM answerPromise (n_predict=80)  ← 병렬 시작
    └─ LLM wrapPromise  (n_predict=30)  ← 병렬 시작

t=0s : 브릿지 TTS → 파일 저장 → SSE tts_done
t=2s : 첫 오디오 재생
t=8s : answer LLM 완료 → TTS → SSE tts_done
t=10s: wrap LLM 완료 → TTS → SSE tts_done
```

**타이밍 분석**
- 브릿지: 룰기반(즉시) → TTS ~2s → 첫 오디오
- LLM TTFT 느린 이유: EXAONE-7.8B 프리필 지연 (~8s), 슬롯 경쟁 가능성

---

## 4. Interactive Ask — 다음 설계 (미구현)

**설계 방향**: 즉각 대화(15초) + 백그라운드 RAG 보강

```
질문 입력
    ├─ [즉시] LLM (RAG 없음) → 4줄 대화 (15초 분량)
    │       parseLinesAndSend 방식으로 줄마다 TTS 트리거
    │       포맷: "잇섭: ...\n시아: ...\n잇섭: ...\n시아: ..."
    │
    └─ [백그라운드] fetchKnowledge(question)
            ↓ (knowledge_entries crawl 데이터, 보통 1-2s)
            결과 있으면 → LLM에 컨텍스트 주입 → 추가 1-2줄 생성
            결과 없으면 → 그냥 종료

예상 타이밍:
  t=0s  : 첫 줄 LLM 시작 + RAG 쿼리 병렬
  t=2s  : 첫 줄 TTS → 오디오 재생
  t=5s  : 두 번째 줄 재생
  t=15s : 기본 4줄 완료
  t=12s : RAG 완료 → 보강 세그먼트 생성
```

---

## 5. 자동주제 전환 (`auto-topic`)

**파일**: `butler/lib/podcast-auto-topic.js`
**트리거**: 뉴스 스파이크, 키워드 이상 감지, 타이머
**OBS 연동**: `/api/podcast/ticker` HTML iframe

---

## 6. TTS 레이어

| 레이어 | 포트 | 용도 |
|--------|------|------|
| faster-qwen3-tts 1.7B | WSL:7890 | `/ask` GPU TTS (고품질, 한국어) |
| Chatterbox | :8001 | `/tts` 엔드포인트 (목소리 클론) |
| edge-tts | 프로세스 | fallback (무료, Azure 신경망) |

---

## 7. Knowledge DB RAG

**소스**: `platform/lib/knowledge-db` → `knowledge_entries`
**쿼리**: `category='crawl'`, tags 매칭 또는 FTS LIKE
**사용처**: `/stream` (현재 활성), `/ask` (다음 구현 예정)
