이윤준 8회기 종료멘트 선발화 자동전환 실패
— 스텝 미매칭 원인 분석 분석 완료 · 수정 미적용

마지막 업데이트 2026-09-12

쉬운 설명 한 장 보기 · 사전지식 없이 읽는 요약

작성일: 2026-08-27로그 사건: 2026-08-25 20:12 KSTroom: 30806c4c…_8대상: 아동 클라이언트 로그 15,028건런타임: LiveKit

결론

핑퐁이가 스텝1(5명 친구중에 고르기)의 종료멘트를 아직 스텝0(대화 2)에 있을 때 미리 발화했고, 자동전환 판정은 발화 종료 시점의 현재 스텝 문구만 검사하기 때문에 미매칭으로 폐기됐다. LiveKit 완료 이벤트는 정상 도착했고 checkPhrase 정규화도 정상이었다. 즉 이벤트 누락이 아닌 순수 미매칭이다.

진행자 화면과의 괴리 — 하이라이트는 판정이 아니다. 진행자 캡처 순간(모니터 타이머 11:39 → cumulativeElapsedSeconds 역산 ≈20:12:24)은 이미 스텝1 진행 중이고 화면에는 종료멘트가 하이라이트되어 있다. 그래서 “스텝1에서 종료멘트가 매칭됐는데 안 넘어간다”로 보인다. 그러나 그 발화는 20:12:02에 스텝0에서 나온 것이고 판정은 20:12:09에 스텝0 문구로 이미 끝나 폐기됐다. 하이라이트는 transcript-chat-area.tsx:256현재 스텝 phrases로 과거 assistant 메시지 전체를 매번 재계산하는 별개 코드이며, highlight-keywords.tsx는 임계값 없이 키워드 1개만 걸려도 칠한다. 즉 스텝이 바뀌는 순간 옛 발화가 소급 하이라이트된다 — 판정 결과와 무관하다.

시간순 인과 다이어그램

아동 2분 무응답 (스텝0 = 대화 2)
   → 진행자 무응답 필러 "음..." 전송 (20:12:01.252, responseInstructions 없음)
   → agent는 활동 단위 instruction 1개만 보유 (스텝 인식 없음)
   → 내용 없는 입력을 "마무리 신호"로 해석 → 스텝1 종료멘트를 스텝0에서 선발화 (20:12:02.151)
   → transcript는 responseDataRef에 보관만 (판정 없음)
   → agent_response_completed 정상 도착 (20:12:09.723) → checkPhrase(현재=스텝0 문구 3개) = 0/3 미매칭
   → responseDataRef.delete() 로 폐기, "AI speech ended…" 로그 없음, 자동전환 없음
   → 진행자 수동 전환 스텝0→스텝1 (20:12:14.196), config를 스텝1 문구로 교체
   → 그러나 AI는 종료멘트를 이미 소진 → 스텝1에서 자발적으로 재발화하지 않음
   → 진행자가 프리셋 "- 다음으로 넘어가자" 입력 (20:12:33.635)
   → 종료멘트 재발화 (20:12:34.589) → 매칭 성공 (20:12:43.145) → 자동전환 (20:12:44.174)
   ⇒ 체감 지연 약 42초, 그리고 아동은 스텝0 종료멘트("5명 중 2명 골라봐")를 끝까지 듣지 못함
부수 피해: 스텝0의 종료멘트가 발화되지 않았으므로 아동은 “다섯 명 중 두 명을 골라봐”라는 과제 지시 자체를 받지 못한 채 스텝1로 넘어갔다. 자동전환 지연보다 이쪽이 임상적으로 더 큰 손실이다.

시각 타임라인 (2026-08-25 KST)

20:11:58

GUEST_AUTO_RESPONSE · Manual mode ready at 7 seconds — 아동 무응답 7초 경과, 진행자 필러 버튼 활성.

20:12:01.252

Published LiveKit user text message hasResponseInstructions:false — 필러 "음..." 전송 (textLength 4).

