mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-22 00:31:44 +00:00
fix(router): add _update_kwargs_with_deployment to aspeech()
aspeech() is the only router method that does not call _update_kwargs_with_deployment(). Without it, the deployment UUID is not stored in kwargs metadata, so get_router_model_id() returns None and the cost calculator falls back to the bare model name with no pricing entry -- spend is always $0 for custom-priced TTS deployments. Also replaces inline client-selection logic and manual default_litellm_params loop with the shared helpers already used by other router methods (_get_async_openai_model_client, _update_kwargs_with_deployment). Fixes #27390
This commit is contained in:
parent
fa81017e12
commit
f937668eea
2 changed files with 126 additions and 22 deletions
|
|
@ -3582,29 +3582,14 @@ class Router:
|
|||
request_kwargs=kwargs,
|
||||
)
|
||||
self._update_kwargs_before_fallbacks(model=model, kwargs=kwargs)
|
||||
data = deployment["litellm_params"].copy()
|
||||
data["model"]
|
||||
for k, v in self.default_litellm_params.items():
|
||||
if (
|
||||
k not in kwargs
|
||||
): # prioritize model-specific params > default router params
|
||||
kwargs[k] = v
|
||||
elif k == "metadata":
|
||||
kwargs[k].update(v)
|
||||
|
||||
potential_model_client = self._get_client(
|
||||
deployment=deployment, kwargs=kwargs, client_type="async"
|
||||
self._update_kwargs_with_deployment(
|
||||
deployment=deployment, kwargs=kwargs, function_name="aspeech"
|
||||
)
|
||||
data = deployment["litellm_params"].copy()
|
||||
|
||||
model_client = self._get_async_openai_model_client(
|
||||
deployment=deployment, kwargs=kwargs
|
||||
)
|
||||
# check if provided keys == client keys #
|
||||
dynamic_api_key = kwargs.get("api_key", None)
|
||||
if (
|
||||
dynamic_api_key is not None
|
||||
and potential_model_client is not None
|
||||
and dynamic_api_key != potential_model_client.api_key
|
||||
):
|
||||
model_client = None
|
||||
else:
|
||||
model_client = potential_model_client
|
||||
|
||||
response = await litellm.aspeech(
|
||||
**{
|
||||
|
|
|
|||
119
tests/test_litellm/test_router_aspeech_deployment_kwargs.py
Normal file
119
tests/test_litellm/test_router_aspeech_deployment_kwargs.py
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
"""
|
||||
Tests that Router.aspeech() calls _update_kwargs_with_deployment() so
|
||||
deployment metadata (model_info, model_id) is stored in kwargs for cost
|
||||
calculation.
|
||||
|
||||
Without this call, get_router_model_id() returns None, the cost
|
||||
calculator falls back to the bare model name (no pricing entry), and
|
||||
spend is always $0 for custom-priced TTS deployments.
|
||||
|
||||
See: https://github.com/BerriAI/litellm/issues/27390
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.abspath("../..")
|
||||
) # Adds the parent directory to the system path
|
||||
|
||||
from litellm import Router
|
||||
|
||||
|
||||
def _make_tts_router() -> Router:
|
||||
return Router(
|
||||
model_list=[
|
||||
{
|
||||
"model_name": "my-tts",
|
||||
"litellm_params": {
|
||||
"model": "openai/tts-1",
|
||||
"api_key": "fake-key",
|
||||
},
|
||||
"model_info": {
|
||||
"id": "test-deployment-id",
|
||||
"mode": "audio_speech",
|
||||
"input_cost_per_character": 0.000015,
|
||||
},
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
class TestAspeechDeploymentKwargs:
|
||||
@pytest.mark.asyncio
|
||||
async def test_aspeech_calls_update_kwargs_with_deployment(self):
|
||||
"""aspeech must call _update_kwargs_with_deployment so deployment
|
||||
metadata is available for cost calculation."""
|
||||
router = _make_tts_router()
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
router,
|
||||
"async_get_available_deployment",
|
||||
new_callable=AsyncMock,
|
||||
return_value={
|
||||
"model_name": "my-tts",
|
||||
"litellm_params": {
|
||||
"model": "openai/tts-1",
|
||||
"api_key": "fake-key",
|
||||
},
|
||||
"model_info": {
|
||||
"id": "test-deployment-id",
|
||||
},
|
||||
},
|
||||
),
|
||||
patch.object(
|
||||
router,
|
||||
"_update_kwargs_with_deployment",
|
||||
wraps=router._update_kwargs_with_deployment,
|
||||
) as mock_update,
|
||||
patch("litellm.aspeech", new_callable=AsyncMock, return_value=MagicMock()),
|
||||
):
|
||||
await router.aspeech(model="my-tts", input="hello world", voice="alloy")
|
||||
|
||||
mock_update.assert_called_once()
|
||||
call_kwargs = mock_update.call_args
|
||||
assert call_kwargs.kwargs.get("function_name") == "aspeech" or (
|
||||
len(call_kwargs.args) >= 3 and call_kwargs.args[2] == "aspeech"
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_aspeech_stores_model_info_in_metadata(self):
|
||||
"""After aspeech, kwargs metadata should contain model_info with
|
||||
the deployment ID needed for cost lookup."""
|
||||
router = _make_tts_router()
|
||||
|
||||
deployment = {
|
||||
"model_name": "my-tts",
|
||||
"litellm_params": {
|
||||
"model": "openai/tts-1",
|
||||
"api_key": "fake-key",
|
||||
},
|
||||
"model_info": {
|
||||
"id": "test-deployment-id",
|
||||
"input_cost_per_character": 0.000015,
|
||||
},
|
||||
}
|
||||
captured_kwargs = {}
|
||||
|
||||
async def capture_aspeech(**kwargs):
|
||||
captured_kwargs.update(kwargs)
|
||||
return MagicMock()
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
router,
|
||||
"async_get_available_deployment",
|
||||
new_callable=AsyncMock,
|
||||
return_value=deployment,
|
||||
),
|
||||
patch("litellm.aspeech", side_effect=capture_aspeech),
|
||||
):
|
||||
await router.aspeech(model="my-tts", input="hello world", voice="alloy")
|
||||
|
||||
metadata = captured_kwargs.get("metadata", {})
|
||||
assert metadata.get("model_info") is not None
|
||||
assert metadata["model_info"]["id"] == "test-deployment-id"
|
||||
Loading…
Add table
Reference in a new issue