Skip to content

Commit 688e65b

Browse files
[Feat] Perf fix - ensure deepgram provider uses async httpx calls (#11641)
* Checkpoint before follow-up message * Add comprehensive tests for Deepgram transcription functionality * clean up transform * just use 1 test * test cleanup * test fix get_complete_url * test rename file * refactor deepgram URL construction * add logging_obj.pre_call * fix unused imports * feat - add async deepgram support * test_audio_transcription_async * fix python 3.8 test --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent 02b02c7 commit 688e65b

File tree

3 files changed

+185
-27
lines changed

3 files changed

+185
-27
lines changed

litellm/llms/custom_httpx/http_handler.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ async def post(
212212
stream: bool = False,
213213
logging_obj: Optional[LiteLLMLoggingObject] = None,
214214
files: Optional[RequestFiles] = None,
215+
content: Any = None,
215216
):
216217
start_time = time.time()
217218
try:
@@ -227,6 +228,7 @@ async def post(
227228
headers=headers,
228229
timeout=timeout,
229230
files=files,
231+
content=content,
230232
)
231233
response = await self.client.send(req, stream=stream)
232234
response.raise_for_status()
@@ -452,14 +454,15 @@ async def single_connection_post_request(
452454
params: Optional[dict] = None,
453455
headers: Optional[dict] = None,
454456
stream: bool = False,
457+
content: Any = None,
455458
):
456459
"""
457460
Making POST request for a single connection client.
458461
459462
Used for retrying connection client errors.
460463
"""
461464
req = client.build_request(
462-
"POST", url, data=data, json=json, params=params, headers=headers # type: ignore
465+
"POST", url, data=data, json=json, params=params, headers=headers, content=content # type: ignore
463466
)
464467
response = await client.send(req, stream=stream)
465468
response.raise_for_status()

litellm/llms/custom_httpx/llm_http_handler.py

Lines changed: 165 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -982,28 +982,22 @@ async def arerank(
982982
request_data=request_data,
983983
)
984984

985-
def audio_transcriptions(
985+
def _prepare_audio_transcription_request(
986986
self,
987987
model: str,
988988
audio_file: FileTypes,
989989
optional_params: dict,
990990
litellm_params: dict,
991-
model_response: TranscriptionResponse,
992-
timeout: float,
993-
max_retries: int,
994991
logging_obj: LiteLLMLoggingObj,
995992
api_key: Optional[str],
996993
api_base: Optional[str],
997-
custom_llm_provider: str,
998-
client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None,
999-
atranscription: bool = False,
1000-
headers: Optional[Dict[str, Any]] = None,
1001-
provider_config: Optional[BaseAudioTranscriptionConfig] = None,
1002-
) -> TranscriptionResponse:
1003-
if provider_config is None:
1004-
raise ValueError(
1005-
f"No provider config found for model: {model} and provider: {custom_llm_provider}"
1006-
)
994+
headers: Optional[Dict[str, Any]],
995+
provider_config: BaseAudioTranscriptionConfig,
996+
) -> Tuple[dict, str, Optional[bytes], Optional[dict]]:
997+
"""
998+
Shared logic for preparing audio transcription requests.
999+
Returns: (headers, complete_url, binary_data, json_data)
1000+
"""
10071001
headers = provider_config.validate_environment(
10081002
api_key=api_key,
10091003
headers=headers or {},
@@ -1013,9 +1007,6 @@ def audio_transcriptions(
10131007
litellm_params=litellm_params,
10141008
)
10151009

1016-
if client is None or not isinstance(client, HTTPHandler):
1017-
client = _get_httpx_client()
1018-
10191010
complete_url = provider_config.get_complete_url(
10201011
api_base=api_base,
10211012
api_key=api_key,
@@ -1049,6 +1040,91 @@ def audio_transcriptions(
10491040
},
10501041
)
10511042

1043+
return headers, complete_url, binary_data, json_data
1044+
1045+
def _transform_audio_transcription_response(
1046+
self,
1047+
provider_config: BaseAudioTranscriptionConfig,
1048+
model: str,
1049+
response: httpx.Response,
1050+
model_response: TranscriptionResponse,
1051+
logging_obj: LiteLLMLoggingObj,
1052+
optional_params: dict,
1053+
api_key: Optional[str],
1054+
) -> TranscriptionResponse:
1055+
"""Shared logic for transforming audio transcription responses."""
1056+
if isinstance(provider_config, litellm.DeepgramAudioTranscriptionConfig):
1057+
return provider_config.transform_audio_transcription_response(
1058+
model=model,
1059+
raw_response=response,
1060+
model_response=model_response,
1061+
logging_obj=logging_obj,
1062+
request_data={},
1063+
optional_params=optional_params,
1064+
litellm_params={},
1065+
api_key=api_key,
1066+
)
1067+
return model_response
1068+
1069+
def audio_transcriptions(
1070+
self,
1071+
model: str,
1072+
audio_file: FileTypes,
1073+
optional_params: dict,
1074+
litellm_params: dict,
1075+
model_response: TranscriptionResponse,
1076+
timeout: float,
1077+
max_retries: int,
1078+
logging_obj: LiteLLMLoggingObj,
1079+
api_key: Optional[str],
1080+
api_base: Optional[str],
1081+
custom_llm_provider: str,
1082+
client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None,
1083+
atranscription: bool = False,
1084+
headers: Optional[Dict[str, Any]] = None,
1085+
provider_config: Optional[BaseAudioTranscriptionConfig] = None,
1086+
) -> Union[TranscriptionResponse, Coroutine[Any, Any, TranscriptionResponse]]:
1087+
if provider_config is None:
1088+
raise ValueError(
1089+
f"No provider config found for model: {model} and provider: {custom_llm_provider}"
1090+
)
1091+
1092+
if atranscription is True:
1093+
return self.async_audio_transcriptions( # type: ignore
1094+
model=model,
1095+
audio_file=audio_file,
1096+
optional_params=optional_params,
1097+
litellm_params=litellm_params,
1098+
model_response=model_response,
1099+
timeout=timeout,
1100+
max_retries=max_retries,
1101+
logging_obj=logging_obj,
1102+
api_key=api_key,
1103+
api_base=api_base,
1104+
custom_llm_provider=custom_llm_provider,
1105+
client=client,
1106+
headers=headers,
1107+
provider_config=provider_config,
1108+
)
1109+
1110+
# Prepare the request
1111+
headers, complete_url, binary_data, json_data = (
1112+
self._prepare_audio_transcription_request(
1113+
model=model,
1114+
audio_file=audio_file,
1115+
optional_params=optional_params,
1116+
litellm_params=litellm_params,
1117+
logging_obj=logging_obj,
1118+
api_key=api_key,
1119+
api_base=api_base,
1120+
headers=headers,
1121+
provider_config=provider_config,
1122+
)
1123+
)
1124+
1125+
if client is None or not isinstance(client, HTTPHandler):
1126+
client = _get_httpx_client()
1127+
10521128
try:
10531129
# Make the POST request
10541130
response = client.post(
@@ -1061,19 +1137,82 @@ def audio_transcriptions(
10611137
except Exception as e:
10621138
raise self._handle_error(e=e, provider_config=provider_config)
10631139

1064-
if isinstance(provider_config, litellm.DeepgramAudioTranscriptionConfig):
1065-
returned_response = provider_config.transform_audio_transcription_response(
1140+
return self._transform_audio_transcription_response(
1141+
provider_config=provider_config,
1142+
model=model,
1143+
response=response,
1144+
model_response=model_response,
1145+
logging_obj=logging_obj,
1146+
optional_params=optional_params,
1147+
api_key=api_key,
1148+
)
1149+
1150+
async def async_audio_transcriptions(
1151+
self,
1152+
model: str,
1153+
audio_file: FileTypes,
1154+
optional_params: dict,
1155+
litellm_params: dict,
1156+
model_response: TranscriptionResponse,
1157+
timeout: float,
1158+
max_retries: int,
1159+
logging_obj: LiteLLMLoggingObj,
1160+
api_key: Optional[str],
1161+
api_base: Optional[str],
1162+
custom_llm_provider: str,
1163+
client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None,
1164+
headers: Optional[Dict[str, Any]] = None,
1165+
provider_config: Optional[BaseAudioTranscriptionConfig] = None,
1166+
) -> TranscriptionResponse:
1167+
if provider_config is None:
1168+
raise ValueError(
1169+
f"No provider config found for model: {model} and provider: {custom_llm_provider}"
1170+
)
1171+
1172+
# Prepare the request
1173+
headers, complete_url, binary_data, json_data = (
1174+
self._prepare_audio_transcription_request(
10661175
model=model,
1067-
raw_response=response,
1068-
model_response=model_response,
1069-
logging_obj=logging_obj,
1070-
request_data={},
1176+
audio_file=audio_file,
10711177
optional_params=optional_params,
1072-
litellm_params={},
1178+
litellm_params=litellm_params,
1179+
logging_obj=logging_obj,
10731180
api_key=api_key,
1181+
api_base=api_base,
1182+
headers=headers,
1183+
provider_config=provider_config,
10741184
)
1075-
return returned_response
1076-
return model_response
1185+
)
1186+
1187+
if client is None or not isinstance(client, AsyncHTTPHandler):
1188+
async_httpx_client = get_async_httpx_client(
1189+
llm_provider=litellm.LlmProviders(custom_llm_provider),
1190+
params={"ssl_verify": litellm_params.get("ssl_verify", None)},
1191+
)
1192+
else:
1193+
async_httpx_client = client
1194+
1195+
try:
1196+
# Make the async POST request
1197+
response = await async_httpx_client.post(
1198+
url=complete_url,
1199+
headers=headers,
1200+
content=binary_data,
1201+
json=json_data,
1202+
timeout=timeout,
1203+
)
1204+
except Exception as e:
1205+
raise self._handle_error(e=e, provider_config=provider_config)
1206+
1207+
return self._transform_audio_transcription_response(
1208+
provider_config=provider_config,
1209+
model=model,
1210+
response=response,
1211+
model_response=model_response,
1212+
logging_obj=logging_obj,
1213+
optional_params=optional_params,
1214+
api_key=api_key,
1215+
)
10771216

10781217
async def async_anthropic_messages_handler(
10791218
self,

tests/llm_translation/base_audio_transcription_unit_tests.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,22 @@ def test_audio_transcription(self):
5252

5353
assert transcript.text is not None
5454

55+
@pytest.mark.asyncio
56+
async def test_audio_transcription_async(self):
57+
"""
58+
Test that the audio transcription is translated correctly.
59+
"""
60+
61+
litellm.set_verbose = True
62+
litellm._turn_on_debug()
63+
AUDIO_FILE = open(file_path, "rb")
64+
transcription_call_args = self.get_base_audio_transcription_call_args()
65+
transcript = await litellm.atranscription(**transcription_call_args, file=AUDIO_FILE)
66+
print(f"transcript: {transcript.model_dump()}")
67+
print(f"transcript hidden params: {transcript._hidden_params}")
68+
69+
assert transcript.text is not None
70+
5571
def test_audio_transcription_optional_params(self):
5672
"""
5773
Test that the audio transcription is translated correctly.

0 commit comments

Comments
 (0)