20:12:02.151

Checking auto-finish phrase — transcript = “어떤 친구랑 잘 맞는지 너랑 얘기해볼 수 있어서 나도 생각이 더 정리된 것 같아! 고마워!”, 그런데 phrases스텝0의 3개. 스텝1 종료멘트를 스텝0에서 선발화.

20:12:09.723

agent_response_completed(response-12) 정상 수신 → assistant_audio_stoppedcheckPhrase 실행 → 0/3 미매칭. AI speech ended, processing auto-finish 로그 없음, 응답 폐기.

20:12:14.196

Step changed by host activityIndex 3 / stepIndex 1 — 자동이 아닌 진행자 수동 전환. 직후 same_activity_session_config_updated autoFinishPhraseCount:2(스텝1 문구).

≈20:12:24 (캡처 시점)

모니터 타이머 11:39 = 누적 699초. 같은 로그의 cumulativeElapsedSeconds(20:12:44 → 719초, 벽시계와 약 37초 오프셋)로 역산한 값. 이 순간 현재 스텝은 이미 스텝1이고, 스텝0 시절 발화가 스텝1 문구로 소급 하이라이트되어 “매칭됐는데 전환 안 됨”으로 보인다.

20:12:20.326

필러 "음..." 재전송 → 20:12:21 “천천히 생각해도 괜찮아…” (미매칭). AI는 종료멘트를 다시 말하지 않는다.

20:12:33.635

진행자 텍스트 입력 - 다음으로 넘어가자 (textLength 11) — 실제 복구 액션. 자연 복구가 아니다.

20:12:34.589 → 20:12:43.145 → 20:12:44.174

종료멘트 재발화(response-14) → AI speech ended, processing auto-finish matchedCondition 기록 → Auto-finish triggeredactivityIndex 4(활동회고) 전환. 경로는 건강했다.

추정 원인

순서원인확정도
0 (트리거)아동 2분 무응답 → 진행자 필러 "음..."responseInstructions 없이 전송. 내용 없는 입력이라 모델이 “마무리” 방향으로 수렴추정 필러 전송·순서·hasResponseInstructions:false는 사실, 모델의 해석은 추정
1 (직접)핑퐁이가 스텝0 종료멘트를 건너뛰고 스텝1 종료멘트를 스텝0에서 선발화사실 20:12:02 로그의 transcript와 phrases 불일치
2 (직접)자동전환 판정이 오디오 종료 시점의 현재 스텝 phrases만 검사하고, 실패하면 응답을 즉시 폐기사실 use-ai-session.ts 4196–4231 + use-session-auto-finish.ts checkPhrase
3 (구조)세션 instruction이 활동 단위 1개이고 스텝 전환 시 갱신되지 않음 → 모델이 활동 내 모든 스텝의 종료멘트를 언제든 말할 수 있다사실 buildActivityInstructionsteps/stepIndex를 보지 않음

배제된 원인

영향 범위

범위
발생 조건한 활동 안에 연속된 AI 스텝(prompt/hybrid)이 2개 이상이고, 스텝별 종료멘트가 서로 다르며, 모델이 뒤 스텝의 종료멘트를 앞 스텝에서 발화하는 경우
런타임LiveKit·Realtime·TTS 공통. 단 selectResponseIdsForAssistantAudioEnd가 LiveKit은 played 1건, Realtime/TTS는 pending 전체를 검사해 노출면이 모드별로 다르다
데이터 의존판정식은 진행자/기획이 편집하는 step.autoFinishPhrases 문자열이 가공·검증 없이 그대로 쓰인다 (createAutoFinishConfigFromStep, activity-utils.ts:320)
사용자 체감이번 건 약 42초 지연 + 진행자 수동 전환·프리셋 입력 2회 개입. 더 중요한 손실은 스텝0 과제 지시 미전달
재현성모델 출력에 의존해 비결정적. 다만 “장시간 무응답 + 필러 전송” 조합에서 재현율이 높을 것으로 추정 — 다른 세션 로그로 값싸게 교차 확인 가능

