Repository navigation
fix(ai): STT 일시 장애 재시도 + Deepgram 타임아웃 분리 - #244
Merged
Merged
Conversation
운영 음성 답변 83건 중 7건(약 8%)이 STT 실패. ai_request_logs 상 성공 호출은 2.4~12초(p50 3.6초)인데 실패 2건은 60.8초/61.5초 — 전부 일괄 timeout=60 에 정확히 걸린 타임아웃이었다. 오디오 크기·손상·인증·모델·로컬 네트워크는 실측으로 배제했다. 실패 파일 6개를 재전송하니 5개가 1.2~4.0초에 정상 전사됐고, Deepgram anycast IP 8개·DNS·PMTU 모두 정상이었다. 상류가 구간적으로 멎는 것이 원인이고, 우리 쪽 문제는 그 딸꾹질을 영구 실패로 굳히는 데 있었다. - VoiceConsumer 가 SttError.retriable 을 읽어 지수 백오프로 재시도 (STT_MAX_ATTEMPTS 기본 3, STT_RETRY_BACKOFF_SEC 0.5). 지금까지 retriable 플래그는 생성만 되고 아무도 읽지 않았다 — questions/followup/feedback 은 classify_failure 로 전파하는데 voice 만 빠져 있었다. - 비재시도(STT_AUTH_FAILED/STT_BAD_REQUEST)와 예상 못 한 예외는 재시도 없이 즉시 실패 콜백 — 재전송해도 결과가 같거나 코드 버그다. - httpx.Timeout(read=30, connect=5) 으로 분리. 연결이 막혔을 때 read 한도만큼 사용자를 붙잡지 않는다. 정상 호출 최대가 12초라 read 30초도 여유가 크다. - 타임아웃 예외에 타입을 같이 남긴다. str() 이 빈 문자열이라 기존 로그가 "Deepgram 호출 실패: " 로 끝나 connect/read 구분이 불가능했다. - ai_request_logs 는 시도마다 한 행. error_message 앞의 [n/N] 으로 재시도율 추적.
This was referenced Sep 23, 2026
i3months
added a commit
to i3months/stackup
that referenced
this pull request
Oct 6, 2026
STT 가 실패하면 답변이 "음성 인식에 실패했습니다" 로 확정되고 끝이었다. 오디오는 S3 에 그대로 남아 있는데 꺼낼 경로가 없어서, 사용자는 방금 말한 답변을 통째로 다시 입력해야 했다. 실패의 대부분은 Deepgram 이 간헐적으로 멎는 것이고 같은 파일을 재전송하면 대체로 전사된다(PR Team-StackUp#244 의 자동 재시도가 1차 방어선). 이건 그마저 소진됐을 때 사용자가 직접 당기는 2차 방어선이다. - POST /api/sessions/{sid}/messages/voice/{mid}/retranscribe (VoiceRetranscribeService) — placeholder 상태로 되돌리고 업로드와 같은 VoiceAnswerUploadedEvent + AFTER_COMMIT 경로로 analyze.voice 재발행 - 실패 메시지가 세션의 마지막일 때만 허용. 이미 다시 답변했다면 뒤늦은 전사가 지나간 턴을 덮어쓴다(VOICE_RETRANSCRIBE_NOT_ALLOWED) - content_type 은 저장하지 않으므로 S3 key 확장자에서 역산(buildKey 의 역함수) - 프론트: 스테이지 인라인 배너 + 대화 기록 드로어 양쪽에 '다시 인식하기'. 스테이지에는 실패 안내가 아예 없어서, 사용자는 왜 답변이 사라졌는지 모른 채 컴포저만 다시 열리는 걸 봐야 했다 - 녹음이 남아 있을 때는 문구도 "다시 답변해 주세요" 대신 "녹음은 남아 있어요" 로
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
작업 내용
VoiceConsumer가SttError.retriable을 읽어 지수 백오프로 재시도 (STT_MAX_ATTEMPTS기본 3,STT_RETRY_BACKOFF_SEC0.5)STT_AUTH_FAILED/STT_BAD_REQUEST)와 예상 못 한 예외는 재시도 없이 즉시 실패 콜백timeout=60→httpx.Timeout(read=30, connect=5)으로 분리ConnectTimeout/ReadTimeout구분 가능)ai_request_logs는 시도마다 한 행,error_message앞에[n/N]변경 이유 / 배경
운영 음성 답변 83건 중 7건(약 8%) 이 STT 실패해 "음성 인식에 실패했습니다. 텍스트로 다시 답변해 주세요." 로 확정됐습니다.
ai_request_logs의stt.transcribe분포:실패는 "느린 성공"이 아니라
deepgram_timeout_sec=60에 정확히 걸린 타임아웃입니다. HTTP 상태도 응답 본문도 없습니다.배제한 원인 (운영 서버 실측)
상류가 구간적으로 멎습니다(테스트 중 3개 모델 30/30 전부 행 → 몇 분 뒤 13/13 정상 회복). 우리가 통제할 수 있는 건 그 딸꾹질을 어떻게 받아내느냐뿐이고, 지금은 1회 실패가 곧 사용자에게 영구 실패였습니다.
retriable이 죽어 있던 문제SttError(retriable=True)는 생성만 되고 아무도 읽지 않았습니다. questions/followup/feedback 컨슈머는classify_failure로 retriable 을 전파하는데 voice 만 빠져 있었고,VoiceCallbackPayload에는 필드 자체가 없습니다. 실패 파일 5/6 이 재전송에서 성공하므로 재시도만으로 대부분 흡수됩니다.테스트
uv run pytest405 passedblack --check,flake8clean영향 범위
DEEPGRAM_CONNECT_TIMEOUT_SEC,STT_MAX_ATTEMPTS,STT_RETRY_BACKOFF_SEC신규 /DEEPGRAM_TIMEOUT_SEC기본값 60→30..env.example(루트·ai) +docs/environment.md동기화 완료. 운영.env에 해당 키가 없어 기본값이 그대로 적용됩니다.리뷰어 체크포인트
ai_request_logs에 시도마다 한 행이 남습니다. Deepgram 호출 자체가 과금 단위라 이게 사실에 가깝다고 판단했는데, 기존에 이 테이블로 실패율을 보고 계셨다면 분모가 달라집니다 (error_message의[n/N]으로 구분 가능). 한 행만 남기는 쪽이 낫다면 말씀해 주세요.ai/CLAUDE.md §5의 "코드 버그 → DLQ" 분류를 따랐는데,failure_signal.classify_failure는 unknown 을 retriable=true 로 봅니다. voice 는 직접-발행 모델이라 다르게 잡았습니다.남은 작업 (별도 PR)
실패 시 오디오는 S3 에 남지만 재전사 API 가 없어 사용자가 답변을 통째로 다시 입력해야 합니다. 스키마·UI 변경이 필요해 분리했습니다.
🤖 Generated with Claude Code