바지인¶
바지인은 봇 TTS가 재생 중일 때 사용자가 말하기 시작하는 흐름입니다. Gateway가 실제 재생 상태와 duck 적용 경계를 알려 주고, EPD가 그 경계 이후 오디오로 후보를 확정하거나 취소합니다.
TTS 재생 중 SOHRI 바지인¶
sequenceDiagram
participant C as Client
participant G as WebRTC Gateway
participant S as Socket Backend
participant E as EPD
participant D as DM
participant DB as PostgreSQL
S-->>G: TTS generation N 오디오
G-->>C: TTS 재생
G->>S: tts_playback_state(active=true)
C->>G: 사용자 발화 PCM
G->>S: PCM
S->>E: PCM(ttsPlaybackActive=true)
E-->>S: candidate(activityId)
S-->>G: speech_candidate(activityId)
G->>G: 실제 track에 duck gain 적용
G->>S: tts_duck_applied(activityId, generation N, ...)
S->>E: duck 적용 control
alt EPD가 사용자 발화로 확정
E-->>S: confirmed(activityId)
S->>S: utterance generation 증가
S->>S: response generation N 무효화
S-->>G: speech_started(activityId)
G->>G: generation N 재생 중단
E-->>S: END + STT
S->>DB: 새 USER 저장
S->>D: 새 사용자 메시지
Note over D,DB: 기존 DM 호출·USER/BOT 저장은 롤백하지 않음
D-->>S: 새 응답
else 후보 취소
E-->>S: cancelled(activityId)
S-->>G: speech_candidate_cancelled
E-->>S: END + STT(있을 수 있음)
opt cancelled-end.mode=PERSIST
S->>DB: speech_phase=CANCELLED로 USER만 보존
end
Note over S,D: 취소 발화로 DM 턴은 진행하지 않음
end
EPD candidate 뒤 tts_duck_applied는 필수 제어 경계입니다. EPD는 ACK 이후의 오디오를
검증하며 gain의 특정 값 자체로 확정하지 않습니다. ACK가 오디오 프레임 기준 기한 안에 오지
않으면 후보가 취소될 수 있습니다.
response generation 계약¶
기본 대화 경로의 취소는 트랜잭션 롤백이 아니라 출력 세대 무효화입니다.
- 확정 발화가 시작되면
beginUtterance()가 기존 response generation을 증가시킵니다. - 이미 실행 중인 DM HTTP 요청은 계속될 수 있습니다.
- DM 호출 전 저장한 USER, DM 응답과 함께 저장한 BOT/응답 행은 삭제하지 않습니다.
- 레거시 TTS는 합성 후·송신 직전·송신 임계 구역에서 generation을 다시 확인합니다.
- 스트리밍 TTS는 시작 직전과 모든 청크에서 generation을 확인합니다.
- generation이 오래됐으면 오디오,
dialog_response, 오류 프레임을 억제합니다.
이 방식은 이미 기록된 대화 사실을 보존하면서, 늦게 끝난 이전 답변이 다음 질문 위로 재생되는 것을 막습니다.
입력 gate와의 관계¶
입력 gate가 닫힌 DM/TTS 준비 구간에 candidate가 오면 activity를 억제하고
speech_candidate_cancelled를 보냅니다. gate는 재생 상태가 active라는 이유만으로 닫히지 않습니다.
따라서 실제 TTS 재생 중에는 PCM이 EPD로 가며 정상 바지인이 가능하지만, 새 응답을 만드는 임계
구간에는 중첩 USER 턴을 만들지 않습니다.
OpenAI Realtime 바지인¶
OpenAI 엔진은 provider의 semantic VAD와 interrupt_response=true를 사용합니다.
- provider
input_audio_buffer.speech_started를 받으면 로컬 출력 세대를 종료하고realtime_playback_interrupt를 Gateway에 보냅니다. - 클라이언트의
cancel_response에 활성 출력이 있으면 upstream으로response.cancel을 보냅니다. - Gateway가 실제 재생 종료 위치를
realtime_playback_truncated로 보내면 upstream으로conversation.item.truncate를 보냅니다. - 취소된 출력은
tts_stream_end{status:"aborted", reason:"cancelled"}로 닫고 BOT 기록 대상으로 확정하지 않습니다.
취소된 EPD END¶
barge_in_phase=cancelled는 “발화가 아니었다”와 동의어가 아닙니다. 설정이 PERSIST이면
텍스트가 있는 END를 speech_phase=CANCELLED로 저장하되 DM과 TTS는 실행하지 않습니다.
분석·대화 요약·바이오마커 요약은 CONFIRMED 화이트리스트를 사용해 이 행을 제외합니다.
DROP 모드도 가능하지만 두 모드 모두 결과 메트릭과 구조화 로그를 남깁니다.