refactor: clean up fresh tech debt in proxy auth reads, chatgpt credential forwarding, messages drop_params reads, router context signatures and spend log reads (#46643)

* refactor: clean up fresh tech debt in proxy sub-call budget checks, model search and logger init

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor: clean up fresh tech debt in otel baggage metadata, model budget limiter and batch line items

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* Revert "refactor: clean up fresh tech debt in otel baggage metadata, model budget limiter and batch line items"

This reverts commit 68737c457c.

* refactor: clean up fresh tech debt in guardrail init, cost map fetch and stream continuation

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor: clean up fresh tech debt in kimi k3 token counting and two slop comments

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor: clean up fresh tech debt in mcp tool call auth fields, messages drop_params, exception chain and spend log noqa

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: preserve dict-compatible drop params access

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor: clean up fresh tech debt in user list sorting, opencode affinity header and aws polly comment

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor: clean up fresh tech debt in responses bridge check, chatgpt authenticator and image header adapters

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor: clean up fresh tech debt in auth 401 detection, cooldown fallback lookup and bedrock guardrail billing

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* Revert "refactor: clean up fresh tech debt in auth 401 detection, cooldown fallback lookup and bedrock guardrail billing"

This reverts commit d497e02bbf.

* refactor: clean up fresh tech debt in responses stream usage cost, bedrock count tokens comments and router return types

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor: clean up fresh tech debt in messages drop_params reads, auth zero cost comment and two return types

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor: keep getattr drop_params reads where callers pass a plain dict

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor: clean up fresh tech debt in router context signatures, search tool comment and gemini realtime init

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor: drop fresh private-alias, getattr and duplicated response id helpers

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor: bind response id helper to logging instance

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor: simplify media guard and spend log reads

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: satisfy media guard type discipline and formatting

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor: keep media guard cast rather than trade it for new suppressions

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: mateo <277851410+mateo-berri@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot] 2026-11-04 09:09:50 +00:00 • committed by GitHub
parent 48bd9b01f6
commit fd4cef22ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 110 additions and 164 deletions

View file

@ -198,7 +198,6 @@ def _load_beta_headers_config() -> dict:
if _BETA_HEADERS_CONFIG is not None:
return _BETA_HEADERS_CONFIG
# Get the URL from environment or use default
from litellm import anthropic_beta_headers_url
_BETA_HEADERS_CONFIG = get_beta_headers_config(url=anthropic_beta_headers_url)

View file

