마지막 업데이트 2026-10-02
full:video 재생 동안 LiveKit 방 접속과 Agent 준비를 백그라운드로 끝내 둡니다. AI 스텝에 들어가는 순간 준비된 연결을 활성화해 영상→AI 전환의 연결 대기를 줄입니다. 핵심은 “연결 준비”와 “대화 시작”의 분리입니다. 준비 중에는 AI가 듣지도, 말하지도, 메시지를 받지도 않습니다.
| 파일 | 역할 |
|---|---|
apps/web/entities/guest-page-session/lib/first-video-ai-prepare.ts (신규) | 준비 대상 판정 shouldPrepareAiSessionForStep, 롤백 플래그 aiPrepare, stepRequiresSession 이동 |
apps/web/entities/guest-page-session/model/use-step-transition.ts | 첫 영상에서 준비 요청, AI 스텝에서 준비 합류·활성화·fallback, 활동 이동 시 준비 세션 정지 |
apps/web/entities/guest-session/model/use-ai-session.ts | startSession({prepareOnly}), prepareSession·hasPreparedSession·waitForPreparedSession·activatePreparedSession, 텍스트 차단, 준비 무효화 |
*.test.ts 2개 | 판정 helper 6건, 전환 훅 시나리오 7건 (fake aiSession) |
max(0, C − V)입니다. 첫 응답 생성과 재생 시간은 그대로 남습니다. 실측은 아직 하지 않았습니다.?aiPrepare=off 또는 localStorage aiPrepare=off로 바로 기존 동작으로 돌아갑니다.① 새 활동 진입, 첫 스텝이 full:video
use-step-transition.ts:111 이전 세션(활성/준비) 있으면 정지
use-step-transition.ts:141 응답 차단 ON (영상 구간)
use-step-transition.ts:149 판정: 새 활동 + full:video + 뒤에 AI 스텝 + 플래그 ON
use-step-transition.ts:159 prepareSession() 호출, 결과를 기다리지 않음 → 영상은 그대로 재생
│
▼
② 준비 시작
use-ai-session.ts:3012 prepareSession: startSession({prepareOnly:true})
pendingPrepareRef = 진행 중 Promise (나중에 합류용)
use-ai-session.ts:1685 preparedSessionRef = {활동ID, 세대, ready:false}
│
▼
③ 런타임 확인 · 입력 차단
use-ai-session.ts:1877 LiveKit이 아니면 조용히 중단 → AI 스텝에서 기존 시작
use-ai-session.ts:1897 입력 정책 transition_blocked = true (연결·게시 전)
│
▼
④ LiveKit 연결 · Agent 준비
use-ai-session.ts:2501 5초 마이크 fallback 예약 안 함
use-ai-session.ts:2528 waitUntilAgentReady()
use-ai-session.ts:2564 ready:true → "session_prepared" 로그 후 return
(첫 인사 없음, active 아님)
│ 영상 재생 중:
│ · 진행자/수동 텍스트 → :748 차단
│ · Agent 응답 소리 → responseBlocked로 출력 차단
│ · 연결 끊김/실패/ASR 실패 → :1741 준비 무효화
▼
⑤ 영상 종료 → 같은 활동 AI 스텝 진입
use-step-transition.ts:170 응답 차단 OFF
use-step-transition.ts:173 준비 진행 중이면 같은 시도에 합류해 대기
use-step-transition.ts:179 응답 차단 OFF 재적용 (준비 실패 정리가 다시 걸었을 경우 대비)
듣기 의도가 있으면 마이크 예약 (첫 응답 뒤 켜짐)
use-step-transition.ts:187 activatePreparedSession()
│
▼
⑥ 활성화
use-ai-session.ts:3059 재사용 판정: ready + 같은 활동 + 같은 세대 + 연결 있음
use-ai-session.ts:3078 준비 표시 해제 → 목적 AI 스텝의 stepIndex·startMent·autoFinish 적용
use-ai-session.ts:3085 입력 차단 해제 (실제 마이크는 첫 응답 뒤 열림)
use-ai-session.ts:3089 첫 인사 1회 → active = true
│
├─ 재사용 불가 → 기존 handleStartSession (현재와 같은 동작)
▼
⑦ 대화 진행 (기존 흐름과 동일)
정리 경로: 다른 활동 이동(:111) · 세션 정지(use-ai-session.ts:1510) · 언마운트
→ 준비 세션도 함께 닫힘
prepare 상태는 AI 연결과 Agent 준비는 끝났지만 대화는 아직 허용하지 않은 상태입니다. use-ai-session.ts의 ref 두 개로 표현합니다.
| 상태 | pendingPrepareRef | preparedSessionRef | isActive | 의미 |
|---|---|---|---|---|
| idle | null | null | false | 준비 없음 |
| preparing | Promise | {ready:false} | false | 방 접속·Agent 준비 대기 중 |
| prepared | null | {ready:true} | false | 연결 완료, 대화 금지 |
| active | null | null | true | 일반 대화 상태 (기존과 동일) |
idle ──prepareSession()──▶ preparing ──Agent ready──▶ prepared ──activatePreparedSession()──▶ active preparing·prepared → idle: 실패 · 비LiveKit 런타임 · 연결 끊김/실패/ASR 실패 · 다른 활동 이동 · 세션 정지 · 언마운트 · 일반 startSession 호출(새 시작이 덮어씀)
transition_blocked = true)responseBlocked = true)isActive = false: 외부(진행자 화면, 재시작, 자동 듣기 복구)에는 세션이 없는 것으로 보입니다.ready === true, 같은 활동 ID, 같은 세대, LiveKit 세션 존재. 네 가지를 모두 만족해야 재사용하고, 하나라도 어긋나면 기존 방식으로 새로 시작합니다.
| 항목 | 방법 |
|---|---|
| 첫 인사 (AUTO_START) | 준비 모드에서 return, 활성화 때 1회 |
| 아동 마이크 → AI 송신 | 입력 정책 transition_blocked |
| 5초 마이크 자동 복구 | 준비 모드에서 예약 안 함 |
| 진행자·수동 텍스트 | sendTextMessage false 반환 |
| 스텝 멘트 (startMent) | isActive false (기존 가드) |
| AI 소리 출력 | responseBlocked |
| 세션 재시작·자동 듣기 복구 | isSessionActive false (기존 가드) |
greeting_enabled)가 꺼져 있습니다.prepare → startSession → /api/livekit/call
→ 토큰에 Agent dispatch 포함 (livekit-token.ts RoomAgentDispatch)
→ 브라우저 방 접속 → LiveKit 서버가 worker에 job 배정
→ ppi_agent 엔트리포인트 (agent.py:5257)
· 첫 줄에서 job 수명 타이머 시작 (agent.py:5260, 기본 20분)
→ ready 신호 → waitUntilAgentReady 통과 → session_prepared
cli.run_app)입니다. 수명 변화가 없습니다.기존: [영상 ─────][AI 대화 ──────────]
↑ job 시작 ↑ 활동 종료 → job 종료
변경: [영상 ─────][AI 대화 ──────────]
↑ job 시작 ↑ 활동 종료 → job 종료
job_lifetime.py). 예를 들어 영상 5분 뒤에 AI 대화 18분이 이어지는 활동은 기존에는 job이 18분이라 정상이었습니다. 변경 후에는 job이 23분이 되어 AI 대화 15분 지점에서 만료됩니다. 대응은 배포 없이 PPI_JOB_MAX_DURATION_SECONDS로 상한을 늘리거나, 긴 영상은 끝 무렵에 준비를 시작하는 것입니다(후자는 코드 수정 필요).worker A (최대 2 job, 부하 = active_jobs / 2 × 0.99) [job: 아동1 준비 중] [빈 자리] → 부하 0.495 → 새 job 받음 [job: 아동1 준비 중] [job: 아동2] → 부하 0.99 → 새 job 거부 → 다른 worker로
agent.py:367 _ppi_agent_load). 준비 중인 job도 그대로 1개로 셉니다.?aiPrepare=off로 바로 되돌릴 수 있습니다.use-ai-session)는 hook 테스트 harness가 없어 자동 검증하지 못했습니다.onDisconnected·onFailed·onAsrStartFailed에서 준비를 무효화하고 AI 스텝에서 새로 시작하도록 고쳤습니다.first_video_prepare_requested → session_prepared → prepared_session_activated