From adcdbc069595c0dda7f5e3bbdb11042f3b08f31c Mon Sep 17 00:00:00 2001 From: Florent Chehab Date: Fri, 17 Jul 2026 17:35:30 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B(summary)=20whisper=20call=20error?= =?UTF-8?q?=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Consider that HTTP 400 errors are due to corrupted audio files by default. * Properly reraise the http error otherwise so that the retry mechanism actually works. --- CHANGELOG.md | 1 + src/summary/summary/core/celery_worker.py | 21 ++++++++++++++++++--- src/summary/summary/core/file_service.py | 6 ++++++ src/summary/summary/core/shared_models.py | 8 +++++++- 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00538e55..d6da3440 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ and this project adheres to - 🩹(backend) identify externally provisioned users to PostHog - 🐛(backend) fix info panel crash for unregistered rooms - ♿️(frontend) focus side panel container on open #1452 +- 🐛(summary) whisper call error handling ## [1.23.0] - 2026-07-08 diff --git a/src/summary/summary/core/celery_worker.py b/src/summary/summary/core/celery_worker.py index cbfa0bcf..11db588a 100644 --- a/src/summary/summary/core/celery_worker.py +++ b/src/summary/summary/core/celery_worker.py @@ -18,7 +18,12 @@ from requests import exceptions from summary.core.analytics import MetadataManager, get_analytics from summary.core.config import get_settings from summary.core.docs_service import create_document_in_lasuite_docs -from summary.core.file_service import FileService, FileServiceException, TranscribeError +from summary.core.file_service import ( + CorruptedAudioFile, + FileService, + FileServiceException, + TranscribeError, +) from summary.core.llm_service import LLMException, LLMObservability, LLMService from summary.core.locales import get_locale from summary.core.models import ( @@ -147,10 +152,20 @@ def transcribe_audio( # Mimic OpenAI's timeout settings timeout=(60, 10 * 60), ) + if res.status_code == 400: + logger.info( + "WhisperX transcription failed, " + "likely due to a corrupted audio file: %s", + res.text, + ) + raise CorruptedAudioFile("WhisperX coudln't decode the audio file.") + try: res.raise_for_status() - except requests.exceptions.HTTPError as e: - raise RuntimeError("WhisperX transcription failed, %s", res.text) from e + except requests.exceptions.HTTPError: + logger.exception("WhisperX transcription failed") + # We reraise the error so that it can be retried by celery + raise transcription_json: dict[str, Any] = res.json() # We remove the "usage" key from the transcription_json dictionary diff --git a/src/summary/summary/core/file_service.py b/src/summary/summary/core/file_service.py index 920f577e..3c10f26d 100644 --- a/src/summary/summary/core/file_service.py +++ b/src/summary/summary/core/file_service.py @@ -41,6 +41,12 @@ class NoAudioInFileError(TranscribeError): error_code = "no_audio_in_file" +class CorruptedAudioFile(TranscribeError): + """Raised when a media file does not contain any audio.""" + + error_code = "corrupted_audio_file" + + def _get_duration_from_packets(local_path: Path) -> float: """Estimate duration from audio packet timestamps.""" # Run ffprobe to inspect the first audio stream in the file. diff --git a/src/summary/summary/core/shared_models.py b/src/summary/summary/core/shared_models.py index 2e80896c..f7423d7d 100644 --- a/src/summary/summary/core/shared_models.py +++ b/src/summary/summary/core/shared_models.py @@ -96,7 +96,13 @@ class TranscribeWebhookFailurePayload(BaseWebhook): # we authorized any other string than the one in the literal # to avoid causing a breaking change on the client side error_code: ( - Literal["unknown_error", "no_audio_in_file", "media_duration_too_long"] | str + Literal[ + "unknown_error", + "no_audio_in_file", + "media_duration_too_long", + "corrupted_audio_file", + ] + | str ) = Field(title="Error code", description="The error code.")