@ -67,6 +67,10 @@ from litellm.utils import (
)
_PROVIDER_ROUTING_HINTS: Final = TypeAdapter(ProviderRoutingHints)
_OPTIONAL_OBJECT_DICT_ADAPTER: Final = TypeAdapter[dict[str, object] | None](dict[str, object] | None)
_OPTIONAL_STRING_DICT_ADAPTER: Final = TypeAdapter[dict[str, str] | None](dict[str, str] | None)
_OBJECT_DICT_ADAPTER: Final = TypeAdapter[dict[str, object]](dict[str, object])
_STRING_DICT_ADAPTER: Final = TypeAdapter[dict[str, str]](dict[str, str])
_PROVIDERS_RESOLVING_THEIR_OWN_IMAGE_BASE: Final = (
LlmProviders.DASHSCOPE,
LlmProviders.QWENCLOUD,
@ -93,8 +97,8 @@ def _chatgpt_image_params(kwargs: Mapping[str, object]) -> GenericLiteLLMParams:
from litellm.responses.utils import ResponsesAPIRequestUtils
headers: Final = ResponsesAPIRequestUtils.merge_client_forwarded_headers(
TypeAdapter[dict[str, object] | None](dict[str, object] | None).validate_python(kwargs.get("extra_headers")),
TypeAdapter[dict[str, str] | None](dict[str, str] | None).validate_python(kwargs.get("headers")),
_OPTIONAL_OBJECT_DICT_ADAPTER.validate_python(kwargs.get("extra_headers")),
_OPTIONAL_STRING_DICT_ADAPTER.validate_python(kwargs.get("headers")),
)
return GenericLiteLLMParams.model_validate({**kwargs, "extra_headers": headers})
@ -167,7 +171,7 @@ async def aimage_generation(*args, **kwargs) -> ImageResponse:
custom_llm_provider=provider_hints.get("custom_llm_provider"),
api_base=provider_hints.get("api_base"),
litellm_params=(
_chatgpt_image_params(TypeAdapter(dict[str, object]).validate_python(kwargs))
_chatgpt_image_params(_OBJECT_DICT_ADAPTER.validate_python(kwargs))
if provider_hints.get("custom_llm_provider") == "chatgpt"
or (isinstance(model, str) and model.startswith("chatgpt/"))
else None
@ -299,7 +303,7 @@ def image_generation(
headers.update(extra_headers)
model_response: ImageResponse = litellm.utils.ImageResponse()
chatgpt_params: Final = (
_chatgpt_image_params(TypeAdapter(dict[str, object]).validate_python({**kwargs, "api_key": api_key}))
_chatgpt_image_params(_OBJECT_DICT_ADAPTER.validate_python({**kwargs, "api_key": api_key}))
if custom_llm_provider == "chatgpt" or (model is not None and model.startswith("chatgpt/"))
else None
)
@ -582,7 +586,7 @@ def image_generation(
_chatgpt_image_headers(
model,
chatgpt_params.model_copy(update={"api_key": dynamic_api_key or api_key}),
TypeAdapter(dict[str, str]).validate_python(headers),
_STRING_DICT_ADAPTER.validate_python(headers),
)
)
if chatgpt_params is not None

View file

@ -177,7 +177,7 @@ class _TranscriptionEventCollector:
def _billed_usage(response: TranscriptionResponse) -> Usage:
usage: Final[object] = getattr(response, "usage", None)
usage: Final = response.usage
token_usage: Final = (
TranscriptionUsageObjectTransformation.transform_transcription_usage_object(usage)
if isinstance(usage, TranscriptionUsageTokensObject)

View file

@ -13,6 +13,7 @@ from litellm.litellm_core_utils.fallback_generalizations import (
match_routing_generalization,
)
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
from litellm.llms.opencode.common_utils import is_opencode_api_base
from litellm.secret_managers.main import get_secret, get_secret_str
from ..types.router import GenericLiteLLMParams, LiteLLM_Params
@ -1008,6 +1009,4 @@ def _get_openai_compatible_provider_info(
def get_provider_affinity_header(api_base: str | None) -> str | None:
from litellm.llms.opencode.transformation import OpenCodeProviderAdapter
return OpenCodeProviderAdapter().get_provider_affinity_header(api_base)
return "x-opencode-session" if is_opencode_api_base(api_base) else None

View file

@ -320,9 +320,7 @@ def _classify_fetch_content(content: bytes, url: str, etag: str | None) -> _Fetc
return ModelCostMapReloadUnavailable(reason=f"invalid JSON from {url}: {e}")
if not isinstance(parsed, dict):
return ModelCostMapReloadUnavailable(reason=f"expected a JSON object from {url}, got {type(parsed).__name__}")
model_cost_map: Final = cast( # cast-ok: JSON object keys are strings by definition
dict[str, object], parsed
)
model_cost_map: Final = _BUNDLED_CATALOG_ADAPTER.validate_python(parsed)
return ModelCostMapReloaded(model_cost_map=model_cost_map, revision=git_blob_id(content), etag=etag)

View file

@ -12,10 +12,18 @@ _PROVIDER_HEADERS: Final[TypeAdapter[ProviderSpecificHeader | Sequence[ProviderS
class ProviderSpecificHeaderUtils:
@staticmethod
def forwarded_authorization(provider_specific_header: object, custom_llm_provider: str | None) -> str | None:
headers: Final = ProviderSpecificHeaderUtils.get_provider_specific_headers(
def validated_provider_specific_headers(
provider_specific_header: object, custom_llm_provider: str | None
) -> Mapping[str, str]:
return ProviderSpecificHeaderUtils.get_provider_specific_headers(
_PROVIDER_HEADERS.validate_python(provider_specific_header), custom_llm_provider
)
@staticmethod
def forwarded_authorization(provider_specific_header: object, custom_llm_provider: str | None) -> str | None:
headers: Final = ProviderSpecificHeaderUtils.validated_provider_specific_headers(
provider_specific_header, custom_llm_provider
)
return next((value for key, value in headers.items() if key.lower() == "authorization"), None)
@staticmethod

View file

@ -3389,21 +3389,12 @@ class Logging(LiteLLMLoggingBaseClass):
set_hidden_params(served, {**(get_hidden_params(translated) or {}), **(get_hidden_params(response) or {})})
return served
def _with_continued_response_id(
def _with_response_id(
self,
response: ModelResponse | TextCompletionResponse | ResponsesAPIResponse | InteractionsAPIResponse,
response_id: str | None,
) -> ModelResponse | TextCompletionResponse | ResponsesAPIResponse | InteractionsAPIResponse:
response_id: Final = self.model_call_details.get("continued_response_id")
if not isinstance(response_id, str) or getattr(response, "id", None) == response_id:
return response
return response.model_copy(update={"id": response_id})
def _with_client_facing_response_id(
self,
response: ModelResponse | TextCompletionResponse | ResponsesAPIResponse | InteractionsAPIResponse,
) -> ModelResponse | TextCompletionResponse | ResponsesAPIResponse | InteractionsAPIResponse:
response_id: Final = self.client_facing_response_id
if response_id is None or getattr(response, "id", None) == response_id:
if response_id is None or response.id == response_id:
return response
return response.model_copy(update={"id": response_id})
@ -3411,8 +3402,13 @@ class Logging(LiteLLMLoggingBaseClass):
self,
response: ModelResponse | TextCompletionResponse | ResponsesAPIResponse | InteractionsAPIResponse,
) -> ModelResponse | TextCompletionResponse | ResponsesAPIResponse | InteractionsAPIResponse:
return self._with_client_facing_response_id(
self._with_continued_response_id(self._with_client_facing_stream_model(response))
continued_response_id: Final = self.model_call_details.get("continued_response_id")
return self._with_response_id(
self._with_response_id(
self._with_client_facing_stream_model(response),
continued_response_id if isinstance(continued_response_id, str) else None,
),
self.client_facing_response_id,
)
def _success_handler_helper_fn(
@ -6499,7 +6495,7 @@ def _init_custom_logger_compatible_class(
logging_integration: str,
internal_usage_cache: DualCache | None,
llm_router: object,
custom_logger_init_args: dict[str, object] | None = {},
custom_logger_init_args: dict[str, object] | None = None,
raise_import_errors: bool = False,
) -> CustomLogger | None:
"""

View file

@ -2826,8 +2826,6 @@ class CustomStreamWrapper:
return None if is_model_response_stream_empty(model_response=usage_free_chunk) else usage_free_chunk
def _accumulate_streamed_delta(self, delta: object) -> None:
# Shared by the sync, async, and non-aiohttp iteration sites so answer
# text and the disqualifying-content latch stay in step across all three.
get: Final = getattr(delta, "get", None)
content: Final = get("content") if callable(get) else None
self.response_uptil_now += content if isinstance(content, str) else ""

View file

@ -243,8 +243,7 @@ async def caller_within_model_budget(
return "allowed"
model_max_budget: Final = getattr(user_api_key_auth, "model_max_budget", None)
token: Final = getattr(user_api_key_auth, "token", None)
if isinstance(model_max_budget, dict) and model_max_budget and token is not None:
if isinstance(model_max_budget, dict) and model_max_budget and user_api_key_auth.token is not None:
try:
await model_max_budget_limiter.is_key_within_model_budget(
user_api_key_dict=user_api_key_auth,
@ -300,10 +299,8 @@ async def caller_within_model_budget(
)
return "denied_for_now"
end_user_model_max_budget: Final[dict[str, object] | None] = getattr(
user_api_key_auth, "end_user_model_max_budget", None
)
end_user_id: Final[str | None] = getattr(user_api_key_auth, "end_user_id", None)
end_user_model_max_budget: Final[dict[str, object] | None] = user_api_key_auth.end_user_model_max_budget
end_user_id: Final[str | None] = user_api_key_auth.end_user_id
if isinstance(end_user_model_max_budget, dict) and end_user_model_max_budget and end_user_id is not None:
try:
await model_max_budget_limiter.is_end_user_within_model_budget(

View file

@ -178,7 +178,6 @@ class AWSPollyTextToSpeechConfig(BaseTextToSpeechConfig, BaseAWSLLM):
else:
mapped_params["output_format"] = self.DEFAULT_OUTPUT_FORMAT
# model arrives without the "aws_polly/" prefix, e.g. "standard"
engine: Final = self._extract_engine_from_model(model)
mapped_params["engine"] = engine

View file

@ -455,9 +455,7 @@ def _converse_request(
if not all(_text_only(message) for message in messages):
return None
try:
chat_tools: Final = tuple(
chat_tool for tool in tools or () for chat_tool in _as_converse_input(tool)
) # comprehension-ok: flattens each tool's translation
chat_tools: Final = tuple(chain.from_iterable(_as_converse_input(tool) for tool in tools or ()))
return _ConverseRequest.model_validate(
litellm.AmazonConverseConfig().transform_request(
model=model,

View file

@ -66,6 +66,13 @@ def get_cached_authenticator(auth_file: str) -> "Authenticator":
return Authenticator(auth_file=auth_file)
def resolve_chatgpt_authenticator(
litellm_params: "Mapping[str, object] | GenericLiteLLMParams | None", default: "Authenticator"
) -> "Authenticator":
auth_file: Final = get_chatgpt_auth_file(litellm_params)
return get_cached_authenticator(auth_file) if auth_file else default
def _token_refresh_timeout() -> httpx.Timeout:
configured: Final = get_configured_request_timeout()
seconds: Final = (

View file

@ -6,7 +6,7 @@ from litellm.llms.openai.openai import OpenAIConfig
from litellm.types.llms.openai import AllMessageValues
from litellm.types.router import GenericLiteLLMParams
from ..authenticator import Authenticator, get_cached_authenticator, get_chatgpt_auth_file
from ..authenticator import Authenticator, get_chatgpt_auth_file, resolve_chatgpt_authenticator
from ..common_utils import (
GetAccessTokenError,
chatgpt_client_api_base,
@ -30,12 +30,6 @@ class ChatGPTConfig(OpenAIConfig):
super().__init__()
self.authenticator = authenticator if authenticator is not None else Authenticator()
def _resolve_authenticator(
self, litellm_params: Mapping[str, object] | GenericLiteLLMParams | None
) -> Authenticator:
auth_file: Final = get_chatgpt_auth_file(litellm_params)
return get_cached_authenticator(auth_file) if auth_file else self.authenticator
@staticmethod
def _get_access_token_or_raise(authenticator: Authenticator, model: str, llm_provider: str) -> str:
try:
@ -63,7 +57,7 @@ class ChatGPTConfig(OpenAIConfig):
return chatgpt_client_api_base(api_base), client_credential.access_token, custom_llm_provider
if api_key:
return api_base or self.api_base_without_login(), api_key, custom_llm_provider
authenticator: Final = self._resolve_authenticator(litellm_params)
authenticator: Final = resolve_chatgpt_authenticator(litellm_params, self.authenticator)
dynamic_api_base: Final = authenticator.get_api_base()
dynamic_api_key: Final = self._get_access_token_or_raise(authenticator, model, custom_llm_provider)
return dynamic_api_base, dynamic_api_key, custom_llm_provider
@ -98,7 +92,7 @@ class ChatGPTConfig(OpenAIConfig):
),
**strip_chatgpt_client_credential_headers(headers),
)
authenticator: Final = self._resolve_authenticator(litellm_params)
authenticator: Final = resolve_chatgpt_authenticator(litellm_params, self.authenticator)
resolved_api_key: Final = (
self._get_access_token_or_raise(authenticator, model, "chatgpt")
if get_chatgpt_auth_file(litellm_params)

View file

@ -7,7 +7,7 @@ import hashlib
import json
import os
import platform
from collections.abc import Mapping, Sequence
from collections.abc import Mapping
from dataclasses import dataclass
from types import MappingProxyType
from typing import TYPE_CHECKING, Final, TypeAlias
@ -20,14 +20,10 @@ from pydantic import JsonValue, TypeAdapter
from litellm.litellm_core_utils.get_provider_specific_headers import ProviderSpecificHeaderUtils
from litellm.litellm_core_utils.provider_affinity import get_caller_litellm_session_id, get_requested_session_id
from litellm.llms.base_llm.chat.transformation import BaseLLMException
from litellm.types.utils import ProviderSpecificHeader
if TYPE_CHECKING:
from litellm.types.router import GenericLiteLLMParams
_SCOPED_HEADERS: Final[TypeAdapter[ProviderSpecificHeader | Sequence[ProviderSpecificHeader] | None]] = TypeAdapter(
ProviderSpecificHeader | Sequence[ProviderSpecificHeader] | None
)
_STRING_HEADERS: Final = TypeAdapter(Mapping[str, str])
_PARAMS: Final = TypeAdapter(Mapping[str, object])
@ -357,8 +353,8 @@ def get_chatgpt_request_credential(
normalized: Final = _normalize_litellm_params(params)
scoped: Final = normalized.get("provider_specific_header")
extra: Final = normalized.get("extra_headers")
headers: Final = ProviderSpecificHeaderUtils.get_provider_specific_headers(
provider_specific_header=_SCOPED_HEADERS.validate_python(scoped), custom_llm_provider="chatgpt"
headers: Final = ProviderSpecificHeaderUtils.validated_provider_specific_headers(
provider_specific_header=scoped, custom_llm_provider="chatgpt"
)
return get_chatgpt_client_credential(headers) or get_chatgpt_client_credential(
_STRING_HEADERS.validate_python(extra) if extra is not None else MappingProxyType({})

View file

@ -28,7 +28,7 @@ from litellm.types.llms.openai import (
from litellm.types.router import GenericLiteLLMParams
from litellm.types.utils import LlmProviders
from ..authenticator import Authenticator, get_cached_authenticator, get_chatgpt_auth_file
from ..authenticator import Authenticator, get_chatgpt_auth_file, resolve_chatgpt_authenticator
from ..common_utils import (
CHATGPT_API_BASE,
GetAccessTokenError,
@ -64,10 +64,6 @@ class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig):
def custom_llm_provider(self) -> LlmProviders:
return LlmProviders.CHATGPT
def _resolve_authenticator(self, litellm_params: GenericLiteLLMParams | None) -> Authenticator:
auth_file: Final = get_chatgpt_auth_file(litellm_params)
return get_cached_authenticator(auth_file) if auth_file else self.authenticator
def validate_environment(
self,
headers: dict[str, str],
@ -81,7 +77,7 @@ class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig):
**get_chatgpt_default_headers(client_credential.access_token, client_credential.account_id, session_id),
**strip_chatgpt_client_credential_headers(headers),
}
authenticator: Final = self._resolve_authenticator(litellm_params)
authenticator: Final = resolve_chatgpt_authenticator(litellm_params, self.authenticator)
safe_headers: Final = strip_chatgpt_client_credential_headers(headers)
configured_api_key: Final = litellm_params.api_key if litellm_params is not None else None
if configured_api_key and not get_chatgpt_auth_file(litellm_params):

View file

@ -192,7 +192,7 @@ class DatabricksAnthropicMessagesConfig(DatabricksBase, AnthropicMessagesConfig)
headers=headers,
)
self._normalize_system_role_messages( # pyright: ignore[reportUnknownMemberType] # the shared helper takes a bare dict
anthropic_messages_request, model=model, drop_params=getattr(litellm_params, "drop_params", None)
anthropic_messages_request, model=model, drop_params=litellm_params.drop_params
)
self._remove_scope_from_cache_control(anthropic_messages_request) # pyright: ignore[reportUnknownMemberType] # the shared helper takes a bare dict
return anthropic_messages_request

View file

@ -218,7 +218,7 @@ def _system_instruction_text(system_instruction: HttpxContentType | None) -> str
class GeminiRealtimeConfig(BaseRealtimeConfig):
_TOOL_CALL_ID_TO_NAME_MAX = 256 # LRU cap for call_id→name mapping
def __init__(self, drop_params: bool = False):
def __init__(self, drop_params: bool = False) -> None:
super().__init__()
self.drop_params = drop_params
self._tool_call_id_to_name: OrderedDict[str, str] = OrderedDict()

View file

@ -1,6 +0,0 @@
from litellm.llms.opencode.common_utils import is_opencode_api_base
class OpenCodeProviderAdapter:
def get_provider_affinity_header(self, api_base: str | None) -> str | None:
return "x-opencode-session" if is_opencode_api_base(api_base) else None

View file

@ -249,7 +249,7 @@ class VertexAIPartnerModelsAnthropicMessagesConfig(AnthropicMessagesConfig, Vert
)
self._normalize_system_role_messages(
anthropic_messages_request, model=model, drop_params=getattr(litellm_params, "drop_params", None)
anthropic_messages_request, model=model, drop_params=litellm_params.drop_params
)
self._remove_scope_from_cache_control(anthropic_messages_request)

View file

@ -6069,7 +6069,6 @@ def completion(
return resolve_timeout(stream_kind=StreamKind.NOT_STREAMED)
return resolve_timeout(stream_kind=StreamKind.FAKED if fakes_stream(attempt_params) else StreamKind.LIVE)
### TIMEOUT LOGIC ###
timeout = timeout_for_optional_params(optional_params)
logging.record_applied_timeout(timeout)
processed_non_default_params: Final = pre_process_non_default_params(

View file

@ -574,7 +574,6 @@ def is_model_cost_zero(model: str | list[str] | None, llm_router: RegistryReader
return False
continue
try:
# Use the router registry's group info directly for better reliability
model_group_info = llm_router.registry().group(model_name, include_hidden=True)
if model_group_info is None:

View file

@ -84,7 +84,6 @@ def fips_mode_refusal(general_settings: Mapping[str, object]) -> str | None:
def enforce_fips_mode(general_settings: Mapping[str, object]) -> None:
"""Raise FipsModeError with the fips_mode_refusal reason, if there is one."""
refusal: Final = fips_mode_refusal(general_settings)
if refusal is not None:
raise FipsModeError(refusal)

View file

@ -598,7 +598,7 @@ class SpendLogCleanup:
rows: Final = _STORAGE_ROWS.validate_python(
await tx.query_raw(trusted_sql(_STORAGE_SQL), list(_SPEND_LOG_TABLES))
)
except Exception as e: # noqa: BLE001 - a failed measurement skips the size pass, it must not fail the run
except Exception as e: # noqa: BLE001 # a failed measurement skips the size pass, it must not fail the run
verbose_proxy_logger.warning("Could not measure spend-log storage: %s: %s", type(e).__name__, e)
return None
if not rows:
@ -666,7 +666,7 @@ class SpendLogCleanup:
await tx.query_raw(trusted_sql(_LOAD_SIZE_STATE_SQL), SPEND_LOG_SIZE_CLEANUP_STATE_PARAM)
)
return SizeCleanupState.model_validate_json(rows[0].state_json) if rows else None
except Exception as e: # noqa: BLE001 - an unreadable count restarts from the catalog, it must not fail the run
except Exception as e: # noqa: BLE001 # an unreadable count restarts from the catalog, it must not fail the run
verbose_proxy_logger.warning("Could not read the spend-log size cleanup state: %s: %s", type(e).__name__, e)
return None
@ -678,7 +678,7 @@ class SpendLogCleanup:
await tx.execute_raw(
trusted_sql(_SAVE_SIZE_STATE_SQL), SPEND_LOG_SIZE_CLEANUP_STATE_PARAM, state.model_dump_json()
)
except Exception as e: # noqa: BLE001 - a lost save costs one re-seed from the catalog, it must not fail the run
except Exception as e: # noqa: BLE001 # a lost save costs one re-seed from the catalog, it must not fail the run
verbose_proxy_logger.warning("Could not save the spend-log size cleanup state: %s: %s", type(e).__name__, e)
async def _size_step_cutoff(self, prisma_client: PrismaClient, deadline: float, rows: int) -> datetime | None:

View file

@ -32,7 +32,7 @@ def initialize_guardrail(litellm_params: "LitellmParams", guardrail: "Guardrail"
additional_provider_specific_params=getattr(litellm_params, "additional_provider_specific_params", {}),
unreachable_fallback=getattr(litellm_params, "unreachable_fallback", "fail_closed"),
fail_on_error=getattr(litellm_params, "fail_on_error", True),
forward_messages_as_sent=getattr(litellm_params, "forward_messages_as_sent", False),
forward_messages_as_sent=litellm_params.forward_messages_as_sent,
extra_headers=getattr(litellm_params, "extra_headers", None),
guardrail_name=guardrail.get("guardrail_name", ""),
event_hook=litellm_params.mode,

View file

@ -504,8 +504,7 @@ class NomaGuardrail(CustomGuardrail):
messages[i]["content"] = anonymized_content
break
def _replace_llm_response_content(self, response: LLMResponse, anonymized_content: str, position: int):
"""Replace the content of the choice at ``position`` with its anonymized version."""
def _replace_llm_response_content(self, response: LLMResponse, anonymized_content: str, position: int) -> None:
if not isinstance(response, litellm.ModelResponse):
return

View file

@ -15,6 +15,10 @@ class _UserId(BaseModel):
_USER_IDS: Final = TypeAdapter(tuple[_UserId, ...])
def _csv_values(raw: str | None) -> tuple[str, ...]:
return tuple(value.strip() for value in (raw or "").split(",") if value.strip())
async def sorted_user_ids(
prisma_client: PrismaClient,
*,
@ -30,9 +34,9 @@ async def sorted_user_ids(
organization_ids: str | None,
search: str | None,
) -> Sequence[str]:
ids: Final = tuple(value.strip() for value in (user_ids or "").split(",") if value.strip())
sso_ids: Final = tuple(value.strip() for value in (sso_user_ids or "").split(",") if value.strip())
org_ids: Final = tuple(value.strip() for value in (organization_ids or "").split(",") if value.strip())
ids: Final = _csv_values(user_ids)
sso_ids: Final = _csv_values(sso_user_ids)
org_ids: Final = _csv_values(organization_ids)
expression: Final = (
"CASE WHEN u.metadata->'scim_active' = 'false'::jsonb THEN 1 ELSE 0 END"
if sort_by == "status"

View file

@ -6253,9 +6253,9 @@ class MCPServerManager:
"server_name": server_name,
"mcp_rate_limit_server_name": server.alias or server.server_name or server.name,
"user_api_key_auth": checked_auth,
"user_api_key_user_id": (getattr(checked_auth, "user_id", None) if checked_auth else None),
"user_api_key_team_id": (getattr(checked_auth, "team_id", None) if checked_auth else None),
"user_api_key_end_user_id": (getattr(checked_auth, "end_user_id", None) if checked_auth else None),
"user_api_key_user_id": checked_auth.user_id if checked_auth else None,
"user_api_key_team_id": checked_auth.team_id if checked_auth else None,
"user_api_key_end_user_id": checked_auth.end_user_id if checked_auth else None,
"user_api_key_hash": (getattr(checked_auth, "api_key_hash", None) if checked_auth else None),
"incoming_bearer_token": caller_bearer.get_secret_value() if caller_bearer is not None else None,
"headers": logging_safe_mcp_headers(raw_headers),

View file

@ -1417,13 +1417,12 @@ async def handle_bedrock_count_tokens(
if deployment is not None:
model_litellm_params: Final = deployment.get("litellm_params", {})
# Get the resolved model ID from the configuration
if "model" in model_litellm_params:
resolved_model = model_litellm_params["model"]
# Copy all litellm_params - BaseAWSLLM will handle AWS credential discovery
for key, value in model_litellm_params.items():
if key != "user_api_key_dict": # Don't overwrite user_api_key_dict
if key != "user_api_key_dict":
litellm_params[key] = value
verbose_proxy_logger.debug("Count tokens litellm_params: %s", litellm_params)

View file

@ -158,9 +158,7 @@ def _responses_turn(event: Mapping[str, object], session_model: str) -> _Complet
return _CompletedTurn(
model=ended.response.model or session_model,
response=ended.response,
usage=ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage( # pyright: ignore[reportPrivateUsage] # the one shared Responses usage converter
ended.response.usage
),
usage=ResponseAPILoggingUtils.transform_response_api_usage_to_chat_usage(ended.response.usage),
)

View file

@ -13919,7 +13919,7 @@ async def audio_transcriptions(
api_base=api_base,
version=version,
response_cost=response_cost,
model_region=getattr(user_api_key_dict, "allowed_model_region", ""),
model_region=user_api_key_dict.allowed_model_region,
call_id=response_call_id,
request_data=data,
hidden_params=hidden_params,
@ -17009,9 +17009,6 @@ async def _apply_search_filter_to_models(
team_public_model_name: Final = (m.get("model_info") or {}).get("team_public_model_name") or ""
if search_lower in team_public_model_name.lower():
return True
# Also match the underlying LiteLLM model name (e.g.
# "openrouter/deepseek/deepseek-chat"), so users can find
# deployments by typing the provider or the upstream model id.
litellm_params: Final[Mapping[str, object]] = m.get("litellm_params") or MappingProxyType({})
litellm_model: Final = litellm_params.get("model")
return isinstance(litellm_model, str) and search_lower in litellm_model.lower()

View file

@ -269,23 +269,13 @@ async def _query_raw_or_none(prisma_client: PrismaClient, sql_query: str, *args:
async def _query_spend_logs(prisma_client: PrismaClient, sql_query: str, *args: object) -> Sequence[_RowT]:
"""``_query_raw`` for raw ``LiteLLM_SpendLogs`` reads, which Postgres cancels at the statement timeout."""
return cast( # cast-ok: callers declare the row shape their SELECT list produces, as with _query_raw
"Sequence[_RowT]", await query_raw_within_statement_timeout(prisma_client, sql_query, *args)
)
async def _query_spend_logs_or_none(
prisma_client: PrismaClient, sql_query: str, *args: object
) -> Sequence[_RowT] | None:
"""``_query_spend_logs`` for the call sites that guard the result against ``None``."""
return await _query_spend_logs(prisma_client, sql_query, *args)
@dataclass(frozen=True, slots=True)
class _SpendLogsReader:
"""``read_session_page``'s raw-query handle, each read cancelled by Postgres at the statement timeout."""
prisma_client: PrismaClient
async def query_raw(self, query: str, *args: object) -> Sequence[Mapping[str, object]]:
@ -1591,12 +1581,7 @@ async def get_global_spend_report(
ORDER BY
total_cost DESC;
"""
db_response = await _query_spend_logs_or_none(
prisma_client, sql_query, start_date_obj, end_date_obj, api_key
)
if db_response is None:
return []
db_response = await _query_spend_logs(prisma_client, sql_query, start_date_obj, end_date_obj, api_key)
return db_response
elif internal_user_id is not None:
verbose_proxy_logger.debug("Getting /spend for internal_user_id: %s", internal_user_id)
@ -1636,12 +1621,9 @@ async def get_global_spend_report(
ORDER BY
total_cost DESC;
"""
db_response = await _query_spend_logs_or_none(
db_response = await _query_spend_logs(
prisma_client, sql_query, start_date_obj, end_date_obj, internal_user_id
)
if db_response is None:
return []
return db_response
elif team_id is not None and customer_id is not None:
return await get_spend_by_team_and_customer(
@ -1703,10 +1685,7 @@ async def get_global_spend_report(
group_by_day;
"""
db_response = await _query_spend_logs_or_none(prisma_client, sql_query, start_date_obj, end_date_obj)
if db_response is None:
return []
db_response = await _query_spend_logs(prisma_client, sql_query, start_date_obj, end_date_obj)
return db_response
elif group_by == "api_key":
sql_query = """
@ -1744,10 +1723,7 @@ async def get_global_spend_report(
ORDER BY
total_cost DESC;
"""
db_response = await _query_spend_logs_or_none(prisma_client, sql_query, start_date_obj, end_date_obj)
if db_response is None:
return []
db_response = await _query_spend_logs(prisma_client, sql_query, start_date_obj, end_date_obj)
return db_response
except HTTPException:
@ -1995,7 +1971,7 @@ async def get_key_spend_report(
caller_value=LiteLLMProxyRequestSetup.get_logged_api_key(user_api_key_dict),
scope_name="api_key",
)
db_response: Sequence[Mapping[str, object]] | None = await _query_spend_logs_or_none(
db_response: Sequence[Mapping[str, object]] = await _query_spend_logs(
prisma_client,
_scoped_spend_report_sql(scope_column="api_key"),
start_date_obj,
@ -2037,7 +2013,7 @@ async def get_user_spend_report(
caller_value=user_api_key_dict.user_id,
scope_name="internal_user_id",
)
db_response: Sequence[Mapping[str, object]] | None = await _query_spend_logs_or_none(
db_response: Sequence[Mapping[str, object]] = await _query_spend_logs(
prisma_client,
_scoped_spend_report_sql(scope_column="user"),
start_date_obj,
@ -2079,7 +2055,7 @@ async def get_team_spend_report(
caller_value=user_api_key_dict.team_id,
scope_name="team_id",
)
db_response: Sequence[Mapping[str, object]] | None = await _query_spend_logs_or_none(
db_response: Sequence[Mapping[str, object]] = await _query_spend_logs(
prisma_client,
_scoped_spend_report_sql(scope_column="team_id"),
start_date_obj,
@ -2121,7 +2097,7 @@ async def get_organization_spend_report(
organization_id=organization_id,
prisma_client=prisma_client,
)
db_response: Sequence[Mapping[str, object]] | None = await _query_spend_logs_or_none(
db_response: Sequence[Mapping[str, object]] = await _query_spend_logs(
prisma_client,
_ORG_SPEND_REPORT_SQL,
start_date_obj,
@ -3046,7 +3022,7 @@ async def ui_view_spend_logs(
LIMIT ${p}
) AS bounded_matches
"""
count_rows: Final[Sequence[_SpendLogsCountRow] | None] = await _query_spend_logs_or_none(
count_rows: Final[Sequence[_SpendLogsCountRow]] = await _query_spend_logs(
prisma_client, count_query, *sql_params, SPEND_LOGS_PAGINATION_COUNT_CAP + 1
)
raw_total: Final = int(count_rows[0]["total_count"]) if count_rows else 0

View file

@ -1094,7 +1094,6 @@ async def _query_raw_rows(
sql_query: str,
*args: object,
) -> Sequence[Mapping[str, object]] | None:
"""A raw ``LiteLLM_SpendLogs`` read that Postgres cancels at the statement timeout."""
return await query_raw_within_statement_timeout(prisma_client, sql_query, *args)

View file

@ -28,7 +28,7 @@ from dataclasses import dataclass, field
from datetime import date, datetime, timedelta, timezone
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from functools import partial
from functools import partial, reduce
from itertools import takewhile
from operator import attrgetter
from types import MappingProxyType
@ -1681,14 +1681,17 @@ _LOG_DB_METRICS_CALL_TYPES: Final = frozenset(("get_data", "insert_data", "updat
_EXCEPTION_CHAIN_MAX_DEPTH: Final = 16
def _extended_exception_chain(chain: tuple[BaseException, ...], iteration: int) -> tuple[BaseException, ...]:
if len(chain) != iteration + 1:
return chain
following: Final = chain[-1].__cause__ or chain[-1].__context__
if following is None or following in chain:
return chain
return (*chain, following)
def _exception_chain(exception: BaseException) -> tuple[BaseException, ...]:
chain: Final[list[BaseException]] = [exception] # mutable-ok: built once, returned frozen
for _ in range(_EXCEPTION_CHAIN_MAX_DEPTH):
following = chain[-1].__cause__ or chain[-1].__context__
if following is None or following in chain:
break
chain.append(following)
return tuple(chain)
return reduce(_extended_exception_chain, range(_EXCEPTION_CHAIN_MAX_DEPTH), (exception,))
def _timed_out_reading_from_provider(exception: Exception) -> bool:

View file

@ -759,9 +759,7 @@ def _api_base_kwarg(kwargs: Mapping[str, object]) -> str | None:
return api_base if isinstance(api_base, str) else None
def _dispatched_model_name(
model: str, custom_llm_provider: str, api_base: str | None, litellm_params: GenericLiteLLMParams | None = None
) -> str:
def _dispatched_model_name(model: str, custom_llm_provider: str) -> str:
from litellm.litellm_core_utils.get_llm_provider_logic import resolve_model_and_provider_for_metadata
provider_model, _ = resolve_model_and_provider_for_metadata(model=model, custom_llm_provider=custom_llm_provider)
@ -774,7 +772,6 @@ def _will_bridge_to_chat_completions(
use_chat_completions_api: bool,
model_info: object,
api_base: str | None,
litellm_params: GenericLiteLLMParams | None = None,
) -> bool:
"""``_bridges_to_chat_completions`` for callers running before the provider config is resolved.
@ -791,7 +788,7 @@ def _will_bridge_to_chat_completions(
return True
return _bridges_to_chat_completions(
_resolve_responses_api_provider_config(
_dispatched_model_name(normalized_model[0], custom_llm_provider, api_base, litellm_params),
_dispatched_model_name(normalized_model[0], custom_llm_provider),
custom_llm_provider,
model_info,
api_base,
@ -816,7 +813,6 @@ def provider_stores_the_conversation(model: str, custom_llm_provider: str | None
bool(kwargs.get("use_chat_completions_api")),
kwargs.get("model_info"),
api_base,
litellm_params,
)
@ -999,7 +995,6 @@ async def aresponses(
bool(kwargs.get("use_chat_completions_api")),
kwargs.get("model_info"),
_api_base_kwarg(kwargs),
GenericLiteLLMParams(**kwargs),
),
):
_stamp_injection_points_for_the_hook(kwargs, model, custom_llm_provider, input, tools)
@ -1175,7 +1170,6 @@ def _apply_prompt_management_to_responses_call(
use_chat_completions_api,
kwargs.get("model_info"),
_api_base_kwarg(kwargs),
GenericLiteLLMParams(**kwargs),
),
):
_stamp_injection_points_for_the_hook(kwargs, model, custom_llm_provider, input, tools)

View file

@ -1031,9 +1031,7 @@ class BaseResponsesAPIStreamingIterator:
)
if estimate is None:
return
usage: Final = ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage( # pyright: ignore[reportPrivateUsage] # same shared transform the success path above uses
estimate
)
usage: Final = ResponseAPILoggingUtils.transform_response_api_usage_to_chat_usage(estimate)
served_tier: Final[object] = getattr(self._created_response, "service_tier", None)
assembled: Final = ModelResponse(
model=self.model, usage=usage, service_tier=served_tier if isinstance(served_tier, str) else None
@ -1892,7 +1890,7 @@ def stamp_responses_usage_cost(
return
if isinstance(bill, bool) or not isinstance(bill, (int, float)):
return
if bill > 0 or isinstance(getattr(usage_obj, "cost", None), (int, float)):
if bill > 0 or isinstance(usage_obj.cost, (int, float)):
stamp_litellm_bill_on_usage(response_obj, usage_obj, float(bill))

View file

@ -30,7 +30,7 @@ class CustomDenseEncoder(DenseEncoder):
litellm_router_instance: ClassVar = RenamedRouterAttribute() # deprecated: the router attribute before ``ctx``
@renamed_router_param("litellm_router_instance")
def __init__(self, ctx: Optional["RoutingContext"] = None, **kwargs):
def __init__(self, ctx: Optional["RoutingContext"] = None, **kwargs) -> None:
if "ctx" in kwargs:
ctx = kwargs.pop("ctx")
@ -55,7 +55,7 @@ class LiteLLMRouterEncoder(CustomDenseEncoder, AsymmetricDenseMixin):
model_name: str,
score_threshold: float | None = None,
max_input_chars: int = 0,
):
) -> None:
"""Initialize the LiteLLMEncoder.
:param ctx: The routing context of the owning router.

View file

@ -50,7 +50,7 @@ class MaxParallelRequestsLimit:
class InitalizeCachedClient:
@staticmethod
@renamed_router_param("litellm_router_instance")
def set_max_parallel_requests_client(ctx: "RoutingContext", model: dict):
def set_max_parallel_requests_client(ctx: "RoutingContext", model: dict) -> None:
litellm_params: Final = model.get("litellm_params", {})
model_id: Final = model["model_info"]["id"]
rpm: Final = litellm_params.get("rpm", None)

View file

@ -134,7 +134,7 @@ class ModelRateLimitingCheck(CustomLogger):
this check actively enforces those limits across ALL routing strategies.
"""
def __init__(self, dual_cache: DualCache, clock: Callable[[], datetime] = get_utc_datetime):
def __init__(self, dual_cache: DualCache, clock: Callable[[], datetime] = get_utc_datetime) -> None:
self.dual_cache = dual_cache
self.clock = clock
# model_ids already warned about itpm/otpm + tpm/rpm on the same deployment,

View file

@ -52,7 +52,7 @@ class SearchAPIRouter:
@staticmethod
@renamed_router_param("router_instance")
async def update_router_search_tools(ctx: "RoutingContext", search_tools: list):
async def update_router_search_tools(ctx: "RoutingContext", search_tools: list) -> None:
"""
Update the router with search tools from the database.
@ -143,7 +143,6 @@ class SearchAPIRouter:
# Set up kwargs for the fallback system
kwargs["model"] = search_tool_name # Use model field for compatibility with fallback system
kwargs["original_generic_function"] = original_function
# Bind ctx to the helper method using partial
kwargs["original_function"] = partial(
SearchAPIRouter.async_search_with_fallbacks_helper,
litellm_routing_ctx=ctx,

View file

@ -31,7 +31,7 @@ class TextCompletionStreamWrapper:
def __aiter__(self):
return self
def convert_to_text_completion_object(self, chunk: ModelResponse | ModelResponseStream):
def convert_to_text_completion_object(self, chunk: ModelResponse | ModelResponseStream) -> TextCompletionResponse:
try:
response: Final = TextCompletionResponse()
response["id"] = chunk.get("id", None)

View file

@ -4,7 +4,7 @@ import pytest
from litellm.constants import SESSION_ID_GENERATED_METADATA_KEY
from litellm.litellm_core_utils.provider_affinity import add_provider_affinity_header
from litellm.llms.opencode.transformation import OpenCodeProviderAdapter
from litellm.litellm_core_utils.get_llm_provider_logic import get_provider_affinity_header
@pytest.mark.parametrize(
@ -22,7 +22,7 @@ def test_opencode_defaults_affinity_header_to_the_callers_session(api_base: str)
headers: Final = {"Authorization": "Bearer test-key"}
params: Final = {"metadata": {"session_id": "conversation-123"}}
assert add_provider_affinity_header(
headers, params, OpenCodeProviderAdapter().get_provider_affinity_header(api_base)
headers, params, get_provider_affinity_header(api_base)
) == {
**headers,
"x-opencode-session": "conversation-123",
@ -50,7 +50,7 @@ def test_other_destinations_do_not_receive_an_automatic_session_header(api_base:
add_provider_affinity_header(
headers,
{"litellm_session_id": "conversation-123"},
OpenCodeProviderAdapter().get_provider_affinity_header(api_base),
get_provider_affinity_header(api_base),
)
== headers
)
@ -71,7 +71,7 @@ def test_other_destinations_do_not_receive_an_automatic_session_header(api_base:
def test_opencode_does_not_invent_a_conversation_id(params: dict[str, object]) -> None:
assert (
add_provider_affinity_header(
{}, params, OpenCodeProviderAdapter().get_provider_affinity_header("https://opencode.ai/zen/go/v1")
{}, params, get_provider_affinity_header("https://opencode.ai/zen/go/v1")
)
== {}
)
@ -81,10 +81,10 @@ def test_opencode_preserves_explicit_header_and_affinity_configuration() -> None
assert add_provider_affinity_header(
{"X-OpenCode-Session": "explicit"},
{"litellm_session_id": "conversation-123"},
OpenCodeProviderAdapter().get_provider_affinity_header("https://opencode.ai/zen/go/v1"),
get_provider_affinity_header("https://opencode.ai/zen/go/v1"),
) == {"X-OpenCode-Session": "explicit"}
assert add_provider_affinity_header(
{},
{"litellm_session_id": "conversation-123", "provider_affinity_header": "x-custom-session"},
OpenCodeProviderAdapter().get_provider_affinity_header("https://opencode.ai/zen/go/v1"),
get_provider_affinity_header("https://opencode.ai/zen/go/v1"),
) == {"x-custom-session": "conversation-123"}