mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-06 08:16:43 +00:00
Merge fa26fec171 into 31ca4ddf32
This commit is contained in:
commit
2db8f19dcf
5 changed files with 128 additions and 5 deletions
|
|
@ -833,6 +833,17 @@ def _count_content_list(
|
|||
tool_name = str(c.get("tool_name") or "")
|
||||
if tool_name:
|
||||
num_tokens += count_function(tool_name)
|
||||
elif c["type"] == "input_audio":
|
||||
# OpenAI input_audio content block: {"type": "input_audio", "input_audio": {"data": "<b64>", "format": "wav"}}
|
||||
# Estimate tokens based on payload size (or default floor)
|
||||
input_audio = c.get("input_audio")
|
||||
b64_data = input_audio.get("data") if isinstance(input_audio, dict) else None
|
||||
if b64_data and isinstance(b64_data, str):
|
||||
decoded_bytes = len(b64_data) * 3 // 4
|
||||
# 1 token ~ 32 bytes of 24kHz/16-bit mono audio (approx standard across audio models)
|
||||
num_tokens += max(decoded_bytes // 32, 50)
|
||||
else:
|
||||
num_tokens += 50
|
||||
else:
|
||||
content_type = c.get("type", type(c).__name__) if isinstance(c, dict) else type(c).__name__
|
||||
raise ValueError(
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ from litellm.litellm_core_utils.core_helpers import (
|
|||
filter_exceptions_from_params,
|
||||
filter_internal_params,
|
||||
map_finish_reason,
|
||||
process_response_headers,
|
||||
safe_deep_copy,
|
||||
)
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging
|
||||
|
|
@ -2392,6 +2393,11 @@ class AmazonConverseConfig(BaseConfig):
|
|||
if isinstance(service_tier_block, dict) and "type" in service_tier_block:
|
||||
setattr(model_response, "service_tier", service_tier_block["type"])
|
||||
|
||||
# Populate response headers into _hidden_params["additional_headers"]
|
||||
if hasattr(response, "headers") and response.headers is not None:
|
||||
raw_headers = dict(response.headers)
|
||||
model_response._hidden_params["additional_headers"] = process_response_headers(raw_headers)
|
||||
|
||||
return model_response
|
||||
|
||||
def get_error_class(self, error_message: str, status_code: int, headers: dict | httpx.Headers) -> BaseLLMException:
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ DELETE /fallback/{model} - Delete fallbacks for a specific model
|
|||
|
||||
# pyright: reportMissingImports=false
|
||||
|
||||
import copy
|
||||
import json
|
||||
from typing import TYPE_CHECKING, Final, Literal
|
||||
|
||||
|
|
@ -302,17 +303,32 @@ async def delete_fallback(
|
|||
elif fallback_type == "content_policy":
|
||||
fallback_key = "content_policy_fallbacks"
|
||||
|
||||
# Get existing fallbacks
|
||||
existing_fallbacks: Final[list[dict[str, list[str]]]] = router_settings.get(fallback_key, [])
|
||||
# Get existing fallbacks from router_settings OR fallback to in-memory router
|
||||
existing_fallbacks: list[dict[str, list[str]]] = router_settings.get(fallback_key, [])
|
||||
if not existing_fallbacks and hasattr(llm_router, fallback_key):
|
||||
in_mem = getattr(llm_router, fallback_key, [])
|
||||
if in_mem and isinstance(in_mem, list):
|
||||
existing_fallbacks = copy.deepcopy(in_mem)
|
||||
|
||||
# Find and remove the fallback configuration
|
||||
fallback_found = False
|
||||
updated_fallbacks: Final = []
|
||||
for fallback_dict in existing_fallbacks:
|
||||
if model not in fallback_dict:
|
||||
updated_fallbacks.append(fallback_dict)
|
||||
else:
|
||||
if isinstance(fallback_dict, dict):
|
||||
if model not in fallback_dict:
|
||||
updated_fallbacks.append(fallback_dict)
|
||||
else:
|
||||
fallback_found = True
|
||||
|
||||
if not fallback_found:
|
||||
# Also check if model was in in-memory router directly
|
||||
in_mem_fallbacks = getattr(llm_router, fallback_key, [])
|
||||
if any(isinstance(d, dict) and model in d for d in in_mem_fallbacks):
|
||||
fallback_found = True
|
||||
updated_fallbacks.clear()
|
||||
for d in in_mem_fallbacks:
|
||||
if isinstance(d, dict) and model not in d:
|
||||
updated_fallbacks.append(d)
|
||||
|
||||
if not fallback_found:
|
||||
raise HTTPException(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
import httpx
|
||||
import pytest
|
||||
from litellm.llms.bedrock.chat.converse_transformation import AmazonConverseConfig
|
||||
from litellm.types.utils import ModelResponse
|
||||
|
||||
def test_bedrock_converse_populates_additional_headers():
|
||||
"""
|
||||
Regression test for #38357: Bedrock Converse handler should populate
|
||||
response headers (e.g. x-amzn-RequestId) into _hidden_params['additional_headers'].
|
||||
"""
|
||||
config = AmazonConverseConfig()
|
||||
|
||||
mock_payload = {
|
||||
"output": {
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": [{"text": "Hello world"}]
|
||||
}
|
||||
},
|
||||
"stopReason": "end_turn",
|
||||
"usage": {
|
||||
"inputTokens": 10,
|
||||
"outputTokens": 5,
|
||||
"totalTokens": 15
|
||||
}
|
||||
}
|
||||
|
||||
headers = {
|
||||
"x-amzn-RequestId": "test-request-id-12345",
|
||||
"content-type": "application/json",
|
||||
"date": "Wed, 26 Aug 2026 16:00:00 GMT"
|
||||
}
|
||||
|
||||
raw_response = httpx.Response(
|
||||
status_code=200,
|
||||
json=mock_payload,
|
||||
headers=headers,
|
||||
request=httpx.Request("POST", "https://bedrock.test")
|
||||
)
|
||||
|
||||
model_response = ModelResponse()
|
||||
|
||||
res = config._transform_response(
|
||||
model="bedrock/anthropic.claude-v2",
|
||||
response=raw_response,
|
||||
model_response=model_response,
|
||||
stream=False,
|
||||
logging_obj=None,
|
||||
optional_params={},
|
||||
api_key="test-key",
|
||||
data={},
|
||||
messages=[{"role": "user", "content": "hi"}],
|
||||
encoding=None
|
||||
)
|
||||
|
||||
additional_headers = res._hidden_params.get("additional_headers", {})
|
||||
assert "llm_provider-x-amzn-requestid" in additional_headers or "x-amzn-requestid" in additional_headers
|
||||
assert additional_headers.get("llm_provider-x-amzn-requestid") == "test-request-id-12345" or additional_headers.get("x-amzn-requestid") == "test-request-id-12345"
|
||||
32
tests/test_litellm/test_input_audio_token_counter_38459.py
Normal file
32
tests/test_litellm/test_input_audio_token_counter_38459.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import unittest
|
||||
from litellm.litellm_core_utils.token_counter import _count_content_list
|
||||
|
||||
class TestInputAudioTokenCounter(unittest.TestCase):
|
||||
def test_input_audio_block_does_not_raise(self):
|
||||
"""
|
||||
Regression test for #38459:
|
||||
_count_content_list should handle input_audio blocks without raising ValueError.
|
||||
"""
|
||||
content_list = [
|
||||
{"type": "text", "text": "Hello audio"},
|
||||
{
|
||||
"type": "input_audio",
|
||||
"input_audio": {
|
||||
"data": "UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAIA+AAACABAAZGF0YQAAAAA=",
|
||||
"format": "wav"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
# Simple character counter for testing
|
||||
def mock_count_func(s):
|
||||
return len(s)
|
||||
|
||||
total_tokens = _count_content_list(
|
||||
count_function=mock_count_func,
|
||||
content_list=content_list,
|
||||
use_default_image_token_count=True,
|
||||
default_token_count=None
|
||||
)
|
||||
|
||||
self.assertGreater(total_tokens, len("Hello audio"))
|
||||
Loading…
Add table
Reference in a new issue