From f015c67f1312131579bdd3fea56332e86ad41581 Mon Sep 17 00:00:00 2001 From: Akhil Sanjay Potdar Date: Fri, 4 Sep 2026 08:18:08 -0400 Subject: [PATCH] fix(bedrock): drop system message from voxtral audio transcription request Bedrock's Converse API rejects a request that carries both a system block and an audio content block, so every Bedrock Voxtral transcription call 400s ("Found system messages ... and audio chunks ... This is not allowed prior to the tokenizer version 13."). Confirmed against live Bedrock that removing the system block, with everything else identical, makes the same request succeed. Voxtral already takes its transcription instruction from the user turn's text block, so the system block was never needed. Update the existing shape test and add an explicit regression test asserting the body carries no system key, plus a Python-side test guarding the dispatch boundary against the same message being reintroduced from that layer. --- .../providers/bedrock/audio_transcription.rs | 30 ++++++++++++++-- .../test_audio_transcription_rust_bridge.py | 34 +++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/litellm-rust/crates/core/src/providers/bedrock/audio_transcription.rs b/litellm-rust/crates/core/src/providers/bedrock/audio_transcription.rs index 9bf1f73a74d..a33670c5113 100644 --- a/litellm-rust/crates/core/src/providers/bedrock/audio_transcription.rs +++ b/litellm-rust/crates/core/src/providers/bedrock/audio_transcription.rs @@ -70,6 +70,12 @@ impl AudioTranscriptionProviderConfig for BedrockAudioTranscriptionConfig { if let Some(temperature) = optional_params.get("temperature") { inference_config.insert("temperature".to_string(), temperature.clone()); } + // Bedrock's Converse API rejects a `system` block on any request that + // also carries an audio content block (HTTP 400: "Found system + // messages ... and audio chunks ... This is not allowed prior to the + // tokenizer version 13."). Voxtral takes the transcription + // instruction from the user turn's `text` block above, so no system + // prompt is needed here. Ok(AudioTranscriptionRequestData { body: json!({ "messages": [{ @@ -79,7 +85,6 @@ impl AudioTranscriptionProviderConfig for BedrockAudioTranscriptionConfig { {"text": instruction} ] }], - "system": [{"text": "You are a transcription assistant."}], "inferenceConfig": inference_config, }), }) @@ -179,12 +184,33 @@ mod tests { {"text": "Transcribe the audio. Respond with only the transcript. The audio language is en. Additional context: Speaker names"} ] }], - "system": [{"text": "You are a transcription assistant."}], "inferenceConfig": {"maxTokens": 4096, "temperature": 0} }) ); } + #[test] + fn request_omits_system_message_alongside_audio_content() { + // Regression test: Bedrock's Converse API 400s on a `system` block + // combined with an audio content block ("Found system messages ... + // and audio chunks ... This is not allowed prior to the tokenizer + // version 13."). Confirmed against live Bedrock that dropping + // `system` while keeping everything else identical makes the + // request succeed. + let result = BEDROCK_AUDIO_TRANSCRIPTION_CONFIG + .transform_transcription_request( + "mistral.voxtral-mini-3b-2507", + json!({"data": "AQI=", "format": "wav", "filename": "sample.wav"}), + Map::new(), + ) + .expect("request"); + assert!( + result.body.get("system").is_none(), + "request body must not carry a `system` block alongside an audio content block: {}", + result.body + ); + } + #[test] fn response_concatenates_content_blocks() { let result = BEDROCK_AUDIO_TRANSCRIPTION_CONFIG diff --git a/tests/test_litellm/test_audio_transcription_rust_bridge.py b/tests/test_litellm/test_audio_transcription_rust_bridge.py index bbeb6c38f78..40ea20c17ef 100644 --- a/tests/test_litellm/test_audio_transcription_rust_bridge.py +++ b/tests/test_litellm/test_audio_transcription_rust_bridge.py @@ -149,3 +149,37 @@ async def test_bedrock_atranscription_uses_rust_only_path() -> None: rust_bridge.configure_rust_transcription(transcription=None, atranscription=None) assert response.text == "rust" + + +def test_bedrock_transcription_dispatch_adds_no_system_message() -> None: + """Guard the Python/Rust dispatch boundary for the Bedrock Voxtral fix. + + Bedrock's Converse API rejects a request that carries both a `system` + block and an audio content block. The fix removes that `system` block + from the request the Rust transform builds (see + litellm-rust/crates/core/src/providers/bedrock/audio_transcription.rs), + since this dispatch layer mocks the whole bridge callable and never sees + that internal JSON. This test instead pins the boundary Python does + control: it must not hand the bridge a `system` message (directly or via + optional_params) that could reintroduce the same conflict from this side. + """ + captured: dict[str, object] = {} + + def capture(**kwargs: object) -> dict[str, object]: + captured.update(kwargs) + return {"text": "ok"} + + rust_bridge.configure_rust_transcription(transcription=capture, atranscription=None) + try: + litellm.transcription( + model="bedrock/mistral.voxtral-mini-3b-2507", + file=("audio.wav", b"audio", "audio/wav"), + language="en", + ) + finally: + rust_bridge.configure_rust_transcription(transcription=None, atranscription=None) + + assert "system" not in captured + optional_params = captured.get("optional_params") + assert isinstance(optional_params, dict) + assert "system" not in optional_params