추천 해결책

1순위 — 필러·스텝 메시지에 responseInstructions 주입 (원인 0·1을 직접 차단)
sendTextMessage(msg, { responseInstructions }) 경로는 이미 Realtime(response.instructions)과 LiveKit(payload responseInstructions) 양쪽에서 동작하며 첫 발화(firstTurnMent)가 이 경로를 쓴다. 필러 "음..."와 스텝 전환 메시지에 “지금 스텝의 목표를 계속 진행할 것, 활동 마무리 멘트는 하지 말 것”을 동봉한다.
신규 채널 0, 매칭 로직 무변경, agent 레포 변경 없음. 현재 이 두 경로는 모두 hasResponseInstructions:false로 전송되고 있다.
2순위 — 다음 스텝 문구 감지 시 “자동 전환”이 아니라 “진행자 확인”
현재 스텝 미매칭 + 다음 스텝 phrase 매칭이면 ① 전용 로그(예 auto_finish_next_step_phrase_detected) ② 진행자 모니터에 “핑퐁이가 다음 종료멘트를 먼저 말했어요 — 넘어갈까요?” 확인 배너를 띄운다. 선례가 있다: resolveEndConversationKeywords의 크로스 스텝 매칭도 진행자가 “시간종료”를 누른 뒤에만 활성된다.
3순위 — 관측 보강
미매칭으로 응답을 폐기하는 지점에 responseId / stepIndex / phrases / 미매칭 1줄 로그를 남긴다. 현재는 “AI speech ended… 로그가 없다”로만 판별해야 해서 아래 3개 경로가 구분되지 않는다.
4순위 — 하이라이트와 판정의 불일치 해소
진행자가 “매칭됐다”고 오인하는 직접 원인이다. ① 하이라이트에도 판정과 같은 임계값(조각 3개 이상이면 2개 이상)을 적용하거나 ② 실제 판정이 성립한 메시지만 별도 표시(예 “전환 조건 충족”)로 구분하고, ③ 스텝 전환 이전에 발화된 메시지는 현재 스텝 문구로 재하이라이트하지 않는다. 자동전환 동작은 건드리지 않으므로 사이드 이펙트 표면이 좁다.

하지 말아야 할 것

회귀 위험 · 사이드 이펙트

수정안사이드 이펙트 위험검증 포인트
1순위 responseInstructions 주입낮음. 매칭 로직 무변경이라 자동전환 오탐 0. 다만 지시문이 강하면 정상 종료 타이밍에도 마무리 멘트를 미루어 전환이 늦어질 수 있다스텝 마지막 발화에서 종료멘트가 정상적으로 나오는지, 필러 후 대화가 이어지는지 실세션 확인
2순위 진행자 확인 배너중간. 배너가 잦으면 진행자 피로. 스텝 전환 직후 새 응답에 감지가 걸려 진행자 의도를 덮는 이중 전환 방지 가드(전환 후 N초 억제)가 필요모니터 새로고침 stale step push와 겹칠 때 되돌아간 스텝에서 앞 스텝 문구로 재감지되지 않는지
관측 로그 추가낮음. 로그량 증가만세션당 미매칭 폐기 건수가 과다하지 않은지
(비권장) 자동 look-ahead높음. 아동 활동 무음 스킵, jumpConfig 오분기, Realtime/TTS의 pending 전체 검사로 오탐 폭 확대, 마지막 스텝·비AI 스텝에서 미탐도입하지 않는 것을 권고

같은 증상의 인접 경로와 판별법

“종료멘트는 나왔는데 전환 안 됨”은 최소 3개 경로가 있고, 로그 시그니처로 구분된다.

경로판별 시그니처상태
A. 스텝 미매칭(이 문서)Checking auto-finish phrase 있음 + agent_response_completed 있음 + AI speech ended… 없음, 그리고 phrases가 발화 내용과 다른 스텝 것미수정
B. LiveKit 완료 이벤트 누락Checking auto-finish phrase 있음 + agent_response_completed 없음(terminal_boundary만)미수정 · 별도 문서
C. 끼어들기 폐기assistant_audio_cleared 경로 — shouldFinalizeAssistantAudio=false이므로 판정 없이 transcript 폐기미수정 · 미문서화
추가 미확인: autoTransitionEnabled가 OFF인 동안에는 assistant_transcript_done 단계에서 저장조차 되지 않아 도중에 ON으로 바꿔도 소급 매칭이 불가하다. 또한 updateStartMent가 같은 플래그에 묶여 있어 자동전환 OFF 세션에서 startMent 전송이 누락되는지 별도 확인이 필요하다.

코드 근거

경로확인한 동작
apps/web/entities/guest-session/model/use-ai-session.ts:3754-3775assistant_transcript_done에서 responseDataRef.set(responseId, {itemId, transcript})보관만, 판정 없음. Checking auto-finish phrase 로그가 이 단계다.
apps/web/entities/guest-session/model/use-ai-session.ts:4196-4231assistant_audio_stopped/cleared에서 autoFinish.checkPhrase(transcript). 오디오 종료 시점의 현재 config로만 판정하고, 실패 시 responseDataRef.delete(id).
apps/web/hooks/use-session-auto-finish.tscheckPhrase: 큰따옴표·공백 제거 후 쉼표 조각 포함 검사, 조각 3개 이상이면 2개 이상 매칭. handlePingpongSpeech → 1초 뒤 onAutoFinish.
apps/web/entities/guest-page-session/model/use-step-transition.ts:143-189같은 활동 내 스텝 전환은 세션 유지 + resetAutoFinish()createAutoFinishConfigFromStep()updateAutoFinishConfig()updateStartMent(). instruction은 갱신하지 않는다.
apps/web/lib/activity-utils.ts:320createAutoFinishConfigFromStep: step.autoFinishPhrases를 가공·검증 없이 그대로 판정식으로 사용.
apps/web/lib/openai-create-call.ts:29-51,84-101 · app/api/livekit/call/route.ts:220-233buildActivityInstructionactivity.instruction 단일 문자열만 변수 치환. steps/stepIndex를 보지 않는다.
apps/web/widgets/monitor/ui/transcript-chat-area.tsx:69-72,256 · apps/web/shared/lib/highlight-keywords.tsx · app/monitor-dashboard/[group]/[roomId]/page.tsx:825-827,2068모니터 하이라이트는 현재 스텝autoFinishPhrases를 쉼표로 합쳐 과거 assistant 메시지 전체에 매번 적용한다. highlightKeywords에는 2/3 임계값이 없어 키워드 1개만 포함돼도 칠한다 — 하이라이트 ≠ 자동전환 판정. 스텝 전환 시 옛 발화가 소급 하이라이트되는 원인.
apps/web/lib/voice-agent/session-events.ts:351LiveKit은 raw agent_response_completed일 때만 자동종료 판정을 실행한다(경로 B의 근거).

조사 방법

아동 클라이언트 세션 로그 15,028건을 KST 시각·responseId·stepIndex로 정렬해 20:12 구간을 복원했고, checkPhrase를 재구현해 세션 내 66개 AI 발화 × 8개 phrase set 전수 시뮬레이션으로 오탐/미탐을 확인했다. 원인 분석은 developer(Codex)·reviewer(Claude Code) 2단계 교차 검토를 거쳤고, reviewer가 제기한 필러 트리거·복구 액션·완료 이벤트 도착 시각은 원본 로그로 재확인했다.

로그 취급 주의: 세션 로그 .gz를 브라우저로 내려받으면 ContentEncoding 때문에 실제로는 평문 JSONL이다. 확장자만 보고 gzip -d를 시도하면 실패한다.

수정 미적용: 이 문서는 원인 분석과 방안 정리까지다. 코드 변경은 없다.