From 22d584a4c1ef6ce452ad5ea465e28c8882cad062 Mon Sep 17 00:00:00 2001 From: mateo Date: Wed, 7 Oct 2026 17:11:20 +0000 Subject: [PATCH] refactor(lint): allow dict and mapping annotations in LIT001 Main already retired LIT002 (#43971). This narrows LIT001 to mutable sequences and sets, so dict, Dict, DefaultDict, OrderedDict, Counter, ChainMap, defaultdict and MutableMapping annotations are allowed, nested list/set inside a mapping still trips, and the 945 mutable-ok suppressions that only covered mapping annotations are deleted (LIT013 now flags them). AGENTS.md guidance updated to match Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- AGENTS.md | 4 +- litellm/_redis.py | 2 +- litellm/caching/redis_batch.py | 6 +- .../caching/redis_cluster_node_isolation.py | 4 +- .../transformation.py | 6 +- litellm/harness/context.py | 4 +- litellm/harness/endpoint.py | 43 +++--- litellm/harness/handlers/cli_handler.py | 4 +- .../harness/handlers/deepagents_handler.py | 28 ++-- litellm/harness/handlers/tool_loop_handler.py | 6 +- litellm/harness/runtime.py | 8 +- litellm/harness/sandbox/docker.py | 8 +- litellm/harness/sandbox/snapshot.py | 14 +- litellm/harness/sync.py | 2 +- litellm/harness/types.py | 2 +- litellm/integrations/custom_guardrail.py | 14 +- litellm/integrations/langfuse/langfuse_sdk.py | 6 +- .../integrations/otel/plumbing/otlp_json.py | 2 +- .../integrations/otel/plumbing/providers.py | 14 +- litellm/integrations/otel/plumbing/routing.py | 4 +- litellm/integrations/otel/presets/signoz.py | 2 +- .../shared_prometheus_series_admissions.py | 2 +- litellm/integrations/s3_v2.py | 2 +- litellm/integrations/shadow_eval_logger.py | 6 +- .../websearch_interception/handler.py | 2 +- .../websearch_interception/tools.py | 2 +- .../interactions/background_cost_polling.py | 6 +- litellm/litellm_core_utils/core_helpers.py | 16 +-- .../litellm_core_utils/get_model_cost_map.py | 4 +- .../internal_call_metadata.py | 6 +- litellm/litellm_core_utils/litellm_logging.py | 4 +- litellm/litellm_core_utils/llm_judge.py | 2 +- litellm/litellm_core_utils/logging_worker.py | 2 +- .../prompt_templates/common_utils.py | 6 +- .../litellm_core_utils/provider_affinity.py | 4 +- .../sensitive_data_masker.py | 4 +- .../litellm_core_utils/streaming_handler.py | 4 +- litellm/litellm_core_utils/tokenizer.py | 8 +- litellm/llms/anthropic/batches/handler.py | 4 +- .../chat/guardrail_translation/handler.py | 10 +- litellm/llms/anthropic/chat/handler.py | 4 +- litellm/llms/anthropic/chat/transformation.py | 10 +- litellm/llms/anthropic/common_utils.py | 16 +-- .../anthropic/count_tokens/transformation.py | 2 +- .../llms/anthropic/files/transformation.py | 10 +- .../pass_through/messages/response_cache.py | 4 +- .../pass_through/messages/transformation.py | 16 +-- .../responses_adapters/transformation.py | 10 +- .../llms/anthropic/prompt_cache_prediction.py | 2 +- litellm/llms/azure/azure.py | 2 +- litellm/llms/azure/common_utils.py | 4 +- litellm/llms/azure/search/transformation.py | 12 +- .../llms/azure_ai/agents/transformation.py | 2 +- litellm/llms/azure_ai/chat/transformation.py | 6 +- .../azure_ai/embed/cohere_transformation.py | 2 +- .../image_generation/flux_transformation.py | 2 +- .../azure_ai/passthrough/transformation.py | 2 +- .../anthropic_messages/transformation.py | 8 +- litellm/llms/base_llm/auth/token_exchange.py | 2 +- litellm/llms/base_llm/base_model_iterator.py | 2 +- litellm/llms/base_llm/chat/transformation.py | 4 +- litellm/llms/base_llm/files/batch_records.py | 2 +- litellm/llms/base_llm/files/transformation.py | 2 +- .../guardrail_translation/base_translation.py | 2 +- .../llms/base_llm/harness/transformation.py | 2 +- litellm/llms/base_llm/harness/utils.py | 14 +- litellm/llms/base_llm/ocr/transformation.py | 4 +- .../llms/base_llm/sandbox/transformation.py | 8 +- .../llms/base_llm/search/transformation.py | 10 +- litellm/llms/bedrock/base_aws_llm.py | 8 +- .../bedrock/chat/agentcore/transformation.py | 4 +- .../chat/chat_completions/transformation.py | 60 ++++---- litellm/llms/bedrock/chat/invoke_handler.py | 4 +- .../bedrock/claude_platform/common_utils.py | 2 +- litellm/llms/bedrock/common_utils.py | 4 +- .../embed/amazon_nova_transformation.py | 6 +- .../embed/amazon_titan_g1_transformation.py | 2 +- .../amazon_titan_multimodal_transformation.py | 2 +- .../embed/amazon_titan_v2_transformation.py | 2 +- .../bedrock/embed/cohere_transformation.py | 2 +- litellm/llms/bedrock/embed/embedding.py | 4 +- .../twelvelabs_marengo_transformation.py | 4 +- litellm/llms/bedrock/files/transformation.py | 4 +- ...n_nova_canvas_image_edit_transformation.py | 2 +- .../image_edit/stability_transformation.py | 2 +- .../anthropic_claude3_transformation.py | 2 +- .../bedrock/passthrough/transformation.py | 2 +- litellm/llms/bedrock/realtime/handler.py | 2 +- .../llms/bedrock/realtime/transformation.py | 2 +- litellm/llms/bedrock/rerank/transformation.py | 2 +- .../llms/bedrock/responses/transformation.py | 36 ++--- litellm/llms/bedrock/search/transformation.py | 18 +-- .../bedrock/vector_stores/transformation.py | 2 +- .../passthrough/transformation.py | 2 +- .../claude_code/harness/transformation.py | 11 +- litellm/llms/codex/harness/transformation.py | 6 +- .../llms/cohere/embed/v1_transformation.py | 6 +- litellm/llms/custom_httpx/llm_http_handler.py | 50 +++---- .../deepagents/harness/sandbox_backend.py | 18 +-- .../llms/deepagents/harness/transformation.py | 39 +++-- .../audio_transcription/transformation.py | 20 +-- litellm/llms/edenai/chat/transformation.py | 20 +-- litellm/llms/edenai/common_utils.py | 8 +- .../llms/edenai/embedding/transformation.py | 32 ++--- .../edenai/image_generation/transformation.py | 34 ++--- .../llms/edenai/messages/transformation.py | 12 +- .../llms/edenai/responses/transformation.py | 8 +- .../edenai/text_to_speech/transformation.py | 22 +-- litellm/llms/edenai/videos/transformation.py | 22 ++- litellm/llms/fal_ai/chat/transformation.py | 36 ++--- .../flux_lora_depth_transformation.py | 12 +- .../llms/fal_ai/image_edit/transformation.py | 2 +- .../llms/fireworks_ai/chat/transformation.py | 4 +- .../fireworks_ai/completion/transformation.py | 14 +- .../fireworks_ai/responses/transformation.py | 8 +- .../audio_transcription/transformation.py | 6 +- litellm/llms/gemini/chat/transformation.py | 2 +- .../guardrail_translation/handler.py | 4 +- litellm/llms/gigachat/chat/transformation.py | 10 +- .../gigachat/passthrough/transformation.py | 4 +- .../hosted_vllm/image_edit/transformation.py | 8 +- .../llms/hosted_vllm/videos/transformation.py | 14 +- litellm/llms/meta/realtime/transformation.py | 6 +- .../llms/minimax/messages/transformation.py | 8 +- .../mistral/audio_speech/transformation.py | 6 +- .../llms/mistral/batches/transformation.py | 8 +- litellm/llms/mistral/common_utils.py | 4 +- litellm/llms/mistral/files/transformation.py | 18 +-- .../mongodb/vector_stores/transformation.py | 8 +- litellm/llms/nadir/chat/transformation.py | 6 +- litellm/llms/nimble/search/transformation.py | 12 +- .../nvidia_nim/passthrough/transformation.py | 2 +- .../rerank/ranking_transformation.py | 10 +- .../llms/ollama/completion/transformation.py | 8 +- .../chat/guardrail_translation/handler.py | 2 +- .../videos/guardrail_translation/handler.py | 6 +- .../openai_like/messages/transformation.py | 6 +- .../llms/opencode/harness/transformation.py | 22 +-- litellm/llms/sagemaker/chat/transformation.py | 8 +- litellm/llms/sail/chat/transformation.py | 14 +- litellm/llms/sail/common_utils.py | 2 +- litellm/llms/sail/responses/transformation.py | 8 +- litellm/llms/sap/chat/models.py | 12 +- litellm/llms/sap/chat/transformation.py | 4 +- .../llms/scaleway/rerank/transformation.py | 2 +- litellm/llms/snowflake/chat/transformation.py | 12 +- .../audio_transcription/transformation.py | 2 +- .../llms/together_ai/rerank/transformation.py | 2 +- .../gemini_transcribe_transformation.py | 6 +- .../realtime_transformation.py | 4 +- litellm/llms/vertex_ai/batches/handler.py | 2 +- .../vertex_and_google_ai_studio_gemini.py | 16 +-- .../vertex_ai/interactions/transformation.py | 10 +- .../text_to_speech/transformation.py | 16 +-- .../vertex_gemma_models/transformation.py | 8 +- .../embedding/transformation_contextual.py | 8 +- litellm/llms/watsonx/common_utils.py | 2 +- .../xai/audio_transcription/transformation.py | 8 +- litellm/llms/xai/batches/handler.py | 2 +- litellm/llms/xai/batches/transformation.py | 4 +- litellm/llms/xai/chat/transformation.py | 2 +- litellm/llms/xai/files/transformation.py | 20 +-- litellm/llms/xai/responses/transformation.py | 2 +- litellm/passthrough/main.py | 2 +- .../mcp_server/auth/user_api_key_auth_mcp.py | 4 +- .../proxy/_experimental/mcp_server/catalog.py | 4 +- .../_experimental/mcp_server/mcp_debug.py | 2 +- .../mcp_server/mcp_server_manager.py | 14 +- .../proxy/_experimental/mcp_server/server.py | 4 +- .../_experimental/mcp_server/tool_registry.py | 10 +- .../_experimental/mcp_server/tool_search.py | 8 +- litellm/proxy/agent_endpoints/endpoints.py | 2 +- litellm/proxy/auth/handle_jwt.py | 2 +- litellm/proxy/auth/user_api_key_auth.py | 2 +- litellm/proxy/batches_endpoints/endpoints.py | 2 +- .../litellm_executed_batches.py | 2 +- litellm/proxy/client/cli/commands/pi.py | 12 +- .../proxy/common_utils/error_body_call_id.py | 4 +- .../proxy/common_utils/http_parsing_utils.py | 2 +- .../common_utils/openai_error_payload.py | 2 +- .../proxy/common_utils/semantic_text_index.py | 4 +- .../proxy/config_resolvers/_descriptors.py | 4 +- litellm/proxy/db/db_spend_update_writer.py | 2 +- litellm/proxy/db/gateway_request_tracking.py | 4 +- litellm/proxy/db/shadow_eval_funnel.py | 2 +- .../guardrails/auto_router_compression.py | 2 +- .../guardrail_hooks/agent_365/agent_365.py | 36 ++--- .../guardrails/guardrail_hooks/alice/alice.py | 2 +- .../guardrail_hooks/azure/prompt_shield.py | 24 ++-- .../guardrail_hooks/bedrock_guardrails.py | 14 +- .../guardrail_hooks/conduct/conduct.py | 4 +- .../guardrail_hooks/headroom/headroom.py | 2 +- .../guardrail_hooks/lakera_ai_v2.py | 4 +- .../llm_shield_proxy/stream_restorers.py | 8 +- .../guardrails/guardrail_hooks/presidio.py | 2 +- .../guardrail_hooks/singulr/singulr.py | 2 +- .../guardrail_hooks/typesafe/typesafe.py | 2 +- .../proxy/guardrails/guardrail_registry.py | 2 +- .../proxy/hooks/proxy_track_cost_callback.py | 2 +- litellm/proxy/lens/agent_workspace.py | 4 +- litellm/proxy/lens/inference.py | 2 +- litellm/proxy/litellm_pre_call_utils.py | 8 +- .../auto_router_endpoints.py | 2 +- .../budget_management_endpoints.py | 2 +- .../common_daily_activity.py | 2 +- .../management_endpoints/common_utils.py | 2 +- .../config_override_endpoints.py | 10 +- .../model_insights_endpoints.py | 4 +- .../model_management_endpoints.py | 4 +- .../management_endpoints/team_endpoints.py | 4 +- .../batch_guardrails.py | 4 +- .../openai_files_endpoints/common_utils.py | 4 +- .../llm_passthrough_endpoints.py | 2 +- .../transcribe_passthrough_logging_handler.py | 2 +- .../vertex_passthrough_logging_handler.py | 4 +- .../pass_through_endpoints.py | 4 +- .../proxy/policy_engine/pipeline_executor.py | 34 ++--- .../proxy/policy_engine/response_retrieval.py | 2 +- litellm/proxy/proxy_server.py | 6 +- .../proxy/response_api_endpoints/endpoints.py | 8 +- litellm/proxy/route_llm_request.py | 2 +- .../spend_tracking/budget_reservation.py | 2 +- .../spend_management_endpoints.py | 4 +- litellm/proxy/utils.py | 36 +++-- .../streaming_iterator.py | 4 +- .../transformation.py | 2 +- litellm/responses/main.py | 2 +- .../responses/mcp/mcp_streaming_iterator.py | 2 +- litellm/responses/utils.py | 4 +- litellm/router.py | 26 ++-- .../complexity_router/complexity_router.py | 16 +-- .../router_utils/fallback_event_handlers.py | 4 +- litellm/rust_bridge/failures.py | 4 +- litellm/rust_bridge/streams.py | 4 +- litellm/types/agents.py | 20 +-- litellm/types/containers/main.py | 8 +- litellm/types/decisions.py | 2 +- litellm/types/google_genai/main.py | 8 +- litellm/types/interactions/generated.py | 16 +-- litellm/types/litellm_params.py | 4 +- litellm/types/llms/openai.py | 24 ++-- .../guardrail_hooks/hide_secrets.py | 2 +- litellm/types/rerank.py | 4 +- litellm/types/responses/main.py | 4 +- litellm/types/utils.py | 28 ++-- litellm/types/videos/main.py | 8 +- litellm/utils.py | 2 +- scripts/check_type_discipline.py | 62 ++++---- tests/e2e/mcp/oauth_chat_client.py | 4 +- tests/integration/_support/agentic_probe.py | 2 +- .../test_rag_query_vector_store_allowlist.py | 2 +- .../test_search_tool_deny_by_default.py | 2 +- .../observability/_azure_storage_support.py | 10 +- .../observability/_s3_v2_support.py | 4 +- .../test_cache_hit_guardrail_metrics.py | 2 +- .../observability/test_langtrace_delivery.py | 2 +- .../test_s3_v2_partition_granularity.py | 2 +- .../observability/test_s3_v2_upload_fanout.py | 2 +- tests/integration/security/_canary.py | 2 +- .../otel/test_otel_v2_destinations.py | 2 +- tests/unit/integrations/test_s3_v2.py | 27 +++- .../anthropic/test_anthropic_common_utils.py | 3 +- .../llms/base_llm/auth/test_token_exchange.py | 6 +- .../mcp_server/test_gateway_dcr_flow.py | 12 +- tests/unit/proxy/db/test_prisma_query_span.py | 2 +- .../test_unified_guardrail.py | 2 +- .../unit/proxy/lens/test_dataset_endpoints.py | 2 +- .../test_pass_through_endpoints.py | 136 ++++++++++++------ .../policy_engine/test_policy_matcher.py | 47 ++---- .../unit/router_utils/test_cooldown_cache.py | 2 +- tests/unit/test_check_type_discipline.py | 41 +++--- tests/unit/test_main.py | 107 +++++--------- 272 files changed, 1157 insertions(+), 1247 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a7d7256eeb4..948a46d4172 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -62,7 +62,7 @@ Never edit or commit `ruff-strict-budget.json`, `type-discipline-budget.json`, ` If you're trying to create a new function that relies on untyped stuff, instead of adding more Any's and pushing `reportAny` / `reportExplicitAny` closer to their basedpyright ceilings, just validate it in the caller with Pydantic (a model or `TypeAdapter` that returns the typed thing or raises will do) and then pass the now typed variable in -If you get an LIT001 fail, refactor the code to follow functional programming best practices rather than introducing mutable data structures. For example, build values in one shot with comprehensions or generators wrapped in `tuple()` / `MappingProxyType()` / `frozenset()` instead of seeding an empty `list`/`dict`/`set` and mutating it over time. Ideally, `# mutable-ok` is never used; reach for it only as a genuine last resort when an immutable rewrite is truly impossible, and always pair it with a real reason +If you get an LIT001 fail, refactor the code to follow functional programming best practices rather than introducing mutable sequences or sets. For example, build values in one shot with comprehensions or generators wrapped in `tuple()` / `frozenset()` instead of seeding an empty `list`/`set` and appending to it over time. Ideally, `# mutable-ok` is never used; reach for it only as a last resort when an immutable rewrite is impossible, and always pair it with a real reason. Plain `dict` is allowed: most of the Python ecosystem takes and returns dicts, and converting to `MappingProxyType` at every boundary costs more than it protects. Never deep copy a value just to hand out an immutable view; a defensive copy of a self-referential or large object is worse than the mutation it guards against Every lint or type suppression must name the exact rule inside brackets and carry a reason comment, e.g. `# pyright: ignore[reportArgumentType] # stubs lack async overload` or `# noqa: TID251 # `. `# type: ignore` is banned (LIT009): pyrightconfig.json sets `enableTypeIgnoreComments` to false, so it silently does nothing @@ -93,7 +93,7 @@ Follow these coding conventions for new/updated code (a three-line fix in a lega - Composition over inheritance - Never-nester: early returns over deep nesting - Don't throw; model failures as values (One function (e.g., raise_public) maps error union to existing public exception contracts via exhaustive match + assert_never) -- No mutation; don't reassign variables, global or local. Instead of mutable lists and dicts, prefer tuples, frozen dataclasses (with slots=True), `MappingProxyType`, etc. +- No mutation; don't reassign variables, global or local. Prefer tuples over lists and frozensets over sets. For structured records prefer frozen dataclasses (with slots=True) or `ReadOnly` TypedDicts over dicts. A `dict` is fine for a mapping with arbitrary keys, especially at a library boundary; `MappingProxyType` is optional there, and never worth a copy - Annotate every variable with `: Final` (LIT010). Unpacking and walrus targets cannot carry the annotation, so they are implicitly final. Don't rebind them. Never rebind or mutate function parameters (LIT011); `self`/`cls` attribute stores are the exception. If rebinding or in-place mutation is truly unavoidable, suppress with `# rebind-ok: ` - Qualify every TypedDict field with `ReadOnly[...]` (LIT012), which nests freely with `Required` / `NotRequired` / `Annotated` in any order. If making the key writable is truly unavoidable, suppress with `# writable-ok: ` - Comprehensions take at most one `for` clause and one `if` clause (LIT014); split stacked clauses into a helper generator, a named intermediate, or a plain loop. Suppress with `# comprehension-ok: ` only when unavoidable diff --git a/litellm/_redis.py b/litellm/_redis.py index 499d713e5e4..ff4709e0810 100644 --- a/litellm/_redis.py +++ b/litellm/_redis.py @@ -205,7 +205,7 @@ def _str_to_bool(value: str) -> bool: def _coerce_redis_kwargs_types( redis_kwargs: Mapping[str, object], client: type | tuple[type, ...] = redis.Redis, -) -> dict[str, object]: # mutable-ok: a caller mutates the returned kwargs before constructing its client +) -> dict[str, object]: """Coerces string values to the numeric/boolean type ``client``'s constructor declares for that parameter. ``client`` may be a tuple of client classes; a parameter's type is taken from the first signature that declares it, which diff --git a/litellm/caching/redis_batch.py b/litellm/caching/redis_batch.py index aa458c9c926..fb258fb9e2b 100644 --- a/litellm/caching/redis_batch.py +++ b/litellm/caching/redis_batch.py @@ -466,13 +466,13 @@ class RequestRedisBatches: ) def __init__(self, post_call_deadline: float = POST_CALL_FLUSH_DEADLINE_SECONDS) -> None: - self._batches: Final[dict[object, RedisBatch]] = {} # mutable-ok: lazily filled per backend - self._post_call: Final[dict[object, RedisBatch]] = {} # mutable-ok: lazily filled per backend + self._batches: Final[dict[object, RedisBatch]] = {} + self._post_call: Final[dict[object, RedisBatch]] = {} self.post_call_deadline: Final = post_call_deadline self._deadline: asyncio.TimerHandle | None = None self._deadline_flush: asyncio.Task[None] | None = None # Reads declared early for a consumer that runs later in the request, keyed by consumer name. - self.prefetched: Final[dict[str, object]] = {} # mutable-ok: armed pre-admission, taken at use + self.prefetched: Final[dict[str, object]] = {} def batch(self, redis_cache: RedisCache) -> RedisBatch: key: Final = _backend_key(redis_cache) diff --git a/litellm/caching/redis_cluster_node_isolation.py b/litellm/caching/redis_cluster_node_isolation.py index 0035801018b..00d6ceba20b 100644 --- a/litellm/caching/redis_cluster_node_isolation.py +++ b/litellm/caching/redis_cluster_node_isolation.py @@ -142,9 +142,7 @@ def get_litellm_async_redis_cluster_class( # noqa: C901 # supports redis-py ve self._litellm_reinit_requests = 0 self._litellm_tolerated_timeouts = 0 super().__init__(*args, **kwargs) - self._litellm_consecutive_timeouts: dict[ # mutable-ok: per-node counter updated on the command hot path - str, int - ] = {} + self._litellm_consecutive_timeouts: dict[str, int] = {} @property def _initialize(self) -> bool: diff --git a/litellm/completion_extras/litellm_responses_transformation/transformation.py b/litellm/completion_extras/litellm_responses_transformation/transformation.py index eeed2ce784f..1180d5fded9 100644 --- a/litellm/completion_extras/litellm_responses_transformation/transformation.py +++ b/litellm/completion_extras/litellm_responses_transformation/transformation.py @@ -1382,7 +1382,7 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator): super().__init__(streaming_response, sync_stream, json_mode) self._chat_completion_id: str | None = None self._served_service_tier: str | None = None - self._tool_call_index_map: dict[int, int] = {} # mutable-ok: per-stream accumulator state + self._tool_call_index_map: dict[int, int] = {} def _handle_string_chunk( self, str_line: Union[str, "BaseModel"] @@ -1403,7 +1403,7 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator): @staticmethod def _sequential_tool_call_index( - tool_call_index_map: dict[int, int] | None, # mutable-ok: per-stream state, remapped in place + tool_call_index_map: dict[int, int] | None, output_index: int, ) -> int: """Chat-completions tool_call indices must be 0-based and sequential, but @@ -1422,7 +1422,7 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator): @staticmethod def translate_responses_chunk_to_openai_stream( parsed_chunk: dict | BaseModel, - tool_call_index_map: dict[int, int] | None = None, # mutable-ok: per-stream state, remapped in place + tool_call_index_map: dict[int, int] | None = None, ) -> "ModelResponseStream": """ Translate a Responses API streaming chunk to OpenAI chat completion streaming format. diff --git a/litellm/harness/context.py b/litellm/harness/context.py index f75ee7e49f1..5071782c7ec 100644 --- a/litellm/harness/context.py +++ b/litellm/harness/context.py @@ -15,9 +15,7 @@ from litellm.harness.types import Approval, Harness, PermissionMode if TYPE_CHECKING: from litellm.harness.endpoint import ModelEndpoint -ApprovalHandler: TypeAlias = Callable[ - [Approval], bool | Awaitable[bool] # mutable-ok: Callable parameter list in a type alias, not a runtime collection -] +ApprovalHandler: TypeAlias = Callable[[Approval], bool | Awaitable[bool]] @dataclass(frozen=True) diff --git a/litellm/harness/endpoint.py b/litellm/harness/endpoint.py index 1f500fafba2..d8d533b63d4 100644 --- a/litellm/harness/endpoint.py +++ b/litellm/harness/endpoint.py @@ -303,7 +303,7 @@ def gateway_headers( for name, value in incoming.items() if name.lower() not in DROPPED_REQUEST_HEADERS and not name.lower().startswith("x-litellm-") ) - metadata_json = json.dumps(dict(metadata), default=str) if metadata else None # mutable-ok: for json.dumps + metadata_json = json.dumps(dict(metadata), default=str) if metadata else None metadata_header = (("x-litellm-spend-logs-metadata", metadata_json),) if metadata_json is not None else () added = ( ("authorization", f"Bearer {gateway.api_key}"), @@ -333,15 +333,15 @@ def error_status(exc: BaseException) -> int: return 500 -def error_body(exc: BaseException, message: str) -> dict[str, dict[str, str]]: # mutable-ok: JSONResponse body - return {"error": {"type": type(exc).__name__, "message": message}} # mutable-ok: JSONResponse body +def error_body(exc: BaseException, message: str) -> dict[str, dict[str, str]]: + return {"error": {"type": type(exc).__name__, "message": message}} def to_jsonable(obj: object) -> object: if hasattr(obj, "model_dump"): return obj.model_dump(mode="json", exclude_none=True) if isinstance(obj, Mapping): - return dict(obj) # mutable-ok: plain-dict copy so json.dumps can serialize any Mapping + return dict(obj) return obj @@ -457,9 +457,7 @@ class ModelEndpoint: return self._injected_client handler = get_async_httpx_client( llm_provider=httpxSpecialProvider.AgentHarness, - params={ # mutable-ok: get_async_httpx_client takes a dict params argument - "timeout": HARNESS_ENDPOINT_REQUEST_TIMEOUT_SECONDS - }, + params={"timeout": HARNESS_ENDPOINT_REQUEST_TIMEOUT_SECONDS}, ) return handler.client @@ -494,17 +492,12 @@ class ModelEndpoint: Route( f"{prefix}/{route}", self._handle, - methods=["POST"], # mutable-ok: Starlette Route takes a methods list + methods=["POST"], ) for prefix, route in itertools.product(ROUTE_PREFIXES, POST_ROUTES) ) - get_routes = tuple( - Route(f"{prefix}/models", self._models, methods=["GET"]) # mutable-ok: Starlette Route takes a methods list - for prefix in ROUTE_PREFIXES - ) - return deps.applications.Starlette( - routes=[*post_routes, *get_routes] # mutable-ok: Starlette takes a routes list - ) + get_routes = tuple(Route(f"{prefix}/models", self._models, methods=["GET"]) for prefix in ROUTE_PREFIXES) + return deps.applications.Starlette(routes=[*post_routes, *get_routes]) @property def _responses(self) -> _ResponsesModule: @@ -521,7 +514,7 @@ class ModelEndpoint: def _unauthorized(self) -> Response: return self._json( - {"error": {"type": "authentication_error", "message": "invalid token"}}, # mutable-ok: JSONResponse body + {"error": {"type": "authentication_error", "message": "invalid token"}}, 401, ) @@ -536,9 +529,9 @@ class ModelEndpoint: async def _models(self, request: Request) -> Response: if not self._authorized(request): return self._unauthorized() - entry = {"id": self.model, "object": "model", "created": 0, "owned_by": "litellm"} # mutable-ok: JSON body + entry = {"id": self.model, "object": "model", "created": 0, "owned_by": "litellm"} data = (entry,) if self.model else () - return self._json({"object": "list", "data": data}) # mutable-ok: JSON response body for Starlette JSONResponse + return self._json({"object": "list", "data": data}) async def _handle(self, request: Request) -> Response: if not self._authorized(request): @@ -574,7 +567,7 @@ class ModelEndpoint: if self._client is None or self.gateway is None: raise HarnessError("gateway client is not started") if self.model: - body = {**body, "model": self.model} # mutable-ok: JSON request body re-sent upstream via httpx json= + body = {**body, "model": self.model} upstream_request = self._client.build_request( "POST", f"{self.gateway.api_base}/v1/{route}", @@ -626,10 +619,8 @@ class ModelEndpoint: tokens = (0, 0) self._record(model, tokens[0], tokens[1], header_cost(upstream.headers)) - def _sdk_kwargs( - self, body: Mapping[str, object] - ) -> dict[str, object]: # mutable-ok: SDK call kwargs, mutated by _invoke_sdk then splatted - kwargs: dict[str, object] = {**body} # mutable-ok: SDK call kwargs built from the JSON body, then overridden + def _sdk_kwargs(self, body: Mapping[str, object]) -> dict[str, object]: + kwargs: dict[str, object] = {**body} if self.model: kwargs["model"] = self.model if self.api_key: @@ -641,14 +632,14 @@ class ModelEndpoint: async def _invoke_sdk( self, route: str, - kwargs: dict[str, Any], # mutable-ok: injects stream_options into the SDK kwargs + kwargs: dict[str, Any], ) -> object: if route == ROUTE_MESSAGES: return await litellm.anthropic.messages.acreate(**kwargs) if route == ROUTE_CHAT: if kwargs.get("stream"): - stream_options = kwargs.get("stream_options") or {} # mutable-ok: empty default for a JSON field - kwargs["stream_options"] = { # mutable-ok: JSON field sent to litellm.acompletion + stream_options = kwargs.get("stream_options") or {} + kwargs["stream_options"] = { "include_usage": True, **stream_options, } diff --git a/litellm/harness/handlers/cli_handler.py b/litellm/harness/handlers/cli_handler.py index d217d1e5bbb..ba7e2512a6e 100644 --- a/litellm/harness/handlers/cli_handler.py +++ b/litellm/harness/handlers/cli_handler.py @@ -73,7 +73,7 @@ def sandbox_path(private_dir: str, path: str) -> str: async def persist_dir(sandbox: Sandbox, link_path: str, cache_subpath: str) -> None: script_args: Final = ("-c", PERSIST_DIR_SCRIPT, "sh", link_path, cache_subpath) - cmd: Final = ["sh", *script_args] # mutable-ok: Sandbox.run takes list[str] + cmd: Final = ["sh", *script_args] run = await sandbox.run(cmd) if run.exit_code != 0: verbose_logger.debug( @@ -120,7 +120,7 @@ class CLIHarnessHandler(BaseHarnessHandler): if self._setup is None or self._private_dir is None: raise RuntimeError("CLIHarnessHandler.turn() called before start()") request = self.config.transform_turn_request(ctx, self._setup, self._private_dir, prompt, self._native_id) - argv: Final = list(request.argv) # mutable-ok: Sandbox.exec takes list[str] + argv: Final = list(request.argv) proc = await ctx.sandbox.exec(argv, env=request.env, cwd=request.cwd) self._proc = proc tail: Final[deque[str]] = deque(maxlen=HARNESS_STDERR_TAIL_LINES) # mutable-ok: bounded stderr ring buffer diff --git a/litellm/harness/handlers/deepagents_handler.py b/litellm/harness/handlers/deepagents_handler.py index e6ca7c161c7..17fd8db22cd 100644 --- a/litellm/harness/handlers/deepagents_handler.py +++ b/litellm/harness/handlers/deepagents_handler.py @@ -91,7 +91,7 @@ def load_deps() -> DeepAgentsDeps: ) -_SHARED_CHECKPOINTER: dict[str, Any] = {} # mutable-ok: process-wide lazy singleton slot for the in-memory checkpointer +_SHARED_CHECKPOINTER: dict[str, Any] = {} def shared_checkpointer(deps: DeepAgentsDeps) -> BaseCheckpointSaver: @@ -129,7 +129,7 @@ class DeepAgentsHandler(BaseHarnessHandler): ) self._agent = deps.create_deep_agent( model=build_chat_model(ctx, deps), - tools=list(ctx.tools), # mutable-ok: deepagents create_deep_agent(tools=) takes a list + tools=list(ctx.tools), system_prompt=ctx.instructions, middleware=self._middleware(deps, blocked), subagents=self._subagents(ctx, deps, blocked), @@ -166,39 +166,39 @@ class DeepAgentsHandler(BaseHarnessHandler): async def turn(self, ctx: SessionContext, prompt: str) -> AsyncIterator[Event]: agent, deps = self._require_agent() run_config = self._run_config(ctx, deps.backend.UsageCallback(ctx, ctx.model)) - user_message = {"role": "user", "content": prompt} # mutable-ok: LangGraph input message dict - payload: dict[str, object] | Command = {"messages": [user_message]} # mutable-ok: LangGraph input state + user_message = {"role": "user", "content": prompt} + payload: dict[str, object] | Command = {"messages": [user_message]} while True: state = TurnState() async for event in self._stream_pass(agent, payload, run_config, state): yield event if not state.interrupts: break - resume: dict[str, object] = {} # mutable-ok: Command(resume=) payload, filled per answered approval + resume: dict[str, object] = {} for interrupt in state.interrupts: decisions: list[dict[str, Any]] = [] # mutable-ok: HITL decisions collected across awaited approvals for request in approval_requests(getattr(interrupt, "value", None)): approval = Approval( tool=normalized_tool_name(str(request.get("name") or "")), - input=dict(request.get("args") or ()), # mutable-ok: Approval.input is a public dict field + input=dict(request.get("args") or ()), ) yield approval decisions.append(decision(*await approval.wait())) - resume[interrupt.id] = {"decisions": decisions} # mutable-ok: LangGraph HITL resume payload + resume[interrupt.id] = {"decisions": decisions} payload = deps.command_cls(resume=resume) await self._finish_turn(ctx, agent, run_config) async def _stream_pass( self, agent: CompiledStateGraph, - payload: dict[str, object] | Command, # mutable-ok: LangGraph astream input type + payload: dict[str, object] | Command, run_config: RunnableConfig, state: TurnState, ) -> AsyncIterator[Event]: async for part in agent.astream( payload, run_config, - stream_mode=["messages", "updates"], # mutable-ok: LangGraph stream_mode takes a list + stream_mode=["messages", "updates"], ): # A list stream_mode yields (mode, chunk) tuples; LangGraph's overloads don't say so. if not isinstance(part, tuple) or len(part) != 2: @@ -232,13 +232,13 @@ class DeepAgentsHandler(BaseHarnessHandler): "recursion_limit": recursion_limit(ctx), } if usage_callback is not None: - run_config["callbacks"] = [usage_callback] # mutable-ok: LangChain RunnableConfig.callbacks is a list + run_config["callbacks"] = [usage_callback] return run_config @staticmethod def _middleware(deps: DeepAgentsDeps, blocked: frozenset[str]) -> list[object]: # mutable-ok: deepagents API filters = (deps.backend.ToolFilterMiddleware(blocked),) if blocked else () - return list(filters) # mutable-ok: deepagents create_deep_agent(middleware=) takes a list + return list(filters) def _subagents( self, ctx: SessionContext, deps: DeepAgentsDeps, blocked: frozenset[str] @@ -249,13 +249,13 @@ class DeepAgentsHandler(BaseHarnessHandler): has_general = any( isinstance(s, Mapping) and s.get("name") == deps.subagent_defaults["name"] for s in user_subagents ) - spec = {**deps.subagent_defaults, "middleware": self._middleware(deps, blocked)} # mutable-ok: SubAgent dict + spec = {**deps.subagent_defaults, "middleware": self._middleware(deps, blocked)} general = (spec,) if blocked and not has_general else () - return [*general, *user_subagents] # mutable-ok: deepagents create_deep_agent(subagents=) takes a list + return [*general, *user_subagents] @staticmethod async def _install_skills(ctx: SessionContext) -> list[str] | None: # mutable-ok: deepagents skills= takes a list if not ctx.skills: return None await copy_skills(ctx.sandbox, ctx.skills, f"{ctx.sandbox.workdir}/{SKILLS_DIR}") - return [f"/{SKILLS_DIR}/"] # mutable-ok: deepagents create_deep_agent(skills=) takes a list + return [f"/{SKILLS_DIR}/"] diff --git a/litellm/harness/handlers/tool_loop_handler.py b/litellm/harness/handlers/tool_loop_handler.py index e3276793fcb..b3f7dce4f72 100644 --- a/litellm/harness/handlers/tool_loop_handler.py +++ b/litellm/harness/handlers/tool_loop_handler.py @@ -89,7 +89,7 @@ def _normalize_tool_call( return _FunctionToolCall(id=call.id, name=call.custom.name, arguments=call.custom.input) -def _parse_arguments(raw: str) -> tuple[dict[str, object], str | None]: # mutable-ok: event input uses a dict +def _parse_arguments(raw: str) -> tuple[dict[str, object], str | None]: text: Final = raw.strip() try: raw_decoded: object = _JSON_DECODER.raw_decode(text) @@ -195,7 +195,7 @@ async def _execute_tool(tool: FunctionTool, arguments: Mapping[str, object]) -> positional_args: Final = tuple( validated[parameter.name] for parameter in parameters if parameter.kind is inspect.Parameter.POSITIONAL_ONLY ) - keyword_args: Final[dict[str, object]] = { # mutable-ok: tool calls need keyword arguments + keyword_args: Final[dict[str, object]] = { parameter.name: validated[parameter.name] for parameter in parameters if parameter.kind is not inspect.Parameter.POSITIONAL_ONLY @@ -268,7 +268,7 @@ class ToolLoopHandler(BaseHarnessHandler): tool_specs: list[ChatCompletionToolParam] = copy.deepcopy( # mutable-ok: acompletion takes tool list list(self._tool_specs) ) - kwargs: dict[str, object] = { # mutable-ok: acompletion takes keyword arguments + kwargs: dict[str, object] = { **{key: value for key, value in self._completion_kwargs.items() if key not in {"messages", "tools"}}, "messages": messages, **({"tools": tool_specs} if tool_specs else {}), diff --git a/litellm/harness/runtime.py b/litellm/harness/runtime.py index dc24a1f0ef7..e034d2c271b 100644 --- a/litellm/harness/runtime.py +++ b/litellm/harness/runtime.py @@ -461,9 +461,7 @@ class _Turn: sandbox = self.ctx.sandbox after = await sandbox.snapshot() files = await build_file_changes(sandbox, self.before, after, self.before_contents) - seen = { # mutable-ok: dedupe set grown while merging streamed FileChange events - change.path for change in files - } + seen = {change.path for change in files} for event in self.events: if isinstance(event, FileChange) and event.path not in seen: files.append(event) @@ -496,9 +494,7 @@ class _Turn: text=self._text(), output=output, files=files, - events=list( # mutable-ok: Result.events is a public list field; copy detaches it from the accumulator - self.events - ), + events=list(self.events), usage=usage, cost=cost, stop_reason=self.stop_reason, diff --git a/litellm/harness/sandbox/docker.py b/litellm/harness/sandbox/docker.py index ac8f357200b..9f5679d3039 100644 --- a/litellm/harness/sandbox/docker.py +++ b/litellm/harness/sandbox/docker.py @@ -124,7 +124,7 @@ class DockerSandbox: env_args = tuple( arg for key, value in self.env.items() for arg in ("-e", f"{key}={value}") ) # comprehension-ok: flattens (flag, value) pairs into argv - return [ # mutable-ok: argv is returned as a list, the shape callers and tests compare against + return [ "run", "-d", "--rm", @@ -150,7 +150,7 @@ class DockerSandbox: env_args = tuple( arg for key, value in (env.items() if env else ()) for arg in ("-e", f"{key}={value}") ) # comprehension-ok: flattens (flag, value) pairs into argv - return [ # mutable-ok: argv is returned as a list, the shape callers and tests compare against + return [ "exec", "-i", "-w", @@ -257,9 +257,7 @@ class DockerSandbox: self._processes.clear() if self.container_id is not None: container_id, self.container_id = self.container_id, None - await self._docker( - ["rm", "-f", container_id] # mutable-ok: argv list, the shape _spawn records and tests assert on - ) + await self._docker(["rm", "-f", container_id]) async def __aenter__(self) -> DockerSandbox: await self.start() diff --git a/litellm/harness/sandbox/snapshot.py b/litellm/harness/sandbox/snapshot.py index 61407c6c063..8d95196bb54 100644 --- a/litellm/harness/sandbox/snapshot.py +++ b/litellm/harness/sandbox/snapshot.py @@ -60,9 +60,7 @@ def _hash_entry(root: str, dirpath: str, filename: str) -> tuple[str, str] | Non def _walk_entries(root: str) -> Iterator[tuple[str, str]]: for dirpath, dirnames, filenames in os.walk(root, followlinks=False): - dirnames[:] = [ # mutable-ok: os.walk prunes only via in-place mutation of its dirnames list - d for d in dirnames if d not in HARNESS_SNAPSHOT_SKIP_DIRS - ] + dirnames[:] = [d for d in dirnames if d not in HARNESS_SNAPSHOT_SKIP_DIRS] for filename in filenames: entry = _hash_entry(root, dirpath, filename) if entry is not None: @@ -94,9 +92,7 @@ def diff_snapshots( ) -> list[tuple[str, FileChangeKind]]: # mutable-ok: public sandbox helper; callers compare against a list """Return (path, kind) for every changed file, sorted by path.""" kinds = ((path, _change_kind(path, before, after)) for path in sorted(frozenset(before) | frozenset(after))) - return [ # mutable-ok: public sandbox helper returns a list - (path, kind) for path, kind in kinds if kind is not None - ] + return [(path, kind) for path, kind in kinds if kind is not None] def _as_text(data: bytes) -> str | None: @@ -134,7 +130,7 @@ async def capture_text_contents(sandbox: Sandbox, paths_hashes: Mapping[str, str Each kept file is <= HARNESS_MAX_DIFF_BYTES; every byte read (kept or not) counts toward HARNESS_SNAPSHOT_MAX_TOTAL_BYTES, after which capture stops. """ - captured: dict[str, bytes] = {} # mutable-ok: async accumulator (awaits per read), frozen on return + captured: dict[str, bytes] = {} total = 0 for path in sorted(paths_hashes): if total >= HARNESS_SNAPSHOT_MAX_TOTAL_BYTES: @@ -178,6 +174,4 @@ async def build_file_changes( ) -> list[FileChange]: # mutable-ok: feeds the public Result.files list """FileChange per changed path. diff is None when it cannot be built as text.""" contents: Mapping[str, bytes] = before_contents or MappingProxyType({}) - return [ # mutable-ok: feeds the public Result.files list - await _change_for(sandbox, path, kind, contents) for path, kind in diff_snapshots(before, after) - ] + return [await _change_for(sandbox, path, kind, contents) for path, kind in diff_snapshots(before, after)] diff --git a/litellm/harness/sync.py b/litellm/harness/sync.py index 18336a3d06e..974b870dccf 100644 --- a/litellm/harness/sync.py +++ b/litellm/harness/sync.py @@ -199,7 +199,7 @@ class Session: @property def results(self) -> list[Result]: # mutable-ok: public property; returns a detached copy of the session's results - return list(self._inner.results) # mutable-ok: detached copy so callers cannot mutate the session's accumulator + return list(self._inner.results) @property def session_id(self) -> str: diff --git a/litellm/harness/types.py b/litellm/harness/types.py index 6b0118f4349..2428255cac5 100644 --- a/litellm/harness/types.py +++ b/litellm/harness/types.py @@ -187,7 +187,7 @@ class State: def dumps(self) -> bytes: return json.dumps( - { # mutable-ok: JSON payload serialized immediately by json.dumps + { "harness": self.harness.value, "native_session_id": self.native_session_id, "workdir": self.workdir, diff --git a/litellm/integrations/custom_guardrail.py b/litellm/integrations/custom_guardrail.py index 67e2173ecd0..a315d88d552 100644 --- a/litellm/integrations/custom_guardrail.py +++ b/litellm/integrations/custom_guardrail.py @@ -345,7 +345,7 @@ class CustomGuardrail(CustomLogger): def inject_advisory_message( self, - data: dict[str, Any], # mutable-ok: caller's dict is mutated in place, matching mark_pre_call_hook_ran + data: dict[str, Any], message: str, ) -> bool: """ @@ -872,7 +872,7 @@ class CustomGuardrail(CustomLogger): ) target: Final = self._deployment_hook_target() - scan_request: Final[dict[str, object]] = { # mutable-ok: async_pre_call_hook writes into the dict it is handed + scan_request: Final[dict[str, object]] = { **{key: value for key, value in request_data.items() if key not in _PRE_CALL_CONTENT_KEYS}, "messages": list(messages), **({} if target is self else _unified_hook_fields(self, request_data)), @@ -944,10 +944,10 @@ class CustomGuardrail(CustomLogger): async def async_logging_hook( self, - kwargs: dict, # mutable-ok: CustomLogger.async_logging_hook contract + kwargs: dict, result: object, call_type: str, - ) -> tuple[dict, object]: # mutable-ok: CustomLogger.async_logging_hook contract + ) -> tuple[dict, object]: """logging_only: run apply_guardrail on copies of the logged request/response and record the verdict.""" from litellm.llms import get_guardrail_translation_mapping @@ -997,11 +997,11 @@ class CustomGuardrail(CustomLogger): async def _scan_logged_call( self, - kwargs: dict, # mutable-ok: CustomLogger.async_logging_hook contract + kwargs: dict, response: object | None, translation: "BaseTranslation", output_translation: "BaseTranslation", - scratch_metadata: dict, # mutable-ok: apply_guardrail records its verdict into request metadata + scratch_metadata: dict, ) -> None: optional_params: Final = kwargs.get("optional_params") or {} scratch_input: Final = copy.deepcopy(kwargs.get("messages") or kwargs.get("input")) @@ -1567,7 +1567,7 @@ def _original_inputs_for( kwargs: Mapping[str, object], request_data: Mapping[str, object], event_type: GuardrailEventHooks | None, -) -> dict | None: # mutable-ok: matches _process_response(original_inputs=) signature +) -> dict | None: """Baseline the hook's return value is compared against to decide "allow" vs "mask". Hooks may edit their argument in place and return it, so the baseline is always a deep diff --git a/litellm/integrations/langfuse/langfuse_sdk.py b/litellm/integrations/langfuse/langfuse_sdk.py index 864c0697eb3..b4c62724231 100644 --- a/litellm/integrations/langfuse/langfuse_sdk.py +++ b/litellm/integrations/langfuse/langfuse_sdk.py @@ -884,7 +884,7 @@ class _Lease: _TRACING_LOCK: Final = threading.Lock() -_TRACING: Final[dict[_TracingKey, _Lease]] = {} # mutable-ok: process-wide channel cache, guarded by _TRACING_LOCK +_TRACING: Final[dict[_TracingKey, _Lease]] = {} def acquire_langfuse_tracing( @@ -1108,9 +1108,9 @@ class LangfuseApiClient: def __init__(self, api: LangfuseAPI, *, prompt_cache_ttl_seconds: float) -> None: self.api: Final = api self.prompt_cache_ttl_seconds: Final = prompt_cache_ttl_seconds - # mutable-ok: per-client prompt cache, guarded by _lock + self._prompts: Final[dict[_PromptKey, _CachedPrompt]] = {} - # mutable-ok: keys with a refresh in flight, guarded by _lock + self._refreshing: Final[set[_PromptKey]] = set() self._lock: Final = threading.Lock() diff --git a/litellm/integrations/otel/plumbing/otlp_json.py b/litellm/integrations/otel/plumbing/otlp_json.py index bc6d7d435b0..57fb2c0a225 100644 --- a/litellm/integrations/otel/plumbing/otlp_json.py +++ b/litellm/integrations/otel/plumbing/otlp_json.py @@ -66,7 +66,7 @@ class OTLPJsonSpanExporter(OTLPSpanExporter): def __init__( self, endpoint: str | None, - headers: dict[str, str], # mutable-ok: SDK __init__ takes Dict + headers: dict[str, str], certificate_file: str | None = None, session: "requests.Session | None" = None, ) -> None: diff --git a/litellm/integrations/otel/plumbing/providers.py b/litellm/integrations/otel/plumbing/providers.py index 37a8f1951d9..60f1cf0c4ff 100644 --- a/litellm/integrations/otel/plumbing/providers.py +++ b/litellm/integrations/otel/plumbing/providers.py @@ -624,12 +624,12 @@ class TenantFanOutSpanProcessor(SpanProcessor): self._closed = False # guarded by ``_lock``: an unlocked read races the teardown it gates self._draining = False # guarded by ``_lock``: shutdown is flushing, nothing is held any more self._build: Final = processor_factory if processor_factory is not None else _destination_processor - self._processors: OrderedDict[object, SpanProcessor] = OrderedDict() # mutable-ok: bounded LRU - self._retired: OrderedDict[int, SpanProcessor] = OrderedDict() # mutable-ok: drains as exports finish - self._exporting: dict[int, int] = {} # mutable-ok: per-processor in-flight export count - self._pending: OrderedDict[int, _PendingTree] = OrderedDict() # mutable-ok: bounded, by trace id - self._open: OrderedDict[int, int] = OrderedDict() # mutable-ok: bounded, spans in flight by trace id - self._verdicts: OrderedDict[_VerdictKey, bool] = OrderedDict() # mutable-ok: bounded LRU + self._processors: OrderedDict[object, SpanProcessor] = OrderedDict() + self._retired: OrderedDict[int, SpanProcessor] = OrderedDict() + self._exporting: dict[int, int] = {} + self._pending: OrderedDict[int, _PendingTree] = OrderedDict() + self._open: OrderedDict[int, int] = OrderedDict() + self._verdicts: OrderedDict[_VerdictKey, bool] = OrderedDict() self._drain: Final = drain_pool if drain_pool is not None else _DrainPool(capacity=pending_drains) def on_start(self, span: SDKSpan, parent_context: Context | None = None) -> None: @@ -730,7 +730,7 @@ class TenantFanOutSpanProcessor(SpanProcessor): tree: Final = self._pending.pop(trace_id, None) if tree is None: return () - verdicts: dict[_VerdictKey, bool] = {} # mutable-ok: one draw per destination of this tree + verdicts: dict[_VerdictKey, bool] = {} for _, destination in tree.held: key = _verdict_key(trace_id, destination) if key not in verdicts: diff --git a/litellm/integrations/otel/plumbing/routing.py b/litellm/integrations/otel/plumbing/routing.py index d7b1cadfc92..b3b0cd16fa3 100644 --- a/litellm/integrations/otel/plumbing/routing.py +++ b/litellm/integrations/otel/plumbing/routing.py @@ -172,9 +172,9 @@ class TenantTracerCache: # updates, span counts, and retirement must be atomic. self._lock: Final = threading.Lock() self._providers: OrderedDict[_RouteKey, TracerProvider] = OrderedDict() - self._open_span_counts: dict[TracerProvider, int] = {} # mutable-ok: live refcount state + self._open_span_counts: dict[TracerProvider, int] = {} # Oldest-first so an overflow of draining providers sheds the stalest. - self._retired: OrderedDict[TracerProvider, None] = OrderedDict() # mutable-ok: draining evicted providers + self._retired: OrderedDict[TracerProvider, None] = OrderedDict() # An owned exporter is routable only when its kind actually resolves to a # header-carrying OTLP exporter. A denylist would accept a typo'd or # unavailable kind, which ``_exporter_from_spec`` falls back to a diff --git a/litellm/integrations/otel/presets/signoz.py b/litellm/integrations/otel/presets/signoz.py index 1d55f99cb52..f85ea389494 100644 --- a/litellm/integrations/otel/presets/signoz.py +++ b/litellm/integrations/otel/presets/signoz.py @@ -88,7 +88,7 @@ def signoz_dynamic_endpoint(params: StandardCallbackDynamicParams) -> str | None def signoz_dynamic_headers( params: StandardCallbackDynamicParams, -) -> dict[str, str]: # mutable-ok: DYNAMIC_HEADERS_BY_CALLBACK returns a dict +) -> dict[str, str]: key: Final = params.get("signoz_ingestion_key") if _tenant_endpoint_is_unusable(params) or not key: return {} diff --git a/litellm/integrations/prometheus_helpers/shared_prometheus_series_admissions.py b/litellm/integrations/prometheus_helpers/shared_prometheus_series_admissions.py index 1df27175445..6e8e408315b 100644 --- a/litellm/integrations/prometheus_helpers/shared_prometheus_series_admissions.py +++ b/litellm/integrations/prometheus_helpers/shared_prometheus_series_admissions.py @@ -79,7 +79,7 @@ class SharedPrometheusSeriesAdmissions: def __init__(self, directory: str) -> None: self._directory = directory - self._admissions: dict[str, _MetricAdmissions] = {} # mutable-ok: one entry per metric, added on first use + self._admissions: dict[str, _MetricAdmissions] = {} self.lock = RLock() def admit_series(self, metric_name: str, label_values: tuple[str, ...], max_series: int) -> bool: diff --git a/litellm/integrations/s3_v2.py b/litellm/integrations/s3_v2.py index 47d9b1c7b64..d2f33fded0f 100644 --- a/litellm/integrations/s3_v2.py +++ b/litellm/integrations/s3_v2.py @@ -412,7 +412,7 @@ class S3Logger(CustomBatchLogger, BaseAWSLLM): def _sign_put( self, credentials: "Credentials", url: str, json_string: str, headers: Mapping[str, str] - ) -> dict[str, str]: # mutable-ok: [LIT001] AsyncHTTPHandler.put/HTTPHandler.put only accept dict headers + ) -> dict[str, str]: """ ``RefreshableCredentials`` (IMDS roles) may refresh between the access key, secret and token reads SigV4 performs, producing a mixed-generation signature that S3 rejects with 403. diff --git a/litellm/integrations/shadow_eval_logger.py b/litellm/integrations/shadow_eval_logger.py index 76ba1d80841..2169a89e8d7 100644 --- a/litellm/integrations/shadow_eval_logger.py +++ b/litellm/integrations/shadow_eval_logger.py @@ -847,7 +847,7 @@ class ShadowEvalLogger(CustomLogger): self._inflight_shadow_tasks: int = 0 # Starts per job since the last cache fill, never decremented within a # generation; the refill absorbs written rows and resets. - self._job_starts: dict[str, int] = {} # mutable-ok: per-generation counter + self._job_starts: dict[str, int] = {} async def _active_jobs(self) -> Mapping[tuple[str, str], tuple[ActiveShadowEvalJob, ...]]: """Active jobs by (target_type, target_id), cache-first. A target holds at most @@ -1278,8 +1278,8 @@ class ShadowEvalLogger(CustomLogger): router: Final = self._router_provider() if router is None: return _CallFailure("no router configured on this pod") - shadow_metadata: Final[dict[str, object]] = ( # mutable-ok: router writes its routing decision back - sanitized_forwardable_call_metadata(parent_metadata, SHADOW_EVAL_ROUTER_CALL_ORIGIN) + shadow_metadata: Final[dict[str, object]] = sanitized_forwardable_call_metadata( + parent_metadata, SHADOW_EVAL_ROUTER_CALL_ORIGIN ) try: response: Final = await router.acompletion( diff --git a/litellm/integrations/websearch_interception/handler.py b/litellm/integrations/websearch_interception/handler.py index 15413140d9e..25d3eea355d 100644 --- a/litellm/integrations/websearch_interception/handler.py +++ b/litellm/integrations/websearch_interception/handler.py @@ -1834,7 +1834,7 @@ class WebSearchInterceptionLogger(CustomLogger): for tool_call in tool_calls: # Handle both Anthropic-style input and OpenAI-style function.arguments query = None - tool_args: dict[str, object] | None = None # mutable-ok: the tool call's own arguments dict + tool_args: dict[str, object] | None = None if "input" in tool_call and isinstance(tool_call["input"], dict): tool_args = tool_call["input"] query = tool_args.get("query") diff --git a/litellm/integrations/websearch_interception/tools.py b/litellm/integrations/websearch_interception/tools.py index 2e1ae07eb68..9e3d3fd91f3 100644 --- a/litellm/integrations/websearch_interception/tools.py +++ b/litellm/integrations/websearch_interception/tools.py @@ -17,7 +17,7 @@ _WEB_SEARCH_TOOL_DESCRIPTION: Final = ( ) -def _web_search_input_schema() -> dict[str, object]: # mutable-ok: plain-dict tool shape, as the get_* builders +def _web_search_input_schema() -> dict[str, object]: """ JSON schema for the web search tool's input, shared by every tool format. diff --git a/litellm/interactions/background_cost_polling.py b/litellm/interactions/background_cost_polling.py index 21d438d9621..a1188a1da61 100644 --- a/litellm/interactions/background_cost_polling.py +++ b/litellm/interactions/background_cost_polling.py @@ -126,9 +126,7 @@ class InMemoryBackgroundSettlementStore: registration relies on. """ - _rows: dict[str, PendingBackgroundInteraction | None] = field( # mutable-ok: the registry every settler shares - default_factory=dict - ) + _rows: dict[str, PendingBackgroundInteraction | None] = field(default_factory=dict) async def register(self, pending: PendingBackgroundInteraction) -> None: self._rows[pending.interaction_id] = pending @@ -470,7 +468,7 @@ class _ActiveBackgroundPoll: context: BackgroundInteractionPollContext -_ACTIVE_POLLS: Final[dict[str, _ActiveBackgroundPoll]] = {} # mutable-ok: asyncio needs strong refs to poll tasks +_ACTIVE_POLLS: Final[dict[str, _ActiveBackgroundPoll]] = {} def _discard_poll(interaction_id: str, task: "asyncio.Task[SettlementOutcome | None]") -> None: diff --git a/litellm/litellm_core_utils/core_helpers.py b/litellm/litellm_core_utils/core_helpers.py index e857461785c..bdd30dcaa23 100644 --- a/litellm/litellm_core_utils/core_helpers.py +++ b/litellm/litellm_core_utils/core_helpers.py @@ -579,8 +579,8 @@ def safe_deep_copy(data): def independent_snapshot( - data: dict, # mutable-ok: caller-defined request-payload shape -) -> dict: # mutable-ok: caller-defined request-payload shape + data: dict, +) -> dict: """ A copy of ``data`` whose top-level keys are deep-copied independently where possible -- always attempted, regardless of @@ -771,13 +771,13 @@ _NO_HEADERS: Final[Mapping[str, object]] = MappingProxyType({}) class _CarriesHiddenParams(Protocol): - _hidden_params: dict[str, object] # mutable-ok: the responses billed here keep hidden params in a plain dict + _hidden_params: dict[str, object] @property - def hidden_params(self) -> dict[str, object]: ... # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: ... @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: ... # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: ... def set_response_cost_in_hidden_params(response: _CarriesHiddenParams, cost: float | None) -> None: @@ -786,7 +786,7 @@ def set_response_cost_in_hidden_params(response: _CarriesHiddenParams, cost: flo return hidden_params: Final = response.hidden_params additional_headers: Final[object] = hidden_params.get("additional_headers") - merged: Final[dict[str, object]] = { # mutable-ok: assigned into the plain-dict hidden params + merged: Final[dict[str, object]] = { **(additional_headers if isinstance(additional_headers, Mapping) else _NO_HEADERS), RESPONSE_COST_HEADER: cost, } @@ -802,8 +802,8 @@ def set_provider_response_headers_in_hidden_params( ) -> None: hidden_params: Final = response.hidden_params existing_additional_headers: Final[object] = hidden_params.get("additional_headers") - raw_headers: Final[dict[str, str]] = dict(headers) # mutable-ok: stored as the plain-dict hidden param - additional_headers: Final[dict[str, object]] = { # mutable-ok: assigned into the plain-dict hidden params + raw_headers: Final[dict[str, str]] = dict(headers) + additional_headers: Final[dict[str, object]] = { **process_response_headers(raw_headers), **(existing_additional_headers if isinstance(existing_additional_headers, Mapping) else _NO_HEADERS), } diff --git a/litellm/litellm_core_utils/get_model_cost_map.py b/litellm/litellm_core_utils/get_model_cost_map.py index 3f7c8984def..4df965afd7a 100644 --- a/litellm/litellm_core_utils/get_model_cost_map.py +++ b/litellm/litellm_core_utils/get_model_cost_map.py @@ -213,7 +213,7 @@ def mark_litellm_import_complete() -> None: @dataclass(frozen=True, slots=True) class ModelCostMapReloaded: - model_cost_map: dict # mutable-ok: adopted as litellm.model_cost, whose consumer contract is a plain mutable dict + model_cost_map: dict revision: str | None = None etag: str | None = None @@ -557,7 +557,7 @@ def _finalize_loaded_model_cost_map(loaded: ModelCostMapReloaded) -> ModelCostMa def adopt_model_cost_map( - new_model_cost_map: dict, # mutable-ok: public API preserves the mutable cost-map contract + new_model_cost_map: dict, ) -> int: import litellm from litellm import utils diff --git a/litellm/litellm_core_utils/internal_call_metadata.py b/litellm/litellm_core_utils/internal_call_metadata.py index d844cbae367..88318532ea9 100644 --- a/litellm/litellm_core_utils/internal_call_metadata.py +++ b/litellm/litellm_core_utils/internal_call_metadata.py @@ -120,7 +120,7 @@ def sanitize_user_api_key_auth(auth: object) -> object: return auth -def _sanitized(parent_metadata: Mapping[str, object]) -> dict[str, object]: # mutable-ok: SDK metadata kwarg +def _sanitized(parent_metadata: Mapping[str, object]) -> dict[str, object]: return { k: sanitize_user_api_key_auth(v) if k == _USER_API_KEY_AUTH_KEY else v for k, v in parent_metadata.items() @@ -131,7 +131,7 @@ def _sanitized(parent_metadata: Mapping[str, object]) -> dict[str, object]: # m def forwarded_internal_call_metadata( parent_metadata: Mapping[str, object] | None, call_origin: InternalCallOrigin, -) -> dict[str, object]: # mutable-ok: SDK metadata kwarg +) -> dict[str, object]: """Parent metadata, minus its budget reservation, stamped with the sub-call's origin. For sub-calls made inside the parent request (classifier, embeddings), where the @@ -158,7 +158,7 @@ def effective_turn_off_message_logging(request_kwargs: Mapping[str, object] | No def sanitized_forwardable_call_metadata( parent_metadata: Mapping[str, object], call_origin: InternalCallOrigin, -) -> dict[str, object]: # mutable-ok: SDK metadata kwarg +) -> dict[str, object]: """Just the caller's identity, stamped with the sub-call's origin. For sub-calls detached from the parent request (shadow eval), which outlive it and diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 2ce05b9aa39..d935ce1fc0d 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -1107,7 +1107,7 @@ class Logging(LiteLLMLoggingBaseClass): prompt_management_logger: CustomLogger | None = None, prompt_label: str | None = None, prompt_version: int | None = None, - request_kwargs: dict[str, object] | None = None, # mutable-ok: marker stamped into live request kwargs + request_kwargs: dict[str, object] | None = None, injected_for_every_deployment: bool = False, ) -> tuple[str, list[AllMessageValues], dict]: from litellm.integrations.anthropic_cache_control_hook import AnthropicCacheControlHook @@ -1158,7 +1158,7 @@ class Logging(LiteLLMLoggingBaseClass): tools: list[dict] | None = None, prompt_label: str | None = None, prompt_version: int | None = None, - request_kwargs: dict[str, object] | None = None, # mutable-ok: marker stamped into live request kwargs + request_kwargs: dict[str, object] | None = None, injected_for_every_deployment: bool = False, ) -> tuple[str, list[AllMessageValues], dict]: from litellm.integrations.anthropic_cache_control_hook import AnthropicCacheControlHook diff --git a/litellm/litellm_core_utils/llm_judge.py b/litellm/litellm_core_utils/llm_judge.py index ed3b89dd420..6b366d5f182 100644 --- a/litellm/litellm_core_utils/llm_judge.py +++ b/litellm/litellm_core_utils/llm_judge.py @@ -27,7 +27,7 @@ def default_router_provider() -> Router | None: return llm_router -def parse_json_verdict(raw: str) -> dict[str, object]: # mutable-ok: plain parsed-JSON payload +def parse_json_verdict(raw: str) -> dict[str, object]: """Parse a judge's JSON verdict, tolerating markdown fences and surrounding prose.""" text = raw.strip() # rebind-ok: progressively narrowed to the JSON payload fenced: Final = JSON_FENCE_RE.search(text) diff --git a/litellm/litellm_core_utils/logging_worker.py b/litellm/litellm_core_utils/logging_worker.py index 03420b84c22..16cc352cda0 100644 --- a/litellm/litellm_core_utils/logging_worker.py +++ b/litellm/litellm_core_utils/logging_worker.py @@ -61,7 +61,7 @@ class LoggingWorker: self._queue: asyncio.Queue[LoggingTask] | None = None self._worker_task: asyncio.Task | None = None self._running_tasks: set[asyncio.Task] = set() - self._dequeued_tasks: dict[int, LoggingTask] = {} # mutable-ok: refs so flush can rescue never-started tasks + self._dequeued_tasks: dict[int, LoggingTask] = {} self._sem: asyncio.Semaphore | None = None self._bound_loop: asyncio.AbstractEventLoop | None = None self._last_aggressive_clear_time: float = 0.0 diff --git a/litellm/litellm_core_utils/prompt_templates/common_utils.py b/litellm/litellm_core_utils/prompt_templates/common_utils.py index 49c4198c939..becbc5fb1c5 100644 --- a/litellm/litellm_core_utils/prompt_templates/common_utils.py +++ b/litellm/litellm_core_utils/prompt_templates/common_utils.py @@ -1212,7 +1212,7 @@ def _mergeable_branch( branch: object, seen_refs: frozenset[str], depth: int, - expanded_refs: dict[str, Mapping[str, object] | None], # mutable-ok: per-call memo bounding repeated $ref work + expanded_refs: dict[str, Mapping[str, object] | None], ) -> Mapping[str, object] | None: if not isinstance(branch, dict) or depth > _MAX_SCHEMA_FLATTEN_DEPTH: return None @@ -1245,7 +1245,7 @@ def _flatten_schema_against_root( root: Mapping[str, object], seen_refs: frozenset[str], depth: int, - expanded_refs: dict[str, Mapping[str, object] | None], # mutable-ok: per-call memo bounding repeated $ref work + expanded_refs: dict[str, Mapping[str, object] | None], ) -> Mapping[str, object]: raw_branch_groups: Final = tuple( ( @@ -1377,7 +1377,7 @@ def drop_lookaround_regex_patterns(schema: Mapping[str, object]) -> Mapping[str, def _schema_without_rejected_regex( schema: Mapping[str, object], rejected: Callable[[str], bool] ) -> Mapping[str, object]: - rebuilt: dict[int, Mapping[str, object]] = {} # mutable-ok: per-call memo of rewritten nodes, deepest level first + rebuilt: dict[int, Mapping[str, object]] = {} for level in reversed(tuple(islice(_schema_levels(schema), _MAX_SCHEMA_NESTING))): rebuilt.update( (id(node), rewritten) diff --git a/litellm/litellm_core_utils/provider_affinity.py b/litellm/litellm_core_utils/provider_affinity.py index 02c4cd69159..1bb8e6337a0 100644 --- a/litellm/litellm_core_utils/provider_affinity.py +++ b/litellm/litellm_core_utils/provider_affinity.py @@ -83,9 +83,7 @@ def get_stable_session_id(litellm_params: object | None) -> str | None: return None -def add_provider_affinity_header( - headers: Mapping[str, object], litellm_params: object | None -) -> dict[str, object]: # mutable-ok: downstream handlers add auth and signing headers +def add_provider_affinity_header(headers: Mapping[str, object], litellm_params: object | None) -> dict[str, object]: header_name: Final = _get_provider_affinity_header_name(litellm_params) if header_name is None or any(key.lower() == header_name.lower() for key in headers): return dict(headers) diff --git a/litellm/litellm_core_utils/sensitive_data_masker.py b/litellm/litellm_core_utils/sensitive_data_masker.py index ef18cda0c6c..6f0483609dd 100644 --- a/litellm/litellm_core_utils/sensitive_data_masker.py +++ b/litellm/litellm_core_utils/sensitive_data_masker.py @@ -194,9 +194,7 @@ def mask_credentials_in_payload(data: object) -> object: @dataclass(frozen=True, slots=True) class _PayloadWalker: - _memo: dict[tuple[int, bool], tuple[object, object]] = field( # mutable-ok: memo of one walk, pins each keyed node - default_factory=dict - ) + _memo: dict[tuple[int, bool], tuple[object, object]] = field(default_factory=dict) def walk(self, node: object, key_is_sensitive: bool, depth: int) -> object: if not isinstance(node, (Mapping, list, tuple, BaseModel)): diff --git a/litellm/litellm_core_utils/streaming_handler.py b/litellm/litellm_core_utils/streaming_handler.py index 43611eb3192..5247f558eab 100644 --- a/litellm/litellm_core_utils/streaming_handler.py +++ b/litellm/litellm_core_utils/streaming_handler.py @@ -208,11 +208,11 @@ def _provider_hidden_params( class CustomStreamWrapper: @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: self._hidden_params = hidden_params def __init__( diff --git a/litellm/litellm_core_utils/tokenizer.py b/litellm/litellm_core_utils/tokenizer.py index 31cd8f63116..979b690793b 100644 --- a/litellm/litellm_core_utils/tokenizer.py +++ b/litellm/litellm_core_utils/tokenizer.py @@ -263,13 +263,13 @@ class HuggingFaceTokenizer: def id_to_token(self, id: int) -> str | None: return self._native.id_to_token(id) - def get_vocab(self, with_added_tokens: bool = True) -> dict[str, int]: # mutable-ok: [LIT001] SDK return type + def get_vocab(self, with_added_tokens: bool = True) -> dict[str, int]: return self._native.get_vocab(with_added_tokens) def get_vocab_size(self, with_added_tokens: bool = True) -> int: return self._native.get_vocab_size(with_added_tokens) - def get_added_tokens_decoder(self) -> dict[int, AddedToken]: # mutable-ok: [LIT001] SDK return type + def get_added_tokens_decoder(self) -> dict[int, AddedToken]: return { token_id: AddedToken( content, single_word=single_word, lstrip=lstrip, rstrip=rstrip, normalized=normalized, special=special @@ -288,11 +288,11 @@ class HuggingFaceTokenizer: return self._native.num_special_tokens_to_add(is_pair) @property - def padding(self) -> dict[str, object] | None: # mutable-ok: [LIT001] SDK return type + def padding(self) -> dict[str, object] | None: return self._native.padding() @property - def truncation(self) -> dict[str, object] | None: # mutable-ok: [LIT001] SDK return type + def truncation(self) -> dict[str, object] | None: return self._native.truncation() @property diff --git a/litellm/llms/anthropic/batches/handler.py b/litellm/llms/anthropic/batches/handler.py index 3f3716e0de6..4aec37ca190 100644 --- a/litellm/llms/anthropic/batches/handler.py +++ b/litellm/llms/anthropic/batches/handler.py @@ -42,7 +42,7 @@ class AnthropicBatchesHandler: timeout: float | httpx.Timeout, max_retries: int | None, logging_obj: LiteLLMLoggingObj | None = None, - litellm_params: dict | None = None, # mutable-ok: handed straight to validate_environment + litellm_params: dict | None = None, ) -> LiteLLMBatch: """ Async: Retrieve a batch from Anthropic. @@ -131,7 +131,7 @@ class AnthropicBatchesHandler: timeout: float | httpx.Timeout, max_retries: int | None, logging_obj: LiteLLMLoggingObj | None = None, - litellm_params: dict | None = None, # mutable-ok: handed straight to validate_environment + litellm_params: dict | None = None, ) -> LiteLLMBatch | Coroutine[Any, Any, LiteLLMBatch]: """ Retrieve a batch from Anthropic. diff --git a/litellm/llms/anthropic/chat/guardrail_translation/handler.py b/litellm/llms/anthropic/chat/guardrail_translation/handler.py index 405ebce548a..6d26dd31b5e 100644 --- a/litellm/llms/anthropic/chat/guardrail_translation/handler.py +++ b/litellm/llms/anthropic/chat/guardrail_translation/handler.py @@ -719,7 +719,7 @@ class AnthropicMessagesHandler(BaseTranslation): @staticmethod def _openai_system_message_to_anthropic( message: Mapping[str, object], - ) -> dict[str, object] | None: # mutable-ok: API message payload + ) -> dict[str, object] | None: """Convert an OpenAI system message to the client's Anthropic-shaped entry.""" content: Final = message.get("content") if isinstance(content, str): @@ -733,7 +733,7 @@ class AnthropicMessagesHandler(BaseTranslation): text = block.get("text") if not isinstance(text, str) or not text: continue - anthropic_block: dict[str, object] = { # mutable-ok: API message payload + anthropic_block: dict[str, object] = { "type": "text", "text": text, } @@ -745,7 +745,7 @@ class AnthropicMessagesHandler(BaseTranslation): @staticmethod def _fold_leading_systems_into_top_level( - data: dict[str, object], # mutable-ok: API message payload + data: dict[str, object], leading_systems: Sequence[object], include_existing_system: bool, ) -> None: @@ -826,7 +826,7 @@ class AnthropicMessagesHandler(BaseTranslation): @staticmethod def _write_back_structured_messages( - data: dict, # mutable-ok: API message payload + data: dict, structured_messages: list, # mutable-ok: API message payload hoisted_system_message: object = None, preserve_system_messages: bool = False, @@ -1070,7 +1070,7 @@ class AnthropicMessagesHandler(BaseTranslation): async def _apply_guardrail_responses_to_input( self, - data: dict[str, object], # mutable-ok: API message payload + data: dict[str, object], responses: Sequence[str], scanned: tuple[ScannedText, ...], ) -> None: diff --git a/litellm/llms/anthropic/chat/handler.py b/litellm/llms/anthropic/chat/handler.py index a110cc5b15d..c183ff1313d 100644 --- a/litellm/llms/anthropic/chat/handler.py +++ b/litellm/llms/anthropic/chat/handler.py @@ -369,7 +369,7 @@ class AnthropicChatCompletion(BaseLLM): transform_params: Final = {**optional_params, "is_vertex_request": is_vertex_request} - def finish_request(request_data: dict) -> tuple[dict, dict]: # mutable-ok: rewritten in place downstream + def finish_request(request_data: dict) -> tuple[dict, dict]: """Filter beta headers and emit pre_call, returning `(headers, data)`. The pair stays mutable because the streaming path rewrites it in @@ -586,7 +586,7 @@ class ModelResponseIterator: # Accumulate web_search_tool_result blocks for multi-turn reconstruction # See: https://github.com/BerriAI/litellm/issues/17737 self.web_search_results: list[dict[str, object]] = [] - self._web_search_calls: dict[str, object] = {} # mutable-ok: provider call state by id + self._web_search_calls: dict[str, object] = {} # Accumulate compaction blocks for multi-turn reconstruction self.compaction_blocks: list[dict[str, object]] = [] diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 54a8780ca5b..7c1d4d137d3 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -493,7 +493,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): def maybe_drop_speed_param( cls, model: str, - optional_params: dict[str, object], # mutable-ok: mirrors override contract + optional_params: dict[str, object], drop_params: bool, custom_llm_provider: str | None = None, ) -> None: @@ -1949,10 +1949,10 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): self, model: str, messages: list[AllMessageValues], # mutable-ok: BaseConfig signature - optional_params: dict[str, object], # mutable-ok: BaseConfig signature - litellm_params: dict[str, object], # mutable-ok: BaseConfig signature - headers: dict[str, object], # mutable-ok: BaseConfig signature - ) -> dict[str, object]: # mutable-ok: BaseConfig signature + optional_params: dict[str, object], + litellm_params: dict[str, object], + headers: dict[str, object], + ) -> dict[str, object]: return self.transform_request( model=model, messages=await async_inline_remote_media(messages, should_inline=self.inlines_remote_media), diff --git a/litellm/llms/anthropic/common_utils.py b/litellm/llms/anthropic/common_utils.py index 16de0e119c2..e870bf179f4 100644 --- a/litellm/llms/anthropic/common_utils.py +++ b/litellm/llms/anthropic/common_utils.py @@ -667,7 +667,7 @@ class AnthropicModelInfo(BaseLLMModelInfo): @classmethod def apply_sampling_param( cls, - optional_params: dict[str, object], # mutable-ok: mirrors override contract + optional_params: dict[str, object], model: str, param: str, value: object, @@ -892,7 +892,7 @@ class AnthropicModelInfo(BaseLLMModelInfo): @staticmethod def maybe_drop_disabled_thinking( model: str, - optional_params: MutableMapping[str, object], # mutable-ok: in-place out-param, as in _maybe_drop_speed_param + optional_params: MutableMapping[str, object], custom_llm_provider: str, ) -> None: """Omit ``thinking={'type': 'disabled'}`` for always-on-thinking models @@ -912,7 +912,7 @@ class AnthropicModelInfo(BaseLLMModelInfo): @staticmethod def translate_legacy_thinking_for_adaptive_model( model: str, - optional_params: MutableMapping[str, object], # mutable-ok: in-place out-param like the sibling helpers + optional_params: MutableMapping[str, object], custom_llm_provider: str, ) -> None: """Translate legacy ``thinking.type=enabled`` to adaptive for the @@ -1634,7 +1634,7 @@ def strip_thinking_blocks_from_anthropic_messages(messages: Sequence[object]) -> return out -def _without_encrypted_reasoning_blocks(message: dict) -> dict | None: # mutable-ok: Anthropic message payload shape +def _without_encrypted_reasoning_blocks(message: dict) -> dict | None: if not isinstance(message, Mapping): return message content: Final = message.get("content") @@ -1649,7 +1649,7 @@ def _without_encrypted_reasoning_blocks(message: dict) -> dict | None: # mutabl def strip_encrypted_reasoning_blocks_from_anthropic_messages( - messages: Sequence[dict], # mutable-ok: Anthropic message payload shape + messages: Sequence[dict], ) -> list[dict]: # mutable-ok: AnthropicMessagesRequest.messages is typed list[dict] """ Drop thinking / redacted_thinking blocks that carry another provider's encrypted @@ -1999,14 +1999,14 @@ def strip_provider_specific_fields_from_anthropic_messages( return [_strip_provider_specific_fields_in_message(m) for m in messages] -def _normalized_cache_control(cache_control: object) -> dict[str, str] | None: # mutable-ok: JSON wire format +def _normalized_cache_control(cache_control: object) -> dict[str, str] | None: if not isinstance(cache_control, Mapping): return None cache_type: Final = cache_control.get("type") return {"type": cache_type if isinstance(cache_type, str) else "ephemeral"} -def _with_portable_cache_control(block: Mapping[str, object]) -> dict[str, object]: # mutable-ok: JSON wire format +def _with_portable_cache_control(block: Mapping[str, object]) -> dict[str, object]: if "cache_control" not in block: return dict(block) normalized: Final = _normalized_cache_control(block["cache_control"]) @@ -2062,7 +2062,7 @@ def _with_portable_cache_control_in_scoped_value(key: str, value: object) -> obj def normalize_cache_control_in_anthropic_payload( payload: Mapping[str, object], -) -> dict[str, object]: # mutable-ok: JSON wire format +) -> dict[str, object]: """ Return a copy of an Anthropic /v1/messages payload with every ``cache_control`` entry reduced to ``{"type": }`` diff --git a/litellm/llms/anthropic/count_tokens/transformation.py b/litellm/llms/anthropic/count_tokens/transformation.py index 1e97d56913a..cb32057b120 100644 --- a/litellm/llms/anthropic/count_tokens/transformation.py +++ b/litellm/llms/anthropic/count_tokens/transformation.py @@ -51,7 +51,7 @@ class AnthropicCountTokensConfig: tools: list[dict[str, JsonValue]] | None = None, system: JsonValue = None, optional_params: Mapping[str, JsonValue] | None = None, - ) -> dict[str, JsonValue]: # mutable-ok: provider transport requires JSON dictionaries + ) -> dict[str, JsonValue]: """ Transform request to Anthropic CountTokens format. diff --git a/litellm/llms/anthropic/files/transformation.py b/litellm/llms/anthropic/files/transformation.py index 082902e8897..b02ba9b84f5 100644 --- a/litellm/llms/anthropic/files/transformation.py +++ b/litellm/llms/anthropic/files/transformation.py @@ -111,14 +111,14 @@ class AnthropicFilesConfig(BaseFilesConfig): async def avalidate_environment( self, - headers: dict, # mutable-ok: mirrors the sync validate_environment contract this overrides + headers: dict, model: str, messages: list, # mutable-ok: mirrors the sync validate_environment contract this overrides - optional_params: dict, # mutable-ok: mirrors the sync validate_environment contract this overrides - litellm_params: dict, # mutable-ok: mirrors the sync validate_environment contract this overrides + optional_params: dict, + litellm_params: dict, api_key: str | None = None, api_base: str | None = None, - ) -> dict: # mutable-ok: mirrors the sync validate_environment contract this overrides + ) -> dict: """Async counterpart of validate_environment: the WIF tier can block on a token exchange POST, so async callers await it off the event loop.""" params_mapping, resolved_api_base = self._resolve_params(litellm_params, api_base) @@ -136,7 +136,7 @@ class AnthropicFilesConfig(BaseFilesConfig): return params_mapping, resolved_api_base @staticmethod - def _finalize_headers(headers: dict, auth_header: Mapping[str, str] | None) -> dict: # mutable-ok: out-param + def _finalize_headers(headers: dict, auth_header: Mapping[str, str] | None) -> dict: if auth_header is None: raise ValueError( "Anthropic API key is required. Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN environment variable or pass api_key parameter." diff --git a/litellm/llms/anthropic/pass_through/messages/response_cache.py b/litellm/llms/anthropic/pass_through/messages/response_cache.py index 4c5f47bc650..03042fececa 100644 --- a/litellm/llms/anthropic/pass_through/messages/response_cache.py +++ b/litellm/llms/anthropic/pass_through/messages/response_cache.py @@ -44,7 +44,7 @@ class AnthropicMessagesStreamCacheWriter: self.caching_handler = caching_handler self.collected_chunks: list[bytes] = [] # mutable-ok: rebuilding a tuple per SSE chunk is quadratic self.persisted = False - self._hidden_params: dict[str, object] = dict( # mutable-ok: callers stamp cache_key in here + self._hidden_params: dict[str, object] = dict( stream.hidden_params if isinstance(stream, AnthropicMessagesStreamingResponse) else _EMPTY_MAPPING ) @@ -139,7 +139,7 @@ class CachedAnthropicMessagesStreamIterator(BaseAnthropicMessagesStreamingIterat self.chunks: Final[tuple[bytes, ...]] = tuple(event.encode("utf-8") for event in events) self.current_index = 0 self.logged = False - self._hidden_params: dict[str, object] = {"cache_hit": True} # mutable-ok: callers stamp cache_key in here + self._hidden_params: dict[str, object] = {"cache_hit": True} litellm_logging_obj.model_call_details["cache_hit"] = True def __aiter__(self) -> "CachedAnthropicMessagesStreamIterator": diff --git a/litellm/llms/anthropic/pass_through/messages/transformation.py b/litellm/llms/anthropic/pass_through/messages/transformation.py index f1eb30b63c1..6baedea37dd 100644 --- a/litellm/llms/anthropic/pass_through/messages/transformation.py +++ b/litellm/llms/anthropic/pass_through/messages/transformation.py @@ -290,14 +290,14 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig): async def avalidate_anthropic_messages_environment( self, - headers: dict, # mutable-ok: mirrors the sync validate_anthropic_messages_environment contract + headers: dict, model: str, messages: list[Any], # mutable-ok: mirrors the sync validate_anthropic_messages_environment contract - optional_params: dict, # mutable-ok: mirrors the sync validate_anthropic_messages_environment contract - litellm_params: dict, # mutable-ok: mirrors the sync validate_anthropic_messages_environment contract + optional_params: dict, + litellm_params: dict, api_key: str | None = None, api_base: str | None = None, - ) -> tuple[dict, str | None]: # mutable-ok: mirrors the sync validate_anthropic_messages_environment contract + ) -> tuple[dict, str | None]: if type(self).validate_anthropic_messages_environment is not ( AnthropicMessagesConfig.validate_anthropic_messages_environment ): @@ -342,7 +342,7 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig): return auth_header @staticmethod - def _apply_env_auth_header(headers: dict, auth_header: Mapping[str, str] | None) -> None: # mutable-ok: out-param + def _apply_env_auth_header(headers: dict, auth_header: Mapping[str, str] | None) -> None: if auth_header is None: return merged_beta: Final = merge_anthropic_beta_headers( @@ -362,10 +362,10 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig): def _finalize_messages_headers( self, - headers: dict, # mutable-ok: out-param - optional_params: dict, # mutable-ok: out-param + headers: dict, + optional_params: dict, messages: list[object], # mutable-ok: mirrors the validate_anthropic_messages_environment contract - ) -> dict: # mutable-ok: out-param + ) -> dict: if "anthropic-version" not in headers: headers["anthropic-version"] = DEFAULT_ANTHROPIC_API_VERSION if "content-type" not in headers: diff --git a/litellm/llms/anthropic/pass_through/responses_adapters/transformation.py b/litellm/llms/anthropic/pass_through/responses_adapters/transformation.py index 642f1857058..1eef1eed39e 100644 --- a/litellm/llms/anthropic/pass_through/responses_adapters/transformation.py +++ b/litellm/llms/anthropic/pass_through/responses_adapters/transformation.py @@ -98,7 +98,7 @@ class LiteLLMAnthropicToResponsesAPIAdapter: @staticmethod def _translate_anthropic_document_block_to_file_part( block: Mapping[str, object], - ) -> dict[str, str] | None: # mutable-ok: API message payload + ) -> dict[str, str] | None: """Convert an Anthropic document block to a Responses input_file part.""" raw_source: Final = block.get("source") if not isinstance(raw_source, Mapping): @@ -130,7 +130,7 @@ class LiteLLMAnthropicToResponsesAPIAdapter: @staticmethod def _tool_result_output_value( output_text: str, - file_parts: tuple[dict[str, str], ...], # mutable-ok: json content parts + file_parts: tuple[dict[str, str], ...], ) -> str | list[dict[str, str]]: # mutable-ok: API message payload """Plain string output, or a part list when document file parts are present.""" if not file_parts: @@ -165,7 +165,7 @@ class LiteLLMAnthropicToResponsesAPIAdapter: cls, summary: Iterable[object], encrypted_content: object, - ) -> dict[str, object] | None: # mutable-ok: API message payload + ) -> dict[str, object] | None: """The one Anthropic block for a Responses reasoning item. The item's encrypted reasoning rides the block's opaque field (`signature`, or @@ -192,9 +192,7 @@ class LiteLLMAnthropicToResponsesAPIAdapter: return "thinking" if block.get("type") in ("thinking", "redacted_thinking") else f"block:{index}" @classmethod - def _assistant_group_to_input_items( - cls, group: tuple[Mapping[str, object], ...] - ) -> tuple[dict[str, object], ...]: # mutable-ok: API message payload + def _assistant_group_to_input_items(cls, group: tuple[Mapping[str, object], ...]) -> tuple[dict[str, object], ...]: first: Final = group[0] btype: Final = first.get("type") if btype in ("thinking", "redacted_thinking"): diff --git a/litellm/llms/anthropic/prompt_cache_prediction.py b/litellm/llms/anthropic/prompt_cache_prediction.py index 00fe56a5e39..c346f83839f 100644 --- a/litellm/llms/anthropic/prompt_cache_prediction.py +++ b/litellm/llms/anthropic/prompt_cache_prediction.py @@ -425,7 +425,7 @@ def _digest(value: object) -> str: ).hexdigest() -def _json_object(value: object) -> dict[str, JsonValue]: # mutable-ok: JSON serialization requires a dictionary +def _json_object(value: object) -> dict[str, JsonValue]: return _JSON_OBJECT.validate_python(value) diff --git a/litellm/llms/azure/azure.py b/litellm/llms/azure/azure.py index d396e1f4867..60be0f31466 100644 --- a/litellm/llms/azure/azure.py +++ b/litellm/llms/azure/azure.py @@ -1269,7 +1269,7 @@ class AzureChatCompletion(BaseAzureLLM, BaseLLM): if not isinstance(max_retries, int): raise AzureOpenAIError(status_code=422, message="max retries must be an int") - auth_params: Final[dict[str, object]] = {**(litellm_params or {})} # mutable-ok: SDK init takes a dict + auth_params: Final[dict[str, object]] = {**(litellm_params or {})} if azure_ad_token is not None: auth_params["azure_ad_token"] = azure_ad_token if azure_ad_token_provider is not None: diff --git a/litellm/llms/azure/common_utils.py b/litellm/llms/azure/common_utils.py index c640405c269..3a78950b6ff 100644 --- a/litellm/llms/azure/common_utils.py +++ b/litellm/llms/azure/common_utils.py @@ -789,9 +789,9 @@ class BaseAzureLLM(BaseOpenAILLM): @classmethod def base_validate_azure_environment( cls, - headers: dict[str, str], # mutable-ok: mirrors override contract + headers: dict[str, str], litellm_params: GenericLiteLLMParams | None, - ) -> dict[str, str]: # mutable-ok: mirrors override contract + ) -> dict[str, str]: return cls._base_validate_azure_environment(headers, litellm_params) @staticmethod diff --git a/litellm/llms/azure/search/transformation.py b/litellm/llms/azure/search/transformation.py index 4264dae018a..5efc87e4866 100644 --- a/litellm/llms/azure/search/transformation.py +++ b/litellm/llms/azure/search/transformation.py @@ -279,11 +279,11 @@ class BingGroundingSearchConfig(BaseSearchConfig): def validate_environment( self, - headers: dict[str, str], # mutable-ok: BaseSearchConfig.validate_environment signature + headers: dict[str, str], api_key: str | None = None, api_base: str | None = None, **kwargs: object, # kwargs-ok: BaseSearchConfig.validate_environment signature - ) -> dict[str, str]: # mutable-ok: the http handler passes this straight to httpx as headers + ) -> dict[str, str]: """ Validate environment and return headers. @@ -331,7 +331,7 @@ class BingGroundingSearchConfig(BaseSearchConfig): def get_complete_url( self, api_base: str | None, - optional_params: dict[str, object], # mutable-ok: BaseSearchConfig.get_complete_url signature + optional_params: dict[str, object], data: dict[str, object] | list[dict[str, object]] | None = None, # mutable-ok: base signature **kwargs: object, # kwargs-ok: BaseSearchConfig.get_complete_url signature ) -> str: @@ -349,9 +349,9 @@ class BingGroundingSearchConfig(BaseSearchConfig): def transform_search_request( self, query: str | list[str], # mutable-ok: BaseSearchConfig.transform_search_request signature - optional_params: dict[str, object], # mutable-ok: base signature + optional_params: dict[str, object], **kwargs: object, # kwargs-ok: BaseSearchConfig.transform_search_request signature - ) -> dict[str, object]: # mutable-ok: the http handler passes this straight to httpx as the JSON body + ) -> dict[str, object]: """ Transform Search request to the Foundry Responses API format. @@ -429,7 +429,7 @@ class BingGroundingSearchConfig(BaseSearchConfig): self, error_message: str, status_code: int, - headers: dict[str, str], # mutable-ok: BaseSearchConfig.get_error_class signature + headers: dict[str, str], ) -> Exception: detail: Final = _unwrap_error_detail(error_message).rstrip(". ") return BaseLLMException( diff --git a/litellm/llms/azure_ai/agents/transformation.py b/litellm/llms/azure_ai/agents/transformation.py index 18f8f7916b7..36b1dfa2bbe 100644 --- a/litellm/llms/azure_ai/agents/transformation.py +++ b/litellm/llms/azure_ai/agents/transformation.py @@ -187,7 +187,7 @@ class AzureAIAgentsConfig(BaseConfig): def get_agent_id( self, model: str, - optional_params: dict[str, object], # mutable-ok: mirrors override contract + optional_params: dict[str, object], ) -> str: return self._get_agent_id(model, optional_params) diff --git a/litellm/llms/azure_ai/chat/transformation.py b/litellm/llms/azure_ai/chat/transformation.py index a8e6320bd1e..e33beee0726 100644 --- a/litellm/llms/azure_ai/chat/transformation.py +++ b/litellm/llms/azure_ai/chat/transformation.py @@ -91,11 +91,11 @@ class AzureAIStudioConfig(OpenAIConfig): def map_openai_params( self, - non_default_params: dict[str, object], # mutable-ok: OpenAIConfig.map_openai_params signature - optional_params: dict[str, object], # mutable-ok: OpenAIConfig.map_openai_params signature + non_default_params: dict[str, object], + optional_params: dict[str, object], model: str, drop_params: bool, - ) -> dict[str, object]: # mutable-ok: OpenAIConfig.map_openai_params signature + ) -> dict[str, object]: if not azureAIGPT5Config.is_model_gpt_5_model(model): return super().map_openai_params( non_default_params=non_default_params, diff --git a/litellm/llms/azure_ai/embed/cohere_transformation.py b/litellm/llms/azure_ai/embed/cohere_transformation.py index 82e855d8706..9dbde205123 100644 --- a/litellm/llms/azure_ai/embed/cohere_transformation.py +++ b/litellm/llms/azure_ai/embed/cohere_transformation.py @@ -70,7 +70,7 @@ class AzureAICohereConfig: def transform_request( self, input: list[str], # mutable-ok: mirrors override contract - optional_params: dict[str, object], # mutable-ok: mirrors override contract + optional_params: dict[str, object], model: str, ) -> tuple[ImageEmbeddingRequest, EmbeddingCreateParams, list[int]]: # mutable-ok: mirrors override contract return self._transform_request(input, optional_params, model) diff --git a/litellm/llms/azure_ai/image_generation/flux_transformation.py b/litellm/llms/azure_ai/image_generation/flux_transformation.py index 8b4fa78cdf4..49a65b45f62 100644 --- a/litellm/llms/azure_ai/image_generation/flux_transformation.py +++ b/litellm/llms/azure_ai/image_generation/flux_transformation.py @@ -123,7 +123,7 @@ class AzureFoundryFluxImageGenerationConfig(GPTImageGenerationConfig): optional_params: Mapping[str, object], model: str, drop_params: bool, - ) -> dict[str, object]: # mutable-ok: inherited config contract returns a dict + ) -> dict[str, object]: if not self.is_flux2_model(model): return super().map_openai_params( non_default_params=dict(non_default_params), diff --git a/litellm/llms/azure_ai/passthrough/transformation.py b/litellm/llms/azure_ai/passthrough/transformation.py index 35b7642d7de..7bb07cdfa2e 100644 --- a/litellm/llms/azure_ai/passthrough/transformation.py +++ b/litellm/llms/azure_ai/passthrough/transformation.py @@ -128,7 +128,7 @@ class AzureAIPassthroughConfig(AzureFoundryModelInfo, BasePassthroughConfig): litellm_params: Mapping[str, object], api_key: str | None = None, api_base: str | None = None, - ) -> dict[str, str]: # mutable-ok: base class contract returns dict for httpx + ) -> dict[str, str]: auth_headers: Final = get_azure_ai_auth_headers( api_key=api_key, litellm_params=litellm_params, diff --git a/litellm/llms/base_llm/anthropic_messages/transformation.py b/litellm/llms/base_llm/anthropic_messages/transformation.py index 5758d156a61..5d6c029bed2 100644 --- a/litellm/llms/base_llm/anthropic_messages/transformation.py +++ b/litellm/llms/base_llm/anthropic_messages/transformation.py @@ -43,14 +43,14 @@ class BaseAnthropicMessagesConfig(ABC): async def avalidate_anthropic_messages_environment( self, - headers: dict, # mutable-ok: mirrors the sync validate_anthropic_messages_environment contract + headers: dict, model: str, messages: list[Any], # mutable-ok: mirrors the sync validate_anthropic_messages_environment contract - optional_params: dict, # mutable-ok: mirrors the sync validate_anthropic_messages_environment contract - litellm_params: dict, # mutable-ok: mirrors the sync validate_anthropic_messages_environment contract + optional_params: dict, + litellm_params: dict, api_key: str | None = None, api_base: str | None = None, - ) -> tuple[dict, str | None]: # mutable-ok: mirrors the sync validate_anthropic_messages_environment contract + ) -> tuple[dict, str | None]: """Async counterpart used by the async handler. The default delegates to the sync implementation; providers whose sync path can block the event loop (e.g. a WIF token exchange) override this.""" diff --git a/litellm/llms/base_llm/auth/token_exchange.py b/litellm/llms/base_llm/auth/token_exchange.py index 5bf1f4dd0db..d97de6a7846 100644 --- a/litellm/llms/base_llm/auth/token_exchange.py +++ b/litellm/llms/base_llm/auth/token_exchange.py @@ -653,7 +653,7 @@ class JwtBearerTokenExchangeEngine: self._shared_store: Final = shared_store self._wall_clock: Final = wall_clock self._lock: Final = threading.Lock() - self._entries: Final[dict[str, _Entry]] = {} # mutable-ok: engine-owned map guarded by _lock + self._entries: Final[dict[str, _Entry]] = {} def get_token(self, spec: TokenExchangeSpec) -> ExchangeResult: """A follower whose leader published nothing re-classifies rather than recursing, so a diff --git a/litellm/llms/base_llm/base_model_iterator.py b/litellm/llms/base_llm/base_model_iterator.py index 9cb5c2c55dd..5f742548f5e 100644 --- a/litellm/llms/base_llm/base_model_iterator.py +++ b/litellm/llms/base_llm/base_model_iterator.py @@ -112,7 +112,7 @@ class BaseModelResponseIterator: def string_to_dict_parser( cls, str_line: str, - ) -> dict[str, object] | None: # mutable-ok: mirrors override contract + ) -> dict[str, object] | None: return cls._string_to_dict_parser(str_line) def _handle_string_chunk(self, str_line: str) -> GenericStreamingChunk | ModelResponseStream: diff --git a/litellm/llms/base_llm/chat/transformation.py b/litellm/llms/base_llm/chat/transformation.py index 50825720331..5bae66c2681 100644 --- a/litellm/llms/base_llm/chat/transformation.py +++ b/litellm/llms/base_llm/chat/transformation.py @@ -148,9 +148,9 @@ class BaseConfig(ABC): def add_tools_to_optional_params( self, - optional_params: dict[str, object], # mutable-ok: mirrors override contract + optional_params: dict[str, object], tools: list[ChatCompletionToolParam], # mutable-ok: mirrors override contract - ) -> dict[str, object]: # mutable-ok: mirrors override contract + ) -> dict[str, object]: return self._add_tools_to_optional_params(optional_params, tools) def translate_developer_role_to_system_role( diff --git a/litellm/llms/base_llm/files/batch_records.py b/litellm/llms/base_llm/files/batch_records.py index 6bb98456e69..e6b6132c86a 100644 --- a/litellm/llms/base_llm/files/batch_records.py +++ b/litellm/llms/base_llm/files/batch_records.py @@ -18,7 +18,7 @@ def _responses_request_keys() -> frozenset[str]: def responses_batch_body_to_chat_body( openai_request_body: Mapping[str, object], custom_llm_provider: str | None = None, -) -> dict[str, object]: # mutable-ok: provider transforms take the bridged chat body as a plain dict +) -> dict[str, object]: """ Rewrite the body of an OpenAI `/v1/responses` batch record as a Chat Completions body. diff --git a/litellm/llms/base_llm/files/transformation.py b/litellm/llms/base_llm/files/transformation.py index ec0b28a63ad..30e7d82f840 100644 --- a/litellm/llms/base_llm/files/transformation.py +++ b/litellm/llms/base_llm/files/transformation.py @@ -166,7 +166,7 @@ class BaseFilesConfig(BaseConfig): self, raw_response: httpx.Response, optional_params: Mapping[str, object], - litellm_params: dict, # mutable-ok: carries provider stashes from the request transform to the response one + litellm_params: dict, ) -> tuple[str, dict[str, str]] | None: """Request for the page after `raw_response`, or None once the listing is complete.""" return None diff --git a/litellm/llms/base_llm/guardrail_translation/base_translation.py b/litellm/llms/base_llm/guardrail_translation/base_translation.py index 70b8291d32c..b895e6787f5 100644 --- a/litellm/llms/base_llm/guardrail_translation/base_translation.py +++ b/litellm/llms/base_llm/guardrail_translation/base_translation.py @@ -121,7 +121,7 @@ class BaseTranslation(ABC): @staticmethod def merge_user_api_key_metadata_into_request( - request_data: dict[str, Any], # mutable-ok: proxy hooks share and mutate the request payload dict in place + request_data: dict[str, Any], user_api_key_dict: Optional["UserAPIKeyAuth"], ) -> None: """ diff --git a/litellm/llms/base_llm/harness/transformation.py b/litellm/llms/base_llm/harness/transformation.py index d6db3d2ecb1..69dff5e69f3 100644 --- a/litellm/llms/base_llm/harness/transformation.py +++ b/litellm/llms/base_llm/harness/transformation.py @@ -33,7 +33,7 @@ StreamStateT = TypeVar("StreamStateT") def event_list(*events: Event) -> Sequence[Event]: """A transform_stream_line result. One place builds it so every parser returns the same shape.""" - return list(events) # mutable-ok: stream-line results are list-shaped; callers and tests compare with list literals + return list(events) class HarnessTurnError(HarnessError): diff --git a/litellm/llms/base_llm/harness/utils.py b/litellm/llms/base_llm/harness/utils.py index 8dcf784c90f..d642ef3a2d0 100644 --- a/litellm/llms/base_llm/harness/utils.py +++ b/litellm/llms/base_llm/harness/utils.py @@ -26,7 +26,7 @@ _JSON_OBJECT_ADAPTER: Final = TypeAdapter(dict[str, object]) _RAW_DECODE_ADAPTER: Final = TypeAdapter(tuple[object, int]) -def gateway_headers(ctx: SessionContext) -> dict[str, str]: # mutable-ok: acompletion(extra_headers=) requires dict +def gateway_headers(ctx: SessionContext) -> dict[str, str]: metadata_json: Final = json.dumps(dict(ctx.metadata), default=str) if ctx.metadata else None return { "x-litellm-tags": f"harness,{ctx.harness.value}", @@ -42,7 +42,7 @@ def normalize_tool_name(native_name: str, mapping: Mapping[str, str]) -> str: def native_tool_names(normalized: Sequence[str], mapping: Mapping[str, Sequence[str]]) -> Sequence[str]: """Native names for normalized tool names, de-duplicated, order kept.""" expanded: Final = itertools.chain.from_iterable(mapping.get(name, (name,)) for name in normalized) - return list(dict.fromkeys(expanded)) # mutable-ok: public helper whose callers/tests compare against list literals + return list(dict.fromkeys(expanded)) def last_json_object(text: str) -> str | None: @@ -80,22 +80,22 @@ def strict_json_schema(schema: JSONValue, depth: int = 0) -> JSONValue: if depth > DEFAULT_MAX_RECURSE_DEPTH: raise ValueError(f"output schema is nested deeper than {DEFAULT_MAX_RECURSE_DEPTH} levels") if isinstance(schema, list): - return [strict_json_schema(entry, depth + 1) for entry in schema] # mutable-ok: JSON document output + return [strict_json_schema(entry, depth + 1) for entry in schema] if not isinstance(schema, dict): return schema entries: Final = ((key, strict_json_schema(value, depth + 1)) for key, value in schema.items()) - result = dict(entries) # mutable-ok: JSON document; "default" is popped below + result = dict(entries) if "$ref" in result: - return {"$ref": result["$ref"]} # mutable-ok: JSONValue output is a plain JSON document + return {"$ref": result["$ref"]} result.pop("default", None) properties = result.get("properties") if result.get("type") == "object" or isinstance(properties, dict): - props = properties if isinstance(properties, dict) else {} # mutable-ok: JSONValue object member + props = properties if isinstance(properties, dict) else {} required: Final[list[JSONValue]] = list(props) # mutable-ok: JSON array in the output schema strict: Final[Mapping[str, JSONValue]] = MappingProxyType( {"properties": props, "required": required, "additionalProperties": False} ) - result = {**result, **strict} # mutable-ok: JSON document output + result = {**result, **strict} return result diff --git a/litellm/llms/base_llm/ocr/transformation.py b/litellm/llms/base_llm/ocr/transformation.py index 97d402e7a1b..80e4a1182d6 100644 --- a/litellm/llms/base_llm/ocr/transformation.py +++ b/litellm/llms/base_llm/ocr/transformation.py @@ -92,11 +92,11 @@ class OCRResponse(LiteLLMPydanticObjectBase): _hidden_params: dict = PrivateAttr(default_factory=dict) @property - def hidden_params(self) -> dict[str, builtins.object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, builtins.object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: self._hidden_params = hidden_params def set_provider_native_response(self, native_response: Mapping[str, builtins.object]) -> None: diff --git a/litellm/llms/base_llm/sandbox/transformation.py b/litellm/llms/base_llm/sandbox/transformation.py index 877edc15ec7..837ce2a73a0 100644 --- a/litellm/llms/base_llm/sandbox/transformation.py +++ b/litellm/llms/base_llm/sandbox/transformation.py @@ -29,11 +29,11 @@ class ContainerHandle(LiteLLMPydanticObjectBase): _hidden_params: dict = PrivateAttr(default_factory=dict) @property - def hidden_params(self) -> dict[str, builtins.object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, builtins.object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: self._hidden_params = hidden_params @@ -52,11 +52,11 @@ class CodeExecutionResult(LiteLLMPydanticObjectBase): _hidden_params: dict = PrivateAttr(default_factory=dict) @property - def hidden_params(self) -> dict[str, builtins.object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, builtins.object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: self._hidden_params = hidden_params diff --git a/litellm/llms/base_llm/search/transformation.py b/litellm/llms/base_llm/search/transformation.py index 8ff1beac782..bce9cff2453 100644 --- a/litellm/llms/base_llm/search/transformation.py +++ b/litellm/llms/base_llm/search/transformation.py @@ -79,11 +79,11 @@ class SearchResponse(LiteLLMPydanticObjectBase): _hidden_params: dict = PrivateAttr(default_factory=dict) @property - def hidden_params(self) -> dict[str, builtins.object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, builtins.object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: self._hidden_params = hidden_params @@ -206,12 +206,12 @@ class BaseSearchConfig: def sign_request( self, - headers: dict[str, str], # mutable-ok: matches the request header dict every other hook on this base takes - optional_params: dict[str, object], # mutable-ok: matches every other hook on this base + headers: dict[str, str], + optional_params: dict[str, object], request_data: dict[str, object] | list[dict[str, object]], # mutable-ok: transform_search_request's body api_base: str, api_key: str | None = None, - ) -> tuple[dict[str, str], bytes | None]: # mutable-ok: the handler passes these headers straight to httpx + ) -> tuple[dict[str, str], bytes | None]: """ OPTIONAL diff --git a/litellm/llms/bedrock/base_aws_llm.py b/litellm/llms/bedrock/base_aws_llm.py index 424e1dc2106..70864070ffd 100644 --- a/litellm/llms/bedrock/base_aws_llm.py +++ b/litellm/llms/bedrock/base_aws_llm.py @@ -170,7 +170,7 @@ def build_web_identity_session_policy() -> WebIdentitySessionPolicy: def pop_aws_auth_params( - optional_params: MutableMapping[str, object], # mutable-ok: pops the aws_* keys out of the caller's mapping + optional_params: MutableMapping[str, object], ) -> AwsAuthParams: return AwsAuthParams.model_validate( MappingProxyType({key: optional_params.pop(key, None) for key in AWS_AUTH_PARAM_KEYS}) @@ -792,7 +792,7 @@ class BaseAWSLLM(SignsRequestsWithAWS): def get_aws_region_name( self, - optional_params: dict[str, object], # mutable-ok: mirrors override contract + optional_params: dict[str, object], model: str | None = None, model_id: str | None = None, ) -> str: @@ -1535,7 +1535,7 @@ class BaseAWSLLM(SignsRequestsWithAWS): @overload def _get_boto_credentials_from_optional_params( self, - optional_params: dict, # mutable-ok: the implementation pops the aws_* keys out of the caller's dict in place + optional_params: dict, model: str | None = None, bearer_token: None = None, ) -> Boto3CredentialsInfo: ... @@ -1543,7 +1543,7 @@ class BaseAWSLLM(SignsRequestsWithAWS): @overload def _get_boto_credentials_from_optional_params( self, - optional_params: dict, # mutable-ok: the implementation pops the aws_* keys out of the caller's dict in place + optional_params: dict, model: str | None = None, *, bearer_token: str, diff --git a/litellm/llms/bedrock/chat/agentcore/transformation.py b/litellm/llms/bedrock/chat/agentcore/transformation.py index 207ed249866..b97ed740daa 100644 --- a/litellm/llms/bedrock/chat/agentcore/transformation.py +++ b/litellm/llms/bedrock/chat/agentcore/transformation.py @@ -195,7 +195,7 @@ class AmazonAgentCoreConfig(BaseConfig, BaseAWSLLM): def get_runtime_session_id( self, - optional_params: dict[str, object], # mutable-ok: mirrors override contract + optional_params: dict[str, object], ) -> str: return self._get_runtime_session_id(optional_params) @@ -210,7 +210,7 @@ class AmazonAgentCoreConfig(BaseConfig, BaseAWSLLM): def get_runtime_user_id( self, - optional_params: dict[str, object], # mutable-ok: mirrors override contract + optional_params: dict[str, object], ) -> str | None: return self._get_runtime_user_id(optional_params) diff --git a/litellm/llms/bedrock/chat/chat_completions/transformation.py b/litellm/llms/bedrock/chat/chat_completions/transformation.py index ad5f0da8542..728b8166cee 100644 --- a/litellm/llms/bedrock/chat/chat_completions/transformation.py +++ b/litellm/llms/bedrock/chat/chat_completions/transformation.py @@ -224,7 +224,7 @@ class BedrockRuntimeChatCompletionsStreamingHandler(OpenAIChatCompletionStreamin super().__init__(streaming_response=streaming_response, sync_stream=sync_stream, json_mode=json_mode) self._splitters: Mapping[int, ReasoningTagSplitter] = MappingProxyType({}) - def chunk_parser(self, chunk: dict) -> ModelResponseStream: # mutable-ok: BaseModelResponseIterator signature + def chunk_parser(self, chunk: dict) -> ModelResponseStream: parsed: Final = super().chunk_parser(chunk) for choice in parsed.choices: next_state, reasoning, content = _split_streamed_content( @@ -275,20 +275,20 @@ class AmazonBedrockRuntimeChatCompletionsConfig(OpenAILikeChatConfig): self, error_message: str, status_code: int, - headers: dict[str, object] | httpx.Headers, # mutable-ok: BaseConfig signature + headers: dict[str, object] | httpx.Headers, ) -> BaseLLMException: return BedrockError(status_code=status_code, message=error_message, headers=headers) def validate_environment( self, - headers: dict, # mutable-ok: BaseConfig signature + headers: dict, model: str, messages: list[AllMessageValues], - optional_params: dict, # mutable-ok: BaseConfig signature - litellm_params: dict, # mutable-ok: BaseConfig signature + optional_params: dict, + litellm_params: dict, api_key: str | None = None, api_base: str | None = None, - ) -> dict: # mutable-ok: BaseConfig signature + ) -> dict: return super().validate_environment( headers=headers, model=model, @@ -304,8 +304,8 @@ class AmazonBedrockRuntimeChatCompletionsConfig(OpenAILikeChatConfig): api_base: str | None, api_key: str | None, model: str, - optional_params: dict, # mutable-ok: BaseConfig signature - litellm_params: dict, # mutable-ok: BaseConfig signature + optional_params: dict, + litellm_params: dict, stream: bool | None = None, ) -> str: if api_base is not None and "chat/completions" in api_base: @@ -328,9 +328,7 @@ class AmazonBedrockRuntimeChatCompletionsConfig(OpenAILikeChatConfig): return f"{base}/chat/completions" return f"{base}/openai/v1/chat/completions" - def _params_with_region_from_path( - self, optional_params: dict, model: str | None - ) -> dict: # mutable-ok: BaseAWSLLM's region resolver and signer take a plain dict + def _params_with_region_from_path(self, optional_params: dict, model: str | None) -> dict: region_from_path, _ = split_bedrock_region_path(model or "") if region_from_path is None or optional_params.get("aws_region_name") is not None: return optional_params @@ -338,15 +336,15 @@ class AmazonBedrockRuntimeChatCompletionsConfig(OpenAILikeChatConfig): def sign_request( self, - headers: dict, # mutable-ok: BaseConfig signature - optional_params: dict, # mutable-ok: BaseConfig signature - request_data: dict, # mutable-ok: BaseConfig signature + headers: dict, + optional_params: dict, + request_data: dict, api_base: str, api_key: str | None = None, model: str | None = None, stream: bool | None = None, fake_stream: bool | None = None, - ) -> tuple[dict, bytes | None]: # mutable-ok: BaseConfig signature + ) -> tuple[dict, bytes | None]: return self._aws_signer._sign_request( # pyright: ignore[reportPrivateUsage] # BaseAWSLLM has no public signer service_name="bedrock", headers=headers, @@ -361,12 +359,12 @@ class AmazonBedrockRuntimeChatCompletionsConfig(OpenAILikeChatConfig): def map_openai_params( self, - non_default_params: dict, # mutable-ok: BaseConfig signature - optional_params: dict, # mutable-ok: BaseConfig signature + non_default_params: dict, + optional_params: dict, model: str, drop_params: bool, replace_max_completion_tokens_with_max_tokens: bool = False, - ) -> dict: # mutable-ok: BaseConfig signature + ) -> dict: mapped: Final = _PARAMS_DICT_ADAPTER.validate_python( super().map_openai_params( non_default_params=non_default_params, @@ -404,9 +402,7 @@ class AmazonBedrockRuntimeChatCompletionsConfig(OpenAILikeChatConfig): ) ) - def _inference_params( - self, optional_params: Mapping[str, object] - ) -> dict[str, object]: # mutable-ok: BaseConfig signature of transform_request + def _inference_params(self, optional_params: Mapping[str, object]) -> dict[str, object]: return { key: value for key, value in optional_params.items() @@ -417,10 +413,10 @@ class AmazonBedrockRuntimeChatCompletionsConfig(OpenAILikeChatConfig): self, model: str, messages: list[AllMessageValues], # mutable-ok: BaseConfig signature - optional_params: dict, # mutable-ok: BaseConfig signature - litellm_params: dict, # mutable-ok: BaseConfig signature - headers: dict, # mutable-ok: BaseConfig signature - ) -> dict: # mutable-ok: BaseConfig signature + optional_params: dict, + litellm_params: dict, + headers: dict, + ) -> dict: optional_params_view: Final = _PARAMS_DICT_ADAPTER.validate_python(optional_params) return super().transform_request( model=split_bedrock_region_path(model)[1], @@ -434,10 +430,10 @@ class AmazonBedrockRuntimeChatCompletionsConfig(OpenAILikeChatConfig): self, model: str, messages: list[AllMessageValues], # mutable-ok: BaseConfig signature - optional_params: dict, # mutable-ok: BaseConfig signature - litellm_params: dict, # mutable-ok: BaseConfig signature - headers: dict, # mutable-ok: BaseConfig signature - ) -> dict: # mutable-ok: BaseConfig signature + optional_params: dict, + litellm_params: dict, + headers: dict, + ) -> dict: optional_params_view: Final = _PARAMS_DICT_ADAPTER.validate_python(optional_params) return await super().async_transform_request( model=split_bedrock_region_path(model)[1], @@ -453,10 +449,10 @@ class AmazonBedrockRuntimeChatCompletionsConfig(OpenAILikeChatConfig): raw_response: httpx.Response, model_response: ModelResponse, logging_obj: "LiteLLMLoggingObj", - request_data: dict, # mutable-ok: BaseConfig signature + request_data: dict, messages: list[AllMessageValues], # mutable-ok: BaseConfig signature - optional_params: dict, # mutable-ok: BaseConfig signature - litellm_params: dict, # mutable-ok: BaseConfig signature + optional_params: dict, + litellm_params: dict, encoding: "tiktoken.Encoding | None", api_key: str | None = None, json_mode: bool | None = None, diff --git a/litellm/llms/bedrock/chat/invoke_handler.py b/litellm/llms/bedrock/chat/invoke_handler.py index 6276324ad7e..bc648d1f816 100644 --- a/litellm/llms/bedrock/chat/invoke_handler.py +++ b/litellm/llms/bedrock/chat/invoke_handler.py @@ -779,8 +779,8 @@ class AWSEventStreamDecoder: def chunk_parser( self, - chunk_data: dict[str, object], # mutable-ok: mirrors override contract - ) -> GChunk | ModelResponseStream | dict[str, object]: # mutable-ok: mirrors override contract + chunk_data: dict[str, object], + ) -> GChunk | ModelResponseStream | dict[str, object]: return self._chunk_parser(chunk_data) def iter_bytes( diff --git a/litellm/llms/bedrock/claude_platform/common_utils.py b/litellm/llms/bedrock/claude_platform/common_utils.py index e25e58056f5..a94d9e15f21 100644 --- a/litellm/llms/bedrock/claude_platform/common_utils.py +++ b/litellm/llms/bedrock/claude_platform/common_utils.py @@ -83,7 +83,7 @@ class BedrockClaudePlatformMixin(BaseAWSLLM): self, error_message: str, status_code: int, - headers: dict[str, object] | httpx.Headers, # mutable-ok: base passes response headers as a dict + headers: dict[str, object] | httpx.Headers, ) -> BedrockError: return BedrockError(status_code=status_code, message=error_message, headers=headers) diff --git a/litellm/llms/bedrock/common_utils.py b/litellm/llms/bedrock/common_utils.py index 7c637bf6724..5b43ff8cae2 100644 --- a/litellm/llms/bedrock/common_utils.py +++ b/litellm/llms/bedrock/common_utils.py @@ -253,7 +253,7 @@ def _bedrock_model_supports(model: str, key: str) -> bool: def apply_bedrock_invoke_structured_output( model: str, - request_body: dict[str, object], # mutable-ok: edited in place like siblings + request_body: dict[str, object], ) -> None: """ Route Anthropic structured-output params to what the Bedrock model supports. @@ -295,7 +295,7 @@ def apply_bedrock_invoke_structured_output( def strip_unsupported_bedrock_invoke_output_config_keys( model: str, - request_body: dict[str, object], # mutable-ok: edited in place like siblings + request_body: dict[str, object], ) -> None: """ Drop ``output_config`` keys the Bedrock model does not accept. diff --git a/litellm/llms/bedrock/embed/amazon_nova_transformation.py b/litellm/llms/bedrock/embed/amazon_nova_transformation.py index cc8c8d37c42..cd450f77bc8 100644 --- a/litellm/llms/bedrock/embed/amazon_nova_transformation.py +++ b/litellm/llms/bedrock/embed/amazon_nova_transformation.py @@ -207,11 +207,11 @@ class AmazonNovaEmbeddingConfig: def transform_request( self, input: str, - inference_params: dict[str, object], # mutable-ok: mirrors override contract + inference_params: dict[str, object], async_invoke_route: bool = False, model_id: str | None = None, output_s3_uri: str | None = None, - ) -> dict[str, object]: # mutable-ok: mirrors override contract + ) -> dict[str, object]: return self._transform_request(input, inference_params, async_invoke_route, model_id, output_s3_uri) def _wrap_async_invoke_request( @@ -372,7 +372,7 @@ class AmazonNovaEmbeddingConfig: def transform_async_invoke_response( self, - response: dict[str, object], # mutable-ok: mirrors override contract + response: dict[str, object], model: str, ) -> EmbeddingResponse: return self._transform_async_invoke_response(response, model) diff --git a/litellm/llms/bedrock/embed/amazon_titan_g1_transformation.py b/litellm/llms/bedrock/embed/amazon_titan_g1_transformation.py index 581b89369f7..9ab9b9c65d6 100644 --- a/litellm/llms/bedrock/embed/amazon_titan_g1_transformation.py +++ b/litellm/llms/bedrock/embed/amazon_titan_g1_transformation.py @@ -63,7 +63,7 @@ class AmazonTitanG1Config: def transform_request( self, input: str, - inference_params: dict[str, object], # mutable-ok: mirrors override contract + inference_params: dict[str, object], ) -> AmazonTitanG1EmbeddingRequest: return self._transform_request(input, inference_params) diff --git a/litellm/llms/bedrock/embed/amazon_titan_multimodal_transformation.py b/litellm/llms/bedrock/embed/amazon_titan_multimodal_transformation.py index e7a5b4a3d78..5598d15fc2b 100644 --- a/litellm/llms/bedrock/embed/amazon_titan_multimodal_transformation.py +++ b/litellm/llms/bedrock/embed/amazon_titan_multimodal_transformation.py @@ -55,7 +55,7 @@ class AmazonTitanMultimodalEmbeddingG1Config: def transform_request( self, input: str, - inference_params: dict[str, object], # mutable-ok: mirrors override contract + inference_params: dict[str, object], ) -> AmazonTitanMultimodalEmbeddingRequest: return self._transform_request(input, inference_params) diff --git a/litellm/llms/bedrock/embed/amazon_titan_v2_transformation.py b/litellm/llms/bedrock/embed/amazon_titan_v2_transformation.py index 3b594662a9b..09fb8f08f7f 100644 --- a/litellm/llms/bedrock/embed/amazon_titan_v2_transformation.py +++ b/litellm/llms/bedrock/embed/amazon_titan_v2_transformation.py @@ -80,7 +80,7 @@ class AmazonTitanV2Config: def transform_request( self, input: str, - inference_params: dict[str, object], # mutable-ok: mirrors override contract + inference_params: dict[str, object], ) -> AmazonTitanV2EmbeddingRequest: return self._transform_request(input, inference_params) diff --git a/litellm/llms/bedrock/embed/cohere_transformation.py b/litellm/llms/bedrock/embed/cohere_transformation.py index 9b626515271..21e270a9994 100644 --- a/litellm/llms/bedrock/embed/cohere_transformation.py +++ b/litellm/llms/bedrock/embed/cohere_transformation.py @@ -46,6 +46,6 @@ class BedrockCohereEmbeddingConfig: self, model: str, input: list[str], # mutable-ok: mirrors override contract - inference_params: dict[str, object], # mutable-ok: mirrors override contract + inference_params: dict[str, object], ) -> CohereEmbeddingRequest: return self._transform_request(model, input, inference_params) diff --git a/litellm/llms/bedrock/embed/embedding.py b/litellm/llms/bedrock/embed/embedding.py index 58cfb6cb95e..ce5f7f94a5a 100644 --- a/litellm/llms/bedrock/embed/embedding.py +++ b/litellm/llms/bedrock/embed/embedding.py @@ -67,14 +67,14 @@ class BedrockEmbedding(BaseAWSLLM): @overload def _load_credentials( self, - optional_params: dict, # mutable-ok: the implementation pops the aws_* keys out of the caller's dict in place + optional_params: dict, bearer_token: None = None, ) -> tuple[Credentials, str]: ... @overload def _load_credentials( self, - optional_params: dict, # mutable-ok: the implementation pops the aws_* keys out of the caller's dict in place + optional_params: dict, bearer_token: str, ) -> tuple[None, str]: ... diff --git a/litellm/llms/bedrock/embed/twelvelabs_marengo_transformation.py b/litellm/llms/bedrock/embed/twelvelabs_marengo_transformation.py index 49a35e7cdc0..2fd628d2f0a 100644 --- a/litellm/llms/bedrock/embed/twelvelabs_marengo_transformation.py +++ b/litellm/llms/bedrock/embed/twelvelabs_marengo_transformation.py @@ -275,7 +275,7 @@ class TwelveLabsMarengoEmbeddingConfig: def transform_request( self, input: str, - inference_params: dict[str, object], # mutable-ok: mirrors override contract + inference_params: dict[str, object], async_invoke_route: bool = False, model_id: str | None = None, output_s3_uri: str | None = None, @@ -390,7 +390,7 @@ class TwelveLabsMarengoEmbeddingConfig: def transform_async_invoke_response( self, - response: dict[str, object], # mutable-ok: mirrors override contract + response: dict[str, object], model: str, ) -> EmbeddingResponse: return self._transform_async_invoke_response(response, model) diff --git a/litellm/llms/bedrock/files/transformation.py b/litellm/llms/bedrock/files/transformation.py index e7ee2f89bbf..b72d37e7e32 100644 --- a/litellm/llms/bedrock/files/transformation.py +++ b/litellm/llms/bedrock/files/transformation.py @@ -1356,9 +1356,7 @@ class BedrockFilesConfig(BaseAWSLLM, BaseFilesConfig): url: Final = f"{target.endpoint_url}/{bucket_name}/" listing_query: Final = _listing_query(configured_prefix, purpose) continuation_query: Final = (("continuation-token", continuation_token),) if continuation_token else () - query: Final[dict[str, str]] = dict( # mutable-ok: the base files contract returns the query as a dict - listing_query + continuation_query - ) + query: Final[dict[str, str]] = dict(listing_query + continuation_query) signed_headers: Final = self._sign_s3_request_without_body( method="GET", api_base=f"{url}?{urlencode(query, quote_via=quote, safe='')}", diff --git a/litellm/llms/bedrock/image_edit/amazon_nova_canvas_image_edit_transformation.py b/litellm/llms/bedrock/image_edit/amazon_nova_canvas_image_edit_transformation.py index db0dc7e65c2..9011572cbaa 100644 --- a/litellm/llms/bedrock/image_edit/amazon_nova_canvas_image_edit_transformation.py +++ b/litellm/llms/bedrock/image_edit/amazon_nova_canvas_image_edit_transformation.py @@ -240,7 +240,7 @@ class BedrockAmazonNovaCanvasImageEditConfig(BaseImageEditConfig): self, error_message: str, status_code: int, - headers: dict[str, object] | httpx.Headers, # mutable-ok: base passes response headers as a dict + headers: dict[str, object] | httpx.Headers, ) -> BedrockError: return BedrockError(status_code=status_code, message=error_message, headers=headers) diff --git a/litellm/llms/bedrock/image_edit/stability_transformation.py b/litellm/llms/bedrock/image_edit/stability_transformation.py index d0ce5895970..aa0056f8b05 100644 --- a/litellm/llms/bedrock/image_edit/stability_transformation.py +++ b/litellm/llms/bedrock/image_edit/stability_transformation.py @@ -97,7 +97,7 @@ class BedrockStabilityImageEditConfig(BaseImageEditConfig): self, error_message: str, status_code: int, - headers: dict[str, object] | httpx.Headers, # mutable-ok: base passes response headers as a dict + headers: dict[str, object] | httpx.Headers, ) -> BedrockError: return BedrockError(status_code=status_code, message=error_message, headers=headers) diff --git a/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py b/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py index 26a0302ef61..f3e572cbd50 100644 --- a/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py +++ b/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py @@ -92,7 +92,7 @@ class AmazonAnthropicClaudeMessagesConfig( self, error_message: str, status_code: int, - headers: dict[str, object] | httpx.Headers, # mutable-ok: base passes response headers as a dict + headers: dict[str, object] | httpx.Headers, ) -> BedrockError: return BedrockError(status_code=status_code, message=error_message, headers=headers) diff --git a/litellm/llms/bedrock/passthrough/transformation.py b/litellm/llms/bedrock/passthrough/transformation.py index c61576fa7b2..efad2e73e50 100644 --- a/litellm/llms/bedrock/passthrough/transformation.py +++ b/litellm/llms/bedrock/passthrough/transformation.py @@ -129,7 +129,7 @@ class BedrockPassthroughConfig(BaseAWSLLM, BedrockModelInfo, BedrockEventStreamD self, error_message: str, status_code: int, - headers: dict[str, object] | httpx.Headers, # mutable-ok: base passes response headers as a dict + headers: dict[str, object] | httpx.Headers, ) -> BedrockError: return BedrockError(status_code=status_code, message=error_message, headers=headers) diff --git a/litellm/llms/bedrock/realtime/handler.py b/litellm/llms/bedrock/realtime/handler.py index 2ceac5899fd..3b675d9024e 100644 --- a/litellm/llms/bedrock/realtime/handler.py +++ b/litellm/llms/bedrock/realtime/handler.py @@ -89,7 +89,7 @@ def _should_log_event(openai_message: Mapping[str, object]) -> bool: class RealtimeClientWebSocket(Protocol): """The client-facing websocket surface the realtime bridge talks to.""" - scope: MutableMapping[str, object] # mutable-ok: the ASGI scope is the per-connection state store + scope: MutableMapping[str, object] async def receive_text(self) -> str: ... diff --git a/litellm/llms/bedrock/realtime/transformation.py b/litellm/llms/bedrock/realtime/transformation.py index 8fef84bae29..f9a5d38d61c 100644 --- a/litellm/llms/bedrock/realtime/transformation.py +++ b/litellm/llms/bedrock/realtime/transformation.py @@ -128,7 +128,7 @@ class BedrockRealtimeConfig(BaseRealtimeConfig): self, error_message: str, status_code: int, - headers: dict[str, object] | httpx.Headers, # mutable-ok: base passes response headers as a dict + headers: dict[str, object] | httpx.Headers, ) -> BedrockError: return BedrockError(status_code=status_code, message=error_message, headers=headers) diff --git a/litellm/llms/bedrock/rerank/transformation.py b/litellm/llms/bedrock/rerank/transformation.py index 442903fc05e..058794b468f 100644 --- a/litellm/llms/bedrock/rerank/transformation.py +++ b/litellm/llms/bedrock/rerank/transformation.py @@ -117,6 +117,6 @@ class BedrockRerankConfig: def transform_response( self, - response: dict[str, object], # mutable-ok: mirrors override contract + response: dict[str, object], ) -> RerankResponse: return self._transform_response(response) diff --git a/litellm/llms/bedrock/responses/transformation.py b/litellm/llms/bedrock/responses/transformation.py index 086d1211835..460cd6fd415 100644 --- a/litellm/llms/bedrock/responses/transformation.py +++ b/litellm/llms/bedrock/responses/transformation.py @@ -157,7 +157,7 @@ def inline_remote_image_urls( def _without_disabled_reasoning_effort( params: Mapping[str, object], model: str, drop_params: bool -) -> dict[str, object]: # mutable-ok: becomes the map_openai_params return value +) -> dict[str, object]: reasoning: Final = params.get("reasoning") effort: Final = reasoning.get("effort") if isinstance(reasoning, Mapping) else None if not isinstance(reasoning, Mapping) or not isinstance(effort, str): @@ -220,7 +220,7 @@ class BedrockOpenAIResponsesConfig(BaseAWSLLM, OpenAIResponsesAPIConfig): def get_complete_url( self, api_base: str | None, - litellm_params: dict, # mutable-ok: signature fixed by the BaseResponsesAPIConfig override contract + litellm_params: dict, ) -> str: region: Final = self._get_aws_region_name(optional_params=litellm_params, model=None) override: Final = ( @@ -244,10 +244,10 @@ class BedrockOpenAIResponsesConfig(BaseAWSLLM, OpenAIResponsesAPIConfig): def validate_environment( self, - headers: dict, # mutable-ok: signature fixed by the BaseResponsesAPIConfig override contract + headers: dict, model: str, litellm_params: GenericLiteLLMParams | None, - ) -> dict: # mutable-ok: signature fixed by the BaseResponsesAPIConfig override contract + ) -> dict: api_key: Final = litellm_params.api_key if litellm_params is not None else None bearer: Final = resolve_bedrock_bearer_token(api_key) if not bearer: @@ -256,15 +256,15 @@ class BedrockOpenAIResponsesConfig(BaseAWSLLM, OpenAIResponsesAPIConfig): def sign_request( self, - headers: dict, # mutable-ok: signature fixed by the BaseResponsesAPIConfig override contract - optional_params: dict, # mutable-ok: same - request_data: dict, # mutable-ok: same + headers: dict, + optional_params: dict, + request_data: dict, api_base: str, api_key: str | None = None, model: str | None = None, stream: bool | None = None, fake_stream: bool | None = None, - ) -> "tuple[dict, bytes | None]": # mutable-ok: signature fixed by the override contract + ) -> "tuple[dict, bytes | None]": if resolve_bedrock_bearer_token(api_key): # Bedrock API keys are Bearer credentials; SigV4 on top would be wrong. return headers, None @@ -284,7 +284,7 @@ class BedrockOpenAIResponsesConfig(BaseAWSLLM, OpenAIResponsesAPIConfig): response_api_optional_params: ResponsesAPIOptionalRequestParams, model: str, drop_params: bool, - ) -> dict: # mutable-ok: signature fixed by the override contract + ) -> dict: mapped: Final = super().map_openai_params( response_api_optional_params=response_api_optional_params, model=model, drop_params=drop_params ) @@ -316,10 +316,10 @@ class BedrockOpenAIResponsesConfig(BaseAWSLLM, OpenAIResponsesAPIConfig): self, model: str, input: "str | ResponseInputParam", - response_api_optional_request_params: dict, # mutable-ok: signature fixed by the override contract + response_api_optional_request_params: dict, litellm_params: GenericLiteLLMParams, - headers: dict, # mutable-ok: same - ) -> dict: # mutable-ok: same + headers: dict, + ) -> dict: inlined: Final = MappingProxyType({url: self.fetch_image(url) for url in collect_remote_image_urls(input)}) return self._transform_inlined_request( model=model, @@ -333,10 +333,10 @@ class BedrockOpenAIResponsesConfig(BaseAWSLLM, OpenAIResponsesAPIConfig): self, model: str, input: "str | ResponseInputParam", - response_api_optional_request_params: dict, # mutable-ok: signature fixed by the override contract + response_api_optional_request_params: dict, litellm_params: GenericLiteLLMParams, - headers: dict, # mutable-ok: same - ) -> dict: # mutable-ok: same + headers: dict, + ) -> dict: remote_urls: Final = collect_remote_image_urls(input) data_uris: Final = await asyncio.gather(*(self.async_fetch_image(url) for url in remote_urls)) return self._transform_inlined_request( @@ -351,10 +351,10 @@ class BedrockOpenAIResponsesConfig(BaseAWSLLM, OpenAIResponsesAPIConfig): self, model: str, input: "str | ResponseInputParam", - response_api_optional_request_params: dict, # mutable-ok: signature fixed by the override contract + response_api_optional_request_params: dict, litellm_params: GenericLiteLLMParams, - headers: dict, # mutable-ok: same - ) -> dict: # mutable-ok: same + headers: dict, + ) -> dict: normalized_input, rewritten_types = normalize_codex_input_items(input) if rewritten_types: verbose_logger.warning( diff --git a/litellm/llms/bedrock/search/transformation.py b/litellm/llms/bedrock/search/transformation.py index 565fc12aa1e..15d638896f0 100644 --- a/litellm/llms/bedrock/search/transformation.py +++ b/litellm/llms/bedrock/search/transformation.py @@ -168,11 +168,11 @@ class AgentCoreSearchConfig(BaseSearchConfig, BaseAWSLLM): def validate_environment( self, - headers: dict, # mutable-ok: BaseSearchConfig hands providers the mutable request header dict + headers: dict, api_key: str | None = None, api_base: str | None = None, **kwargs: object, # kwargs-ok: BaseSearchConfig.validate_environment forwards provider-specific extras - ) -> dict: # mutable-ok: the handler passes these headers straight to httpx, which wants a dict + ) -> dict: """ Set MCP transport headers. Per the MCP Streamable HTTP transport spec, the client MUST accept both application/json and text/event-stream, and @@ -192,7 +192,7 @@ class AgentCoreSearchConfig(BaseSearchConfig, BaseAWSLLM): def get_complete_url( self, api_base: str | None, - optional_params: dict, # mutable-ok: BaseSearchConfig passes optional params as a dict + optional_params: dict, data: dict | list[dict] | None = None, # mutable-ok: BaseSearchConfig request bodies are JSON dicts **kwargs: object, # kwargs-ok: BaseSearchConfig.get_complete_url forwards provider-specific extras ) -> str: @@ -208,9 +208,9 @@ class AgentCoreSearchConfig(BaseSearchConfig, BaseAWSLLM): def transform_search_request( self, query: str | list[str], # mutable-ok: BaseSearchConfig accepts a list of queries - optional_params: dict, # mutable-ok: BaseSearchConfig passes optional params as a dict + optional_params: dict, **kwargs: object, # kwargs-ok: BaseSearchConfig.transform_search_request forwards provider-specific extras - ) -> dict: # mutable-ok: the JSON-RPC body is serialized as a JSON object + ) -> dict: """ Transform Search request to an MCP tools/call request. @@ -252,12 +252,12 @@ class AgentCoreSearchConfig(BaseSearchConfig, BaseAWSLLM): def sign_request( self, - headers: dict[str, str], # mutable-ok: BaseSearchConfig hands providers the mutable request header dict - optional_params: dict[str, object], # mutable-ok: BaseSearchConfig passes optional params as a dict + headers: dict[str, str], + optional_params: dict[str, object], request_data: dict[str, object] | list[dict[str, object]], # mutable-ok: request bodies are JSON dicts api_base: str, api_key: str | None = None, - ) -> tuple[dict[str, str], bytes | None]: # mutable-ok: BaseSearchConfig.sign_request returns httpx headers + ) -> tuple[dict[str, str], bytes | None]: """ Authenticate the MCP request. @@ -451,7 +451,7 @@ class AgentCoreSearchConfig(BaseSearchConfig, BaseAWSLLM): self, error_message: str, status_code: int, - headers: dict, # mutable-ok: BaseSearchConfig.get_error_class takes the response headers as a dict + headers: dict, ) -> Exception: return BedrockError( status_code=status_code, diff --git a/litellm/llms/bedrock/vector_stores/transformation.py b/litellm/llms/bedrock/vector_stores/transformation.py index ba8ce7e5625..263af85e2f3 100644 --- a/litellm/llms/bedrock/vector_stores/transformation.py +++ b/litellm/llms/bedrock/vector_stores/transformation.py @@ -45,7 +45,7 @@ class BedrockVectorStoreConfig(BaseVectorStoreConfig, BaseAWSLLM): self, error_message: str, status_code: int, - headers: dict[str, object] | httpx.Headers, # mutable-ok: base passes response headers as a dict + headers: dict[str, object] | httpx.Headers, ) -> BedrockError: return BedrockError(status_code=status_code, message=error_message, headers=headers) diff --git a/litellm/llms/bedrock_mantle/passthrough/transformation.py b/litellm/llms/bedrock_mantle/passthrough/transformation.py index e6b831efa57..07ebff436ab 100644 --- a/litellm/llms/bedrock_mantle/passthrough/transformation.py +++ b/litellm/llms/bedrock_mantle/passthrough/transformation.py @@ -55,7 +55,7 @@ class BedrockMantlePassthroughConfig(BedrockPassthroughConfig): model: str, custom_llm_provider: str, httpx_response: Response, - request_data: dict, # mutable-ok: mirrors the inherited BedrockPassthroughConfig signature + request_data: dict, logging_obj: Logging, endpoint: str, ) -> Optional["CostResponseTypes"]: diff --git a/litellm/llms/claude_code/harness/transformation.py b/litellm/llms/claude_code/harness/transformation.py index f75d86c7e63..a3187b909ab 100644 --- a/litellm/llms/claude_code/harness/transformation.py +++ b/litellm/llms/claude_code/harness/transformation.py @@ -164,7 +164,7 @@ def _message_blocks(event: Mapping[str, object]) -> Sequence[object]: message: Final = event.get("message") content: Final = message.get("content") if isinstance(message, Mapping) else None if isinstance(content, str): - return ({"type": "text", "text": content},) # mutable-ok: JSON content block, like the stream's + return ({"type": "text", "text": content},) return content if isinstance(content, list) else () @@ -182,8 +182,7 @@ def _assistant_block_events(block: Mapping[str, Any], state: ClaudeCodeStreamSta id=str(block.get("id", "")), name=normalize_tool_name(native, NATIVE_TO_NORMALIZED), native_name=native, - input=block.get("input") - or {}, # mutable-ok: ToolCall.input is a dict field; empty default for a missing input + input=block.get("input") or {}, builtin=not native.startswith("mcp__"), ), ) @@ -236,7 +235,7 @@ def _record_result(event: Mapping[str, Any], state: ClaudeCodeStreamState) -> Se state.is_error = bool(event.get("is_error", False)) result = event.get("result") state.result_text = result if isinstance(result, str) else None - state.errors = [str(e) for e in event.get("errors") or ()] # mutable-ok: mirrors the JSON errors array + state.errors = [str(e) for e in event.get("errors") or ()] state.structured_output = event.get("structured_output") if event.get("session_id"): state.session_id = str(event["session_id"]) @@ -318,7 +317,7 @@ class ClaudeCodeHarnessConfig(BaseCLIHarnessConfig): } ) return HarnessSessionSetup( - persisted_dirs=[("projects", "claude_code/projects")], # mutable-ok: tests compare to a list + persisted_dirs=[("projects", "claude_code/projects")], skills_dir="skills", env=env, ) @@ -335,7 +334,7 @@ class ClaudeCodeHarnessConfig(BaseCLIHarnessConfig): schema = ctx.output.model_json_schema() if ctx.output is not None else None system_prompt: Final = build_system_prompt(ctx.instructions, schema) disallowed: Final = native_tool_names(ctx.disable_tools, NORMALIZED_TO_NATIVE) - config: Final = dict(options.config) # mutable-ok: json.dumps needs a plain dict + config: Final = dict(options.config) settings: Final = json.dumps(config) if config else None argv: Final = ( CLAUDE_BINARY, diff --git a/litellm/llms/codex/harness/transformation.py b/litellm/llms/codex/harness/transformation.py index 4512e39189e..3b146186c62 100644 --- a/litellm/llms/codex/harness/transformation.py +++ b/litellm/llms/codex/harness/transformation.py @@ -83,7 +83,7 @@ def _tool_input(item: Mapping[str, Any]) -> tuple[str, str, Mapping[str, object] if item_type == "command_execution": return "bash", "command_execution", MappingProxyType({"command": item.get("command", "")}), True if item_type == "file_change": - changes: Final = list(item.get("changes") or ()) # mutable-ok: JSON array, as codex reports it + changes: Final = list(item.get("changes") or ()) return "edit", "apply_patch", MappingProxyType({"changes": changes}), True if item_type == "web_search": return "web_search", "web_search", MappingProxyType({"query": item.get("query", "")}), True @@ -188,7 +188,7 @@ def _config_override(key: object, value: object) -> str: def config_overrides(config: Mapping[str, object]) -> Sequence[str]: """`-c` override strings for CodexOptions.config, rejecting managed keys.""" overrides: Final = (_config_override(key, value) for key, value in config.items()) - return list(overrides) # mutable-ok: public helper; tests compare to a list + return list(overrides) def _flag_pairs(flag: str, values: Sequence[str]) -> tuple[str, ...]: @@ -232,7 +232,7 @@ class CodexHarnessConfig(BaseCLIHarnessConfig): ) return HarnessSessionSetup( files=files, - persisted_dirs=[("sessions", "codex/sessions")], # mutable-ok: tests compare to a list + persisted_dirs=[("sessions", "codex/sessions")], skills_dir="skills", env=MappingProxyType({**options.env, CODEX_TOKEN_ENV: ctx.endpoint.token, "CODEX_HOME": private_dir}), ) diff --git a/litellm/llms/cohere/embed/v1_transformation.py b/litellm/llms/cohere/embed/v1_transformation.py index ad5bfa507e5..18337c04faa 100644 --- a/litellm/llms/cohere/embed/v1_transformation.py +++ b/litellm/llms/cohere/embed/v1_transformation.py @@ -72,7 +72,7 @@ class CohereEmbeddingConfig: self, model: str, input: list[str], # mutable-ok: mirrors override contract - inference_params: dict[str, object], # mutable-ok: mirrors override contract + inference_params: dict[str, object], ) -> CohereEmbeddingRequestWithModel: return self._transform_request(model, input, inference_params) @@ -136,7 +136,7 @@ class CohereEmbeddingConfig: response: httpx.Response, api_key: str | None, logging_obj: LiteLLMLoggingObj, - data: dict[str, object] | CohereEmbeddingRequest, # mutable-ok: mirrors override contract + data: dict[str, object] | CohereEmbeddingRequest, model_response: EmbeddingResponse, model: str, encoding: _SupportsEncode, @@ -202,7 +202,7 @@ class CohereEmbeddingConfig: def populate_embedding_response( self, - response_json: dict[str, object], # mutable-ok: mirrors override contract + response_json: dict[str, object], model_response: EmbeddingResponse, model: str, encoding: _SupportsEncode, diff --git a/litellm/llms/custom_httpx/llm_http_handler.py b/litellm/llms/custom_httpx/llm_http_handler.py index 2b86b9bbe5c..edca129daa8 100644 --- a/litellm/llms/custom_httpx/llm_http_handler.py +++ b/litellm/llms/custom_httpx/llm_http_handler.py @@ -283,26 +283,26 @@ class _MediaUploadKwargs(TypedDict, total=False): class _AsyncFilesEnvironmentValidator(Protocol): async def avalidate_environment( self, - headers: dict, # mutable-ok: mirrors the sync validate_environment contract this overrides + headers: dict, model: str, messages: list, # mutable-ok: mirrors the sync validate_environment contract this overrides - optional_params: dict, # mutable-ok: mirrors the sync validate_environment contract this overrides - litellm_params: dict, # mutable-ok: mirrors the sync validate_environment contract this overrides + optional_params: dict, + litellm_params: dict, api_key: str | None = None, api_base: str | None = None, - ) -> dict: ... # mutable-ok: mirrors the sync validate_environment contract this overrides + ) -> dict: ... async def _avalidate_files_environment( provider_config: BaseFilesConfig | BaseBatchesConfig, *, - headers: dict, # mutable-ok: mirrors the sync validate_environment contract this overrides + headers: dict, model: str, messages: list, # mutable-ok: mirrors the sync validate_environment contract this overrides - optional_params: dict, # mutable-ok: mirrors the sync validate_environment contract this overrides - litellm_params: dict, # mutable-ok: mirrors the sync validate_environment contract this overrides + optional_params: dict, + litellm_params: dict, api_key: str | None, -) -> dict: # mutable-ok: mirrors the sync validate_environment contract this overrides +) -> dict: """Await the provider's async credential hook when it has one (e.g. Anthropic's workload identity token exchange); otherwise offload the sync hook to a worker thread. Either way the caller, an async file handler, never blocks the event loop on it.""" @@ -736,8 +736,8 @@ class BaseLLMHTTPHandler: ) def sign_and_log( - transformed: dict[str, object], # mutable-ok: async_completion takes dict - ) -> tuple[dict[str, object], dict[str, object], bytes | None]: # mutable-ok: async_completion takes dict + transformed: dict[str, object], + ) -> tuple[dict[str, object], dict[str, object], bytes | None]: data: Final = ( { **transformed, @@ -778,8 +778,8 @@ class BaseLLMHTTPHandler: return data, signed[0], signed[1] def dispatch_async( - data: dict[str, object], # mutable-ok: async_completion takes dict - signed_headers: dict[str, object], # mutable-ok: async_completion takes dict + data: dict[str, object], + signed_headers: dict[str, object], signed_json_body: bytes | None, ) -> Coroutine[object, object, ModelResponse | CustomStreamWrapper]: async_client: Final = client if isinstance(client, AsyncHTTPHandler) else None @@ -3569,9 +3569,9 @@ class BaseLLMHTTPHandler: self, *, create_file_data: CreateFileRequest, - litellm_params: dict, # mutable-ok: mirrors the create_file contract this dispatches for + litellm_params: dict, provider_config: BaseFilesConfig, - headers: dict, # mutable-ok: mirrors the create_file contract this dispatches for + headers: dict, api_base: str | None, api_key: str | None, logging_obj: LiteLLMLoggingObj, @@ -4046,9 +4046,9 @@ class BaseLLMHTTPHandler: self, *, create_batch_data: "CreateBatchRequest", - litellm_params: dict, # mutable-ok: mirrors the create_batch contract this dispatches for + litellm_params: dict, provider_config: "BaseBatchesConfig", - headers: dict, # mutable-ok: mirrors the create_batch contract this dispatches for + headers: dict, api_base: str | None, api_key: str | None, logging_obj: "LiteLLMLoggingObj", @@ -4980,8 +4980,8 @@ class BaseLLMHTTPHandler: first_page: httpx.Response, provider_config: BaseFilesConfig, logging_obj: LiteLLMLoggingObj, - litellm_params: dict, # mutable-ok: handed to the files contract, which types it as a dict - headers: dict, # mutable-ok: handed to validate_environment, which types it as a dict + litellm_params: dict, + headers: dict, client: HTTPHandler, timeout: float | httpx.Timeout | None, ) -> Iterator[list[OpenAIFileObject]]: # mutable-ok: each page arrives as the list the files contract returns @@ -5008,8 +5008,8 @@ class BaseLLMHTTPHandler: first_page: httpx.Response, provider_config: BaseFilesConfig, logging_obj: LiteLLMLoggingObj, - litellm_params: dict, # mutable-ok: handed to the files contract, which types it as a dict - headers: dict, # mutable-ok: handed to validate_environment, which types it as a dict + litellm_params: dict, + headers: dict, client: AsyncHTTPHandler, timeout: float | httpx.Timeout | None, ) -> AsyncIterator[list[OpenAIFileObject]]: # mutable-ok: each page arrives as the list the files contract returns @@ -5034,9 +5034,9 @@ class BaseLLMHTTPHandler: def _next_listing_page_headers( self, provider_config: BaseFilesConfig, - headers: dict, # mutable-ok: handed to validate_environment, which types it as a dict - litellm_params: dict, # mutable-ok: handed to the files contract, which types it as a dict - ) -> dict: # mutable-ok: validate_environment returns the header dict the files contract types + headers: dict, + litellm_params: dict, + ) -> dict: return provider_config.validate_environment( api_key=litellm_params.get("api_key"), headers=headers, @@ -5050,9 +5050,9 @@ class BaseLLMHTTPHandler: self, latest_page: httpx.Response, provider_config: BaseFilesConfig, - litellm_params: dict, # mutable-ok: handed to the files contract, which types it as a dict + litellm_params: dict, listed_count: int, - ) -> tuple[str, dict[str, str]] | None: # mutable-ok: the base files contract returns the query as a dict + ) -> tuple[str, dict[str, str]] | None: if listed_count >= MAX_FILE_LIST_LIMIT: return None return provider_config.transform_list_files_next_request( diff --git a/litellm/llms/deepagents/harness/sandbox_backend.py b/litellm/llms/deepagents/harness/sandbox_backend.py index d39690ccdd1..b5f514ed51f 100644 --- a/litellm/llms/deepagents/harness/sandbox_backend.py +++ b/litellm/llms/deepagents/harness/sandbox_backend.py @@ -314,7 +314,7 @@ class SandboxBackend(SandboxBackendProtocol): # pyright: ignore[reportUntypedBa except (InvalidGlobPatternError, ValueError, SandboxError) as e: return GlobResult(error=str(e), matches=None) if done.exit_code != 0 and not done.stdout: - return GlobResult(matches=[]) # mutable-ok: deepagents GlobResult.matches is typed list[FileInfo] + return GlobResult(matches=[]) matches = sorted( ( FileInfo(path=self.to_virtual(real), is_dir=False) @@ -353,9 +353,7 @@ class SandboxBackend(SandboxBackendProtocol): # pyright: ignore[reportUntypedBa return GrepResult(error=f"Path '{path or '/'}': {e}") if done.exit_code not in (0, 1) and not done.stdout: return GrepResult(error=f"Path '{path or '/'}': {done.stderr.strip() or 'grep failed'}") - matches = list( # mutable-ok: GrepResult.matches is list[GrepMatch] - self._grep_matches(done.stdout, root, include) - ) + matches = list(self._grep_matches(done.stdout, root, include)) if max_count is not None and len(matches) > max_count: return GrepResult(matches=matches[:max_count], truncated=True) return GrepResult(matches=matches) @@ -385,9 +383,7 @@ class SandboxBackend(SandboxBackendProtocol): # pyright: ignore[reportUntypedBa self, files: list[tuple[str, bytes]], # mutable-ok: signature fixed by deepagents BackendProtocol ) -> list[FileUploadResponse]: # mutable-ok: return type fixed by deepagents BackendProtocol - return [ # mutable-ok: BackendProtocol returns a list - await self._upload_one(path, data) for path, data in files - ] + return [await self._upload_one(path, data) for path, data in files] def upload_files( self, @@ -407,7 +403,7 @@ class SandboxBackend(SandboxBackendProtocol): # pyright: ignore[reportUntypedBa self, paths: list[str], # mutable-ok: signature fixed by deepagents BackendProtocol ) -> list[FileDownloadResponse]: # mutable-ok: return type fixed by deepagents BackendProtocol - return [await self._download_one(path) for path in paths] # mutable-ok: BackendProtocol returns a list + return [await self._download_one(path) for path in paths] def download_files( self, @@ -464,11 +460,7 @@ class ToolFilterMiddleware(AgentMiddleware): # pyright: ignore[reportUntypedBas return _FILTER_MIDDLEWARE_NAME def _filtered(self, request: ModelRequest) -> ModelRequest: - return request.override( - tools=[ # mutable-ok: ModelRequest.tools is a list - t for t in request.tools if _tool_name(t) not in self._blocked - ] - ) + return request.override(tools=[t for t in request.tools if _tool_name(t) not in self._blocked]) def wrap_model_call( self, request: ModelRequest, handler: Callable[[ModelRequest], ModelResponse] diff --git a/litellm/llms/deepagents/harness/transformation.py b/litellm/llms/deepagents/harness/transformation.py index 1f8728230bc..fe175021c70 100644 --- a/litellm/llms/deepagents/harness/transformation.py +++ b/litellm/llms/deepagents/harness/transformation.py @@ -73,18 +73,18 @@ _APPROVAL_DECISIONS: Final = ("approve", "reject") def chat_model_kwargs( ctx: SessionContext, -) -> dict[str, Any]: # mutable-ok: ChatLiteLLM constructor kwargs, splatted as **kwargs +) -> dict[str, Any]: """ChatLiteLLM constructor kwargs for gateway or SDK mode.""" if not ctx.model: raise ValueError("Harness.DEEPAGENTS needs model=") if ctx.gateway is not None: - return { # mutable-ok: ChatLiteLLM constructor kwargs, splatted as **kwargs + return { "model": f"litellm_proxy/{ctx.model}", "api_base": ctx.gateway.api_base, "api_key": ctx.gateway.api_key, "extra_headers": gateway_headers(ctx), } - return {"model": ctx.model, "api_key": ctx.api_key, "api_base": ctx.api_base} # mutable-ok: ChatLiteLLM kwargs + return {"model": ctx.model, "api_key": ctx.api_key, "api_base": ctx.api_base} def native_tool_name(name: str) -> str: @@ -105,16 +105,11 @@ def blocked_tools(permissions: str, disable_tools: Sequence[str]) -> frozenset[s return disabled -def interrupt_config( - permissions: str, blocked: frozenset[str] -) -> dict[str, Any] | None: # mutable-ok: deepagents create_deep_agent(interrupt_on=) takes a dict +def interrupt_config(permissions: str, blocked: frozenset[str]) -> dict[str, Any] | None: """interrupt_on for permissions='ask': approve/reject every mutating built-in.""" if permissions != "ask": return None - return { # mutable-ok: deepagents interrupt_on config (dict of InterruptOnConfig with list allowed_decisions) - name: {"allowed_decisions": list(_APPROVAL_DECISIONS)} # mutable-ok: deepagents InterruptOnConfig shape - for name in sorted(APPROVAL_TOOLS - blocked) - } + return {name: {"allowed_decisions": list(_APPROVAL_DECISIONS)} for name in sorted(APPROVAL_TOOLS - blocked)} def recursion_limit(ctx: SessionContext) -> int: @@ -162,12 +157,12 @@ def stream_events( ) -> list[Event]: # mutable-ok: returns a list; existing callers/tests compare it to list literals """Text / Reasoning deltas for one streamed message chunk.""" if getattr(message, "type", None) not in ("AIMessageChunk", "ai"): - return [] # mutable-ok: returns a list; existing callers/tests compare it to list literals + return [] reasoning = reasoning_text(message) text = content_text(getattr(message, "content", "")) reasoning_events: tuple[Event, ...] = (Reasoning(delta=reasoning),) if reasoning else () text_events: tuple[Event, ...] = (Text(delta=text),) if text else () - return [ # mutable-ok: returns a list; existing callers/tests compare it to list literals + return [ *reasoning_events, *text_events, ] @@ -180,7 +175,7 @@ def tool_call_event(call: Mapping[str, object]) -> ToolCall: id=str(call.get("id") or ""), name=normalized_tool_name(native), native_name=native, - input=dict(args) if isinstance(args, Mapping) else {"args": args}, # mutable-ok: ToolCall.input is a dict + input=dict(args) if isinstance(args, Mapping) else {"args": args}, builtin=native in BUILTIN_TOOLS, ) @@ -199,8 +194,8 @@ def update_events( ) -> list[Event]: # mutable-ok: returns a list; existing callers/tests compare it to list literals """ToolCall / ToolResult events from one `updates` stream chunk (node -> state delta).""" if not isinstance(update, Mapping): - return [] # mutable-ok: returns a list; existing callers/tests compare it to list literals - return list( # mutable-ok: returns a list; existing callers/tests compare it to list literals + return [] + return list( itertools.chain.from_iterable(_message_events(message, skip_tools) for message in _node_messages(update)) ) @@ -225,10 +220,10 @@ def interrupts_in( update: object, ) -> list[Any]: # mutable-ok: returns a list; existing callers/tests compare it to list literals if not isinstance(update, Mapping): - return [] # mutable-ok: returns a list; existing callers/tests compare it to list literals + return [] found = update.get("__interrupt__") items = tuple(found) if isinstance(found, (list, tuple)) else () - return list(items) # mutable-ok: list return; callers/tests compare to lists + return list(items) def final_ai_text(messages: Sequence[object]) -> str: @@ -254,16 +249,16 @@ def approval_requests( ) -> list[Mapping[str, Any]]: # mutable-ok: returns a list; existing callers/tests compare it to list literals """action_requests of a HumanInTheLoopMiddleware interrupt payload.""" if not isinstance(interrupt_value, Mapping): - return [] # mutable-ok: returns a list; existing callers/tests compare it to list literals + return [] requests = interrupt_value.get("action_requests") kept = tuple(r for r in requests if isinstance(r, Mapping)) if isinstance(requests, list) else () - return list(kept) # mutable-ok: list return; callers/tests compare to lists + return list(kept) -def decision(allowed: bool, reason: str) -> dict[str, Any]: # mutable-ok: LangGraph resume payload (HITL decision dict) +def decision(allowed: bool, reason: str) -> dict[str, Any]: if allowed: - return {"type": "approve"} # mutable-ok: LangGraph resume payload (HITL decision dict) - return { # mutable-ok: LangGraph HITL decision + return {"type": "approve"} + return { "type": "reject", "message": reason or "The user denied this tool call.", } diff --git a/litellm/llms/edenai/audio_transcription/transformation.py b/litellm/llms/edenai/audio_transcription/transformation.py index ee574a4f406..d6598a74968 100644 --- a/litellm/llms/edenai/audio_transcription/transformation.py +++ b/litellm/llms/edenai/audio_transcription/transformation.py @@ -22,7 +22,7 @@ from litellm.utils import convert_to_model_response_object from ..common_utils import EdenAIException, authorized_headers, endpoint_url, reported_cost -def _form_fields(model: str, optional_params: Mapping[str, object]) -> dict[str, object]: # mutable-ok: httpx form data +def _form_fields(model: str, optional_params: Mapping[str, object]) -> dict[str, object]: """LiteLLM parks non-OpenAI params, `model` included, under `extra_body` for the OpenAI SDK; a multipart body carries them as top-level text fields instead.""" extras: Final = optional_params.get("extra_body") @@ -41,30 +41,30 @@ class EdenAIAudioTranscriptionConfig(OpenAIWhisperAudioTranscriptionConfig): api_base: str | None, api_key: str | None, model: str, - optional_params: dict[str, object], # mutable-ok: inherited contract - litellm_params: dict[str, object], # mutable-ok: inherited contract + optional_params: dict[str, object], + litellm_params: dict[str, object], stream: bool | None = None, ) -> str: return endpoint_url(api_base, "audio/transcriptions") def validate_environment( self, - headers: dict[str, object], # mutable-ok: inherited contract + headers: dict[str, object], model: str, messages: list[AllMessageValues], # mutable-ok: inherited contract - optional_params: dict[str, object], # mutable-ok: inherited contract - litellm_params: dict[str, object], # mutable-ok: inherited contract + optional_params: dict[str, object], + litellm_params: dict[str, object], api_key: str | None = None, api_base: str | None = None, - ) -> dict[str, object]: # mutable-ok: inherited contract + ) -> dict[str, object]: return authorized_headers(headers, api_key, model) def transform_audio_transcription_request( self, model: str, audio_file: FileTypes, - optional_params: dict[str, object], # mutable-ok: inherited contract - litellm_params: dict[str, object], # mutable-ok: inherited contract + optional_params: dict[str, object], + litellm_params: dict[str, object], ) -> AudioTranscriptionRequestData: """Eden reports `duration` and `cost` on every body, so the Whisper default of `verbose_json`, which the gpt-4o-transcribe models reject, is not needed for cost tracking.""" @@ -86,6 +86,6 @@ class EdenAIAudioTranscriptionConfig(OpenAIWhisperAudioTranscriptionConfig): self, error_message: str, status_code: int, - headers: dict[str, object] | httpx.Headers, # mutable-ok: inherited contract + headers: dict[str, object] | httpx.Headers, ) -> BaseLLMException: return EdenAIException(message=error_message, status_code=status_code, headers=headers) diff --git a/litellm/llms/edenai/chat/transformation.py b/litellm/llms/edenai/chat/transformation.py index ae5da5d1cfd..e8f67bf2b08 100644 --- a/litellm/llms/edenai/chat/transformation.py +++ b/litellm/llms/edenai/chat/transformation.py @@ -46,7 +46,7 @@ def _stream_options_with_usage(request: Mapping[str, object]) -> Mapping[str, ob class EdenAIChatCompletionStreamingHandler(OpenAIChatCompletionStreamingHandler): - def chunk_parser(self, chunk: dict[str, object]) -> ModelResponseStream: # mutable-ok: inherited contract + def chunk_parser(self, chunk: dict[str, object]) -> ModelResponseStream: parsed: Final = super().chunk_parser(chunk) cost: Final = reported_cost(chunk) usage: Final[object] = getattr(parsed, "usage", None) @@ -76,11 +76,11 @@ class EdenAIChatConfig(OpenAIGPTConfig): self, model: str, messages: list[AllMessageValues], # mutable-ok: inherited contract - optional_params: dict[str, object], # mutable-ok: inherited contract - litellm_params: dict[str, object], # mutable-ok: inherited contract - headers: dict[str, object], # mutable-ok: inherited contract - ) -> dict[str, object]: # mutable-ok: inherited contract - request: Final[dict[str, object]] = super().transform_request( # mutable-ok: inherited contract + optional_params: dict[str, object], + litellm_params: dict[str, object], + headers: dict[str, object], + ) -> dict[str, object]: + request: Final[dict[str, object]] = super().transform_request( model, messages, optional_params, litellm_params, headers ) if not request.get("stream"): @@ -93,10 +93,10 @@ class EdenAIChatConfig(OpenAIGPTConfig): raw_response: httpx.Response, model_response: ModelResponse, logging_obj: "LiteLLMLoggingObj", - request_data: dict[str, object], # mutable-ok: inherited contract + request_data: dict[str, object], messages: list[AllMessageValues], # mutable-ok: inherited contract - optional_params: dict[str, object], # mutable-ok: inherited contract - litellm_params: dict[str, object], # mutable-ok: inherited contract + optional_params: dict[str, object], + litellm_params: dict[str, object], encoding: "Encoding | None", api_key: str | None = None, json_mode: bool | None = None, @@ -121,7 +121,7 @@ class EdenAIChatConfig(OpenAIGPTConfig): self, error_message: str, status_code: int, - headers: dict[str, object] | httpx.Headers, # mutable-ok: inherited contract + headers: dict[str, object] | httpx.Headers, ) -> BaseLLMException: return EdenAIException(message=error_message, status_code=status_code, headers=headers) diff --git a/litellm/llms/edenai/common_utils.py b/litellm/llms/edenai/common_utils.py index 3216f798fbe..edd189ea080 100644 --- a/litellm/llms/edenai/common_utils.py +++ b/litellm/llms/edenai/common_utils.py @@ -59,15 +59,11 @@ def reported_cost(payload: object) -> float | None: return extras.cost -def authorized_headers( - headers: Mapping[str, object], api_key: str | None, model: str -) -> dict[str, object]: # mutable-ok: header contract +def authorized_headers(headers: Mapping[str, object], api_key: str | None, model: str) -> dict[str, object]: return {**headers, "Authorization": f"Bearer {require_api_key(api_key, model)}"} -def json_headers( - headers: Mapping[str, object], api_key: str | None, model: str -) -> dict[str, object]: # mutable-ok: header contract +def json_headers(headers: Mapping[str, object], api_key: str | None, model: str) -> dict[str, object]: """The shared HTTP handler sends some JSON bodies as raw content, so the type must be set here.""" authorized: Final = authorized_headers(headers, api_key, model) return {**authorized, "Content-Type": "application/json"} diff --git a/litellm/llms/edenai/embedding/transformation.py b/litellm/llms/edenai/embedding/transformation.py index c79a6839434..1b613e2d1e0 100644 --- a/litellm/llms/edenai/embedding/transformation.py +++ b/litellm/llms/edenai/embedding/transformation.py @@ -30,23 +30,23 @@ class EdenAIEmbeddingConfig(BaseEmbeddingConfig): def map_openai_params( self, - non_default_params: dict[str, object], # mutable-ok: inherited contract - optional_params: dict[str, object], # mutable-ok: inherited contract + non_default_params: dict[str, object], + optional_params: dict[str, object], model: str, drop_params: bool, - ) -> dict[str, object]: # mutable-ok: inherited contract + ) -> dict[str, object]: return {**optional_params, **pick(non_default_params, _SUPPORTED_PARAMS)} def validate_environment( self, - headers: dict[str, object], # mutable-ok: inherited contract + headers: dict[str, object], model: str, messages: list[AllMessageValues], # mutable-ok: inherited contract - optional_params: dict[str, object], # mutable-ok: inherited contract - litellm_params: dict[str, object], # mutable-ok: inherited contract + optional_params: dict[str, object], + litellm_params: dict[str, object], api_key: str | None = None, api_base: str | None = None, - ) -> dict[str, object]: # mutable-ok: inherited contract + ) -> dict[str, object]: return json_headers(headers, api_key, model) def get_complete_url( @@ -54,8 +54,8 @@ class EdenAIEmbeddingConfig(BaseEmbeddingConfig): api_base: str | None, api_key: str | None, model: str, - optional_params: dict[str, object], # mutable-ok: inherited contract - litellm_params: dict[str, object], # mutable-ok: inherited contract + optional_params: dict[str, object], + litellm_params: dict[str, object], stream: bool | None = None, ) -> str: return endpoint_url(api_base, "embeddings") @@ -64,9 +64,9 @@ class EdenAIEmbeddingConfig(BaseEmbeddingConfig): self, model: str, input: AllEmbeddingInputValues, - optional_params: dict[str, object], # mutable-ok: inherited contract - headers: dict[str, object], # mutable-ok: inherited contract - ) -> dict[str, object]: # mutable-ok: inherited contract + optional_params: dict[str, object], + headers: dict[str, object], + ) -> dict[str, object]: return {"model": model, "input": input, **optional_params} def transform_embedding_response( @@ -76,9 +76,9 @@ class EdenAIEmbeddingConfig(BaseEmbeddingConfig): model_response: EmbeddingResponse, logging_obj: "LiteLLMLoggingObj", api_key: str | None, - request_data: dict[str, object], # mutable-ok: inherited contract - optional_params: dict[str, object], # mutable-ok: inherited contract - litellm_params: dict[str, object], # mutable-ok: inherited contract + request_data: dict[str, object], + optional_params: dict[str, object], + litellm_params: dict[str, object], ) -> EmbeddingResponse: body: Final = raw_response.json() logging_obj.post_call(original_response=body) @@ -92,6 +92,6 @@ class EdenAIEmbeddingConfig(BaseEmbeddingConfig): self, error_message: str, status_code: int, - headers: dict[str, object] | httpx.Headers, # mutable-ok: inherited contract + headers: dict[str, object] | httpx.Headers, ) -> BaseLLMException: return EdenAIException(message=error_message, status_code=status_code, headers=headers) diff --git a/litellm/llms/edenai/image_generation/transformation.py b/litellm/llms/edenai/image_generation/transformation.py index e2a0b8684f6..001e0a07159 100644 --- a/litellm/llms/edenai/image_generation/transformation.py +++ b/litellm/llms/edenai/image_generation/transformation.py @@ -44,11 +44,11 @@ class EdenAIImageGenerationConfig(BaseImageGenerationConfig): def map_openai_params( self, - non_default_params: dict[str, object], # mutable-ok: inherited contract - optional_params: dict[str, object], # mutable-ok: inherited contract + non_default_params: dict[str, object], + optional_params: dict[str, object], model: str, drop_params: bool, - ) -> dict[str, object]: # mutable-ok: inherited contract + ) -> dict[str, object]: return {**optional_params, **pick(non_default_params, _SUPPORTED_PARAMS)} def get_complete_url( @@ -56,32 +56,32 @@ class EdenAIImageGenerationConfig(BaseImageGenerationConfig): api_base: str | None, api_key: str | None, model: str, - optional_params: dict[str, object], # mutable-ok: inherited contract - litellm_params: dict[str, object], # mutable-ok: inherited contract + optional_params: dict[str, object], + litellm_params: dict[str, object], stream: bool | None = None, ) -> str: return endpoint_url(api_base, "images/generations") def validate_environment( self, - headers: dict[str, object], # mutable-ok: inherited contract + headers: dict[str, object], model: str, messages: list[AllMessageValues], # mutable-ok: inherited contract - optional_params: dict[str, object], # mutable-ok: inherited contract - litellm_params: dict[str, object], # mutable-ok: inherited contract + optional_params: dict[str, object], + litellm_params: dict[str, object], api_key: str | None = None, api_base: str | None = None, - ) -> dict[str, object]: # mutable-ok: inherited contract + ) -> dict[str, object]: return json_headers(headers, api_key, model) def transform_image_generation_request( self, model: str, prompt: str, - optional_params: dict[str, object], # mutable-ok: inherited contract - litellm_params: dict[str, object], # mutable-ok: inherited contract - headers: dict[str, object], # mutable-ok: inherited contract - ) -> dict[str, object]: # mutable-ok: inherited contract + optional_params: dict[str, object], + litellm_params: dict[str, object], + headers: dict[str, object], + ) -> dict[str, object]: return {"model": model, "prompt": prompt, **optional_params} def transform_image_generation_response( @@ -90,9 +90,9 @@ class EdenAIImageGenerationConfig(BaseImageGenerationConfig): raw_response: httpx.Response, model_response: ImageResponse, logging_obj: "LiteLLMLoggingObj", - request_data: dict[str, object], # mutable-ok: inherited contract - optional_params: dict[str, object], # mutable-ok: inherited contract - litellm_params: dict[str, object], # mutable-ok: inherited contract + request_data: dict[str, object], + optional_params: dict[str, object], + litellm_params: dict[str, object], encoding: "Encoding | None", api_key: str | None = None, json_mode: bool | None = None, @@ -109,6 +109,6 @@ class EdenAIImageGenerationConfig(BaseImageGenerationConfig): self, error_message: str, status_code: int, - headers: dict[str, object] | httpx.Headers, # mutable-ok: inherited contract + headers: dict[str, object] | httpx.Headers, ) -> BaseLLMException: return EdenAIException(message=error_message, status_code=status_code, headers=headers) diff --git a/litellm/llms/edenai/messages/transformation.py b/litellm/llms/edenai/messages/transformation.py index fbb3ae0c05a..a365b442328 100644 --- a/litellm/llms/edenai/messages/transformation.py +++ b/litellm/llms/edenai/messages/transformation.py @@ -24,7 +24,7 @@ from ..common_utils import EDENAI_API_BASE, EdenAIException, reported_cost, requ if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj -_EDENAI_PROVIDER_SPEC: Final[dict[str, str]] = { # mutable-ok: SimpleProviderConfig takes a plain dict +_EDENAI_PROVIDER_SPEC: Final[dict[str, str]] = { "base_url": EDENAI_API_BASE, "api_key_env": "EDENAI_API_KEY", "api_base_env": "EDENAI_API_BASE", @@ -38,14 +38,14 @@ class EdenAIAnthropicMessagesConfig(JSONProviderAnthropicMessagesConfig): def validate_anthropic_messages_environment( self, - headers: dict[str, str], # mutable-ok: inherited contract + headers: dict[str, str], model: str, messages: list[object], # mutable-ok: inherited contract - optional_params: dict[str, object], # mutable-ok: inherited contract - litellm_params: dict[str, object], # mutable-ok: inherited contract + optional_params: dict[str, object], + litellm_params: dict[str, object], api_key: str | None = None, api_base: str | None = None, - ) -> tuple[dict[str, str], str | None]: # mutable-ok: inherited contract + ) -> tuple[dict[str, str], str | None]: return super().validate_anthropic_messages_environment( headers=headers, model=model, @@ -74,6 +74,6 @@ class EdenAIAnthropicMessagesConfig(JSONProviderAnthropicMessagesConfig): self, error_message: str, status_code: int, - headers: dict[str, object] | httpx.Headers, # mutable-ok: inherited contract + headers: dict[str, object] | httpx.Headers, ) -> BaseLLMException: return EdenAIException(message=error_message, status_code=status_code, headers=headers) diff --git a/litellm/llms/edenai/responses/transformation.py b/litellm/llms/edenai/responses/transformation.py index 3274e70746c..30bbf6bcecf 100644 --- a/litellm/llms/edenai/responses/transformation.py +++ b/litellm/llms/edenai/responses/transformation.py @@ -33,16 +33,16 @@ class EdenAIResponsesAPIConfig(OpenAIResponsesAPIConfig): def validate_environment( self, - headers: dict[str, object], # mutable-ok: inherited contract + headers: dict[str, object], model: str, litellm_params: GenericLiteLLMParams | None, - ) -> dict[str, object]: # mutable-ok: inherited contract + ) -> dict[str, object]: return authorized_headers(headers, litellm_params.api_key if litellm_params else None, model) def get_complete_url( self, api_base: str | None, - litellm_params: dict[str, object], # mutable-ok: inherited contract + litellm_params: dict[str, object], ) -> str: return super().get_complete_url(api_base=resolve_api_base(api_base), litellm_params=litellm_params) @@ -62,7 +62,7 @@ class EdenAIResponsesAPIConfig(OpenAIResponsesAPIConfig): self, error_message: str, status_code: int, - headers: dict[str, object] | httpx.Headers, # mutable-ok: inherited contract + headers: dict[str, object] | httpx.Headers, ) -> BaseLLMException: return EdenAIException(message=error_message, status_code=status_code, headers=headers) diff --git a/litellm/llms/edenai/text_to_speech/transformation.py b/litellm/llms/edenai/text_to_speech/transformation.py index 8d503ffa72f..4aaa0eb7d84 100644 --- a/litellm/llms/edenai/text_to_speech/transformation.py +++ b/litellm/llms/edenai/text_to_speech/transformation.py @@ -28,27 +28,27 @@ class EdenAITextToSpeechConfig(BaseTextToSpeechConfig): def map_openai_params( self, model: str, - optional_params: dict[str, object], # mutable-ok: inherited contract - voice: str | dict[str, object] | None = None, # mutable-ok: inherited contract + optional_params: dict[str, object], + voice: str | dict[str, object] | None = None, drop_params: bool = False, - kwargs: dict[str, object] | None = None, # mutable-ok: inherited contract - ) -> tuple[str | None, dict[str, object]]: # mutable-ok: inherited contract + kwargs: dict[str, object] | None = None, + ) -> tuple[str | None, dict[str, object]]: return (voice if isinstance(voice, str) else None), optional_params def validate_environment( self, - headers: dict[str, object], # mutable-ok: inherited contract + headers: dict[str, object], model: str, api_key: str | None = None, api_base: str | None = None, - ) -> dict[str, object]: # mutable-ok: inherited contract + ) -> dict[str, object]: return json_headers(headers, api_key, model) def get_complete_url( self, model: str, api_base: str | None, - litellm_params: dict[str, object], # mutable-ok: inherited contract + litellm_params: dict[str, object], ) -> str: return endpoint_url(api_base, "audio/speech") @@ -57,9 +57,9 @@ class EdenAITextToSpeechConfig(BaseTextToSpeechConfig): model: str, input: str, voice: str | None, - optional_params: dict[str, object], # mutable-ok: inherited contract - litellm_params: dict[str, object], # mutable-ok: inherited contract - headers: dict[str, object], # mutable-ok: inherited contract + optional_params: dict[str, object], + litellm_params: dict[str, object], + headers: dict[str, object], ) -> TextToSpeechRequestData: fields: Final = (("model", model), ("input", input), ("voice", voice), *optional_params.items()) return TextToSpeechRequestData(dict_body={key: value for key, value in fields if value is not None}) @@ -78,6 +78,6 @@ class EdenAITextToSpeechConfig(BaseTextToSpeechConfig): self, error_message: str, status_code: int, - headers: dict[str, object] | httpx.Headers, # mutable-ok: inherited contract + headers: dict[str, object] | httpx.Headers, ) -> BaseLLMException: return EdenAIException(message=error_message, status_code=status_code, headers=headers) diff --git a/litellm/llms/edenai/videos/transformation.py b/litellm/llms/edenai/videos/transformation.py index cd3e1a7d2d1..b869bfd430a 100644 --- a/litellm/llms/edenai/videos/transformation.py +++ b/litellm/llms/edenai/videos/transformation.py @@ -24,9 +24,7 @@ if TYPE_CHECKING: from litellm.llms.custom_httpx.http_handler import HTTPHandler -def _usage_with_reported_cost( - usage: Mapping[str, object] | None, body: bytes -) -> dict[str, object]: # mutable-ok: VideoObject.usage is a plain dict field +def _usage_with_reported_cost(usage: Mapping[str, object] | None, body: bytes) -> dict[str, object]: cost: Final = reported_cost(body) return { key: value @@ -38,18 +36,18 @@ def _usage_with_reported_cost( class EdenAIVideoConfig(OpenAIVideoConfig): def validate_environment( self, - headers: dict[str, object], # mutable-ok: inherited contract + headers: dict[str, object], model: str, api_key: str | None = None, litellm_params: GenericLiteLLMParams | None = None, - ) -> dict[str, object]: # mutable-ok: inherited contract + ) -> dict[str, object]: return authorized_headers(headers, api_key or (litellm_params.api_key if litellm_params else None), model) def get_complete_url( self, model: str, api_base: str | None, - litellm_params: dict[str, object], # mutable-ok: inherited contract + litellm_params: dict[str, object], ) -> str: return endpoint_url(api_base, "videos") @@ -61,10 +59,10 @@ class EdenAIVideoConfig(OpenAIVideoConfig): model: str, prompt: str, api_base: str, - video_create_optional_request_params: dict[str, object], # mutable-ok: inherited contract + video_create_optional_request_params: dict[str, object], litellm_params: GenericLiteLLMParams, - headers: dict[str, object], # mutable-ok: inherited contract - ) -> tuple[dict[str, object], RequestFiles, str]: # mutable-ok: inherited contract + headers: dict[str, object], + ) -> tuple[dict[str, object], RequestFiles, str]: """A reference image is a multipart file part, or a JSON `{"file_id"}` / `{"image_url"}` object.""" reference: Final = video_create_optional_request_params.get("input_reference") if not isinstance(reference, Mapping): @@ -94,7 +92,7 @@ class EdenAIVideoConfig(OpenAIVideoConfig): raw_response: httpx.Response, logging_obj: "LiteLLMLoggingObj", custom_llm_provider: str | None = None, - request_data: dict[str, object] | None = None, # mutable-ok: inherited contract + request_data: dict[str, object] | None = None, ) -> VideoObject: video: Final = super().transform_video_create_response( model=model, @@ -133,7 +131,7 @@ class EdenAIVideoConfig(OpenAIVideoConfig): raw_response: httpx.Response, logging_obj: "LiteLLMLoggingObj", custom_llm_provider: str | None = None, - ) -> dict[str, str]: # mutable-ok: inherited contract + ) -> dict[str, str]: raw_response.raise_for_status() # the shared GET helpers return error bodies instead of raising return super().transform_video_list_response( raw_response=raw_response, logging_obj=logging_obj, custom_llm_provider=custom_llm_provider @@ -143,6 +141,6 @@ class EdenAIVideoConfig(OpenAIVideoConfig): self, error_message: str, status_code: int, - headers: dict[str, object] | httpx.Headers, # mutable-ok: inherited contract + headers: dict[str, object] | httpx.Headers, ) -> BaseLLMException: return EdenAIException(message=error_message, status_code=status_code, headers=headers) diff --git a/litellm/llms/fal_ai/chat/transformation.py b/litellm/llms/fal_ai/chat/transformation.py index 115b1f840de..a4801b8c082 100644 --- a/litellm/llms/fal_ai/chat/transformation.py +++ b/litellm/llms/fal_ai/chat/transformation.py @@ -55,7 +55,7 @@ class FalAIError(BaseLLMException): self, status_code: int, message: str, - headers: dict | httpx.Headers | None = None, # mutable-ok: BaseLLMException header contract + headers: dict | httpx.Headers | None = None, ) -> None: super().__init__(status_code=status_code, message=message, headers=headers) @@ -134,11 +134,11 @@ class FalAIChatConfig(BaseConfig): def map_openai_params( self, - non_default_params: dict, # mutable-ok: inherited contract - optional_params: dict, # mutable-ok: inherited contract + non_default_params: dict, + optional_params: dict, model: str, drop_params: bool, - ) -> dict: # mutable-ok: inherited contract returns a dict + ) -> dict: mapped: Final = { translated[0]: translated[1] for param, value in non_default_params.items() @@ -148,14 +148,14 @@ class FalAIChatConfig(BaseConfig): def validate_environment( self, - headers: dict, # mutable-ok: inherited contract + headers: dict, model: str, messages: list[AllMessageValues], # mutable-ok: inherited contract - optional_params: dict, # mutable-ok: inherited contract - litellm_params: dict, # mutable-ok: inherited contract + optional_params: dict, + litellm_params: dict, api_key: str | None = None, api_base: str | None = None, - ) -> dict: # mutable-ok: inherited contract returns a dict + ) -> dict: final_api_key: Final = self.get_api_key(api_key) if not final_api_key: raise ValueError("FAL_AI_API_KEY is not set") @@ -170,8 +170,8 @@ class FalAIChatConfig(BaseConfig): api_base: str | None, api_key: str | None, model: str, - optional_params: dict, # mutable-ok: inherited contract - litellm_params: dict, # mutable-ok: inherited contract + optional_params: dict, + litellm_params: dict, stream: bool | None = None, ) -> str: return f"{self.get_api_base(api_base)}/{model.removeprefix(PROVIDER_PREFIX)}" @@ -180,10 +180,10 @@ class FalAIChatConfig(BaseConfig): self, model: str, messages: list[AllMessageValues], # mutable-ok: inherited contract - optional_params: dict, # mutable-ok: inherited contract - litellm_params: dict, # mutable-ok: inherited contract - headers: dict, # mutable-ok: inherited contract - ) -> dict: # mutable-ok: inherited contract returns a dict + optional_params: dict, + litellm_params: dict, + headers: dict, + ) -> dict: if optional_params.get("stream"): raise FalAIError(status_code=400, message="fal_ai chat completions do not support streaming") prompt, image_url = _prompt_and_image(messages) @@ -199,10 +199,10 @@ class FalAIChatConfig(BaseConfig): raw_response: httpx.Response, model_response: ModelResponse, logging_obj: "LiteLLMLoggingObj", - request_data: dict, # mutable-ok: inherited contract + request_data: dict, messages: list[AllMessageValues], # mutable-ok: inherited contract - optional_params: dict, # mutable-ok: inherited contract - litellm_params: dict, # mutable-ok: inherited contract + optional_params: dict, + litellm_params: dict, encoding: "tiktoken.Encoding | None", api_key: str | None = None, json_mode: bool | None = None, @@ -238,6 +238,6 @@ class FalAIChatConfig(BaseConfig): self, error_message: str, status_code: int, - headers: dict | httpx.Headers, # mutable-ok: inherited contract + headers: dict | httpx.Headers, ) -> BaseLLMException: return FalAIError(status_code=status_code, message=error_message, headers=headers) diff --git a/litellm/llms/fal_ai/image_edit/flux_lora_depth_transformation.py b/litellm/llms/fal_ai/image_edit/flux_lora_depth_transformation.py index 6d58caeb384..0b53bf302c6 100644 --- a/litellm/llms/fal_ai/image_edit/flux_lora_depth_transformation.py +++ b/litellm/llms/fal_ai/image_edit/flux_lora_depth_transformation.py @@ -32,7 +32,7 @@ class FalAIFluxLoraDepthEditConfig(FalAIImageEditConfig): image_edit_optional_params: ImageEditOptionalRequestParams, model: str, drop_params: bool, - ) -> dict: # mutable-ok: base class contract returns a dict + ) -> dict: return { PARAM_TRANSLATION.get(key, key): self._translate_value(key, value, model) for key, value in image_edit_optional_params.items() @@ -43,7 +43,7 @@ class FalAIFluxLoraDepthEditConfig(FalAIImageEditConfig): self, model: str, api_base: str | None, - litellm_params: dict, # mutable-ok: base class contract + litellm_params: dict, ) -> str: base_url: Final = (api_base or get_secret_str("FAL_AI_API_BASE") or DEFAULT_BASE_URL).rstrip("/") return f"{base_url}/{FLUX_LORA_DEPTH_ENDPOINT}" @@ -53,10 +53,10 @@ class FalAIFluxLoraDepthEditConfig(FalAIImageEditConfig): model: str, prompt: str | None, image: FileTypes | None, - image_edit_optional_request_params: dict, # mutable-ok: base class contract + image_edit_optional_request_params: dict, litellm_params: GenericLiteLLMParams, - headers: dict, # mutable-ok: base class contract - ) -> tuple[dict, RequestFiles]: # mutable-ok: base class contract returns a dict + headers: dict, + ) -> tuple[dict, RequestFiles]: images: Final = tuple(img for img in (image if isinstance(image, list) else (image,)) if img is not None) if not images: raise ValueError("Fal AI image edit requires at least one input image") @@ -65,7 +65,7 @@ class FalAIFluxLoraDepthEditConfig(FalAIImageEditConfig): provider_params: Final[Mapping[str, object]] = MappingProxyType( {key: value for key, value in image_edit_optional_request_params.items() if key != "mask"} ) - request_body: Final[dict[str, object]] = { # mutable-ok: base class contract returns a dict + request_body: Final[dict[str, object]] = { "prompt": prompt, "image_url": to_data_url(next(iter(images))), **provider_params, diff --git a/litellm/llms/fal_ai/image_edit/transformation.py b/litellm/llms/fal_ai/image_edit/transformation.py index d77769b130d..c3af3fc76fb 100644 --- a/litellm/llms/fal_ai/image_edit/transformation.py +++ b/litellm/llms/fal_ai/image_edit/transformation.py @@ -148,7 +148,7 @@ class FalAIImageEditConfig(BaseImageEditConfig): provider_params: Final[Mapping[str, object]] = MappingProxyType( {key: value for key, value in image_edit_optional_request_params.items() if key != "mask"} ) - request_body: Final[dict[str, object]] = { # mutable-ok: base class contract returns a dict + request_body: Final[dict[str, object]] = { "prompt": prompt, "image_urls": tuple(to_data_url(img) for img in images), **mask_field, diff --git a/litellm/llms/fireworks_ai/chat/transformation.py b/litellm/llms/fireworks_ai/chat/transformation.py index a7a417db2c9..485341c59af 100644 --- a/litellm/llms/fireworks_ai/chat/transformation.py +++ b/litellm/llms/fireworks_ai/chat/transformation.py @@ -348,9 +348,7 @@ class FireworksAIConfig(FireworksAIMixin, OpenAIGPTConfig): return optional_params - def map_extra_body_params( - self, optional_params: Mapping[str, object], model: str - ) -> dict: # mutable-ok: http handler pops extra_body off the returned dict + def map_extra_body_params(self, optional_params: Mapping[str, object], model: str) -> dict: extra_body: Final = optional_params.get("extra_body") if not isinstance(extra_body, dict): return dict(optional_params) diff --git a/litellm/llms/fireworks_ai/completion/transformation.py b/litellm/llms/fireworks_ai/completion/transformation.py index 1f5d4f306a9..d9b0053f655 100644 --- a/litellm/llms/fireworks_ai/completion/transformation.py +++ b/litellm/llms/fireworks_ai/completion/transformation.py @@ -54,9 +54,7 @@ class FireworksAITextCompletionConfig(FireworksAIMixin, BaseTextCompletionConfig optional_params[k] = v return optional_params - def map_extra_body_params( - self, optional_params: Mapping[str, object], model: str - ) -> dict: # mutable-ok: returned dict is spread into the OpenAI SDK call as kwargs + def map_extra_body_params(self, optional_params: Mapping[str, object], model: str) -> dict: raw_extra_body: Final = optional_params.get("extra_body") initial_body: Final = dict(raw_extra_body) if isinstance(raw_extra_body, dict) else {} stripped_body: Final = self._strip_unsupported_params(initial_body, model) @@ -73,9 +71,7 @@ class FireworksAITextCompletionConfig(FireworksAIMixin, BaseTextCompletionConfig return base @staticmethod - def _strip_unsupported_params( - extra_body: Mapping[str, object], model: str - ) -> dict: # mutable-ok: JSON request body + def _strip_unsupported_params(extra_body: Mapping[str, object], model: str) -> dict: stripped: Final = tuple(sorted(k for k in extra_body if k in _TEXT_COMPLETION_STRIP_PARAMS)) if stripped: verbose_logger.debug( @@ -88,7 +84,7 @@ class FireworksAITextCompletionConfig(FireworksAIMixin, BaseTextCompletionConfig @staticmethod def _move_native_params_into_extra_body( extra_body: Mapping[str, object], optional_params: Mapping[str, object] - ) -> dict: # mutable-ok: JSON request body + ) -> dict: moved: Final = dict(extra_body) for key in ("response_format", "reasoning_effort", "thinking"): value = optional_params.get(key) @@ -101,7 +97,7 @@ class FireworksAITextCompletionConfig(FireworksAIMixin, BaseTextCompletionConfig def _translate_chat_template_kwargs( self, extra_body: Mapping[str, object], optional_params: Mapping[str, object], model: str - ) -> dict: # mutable-ok: JSON request body + ) -> dict: chat_template_kwargs: Final = extra_body.get("chat_template_kwargs") if chat_template_kwargs is None: return dict(extra_body) @@ -139,7 +135,7 @@ class FireworksAITextCompletionConfig(FireworksAIMixin, BaseTextCompletionConfig @staticmethod def _translate_guided_into_extra_body( extra_body: Mapping[str, object], optional_params: Mapping[str, object] - ) -> dict: # mutable-ok: JSON request body + ) -> dict: guided_response_format: Final = FireworksAIConfig.translate_guided_params(extra_body, optional_params) remaining: Final = { k: v for k, v in extra_body.items() if k not in ("guided_json", "guided_grammar", "guided_choice") diff --git a/litellm/llms/fireworks_ai/responses/transformation.py b/litellm/llms/fireworks_ai/responses/transformation.py index c1010102093..30e6f053cc1 100644 --- a/litellm/llms/fireworks_ai/responses/transformation.py +++ b/litellm/llms/fireworks_ai/responses/transformation.py @@ -125,7 +125,7 @@ class FireworksAIResponsesAPIConfig(OpenAIResponsesAPIConfig): headers: Mapping[str, str], model: str, litellm_params: GenericLiteLLMParams | None, - ) -> dict: # mutable-ok: overrides the base class signature + ) -> dict: params: Final = litellm_params or GenericLiteLLMParams() api_key: Final = resolve_fireworks_api_key(params.api_key) if api_key is None: @@ -144,10 +144,10 @@ class FireworksAIResponsesAPIConfig(OpenAIResponsesAPIConfig): self, model: str, input: str | ResponseInputParam, - response_api_optional_request_params: dict, # mutable-ok: overrides the base class signature + response_api_optional_request_params: dict, litellm_params: GenericLiteLLMParams, - headers: dict, # mutable-ok: overrides the base class signature - ) -> dict: # mutable-ok: overrides the base class signature + headers: dict, + ) -> dict: instructions_param: Final[object] = response_api_optional_request_params.get("instructions") validated_input: Final = self._validate_input_param(input) instructions, folded_input = ( diff --git a/litellm/llms/gemini/audio_transcription/transformation.py b/litellm/llms/gemini/audio_transcription/transformation.py index 48a345a4940..ff9814a8905 100644 --- a/litellm/llms/gemini/audio_transcription/transformation.py +++ b/litellm/llms/gemini/audio_transcription/transformation.py @@ -59,7 +59,7 @@ class GeminiAudioTranscriptionConfig(BaseAudioTranscriptionConfig): optional_params: Mapping[str, object], model: str, drop_params: bool, - ) -> dict: # mutable-ok: BaseAudioTranscriptionConfig signature + ) -> dict: supported_params: Final = frozenset(self.get_supported_openai_params(model)) accepted: Final = tuple((k, v) for k, v in non_default_params.items() if k in supported_params) return dict((*optional_params.items(), *accepted)) @@ -68,7 +68,7 @@ class GeminiAudioTranscriptionConfig(BaseAudioTranscriptionConfig): self, error_message: str, status_code: int, - headers: dict | Headers, # mutable-ok: base signature and BaseLLMException take dict | Headers + headers: dict | Headers, ) -> BaseLLMException: return GeminiError(status_code=status_code, message=error_message, headers=headers) @@ -81,7 +81,7 @@ class GeminiAudioTranscriptionConfig(BaseAudioTranscriptionConfig): litellm_params: Mapping[str, object], api_key: str | None = None, api_base: str | None = None, - ) -> dict: # mutable-ok: BaseAudioTranscriptionConfig signature + ) -> dict: resolved_api_key: Final = GeminiModelInfo.get_api_key(api_key) if not resolved_api_key: raise GeminiError( diff --git a/litellm/llms/gemini/chat/transformation.py b/litellm/llms/gemini/chat/transformation.py index 143652ee77c..b2f612fd1b1 100644 --- a/litellm/llms/gemini/chat/transformation.py +++ b/litellm/llms/gemini/chat/transformation.py @@ -177,6 +177,6 @@ class GoogleAIStudioGeminiConfig(VertexGeminiConfig): self, messages: list[AllMessageValues], # mutable-ok: mirrors override contract model: str | None = None, - litellm_params: dict[str, object] | None = None, # mutable-ok: mirrors override contract + litellm_params: dict[str, object] | None = None, ) -> list[ContentType]: # mutable-ok: mirrors override contract return self._transform_messages(messages, model, litellm_params) diff --git a/litellm/llms/gemini/google_genai/guardrail_translation/handler.py b/litellm/llms/gemini/google_genai/guardrail_translation/handler.py index 0c1fe8171e8..c9e8d6b7150 100644 --- a/litellm/llms/gemini/google_genai/guardrail_translation/handler.py +++ b/litellm/llms/gemini/google_genai/guardrail_translation/handler.py @@ -157,7 +157,7 @@ class GoogleGenAIGenerateContentHandler(BaseTranslation): async def process_input_messages( self, - data: dict, # mutable-ok: base handler contract passes the proxy's request dict through to apply_guardrail + data: dict, guardrail_to_apply: "CustomGuardrail", litellm_logging_obj: Optional["LiteLLMLoggingObj"] = None, ) -> object: @@ -245,7 +245,7 @@ class GoogleGenAIGenerateContentHandler(BaseTranslation): user_api_key_dict: Optional["UserAPIKeyAuth"], context_key: str, context_value: object, - ) -> dict: # mutable-ok: CustomGuardrail.apply_guardrail requires a plain dict request payload + ) -> dict: base: Final = request_data if request_data is not None else _EMPTY_REQUEST_DATA user_metadata: Final = self.transform_user_api_key_dict_to_metadata(user_api_key_dict) context_pairs: Final = ((context_key, context_value),) if context_key not in base else () diff --git a/litellm/llms/gigachat/chat/transformation.py b/litellm/llms/gigachat/chat/transformation.py index 15a1b463c3f..3d5b2c00444 100644 --- a/litellm/llms/gigachat/chat/transformation.py +++ b/litellm/llms/gigachat/chat/transformation.py @@ -111,14 +111,14 @@ class GigaChatConfig(BaseConfig): def validate_environment( self, - headers: dict, # mutable-ok: mutates in place per GigaChat OAuth setup + headers: dict, model: str, messages: Sequence[AllMessageValues], optional_params: Mapping[str, object], litellm_params: Mapping[str, object], api_key: str | None = None, api_base: str | None = None, - ) -> dict: # mutable-ok: base class contract returns dict for httpx + ) -> dict: """ Set up headers with OAuth token. """ @@ -153,10 +153,10 @@ class GigaChatConfig(BaseConfig): def map_openai_params( self, non_default_params: Mapping[str, object], - optional_params: dict, # mutable-ok: mutated in place per GigaChat mapping + optional_params: dict, model: str, drop_params: bool, - ) -> dict: # mutable-ok: base class contract returns dict + ) -> dict: """Map OpenAI parameters to GigaChat parameters.""" for param, value in non_default_params.items(): if param == "stream": @@ -328,7 +328,7 @@ class GigaChatConfig(BaseConfig): optional_params: Mapping[str, object], litellm_params: Mapping[str, object], headers: Mapping[str, object], - ) -> dict: # mutable-ok: request payload sent to httpx + ) -> dict: """Transform OpenAI request to GigaChat format.""" giga_messages: Final = self._transform_messages(messages) diff --git a/litellm/llms/gigachat/passthrough/transformation.py b/litellm/llms/gigachat/passthrough/transformation.py index d90ddbbbe2c..5951fddb562 100644 --- a/litellm/llms/gigachat/passthrough/transformation.py +++ b/litellm/llms/gigachat/passthrough/transformation.py @@ -49,14 +49,14 @@ class GigaChatPassthroughConfig(BasePassthroughConfig): def validate_environment( self, - headers: dict, # mutable-ok: mutates in place to set OAuth headers + headers: dict, model: str, messages: Sequence[AllMessageValues], optional_params: Mapping[str, object], litellm_params: Mapping[str, object], api_key: str | None = None, api_base: str | None = None, - ) -> dict: # mutable-ok: base class contract returns dict for httpx + ) -> dict: """ Set up headers with OAuth token. """ diff --git a/litellm/llms/hosted_vllm/image_edit/transformation.py b/litellm/llms/hosted_vllm/image_edit/transformation.py index 6804d3a0fb8..15b5ce2e988 100644 --- a/litellm/llms/hosted_vllm/image_edit/transformation.py +++ b/litellm/llms/hosted_vllm/image_edit/transformation.py @@ -16,12 +16,12 @@ class HostedVLLMImageEditConfig(OpenAIImageEditConfig): def validate_environment( self, - headers: dict, # mutable-ok: BaseImageEditConfig contract + headers: dict, model: str, api_key: str | None = None, - litellm_params: dict | None = None, # mutable-ok: BaseImageEditConfig contract + litellm_params: dict | None = None, api_base: str | None = None, - ) -> dict: # mutable-ok: BaseImageEditConfig contract + ) -> dict: resolved_key: Final = api_key or get_secret_str("HOSTED_VLLM_API_KEY") or "fake-api-key" return {**headers, "Authorization": f"Bearer {resolved_key}"} @@ -29,7 +29,7 @@ class HostedVLLMImageEditConfig(OpenAIImageEditConfig): self, model: str, api_base: str | None, - litellm_params: dict, # mutable-ok: BaseImageEditConfig contract + litellm_params: dict, ) -> str: resolved_api_base: Final = api_base or get_secret_str("HOSTED_VLLM_API_BASE") if resolved_api_base is None: diff --git a/litellm/llms/hosted_vllm/videos/transformation.py b/litellm/llms/hosted_vllm/videos/transformation.py index 22e4f4876fb..f040d7bfee9 100644 --- a/litellm/llms/hosted_vllm/videos/transformation.py +++ b/litellm/llms/hosted_vllm/videos/transformation.py @@ -145,16 +145,16 @@ class HostedVLLMVideoConfig(OpenAIVideoConfig): video_create_optional_params: VideoCreateOptionalRequestParams, model: str, drop_params: bool, - ) -> dict: # mutable-ok: BaseVideoConfig contract; extra_body merge mutates this dict + ) -> dict: return {key: value for key, value in video_create_optional_params.items() if value is not None} def validate_environment( self, - headers: dict, # mutable-ok: BaseVideoConfig contract + headers: dict, model: str, api_key: str | None = None, litellm_params: GenericLiteLLMParams | None = None, - ) -> dict: # mutable-ok: BaseVideoConfig contract + ) -> dict: resolved_key: Final = ( (litellm_params.api_key if litellm_params is not None else None) or api_key @@ -167,7 +167,7 @@ class HostedVLLMVideoConfig(OpenAIVideoConfig): self, model: str, api_base: str | None, - litellm_params: dict, # mutable-ok: BaseVideoConfig contract + litellm_params: dict, ) -> str: resolved_api_base: Final = api_base or get_secret_str("HOSTED_VLLM_API_BASE") if resolved_api_base is None: @@ -185,10 +185,10 @@ class HostedVLLMVideoConfig(OpenAIVideoConfig): model: str, prompt: str, api_base: str, - video_create_optional_request_params: dict, # mutable-ok: BaseVideoConfig contract + video_create_optional_request_params: dict, litellm_params: GenericLiteLLMParams, - headers: dict, # mutable-ok: BaseVideoConfig contract - ) -> tuple[dict, RequestFiles, str]: # mutable-ok: BaseVideoConfig contract + headers: dict, + ) -> tuple[dict, RequestFiles, str]: data: Final = { "model": model, "prompt": prompt, diff --git a/litellm/llms/meta/realtime/transformation.py b/litellm/llms/meta/realtime/transformation.py index 46231e5e980..63a4d83b426 100644 --- a/litellm/llms/meta/realtime/transformation.py +++ b/litellm/llms/meta/realtime/transformation.py @@ -315,7 +315,7 @@ class _TurnState: class MuseEventTransformer: def __init__(self, *, turn_limit: int = 128) -> None: - self._turns: dict[str, _TurnState] = {} # mutable-ok: bounded, insertion-ordered per-turn emit state + self._turns: dict[str, _TurnState] = {} self._turn_limit: Final = turn_limit self._active_turn_id: str | None = None self._mode: MuseMode = "ENDPOINTING" @@ -454,10 +454,10 @@ class MetaRealtimeConfig(BaseRealtimeConfig): def validate_environment( self, - headers: dict[str, str], # mutable-ok: BaseRealtimeConfig contract + headers: dict[str, str], model: str, api_key: str | None = None, - ) -> dict[str, str]: # mutable-ok: BaseRealtimeConfig contract + ) -> dict[str, str]: token: Final = api_key or get_secret_str("META_API_KEY") if token is None: raise ValueError("api_key is required for Meta API calls") diff --git a/litellm/llms/minimax/messages/transformation.py b/litellm/llms/minimax/messages/transformation.py index d62b88a24c6..c39f0c868fc 100644 --- a/litellm/llms/minimax/messages/transformation.py +++ b/litellm/llms/minimax/messages/transformation.py @@ -51,14 +51,14 @@ class MinimaxMessagesConfig(AnthropicMessagesConfig): def validate_anthropic_messages_environment( self, - headers: dict, # mutable-ok: mirrors the legacy base override signature + headers: dict, model: str, messages: list[Any], # mutable-ok: mirrors the legacy base override signature - optional_params: dict, # mutable-ok: mirrors the legacy base override signature - litellm_params: dict, # mutable-ok: mirrors the legacy base override signature + optional_params: dict, + litellm_params: dict, api_key: str | None = None, api_base: str | None = None, - ) -> tuple[dict, str | None]: # mutable-ok: mirrors the legacy base override signature + ) -> tuple[dict, str | None]: return super().validate_anthropic_messages_environment( headers=headers, model=model, diff --git a/litellm/llms/mistral/audio_speech/transformation.py b/litellm/llms/mistral/audio_speech/transformation.py index 04a7e3b9341..afddbf74a9a 100644 --- a/litellm/llms/mistral/audio_speech/transformation.py +++ b/litellm/llms/mistral/audio_speech/transformation.py @@ -78,7 +78,7 @@ class MistralTextToSpeechConfig(BaseTextToSpeechConfig): voice: object = None, drop_params: bool = False, kwargs: Mapping[str, object] | None = None, - ) -> tuple[str | None, dict]: # mutable-ok: base class contract returns a plain dict + ) -> tuple[str | None, dict]: response_format: Final = optional_params.get("response_format") ref_audio: Final = kwargs.get("ref_audio") if kwargs else None voice_id_kwarg: Final = kwargs.get("voice_id") if kwargs else None @@ -96,7 +96,7 @@ class MistralTextToSpeechConfig(BaseTextToSpeechConfig): model: str, api_key: str | None = None, api_base: str | None = None, - ) -> dict: # mutable-ok: base class contract returns a plain dict + ) -> dict: resolved_key: Final = api_key or get_secret_str("MISTRAL_API_KEY") if resolved_key is None: raise MistralTextToSpeechException( @@ -201,7 +201,7 @@ class MistralTextToSpeechConfig(BaseTextToSpeechConfig): self, error_message: str, status_code: int, - headers: dict | httpx.Headers, # mutable-ok: BaseLLMException takes a plain dict or httpx.Headers + headers: dict | httpx.Headers, ) -> BaseLLMException: return MistralTextToSpeechException( message=error_message, diff --git a/litellm/llms/mistral/batches/transformation.py b/litellm/llms/mistral/batches/transformation.py index 86de2836048..019efb92eb4 100644 --- a/litellm/llms/mistral/batches/transformation.py +++ b/litellm/llms/mistral/batches/transformation.py @@ -90,7 +90,7 @@ class MistralBatchJob(LiteLLMBaseModel): output_file: str | None = None error_file: str | None = None errors: tuple[MistralBatchError, ...] = () - metadata: dict[str, str] | None = None # mutable-ok: LiteLLMBatch.metadata is typed as dict + metadata: dict[str, str] | None = None def _to_batch_errors(errors: Sequence[MistralBatchError]) -> BatchErrors | None: @@ -144,7 +144,7 @@ class MistralBatchesConfig(BaseBatchesConfig): litellm_params: Mapping[str, object], api_key: str | None = None, api_base: str | None = None, - ) -> dict[str, str]: # mutable-ok: BaseBatchesConfig signature + ) -> dict[str, str]: return get_mistral_auth_headers(headers, api_key) def get_complete_batch_url( @@ -164,7 +164,7 @@ class MistralBatchesConfig(BaseBatchesConfig): create_batch_data: CreateBatchRequest, optional_params: Mapping[str, object], litellm_params: Mapping[str, object], - ) -> dict[str, object]: # mutable-ok: BaseBatchesConfig signature + ) -> dict[str, object]: input_file_id: Final = create_batch_data.get("input_file_id") endpoint: Final = create_batch_data.get("endpoint") if input_file_id is None or endpoint is None: @@ -193,7 +193,7 @@ class MistralBatchesConfig(BaseBatchesConfig): batch_id: str, optional_params: Mapping[str, object], litellm_params: Mapping[str, object], - ) -> dict[str, object]: # mutable-ok: BaseBatchesConfig signature + ) -> dict[str, object]: encoded_batch_id: Final = encode_url_path_segment(batch_id, field_name="batch_id") api_base: Final = litellm_params.get("api_base") api_key: Final = litellm_params.get("api_key") diff --git a/litellm/llms/mistral/common_utils.py b/litellm/llms/mistral/common_utils.py index ef354047b06..ad38ba9d1f8 100644 --- a/litellm/llms/mistral/common_utils.py +++ b/litellm/llms/mistral/common_utils.py @@ -20,9 +20,7 @@ def get_mistral_api_base(api_base: str | None) -> str: return resolved.removesuffix("/v1") -def get_mistral_auth_headers( - headers: Mapping[str, str], api_key: str | None -) -> dict[str, str]: # mutable-ok: BaseConfig.validate_environment contract returns dict +def get_mistral_auth_headers(headers: Mapping[str, str], api_key: str | None) -> dict[str, str]: resolved_key: Final = api_key or get_secret_str(MISTRAL_API_KEY_ENV_VAR) if resolved_key is None: raise ValueError( diff --git a/litellm/llms/mistral/files/transformation.py b/litellm/llms/mistral/files/transformation.py index 86bce763917..1526dbe03b3 100644 --- a/litellm/llms/mistral/files/transformation.py +++ b/litellm/llms/mistral/files/transformation.py @@ -45,7 +45,7 @@ _MISTRAL_PURPOSE_BY_OPENAI: Final[Mapping[str, MistralFilePurpose]] = MappingPro ) _SUPPORTED_PURPOSES: Final = ", ".join(_MISTRAL_PURPOSE_BY_OPENAI) -_NO_QUERY_PARAMS: Final[dict[str, str]] = {} # mutable-ok: BaseFilesConfig request transforms return tuple[str, dict] +_NO_QUERY_PARAMS: Final[dict[str, str]] = {} class MistralMultipartUpload(TypedDict): @@ -150,7 +150,7 @@ class MistralFilesConfig(BaseFilesConfig): litellm_params: Mapping[str, object], api_key: str | None = None, api_base: str | None = None, - ) -> dict[str, str]: # mutable-ok: BaseFilesConfig signature + ) -> dict[str, str]: return get_mistral_auth_headers(headers, api_key) def get_supported_openai_params( @@ -161,10 +161,10 @@ class MistralFilesConfig(BaseFilesConfig): def map_openai_params( self, non_default_params: Mapping[str, object], - optional_params: dict[str, object], # mutable-ok: BaseConfig signature, returned as-is + optional_params: dict[str, object], model: str, drop_params: bool, - ) -> dict[str, object]: # mutable-ok: BaseConfig signature + ) -> dict[str, object]: return optional_params def transform_create_file_request( @@ -173,7 +173,7 @@ class MistralFilesConfig(BaseFilesConfig): create_file_data: CreateFileRequest, optional_params: Mapping[str, object], litellm_params: Mapping[str, object], - ) -> dict[str, object]: # mutable-ok: BaseFilesConfig signature + ) -> dict[str, object]: if "file" not in create_file_data: raise ValueError("File data is required") extracted: Final = extract_file_data(create_file_data["file"]) @@ -199,7 +199,7 @@ class MistralFilesConfig(BaseFilesConfig): file_id: str, optional_params: Mapping[str, object], litellm_params: Mapping[str, object], - ) -> tuple[str, dict[str, str]]: # mutable-ok: BaseFilesConfig signature + ) -> tuple[str, dict[str, str]]: return self._file_url(file_id, litellm_params), _NO_QUERY_PARAMS def transform_retrieve_file_response( @@ -215,7 +215,7 @@ class MistralFilesConfig(BaseFilesConfig): file_id: str, optional_params: Mapping[str, object], litellm_params: Mapping[str, object], - ) -> tuple[str, dict[str, str]]: # mutable-ok: BaseFilesConfig signature + ) -> tuple[str, dict[str, str]]: return self._file_url(file_id, litellm_params), _NO_QUERY_PARAMS def transform_delete_file_response( @@ -232,7 +232,7 @@ class MistralFilesConfig(BaseFilesConfig): purpose: str | None, optional_params: Mapping[str, object], litellm_params: Mapping[str, object], - ) -> tuple[str, dict[str, str]]: # mutable-ok: BaseFilesConfig signature + ) -> tuple[str, dict[str, str]]: url: Final = f"{_api_base_from(litellm_params)}/v1/files" if not purpose: return url, _NO_QUERY_PARAMS @@ -251,7 +251,7 @@ class MistralFilesConfig(BaseFilesConfig): file_content_request: FileContentRequest, optional_params: Mapping[str, object], litellm_params: Mapping[str, object], - ) -> tuple[str, dict[str, str]]: # mutable-ok: BaseFilesConfig signature + ) -> tuple[str, dict[str, str]]: file_id: Final = file_content_request.get("file_id") if file_id is None: raise ValueError("file_id is required to download file content") diff --git a/litellm/llms/mongodb/vector_stores/transformation.py b/litellm/llms/mongodb/vector_stores/transformation.py index 0d931ee78cb..79914d04284 100644 --- a/litellm/llms/mongodb/vector_stores/transformation.py +++ b/litellm/llms/mongodb/vector_stores/transformation.py @@ -187,7 +187,7 @@ class MongoDBVectorStoreConfig(BaseQueryEmbeddingVectorStoreConfig): def validate_environment( self, headers: Mapping[str, object], litellm_params: GenericLiteLLMParams | None - ) -> dict[str, object]: # mutable-ok: the shared HTTP handler requires writable headers + ) -> dict[str, object]: if litellm_params is None: raise config_error("Configure api_base and api_key for the MongoDB BETA sidecar.") self._reject_unknown_params(MappingProxyType(dict(litellm_params))) @@ -273,7 +273,7 @@ class MongoDBVectorStoreConfig(BaseQueryEmbeddingVectorStoreConfig): api_base: str, embedding_response: EmbeddingResponse, timeout: object, - ) -> tuple[str, dict[str, object]]: # mutable-ok: the provider contract returns a writable JSON request body + ) -> tuple[str, dict[str, object]]: if not embedding_response.data: raise config_error( "The embedding model returned no embedding for the search query. Check litellm_embedding_model." @@ -307,7 +307,7 @@ class MongoDBVectorStoreConfig(BaseQueryEmbeddingVectorStoreConfig): litellm_params: Mapping[str, object], extra_body: Mapping[str, object] | None = None, embedding_executor: VectorStoreEmbeddingExecutor | None = None, - ) -> tuple[str, dict[str, object]]: # mutable-ok: the provider contract returns a writable JSON request body + ) -> tuple[str, dict[str, object]]: params: Final = self._params(litellm_params, vector_store_search_optional_params, extra_body) query_text: Final = self._query_text(query) response: Final = (embedding_executor or self.embedding_executor).embed( @@ -333,7 +333,7 @@ class MongoDBVectorStoreConfig(BaseQueryEmbeddingVectorStoreConfig): litellm_params: Mapping[str, object], extra_body: Mapping[str, object] | None = None, embedding_executor: VectorStoreEmbeddingExecutor | None = None, - ) -> tuple[str, dict[str, object]]: # mutable-ok: the provider contract returns a writable JSON request body + ) -> tuple[str, dict[str, object]]: params: Final = self._params(litellm_params, vector_store_search_optional_params, extra_body) query_text: Final = self._query_text(query) response: Final = await (embedding_executor or self.embedding_executor).aembed( diff --git a/litellm/llms/nadir/chat/transformation.py b/litellm/llms/nadir/chat/transformation.py index d22b1a51bab..25ba135b36e 100644 --- a/litellm/llms/nadir/chat/transformation.py +++ b/litellm/llms/nadir/chat/transformation.py @@ -43,10 +43,10 @@ class NadirConfig(OpenAIGPTConfig): raw_response: httpx.Response, model_response: ModelResponse, logging_obj: object, - request_data: dict, # mutable-ok: signature fixed by the base interface + request_data: dict, messages: list[AllMessageValues], # mutable-ok: signature fixed by the base interface - optional_params: dict, # mutable-ok: signature fixed by the base interface - litellm_params: dict, # mutable-ok: signature fixed by the base interface + optional_params: dict, + litellm_params: dict, encoding: object, api_key: str | None = None, json_mode: bool | None = None, diff --git a/litellm/llms/nimble/search/transformation.py b/litellm/llms/nimble/search/transformation.py index 30cfc38eb87..caa93ee53ff 100644 --- a/litellm/llms/nimble/search/transformation.py +++ b/litellm/llms/nimble/search/transformation.py @@ -89,11 +89,11 @@ class NimbleSearchConfig(BaseSearchConfig): def validate_environment( self, - headers: dict[str, str], # mutable-ok: BaseSearchConfig.validate_environment signature + headers: dict[str, str], api_key: str | None = None, api_base: str | None = None, **kwargs: object, # kwargs-ok: BaseSearchConfig.validate_environment signature - ) -> dict[str, str]: # mutable-ok: the http handler passes this straight to httpx as headers + ) -> dict[str, str]: """ Validate environment and return headers. @@ -120,7 +120,7 @@ class NimbleSearchConfig(BaseSearchConfig): def get_complete_url( self, api_base: str | None, - optional_params: dict[str, object], # mutable-ok: BaseSearchConfig.get_complete_url signature + optional_params: dict[str, object], data: dict[str, object] | list[dict[str, object]] | None = None, # mutable-ok: base signature **kwargs: object, # kwargs-ok: BaseSearchConfig.get_complete_url signature ) -> str: @@ -132,9 +132,9 @@ class NimbleSearchConfig(BaseSearchConfig): def transform_search_request( self, query: str | list[str], # mutable-ok: BaseSearchConfig.transform_search_request signature - optional_params: dict[str, object], # mutable-ok: base signature + optional_params: dict[str, object], **kwargs: object, # kwargs-ok: BaseSearchConfig.transform_search_request signature - ) -> dict[str, object]: # mutable-ok: the http handler passes this straight to httpx as the JSON body + ) -> dict[str, object]: """ Transform Search request to Nimble API format. @@ -211,7 +211,7 @@ class NimbleSearchConfig(BaseSearchConfig): self, error_message: str, status_code: int, - headers: dict[str, str], # mutable-ok: BaseSearchConfig.get_error_class signature + headers: dict[str, str], ) -> Exception: detail: Final = _unwrap_error_detail(error_message).rstrip(". ") return BaseLLMException( diff --git a/litellm/llms/nvidia_nim/passthrough/transformation.py b/litellm/llms/nvidia_nim/passthrough/transformation.py index 930481cfceb..f056310a6fc 100644 --- a/litellm/llms/nvidia_nim/passthrough/transformation.py +++ b/litellm/llms/nvidia_nim/passthrough/transformation.py @@ -104,7 +104,7 @@ class NvidiaNimPassthroughConfig(BasePassthroughConfig): litellm_params: Mapping[str, object], api_key: str | None = None, api_base: str | None = None, - ) -> dict[str, str]: # mutable-ok: base class contract returns dict for httpx + ) -> dict[str, str]: if api_key is None: return dict(headers) return { diff --git a/litellm/llms/nvidia_nim/rerank/ranking_transformation.py b/litellm/llms/nvidia_nim/rerank/ranking_transformation.py index 177d7883feb..e9beb14a208 100644 --- a/litellm/llms/nvidia_nim/rerank/ranking_transformation.py +++ b/litellm/llms/nvidia_nim/rerank/ranking_transformation.py @@ -80,7 +80,7 @@ class NvidiaNimRankingConfig(NvidiaNimRerankConfig): def map_cohere_rerank_params( self, - non_default_params: dict | None, # mutable-ok: matches BaseRerankConfig's request contract + non_default_params: dict | None, model: str, drop_params: bool, query: str, @@ -92,7 +92,7 @@ class NvidiaNimRankingConfig(NvidiaNimRerankConfig): max_chunks_per_doc: int | None = None, max_tokens_per_doc: int | None = None, instruction: str | None = None, - ) -> dict: # mutable-ok: LiteLLM provider transforms return mutable request dictionaries + ) -> dict: """ Keep Cohere's top_n as-is instead of mapping it to top_k. @@ -156,9 +156,9 @@ class NvidiaNimRankingConfig(NvidiaNimRerankConfig): model_response: RerankResponse, logging_obj: LiteLLMLoggingObj, api_key: str | None = None, - request_data: dict | None = None, # mutable-ok: matches BaseRerankConfig's response contract - optional_params: dict | None = None, # mutable-ok: matches BaseRerankConfig's response contract - litellm_params: dict | None = None, # mutable-ok: matches BaseRerankConfig's response contract + request_data: dict | None = None, + optional_params: dict | None = None, + litellm_params: dict | None = None, ) -> RerankResponse: """ Convert the native ranking response, then apply top_n client-side. diff --git a/litellm/llms/ollama/completion/transformation.py b/litellm/llms/ollama/completion/transformation.py index c508c27c11a..107cae1a98a 100644 --- a/litellm/llms/ollama/completion/transformation.py +++ b/litellm/llms/ollama/completion/transformation.py @@ -407,10 +407,10 @@ class OllamaConfig(BaseConfig): self, model: str, messages: list[AllMessageValues], # mutable-ok: BaseConfig signature - optional_params: dict[str, object], # mutable-ok: BaseConfig signature - litellm_params: dict[str, object], # mutable-ok: BaseConfig signature - headers: dict[str, object], # mutable-ok: BaseConfig signature - ) -> dict[str, object]: # mutable-ok: BaseConfig signature + optional_params: dict[str, object], + litellm_params: dict[str, object], + headers: dict[str, object], + ) -> dict[str, object]: return self.transform_request( model=model, messages=await async_inline_remote_media(messages, should_inline=inline_remote_image_urls), diff --git a/litellm/llms/openai/chat/guardrail_translation/handler.py b/litellm/llms/openai/chat/guardrail_translation/handler.py index 249d6008f87..2139a7002d7 100644 --- a/litellm/llms/openai/chat/guardrail_translation/handler.py +++ b/litellm/llms/openai/chat/guardrail_translation/handler.py @@ -647,7 +647,7 @@ class OpenAIChatCompletionsHandler(BaseTranslation): guardrail_to_apply: "CustomGuardrail", litellm_logging_obj: "LiteLLMLoggingObj | None", user_api_key_dict: "UserAPIKeyAuth | None", - request_data: dict[str, object] | None, # mutable-ok: same request-payload shape the hooks take + request_data: dict[str, object] | None, deliver_ended_stream_rewrites: bool, ) -> None: """Ended-stream path: rebuild the full response, run the non-streaming diff --git a/litellm/llms/openai/videos/guardrail_translation/handler.py b/litellm/llms/openai/videos/guardrail_translation/handler.py index 7bdcc59ee7e..4a2dad35499 100644 --- a/litellm/llms/openai/videos/guardrail_translation/handler.py +++ b/litellm/llms/openai/videos/guardrail_translation/handler.py @@ -12,10 +12,10 @@ if TYPE_CHECKING: class OpenAIVideoGenerationHandler(BaseTranslation): async def process_input_messages( self, - data: dict[str, object], # mutable-ok: BaseTranslation contract passes the proxy's request dict through + data: dict[str, object], guardrail_to_apply: "CustomGuardrail", litellm_logging_obj: "LiteLLMLoggingObj | None" = None, - ) -> dict[str, object]: # mutable-ok: BaseTranslation contract returns the proxy's request dict + ) -> dict[str, object]: prompt: Final = data.get("prompt") if not isinstance(prompt, str): return data @@ -43,6 +43,6 @@ class OpenAIVideoGenerationHandler(BaseTranslation): guardrail_to_apply: "CustomGuardrail", litellm_logging_obj: "LiteLLMLoggingObj | None" = None, user_api_key_dict: "UserAPIKeyAuth | None" = None, - request_data: dict[str, object] | None = None, # mutable-ok: BaseTranslation contract + request_data: dict[str, object] | None = None, ) -> object: return response diff --git a/litellm/llms/openai_like/messages/transformation.py b/litellm/llms/openai_like/messages/transformation.py index 2e9a300e2fd..ee27532f962 100644 --- a/litellm/llms/openai_like/messages/transformation.py +++ b/litellm/llms/openai_like/messages/transformation.py @@ -70,10 +70,10 @@ class OpenAILikeAnthropicMessagesConfig(AnthropicMessagesConfig): self, model: str, messages: list[dict], # mutable-ok: matches dict-typed base signature - anthropic_messages_optional_request_params: dict, # mutable-ok: matches dict-typed base signature + anthropic_messages_optional_request_params: dict, litellm_params: GenericLiteLLMParams, - headers: dict, # mutable-ok: matches dict-typed base signature - ) -> dict: # mutable-ok: matches dict-typed base signature + headers: dict, + ) -> dict: """ Anthropic ignores prompt-caching hints it cannot honor, but strict non-Anthropic implementations of the Messages API 400 the whole request diff --git a/litellm/llms/opencode/harness/transformation.py b/litellm/llms/opencode/harness/transformation.py index 787bc99a2d7..d6e2918f8b5 100644 --- a/litellm/llms/opencode/harness/transformation.py +++ b/litellm/llms/opencode/harness/transformation.py @@ -228,7 +228,7 @@ def permission_rules(permissions: PermissionMode, disable_tools: Sequence[str]) # Denies go last (later keys win in opencode), so drop them from the mode rules first. kept: Final = ((key, value) for key, value in PERMISSION_RULES[permissions].items() if key not in denied) rules: Final = itertools.chain(kept, ((native, "deny") for native in denied)) - return dict(rules) # mutable-ok: opencode config JSON + return dict(rules) def build_opencode_config( @@ -247,31 +247,31 @@ def build_opencode_config( validate_user_config(user) qualified = f"{OPENCODE_PROVIDER_ID}/{model}" extra_instructions: Final = (instructions_path,) if instructions_path else () - instructions: Final = [*(user.get("instructions") or ()), *extra_instructions] # mutable-ok: opencode config JSON + instructions: Final = [*(user.get("instructions") or ()), *extra_instructions] user_skills = _as_dict(user.get("skills")) extra_skills: Final = (skills_path,) if skills_path else () - skill_paths: Final = [*(user_skills.get("paths") or ()), *extra_skills] # mutable-ok: opencode config JSON - options: Final = {"baseURL": base_url, "apiKey": "{file:" + token_path + "}"} # mutable-ok: opencode config JSON - models: Final[dict[str, Mapping[str, object]]] = {model: {}} # mutable-ok: opencode config JSON - provider: Final = { # mutable-ok: opencode config JSON + skill_paths: Final = [*(user_skills.get("paths") or ()), *extra_skills] + options: Final = {"baseURL": base_url, "apiKey": "{file:" + token_path + "}"} + models: Final[dict[str, Mapping[str, object]]] = {model: {}} + provider: Final = { "npm": OPENCODE_PROVIDER_NPM, "name": "LiteLLM", "options": options, "models": models, } - managed: Final = { # mutable-ok: opencode config JSON - "provider": {OPENCODE_PROVIDER_ID: provider}, # mutable-ok: opencode config JSON - "enabled_providers": [OPENCODE_PROVIDER_ID], # mutable-ok: opencode config JSON + managed: Final = { + "provider": {OPENCODE_PROVIDER_ID: provider}, + "enabled_providers": [OPENCODE_PROVIDER_ID], "model": qualified, "small_model": qualified, "permission": permission_rules(permissions, disable_tools), "autoupdate": False, "share": "disabled", } - skills: Final = {**user_skills, "paths": skill_paths} # mutable-ok: opencode config JSON + skills: Final = {**user_skills, "paths": skill_paths} optional: Final = (("instructions", instructions), ("skills", skills if skill_paths else None)) present: Final = ((key, value) for key, value in optional if value) - return {**user, **managed, **dict(present)} # mutable-ok: opencode config JSON + return {**user, **managed, **dict(present)} def build_instructions(ctx: SessionContext) -> str | None: diff --git a/litellm/llms/sagemaker/chat/transformation.py b/litellm/llms/sagemaker/chat/transformation.py index 6dfc274fed1..1d32ad69f80 100644 --- a/litellm/llms/sagemaker/chat/transformation.py +++ b/litellm/llms/sagemaker/chat/transformation.py @@ -65,10 +65,10 @@ class SagemakerChatConfig(OpenAIGPTConfig, BaseAWSLLM): self, model: str, messages: list[AllMessageValues], # mutable-ok: matches the base chat transform signature - optional_params: dict, # mutable-ok: matches the base chat transform signature - litellm_params: dict, # mutable-ok: matches the base chat transform signature - headers: dict, # mutable-ok: matches the base chat transform signature - ) -> dict: # mutable-ok: the handler sends this body straight to httpx + optional_params: dict, + litellm_params: dict, + headers: dict, + ) -> dict: request: Final = super().transform_request( model=model, messages=messages, diff --git a/litellm/llms/sail/chat/transformation.py b/litellm/llms/sail/chat/transformation.py index 64f69af06fa..ffe5b85a167 100644 --- a/litellm/llms/sail/chat/transformation.py +++ b/litellm/llms/sail/chat/transformation.py @@ -26,11 +26,11 @@ class SailChatConfig(OpenAIGPTConfig): def map_openai_params( self, - non_default_params: dict, # mutable-ok: signature fixed by the base interface - optional_params: dict, # mutable-ok: signature fixed by the base interface + non_default_params: dict, + optional_params: dict, model: str, drop_params: bool, - ) -> dict: # mutable-ok: return type fixed by the base interface + ) -> dict: completion_window_for_service_tier(non_default_params.get("service_tier"), model=model, drop_params=drop_params) return super().map_openai_params( non_default_params=non_default_params, @@ -43,10 +43,10 @@ class SailChatConfig(OpenAIGPTConfig): self, model: str, messages: list[AllMessageValues], # mutable-ok: signature fixed by the base interface - optional_params: dict, # mutable-ok: signature fixed by the base interface - litellm_params: dict, # mutable-ok: signature fixed by the base interface - headers: dict, # mutable-ok: signature fixed by the base interface - ) -> dict: # mutable-ok: return type fixed by the base interface + optional_params: dict, + litellm_params: dict, + headers: dict, + ) -> dict: request: Final = chat_request_for_sail( super().transform_request( model=model, diff --git a/litellm/llms/sail/common_utils.py b/litellm/llms/sail/common_utils.py index cb00ed14214..dd9a73ccde3 100644 --- a/litellm/llms/sail/common_utils.py +++ b/litellm/llms/sail/common_utils.py @@ -44,7 +44,7 @@ def _entry(key: str, value: object) -> Mapping[str, object]: return MappingProxyType({key: value}) -def json_body(mapping: Mapping[str, object]) -> dict[str, object]: # mutable-ok: HTTP bodies are plain dicts +def json_body(mapping: Mapping[str, object]) -> dict[str, object]: return {key: _json_value(value) for key, value in mapping.items()} diff --git a/litellm/llms/sail/responses/transformation.py b/litellm/llms/sail/responses/transformation.py index c3c39a125f5..2c66e086a89 100644 --- a/litellm/llms/sail/responses/transformation.py +++ b/litellm/llms/sail/responses/transformation.py @@ -19,7 +19,7 @@ class SailResponsesAPIConfig(create_responses_config_class(sail_provider_config( response_api_optional_params: ResponsesAPIOptionalRequestParams, model: str, drop_params: bool, - ) -> dict: # mutable-ok: return type fixed by the base interface + ) -> dict: params: Final = responses_params_with_completion_window( super().map_openai_params( response_api_optional_params=response_api_optional_params, model=model, drop_params=drop_params @@ -33,10 +33,10 @@ class SailResponsesAPIConfig(create_responses_config_class(sail_provider_config( self, model: str, input: str | ResponseInputParam, - response_api_optional_request_params: dict, # mutable-ok: signature fixed by the base interface + response_api_optional_request_params: dict, litellm_params: GenericLiteLLMParams, - headers: dict, # mutable-ok: signature fixed by the base interface - ) -> dict: # mutable-ok: return type fixed by the base interface + headers: dict, + ) -> dict: request: Final[Mapping[str, object]] = super().transform_responses_api_request( model=model, input=input, diff --git a/litellm/llms/sap/chat/models.py b/litellm/llms/sap/chat/models.py index 43fa3236e88..82dd3c92615 100644 --- a/litellm/llms/sap/chat/models.py +++ b/litellm/llms/sap/chat/models.py @@ -19,9 +19,7 @@ class CacheControl(LiteLLMBaseModel): ttl: Literal["5m", "1h"] | None = None @model_serializer(mode="wrap") - def _serialize( - self, handler: SerializerFunctionWrapHandler, info: SerializationInfo - ) -> dict: # mutable-ok: pydantic serializer contract requires bare dict return + def _serialize(self, handler: SerializerFunctionWrapHandler, info: SerializationInfo) -> dict: result = handler(self) if result.get("ttl") is None: result.pop("ttl", None) @@ -34,9 +32,7 @@ class TextContent(LiteLLMBaseModel): cache_control: CacheControl | None = None @model_serializer(mode="wrap") - def _serialize( - self, handler: SerializerFunctionWrapHandler, info: SerializationInfo - ) -> dict: # mutable-ok: pydantic serializer contract requires bare dict return + def _serialize(self, handler: SerializerFunctionWrapHandler, info: SerializationInfo) -> dict: result = handler(self) if result.get("cache_control") is None: result.pop("cache_control", None) @@ -54,9 +50,7 @@ class ImageContent(LiteLLMBaseModel): cache_control: CacheControl | None = None @model_serializer(mode="wrap") - def _serialize( - self, handler: SerializerFunctionWrapHandler, info: SerializationInfo - ) -> dict: # mutable-ok: pydantic serializer contract requires bare dict return + def _serialize(self, handler: SerializerFunctionWrapHandler, info: SerializationInfo) -> dict: result = handler(self) if result.get("cache_control") is None: result.pop("cache_control", None) diff --git a/litellm/llms/sap/chat/transformation.py b/litellm/llms/sap/chat/transformation.py index 342a4ff7025..2d20198e0ce 100755 --- a/litellm/llms/sap/chat/transformation.py +++ b/litellm/llms/sap/chat/transformation.py @@ -53,9 +53,7 @@ _SAP_MODEL_PARAMS_EXCLUDED_KEYS: Final[frozenset[str]] = frozenset( ) -def validate_dict( - data: dict, model: type -) -> dict: # mutable-ok: pydantic validation boundary; both input and output are untyped wire dicts +def validate_dict(data: dict, model: type) -> dict: return model(**data).model_dump(by_alias=True, exclude_unset=True) diff --git a/litellm/llms/scaleway/rerank/transformation.py b/litellm/llms/scaleway/rerank/transformation.py index 921252d7091..61f7a8c16c2 100644 --- a/litellm/llms/scaleway/rerank/transformation.py +++ b/litellm/llms/scaleway/rerank/transformation.py @@ -35,7 +35,7 @@ class ScalewayRerankConfig(JinaAIRerankConfig): api_key: str | None = None, optional_params: Mapping[str, object] | None = None, litellm_params: Mapping[str, object] | None = None, - ) -> dict[str, str]: # mutable-ok: BaseRerankConfig contract + ) -> dict[str, str]: key: Final = api_key or get_secret_str("SCW_SECRET_KEY") if not key: raise ValueError( diff --git a/litellm/llms/snowflake/chat/transformation.py b/litellm/llms/snowflake/chat/transformation.py index a6c060732c5..c5a8db095ed 100644 --- a/litellm/llms/snowflake/chat/transformation.py +++ b/litellm/llms/snowflake/chat/transformation.py @@ -160,7 +160,7 @@ def _convert_tool_result_to_anthropic( and non-list shapes it does not model are handled here. """ if not isinstance(content, list): - plain: Final[dict[str, object]] = { # mutable-ok: JSON wire block + plain: Final[dict[str, object]] = { "type": "tool_result", "tool_use_id": tool_call_id, "content": content if isinstance(content, str) else json.dumps(content), @@ -418,10 +418,10 @@ class SnowflakeConfig(SnowflakeBaseConfig, OpenAIGPTConfig): self, model: str, messages: list[AllMessageValues], # mutable-ok: BaseConfig signature - optional_params: dict[str, object], # mutable-ok: BaseConfig signature - litellm_params: dict[str, object], # mutable-ok: BaseConfig signature - headers: dict[str, object], # mutable-ok: BaseConfig signature - ) -> dict[str, object]: # mutable-ok: BaseConfig signature + optional_params: dict[str, object], + litellm_params: dict[str, object], + headers: dict[str, object], + ) -> dict[str, object]: inlined_messages: Final = await async_inline_remote_media(messages) if _is_claude_model(model) else messages return self.transform_request(model, inlined_messages, optional_params, litellm_params, headers) @@ -498,7 +498,7 @@ class SnowflakeConfig(SnowflakeBaseConfig, OpenAIGPTConfig): model_name: Final = model.removeprefix("snowflake/") - body: Final[dict[str, object]] = normalize_cache_control_in_anthropic_payload( # mutable-ok: JSON wire body + body: Final[dict[str, object]] = normalize_cache_control_in_anthropic_payload( { "model": model_name, "messages": conversation, diff --git a/litellm/llms/soniox/audio_transcription/transformation.py b/litellm/llms/soniox/audio_transcription/transformation.py index aa1ff239320..b9c4da11bad 100644 --- a/litellm/llms/soniox/audio_transcription/transformation.py +++ b/litellm/llms/soniox/audio_transcription/transformation.py @@ -272,7 +272,7 @@ class SonioxAudioTranscriptionConfig(BaseAudioTranscriptionConfig): def build_response_from_payload( self, - payload: dict[str, object], # mutable-ok: mirrors override contract + payload: dict[str, object], model_response: TranscriptionResponse | None = None, response_format: str | None = None, ) -> TranscriptionResponse: diff --git a/litellm/llms/together_ai/rerank/transformation.py b/litellm/llms/together_ai/rerank/transformation.py index f1c3ca9e87d..037cd83096d 100644 --- a/litellm/llms/together_ai/rerank/transformation.py +++ b/litellm/llms/together_ai/rerank/transformation.py @@ -59,6 +59,6 @@ class TogetherAIRerankConfig: def transform_response( self, - response: dict[str, object], # mutable-ok: mirrors override contract + response: dict[str, object], ) -> RerankResponse: return self._transform_response(response) diff --git a/litellm/llms/vertex_ai/audio_transcription/gemini_transcribe_transformation.py b/litellm/llms/vertex_ai/audio_transcription/gemini_transcribe_transformation.py index f4db5eb110c..8d9c22828a5 100644 --- a/litellm/llms/vertex_ai/audio_transcription/gemini_transcribe_transformation.py +++ b/litellm/llms/vertex_ai/audio_transcription/gemini_transcribe_transformation.py @@ -62,7 +62,7 @@ class VertexGeminiAudioTranscriptionConfig(BaseAudioTranscriptionConfig, VertexB optional_params: Mapping[str, object], model: str, drop_params: bool, - ) -> dict[str, object]: # mutable-ok: BaseAudioTranscriptionConfig signature + ) -> dict[str, object]: supported_params: Final = frozenset(self.get_supported_openai_params(model)) mapped: Final = { **optional_params, @@ -86,7 +86,7 @@ class VertexGeminiAudioTranscriptionConfig(BaseAudioTranscriptionConfig, VertexB self, error_message: str, status_code: int, - headers: dict | Headers, # mutable-ok: base signature and VertexAIError take dict | Headers + headers: dict | Headers, ) -> BaseLLMException: return VertexAIError(status_code=status_code, message=error_message, headers=headers) @@ -99,7 +99,7 @@ class VertexGeminiAudioTranscriptionConfig(BaseAudioTranscriptionConfig, VertexB litellm_params: Mapping[str, object], api_key: str | None = None, api_base: str | None = None, - ) -> dict[str, str]: # mutable-ok: BaseAudioTranscriptionConfig signature + ) -> dict[str, str]: vertex_params: Final = dict(litellm_params) access_token, project_id = self._ensure_access_token( credentials=self.safe_get_vertex_ai_credentials(vertex_params), diff --git a/litellm/llms/vertex_ai/audio_transcription/realtime_transformation.py b/litellm/llms/vertex_ai/audio_transcription/realtime_transformation.py index 1d4a974fc54..91a487d6bab 100644 --- a/litellm/llms/vertex_ai/audio_transcription/realtime_transformation.py +++ b/litellm/llms/vertex_ai/audio_transcription/realtime_transformation.py @@ -342,10 +342,10 @@ class VertexChirpRealtimeConfig(BaseRealtimeConfig): def validate_environment( self, - headers: dict[str, str], # mutable-ok: BaseRealtimeConfig contract + headers: dict[str, str], model: str, api_key: str | None = None, - ) -> dict[str, str]: # mutable-ok: BaseRealtimeConfig contract + ) -> dict[str, str]: return headers def get_complete_url(self, api_base: str | None, model: str, api_key: str | None = None) -> str: diff --git a/litellm/llms/vertex_ai/batches/handler.py b/litellm/llms/vertex_ai/batches/handler.py index de5fe39cfc9..2ecb7463bc9 100644 --- a/litellm/llms/vertex_ai/batches/handler.py +++ b/litellm/llms/vertex_ai/batches/handler.py @@ -199,7 +199,7 @@ class VertexAIBatchPrediction(VertexLLM): def _resolve_fine_tuned_endpoint_model( self, vertex_batch_request: VertexAIBatchPredictionJob, - headers: dict[str, str], # mutable-ok: HTTPHandler.get only accepts dict headers + headers: dict[str, str], sync_handler: HTTPHandler, api_base: str | None, vertex_location: str, diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py index 6bdce393b82..470bb652e09 100644 --- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py @@ -394,7 +394,7 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): def map_web_search_options( self, - value: dict[str, object], # mutable-ok: mirrors override contract + value: dict[str, object], ) -> Tools: return self._map_web_search_options(value) @@ -459,7 +459,7 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): @classmethod def drop_search_tools_mixed_with_functions( cls, - optional_params: dict[str, object], # mutable-ok: mirrors override contract + optional_params: dict[str, object], ) -> None: return cls._drop_search_tools_mixed_with_functions(optional_params) @@ -802,7 +802,7 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): def map_function( self, value: list[dict[str, object]], # mutable-ok: mirrors override contract - optional_params: dict[str, object], # mutable-ok: mirrors override contract + optional_params: dict[str, object], ) -> list[Tools]: # mutable-ok: mirrors override contract return self._map_function(value, optional_params) @@ -1107,8 +1107,8 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): def map_audio_params( self, - value: dict[str, object], # mutable-ok: mirrors override contract - ) -> dict[str, object]: # mutable-ok: mirrors override contract + value: dict[str, object], + ) -> dict[str, object]: return self._map_audio_params(value) @staticmethod @@ -2481,7 +2481,7 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): cls, _candidates: list[Candidates], # mutable-ok: mirrors override contract model_response: Union[ModelResponse, "ModelResponseStream"], - standard_optional_params: dict[str, object], # mutable-ok: mirrors override contract + standard_optional_params: dict[str, object], cumulative_tool_call_index: int = 0, ) -> tuple[ # mutable-ok: mirrors override contract list[dict[str, object]], @@ -2637,7 +2637,7 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): def transform_google_generate_content_to_openai_model_response( self, - completion_response: GenerateContentResponseBody | dict[str, object], # mutable-ok: mirrors override contract + completion_response: GenerateContentResponseBody | dict[str, object], model_response: ModelResponse, model: str, logging_obj: LoggingClass, @@ -2664,7 +2664,7 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): self, messages: list[AllMessageValues], # mutable-ok: mirrors override contract model: str | None = None, - litellm_params: dict[str, object] | None = None, # mutable-ok: mirrors override contract + litellm_params: dict[str, object] | None = None, ) -> list[ContentType]: # mutable-ok: mirrors override contract return self._transform_messages(messages, model, litellm_params) diff --git a/litellm/llms/vertex_ai/interactions/transformation.py b/litellm/llms/vertex_ai/interactions/transformation.py index 36965fe666f..d6f7923951c 100644 --- a/litellm/llms/vertex_ai/interactions/transformation.py +++ b/litellm/llms/vertex_ai/interactions/transformation.py @@ -89,7 +89,7 @@ class VertexAIInteractionsConfig(VertexBase, GoogleAIStudioInteractionsConfig): headers: Mapping[str, str], model: str, litellm_params: GenericLiteLLMParams | None, - ) -> dict: # mutable-ok: BaseInteractionsAPIConfig declares plain-dict headers + ) -> dict: access_token, _ = self._mint(litellm_params or GenericLiteLLMParams()) return { "Content-Type": "application/json", @@ -117,7 +117,7 @@ class VertexAIInteractionsConfig(VertexBase, GoogleAIStudioInteractionsConfig): api_base: str, litellm_params: GenericLiteLLMParams, url_suffix: str = "", - ) -> tuple[str, dict]: # mutable-ok: BaseInteractionsAPIConfig declares a plain-dict request body + ) -> tuple[str, dict]: target: Final = self._target(api_base or None, litellm_params) return f"{target.interaction_url(interaction_id)}{url_suffix}", {} @@ -127,7 +127,7 @@ class VertexAIInteractionsConfig(VertexBase, GoogleAIStudioInteractionsConfig): api_base: str, litellm_params: GenericLiteLLMParams, headers: Mapping[str, str], - ) -> tuple[str, dict]: # mutable-ok: BaseInteractionsAPIConfig declares a plain-dict request body + ) -> tuple[str, dict]: return self._interaction_by_id_request(interaction_id, api_base, litellm_params) def transform_delete_interaction_request( @@ -136,7 +136,7 @@ class VertexAIInteractionsConfig(VertexBase, GoogleAIStudioInteractionsConfig): api_base: str, litellm_params: GenericLiteLLMParams, headers: Mapping[str, str], - ) -> tuple[str, dict]: # mutable-ok: BaseInteractionsAPIConfig declares a plain-dict request body + ) -> tuple[str, dict]: return self._interaction_by_id_request(interaction_id, api_base, litellm_params) def transform_cancel_interaction_request( @@ -145,5 +145,5 @@ class VertexAIInteractionsConfig(VertexBase, GoogleAIStudioInteractionsConfig): api_base: str, litellm_params: GenericLiteLLMParams, headers: Mapping[str, str], - ) -> tuple[str, dict]: # mutable-ok: BaseInteractionsAPIConfig declares a plain-dict request body + ) -> tuple[str, dict]: return self._interaction_by_id_request(interaction_id, api_base, litellm_params, url_suffix=":cancel") diff --git a/litellm/llms/vertex_ai/text_to_speech/transformation.py b/litellm/llms/vertex_ai/text_to_speech/transformation.py index a78145fdcf4..3aa11fb869a 100644 --- a/litellm/llms/vertex_ai/text_to_speech/transformation.py +++ b/litellm/llms/vertex_ai/text_to_speech/transformation.py @@ -508,11 +508,11 @@ class VertexAILyriaTextToSpeechConfig(VertexAITextToSpeechConfig): def map_openai_params( self, model: str, - optional_params: dict, # mutable-ok: inherited provider interface accepts a concrete parameter dictionary + optional_params: dict, voice: _LyriaVoice = None, drop_params: bool = False, - kwargs: dict | None = None, # mutable-ok: inherited provider interface accepts a concrete keyword dictionary - ) -> tuple[str | None, dict]: # mutable-ok: inherited provider interface returns concrete mapped parameters + kwargs: dict | None = None, + ) -> tuple[str | None, dict]: mapped_params: Final = dict(optional_params) base_model: Final = model.removeprefix("vertex_ai/") model_info: Final = self._get_model_info(model=model) @@ -552,7 +552,7 @@ class VertexAILyriaTextToSpeechConfig(VertexAITextToSpeechConfig): self, model: str, api_base: str | None, - litellm_params: dict, # mutable-ok: inherited provider interface accepts concrete LiteLLM parameters + litellm_params: dict, ) -> str: base_model: Final = model.removeprefix("vertex_ai/") model_info: Final = self._get_model_info(model=model) @@ -601,9 +601,9 @@ class VertexAILyriaTextToSpeechConfig(VertexAITextToSpeechConfig): model: str, input: str, voice: str | None, - optional_params: dict, # mutable-ok: inherited provider interface accepts concrete mapped parameters - litellm_params: dict, # mutable-ok: inherited provider interface accepts concrete LiteLLM parameters - headers: dict, # mutable-ok: inherited provider interface accepts and updates concrete HTTP headers + optional_params: dict, + litellm_params: dict, + headers: dict, ) -> TextToSpeechRequestData: access_token, project = self._ensure_access_token( credentials=self.safe_get_vertex_ai_credentials(litellm_params), @@ -619,7 +619,7 @@ class VertexAILyriaTextToSpeechConfig(VertexAITextToSpeechConfig): ) base_model: Final = model.removeprefix("vertex_ai/") model_info: Final = self._get_model_info(model=model) - request_body: Final[dict[str, object]] = ( # mutable-ok: HTTP dispatch requires a concrete provider payload + request_body: Final[dict[str, object]] = ( { "instances": [{"prompt": input}], "parameters": {"sample_count": 1}, diff --git a/litellm/llms/vertex_ai/vertex_gemma_models/transformation.py b/litellm/llms/vertex_ai/vertex_gemma_models/transformation.py index 9fa86ec30f5..c38804e3377 100644 --- a/litellm/llms/vertex_ai/vertex_gemma_models/transformation.py +++ b/litellm/llms/vertex_ai/vertex_gemma_models/transformation.py @@ -168,8 +168,8 @@ class VertexGemmaConfig(OpenAIGPTConfig): def _sync_post( client: HTTPHandler | httpx.Client | None, api_base: str, - headers: dict[str, str], # mutable-ok: forwarded to post(headers: dict | None) - request_data: dict[str, object], # mutable-ok: forwarded to post(json: dict | ...) + headers: dict[str, str], + request_data: dict[str, object], timeout: float | httpx.Timeout | None, ) -> httpx.Response: if isinstance(client, HTTPHandler): @@ -203,8 +203,8 @@ class VertexGemmaConfig(OpenAIGPTConfig): async def _async_post( client: AsyncHTTPHandler | httpx.AsyncClient | None, api_base: str, - headers: dict[str, str], # mutable-ok: forwarded to post(headers: dict | None) - request_data: dict[str, object], # mutable-ok: forwarded to post(json: dict | ...) + headers: dict[str, str], + request_data: dict[str, object], timeout: float | httpx.Timeout | None, ) -> httpx.Response: from litellm.llms.custom_httpx.http_handler import get_async_httpx_client diff --git a/litellm/llms/voyage/embedding/transformation_contextual.py b/litellm/llms/voyage/embedding/transformation_contextual.py index 870de8756bb..1ce2e6e3f29 100644 --- a/litellm/llms/voyage/embedding/transformation_contextual.py +++ b/litellm/llms/voyage/embedding/transformation_contextual.py @@ -65,11 +65,11 @@ class VoyageContextualEmbeddingConfig(BaseEmbeddingConfig): def map_openai_params( self, - non_default_params: dict, # mutable-ok: base class signature - optional_params: dict, # mutable-ok: base class signature + non_default_params: dict, + optional_params: dict, model: str, drop_params: bool, - ) -> dict: # mutable-ok: base class signature + ) -> dict: """ Map OpenAI params to Voyage params @@ -83,7 +83,7 @@ class VoyageContextualEmbeddingConfig(BaseEmbeddingConfig): def validate_environment( self, - headers: dict, # mutable-ok: base class signature + headers: dict, model: str, messages: list[AllMessageValues], optional_params: dict, diff --git a/litellm/llms/watsonx/common_utils.py b/litellm/llms/watsonx/common_utils.py index fb1fa8b9ffb..6444a5a6c34 100644 --- a/litellm/llms/watsonx/common_utils.py +++ b/litellm/llms/watsonx/common_utils.py @@ -354,5 +354,5 @@ class IBMWatsonXMixin: self, model: str, api_params: WatsonXAPIParams, - ) -> dict[str, object]: # mutable-ok: mirrors override contract + ) -> dict[str, object]: return self._prepare_payload(model, api_params) diff --git a/litellm/llms/xai/audio_transcription/transformation.py b/litellm/llms/xai/audio_transcription/transformation.py index e34034b5ae9..d15d874cf6d 100644 --- a/litellm/llms/xai/audio_transcription/transformation.py +++ b/litellm/llms/xai/audio_transcription/transformation.py @@ -79,7 +79,7 @@ class XAIAudioTranscriptionConfig(BaseAudioTranscriptionConfig): optional_params: Mapping[str, object], model: str, drop_params: bool, - ) -> dict[str, object]: # mutable-ok: base class signature returns dict + ) -> dict[str, object]: supported_params: Final = self.get_supported_openai_params(model) return { **optional_params, @@ -90,7 +90,7 @@ class XAIAudioTranscriptionConfig(BaseAudioTranscriptionConfig): self, error_message: str, status_code: int, - headers: dict[str, object] | Headers, # mutable-ok: base class signature takes dict + headers: dict[str, object] | Headers, ) -> BaseLLMException: return XAIAudioTranscriptionError(message=error_message, status_code=status_code, headers=headers) @@ -189,14 +189,14 @@ class XAIAudioTranscriptionConfig(BaseAudioTranscriptionConfig): def validate_environment( self, - headers: dict[str, object], # mutable-ok: base class signature takes and returns dict + headers: dict[str, object], model: str, messages: Sequence[AllMessageValues], optional_params: Mapping[str, object], litellm_params: Mapping[str, object], api_key: str | None = None, api_base: str | None = None, - ) -> dict[str, object]: # mutable-ok: base class signature returns dict + ) -> dict[str, object]: resolved_key: Final = XAIModelInfo.get_api_key(api_key) if resolved_key is None: raise ValueError("xAI API key is required. Set XAI_API_KEY environment variable.") diff --git a/litellm/llms/xai/batches/handler.py b/litellm/llms/xai/batches/handler.py index 3e45452d94c..0e9f7ada450 100644 --- a/litellm/llms/xai/batches/handler.py +++ b/litellm/llms/xai/batches/handler.py @@ -37,7 +37,7 @@ class _PageParams(TypedDict): pagination_token: NotRequired[ReadOnly[str]] -def _results_params(after: str | None, limit: int | None) -> dict[str, object]: # mutable-ok: httpx params +def _results_params(after: str | None, limit: int | None) -> dict[str, object]: if after is None: return dict(_PageParams(limit=limit or XAI_RESULTS_PAGE_SIZE)) return dict(_PageParams(limit=limit or XAI_RESULTS_PAGE_SIZE, pagination_token=after)) diff --git a/litellm/llms/xai/batches/transformation.py b/litellm/llms/xai/batches/transformation.py index 44a13ff9c84..a77beac9657 100644 --- a/litellm/llms/xai/batches/transformation.py +++ b/litellm/llms/xai/batches/transformation.py @@ -63,9 +63,7 @@ def get_xai_api_base(api_base: str | None) -> str: return resolved.removesuffix("/v1") -def get_xai_auth_headers( - headers: Mapping[str, str] = _EMPTY_HEADERS, api_key: str | None = None -) -> dict[str, str]: # mutable-ok: BaseConfig.validate_environment contract returns dict +def get_xai_auth_headers(headers: Mapping[str, str] = _EMPTY_HEADERS, api_key: str | None = None) -> dict[str, str]: resolved_key: Final = XAIModelInfo.get_api_key(api_key) if resolved_key is None: raise xai_batches_error( diff --git a/litellm/llms/xai/chat/transformation.py b/litellm/llms/xai/chat/transformation.py index 984b62eeb59..381d5da433b 100644 --- a/litellm/llms/xai/chat/transformation.py +++ b/litellm/llms/xai/chat/transformation.py @@ -422,7 +422,7 @@ class XAIChatConfig(OpenAIGPTConfig): @classmethod def normalize_openai_compatible_usage_totals( cls, - usage: Usage | dict[str, object] | None, # mutable-ok: mirrors override contract + usage: Usage | dict[str, object] | None, ) -> None: return cls._normalize_openai_compatible_usage_totals(usage) diff --git a/litellm/llms/xai/files/transformation.py b/litellm/llms/xai/files/transformation.py index 33baca79005..75c00d2f28c 100644 --- a/litellm/llms/xai/files/transformation.py +++ b/litellm/llms/xai/files/transformation.py @@ -35,7 +35,7 @@ from ..batches.transformation import ( xai_batches_error, ) -_NO_QUERY_PARAMS: Final[dict[str, str]] = {} # mutable-ok: BaseFilesConfig request transforms return tuple[str, dict] +_NO_QUERY_PARAMS: Final[dict[str, str]] = {} _DEFAULT_PURPOSE: Final[OpenAIFilesPurpose] = "batch" @@ -121,7 +121,7 @@ class XAIFilesConfig(BaseFilesConfig): litellm_params: Mapping[str, object], api_key: str | None = None, api_base: str | None = None, - ) -> dict[str, str]: # mutable-ok: BaseFilesConfig signature + ) -> dict[str, str]: return get_xai_auth_headers(headers, api_key) def get_supported_openai_params( @@ -132,10 +132,10 @@ class XAIFilesConfig(BaseFilesConfig): def map_openai_params( self, non_default_params: Mapping[str, object], - optional_params: dict[str, object], # mutable-ok: BaseConfig signature, returned as-is + optional_params: dict[str, object], model: str, drop_params: bool, - ) -> dict[str, object]: # mutable-ok: BaseConfig signature + ) -> dict[str, object]: return optional_params def transform_create_file_request( @@ -144,7 +144,7 @@ class XAIFilesConfig(BaseFilesConfig): create_file_data: CreateFileRequest, optional_params: Mapping[str, object], litellm_params: Mapping[str, object], - ) -> dict[str, object]: # mutable-ok: BaseFilesConfig signature + ) -> dict[str, object]: if "file" not in create_file_data: raise ValueError("File data is required") extracted: Final = extract_file_data(create_file_data["file"]) @@ -170,7 +170,7 @@ class XAIFilesConfig(BaseFilesConfig): file_id: str, optional_params: Mapping[str, object], litellm_params: Mapping[str, object], - ) -> tuple[str, dict[str, str]]: # mutable-ok: BaseFilesConfig signature + ) -> tuple[str, dict[str, str]]: return self._file_url(file_id, litellm_params), _NO_QUERY_PARAMS def transform_retrieve_file_response( @@ -186,7 +186,7 @@ class XAIFilesConfig(BaseFilesConfig): file_id: str, optional_params: Mapping[str, object], litellm_params: Mapping[str, object], - ) -> tuple[str, dict[str, str]]: # mutable-ok: BaseFilesConfig signature + ) -> tuple[str, dict[str, str]]: return self._file_url(file_id, litellm_params), _NO_QUERY_PARAMS def transform_delete_file_response( @@ -203,7 +203,7 @@ class XAIFilesConfig(BaseFilesConfig): purpose: str | None, optional_params: Mapping[str, object], litellm_params: Mapping[str, object], - ) -> tuple[str, dict[str, str]]: # mutable-ok: BaseFilesConfig signature + ) -> tuple[str, dict[str, str]]: return f"{_api_base_from(litellm_params)}/v1/files", _NO_QUERY_PARAMS def transform_list_files_next_request( @@ -211,7 +211,7 @@ class XAIFilesConfig(BaseFilesConfig): raw_response: httpx.Response, optional_params: Mapping[str, object], litellm_params: Mapping[str, object], - ) -> tuple[str, dict[str, str]] | None: # mutable-ok: BaseFilesConfig signature + ) -> tuple[str, dict[str, str]] | None: page: Final = XAIFileList.model_validate(raw_response.json()) if not page.pagination_token or not page.data: return None @@ -233,7 +233,7 @@ class XAIFilesConfig(BaseFilesConfig): file_content_request: FileContentRequest, optional_params: Mapping[str, object], litellm_params: Mapping[str, object], - ) -> tuple[str, dict[str, str]]: # mutable-ok: BaseFilesConfig signature + ) -> tuple[str, dict[str, str]]: file_id: Final = file_content_request.get("file_id") if file_id is None: raise ValueError("file_id is required to download file content") diff --git a/litellm/llms/xai/responses/transformation.py b/litellm/llms/xai/responses/transformation.py index 2f638da49c8..022070f9c2c 100644 --- a/litellm/llms/xai/responses/transformation.py +++ b/litellm/llms/xai/responses/transformation.py @@ -274,7 +274,7 @@ class XAIResponsesAPIConfig(OpenAIResponsesAPIConfig): def transform_streaming_response( self, model: str, - parsed_chunk: dict, # mutable-ok: overrides the base class signature + parsed_chunk: dict, logging_obj: LiteLLMLoggingObj, ) -> ResponsesAPIStreamingResponse: event: Final = super().transform_streaming_response( diff --git a/litellm/passthrough/main.py b/litellm/passthrough/main.py index ff9eb8fe79a..47d22fa67a0 100644 --- a/litellm/passthrough/main.py +++ b/litellm/passthrough/main.py @@ -82,7 +82,7 @@ class AsyncPassthroughStreamingResponse(AsyncGenerator[bytes, bytes]): self._spend = _SpendCollection(provider_config, litellm_logging_obj) self._flush_scheduled = False self._background_tasks: set[asyncio.Task] = set() # mutable-ok: instance set for background task tracking - self._hidden_params: dict[str, object] = {} # mutable-ok: router attaches response headers here in place + self._hidden_params: dict[str, object] = {} @property def status_code(self) -> int: diff --git a/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py b/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py index 418bdcd2941..9ee904dd4be 100644 --- a/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py +++ b/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py @@ -939,10 +939,10 @@ class MCPRequestHandler: requested_name: str, authorization_value: str, litellm_api_key: str, - mcp_server_auth_headers: dict[str, dict[str, str]] | None, # mutable-ok: existing MCP sink shape + mcp_server_auth_headers: dict[str, dict[str, str]] | None, request: Request, route: str, - ) -> tuple[UserAPIKeyAuth, dict[str, dict[str, str]] | None]: # mutable-ok: existing MCP sink shape + ) -> tuple[UserAPIKeyAuth, dict[str, dict[str, str]] | None]: if is_bridge_envelope_shaped(authorization_value): return await MCPRequestHandler._admit_dcr_bridge_delegate( server=server, diff --git a/litellm/proxy/_experimental/mcp_server/catalog.py b/litellm/proxy/_experimental/mcp_server/catalog.py index a957b352705..013ef5afab8 100644 --- a/litellm/proxy/_experimental/mcp_server/catalog.py +++ b/litellm/proxy/_experimental/mcp_server/catalog.py @@ -390,9 +390,7 @@ class CatalogSnapshots: closed.set() self._staged_routing.reset(routing_token) - async def _stage_servers( - self, rows: Sequence[BaseModel], *, reuse_unchanged: bool - ) -> dict[str, MCPServer]: # mutable-ok: assign_unique_short_prefix requires a dict registry + async def _stage_servers(self, rows: Sequence[BaseModel], *, reuse_unchanged: bool) -> dict[str, MCPServer]: from litellm.proxy._experimental.mcp_server.db import LiteLLM_MCPServerTable from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( carry_forward_resolved_oauth_endpoints, diff --git a/litellm/proxy/_experimental/mcp_server/mcp_debug.py b/litellm/proxy/_experimental/mcp_server/mcp_debug.py index 4691584ecbd..f3fc1c9d44c 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_debug.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_debug.py @@ -431,7 +431,7 @@ def _sensitive_field(key: str) -> bool: def _redact_object( fields: Mapping[str, JsonValue], -) -> dict[str, JsonValue]: # mutable-ok: the standard JSON encoder requires dict objects +) -> dict[str, JsonValue]: return {key: REDACTED if _sensitive_field(key) else value for key, value in fields.items()} diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index cb0eed8823c..26399b5ea9a 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -1287,10 +1287,10 @@ def _openapi_forwarded_extra_headers( def _resolve_openapi_tool_auth( mcp_server: MCPServer, mcp_auth_header: str | None, - mcp_server_auth_headers: Mapping[str, str | dict[str, str]] | None, # mutable-ok: sink shape - raw_headers: dict[str, str] | None, # mutable-ok: sink takes a concrete dict + mcp_server_auth_headers: Mapping[str, str | dict[str, str]] | None, + raw_headers: dict[str, str] | None, user_api_key_auth: UserAPIKeyAuth | None, -) -> tuple[str | None, dict[str, str] | None, str | dict[str, str] | None]: # mutable-ok: sink shapes +) -> tuple[str | None, dict[str, str] | None, str | dict[str, str] | None]: """The caller's upstream credential for one ``spec_path`` server, for both OpenAPI dispatch arms. A per-server ``x-mcp-{alias}-authorization`` wins over the deprecated global / BYOK @@ -1715,7 +1715,7 @@ def _create_elicitation_callback(): def _record_mcp_guardrail_evaluations( - synthetic_llm_data: dict[str, object], # mutable-ok: `_sync_guardrail_info_to_logging_obj` takes a concrete dict + synthetic_llm_data: dict[str, object], litellm_logging_obj: "LiteLLMLoggingObj | None", ) -> None: """Bridge guardrail decision records off an MCP synthetic request onto the request's logger. @@ -2011,8 +2011,8 @@ class MCPServerManager: "gmail_send_email": "zapier_mcp_server", } """ - self._listed_tools_by_server_id: dict[str, _ListedToolsByCaller] = {} # mutable-ok: refreshed per tools/list - self._listed_tools_generations: dict[str, int] = {} # mutable-ok: bumped per server save + self._listed_tools_by_server_id: dict[str, _ListedToolsByCaller] = {} + self._listed_tools_generations: dict[str, int] = {} self._upstream_initialize_instructions_by_server_id: dict[str, str] = {} # Per-server monotonic timestamp of last upstream prefetch attempt (success, # empty result, or failure). Used to throttle re-probes for servers that do @@ -2468,7 +2468,7 @@ class MCPServerManager: used_aliases: Final = set() # server_id -> the config server_name that claimed it, so a pinned id cannot silently # overwrite another server's entry in self.config_mcp_servers. - assigned_server_ids: MutableMapping[str, str] = {} # mutable-ok: per-load collision index + assigned_server_ids: MutableMapping[str, str] = {} _validate_config_server_names(mcp_servers_config) identifier_owners: Final = _config_identifier_owners(mcp_servers_config, mcp_aliases) diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index c046fa93d54..4021c7ddb47 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -627,8 +627,8 @@ if MCP_AVAILABLE: # still reading the shared object. _stateful_session_locks: Final[dict[str, asyncio.Lock]] = {} _stateful_session_active_request_counts: Final[dict[str, int]] = {} - _stateful_session_client_info: Final[dict[str, Implementation]] = {} # mutable-ok: cleared on session teardown - _admin_terminated_session_ids: Final[dict[str, float]] = {} # mutable-ok: admin-closed id -> last replay + _stateful_session_client_info: Final[dict[str, Implementation]] = {} + _admin_terminated_session_ids: Final[dict[str, float]] = {} class _TerminableTransport(Protocol): async def terminate(self) -> None: ... diff --git a/litellm/proxy/_experimental/mcp_server/tool_registry.py b/litellm/proxy/_experimental/mcp_server/tool_registry.py index dd25f09cc57..f0a5a322df5 100644 --- a/litellm/proxy/_experimental/mcp_server/tool_registry.py +++ b/litellm/proxy/_experimental/mcp_server/tool_registry.py @@ -25,24 +25,22 @@ class MCPToolRegistry: def __init__(self): # Registry to store all registered tools - self.published_tools: dict[str, MCPTool] = {} # mutable-ok: register_tool writes entries through .tools + self.published_tools: dict[str, MCPTool] = {} self._catalog_tools: ContextVar[tuple[dict[str, MCPTool], asyncio.Event] | None] = ContextVar( "mcp_catalog_tools", default=None ) @property - def tools(self) -> dict[str, MCPTool]: # mutable-ok: register_tool mutates the returned mapping + def tools(self) -> dict[str, MCPTool]: scoped: Final = self._catalog_tools.get() return scoped[0] if scoped is not None and not scoped[1].is_set() else self.published_tools @tools.setter - def tools(self, tools: dict[str, MCPTool]) -> None: # mutable-ok: stored dict is mutated by register_tool + def tools(self, tools: dict[str, MCPTool]) -> None: self.published_tools = tools @contextmanager - def catalog_scope( - self, tools: Mapping[str, MCPTool] - ) -> Generator[dict[str, MCPTool]]: # mutable-ok: yields the mutable staged copy + def catalog_scope(self, tools: Mapping[str, MCPTool]) -> Generator[dict[str, MCPTool]]: detached: Final = dict(tools) closed: Final = asyncio.Event() token: Final = self._catalog_tools.set((detached, closed)) diff --git a/litellm/proxy/_experimental/mcp_server/tool_search.py b/litellm/proxy/_experimental/mcp_server/tool_search.py index 217ffcd6b5f..d72e7c99a7a 100644 --- a/litellm/proxy/_experimental/mcp_server/tool_search.py +++ b/litellm/proxy/_experimental/mcp_server/tool_search.py @@ -579,14 +579,14 @@ async def handle_mcp_tool_search( async def handle_mcp_proxy_tool( name: str, - arguments: dict[str, JsonValue], # mutable-ok: MCP dispatcher passes mutable JSON call arguments + arguments: dict[str, JsonValue], user_api_key_dict: UserAPIKeyAuth, client_ip: str | None = None, mcp_servers: list[str] | None = None, # mutable-ok: preserve MCP scope container for existing resolver mcp_auth_header: str | None = None, - mcp_server_auth_headers: dict[str, dict[str, str]] | None = None, # mutable-ok: preserve forwarded headers - oauth2_headers: dict[str, str] | None = None, # mutable-ok: preserve forwarded headers - raw_headers: dict[str, str] | None = None, # mutable-ok: preserve request headers + mcp_server_auth_headers: dict[str, dict[str, str]] | None = None, + oauth2_headers: dict[str, str] | None = None, + raw_headers: dict[str, str] | None = None, litellm_logging_obj: LiteLLMLoggingObj | None = None, ) -> CallToolResult: from fastapi import HTTPException diff --git a/litellm/proxy/agent_endpoints/endpoints.py b/litellm/proxy/agent_endpoints/endpoints.py index c0f34a24a0a..fdc7f2900a9 100644 --- a/litellm/proxy/agent_endpoints/endpoints.py +++ b/litellm/proxy/agent_endpoints/endpoints.py @@ -172,7 +172,7 @@ async def _attach_keys_to_agents(agents: Sequence[AgentResponse], prisma_client) def _redact_agent_litellm_params_dict( litellm_params: Mapping[str, object], -) -> dict[str, object]: # mutable-ok: AgentResponse.litellm_params is declared as a plain dict, not Mapping +) -> dict[str, object]: """Type-narrowing wrapper: a dict in always yields a dict back from ``redact_sensitive_agent_litellm_params``, which the function's general (possible-JSON-string, possibly-None) signature can't express.""" diff --git a/litellm/proxy/auth/handle_jwt.py b/litellm/proxy/auth/handle_jwt.py index d600fb3486b..19ac3930458 100644 --- a/litellm/proxy/auth/handle_jwt.py +++ b/litellm/proxy/auth/handle_jwt.py @@ -256,7 +256,7 @@ class JWTHandler: self.http_handler = HTTPHandler() self.leeway = 0 # Per-cache-key locks so a TTL lapse triggers one refresh instead of one per in-flight request. - self._refresh_locks: dict[str, asyncio.Lock] = {} # mutable-ok: lock registry, keyed by JWKS url + self._refresh_locks: dict[str, asyncio.Lock] = {} self.agent_lookup: AgentLookup = _NoRegisteredAgents() def bind_agent_lookup(self, agent_lookup: AgentLookup) -> None: diff --git a/litellm/proxy/auth/user_api_key_auth.py b/litellm/proxy/auth/user_api_key_auth.py index 65f9b09a4d3..af487ad19af 100644 --- a/litellm/proxy/auth/user_api_key_auth.py +++ b/litellm/proxy/auth/user_api_key_auth.py @@ -265,7 +265,7 @@ async def _normalize_claude_model( async def _resolve_router_settings_model_group_alias( - request_data: dict[str, object], # mutable-ok: the request body is rewritten in place for every downstream reader + request_data: dict[str, object], valid_token: UserAPIKeyAuth, request: Request | None, route: str, diff --git a/litellm/proxy/batches_endpoints/endpoints.py b/litellm/proxy/batches_endpoints/endpoints.py index 2972b5043da..485ea137081 100644 --- a/litellm/proxy/batches_endpoints/endpoints.py +++ b/litellm/proxy/batches_endpoints/endpoints.py @@ -156,7 +156,7 @@ def _litellm_metadata_of(data: MutableMapping[str, object]) -> MutableMapping[st existing: Final = data.get("litellm_metadata") if isinstance(existing, MutableMapping): return existing - created: Final[dict[str, object]] = {} # mutable-ok: the logging layer copies and extends this mapping + created: Final[dict[str, object]] = {} data["litellm_metadata"] = created # rebind-ok: the success handler reads the request's own mapping return created diff --git a/litellm/proxy/batches_endpoints/litellm_executed_batches.py b/litellm/proxy/batches_endpoints/litellm_executed_batches.py index 984aed71b1c..b6457423fef 100644 --- a/litellm/proxy/batches_endpoints/litellm_executed_batches.py +++ b/litellm/proxy/batches_endpoints/litellm_executed_batches.py @@ -650,7 +650,7 @@ class LiteLLMExecutedBatchRunner: raise TypeError(f"the router has no callable for {endpoint}") return method - def _row_metadata(self, run: _BatchRun) -> dict[str, object]: # mutable-ok: router updates metadata in place + def _row_metadata(self, run: _BatchRun) -> dict[str, object]: return { **LiteLLMProxyRequestSetup.get_sanitized_user_information_from_key(run.user_api_key_dict), "user_api_key": LiteLLMProxyRequestSetup.get_logged_api_key(run.user_api_key_dict), diff --git a/litellm/proxy/client/cli/commands/pi.py b/litellm/proxy/client/cli/commands/pi.py index 22c9755ab8e..87c3d3e8db3 100644 --- a/litellm/proxy/client/cli/commands/pi.py +++ b/litellm/proxy/client/cli/commands/pi.py @@ -168,16 +168,12 @@ def models_json_path(env: Mapping[str, str]) -> Path: return root / "models.json" -def _model_entry( - model_id: str, limits: Mapping[str, ModelLimits] -) -> dict[str, JsonValue]: # mutable-ok: JSON object is serialized +def _model_entry(model_id: str, limits: Mapping[str, ModelLimits]) -> dict[str, JsonValue]: limit: Final = limits.get(model_id) - context: Final[dict[str, JsonValue]] = ( # mutable-ok: JSON field + context: Final[dict[str, JsonValue]] = ( {"contextWindow": limit.context_window} if limit and limit.context_window else {} ) - output: Final[dict[str, JsonValue]] = ( # mutable-ok: JSON field - {"maxTokens": limit.max_tokens} if limit and limit.max_tokens else {} - ) + output: Final[dict[str, JsonValue]] = {"maxTokens": limit.max_tokens} if limit and limit.max_tokens else {} return {"id": model_id, **context, **output} @@ -185,7 +181,7 @@ def provider_block( base_url: str, model_ids: tuple[str, ...], limits: Mapping[str, ModelLimits] = _NO_LIMITS, -) -> dict[str, JsonValue]: # mutable-ok: JSON object is serialized +) -> dict[str, JsonValue]: """openai-completions is the one API shape every LiteLLM model serves. Real contextWindow/maxTokens matter: pi otherwise assumes 128k/16384, which diff --git a/litellm/proxy/common_utils/error_body_call_id.py b/litellm/proxy/common_utils/error_body_call_id.py index fb5456b877e..735a049b2b2 100644 --- a/litellm/proxy/common_utils/error_body_call_id.py +++ b/litellm/proxy/common_utils/error_body_call_id.py @@ -5,7 +5,7 @@ from pydantic import TypeAdapter INCLUDE_CALL_ID_IN_ERROR_BODY_SETTING: Final = "include_call_id_in_error_body" LITELLM_CALL_ID_BODY_KEY: Final = "litellm_call_id" -JSON_OBJECT: Final[TypeAdapter[dict[str, object]]] = TypeAdapter(dict[str, object]) # mutable-ok: JSONResponse input +JSON_OBJECT: Final[TypeAdapter[dict[str, object]]] = TypeAdapter(dict[str, object]) def error_body_call_id(general_settings: Mapping[str, object], call_id: str | None) -> str | None: @@ -14,7 +14,7 @@ def error_body_call_id(general_settings: Mapping[str, object], call_id: str | No return call_id if call_id else None -def with_call_id(error: dict[str, object], call_id: str | None) -> dict[str, object]: # mutable-ok: JSONResponse input +def with_call_id(error: dict[str, object], call_id: str | None) -> dict[str, object]: if call_id is None: return error return {**error, LITELLM_CALL_ID_BODY_KEY: call_id} diff --git a/litellm/proxy/common_utils/http_parsing_utils.py b/litellm/proxy/common_utils/http_parsing_utils.py index fa288bad9fe..d1f34f7933f 100644 --- a/litellm/proxy/common_utils/http_parsing_utils.py +++ b/litellm/proxy/common_utils/http_parsing_utils.py @@ -359,7 +359,7 @@ def _safe_set_request_parsed_body( def rewrite_request_model( - request_data: dict[str, object], # mutable-ok: the request body is rewritten in place for every downstream reader + request_data: dict[str, object], request: Request | None, model: str, ) -> None: diff --git a/litellm/proxy/common_utils/openai_error_payload.py b/litellm/proxy/common_utils/openai_error_payload.py index f092f9637f8..e315bff61b2 100644 --- a/litellm/proxy/common_utils/openai_error_payload.py +++ b/litellm/proxy/common_utils/openai_error_payload.py @@ -57,7 +57,7 @@ def openai_error_param(exc: object) -> str | None: return carried if isinstance(carried, str) and carried != STRINGIFIED_NONE else None -def litellm_call_id_headers(litellm_call_id: str | None) -> dict[str, str] | None: # mutable-ok: ProxyException.headers +def litellm_call_id_headers(litellm_call_id: str | None) -> dict[str, str] | None: if litellm_call_id is None: return None return {LITELLM_CALL_ID_HEADER: litellm_call_id} diff --git a/litellm/proxy/common_utils/semantic_text_index.py b/litellm/proxy/common_utils/semantic_text_index.py index 7450ef2b509..5861fd6a0f0 100644 --- a/litellm/proxy/common_utils/semantic_text_index.py +++ b/litellm/proxy/common_utils/semantic_text_index.py @@ -55,7 +55,7 @@ class _EmbeddingRequest(LiteLLMBaseModel): model: str input: tuple[str, ...] - metadata: dict[str, object] # mutable-ok: the router mutates the metadata dict it is handed + metadata: dict[str, object] def cosine_similarity(left: Vector, right: Vector) -> float: @@ -64,7 +64,7 @@ def cosine_similarity(left: Vector, right: Vector) -> float: return dot / norms if norms else 0.0 -def embedding_spend_metadata(user_api_key_dict: UserAPIKeyAuth) -> dict[str, object]: # mutable-ok: router mutates it +def embedding_spend_metadata(user_api_key_dict: UserAPIKeyAuth) -> dict[str, object]: from litellm.proxy.litellm_pre_call_utils import LiteLLMProxyRequestSetup return { diff --git a/litellm/proxy/config_resolvers/_descriptors.py b/litellm/proxy/config_resolvers/_descriptors.py index e2e0534bf7b..8903d1d9e00 100644 --- a/litellm/proxy/config_resolvers/_descriptors.py +++ b/litellm/proxy/config_resolvers/_descriptors.py @@ -69,7 +69,5 @@ def resolve_fields( """ resolved: Final = tuple(_resolve_one(descriptor, db_values, env, empty_db_is_set) for descriptor in descriptors) values: Final = {field_name: value for field_name, value, _ in resolved} - provenance: Final[dict[str, FieldSource]] = dict( # mutable-ok: public resolver contract returns a plain dict - (field_name, source) for field_name, _, source in resolved - ) + provenance: Final[dict[str, FieldSource]] = dict((field_name, source) for field_name, _, source in resolved) return values, provenance diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index 7bbb0e63202..56b1b3176c6 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -1591,7 +1591,7 @@ class DBSpendUpdateWriter: ): verbose_proxy_logger.debug("acquired lock for spend updates") - uncommitted: dict[str, Any] = {} # mutable-ok: tracks popped categories still needing commit + uncommitted: dict[str, Any] = {} committed_spend_tables: Final[list[_SpendTableName]] = [] # mutable-ok: filled as each table lands try: diff --git a/litellm/proxy/db/gateway_request_tracking.py b/litellm/proxy/db/gateway_request_tracking.py index e3483ca3215..b236acdb75d 100644 --- a/litellm/proxy/db/gateway_request_tracking.py +++ b/litellm/proxy/db/gateway_request_tracking.py @@ -66,7 +66,7 @@ class GatewayRequestAccumulator: """Sink for the request-metrics middleware. ``record`` is sync and never awaits.""" def __init__(self) -> None: - self._counts: dict[GatewayRequestKey, GatewayRequestCounts] = {} # mutable-ok: bounded fold, drained per flush + self._counts: dict[GatewayRequestKey, GatewayRequestCounts] = {} def record(self, *, category: BillableCategory, route: str, status_code: int) -> None: key: Final = GatewayRequestKey(date=_utc_date(), category=category.value, route=route) @@ -100,7 +100,7 @@ class GatewayRequestAccumulator: def fold_counts(items: Iterable[tuple[GatewayRequestKey, GatewayRequestCounts]]) -> GatewayRequestSnapshot: """Sum counts key-wise; the result stays bounded by (date x category x route).""" - folded: Final[dict[GatewayRequestKey, GatewayRequestCounts]] = {} # mutable-ok: local fold returned once + folded: Final[dict[GatewayRequestKey, GatewayRequestCounts]] = {} for key, counts in items: existing = folded.get(key, _EMPTY) folded[key] = GatewayRequestCounts( diff --git a/litellm/proxy/db/shadow_eval_funnel.py b/litellm/proxy/db/shadow_eval_funnel.py index 0986a502c74..278a8a20f10 100644 --- a/litellm/proxy/db/shadow_eval_funnel.py +++ b/litellm/proxy/db/shadow_eval_funnel.py @@ -20,7 +20,7 @@ ShadowEvalFunnelStage = Literal["not_sampled", "unjudgeable", "shed", "withheld" FUNNEL_STAGES: Final[tuple[ShadowEvalFunnelStage, ...]] = ("not_sampled", "unjudgeable", "shed", "withheld") -_pending: dict[str, dict[ShadowEvalFunnelStage, int]] = {} # mutable-ok: module-level queue, single event loop +_pending: dict[str, dict[ShadowEvalFunnelStage, int]] = {} _FUNNEL_PLACEHOLDERS: Final = ", ".join(f"${n + 2}" for n in range(len(FUNNEL_STAGES))) diff --git a/litellm/proxy/guardrails/auto_router_compression.py b/litellm/proxy/guardrails/auto_router_compression.py index ecb182ab33c..c8198774c92 100644 --- a/litellm/proxy/guardrails/auto_router_compression.py +++ b/litellm/proxy/guardrails/auto_router_compression.py @@ -139,7 +139,7 @@ def _active_compression_guardrails() -> tuple["CustomGuardrail", ...]: async def arm_pre_call( - data: dict[str, object], # mutable-ok: arms the live request dict in place + data: dict[str, object], llm_router: "Router | None", ) -> None: """Apply an auto router's compression policy, if any, before guardrails run. diff --git a/litellm/proxy/guardrails/guardrail_hooks/agent_365/agent_365.py b/litellm/proxy/guardrails/guardrail_hooks/agent_365/agent_365.py index ac0a6911184..84c28e98054 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/agent_365/agent_365.py +++ b/litellm/proxy/guardrails/guardrail_hooks/agent_365/agent_365.py @@ -193,7 +193,7 @@ class Agent365Guardrail(CustomGuardrail): self.async_handler = async_handler or get_async_httpx_client( llm_provider=httpxSpecialProvider.GuardrailCallback ) - self._obo_token_cache: OrderedDict[str, tuple[str, float]] = OrderedDict() # mutable-ok: lock-guarded LRU + self._obo_token_cache: OrderedDict[str, tuple[str, float]] = OrderedDict() self._obo_cache_lock = threading.Lock() verbose_proxy_logger.info("Initialized Microsoft Agent 365 guardrail: %s", guardrail_name) @@ -210,9 +210,9 @@ class Agent365Guardrail(CustomGuardrail): self, user_api_key_dict: "UserAPIKeyAuth", cache: "DualCache", - data: dict, # mutable-ok: hook contract; guardrail logging appends into the request metadata in place + data: dict, call_type: str, - ) -> Exception | str | dict | None: # mutable-ok: CustomGuardrail.async_pre_call_hook contract + ) -> Exception | str | dict | None: if call_type not in _MCP_CALL_TYPES: return data if "mcp_tool_name" not in data: @@ -294,12 +294,12 @@ class Agent365Guardrail(CustomGuardrail): def _handle_evaluate_error( self, - data: dict, # mutable-ok: guardrail logging appends into the request metadata in place + data: dict, tool_name: str, assertion: str, response: httpx.Response, latency_ms: float, - ) -> dict | None: # mutable-ok: returns the request data dict per hook contract on fail_open + ) -> dict | None: if response.status_code in (408, 429): self._handle_throttled( data=data, @@ -337,11 +337,11 @@ class Agent365Guardrail(CustomGuardrail): def _enforce_verdict( self, - data: dict, # mutable-ok: guardrail logging appends into the request metadata in place + data: dict, tool_name: str, response: httpx.Response, latency_ms: float, - ) -> dict: # mutable-ok: returns the request data dict per hook contract + ) -> dict: try: parsed_verdict: Final = response.json() except ValueError: @@ -404,7 +404,7 @@ class Agent365Guardrail(CustomGuardrail): self, data: Mapping[str, object], user_api_key_dict: "UserAPIKeyAuth", - ) -> dict[str, object]: # mutable-ok: JSON body for AsyncHTTPHandler.post, which requires dict + ) -> dict[str, object]: tool_name: Final = str(data.get("mcp_tool_name") or "") arguments: Final = data.get("mcp_arguments") server_name: Final = str(data.get("mcp_server_name") or "litellm") @@ -415,7 +415,7 @@ class Agent365Guardrail(CustomGuardrail): description=description if isinstance(description, str) and description else None, input_schema=_parse_tool_input_schema(data.get("mcp_input_schema")), ) - payload: Final[dict[str, object]] = { # mutable-ok: JSON body with optional fields added below + payload: Final[dict[str, object]] = { "tool": tool_reference.model_dump(by_alias=True, exclude_none=True), "serverName": server_name, "conversationId": self._resolve_conversation_id(data), @@ -516,9 +516,9 @@ class Agent365Guardrail(CustomGuardrail): async def _post_allowing_error_status( self, url: str, - headers: dict[str, str], # mutable-ok: AsyncHTTPHandler.post requires dict - data: dict[str, str] | None = None, # mutable-ok: AsyncHTTPHandler.post requires dict - json: dict[str, object] | None = None, # mutable-ok: AsyncHTTPHandler.post requires dict + headers: dict[str, str], + data: dict[str, str] | None = None, + json: dict[str, object] | None = None, ) -> httpx.Response: try: return await self.async_handler.post( @@ -533,7 +533,7 @@ class Agent365Guardrail(CustomGuardrail): def _handle_caller_fault( self, - data: dict, # mutable-ok: guardrail logging appends into the request metadata in place + data: dict, tool_name: str, status_code: int, reason: str, @@ -556,7 +556,7 @@ class Agent365Guardrail(CustomGuardrail): def _handle_throttled( self, - data: dict, # mutable-ok: guardrail logging appends into the request metadata in place + data: dict, tool_name: str, reason: str, latency_ms: float | None, @@ -585,13 +585,13 @@ class Agent365Guardrail(CustomGuardrail): def _handle_unavailable( self, - data: dict, # mutable-ok: guardrail logging appends into the request metadata in place + data: dict, tool_name: str, reason: str, defender_status: str | None = None, correlation_id: str | None = None, latency_ms: float | None = None, - ) -> dict: # mutable-ok: returns the request data dict per hook contract + ) -> dict: if self.unreachable_fallback == "fail_open": verbose_proxy_logger.error( "Agent 365 guardrail (%s): %s; unreachable_fallback='fail_open', allowing tool call '%s' unscanned", @@ -628,7 +628,7 @@ class Agent365Guardrail(CustomGuardrail): def _record_verdict( self, - data: dict[str, object], # mutable-ok: standard guardrail logging appends into the request metadata in place + data: dict[str, object], verdict: str, guardrail_status: "GuardrailStatus", defender_status: str | None, @@ -636,7 +636,7 @@ class Agent365Guardrail(CustomGuardrail): latency_ms: float | None, reason: str | None = None, ) -> None: - payload: Final[dict[str, object]] = {"verdict": verdict} # mutable-ok: optional fields added below + payload: Final[dict[str, object]] = {"verdict": verdict} if defender_status: payload["defender_status"] = defender_status if correlation_id: diff --git a/litellm/proxy/guardrails/guardrail_hooks/alice/alice.py b/litellm/proxy/guardrails/guardrail_hooks/alice/alice.py index f677a9b3b96..8f16be0dcd9 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/alice/alice.py +++ b/litellm/proxy/guardrails/guardrail_hooks/alice/alice.py @@ -178,7 +178,7 @@ class AliceGuardrail(CustomGuardrail): async def apply_guardrail( self, inputs: GenericGuardrailAPIInputs, - request_data: dict[str, object], # mutable-ok: overrides CustomGuardrail.apply_guardrail's plain-dict contract + request_data: dict[str, object], input_type: Literal["request", "response"], logging_obj: Optional["LiteLLMLoggingObj"] = None, ) -> GenericGuardrailAPIInputs: diff --git a/litellm/proxy/guardrails/guardrail_hooks/azure/prompt_shield.py b/litellm/proxy/guardrails/guardrail_hooks/azure/prompt_shield.py index 6a9c5aa4fbb..9a5303776ab 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/azure/prompt_shield.py +++ b/litellm/proxy/guardrails/guardrail_hooks/azure/prompt_shield.py @@ -42,7 +42,7 @@ if TYPE_CHECKING: # Per-invocation billing counters. A ContextVar rather than request metadata: the # decorator can swap out ``request_data``, metadata is client-forgeable, and # concurrent guardrails run in separate tasks with their own context copy. -_billing_usage_stash: Final[ContextVar[dict[str, int] | None]] = ContextVar( # mutable-ok: task-local stash +_billing_usage_stash: Final[ContextVar[dict[str, int] | None]] = ContextVar( "azure_prompt_shield_billing_usage", default=None ) @@ -60,7 +60,7 @@ def _resolved_secret_value(value: object) -> object: return value -def _updated_param(litellm_params: "LitellmParams | dict", key: str) -> object: # mutable-ok: DB dict +def _updated_param(litellm_params: "LitellmParams | dict", key: str) -> object: """Read one param from a Mapping or a pydantic object, including pydantic extras (cost_tier / price_per_1000_text_records live there), which the base class ``vars()`` loop never sees.""" @@ -156,7 +156,7 @@ class AzureContentSafetyPromptShieldGuardrail(AzureGuardrailBase, CustomGuardrai async def async_make_request( self, user_prompt: str, - usage_accumulator: MutableMapping[str, int], # mutable-ok: callee-filled accumulator + usage_accumulator: MutableMapping[str, int], ) -> "AzurePromptShieldGuardrailResponse": """ Make a request to the Azure Prompt Shield API. @@ -222,7 +222,7 @@ class AzureContentSafetyPromptShieldGuardrail(AzureGuardrailBase, CustomGuardrai logging_obj: "LiteLLMLoggingObj | None" = None, ) -> GenericGuardrailAPIInputs: _billing_usage_stash.set(None) - usage: Final[dict[str, int]] = {} # mutable-ok: per-invocation billing accumulator + usage: Final[dict[str, int]] = {} try: for text in inputs.get("texts") or (): if text: @@ -256,7 +256,7 @@ class AzureContentSafetyPromptShieldGuardrail(AzureGuardrailBase, CustomGuardrai if user_prompt: verbose_proxy_logger.debug("Azure Prompt Shield: User prompt: %s", user_prompt) - usage: Final[dict[str, int]] = {} # mutable-ok: per-invocation billing accumulator + usage: Final[dict[str, int]] = {} try: await self.async_make_request( user_prompt=user_prompt, @@ -268,7 +268,7 @@ class AzureContentSafetyPromptShieldGuardrail(AzureGuardrailBase, CustomGuardrai verbose_proxy_logger.warning("Azure Prompt Shield: No user prompt found") return None - def update_in_memory_litellm_params(self, litellm_params: "LitellmParams | dict") -> None: # mutable-ok: DB dict + def update_in_memory_litellm_params(self, litellm_params: "LitellmParams | dict") -> None: """Apply updated params in place, re-resolving billing and credentials. Pricing is read via ``_updated_param`` (the values are pydantic extras, and @@ -279,7 +279,7 @@ class AzureContentSafetyPromptShieldGuardrail(AzureGuardrailBase, CustomGuardrai """ cost_tier: Final = _resolved_cost_tier(_updated_param(litellm_params, "cost_tier")) price: Final = _resolved_price(_updated_param(litellm_params, "price_per_1000_text_records"), cost_tier) - resolved_credentials: dict[str, object] = {} # mutable-ok: staged before mutation + resolved_credentials: dict[str, object] = {} for cred_key in ("api_key", "api_base"): cred_value = _updated_param(litellm_params, cred_key) if isinstance(cred_value, str) and cred_value.startswith("os.environ/"): @@ -324,14 +324,14 @@ class AzureContentSafetyPromptShieldGuardrail(AzureGuardrailBase, CustomGuardrai def _process_response( self, - response: dict | None, # mutable-ok: matches CustomGuardrail._process_response signature - request_data: dict, # mutable-ok: matches CustomGuardrail._process_response signature + response: dict | None, + request_data: dict, start_time: float | None = None, end_time: float | None = None, duration: float | None = None, event_type: GuardrailEventHooks | None = None, - original_inputs: dict | None = None, # mutable-ok: matches CustomGuardrail._process_response signature - ) -> dict | None: # mutable-ok: matches CustomGuardrail._process_response return + original_inputs: dict | None = None, + ) -> dict | None: """Override to attach the Azure billing tracing detail (usage counters and estimated cost) and the ``azure`` provider label to the recorded guardrail information. Follows the OpenAI moderation override pattern @@ -357,7 +357,7 @@ class AzureContentSafetyPromptShieldGuardrail(AzureGuardrailBase, CustomGuardrai def _process_error( self, e: Exception, - request_data: dict, # mutable-ok: matches CustomGuardrail._process_error signature + request_data: dict, start_time: float | None = None, end_time: float | None = None, duration: float | None = None, diff --git a/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py b/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py index 37318feba45..aad1fce31e0 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py +++ b/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py @@ -1004,7 +1004,7 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): credentials: "Credentials | None", aws_region_name: str, api_key: str | None, - request_data: dict | None, # mutable-ok: proxy request body dict, mutated by the logging helper + request_data: dict | None, event_type: GuardrailEventHooks, start_time: "datetime", allow_chunking: bool, @@ -1142,7 +1142,7 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): credentials: "Credentials | None", aws_region_name: str, api_key: str | None, - request_data: dict | None, # mutable-ok: proxy request body dict, mutated by the logging helper + request_data: dict | None, event_type: GuardrailEventHooks, start_time: "datetime", completed_chunk_usages: list[BedrockGuardrailUsage], # mutable-ok: passed through to the single-call layer @@ -1192,7 +1192,7 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): credentials: "Credentials | None", aws_region_name: str, api_key: str | None, - request_data: dict | None, # mutable-ok: proxy request body dict, mutated by the logging helper + request_data: dict | None, event_type: GuardrailEventHooks, start_time: "datetime", completed_chunk_usages: list[BedrockGuardrailUsage], # mutable-ok: billed-chunk usage accumulator @@ -1281,8 +1281,8 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): def _log_apply_guardrail_attempt( self, httpx_response: httpx.Response, - json_response: dict, # mutable-ok: raw AWS JSON payload - request_data: dict | None, # mutable-ok: proxy request body dict, mutated by the logging helper + json_response: dict, + request_data: dict | None, event_type: GuardrailEventHooks, start_time: "datetime", aws_region_name: str | None, @@ -1322,7 +1322,7 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): def _log_apply_guardrail_success( self, merged_response: BedrockGuardrailResponse, - request_data: dict | None, # mutable-ok: proxy request body dict, mutated by the logging helper + request_data: dict | None, event_type: GuardrailEventHooks, start_time: "datetime", aws_region_name: str | None, @@ -1356,7 +1356,7 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): def _log_apply_guardrail_failure( self, detail: object, - request_data: dict | None, # mutable-ok: proxy request body dict, mutated by the logging helper + request_data: dict | None, event_type: GuardrailEventHooks, start_time: "datetime", aws_region_name: str | None, diff --git a/litellm/proxy/guardrails/guardrail_hooks/conduct/conduct.py b/litellm/proxy/guardrails/guardrail_hooks/conduct/conduct.py index 077255017f4..50e382a33c0 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/conduct/conduct.py +++ b/litellm/proxy/guardrails/guardrail_hooks/conduct/conduct.py @@ -70,7 +70,7 @@ class ConductVerdict(LiteLLMBaseModel): def record_decision( guardrail: CustomGuardrail, - request_data: dict[str, object], # mutable-ok: the logging helper writes metadata into it + request_data: dict[str, object], decision: ConductDecision, ) -> None: guardrail.add_standard_logging_guardrail_information_to_request_data( @@ -129,7 +129,7 @@ else: async def apply_guardrail( self, inputs: GenericGuardrailAPIInputs, - request_data: dict[str, object], # mutable-ok: CustomGuardrail.apply_guardrail contract + request_data: dict[str, object], input_type: Literal["request", "response"], logging_obj: LiteLLMLoggingObj | None = None, ) -> GenericGuardrailAPIInputs: diff --git a/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py b/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py index 36d48d49c7e..1dfa593fe46 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py +++ b/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py @@ -881,7 +881,7 @@ class HeadroomGuardrail(CustomGuardrail): self, kwargs: dict[str, object], call_type: CallTypes | None, - ) -> dict[str, object] | None: # mutable-ok: overrides CustomLogger hook whose contract is a plain dict + ) -> dict[str, object] | None: base_result: Final = await super().async_pre_call_deployment_hook(kwargs, call_type) effective: Final = base_result if base_result is not None else kwargs if call_type not in _STREAM_CONVERTIBLE_CALL_TYPES: diff --git a/litellm/proxy/guardrails/guardrail_hooks/lakera_ai_v2.py b/litellm/proxy/guardrails/guardrail_hooks/lakera_ai_v2.py index 8efd1dc79e3..6b086175523 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/lakera_ai_v2.py +++ b/litellm/proxy/guardrails/guardrail_hooks/lakera_ai_v2.py @@ -115,7 +115,7 @@ def _pre_masking_scope_indices( def _apply_redacted_messages_back_preserving_fields( guardrail: "LakeraAIGuardrail", - data: dict[str, object], # mutable-ok: writes the redacted result back into the caller's request dict in place + data: dict[str, object], redacted_messages: Sequence[AllMessageValues], ) -> None: """Write masked content back to ``data["messages"]`` without losing fields @@ -493,7 +493,7 @@ class LakeraAIGuardrail(CustomGuardrail): def _mask_unwritable_instructions_pii_in_place( self, - data: dict[str, object], # mutable-ok: writes the redacted result back into the caller's request dict in place + data: dict[str, object], inspected_messages: Sequence[AllMessageValues], lakera_response: LakeraAIResponse | None, masked_entity_count: dict[str, int], diff --git a/litellm/proxy/guardrails/guardrail_hooks/llm_shield_proxy/stream_restorers.py b/litellm/proxy/guardrails/guardrail_hooks/llm_shield_proxy/stream_restorers.py index 4ef8bbdbb3e..85322edffb0 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/llm_shield_proxy/stream_restorers.py +++ b/litellm/proxy/guardrails/guardrail_hooks/llm_shield_proxy/stream_restorers.py @@ -112,8 +112,8 @@ class AnthropicSSERestorer: def __init__(self, step: StreamStep) -> None: self._step: Final = step - self._carries: Final[dict[int, str]] = {} # mutable-ok: per-block windows advanced in place. - self._delta_types: Final[dict[int, str]] = {} # mutable-ok: each block's delta type, for its flush. + self._carries: Final[dict[int, str]] = {} + self._delta_types: Final[dict[int, str]] = {} self._pending = b"" self._as_text = False self._is_sse: bool | None = None @@ -257,8 +257,8 @@ class ResponsesStreamRestorer: def __init__(self, step: StreamStep, rehydrate: Rehydrate) -> None: self._step: Final = step self._rehydrate: Final = rehydrate - self._carries: Final[dict[ResponsesStreamKey, str]] = {} # mutable-ok: per-stream windows advanced in place. - self._last_deltas: Final[dict[ResponsesStreamKey, object]] = {} # mutable-ok: newest delta per stream. + self._carries: Final[dict[ResponsesStreamKey, str]] = {} + self._last_deltas: Final[dict[ResponsesStreamKey, object]] = {} async def restore(self, event: object) -> tuple[object, ...]: """The events to emit in place of `event`: any flush, then the event itself.""" diff --git a/litellm/proxy/guardrails/guardrail_hooks/presidio.py b/litellm/proxy/guardrails/guardrail_hooks/presidio.py index a4e50cc183a..e143704a086 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/presidio.py +++ b/litellm/proxy/guardrails/guardrail_hooks/presidio.py @@ -549,7 +549,7 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail): self, text: str, presidio_config: PresidioPerRequestConfig | None, - request_data: dict, # mutable-ok: shared per-request state dict, matching analyze_text's parameter + request_data: dict, ) -> list[PresidioAnalyzeResponseItem]: # mutable-ok: analyze_text's declared return type requires list """ Analyze an oversized text by splitting it into overlapping chunks. diff --git a/litellm/proxy/guardrails/guardrail_hooks/singulr/singulr.py b/litellm/proxy/guardrails/guardrail_hooks/singulr/singulr.py index a63695a3af2..1d3bb2b0ff0 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/singulr/singulr.py +++ b/litellm/proxy/guardrails/guardrail_hooks/singulr/singulr.py @@ -406,7 +406,7 @@ class SingulrGuardrail(CustomGuardrail): async def apply_guardrail( self, inputs: GenericGuardrailAPIInputs, - request_data: dict, # mutable-ok: required by CustomGuardrail.apply_guardrail override signature + request_data: dict, input_type: str, logging_obj: "LiteLLMLoggingObj | None" = None, ) -> GenericGuardrailAPIInputs: diff --git a/litellm/proxy/guardrails/guardrail_hooks/typesafe/typesafe.py b/litellm/proxy/guardrails/guardrail_hooks/typesafe/typesafe.py index e62f4b70cb8..44d96ac03e7 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/typesafe/typesafe.py +++ b/litellm/proxy/guardrails/guardrail_hooks/typesafe/typesafe.py @@ -255,7 +255,7 @@ class TypeSafeGuardrail(CustomGuardrail): self, state: dict[str, object], question_ids: Sequence[str] ) -> _JevSystemOneResponse | None: """Returns the response, or None when the service failed and fail_open applies.""" - payload: Final[dict[str, object]] = { # mutable-ok: serialized to JSON by httpx + payload: Final[dict[str, object]] = { "model": self.jev_model, "state": state, "questions": { diff --git a/litellm/proxy/guardrails/guardrail_registry.py b/litellm/proxy/guardrails/guardrail_registry.py index 1374a88cbfe..58b6b390576 100644 --- a/litellm/proxy/guardrails/guardrail_registry.py +++ b/litellm/proxy/guardrails/guardrail_registry.py @@ -613,7 +613,7 @@ class InMemoryGuardrailHandler: Guardrail id to CustomGuardrail object mapping """ - self.guardrail_id_to_sibling_callbacks: dict[str, GuardrailCallbacks] = {} # mutable-ok: per-id registry + self.guardrail_id_to_sibling_callbacks: dict[str, GuardrailCallbacks] = {} self._sources: dict[str, Literal["db", "config"]] = {} """ diff --git a/litellm/proxy/hooks/proxy_track_cost_callback.py b/litellm/proxy/hooks/proxy_track_cost_callback.py index ffe752329d4..ac2eff7ae6a 100644 --- a/litellm/proxy/hooks/proxy_track_cost_callback.py +++ b/litellm/proxy/hooks/proxy_track_cost_callback.py @@ -853,7 +853,7 @@ async def _update_database_and_spend_counters_in_batch( async def _reconcile_budget_reservation_before_db_update( - budget_reservation: dict, # mutable-ok: reconcile_budget_reservation stamps applied_adjustment on the caller's shared reservation dict + budget_reservation: dict, response_cost: float, ) -> None: """Reseeds the reserved counters that were flushed since reservation; the adjustments themselves are written by ``increment_spend_counters`` in the same pipeline as its increments, or by diff --git a/litellm/proxy/lens/agent_workspace.py b/litellm/proxy/lens/agent_workspace.py index b10bf3da770..7307dbc1038 100644 --- a/litellm/proxy/lens/agent_workspace.py +++ b/litellm/proxy/lens/agent_workspace.py @@ -94,9 +94,7 @@ class EvidenceWorkspace: read_errors: set[str] = field( # mutable-ok: preserve source diagnostics when concurrent agents recover default_factory=set ) - verified_parts: dict[Evidence, TracePart] = field( # mutable-ok: retain verified quote metadata for review previews - default_factory=dict - ) + verified_parts: dict[Evidence, TracePart] = field(default_factory=dict) def with_reviews(self, records: tuple[ReviewRecord, ...]) -> "EvidenceWorkspace": return replace(self, reviews=records) diff --git a/litellm/proxy/lens/inference.py b/litellm/proxy/lens/inference.py index 39aa06aa726..b5b0c51d518 100644 --- a/litellm/proxy/lens/inference.py +++ b/litellm/proxy/lens/inference.py @@ -428,7 +428,7 @@ async def analyze( now, ) - data: Final[dict[str, object]] = { # mutable-ok: proxy processing enriches request data + data: Final[dict[str, object]] = { "model": job.settings.model, "messages": list(request_messages(body)), **({"cache_control_injection_points": list(cache_injection_points(body))} if body.messages else {}), diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py index 452c653eef0..5394ea010cd 100644 --- a/litellm/proxy/litellm_pre_call_utils.py +++ b/litellm/proxy/litellm_pre_call_utils.py @@ -562,7 +562,7 @@ def _strip_client_message_redaction_opt_out(data: dict[str, object]) -> None: def _strip_client_callback_credentials( - data: dict[str, object], # mutable-ok: strips in place on the request body the pre-call pipeline threads through + data: dict[str, object], ) -> None: """Drop callback credentials and destinations supplied by the caller. @@ -626,7 +626,7 @@ def _strip_client_pricing_overrides(data: dict[str, object]) -> None: def _strip_router_reserved_metadata( - data: dict[str, object], # mutable-ok: strips in place on the request body the pre-call pipeline threads through + data: dict[str, object], ) -> None: """Drop the router-owned fallback stamps from any client-supplied metadata bucket.""" for metadata_key in ("metadata", "litellm_metadata"): @@ -806,7 +806,7 @@ def _is_llm_inference_route(request: Request) -> bool: def apply_missing_session_id_policy( - data: dict[str, object], # mutable-ok: stamps session ids in place on the request body the pipeline threads through + data: dict[str, object], _metadata_variable_name: str, general_settings: Mapping[str, object] | None, request: Request, @@ -3162,7 +3162,7 @@ def _add_guardrails_from_policies_in_metadata( def add_guardrails_from_auth_metadata( user_api_key_dict: UserAPIKeyAuth, - data: dict, # mutable-ok: writes guardrails into the live request dict, same contract as the helpers it wraps + data: dict, metadata_variable_name: str, ) -> None: """Resolve key, team, and project guardrails, direct and via policies, onto the request metadata.""" diff --git a/litellm/proxy/management_endpoints/auto_router_endpoints.py b/litellm/proxy/management_endpoints/auto_router_endpoints.py index 617a76aaa04..4a913bce228 100644 --- a/litellm/proxy/management_endpoints/auto_router_endpoints.py +++ b/litellm/proxy/management_endpoints/auto_router_endpoints.py @@ -550,7 +550,7 @@ async def preview_auto_router_routing( if member_team is not None else user_api_key_dict ) - request_data: Final[dict[str, object]] = { # mutable-ok: auth and routing enrich this request in place + request_data: Final[dict[str, object]] = { **resolved.wire_body(), "metadata": {}, "proxy_server_request": {"body": None}, diff --git a/litellm/proxy/management_endpoints/budget_management_endpoints.py b/litellm/proxy/management_endpoints/budget_management_endpoints.py index e16ea4a812e..daa4539b90f 100644 --- a/litellm/proxy/management_endpoints/budget_management_endpoints.py +++ b/litellm/proxy/management_endpoints/budget_management_endpoints.py @@ -96,7 +96,7 @@ async def new_budget( budget_obj.budget_reset_at = get_budget_reset_time(budget_duration=budget_obj.budget_duration) budget_obj_json: Final = budget_obj.model_dump(exclude_none=True) - budget_obj_jsonified: Final[dict[str, object]] = jsonify_object(budget_obj_json) # mutable-ok: prisma create input + budget_obj_jsonified: Final[dict[str, object]] = jsonify_object(budget_obj_json) try: response: Final = await BudgetRepository(prisma_client).table.create( data={ diff --git a/litellm/proxy/management_endpoints/common_daily_activity.py b/litellm/proxy/management_endpoints/common_daily_activity.py index 28dfbb09eab..7224c4d6cbe 100644 --- a/litellm/proxy/management_endpoints/common_daily_activity.py +++ b/litellm/proxy/management_endpoints/common_daily_activity.py @@ -982,7 +982,7 @@ def _fold_entity_rollups_sync( results: Sequence[DailySpendData], entity_rows: Sequence[EntityRollupRow], api_key_metadata: Mapping[str, KeyMetadataRow], - entity_metadata_field: Mapping[str, dict[str, object]] | None, # mutable-ok: shared field shape + entity_metadata_field: Mapping[str, dict[str, object]] | None, ) -> None: """Write breakdown.entities onto the already-built per-day results.""" by_date: Final = {day.date.strftime("%Y-%m-%d"): day for day in results} diff --git a/litellm/proxy/management_endpoints/common_utils.py b/litellm/proxy/management_endpoints/common_utils.py index be2423fb596..5f201300a7a 100644 --- a/litellm/proxy/management_endpoints/common_utils.py +++ b/litellm/proxy/management_endpoints/common_utils.py @@ -587,7 +587,7 @@ async def _upsert_budget_and_membership( ) source: Final[Mapping[str, object]] = source_row.model_dump() if source_row is not None else MappingProxyType({}) - create_data: Final[dict[str, object]] = { # mutable-ok: Prisma create payloads are dict-shaped + create_data: Final[dict[str, object]] = { "created_by": user_api_key_dict.user_id or "", "updated_by": user_api_key_dict.user_id or "", **MappingProxyType( diff --git a/litellm/proxy/management_endpoints/config_override_endpoints.py b/litellm/proxy/management_endpoints/config_override_endpoints.py index 6b4f2b690db..9186145a78c 100644 --- a/litellm/proxy/management_endpoints/config_override_endpoints.py +++ b/litellm/proxy/management_endpoints/config_override_endpoints.py @@ -158,7 +158,7 @@ HASHICORP_SENSITIVE_FIELDS: Final[set[str]] = { # --- CyberArk Conjur constants --- -CYBERARK_ENV_VAR_MAPPING: Final[dict[str, str]] = { # mutable-ok: module-level env mapping +CYBERARK_ENV_VAR_MAPPING: Final[dict[str, str]] = { "cyberark_api_base": "CYBERARK_API_BASE", "cyberark_account": "CYBERARK_ACCOUNT", "cyberark_username": "CYBERARK_USERNAME", @@ -680,13 +680,13 @@ async def update_cyberark_config( detail=CommonProxyErrors.db_not_connected_error.value, ) - config_data: dict[str, object] = config.model_dump(exclude_none=True) # mutable-ok: merged # rebind-ok: stripped + config_data: dict[str, object] = config.model_dump(exclude_none=True) # rebind-ok: stripped # Merge ALL fields the user didn't send: try DB first, fall back to env vars. # Omitted field = keep existing; empty string = clear/remove the field. existing_record: Final = await _config_overrides_table(prisma_client).find_unique(where={"config_type": "cyberark"}) - existing_decrypted: dict[str, object] | None = None # mutable-ok: DB payload # rebind-ok: set when record exists - env_values: dict[str, str | None] = {} # mutable-ok: env snapshot # rebind-ok: populated when no DB record exists + existing_decrypted: dict[str, object] | None = None # rebind-ok: set when record exists + env_values: dict[str, str | None] = {} # rebind-ok: populated when no DB record exists if existing_record is not None and existing_record.config_value is not None: existing_data: Final = _parse_config_value(existing_record.config_value) existing_decrypted = proxy_config._decrypt_db_variables(existing_data) # pyright: ignore[reportPrivateUsage] # rebind-ok: populated when a prior record decrypts @@ -845,7 +845,7 @@ async def delete_cyberark_config( ) existing_record: Final = await _config_overrides_table(prisma_client).find_unique(where={"config_type": "cyberark"}) - before_config: dict[str, object] | None = None # mutable-ok: audit snapshot # rebind-ok: set when decrypts + before_config: dict[str, object] | None = None # rebind-ok: set when decrypts if existing_record is not None and existing_record.config_value is not None: try: before_config = proxy_config._decrypt_db_variables(_parse_config_value(existing_record.config_value)) # pyright: ignore[reportPrivateUsage] # rebind-ok: populated when the prior record decrypts diff --git a/litellm/proxy/management_endpoints/model_insights_endpoints.py b/litellm/proxy/management_endpoints/model_insights_endpoints.py index c0e7aaa3f7d..8690667e039 100644 --- a/litellm/proxy/management_endpoints/model_insights_endpoints.py +++ b/litellm/proxy/management_endpoints/model_insights_endpoints.py @@ -215,10 +215,10 @@ async def get_model_insights( ) date_rows: Final = _DATE_ROWS.validate_python( await table.group_by( - by=["date"], # mutable-ok: prisma group_by requires a list of fields + by=["date"], sum=_SUM_FIELDS, where=date_window, - order={"date": "asc"}, # mutable-ok: prisma order clause must be a dict + order={"date": "asc"}, ) ) return ModelInsightsResponse( diff --git a/litellm/proxy/management_endpoints/model_management_endpoints.py b/litellm/proxy/management_endpoints/model_management_endpoints.py index c7883f5cf56..3c8472f660a 100644 --- a/litellm/proxy/management_endpoints/model_management_endpoints.py +++ b/litellm/proxy/management_endpoints/model_management_endpoints.py @@ -3072,7 +3072,7 @@ async def update_useful_links( def _validated_labeled_tiers( - tier_labels: dict[ComplexityTier, str], # mutable-ok: Pydantic materializes JSON object fields as dicts + tier_labels: dict[ComplexityTier, str], ) -> tuple[tuple[ComplexityTier, str], ...]: """Validate tier labels once for both prompt-preview transports.""" try: @@ -3107,7 +3107,7 @@ class AutoRouterClassifierPromptPreviewRequest(LiteLLMBaseModel): which must not reach access logs through a URL.""" tier_definitions: tuple[TierDefinition, ...] | None = None - tier_labels: dict[ComplexityTier, str] | None = None # mutable-ok: FastAPI parses JSON object fields into dicts + tier_labels: dict[ComplexityTier, str] | None = None classification_rubric: ClassificationRubric | None = None context_window_size: Annotated[int, Field(ge=0)] = DEFAULT_CLASSIFIER_CONTEXT_WINDOW_SIZE classification_prompt: str | None = None diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py index aeb317c8905..a033ed87613 100644 --- a/litellm/proxy/management_endpoints/team_endpoints.py +++ b/litellm/proxy/management_endpoints/team_endpoints.py @@ -294,7 +294,7 @@ class _RawTeamRow(_TeamIdRow, _ModelDumpRow, _ObjectPermissionRow, _TeamBudgetRo @property def members_with_roles( self, - ) -> Sequence[dict[str, object]] | None: ... # mutable-ok: prisma deserializes this JSON column into plain dicts + ) -> Sequence[dict[str, object]] | None: ... @property def organization_id(self) -> str | None: ... @@ -6525,7 +6525,7 @@ def _daily_activity_error(*, status_code: int, message: str) -> HTTPException: class _TeamDailyActivityScope(NamedTuple): team_ids: list[str] | None # mutable-ok: downstream daily-activity signatures take str | list unions exclude_team_ids: list[str] | None # mutable-ok: downstream daily-activity signatures take str | list unions - team_alias_metadata: dict[str, dict[str, object]] # mutable-ok: entity_metadata_field shape + team_alias_metadata: dict[str, dict[str, object]] api_key_filter: str | list[str] | None # mutable-ok: downstream daily-activity signatures take str | list unions diff --git a/litellm/proxy/openai_files_endpoints/batch_guardrails.py b/litellm/proxy/openai_files_endpoints/batch_guardrails.py index 709845e0ffc..dd9f7ebe395 100644 --- a/litellm/proxy/openai_files_endpoints/batch_guardrails.py +++ b/litellm/proxy/openai_files_endpoints/batch_guardrails.py @@ -378,7 +378,7 @@ async def _scan_record( url=url if isinstance(url, str) else None, ) - scan_input: Final[dict[str, object]] = copy.deepcopy(body) # mutable-ok: pre_call_hook mutates the dict it is given + scan_input: Final[dict[str, object]] = copy.deepcopy(body) own_injected: Final = MappingProxyType({key: body[key] for key in _INJECTED_KEYS if key in body}) for injected in _INJECTED_KEYS: scan_input.pop(injected, None) @@ -393,7 +393,7 @@ async def _scan_record( try: # The chain hands back the body it produced, which may be a replacement for the dict it was # given rather than that same dict mutated, so this is what gets compared. - scanned: Final[dict] = await proxy_logging_obj.pre_call_hook( # mutable-ok: the guardrails' own dict + scanned: Final[dict] = await proxy_logging_obj.pre_call_hook( user_api_key_dict=user_api_key_dict, data=scan_input, call_type=call_type, diff --git a/litellm/proxy/openai_files_endpoints/common_utils.py b/litellm/proxy/openai_files_endpoints/common_utils.py index 7e7ab1dc1f0..6328b900ed0 100644 --- a/litellm/proxy/openai_files_endpoints/common_utils.py +++ b/litellm/proxy/openai_files_endpoints/common_utils.py @@ -432,7 +432,7 @@ async def get_authorized_credentials_for_model( model_id: str, user_api_key_dict: "UserAPIKeyAuth", operation_context: str = "file operation", -) -> dict: # mutable-ok: same contract as get_credentials_for_model, callers merge it into request data +) -> dict: """``get_credentials_for_model`` gated by ``authorize_model_for_key``.""" await authorize_model_for_key(model_id=model_id, llm_router=llm_router, user_api_key_dict=user_api_key_dict) return get_credentials_for_model( @@ -563,7 +563,7 @@ def get_team_provider_credentials( def apply_team_provider_credentials( - data: dict, # mutable-ok: credentials are merged into the request payload in place, same contract as prepare_data_with_credentials + data: dict, llm_router: Optional["Router"], user_api_key_dict: "UserAPIKeyAuth", custom_llm_provider: str, diff --git a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py index 46f2394c845..acacfd193bc 100644 --- a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py @@ -3152,7 +3152,7 @@ class _WebsocketRelay(Protocol): *, websocket: WebSocket, target: str, - custom_headers: dict[str, str], # mutable-ok: the relay takes a plain dict of upstream headers + custom_headers: dict[str, str], user_api_key_dict: UserAPIKeyAuth, forward_headers: bool, endpoint: str, diff --git a/litellm/proxy/pass_through_endpoints/llm_provider_handlers/transcribe_passthrough_logging_handler.py b/litellm/proxy/pass_through_endpoints/llm_provider_handlers/transcribe_passthrough_logging_handler.py index 79f6dee90f7..1169f80bb59 100644 --- a/litellm/proxy/pass_through_endpoints/llm_provider_handlers/transcribe_passthrough_logging_handler.py +++ b/litellm/proxy/pass_through_endpoints/llm_provider_handlers/transcribe_passthrough_logging_handler.py @@ -525,7 +525,7 @@ def transcribe_media_duration_probe(aws_region_name: str, download_slots: asynci from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM, run_aws_signing - def sign_s3_get(url: str) -> dict[str, str]: # mutable-ok: httpx request headers take a dict + def sign_s3_get(url: str) -> dict[str, str]: aws_request: Final = AWSRequest(method="GET", url=url) credentials: Final = BaseAWSLLM().get_credentials(aws_region_name=aws_region_name) S3SigV4Auth(credentials, "s3", aws_region_name).add_auth(aws_request) diff --git a/litellm/proxy/pass_through_endpoints/llm_provider_handlers/vertex_passthrough_logging_handler.py b/litellm/proxy/pass_through_endpoints/llm_provider_handlers/vertex_passthrough_logging_handler.py index 47e1f21d1a9..23b97826bd7 100644 --- a/litellm/proxy/pass_through_endpoints/llm_provider_handlers/vertex_passthrough_logging_handler.py +++ b/litellm/proxy/pass_through_endpoints/llm_provider_handlers/vertex_passthrough_logging_handler.py @@ -419,10 +419,10 @@ class VertexPassthroughLoggingHandler: @staticmethod def _handle_audio_predict_response( - json_response: dict, # mutable-ok: passthrough logging receives the decoded provider response dictionary + json_response: dict, logging_obj: LiteLLMLoggingObj, model: str, - kwargs: dict, # mutable-ok: passthrough logging enriches the shared callback metadata dictionary + kwargs: dict, ) -> PassThroughEndpointLoggingTypedDict: prediction_count: Final = VertexPassthroughLoggingHandler._get_audio_prediction_count( json_response=json_response diff --git a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py index 3f4a30e9179..8c5d841657b 100644 --- a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py @@ -834,7 +834,7 @@ def _build_passthrough_failure_request_payload( class _TeamCallbackWiring: success_callbacks: "list[str | Callable | CustomLogger] | None" = None # mutable-ok: Logging.__init__ arg failure_callbacks: "list[str | Callable | CustomLogger] | None" = None # mutable-ok: Logging.__init__ arg - logging_kwargs: dict[str, str | dict[str, str]] | None = None # mutable-ok: Logging.__init__ arg + logging_kwargs: dict[str, str | dict[str, str]] | None = None def _resolve_team_callback_wiring( @@ -1024,7 +1024,7 @@ async def _relay_reporting_failures( stream: AsyncGenerator[bytes, None], upstream_status: int, user_api_key_dict: UserAPIKeyAuth, - request_payload: dict, # mutable-ok: post_call_failure_hook lifts fields onto request_data in place + request_payload: dict, ) -> AsyncGenerator[bytes, None]: from litellm.proxy.proxy_server import proxy_logging_obj diff --git a/litellm/proxy/policy_engine/pipeline_executor.py b/litellm/proxy/policy_engine/pipeline_executor.py index d80eb56d8e3..e0e17feb88c 100644 --- a/litellm/proxy/policy_engine/pipeline_executor.py +++ b/litellm/proxy/policy_engine/pipeline_executor.py @@ -137,7 +137,7 @@ class _StreamRewriteObserver(CustomGuardrail): async def apply_guardrail( self, inputs: GenericGuardrailAPIInputs, - request_data: dict, # mutable-ok: matches CustomGuardrail.apply_guardrail + request_data: dict, input_type: Literal["request", "response"], logging_obj: "LiteLLMLoggingObj | None" = None, ) -> GenericGuardrailAPIInputs: @@ -175,7 +175,7 @@ class _ScannedTextRecorder(CustomGuardrail): async def apply_guardrail( self, inputs: GenericGuardrailAPIInputs, - request_data: dict, # mutable-ok: matches CustomGuardrail.apply_guardrail + request_data: dict, input_type: Literal["request", "response"], logging_obj: "LiteLLMLoggingObj | None" = None, ) -> GenericGuardrailAPIInputs: @@ -215,7 +215,7 @@ class _LegacyHookStreamAdapter(CustomGuardrail): async def apply_guardrail( self, inputs: GenericGuardrailAPIInputs, - request_data: dict, # mutable-ok: matches CustomGuardrail.apply_guardrail + request_data: dict, input_type: Literal["request", "response"], logging_obj: "LiteLLMLoggingObj | None" = None, ) -> GenericGuardrailAPIInputs: @@ -268,9 +268,9 @@ class _LegacyHookStreamAdapter(CustomGuardrail): def _prepare_hook_input( step: PipelineStep, callback: CustomGuardrail, - data: dict, # mutable-ok: same request-payload shape the hooks mutate - raw_request_snapshot: dict | None, # mutable-ok: same request-payload shape as data -) -> tuple[dict, bool]: # mutable-ok: returns that same request-payload dict + data: dict, + raw_request_snapshot: dict | None, +) -> tuple[dict, bool]: """Inject the step's guardrail name into metadata so should_run_guardrail() allows it, and pick the payload the step scans: a scan_raw_request step evaluates the pristine pre-pipeline snapshot instead of `data` (which earlier pass_data steps in this same @@ -281,7 +281,7 @@ def _prepare_hook_input( data["metadata"]["guardrails"] = [step.guardrail] scans_raw_request: Final = callback.scan_raw_request - hook_input: Final[dict] = ( # mutable-ok: same request-payload shape as data + hook_input: Final[dict] = ( independent_snapshot(raw_request_snapshot) if scans_raw_request and raw_request_snapshot is not None else data ) if hook_input is not data: @@ -315,7 +315,7 @@ class PipelineExecutor: user_api_key_dict: "UserAPIKeyAuth", call_type: str, policy_name: str, - raw_request_snapshot: dict | None = None, # mutable-ok: same request-payload shape as data + raw_request_snapshot: dict | None = None, streaming_chunks: list[object] | None = None, # mutable-ok: shared buffered-stream chunks, read per step endpoint_translation: "BaseTranslation | None" = None, ) -> PipelineExecutionResult: @@ -431,7 +431,7 @@ class PipelineExecutor: callback: CustomGuardrail, endpoint_translation: "BaseTranslation", streaming_chunks: list[object], # mutable-ok: shared buffered-stream chunks the translation rewrites in place - hook_input: dict[str, object], # mutable-ok: same request-payload shape as data + hook_input: dict[str, object], user_api_key_dict: "UserAPIKeyAuth", litellm_logging_obj: "LiteLLMLoggingObj | None", ) -> None: @@ -490,7 +490,7 @@ class PipelineExecutor: data: dict, user_api_key_dict: "UserAPIKeyAuth", call_type: str, - raw_request_snapshot: dict | None = None, # mutable-ok: same request-payload shape as data + raw_request_snapshot: dict | None = None, streaming_chunks: list[object] | None = None, # mutable-ok: shared buffered-stream chunks, read per step endpoint_translation: "BaseTranslation | None" = None, ) -> tuple[ @@ -627,8 +627,8 @@ class PipelineExecutor: def _allow_result( step_results: Sequence[PipelineStepResult], - working_data: dict, # mutable-ok: same request-payload shape as execute_steps' data - request_data: dict, # mutable-ok: same request-payload shape as execute_steps' data + working_data: dict, + request_data: dict, ) -> PipelineExecutionResult: """Build the terminal-allow result, propagating pipeline modifications without the per-step guardrail override.""" restored: Final = _restore_request_guardrails(working_data, request_data) @@ -640,9 +640,9 @@ def _allow_result( def _restore_request_guardrails( - working_data: dict, # mutable-ok: same request-payload shape as execute_steps' data - request_data: dict, # mutable-ok: same request-payload shape as execute_steps' data -) -> dict: # mutable-ok: merged back into the request dict, which downstream code mutates + working_data: dict, + request_data: dict, +) -> dict: """ Restore the request's own metadata["guardrails"] activation list. @@ -674,7 +674,7 @@ def _recorded_guardrail_information(source: Mapping[str, object]) -> list[Standa def _append_guardrail_information( - request_data: dict[str, object], # mutable-ok: same request-payload shape as execute_steps' data + request_data: dict[str, object], entries: Sequence[StandardLoggingGuardrailInformation], ) -> None: if not entries: @@ -689,7 +689,7 @@ def _append_guardrail_information( def _carry_working_guardrail_information( working_data: Mapping[str, object], - request_data: dict[str, object], # mutable-ok: same request-payload shape as execute_steps' data + request_data: dict[str, object], ) -> None: recorded: Final = _recorded_guardrail_information(working_data) existing: Final = _recorded_guardrail_information(request_data) diff --git a/litellm/proxy/policy_engine/response_retrieval.py b/litellm/proxy/policy_engine/response_retrieval.py index 654b1682582..dd2fbc9e94b 100644 --- a/litellm/proxy/policy_engine/response_retrieval.py +++ b/litellm/proxy/policy_engine/response_retrieval.py @@ -104,7 +104,7 @@ def _post_call_pipelines_for_context(context: PolicyMatchContext) -> tuple[Polic def attach_post_call_pipelines_to_retrieval( - data: dict[str, object], # mutable-ok: request-state dict the policy engine hooks all write in place + data: dict[str, object], user_api_key_dict: "UserAPIKeyAuth", llm_router: "Router | None", ) -> None: diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index db6294a364d..b2c5977ccc4 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -5203,7 +5203,7 @@ def validate_auto_router_capability_limits(model_list: Sequence[Mapping[str, obj raise ValueError(f"config.yaml model_list: {' '.join(violations)} {AUTO_ROUTER_LICENSE_REMEDY}") -def pin_complexity_router_model_id(model: dict) -> None: # mutable-ok: out-param, model_info is stamped in place +def pin_complexity_router_model_id(model: dict) -> None: """ Stamps `model_info.id` from the raw litellm_params before plugin resolution swaps dotted-path strings for live instances. `_delete_deployment` re-reads the raw config @@ -5490,11 +5490,11 @@ class ProxyConfig: self._last_semantic_filter_config: dict[str, object] | None = None self._last_websearch_interception_config: dict[str, object] | None = None self._last_hashicorp_vault_config: dict[str, object] | None = None - self._last_cyberark_config: dict[str, object] | None = None # mutable-ok: change-detection cache + self._last_cyberark_config: dict[str, object] | None = None self._last_cleanup_schedule_attempt: tuple[object, ...] | None = None self._cleanup_reschedule_failed: bool = False self._warned_db_mcp_stdio_flag_ignored: bool = False - self._cyberark_boot_env: dict[str, str | None] | None = None # mutable-ok: deployment env snapshot, set once + self._cyberark_boot_env: dict[str, str | None] | None = None self.worker_registry: list[WorkerRegistryEntry] = [] self.config_sync_subscriber: ConfigSyncSubscriber | None = None self.auth_cache_invalidation_subscriber: AuthCacheInvalidationSubscriber | None = None diff --git a/litellm/proxy/response_api_endpoints/endpoints.py b/litellm/proxy/response_api_endpoints/endpoints.py index eae621ba1ce..00af96af94c 100644 --- a/litellm/proxy/response_api_endpoints/endpoints.py +++ b/litellm/proxy/response_api_endpoints/endpoints.py @@ -106,9 +106,7 @@ def _convert_tool_envelope(obj: object, *, to_chat: bool) -> object: return {"type": tool_type, tool_type: payload} if to_chat else {"type": tool_type, **payload} -def _normalize_tool_dialect( - data: dict, *, to_chat: bool -) -> dict: # mutable-ok: the parsed request body contract is a plain dict +def _normalize_tool_dialect(data: dict, *, to_chat: bool) -> dict: tools: Final = data.get("tools") tool_choice: Final = data.get("tool_choice") normalized_tools: Final = ( @@ -156,9 +154,7 @@ def _router_can_serve(model: str, llm_router: "Router | None") -> bool: return bool(llm_router.pattern_router.get_pattern(model)) -def _resolve_cursor_model_variant( - data: dict, llm_router: "Router | None" -) -> dict: # mutable-ok: the parsed request body contract is a plain dict +def _resolve_cursor_model_variant(data: dict, llm_router: "Router | None") -> dict: model: Final = data.get("model") if not isinstance(model, str) or _router_can_serve(model, llm_router): return data diff --git a/litellm/proxy/route_llm_request.py b/litellm/proxy/route_llm_request.py index d6d673c6c76..e97099a6fe0 100644 --- a/litellm/proxy/route_llm_request.py +++ b/litellm/proxy/route_llm_request.py @@ -583,7 +583,7 @@ async def route_request( async def _route_request_single_attempt( # noqa: ANN202 # returns unawaited provider coroutines; the inferred union keeps route_request's callers typed - data: dict, # mutable-ok: request body is the proxy-wide mutable dict contract shared with route_request + data: dict, llm_router: LitellmRouter | None, user_model: str | None, route_type: RouteType, diff --git a/litellm/proxy/spend_tracking/budget_reservation.py b/litellm/proxy/spend_tracking/budget_reservation.py index f9235a33b42..bf9155aed1c 100644 --- a/litellm/proxy/spend_tracking/budget_reservation.py +++ b/litellm/proxy/spend_tracking/budget_reservation.py @@ -445,7 +445,7 @@ async def invalidate_budget_reservation_counters( async def release_or_invalidate_budget_reservation( - budget_reservation: dict | None, # mutable-ok: stamps finalized on the caller's shared reservation dict + budget_reservation: dict | None, ) -> None: """Reconcile a still-open reservation on a terminal path that settles no cost. diff --git a/litellm/proxy/spend_tracking/spend_management_endpoints.py b/litellm/proxy/spend_tracking/spend_management_endpoints.py index af33a3e528b..73fa375d01b 100644 --- a/litellm/proxy/spend_tracking/spend_management_endpoints.py +++ b/litellm/proxy/spend_tracking/spend_management_endpoints.py @@ -3089,14 +3089,14 @@ async def _fetch_session_representatives( ORDER BY {_SESSION_GROUP_KEY_SQL}, {_SESSION_REPRESENTATIVE_ORDER_SQL} ) AS session_representatives """ - rep_rows: Final[Sequence[dict[str, object]]] = await _query_raw( # mutable-ok: rows are enriched in place + rep_rows: Final[Sequence[dict[str, object]]] = await _query_raw( prisma_client, rep_query, *sql_params, [session_key for session_key, _ in session_keys], [api_key for _, api_key in session_keys], ) - rep_by_key: Final[Mapping[tuple[str, str], dict[str, object]]] = MappingProxyType( # mutable-ok: same rows + rep_by_key: Final[Mapping[tuple[str, str], dict[str, object]]] = MappingProxyType( {(str(row["session_id"] or row["request_id"]), str(row["api_key"])): row for row in rep_rows} ) return [rep_by_key[key] for key in session_keys if key in rep_by_key] diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index b775a1770b3..e1f186af7b8 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -690,7 +690,7 @@ def _guardrails_run_standalone_pre_call(data: Mapping[str, object]) -> frozenset def _without_names( - bucket: dict[str, object], # mutable-ok: the applied_* header slots live in the request-state dict hooks write + bucket: dict[str, object], slot: str, names: frozenset[str], ) -> None: @@ -705,7 +705,7 @@ def _without_names( def _withdraw_deferred_claims( - data: dict[str, object], # mutable-ok: same request-payload shape as post_call_success_hook's data + data: dict[str, object], deferred: Sequence[tuple[str, "GuardrailPipeline"]], ) -> None: outside_by_policy: Final = MappingProxyType( @@ -730,7 +730,7 @@ def _withdraw_deferred_claims( def _defer_post_call_pipelines( - data: dict[str, object], # mutable-ok: same request-payload shape as post_call_success_hook's data + data: dict[str, object], response: ResponsesAPIResponse, ) -> None: deferred: Final = _post_call_pipelines(data) @@ -1503,7 +1503,7 @@ class ProxyLogging: synthetic_message: Final = ChatCompletionUserMessage(role="user", content=tool_call_content) - synthetic_metadata: Final[dict[str, object]] = { # mutable-ok: existing guardrail hooks mutate request metadata + synthetic_metadata: Final[dict[str, object]] = { **MappingProxyType({key: value for key, value in parent_metadata.items() if key != "guardrails"}), "headers": kwargs.get("headers") or {}, "user_api_key_user_id": kwargs.get("user_api_key_user_id"), @@ -2022,11 +2022,11 @@ class ProxyLogging: async def _run_sequential_guardrail_callback( self, callback: CustomGuardrail, - data: dict, # mutable-ok: matches _process_guardrail_callback's own request-payload typing - raw_request_snapshot: dict | None, # mutable-ok: same request-payload shape as data + data: dict, + raw_request_snapshot: dict | None, user_api_key_dict: UserAPIKeyAuth, call_type: CallTypesLiteral, - ) -> dict: # mutable-ok: callers reassign the loop's own data from this return value + ) -> dict: """ Run one guardrail from the sequential pre_call loop and return what the rest of the loop should carry forward. @@ -2056,9 +2056,7 @@ class ProxyLogging: # raw_request_snapshot itself) so the comparison isolates the guardrail's # own content mutation from this bookkeeping noise without risking a # premature marker write into shared state. - expected_if_unmutated: Final[dict | None] = ( # mutable-ok: same request-payload shape as data - independent_snapshot(input_data) if scans_raw_request else None - ) + expected_if_unmutated: Final[dict | None] = independent_snapshot(input_data) if scans_raw_request else None if expected_if_unmutated is not None: callback.mark_pre_call_hook_ran(expected_if_unmutated) try: @@ -2210,9 +2208,9 @@ class ProxyLogging: user_api_key_dict: UserAPIKeyAuth, call_type: str, event_hook: str, - raw_request_snapshot: dict | None = None, # mutable-ok: same request-payload shape as data + raw_request_snapshot: dict | None = None, response: LLMResponseTypes | None = None, - ) -> tuple[dict, LLMResponseTypes | None]: # mutable-ok: returns the request-payload dict onward + ) -> tuple[dict, LLMResponseTypes | None]: """ Execute guardrail pipelines if any are configured for this request. @@ -2456,9 +2454,7 @@ class ProxyLogging: isinstance(cb, CustomGuardrail) and cb.scan_raw_request for cb in ProxyLogging._callback_capabilities().resolved_callbacks ) - raw_request_snapshot: Final[dict | None] = ( # mutable-ok: same request-payload shape as data - independent_snapshot(data) if needs_raw_request_snapshot else None - ) + raw_request_snapshot: Final[dict | None] = independent_snapshot(data) if needs_raw_request_snapshot else None try: if not skip_guardrails: @@ -2608,7 +2604,7 @@ class ProxyLogging: self, guardrails: tuple[CustomGuardrail, ...], data: dict, - raw_request_snapshot: dict | None, # mutable-ok: same request-payload shape as data + raw_request_snapshot: dict | None, user_api_key_dict: UserAPIKeyAuth, call_type: CallTypesLiteral, ) -> None: @@ -2633,7 +2629,7 @@ class ProxyLogging: sequential guardrail already masked or rewrote. """ - def _input_for(callback: CustomGuardrail) -> dict: # mutable-ok: same request-payload shape as data + def _input_for(callback: CustomGuardrail) -> dict: if not callback.scan_raw_request or raw_request_snapshot is None: return data return independent_snapshot(raw_request_snapshot) @@ -3033,7 +3029,7 @@ class ProxyLogging: async def _run_during_call_guardrail( self, callback: CustomGuardrail, - data: dict[str, object], # mutable-ok: request payload dict, guardrail_to_apply is written in place + data: dict[str, object], user_api_key_dict: UserAPIKeyAuth | None, user_api_key_auth_dict: UserAPIKeyAuth | dict[str, object] | None, call_type: CallTypesLiteral, @@ -3516,7 +3512,7 @@ class ProxyLogging: async def _run_post_call_pipelines( self, - data: dict[str, object], # mutable-ok: same request-payload shape as post_call_success_hook's data + data: dict[str, object], user_api_key_dict: UserAPIKeyAuth, response: LLMResponseTypes, ) -> LLMResponseTypes | None: @@ -4069,7 +4065,7 @@ class ProxyLogging: self, response: "AsyncGenerator[object, None]", user_api_key_dict: UserAPIKeyAuth, - request_data: dict, # mutable-ok: same request-payload shape the hooks mutate + request_data: dict, pipelines: "tuple[tuple[str, GuardrailPipeline], ...]", translation: "tuple[str, BaseTranslation]", ) -> "AsyncGenerator[object, None]": diff --git a/litellm/responses/litellm_completion_transformation/streaming_iterator.py b/litellm/responses/litellm_completion_transformation/streaming_iterator.py index eccca70fde1..7286563f36f 100644 --- a/litellm/responses/litellm_completion_transformation/streaming_iterator.py +++ b/litellm/responses/litellm_completion_transformation/streaming_iterator.py @@ -127,7 +127,7 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator): self._pending_tool_events: list[BaseLiteLLMOpenAIResponseObject] = [] self._tool_output_index_by_call_id: dict[str, int] = {} self._tool_args_by_call_id: dict[str, str] = {} - self._tool_item_id_by_call_id: dict[str, str] = {} # mutable-ok: filled per call id as tool call events stream + self._tool_item_id_by_call_id: dict[str, str] = {} self._tool_call_id_by_index: dict[int, str] = {} self._ambiguous_tool_call_indexes: set[int] = set() self._next_tool_output_index: int = 1 # output_index=0 reserved for the message item @@ -148,7 +148,7 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator): self._namespace_tool_names = LiteLLMCompletionResponsesConfig.namespace_tool_name_map( self.responses_api_request.get("tools") ) - self._web_search_calls: dict[str, object] = {} # mutable-ok: latest call by provider id + self._web_search_calls: dict[str, object] = {} self._queued_web_search_call_ids: set[str] = set() # mutable-ok: emitted call ids def _get_or_assign_tool_output_index(self, call_id: str) -> int: diff --git a/litellm/responses/litellm_completion_transformation/transformation.py b/litellm/responses/litellm_completion_transformation/transformation.py index b1dc6b3a98a..5fa2b5fdabe 100644 --- a/litellm/responses/litellm_completion_transformation/transformation.py +++ b/litellm/responses/litellm_completion_transformation/transformation.py @@ -2261,7 +2261,7 @@ class LiteLLMCompletionResponsesConfig: def _web_search_calls_by_call_id( chat_completion_response: ModelResponse, ) -> Mapping[str, ResponseFunctionWebSearch]: - calls: Final[dict[str, ResponseFunctionWebSearch]] = {} # mutable-ok: indexes provider-built calls + calls: Final[dict[str, ResponseFunctionWebSearch]] = {} for choice in chat_completion_response.choices: provider_fields = choice.message.provider_specific_fields if not isinstance(provider_fields, Mapping): diff --git a/litellm/responses/main.py b/litellm/responses/main.py index c4632f09723..6610b19a8ee 100644 --- a/litellm/responses/main.py +++ b/litellm/responses/main.py @@ -552,7 +552,7 @@ def _will_bridge_to_chat_completions( @contextmanager def _prompt_management_sees_a_provisional_message_list( - kwargs: dict[str, object], # mutable-ok: the signal is read and popped out of the caller's own kwargs + kwargs: dict[str, object], bridged: bool, ) -> Generator[None, None]: """Tell the cache-control hook that this layer's messages are not the ones sent upstream. diff --git a/litellm/responses/mcp/mcp_streaming_iterator.py b/litellm/responses/mcp/mcp_streaming_iterator.py index d486db72304..830c2e094bc 100644 --- a/litellm/responses/mcp/mcp_streaming_iterator.py +++ b/litellm/responses/mcp/mcp_streaming_iterator.py @@ -753,7 +753,7 @@ class MCPEnhancedStreamingIterator(BaseResponsesAPIStreamingIterator): next_output_index = self._output_index_offset + self._round_output_width( # rebind-ok: advances per item self.collected_response ) - call_items: Final[dict[str, tuple[str, int]]] = {} # mutable-ok: filled per tool call as events queue + call_items: Final[dict[str, tuple[str, int]]] = {} for tool_call in tool_calls: ( tool_name, diff --git a/litellm/responses/utils.py b/litellm/responses/utils.py index f4705e7132e..1b1d166b689 100644 --- a/litellm/responses/utils.py +++ b/litellm/responses/utils.py @@ -47,7 +47,7 @@ def _is_object_sequence(value: object) -> TypeIs[Sequence[object]]: # guard-ok: def _is_object_dict( value: object, -) -> TypeIs[dict[str, object]]: # guard-ok: wire dicts have str keys # mutable-ok: callers rewrite ids in place +) -> TypeIs[dict[str, object]]: # guard-ok: wire dicts have str keys return isinstance(value, dict) @@ -602,7 +602,7 @@ class ResponsesAPIRequestUtils: ) if not readable: return None - kept: Final[dict[str, object]] = { # mutable-ok: request item rebuilt without the undecryptable keys + kept: Final[dict[str, object]] = { key: value for key, value in reasoning.items() if key not in ("encrypted_content", "id") } return kept diff --git a/litellm/router.py b/litellm/router.py index 77cff6254ea..e5358324868 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -2581,7 +2581,7 @@ class Router: @staticmethod def _deployment_params_with_request_reasoning_override( deployment_params: Mapping[str, object], request_kwargs: Mapping[str, object] - ) -> dict[str, object]: # mutable-ok: litellm's request pipeline consumes a mutable kwargs mapping + ) -> dict[str, object]: """Return deployment params whose equivalent effort controls cannot outrank a request override. Providers expose the same setting through several native carriers. A request-level @@ -2624,7 +2624,7 @@ class Router: self, deployment: DeploymentTypedDict, model: str, - kwargs: dict[str, object], # mutable-ok: fallback must update the active request and its log body together + kwargs: dict[str, object], ) -> None: """Let a classifier fallback without reasoning support remain a usable fallback. @@ -3549,8 +3549,8 @@ class Router: self, e: "MidStreamFallbackError", source_iterator: "BaseResponsesAPIStreamingIterator", - initial_kwargs: dict[str, Any], # mutable-ok: mutated in-place before re-entering the fallback chain - adopt_headers: Callable[[object], tuple[dict[str, object], dict[str, object]]], # mutable-ok: hidden params + initial_kwargs: dict[str, Any], + adopt_headers: Callable[[object], tuple[dict[str, object], dict[str, object]]], held_lifecycle_events: tuple[object, ...], ) -> AsyncGenerator[object, None]: """ @@ -5592,7 +5592,7 @@ class Router: async def _aanthropic_messages_streaming_iterator( self, response: AsyncIterator[bytes], - initial_kwargs: dict[str, Any], # mutable-ok: mutated in-place before re-entering the fallback chain + initial_kwargs: dict[str, Any], ) -> AsyncIterator[bytes]: """ Wrap an anthropic_messages (/v1/messages) streaming response so a @@ -5756,7 +5756,7 @@ class Router: has_generated_content: bool, buffered_lifecycle_chunks: tuple[bytes, ...], model: str, - initial_kwargs: dict[str, Any], # mutable-ok: handed to _aanthropic_messages_fallback_attempt, which mutates it + initial_kwargs: dict[str, Any], wrapper: "FallbackAwareAnthropicMessagesStream", ) -> AsyncGenerator[bytes, None]: """Turns a source-iterator failure into a fallback attempt or the error reaching the caller.""" @@ -6019,7 +6019,7 @@ class Router: async def _aanthropic_messages_fallback_attempt( self, e: "MidStreamFallbackError", - initial_kwargs: dict[str, Any], # mutable-ok: mutated in-place before re-entering the fallback chain + initial_kwargs: dict[str, Any], wrapper: "FallbackAwareAnthropicMessagesStream", ) -> AsyncGenerator[bytes, None]: """ @@ -9121,7 +9121,7 @@ class Router: return f"{type(value).__module__}.{type(value).__qualname__}" @staticmethod - def generate_model_id(model_group: str, litellm_params: dict) -> str: # mutable-ok: hashed read-only + def generate_model_id(model_group: str, litellm_params: dict) -> str: """ Helper function to consistently generate the same id for a deployment @@ -9193,7 +9193,7 @@ class Router: @staticmethod def _inherit_builtin_service_tier_pricing( - model_info: dict, # mutable-ok: deployment cost-map entry filled in place + model_info: dict, backend_model: str, custom_llm_provider: str | None, ) -> None: @@ -9218,7 +9218,7 @@ class Router: @staticmethod def _inherit_builtin_base_rates_for_off_peak( - model_info: dict, # mutable-ok: cost-map entry filled in place + model_info: dict, backend_model: str, custom_llm_provider: str | None, ) -> None: @@ -10395,7 +10395,7 @@ class Router: return (backend_key,) @staticmethod - def _deployment_model_cost_payload(deployment: Deployment) -> dict: # mutable-ok: cost-map entry + def _deployment_model_cost_payload(deployment: Deployment) -> dict: """The ``model_info`` a deployment contributes to ``litellm.model_cost``. Custom pricing lives on ``litellm_params`` rather than ``model_info``, and @@ -10403,7 +10403,7 @@ class Router: both are folded back in here. That keeps this reproducible from a deployment alone, which is what lets a refresh rebuild the same entries. """ - model_info: Final[dict] = deployment.model_info.model_dump(exclude_none=True) # mutable-ok: built in place + model_info: Final[dict] = deployment.model_info.model_dump(exclude_none=True) for field in CustomPricingLiteLLMParams.model_fields: field_value = deployment.litellm_params.get(field) if field_value is not None: @@ -10450,7 +10450,7 @@ class Router: def _register_deployment_in_model_cost( *, model_id: str | None, - model_info: dict, # mutable-ok: cost-map entry + model_info: dict, model: str, custom_llm_provider: str | None, ) -> None: diff --git a/litellm/router_strategy/complexity_router/complexity_router.py b/litellm/router_strategy/complexity_router/complexity_router.py index acc9770ab41..f347438bdf2 100644 --- a/litellm/router_strategy/complexity_router/complexity_router.py +++ b/litellm/router_strategy/complexity_router/complexity_router.py @@ -2028,7 +2028,7 @@ class ComplexityRouter(CustomLogger): self, prompt: str, system_prompt: str | None, - request_kwargs: dict[str, object] | None, # mutable-ok: handed to _classify_with_llm as-is + request_kwargs: dict[str, object] | None, messages: Sequence[Mapping[str, object]] | None, ) -> ClassificationOutcome: """Score locally, and only pay for the classifier call when the scorer did not confidently @@ -2061,7 +2061,7 @@ class ComplexityRouter(CustomLogger): self, prompt: str, system_prompt: str | None, - request_kwargs: dict[str, object] | None, # mutable-ok: handed to _classify_with_llm as-is + request_kwargs: dict[str, object] | None, messages: Sequence[Mapping[str, object]] | None, ) -> ClassificationOutcome: """Score locally, and only pay for the classifier when the score sits near a tier boundary. @@ -2190,7 +2190,7 @@ class ComplexityRouter(CustomLogger): self, prompt: str, system_prompt: str | None, - request_kwargs: dict[str, object] | None, # mutable-ok: handed to _classify_with_llm as-is + request_kwargs: dict[str, object] | None, messages: Sequence[Mapping[str, object]] | None, scored: ClassificationOutcome | None = None, ) -> ClassificationOutcome: @@ -2412,7 +2412,7 @@ class ComplexityRouter(CustomLogger): self, prompt: str, system_prompt: str | None, - request_kwargs: dict[str, object] | None, # mutable-ok: handed to resolve_structured_messages as-is + request_kwargs: dict[str, object] | None, raw_messages: list[dict[str, object]] | None, # mutable-ok: same shape _run_routing_plugins receives ) -> ClassificationOutcome: from litellm.litellm_core_utils.prompt_templates.factory import resolve_structured_messages @@ -2974,7 +2974,7 @@ class ComplexityRouter(CustomLogger): tier: ComplexityTier | str, model: str, candidates: tuple[str, ...], - request_kwargs: dict[str, object], # mutable-ok: adaptive feedback metadata must follow the selected model + request_kwargs: dict[str, object], retained_pin: _SessionAffinityPin | None = None, ) -> str: if not self._uses_deployment_pin or model not in candidates: @@ -3692,7 +3692,7 @@ class ComplexityRouter(CustomLogger): response: PreRoutingHookResponse, messages: list[dict[str, object]] | None, # mutable-ok: forwarded verbatim to the list-typed re-pick resolved_messages: Sequence[Mapping[str, object]] | None, - request_kwargs: dict, # mutable-ok: same shape the hook receives + request_kwargs: dict, context_fit: _RequestContextFit | None = None, retained_pin: _SessionAffinityPin | None = None, ) -> PreRoutingHookResponse: @@ -3828,7 +3828,7 @@ class ComplexityRouter(CustomLogger): model_name: str, messages: list[dict[str, Any]] | None, # mutable-ok: forwarded verbatim to the router's own probe input: str | list | None, # mutable-ok: mirrors the owner's own input parameter, which this forwards verbatim - request_kwargs: dict, # mutable-ok: same shape the hook receives + request_kwargs: dict, ) -> bool: """Whether the router would find a deployment for this group ON THIS REQUEST. @@ -3886,7 +3886,7 @@ class ComplexityRouter(CustomLogger): messages: list[dict[str, object]] | None, # mutable-ok: forwarded verbatim to the list-typed re-pick input: str | list | None, # mutable-ok: mirrors the owner's own input parameter, which this forwards verbatim resolved_messages: Sequence[Mapping[str, object]] | None, - request_kwargs: dict, # mutable-ok: same shape the hook receives + request_kwargs: dict, context_fit: _RequestContextFit | None = None, retained_pin: _SessionAffinityPin | None = None, ) -> PreRoutingHookResponse: diff --git a/litellm/router_utils/fallback_event_handlers.py b/litellm/router_utils/fallback_event_handlers.py index e33897e3b63..ffa85c000c5 100644 --- a/litellm/router_utils/fallback_event_handlers.py +++ b/litellm/router_utils/fallback_event_handlers.py @@ -340,7 +340,7 @@ def mid_stream_fallback_hop_kwargs( original_generic_function: Callable[..., object], controls: object, kwargs: Mapping[str, object], -) -> dict[str, object]: # mutable-ok: the streaming iterators rewrite it in place when they re-enter the chain +) -> dict[str, object]: """ The kwargs one streaming attempt re-enters the fallback chain with if its stream fails. @@ -369,7 +369,7 @@ _MID_STREAM_RETRY_BUDGET_KEY: Final = "max_retries" def mid_stream_retry_kwargs( hop_kwargs: Mapping[str, object], -) -> dict[str, object]: # mutable-ok: unpacked as **kwargs into the attempt function, which pops its controls carrier +) -> dict[str, object]: """ The kwargs a same-group retry re-enters the attempt function with. async_function_with_retries pops the per-request controls and the chain's original_function before any attempt runs, and diff --git a/litellm/rust_bridge/failures.py b/litellm/rust_bridge/failures.py index 959448f12bf..a2c8b8d82f6 100644 --- a/litellm/rust_bridge/failures.py +++ b/litellm/rust_bridge/failures.py @@ -44,8 +44,8 @@ class ExceptionMapper(Protocol): model: str, custom_llm_provider: str | None, original_exception: Exception, - completion_kwargs: dict[str, object], # mutable-ok: the legacy public exception mapper mutates its kwargs - extra_kwargs: dict[str, object], # mutable-ok: the legacy public exception mapper mutates its kwargs + completion_kwargs: dict[str, object], + extra_kwargs: dict[str, object], ) -> Exception: ... diff --git a/litellm/rust_bridge/streams.py b/litellm/rust_bridge/streams.py index 6ead17777d2..097ac8871ab 100644 --- a/litellm/rust_bridge/streams.py +++ b/litellm/rust_bridge/streams.py @@ -23,7 +23,7 @@ async def drive(execution: Execution) -> object: class Stream(AsyncIterator[object]): def __init__(self, execution: Execution, hidden_params: object = None) -> None: self._stream: Final = lifecycle.Stream(execution, hidden_params) - self._hidden_params: dict[str, object] = dict(_headers(hidden_params)) # mutable-ok: header writers mutate it + self._hidden_params: dict[str, object] = dict(_headers(hidden_params)) def __aiter__(self) -> Stream: return self @@ -38,7 +38,7 @@ class Stream(AsyncIterator[object]): class SyncStream(Iterator[object]): def __init__(self, execution: Execution, hidden_params: object = None) -> None: self._stream: Final = lifecycle.SyncStream(execution, hidden_params) - self._hidden_params: dict[str, object] = dict(_headers(hidden_params)) # mutable-ok: header writers mutate it + self._hidden_params: dict[str, object] = dict(_headers(hidden_params)) def __iter__(self) -> SyncStream: return self diff --git a/litellm/types/agents.py b/litellm/types/agents.py index 97dd67d3ff8..15c47c6a51b 100644 --- a/litellm/types/agents.py +++ b/litellm/types/agents.py @@ -361,11 +361,11 @@ class AgentCreateResponse(LiteLLMPydanticObjectBase): _hidden_params: dict[str, object] = PrivateAttr(default_factory=dict) @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: self._hidden_params = hidden_params @@ -383,11 +383,11 @@ class AgentDeleteResult(LiteLLMPydanticObjectBase): _hidden_params: dict[str, object] = PrivateAttr(default_factory=dict) @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: self._hidden_params = hidden_params @@ -405,11 +405,11 @@ class AgentListResponse(LiteLLMPydanticObjectBase): _hidden_params: dict[str, object] = PrivateAttr(default_factory=dict) @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: self._hidden_params = hidden_params @@ -427,11 +427,11 @@ class AgentVersionsResponse(LiteLLMPydanticObjectBase): _hidden_params: dict[str, object] = PrivateAttr(default_factory=dict) @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: self._hidden_params = hidden_params @@ -501,11 +501,11 @@ class LiteLLMSendMessageResponse(LiteLLMPydanticObjectBase): _hidden_params: dict[str, object] = PrivateAttr(default_factory=dict) @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: self._hidden_params = hidden_params @classmethod diff --git a/litellm/types/containers/main.py b/litellm/types/containers/main.py index 24a2418a5d0..78c93a7a0bb 100644 --- a/litellm/types/containers/main.py +++ b/litellm/types/containers/main.py @@ -28,11 +28,11 @@ class ContainerObject(LiteLLMBaseModel): _hidden_params: dict[str, Any] = PrivateAttr(default={}) @property - def hidden_params(self) -> dict[str, builtins.object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, builtins.object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: self._hidden_params = hidden_params def __contains__(self, key: str) -> bool: @@ -153,11 +153,11 @@ class ContainerFileObject(LiteLLMBaseModel): _hidden_params: dict[str, builtins.object] = PrivateAttr(default={}) @property - def hidden_params(self) -> dict[str, builtins.object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, builtins.object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: self._hidden_params = hidden_params def __contains__(self, key: str) -> bool: diff --git a/litellm/types/decisions.py b/litellm/types/decisions.py index 2745e00b6c6..a752ad88116 100644 --- a/litellm/types/decisions.py +++ b/litellm/types/decisions.py @@ -123,5 +123,5 @@ class DecisionsResponse(LiteLLMPydanticObjectBase): _hidden_params: dict[str, object] = PrivateAttr(default_factory=dict) @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params diff --git a/litellm/types/google_genai/main.py b/litellm/types/google_genai/main.py index 0241c9fa809..b239651e5ee 100644 --- a/litellm/types/google_genai/main.py +++ b/litellm/types/google_genai/main.py @@ -24,11 +24,11 @@ if TYPE_CHECKING: _hidden_params: dict = {} @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: self._hidden_params = hidden_params else: @@ -59,11 +59,11 @@ else: class GenerateContentResponse(BaseLiteLLMOpenAIResponseObject): @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: self._hidden_params = hidden_params def __init__(self, **kwargs) -> None: diff --git a/litellm/types/interactions/generated.py b/litellm/types/interactions/generated.py index c4f7a2d8a7b..d66489c2140 100644 --- a/litellm/types/interactions/generated.py +++ b/litellm/types/interactions/generated.py @@ -1084,11 +1084,11 @@ class InteractionsAPIResponse(BaseLiteLLMOpenAIResponseObject): _hidden_params: dict = PrivateAttr(default_factory=dict) @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: self._hidden_params = hidden_params @@ -1130,11 +1130,11 @@ class InteractionsAPIStreamingResponse(BaseLiteLLMOpenAIResponseObject): _hidden_params: dict = PrivateAttr(default_factory=dict) @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: self._hidden_params = hidden_params @@ -1147,11 +1147,11 @@ class DeleteInteractionResult(BaseLiteLLMOpenAIResponseObject): _hidden_params: dict = PrivateAttr(default_factory=dict) @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: self._hidden_params = hidden_params @@ -1164,11 +1164,11 @@ class CancelInteractionResult(BaseLiteLLMOpenAIResponseObject): _hidden_params: dict = PrivateAttr(default_factory=dict) @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: self._hidden_params = hidden_params diff --git a/litellm/types/litellm_params.py b/litellm/types/litellm_params.py index 9119fa16b5d..17ab5ce223b 100644 --- a/litellm/types/litellm_params.py +++ b/litellm/types/litellm_params.py @@ -222,8 +222,8 @@ class CostOptions: @dataclass(frozen=True, slots=True, kw_only=True) class ObservabilityOptions: id: str | None = None - metadata: MutableMapping[str, object] | None = None # mutable-ok: the router and logging write keys into it - litellm_metadata: MutableMapping[str, object] | None = None # mutable-ok: the proxy writes keys into it + metadata: MutableMapping[str, object] | None = None + litellm_metadata: MutableMapping[str, object] | None = None tags: Sequence[str] | None = None litellm_trace_id: str | None = None litellm_session_id: str | None = None diff --git a/litellm/types/llms/openai.py b/litellm/types/llms/openai.py index 78196e90003..cf9fbfe2866 100644 --- a/litellm/types/llms/openai.py +++ b/litellm/types/llms/openai.py @@ -122,11 +122,11 @@ class HttpxBinaryResponseContent(_HttpxBinaryResponseContent): _hidden_params: dict @property - def hidden_params(self) -> dict[str, builtins.object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, builtins.object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: self._hidden_params = hidden_params def __init__(self, response: httpx.Response) -> None: @@ -416,11 +416,11 @@ class OpenAIFileObject(LiteLLMBaseModel): _hidden_params: dict = PrivateAttr(default={"response_cost": 0.0}) # no cost for writing a file @property - def hidden_params(self) -> dict[str, builtins.object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, builtins.object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: self._hidden_params = hidden_params @model_serializer(mode="wrap") @@ -1440,11 +1440,11 @@ class ResponsesAPIResponse(BaseLiteLLMOpenAIResponseObject): _hidden_params: dict = PrivateAttr(default_factory=dict) @property - def hidden_params(self) -> dict[str, builtins.object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, builtins.object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: self._hidden_params = hidden_params @field_validator("reasoning", mode="before") @@ -1623,11 +1623,11 @@ class ResponseCompletedEvent(BaseLiteLLMOpenAIResponseObject): _hidden_params: dict = PrivateAttr(default_factory=dict) @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: self._hidden_params = hidden_params @@ -2431,11 +2431,11 @@ class OpenAIModerationResponse(BaseLiteLLMOpenAIResponseObject): _hidden_params: dict = PrivateAttr(default_factory=dict) @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: self._hidden_params = hidden_params @@ -2601,11 +2601,11 @@ class OpenAIVideoObject(LiteLLMBaseModel): _hidden_params: dict[str, _JsonValue] = PrivateAttr(default={}) @property - def hidden_params(self) -> dict[str, _JsonValue]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, _JsonValue]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, _JsonValue]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, _JsonValue]) -> None: self._hidden_params = hidden_params def __contains__(self, key) -> bool: diff --git a/litellm/types/proxy/guardrails/guardrail_hooks/hide_secrets.py b/litellm/types/proxy/guardrails/guardrail_hooks/hide_secrets.py index 3100968e8b1..4bc2015e639 100644 --- a/litellm/types/proxy/guardrails/guardrail_hooks/hide_secrets.py +++ b/litellm/types/proxy/guardrails/guardrail_hooks/hide_secrets.py @@ -10,7 +10,7 @@ class HideSecretsGuardrailConfigModel(GuardrailConfigModel): on the detect-secrets library; ``detect_secrets_config`` overrides the bundled plugin set.""" - detect_secrets_config: dict | None = Field( # mutable-ok: UI type derivation maps dict to "object" + detect_secrets_config: dict | None = Field( default=None, description="Optional detect-secrets configuration (plugins_used, filters_used) overriding the bundled plugin set", ) diff --git a/litellm/types/rerank.py b/litellm/types/rerank.py index 6d1ab85012b..aeeebc4bbb8 100644 --- a/litellm/types/rerank.py +++ b/litellm/types/rerank.py @@ -88,11 +88,11 @@ class RerankResponse(LiteLLMBaseModel): _hidden_params: dict = PrivateAttr(default_factory=dict) @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: self._hidden_params = hidden_params def __getitem__(self, key): diff --git a/litellm/types/responses/main.py b/litellm/types/responses/main.py index 59d34d6e815..26757ae6e72 100644 --- a/litellm/types/responses/main.py +++ b/litellm/types/responses/main.py @@ -166,11 +166,11 @@ class DeleteResponseResult(BaseLiteLLMOpenAIResponseObject): _hidden_params: dict = PrivateAttr(default_factory=dict) @property - def hidden_params(self) -> dict[str, builtins.object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, builtins.object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: self._hidden_params = hidden_params diff --git a/litellm/types/utils.py b/litellm/types/utils.py index f8f8388b6f7..528018e76a9 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -2093,11 +2093,11 @@ class ModelResponseBase(OpenAIObject): _hidden_params: dict = {} @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: self._hidden_params = hidden_params _response_headers: dict | None = None @@ -2324,11 +2324,11 @@ class EmbeddingResponse(OpenAIObject): _hidden_params: dict = {} @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: self._hidden_params = hidden_params _response_headers: dict | None = None @@ -2644,11 +2644,11 @@ class ImageResponse(OpenAIImageResponse, BaseLiteLLMOpenAIResponseObject): _hidden_params: dict = {} @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: self._hidden_params = hidden_params usage: ImageUsage | None = None @@ -2787,11 +2787,11 @@ class TranscriptionResponse(OpenAIObject): _response_headers: dict | None = None @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: self._hidden_params = hidden_params def __init__(self, text=None) -> None: @@ -4386,11 +4386,11 @@ class LiteLLMFineTuningJob(FineTuningJob): _hidden_params: dict = {} @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: self._hidden_params = hidden_params seed: int | None = None @@ -4408,11 +4408,11 @@ class LiteLLMBatch(Batch): _hidden_params: dict = {} @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: self._hidden_params = hidden_params usage: Usage | None = None @@ -4448,11 +4448,11 @@ class LiteLLMRealtimeStreamLoggingObject(LiteLLMPydanticObjectBase): _hidden_params: dict = {} @property - def hidden_params(self) -> dict[str, object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, object]) -> None: self._hidden_params = hidden_params @field_serializer("results") diff --git a/litellm/types/videos/main.py b/litellm/types/videos/main.py index da0fe9e7b67..1e946bce575 100644 --- a/litellm/types/videos/main.py +++ b/litellm/types/videos/main.py @@ -27,11 +27,11 @@ class VideoObject(LiteLLMBaseModel): _hidden_params: dict[str, builtins.object] = PrivateAttr(default={}) @property - def hidden_params(self) -> dict[str, builtins.object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, builtins.object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: self._hidden_params = hidden_params def __contains__(self, key) -> bool: @@ -124,11 +124,11 @@ class CharacterObject(LiteLLMBaseModel): _hidden_params: dict[str, builtins.object] = PrivateAttr(default={}) @property - def hidden_params(self) -> dict[str, builtins.object]: # mutable-ok: API requires mutation + def hidden_params(self) -> dict[str, builtins.object]: return self._hidden_params @hidden_params.setter - def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: # mutable-ok: API requires mutation + def hidden_params(self, hidden_params: dict[str, builtins.object]) -> None: self._hidden_params = hidden_params def __contains__(self, key) -> bool: diff --git a/litellm/utils.py b/litellm/utils.py index 72ce14912a9..63599815569 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -3266,7 +3266,7 @@ def _get_builtin_model_info_for_registration(model: str) -> ModelInfo | None: return None if is_generalized_model_info(info) else info -_runtime_registered_model_cost: Final[dict[str, dict[str, object]]] = {} # mutable-ok: replayed on reload +_runtime_registered_model_cost: Final[dict[str, dict[str, object]]] = {} class _LiveDeploymentReplay: diff --git a/scripts/check_type_discipline.py b/scripts/check_type_discipline.py index 8adc3ac27b7..1adafd33490 100644 --- a/scripts/check_type_discipline.py +++ b/scripts/check_type_discipline.py @@ -3,15 +3,16 @@ Rules ----- -LIT001 Mutable collection in a type annotation, anywhere it appears: function +LIT001 Mutable sequence or set in a type annotation, anywhere it appears: function parameters, return types, class attributes, locals, and module globals. - Covers the builtins (dict/list/set, bare or parameterized), their typing - aliases (Dict/List/...), the collections concretes (deque/defaultdict/...), - and the mutable ABCs (MutableMapping/MutableSequence/MutableSet). A mutable - collection lets whoever holds it grow or rewrite it after the fact; annotate - a read-only view instead (Mapping/Sequence/AbstractSet/tuple[X, ...]/ - frozenset[X], or a frozen dataclass / NamedTuple / ReadOnly TypedDict) and - build it functionally (comprehension / map, not append-in-a-loop). + Covers the builtins (list/set, bare or parameterized), their typing aliases + (List/Deque), the collections concretes (deque), and the mutable ABCs + (MutableSequence/MutableSet). Mappings (dict/Dict/MutableMapping/...) are + allowed: most of the Python ecosystem takes and returns dicts. A mutable + sequence lets whoever holds it grow or rewrite it after the fact; annotate + a read-only view instead (Sequence/AbstractSet/tuple[X, ...]/frozenset[X], + or a frozen dataclass / NamedTuple / ReadOnly TypedDict) and build it + functionally (comprehension / map, not append-in-a-loop). Suppress with `# mutable-ok: ` on the offending line. LIT003 noqa suppression without rule codes or without a reason. Required shape: `# noqa: TID251 # ` @@ -117,27 +118,21 @@ from collections.abc import Iterable, Iterator, Mapping, Sequence from types import MappingProxyType from typing import NamedTuple -# Mutable collection types, banned in *every* annotation. Name-based, so `dict`, -# `typing.Dict`, `collections.deque`, and `collections.abc.MutableMapping` all match -# however they were imported. The read-only interfaces (Mapping, Sequence, the -# immutable AbstractSet / `abc.Set`, Collection) and the immutable concretes (tuple, -# frozenset) are the escape hatch and are deliberately absent -- as is the bare name -# `Set`, which collides with the read-only `collections.abc.Set`. +# Mutable sequence and set types, banned in *every* annotation. Name-based, so `list`, +# `typing.List`, `collections.deque`, and `collections.abc.MutableSequence` all match +# however they were imported. Mappings (`dict`, `Dict`, `MutableMapping`, ...) are +# allowed: most of the Python ecosystem takes and returns dicts. The read-only +# interfaces (Sequence, the immutable AbstractSet / `abc.Set`, Collection) and the +# immutable concretes (tuple, frozenset) are the escape hatch and are deliberately +# absent -- as is the bare name `Set`, which collides with the read-only +# `collections.abc.Set`. MUTABLE_COLLECTIONS = frozenset( ( - "dict", "list", "set", - "Dict", "List", - "DefaultDict", - "OrderedDict", - "Counter", "Deque", - "ChainMap", "deque", - "defaultdict", - "MutableMapping", "MutableSequence", "MutableSet", ) @@ -167,6 +162,7 @@ REBIND_OK_RE = re.compile(r"#\s*rebind-ok(?::\s*(?P.*))?") WRITABLE_OK_RE = re.compile(r"#\s*writable-ok(?::\s*(?P.*))?") COMPREHENSION_OK_RE = re.compile(r"#\s*comprehension-ok(?::\s*(?P.*))?") + @dataclass(frozen=True, slots=True) class _OkToken: """One `*-ok` suppression token: its comment pattern and the rule codes it suppresses.""" @@ -292,9 +288,9 @@ def _is_literal_subscript(node: ast.AST) -> bool: def mutable_names_in(annotation: ast.AST) -> Iterator[str]: """Yield mutable-collection names anywhere inside an annotation expression. - Matches bare names (`dict`, `MutableMapping`) and dotted access (`typing.Dict`, + Matches bare names (`list`, `MutableSequence`) and dotted access (`typing.Dict`, `collections.deque`, `collections.abc.MutableMapping`), descends through nesting - (`Mapping[str, list[int]]`, `tuple[set[int], ...]`) and string forward references. + (`dict[str, list[int]]`, `tuple[set[int], ...]`) and string forward references. Skips `Literal[...]` subtrees: their string arguments are values, not forward references, so `Literal["list"]` is not the `list` type. """ @@ -322,7 +318,7 @@ def _mutable_ann(path: Path, line: int, name: str, where: str) -> Violation: line, "LIT001", f"mutable `{name}` in {where}: a mutable collection can be grown or rewritten " - f"by whoever holds it. Annotate a read-only view -- Mapping[...], Sequence[...], " + f"by whoever holds it. Annotate a read-only view -- Sequence[...], " f"AbstractSet[...], tuple[X, ...], frozenset[X], or a frozen dataclass / " f"NamedTuple / ReadOnly TypedDict -- and build it functionally, not by " f"append-in-a-loop (suppress: `# mutable-ok: `)", @@ -866,9 +862,7 @@ def _comprehension_owners(tree: ast.AST, ok_lines: frozenset[int]) -> Mapping[in silences a multi-line enclosing one and a violation sharing its only line can still be suppressed. """ - violating: Final = tuple( - n for n in ast.walk(tree) if isinstance(n, COMPREHENSION_NODES) and _violates(n) - ) + violating: Final = tuple(n for n in ast.walk(tree) if isinstance(n, COMPREHENSION_NODES) and _violates(n)) def nesting_key(node: ast.expr) -> tuple[int, int]: return (len(_span(node)), (node.end_col_offset or node.col_offset) - node.col_offset) @@ -924,18 +918,14 @@ def apply_suppressions( kept = tuple( v for v in raw - if not any( - v.line in suppressions.get(ok.token, frozenset()) and v.code in ok.codes - for ok in OK_SUPPRESSIONS - ) + if not any(v.line in suppressions.get(ok.token, frozenset()) and v.code in ok.codes for ok in OK_SUPPRESSIONS) ) unused = ( Violation( path, line, "LIT013", - f"`# {ok.token}` suppresses nothing: no " - f"{'/'.join(sorted(ok.codes))} violation on this line, so delete it", + f"`# {ok.token}` suppresses nothing: no {'/'.join(sorted(ok.codes))} violation on this line, so delete it", ) for ok in OK_SUPPRESSIONS for line in sorted(suppressions.get(ok.token, frozenset())) @@ -962,9 +952,7 @@ def check_file(path: Path) -> tuple[Violation, ...]: except SyntaxError as exc: return (*violations, Violation(path, exc.lineno or 0, "LIT000", f"syntax error: {exc.msg}")) - comprehension_violations: Final = tuple( - iter_comprehension_violations(path, tree, suppressions["comprehension-ok"]) - ) + comprehension_violations: Final = tuple(iter_comprehension_violations(path, tree, suppressions["comprehension-ok"])) return ( *violations, diff --git a/tests/e2e/mcp/oauth_chat_client.py b/tests/e2e/mcp/oauth_chat_client.py index 0c5c6106259..e8c4c1d7620 100644 --- a/tests/e2e/mcp/oauth_chat_client.py +++ b/tests/e2e/mcp/oauth_chat_client.py @@ -96,7 +96,7 @@ async def _browser_follow_authorize( code/state are read off the query string.""" from playwright.async_api import async_playwright - captured: dict[str, str] = {} # mutable-ok: hand-off from the request listener + captured: dict[str, str] = {} trail: list[str] = [] # mutable-ok: navigation diagnostics for a failed dance def _note_request(request: object) -> None: @@ -181,7 +181,7 @@ def _oauth_provider( """The SDK's real OAuth machinery (RFC 9728/8414 discovery, RFC 7591 DCR, PKCE, token exchange) with the browser leg driven by Playwright against the upstream's consent screen.""" - code_holder: dict[str, str | None] = {} # mutable-ok: hand-off between the two SDK callbacks + code_holder: dict[str, str | None] = {} async def _reject_redirect(_: str) -> None: raise AssertionError("gateway demanded a fresh upstream consent; stored per-user token was not reused") diff --git a/tests/integration/_support/agentic_probe.py b/tests/integration/_support/agentic_probe.py index ff264718d7d..58a510c1567 100644 --- a/tests/integration/_support/agentic_probe.py +++ b/tests/integration/_support/agentic_probe.py @@ -26,7 +26,7 @@ class AgenticProbe(CustomLogger): stream: bool, custom_llm_provider: str, kwargs: Mapping[str, object], - ) -> tuple[bool, dict[str, object]]: # mutable-ok: the CustomLogger hook contract returns a dict + ) -> tuple[bool, dict[str, object]]: out: Final = os.environ.get(OUT_ENVIRONMENT) if out: line: Final[Mapping[str, JsonValue]] = { diff --git a/tests/integration/authorization/test_rag_query_vector_store_allowlist.py b/tests/integration/authorization/test_rag_query_vector_store_allowlist.py index d3f16e46a1e..4ee0e4fe342 100644 --- a/tests/integration/authorization/test_rag_query_vector_store_allowlist.py +++ b/tests/integration/authorization/test_rag_query_vector_store_allowlist.py @@ -30,7 +30,7 @@ JsonObject: TypeAlias = dict[str, JsonValue] def _json_array(*values: JsonValue) -> JsonValue: - return [*values] # mutable-ok: request payloads and YAML sequences require list values + return [*values] def _permission_for_stores(*store_ids: str) -> JsonObject: diff --git a/tests/integration/authorization/test_search_tool_deny_by_default.py b/tests/integration/authorization/test_search_tool_deny_by_default.py index f29940f68b2..6279e5af51c 100644 --- a/tests/integration/authorization/test_search_tool_deny_by_default.py +++ b/tests/integration/authorization/test_search_tool_deny_by_default.py @@ -23,7 +23,7 @@ JsonObject: TypeAlias = dict[str, JsonValue] def _json_array(*values: JsonValue) -> JsonValue: - return [*values] # mutable-ok: request payloads and YAML sequences require list values + return [*values] def _grant(*search_tools: str) -> JsonObject: diff --git a/tests/integration/observability/_azure_storage_support.py b/tests/integration/observability/_azure_storage_support.py index 74bbb8e82fe..cac7cf14927 100644 --- a/tests/integration/observability/_azure_storage_support.py +++ b/tests/integration/observability/_azure_storage_support.py @@ -72,13 +72,11 @@ class RecordingDataLakeSink: delay_seconds: float = 0.0 lock: threading.Lock = field(default_factory=threading.Lock) directories: set[str] = field(default_factory=set) # mutable-ok: the sink is the durable store for the run - pending: dict[str, bytearray] = field(default_factory=dict) # mutable-ok: append lands before flush - files: dict[str, bytes] = field(default_factory=dict) # mutable-ok: flushed files must be readable later - flush_count: dict[str, int] = field(default_factory=dict) # mutable-ok: re-flush of one path means double upload + pending: dict[str, bytearray] = field(default_factory=dict) + files: dict[str, bytes] = field(default_factory=dict) + flush_count: dict[str, int] = field(default_factory=dict) rejected: list[str] = field(default_factory=list) # mutable-ok: rejected request methods seen while failing - unauthenticated: list[str] = field( - default_factory=list - ) # mutable-ok: targets whose SharedKey signature did not verify + unauthenticated: list[str] = field(default_factory=list) in_flight: int = 0 peak: int = 0 attempt_count: int = 0 diff --git a/tests/integration/observability/_s3_v2_support.py b/tests/integration/observability/_s3_v2_support.py index 205ac833fcc..c4d60bf0c9f 100644 --- a/tests/integration/observability/_s3_v2_support.py +++ b/tests/integration/observability/_s3_v2_support.py @@ -35,8 +35,8 @@ class RecordingS3Sink: peak: int = 0 attempts: int = 0 attempt_log: list[tuple[float, int]] = field(default_factory=list) # mutable-ok: appended under lock per PUT - attempt_counts: dict[str, int] = field(default_factory=dict) # mutable-ok: per-target PUT counts under lock - store: dict[str, bytes] = field(default_factory=dict) # mutable-ok: GET reads must see writes from earlier PUTs + attempt_counts: dict[str, int] = field(default_factory=dict) + store: dict[str, bytes] = field(default_factory=dict) def respond(self, request: Request) -> Reply: if request.method == "GET": diff --git a/tests/integration/observability/test_cache_hit_guardrail_metrics.py b/tests/integration/observability/test_cache_hit_guardrail_metrics.py index 888cfd7ab9d..5f898382e7d 100644 --- a/tests/integration/observability/test_cache_hit_guardrail_metrics.py +++ b/tests/integration/observability/test_cache_hit_guardrail_metrics.py @@ -401,7 +401,7 @@ def test_cache_hit_post_call_reject_keeps_deployment_labels_anthropic(gateway: G http_client=httpx.Client(trust_env=False, timeout=15), ) sdk.messages.create(model=rig.model_name, max_tokens=16, messages=[{"role": "user", "content": text}]) - raised: bool = False # mutable-ok: a flag set inside the except block cannot be Final + raised: bool = False try: sdk.messages.create( model=rig.model_name, diff --git a/tests/integration/observability/test_langtrace_delivery.py b/tests/integration/observability/test_langtrace_delivery.py index 84c9ed5bec0..1e27538120b 100644 --- a/tests/integration/observability/test_langtrace_delivery.py +++ b/tests/integration/observability/test_langtrace_delivery.py @@ -198,7 +198,7 @@ def _accepted(request: Request) -> Reply: class _Sink: wire: Wire api_key: str - # mutable-ok: drain() consumes, so batches accumulate across polls + received: list[Request] = field(default_factory=list) def collect(self) -> tuple[Request, ...]: diff --git a/tests/integration/observability/test_s3_v2_partition_granularity.py b/tests/integration/observability/test_s3_v2_partition_granularity.py index dc0a7184cf8..5d81927957f 100644 --- a/tests/integration/observability/test_s3_v2_partition_granularity.py +++ b/tests/integration/observability/test_s3_v2_partition_granularity.py @@ -519,7 +519,7 @@ def test_s3_v2_hour_layout_holds_when_another_logger_owns_cold_storage(gateway: marker: Final = "s3hgcs" + uuid.uuid4().hex[:8] upstream: Final = CountingUpstream() lock: Final = threading.Lock() - puts: Final[dict[str, bytes]] = {} # mutable-ok: filled per PUT by the bucket thread under lock + puts: Final[dict[str, bytes]] = {} def bucket_reply(request: Request) -> Reply: assert request.method == "PUT", request.method diff --git a/tests/integration/observability/test_s3_v2_upload_fanout.py b/tests/integration/observability/test_s3_v2_upload_fanout.py index 0154cce10df..0ba161400ef 100644 --- a/tests/integration/observability/test_s3_v2_upload_fanout.py +++ b/tests/integration/observability/test_s3_v2_upload_fanout.py @@ -350,7 +350,7 @@ class RejectingS3Sink: lock: threading.Lock = field(default_factory=threading.Lock) rejected_attempts: int = 0 rejected_times: list[float] = field(default_factory=list) # mutable-ok: appended under lock per rejected PUT - store: dict[str, bytes] = field(default_factory=dict) # mutable-ok: later PUTs must be visible to earlier polls + store: dict[str, bytes] = field(default_factory=dict) def respond(self, request: Request) -> Reply: assert request.method == "PUT", request.method diff --git a/tests/integration/security/_canary.py b/tests/integration/security/_canary.py index e58bda0936d..2a2b9a47356 100644 --- a/tests/integration/security/_canary.py +++ b/tests/integration/security/_canary.py @@ -191,7 +191,7 @@ def find_canary( ``AssertionError``) instead of returning a partial, possibly clean, result. """ data: Final = blob.encode() if isinstance(blob, str) else blob - found: Final[dict[str, Match]] = {} # mutable-ok: first (shallowest) encoding per slot wins + found: Final[dict[str, Match]] = {} for match in _matches(data, canaries, "raw", 0, _Budget(budget_bytes)): found.setdefault(match.slot, match) return tuple(found.values()) diff --git a/tests/unit/integrations/otel/test_otel_v2_destinations.py b/tests/unit/integrations/otel/test_otel_v2_destinations.py index 2d7e0745039..24e467e7fb6 100644 --- a/tests/unit/integrations/otel/test_otel_v2_destinations.py +++ b/tests/unit/integrations/otel/test_otel_v2_destinations.py @@ -1355,7 +1355,7 @@ class TestTenantSampling: did not find, ending while it flushes with its root still open, is decided on the spot rather than held by a fan-out that will never forward again.""" dest_exporter = InMemorySpanExporter() - stragglers = [] # mutable-ok: the span the first export ends, mid-shutdown + stragglers = [] class _EndsAStragglerOnExport(SimpleSpanProcessor): def on_end(self, span): diff --git a/tests/unit/integrations/test_s3_v2.py b/tests/unit/integrations/test_s3_v2.py index 8e9a8982cd9..34f96d554b7 100644 --- a/tests/unit/integrations/test_s3_v2.py +++ b/tests/unit/integrations/test_s3_v2.py @@ -4959,7 +4959,7 @@ async def test_503_response_lowers_the_adaptive_limit() -> None: logger.async_httpx_client = AsyncMock() logger.async_httpx_client.put = _StatusPut([_transient_failure_response(503), _ok_response()]) - logger._upload_limiter._limit = 64 # mutable-ok: seed the AIMD state above the floor without replaying growth + logger._upload_limiter._limit = 64 with patch("asyncio.sleep", new_callable=AsyncMock): logger.log_queue = [_element({"i": 0}, "0")] await logger.flush_queue() @@ -4973,7 +4973,7 @@ async def test_429_response_lowers_the_adaptive_limit() -> None: logger.async_httpx_client = AsyncMock() logger.async_httpx_client.put = _StatusPut([_transient_failure_response(429), _ok_response()]) - logger._upload_limiter._limit = 64 # mutable-ok: seed the AIMD state above the floor without replaying growth + logger._upload_limiter._limit = 64 with patch("asyncio.sleep", new_callable=AsyncMock): logger.log_queue = [_element({"i": 0}, "0")] await logger.flush_queue() @@ -4987,7 +4987,7 @@ async def test_slow_down_body_code_lowers_the_adaptive_limit() -> None: logger.async_httpx_client = AsyncMock() logger.async_httpx_client.put = _StatusPut([_slow_down_response()]) - logger._upload_limiter._limit = 64 # mutable-ok: seed the AIMD state above the floor without replaying growth + logger._upload_limiter._limit = 64 logger.log_queue = [_element({"i": 0}, "0")] await logger.async_send_batch() @@ -5002,7 +5002,7 @@ async def test_transport_error_lowers_the_adaptive_limit() -> None: [httpx.ConnectError("connect refused", request=MagicMock()), _ok_response()] ) - logger._upload_limiter._limit = 64 # mutable-ok: seed the AIMD state above the floor without replaying growth + logger._upload_limiter._limit = 64 with patch("asyncio.sleep", new_callable=AsyncMock): logger.log_queue = [_element({"i": 0}, "0")] await logger.flush_queue() @@ -5157,9 +5157,7 @@ async def test_repeated_failure_notifications_upload_once( ) if concurrent: - await asyncio.gather( - *(logging_obj.async_failure_handler(error, "synthetic traceback") for _ in range(3)) - ) + await asyncio.gather(*(logging_obj.async_failure_handler(error, "synthetic traceback") for _ in range(3))) else: for _ in range(3): await logging_obj.async_failure_handler(error, "synthetic traceback") @@ -5177,13 +5175,16 @@ def _vcr_outcome_gate(request, vcr): yield record_vcr_outcome(request, vcr) + @pytest_asyncio.fixture(loop_scope="function") async def drain_logging_worker(isolate_litellm_state: None) -> AsyncIterator[None]: yield await asyncio.wait_for(GLOBAL_LOGGING_WORKER.flush(), timeout=LOGGING_WORKER_DRAIN_TIMEOUT_SECONDS) + LOGGING_WORKER_DRAIN_TIMEOUT_SECONDS: Final = LOGGING_WORKER_MAX_TIME_PER_COROUTINE + 5.0 + @pytest.fixture(scope="function", autouse=True) def isolate_litellm_state(): """ @@ -5221,6 +5222,7 @@ def isolate_litellm_state(): if attr in _DEFAULTS: setattr(litellm, attr, _DEFAULTS[attr]) + _LIST_ATTRS = ( "callbacks", "success_callback", @@ -5252,6 +5254,7 @@ _DEFAULTS: Final = { if hasattr(litellm, attr) } + @pytest.fixture(scope="module") def setup_and_teardown(): """ @@ -5274,10 +5277,12 @@ def setup_and_teardown(): litellm.in_memory_llm_clients_cache.flush_cache() yield + @pytest.fixture def amazing_s3_retries(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(litellm, "num_retries", 3) + class _FakeS3Paginator: def __init__(self, objects): self.objects = objects @@ -5288,6 +5293,7 @@ class _FakeS3Paginator: return [{}] return [{"Contents": [{"Key": key} for key in keys]}] + class _FakeS3Client: def __init__(self): self.objects = defaultdict(dict) @@ -5311,8 +5317,10 @@ class _FakeS3Client: keys = sorted(self.objects[Bucket]) return {"Contents": [{"Key": key, "LastModified": 0} for key in keys]} + _FAKE_S3_CLIENT = _FakeS3Client() + @pytest.fixture def fake_s3_client(monkeypatch): _FAKE_S3_CLIENT.clear() @@ -5328,6 +5336,7 @@ def fake_s3_client(monkeypatch): litellm.success_callback = [] litellm.callbacks = [] + @pytest.mark.usefixtures( "cancel_s3_periodic_flush_tasks", "fake_s3_client", @@ -5385,6 +5394,7 @@ async def test_basic_s3_logging(sync_mode, streaming): for key in all_s3_keys: s3.delete_object(Bucket="load-testing-oct", Key=key) + @pytest.mark.usefixtures( "cancel_s3_periodic_flush_tasks", "fake_s3_client", @@ -5438,6 +5448,7 @@ async def test_basic_s3_v2_logging(streaming): f"Expected response_id={response_id} in one of the uploaded S3 keys: {uploaded_keys}" ) + @pytest.mark.usefixtures( "cancel_s3_periodic_flush_tasks", "fake_s3_client", @@ -5515,6 +5526,7 @@ async def test_basic_s3_v2_logging_failure(): assert data is not None assert '"model": "gpt-5-mini"' in data + def list_all_s3_objects(bucket_name): s3 = boto3.client("s3") @@ -5530,6 +5542,7 @@ def list_all_s3_objects(bucket_name): return total_objects, all_s3_keys + class TestS3Logger(S3Logger): def __init__(self, *args, **kwargs): self.recorded_requests = {} diff --git a/tests/unit/llms/anthropic/test_anthropic_common_utils.py b/tests/unit/llms/anthropic/test_anthropic_common_utils.py index 089538882eb..337108c80f8 100644 --- a/tests/unit/llms/anthropic/test_anthropic_common_utils.py +++ b/tests/unit/llms/anthropic/test_anthropic_common_utils.py @@ -2518,7 +2518,6 @@ class TestWifTierPrecedence: assert [record for record in caplog.records if "takes precedence" in record.getMessage()] == [] - class TestWifZeroBehaviorChange: def test_unconfigured_raises_same_authentication_error(self, clean_anthropic_env): """No WIF config and no keys: same AuthenticationError as today (message @@ -3506,7 +3505,7 @@ class TestModelDiscovery: from litellm.llms.custom_httpx.http_handler import HTTPHandler monkeypatch.setenv("ANTHROPIC_API_KEY", FAKE_REGULAR_KEY) - requested: Final = [] # mutable-ok: a test spy recording the URLs the client was asked for + requested: Final = [] def respond(request: httpx.Request) -> httpx.Response: requested.append(str(request.url)) diff --git a/tests/unit/llms/base_llm/auth/test_token_exchange.py b/tests/unit/llms/base_llm/auth/test_token_exchange.py index 52b4837ba97..fd84c4caf0e 100644 --- a/tests/unit/llms/base_llm/auth/test_token_exchange.py +++ b/tests/unit/llms/base_llm/auth/test_token_exchange.py @@ -138,7 +138,7 @@ class NeverRunsExecutor(concurrent.futures.Executor): test can show the backlog stops growing instead of consuming memory for as long as traffic lasts.""" def __init__(self) -> None: - self.submitted = 0 # mutable-ok: a test spy counting accepted work + self.submitted = 0 def submit(self, fn, /, *args, **kwargs): self.submitted += 1 @@ -916,7 +916,9 @@ class TestAssertionGuards: poster = ScriptedPoster([token_response()]) def reader(ref: str) -> str | None: - raise ImportError("the internal_issuer identity source needs PyJWT and cryptography: pip install 'litellm[proxy]'") + raise ImportError( + "the internal_issuer identity source needs PyJWT and cryptography: pip install 'litellm[proxy]'" + ) result = make_engine(poster, reader=reader).get_token(make_spec()) diff --git a/tests/unit/proxy/_experimental/mcp_server/test_gateway_dcr_flow.py b/tests/unit/proxy/_experimental/mcp_server/test_gateway_dcr_flow.py index 76d071b1852..9cadb3db766 100644 --- a/tests/unit/proxy/_experimental/mcp_server/test_gateway_dcr_flow.py +++ b/tests/unit/proxy/_experimental/mcp_server/test_gateway_dcr_flow.py @@ -743,7 +743,7 @@ async def test_single_use_guard_peek_reads_the_key_under_the_namespace_claim_wro from litellm.proxy._experimental.mcp_server.gateway_dcr_flow import _SingleUseGuard - stored: dict[str, int] = {} # mutable-ok: the fake Redis store the test inspects + stored: dict[str, int] = {} def namespaced(key: str) -> str: return f"ns:{key}" @@ -1914,7 +1914,9 @@ def _redis_that(async_increment, get=None): cache.redis_cache.async_increment = async_increment cache.redis_cache.check_and_fix_namespace = MagicMock(side_effect=lambda key: key) cache.redis_cache.init_async_client.return_value.get = get or AsyncMock(return_value=None) - cache.redis_cache.async_get_cache = AsyncMock(side_effect=AssertionError("peek must read the client, not the wrapper")) + cache.redis_cache.async_get_cache = AsyncMock( + side_effect=AssertionError("peek must read the client, not the wrapper") + ) cache.async_increment_cache = AsyncMock(side_effect=AssertionError("must not fall back to in-memory")) return cache @@ -2068,7 +2070,11 @@ async def test_refresh_of_a_rotated_token_answers_503_before_minting_while_redis (await _redeem_native(await _native_code(client_id, cache=issued), client_id, _Minter(), cache=issued)).body ) rotated = json.loads( - (await _refresh_native(payload["refresh_token"], client_id, _Minter(), _redis_that(AsyncMock(return_value=1)))).body + ( + await _refresh_native( + payload["refresh_token"], client_id, _Minter(), _redis_that(AsyncMock(return_value=1)) + ) + ).body )["refresh_token"] minter = _Minter() diff --git a/tests/unit/proxy/db/test_prisma_query_span.py b/tests/unit/proxy/db/test_prisma_query_span.py index abd95dec517..acae3da792d 100644 --- a/tests/unit/proxy/db/test_prisma_query_span.py +++ b/tests/unit/proxy/db/test_prisma_query_span.py @@ -146,7 +146,7 @@ class _Module: return tuple(chain[1:]) -_PARENTS: Final[dict[int, Mapping[int, ast.AST]]] = {} # mutable-ok: per-tree parent map memo +_PARENTS: Final[dict[int, Mapping[int, ast.AST]]] = {} def _parent_map(tree: ast.Module) -> Mapping[int, ast.AST]: diff --git a/tests/unit/proxy/guardrails/guardrail_hooks/unified_guardrails/test_unified_guardrail.py b/tests/unit/proxy/guardrails/guardrail_hooks/unified_guardrails/test_unified_guardrail.py index e797304a1c6..cf4676f1257 100644 --- a/tests/unit/proxy/guardrails/guardrail_hooks/unified_guardrails/test_unified_guardrail.py +++ b/tests/unit/proxy/guardrails/guardrail_hooks/unified_guardrails/test_unified_guardrail.py @@ -82,7 +82,7 @@ class RewritingGuardrail(RecordingGuardrail): async def apply_guardrail( self, inputs: GenericGuardrailAPIInputs, - request_data: dict, # mutable-ok: CustomGuardrail.apply_guardrail contract + request_data: dict, input_type: Literal["request", "response"], logging_obj: "LiteLLMLoggingObj | None" = None, ) -> GenericGuardrailAPIInputs: diff --git a/tests/unit/proxy/lens/test_dataset_endpoints.py b/tests/unit/proxy/lens/test_dataset_endpoints.py index 53d60d98d20..1c27b6464cc 100644 --- a/tests/unit/proxy/lens/test_dataset_endpoints.py +++ b/tests/unit/proxy/lens/test_dataset_endpoints.py @@ -47,7 +47,7 @@ ADMIN: Final = UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN, user_id="a class MemoryStore: def __init__(self) -> None: - self.rows: Final[dict[tuple[str, int], Dataset]] = {} # mutable-ok: stands in for the table + self.rows: Final[dict[tuple[str, int], Dataset]] = {} async def summaries(self) -> tuple[StoredSummary, ...]: latest: Final = {i: d for (i, _), d in sorted(self.rows.items(), key=lambda item: item[0][1])} diff --git a/tests/unit/proxy/pass_through_endpoints/test_pass_through_endpoints.py b/tests/unit/proxy/pass_through_endpoints/test_pass_through_endpoints.py index 521975ff70c..7a405185b1f 100644 --- a/tests/unit/proxy/pass_through_endpoints/test_pass_through_endpoints.py +++ b/tests/unit/proxy/pass_through_endpoints/test_pass_through_endpoints.py @@ -1696,7 +1696,9 @@ async def test_pass_through_request_streamed_response_is_owned_by_the_caller(): cache_dict[cache_key] = SimpleNamespace(client=httpx.AsyncClient(transport=httpx.MockTransport(transport_handler))) mock_proxy_logging = MagicMock() - mock_proxy_logging.pre_call_hook = AsyncMock(side_effect=lambda user_api_key_dict, data, call_type, endpoint_type: data) + mock_proxy_logging.pre_call_hook = AsyncMock( + side_effect=lambda user_api_key_dict, data, call_type, endpoint_type: data + ) mock_proxy_logging.post_call_failure_hook = AsyncMock() mock_proxy_logging.post_call_response_headers_hook = AsyncMock(return_value={}) mock_proxy_logging.get_proxy_hook = MagicMock(return_value=MagicMock()) @@ -2744,12 +2746,8 @@ async def test_pass_through_request_follows_redirect_to_final_response(httpx_tra mock_user_api_key_dict = MagicMock() with respx.mock(assert_all_called=True) as upstream: - upstream.get("https://upstream.test/redirect/1").respond( - 302, headers={"Location": "/get"} - ) - upstream.get("https://upstream.test/get").respond( - 200, json={"url": "https://upstream.test/get"} - ) + upstream.get("https://upstream.test/redirect/1").respond(302, headers={"Location": "/get"}) + upstream.get("https://upstream.test/get").respond(200, json={"url": "https://upstream.test/get"}) response = await pass_through_request( request=mock_request, @@ -3835,6 +3833,7 @@ from litellm.exceptions import ( GuardrailRaisedException, ) from litellm.litellm_core_utils.logging_worker import GLOBAL_LOGGING_WORKER + _PT_MODULE = "litellm.proxy.pass_through_endpoints.pass_through_endpoints" @@ -7002,18 +7001,32 @@ async def test_user_defined_passthrough_is_neither_tracked_nor_enforced(metadata budget: Final = {"managed-model": {"budget_limit": 0.1, "time_period": "1d"}} limiter: Final = _PROXY_VirtualKeyModelMaxBudgetLimiter(DualCache()) auth: Final = UserAPIKeyAuth( - api_key="custom-key", token="custom-key", team_id="shared-team", team_model_max_budget=budget, + api_key="custom-key", + token="custom-key", + team_id="shared-team", + team_model_max_budget=budget, ) endpoint: Final = create_pass_through_route( - endpoint="/custom-budget-test", target="https://upstream.test/echo", custom_headers={}, cost_per_request=0.25, + endpoint="/custom-budget-test", + target="https://upstream.test/echo", + custom_headers={}, + cost_per_request=0.25, + ) + request: Final = Request( + { + "type": "http", + "method": "POST", + "path": "/custom-budget-test", + "headers": [], + "query_string": b"", + "endpoint": endpoint, + } ) - request: Final = Request({ - "type": "http", "method": "POST", "path": "/custom-budget-test", "headers": [], - "query_string": b"", "endpoint": endpoint, - }) body: Final = { - "model": "upstream-only-model", metadata_slot: { - "model_group": "managed-model", "customer_label": "retained", + "model": "upstream-only-model", + metadata_slot: { + "model_group": "managed-model", + "customer_label": "retained", "user_api_key_team_model_max_budget": budget, }, } @@ -7021,48 +7034,84 @@ async def test_user_defined_passthrough_is_neither_tracked_nor_enforced(metadata assert await limiter.is_team_within_model_budget("shared-team", budget, None, "managed-model") start: Final = datetime.now() logging_obj: Final = LiteLLMLoggingObj( - model="upstream-only-model", messages=[], stream=False, call_type="pass_through_endpoint", - start_time=start, litellm_call_id="custom-budget", function_id="custom-budget", kwargs={}, + model="upstream-only-model", + messages=[], + stream=False, + call_type="pass_through_endpoint", + start_time=start, + litellm_call_id="custom-budget", + function_id="custom-budget", + kwargs={}, dynamic_async_success_callbacks=[limiter], ) payload: Final = { - "url": "https://upstream.test/echo", "request_body": body, "request_method": "POST", "cost_per_request": 0.25, + "url": "https://upstream.test/echo", + "request_body": body, + "request_method": "POST", + "cost_per_request": 0.25, } kwargs: Final = HttpPassThroughEndpointHelpers._init_kwargs_for_pass_through_endpoint( - request=request, user_api_key_dict=auth, passthrough_logging_payload=payload, logging_obj=logging_obj, - _parsed_body=body, litellm_call_id="custom-budget", + request=request, + user_api_key_dict=auth, + passthrough_logging_payload=payload, + logging_obj=logging_obj, + _parsed_body=body, + litellm_call_id="custom-budget", ) logging_obj.update_environment_variables( - model="upstream-only-model", user="unknown", optional_params={}, - litellm_params=kwargs["litellm_params"], call_type="pass_through_endpoint", + model="upstream-only-model", + user="unknown", + optional_params={}, + litellm_params=kwargs["litellm_params"], + call_type="pass_through_endpoint", ) response: Final = httpx.Response( - 200, request=httpx.Request("POST", "https://upstream.test/echo"), json={"ok": True}, + 200, + request=httpx.Request("POST", "https://upstream.test/echo"), + json={"ok": True}, ) await PassThroughEndpointLogging().pass_through_async_success_handler( - httpx_response=response, response_body={"ok": True}, request_body=body, logging_obj=logging_obj, - url_route="https://upstream.test/echo", result=response.text, start_time=start, end_time=datetime.now(), - cache_hit=False, **kwargs, + httpx_response=response, + response_body={"ok": True}, + request_body=body, + logging_obj=logging_obj, + url_route="https://upstream.test/echo", + result=response.text, + start_time=start, + end_time=datetime.now(), + cache_hit=False, + **kwargs, ) assert logging_obj.model_call_details["response_cost"] == 0.25 assert await limiter.is_team_within_model_budget("shared-team", budget, None, "managed-model") metadata: Final = kwargs["litellm_params"]["metadata"] assert (metadata["model_group"], metadata["customer_label"]) == ("managed-model", "retained") - assert metadata.keys().isdisjoint({ - "user_api_key_model_max_budget", "user_api_key_team_model_max_budget", - "user_api_key_user_model_max_budget", "user_api_key_end_user_model_max_budget", - }) + assert metadata.keys().isdisjoint( + { + "user_api_key_model_max_budget", + "user_api_key_team_model_max_budget", + "user_api_key_user_model_max_budget", + "user_api_key_end_user_model_max_budget", + } + ) @pytest.mark.parametrize("metadata_slot", ["metadata", "litellm_metadata"]) def test_builtin_passthrough_pins_model_group_to_the_resolved_model(metadata_slot: str) -> None: - request: Final = Request({ - "type": "http", "method": "POST", "path": "/gemini/v1beta/models/gemini-2.5-flash:generateContent", - "headers": [], "query_string": b"", - }) + request: Final = Request( + { + "type": "http", + "method": "POST", + "path": "/gemini/v1beta/models/gemini-2.5-flash:generateContent", + "headers": [], + "query_string": b"", + } + ) kwargs: Final = HttpPassThroughEndpointHelpers._init_kwargs_for_pass_through_endpoint( - request=request, user_api_key_dict=UserAPIKeyAuth(token="hash", user_id="u-1"), - passthrough_logging_payload=MagicMock(), logging_obj=MagicMock(), + request=request, + user_api_key_dict=UserAPIKeyAuth(token="hash", user_id="u-1"), + passthrough_logging_payload=MagicMock(), + logging_obj=MagicMock(), _parsed_body={"contents": [], metadata_slot: {"model_group": "unbounded-client-choice"}}, ) assert kwargs["litellm_params"]["metadata"]["model_group"] == "gemini-2.5-flash" @@ -7144,7 +7193,7 @@ async def _drive_passthrough_request_and_capture_logging( mock_request.query_params = QueryParams({}) mock_request.body = AsyncMock(return_value=b'{"model": "gemini-2.0-flash"}') - captured_data: dict = {} # mutable-ok: the pre-call hook records the request data into it + captured_data: dict = {} async def capture_pre_call_hook( user_api_key_dict, data, call_type, endpoint_type: EndpointType = EndpointType.GENERIC @@ -7311,7 +7360,7 @@ async def test_pass_through_request_leaves_guardrail_readable_metadata(): }, ) - observed: dict[str, dict[str, str] | BaseException] = {} # mutable-ok: the pre-call hook records into it + observed: dict[str, dict[str, str] | BaseException] = {} def read_headers_the_way_a_guardrail_does(logging_obj: LiteLLMLoggingObj | None) -> None: assert logging_obj is not None @@ -7428,7 +7477,9 @@ def test_passthrough_logs_the_resolved_deployment_model_info_over_the_request_bo the call to (LIT-1761: passthrough successes carried model_id="").""" mock_request = MagicMock(spec=Request) mock_request.method = "POST" - mock_request.url = httpx.URL("http://0.0.0.0:4000/vertex_ai/v1/projects/p/locations/global/publishers/google/models/gemini-3.8-flash:generateContent") + mock_request.url = httpx.URL( + "http://0.0.0.0:4000/vertex_ai/v1/projects/p/locations/global/publishers/google/models/gemini-3.8-flash:generateContent" + ) mock_request.headers = Headers({}) mock_request.scope = {} mock_request.state = SimpleNamespace( @@ -7991,9 +8042,7 @@ async def test_a_deleted_db_pass_through_stops_serving_on_the_next_db_sync(tmp_p @pytest.mark.asyncio -async def test_config_pass_through_reads_its_custom_key_header_when_the_db_holds_pass_throughs( - tmp_path, monkeypatch -): +async def test_config_pass_through_reads_its_custom_key_header_when_the_db_holds_pass_throughs(tmp_path, monkeypatch): proxy: Final = await _boot_db_backed_proxy( tmp_path, monkeypatch, @@ -8368,12 +8417,14 @@ async def _drain_logging_worker(): await GLOBAL_LOGGING_WORKER.stop() yield + @pytest.fixture() def _vcr_outcome_gate(request, vcr): install_live_call_probe(request, vcr) yield record_vcr_outcome(request, vcr) + @pytest.mark.usefixtures("_drain_logging_worker", "_vcr_outcome_gate") def test_update_pass_through_route_updates_registry(): """ @@ -8442,6 +8493,7 @@ def test_update_pass_through_route_updates_registry(): asyncio.run(_async_test()) + @pytest.mark.usefixtures("_drain_logging_worker", "_vcr_outcome_gate") def test_update_subpath_route_updates_registry(): """ diff --git a/tests/unit/proxy/policy_engine/test_policy_matcher.py b/tests/unit/proxy/policy_engine/test_policy_matcher.py index 862b5793eba..5d0047936fb 100644 --- a/tests/unit/proxy/policy_engine/test_policy_matcher.py +++ b/tests/unit/proxy/policy_engine/test_policy_matcher.py @@ -33,13 +33,8 @@ class TestPolicyMatcherPatternMatching: def test_matches_pattern_exact(self): """Test exact pattern matching.""" - assert ( - PolicyMatcher.matches_pattern("healthcare-team", ["healthcare-team"]) - is True - ) - assert ( - PolicyMatcher.matches_pattern("finance-team", ["healthcare-team"]) is False - ) + assert PolicyMatcher.matches_pattern("healthcare-team", ["healthcare-team"]) is True + assert PolicyMatcher.matches_pattern("finance-team", ["healthcare-team"]) is False def test_matches_pattern_wildcard(self): """Test wildcard pattern matching.""" @@ -59,33 +54,25 @@ class TestPolicyMatcherScopeMatching: def test_scope_matches_all_fields(self): """Test scope matches when all fields match.""" scope = PolicyScope(teams=["healthcare-team"], keys=["*"], models=["gpt-4"]) - context = PolicyMatchContext( - team_alias="healthcare-team", key_alias="any-key", model="gpt-4" - ) + context = PolicyMatchContext(team_alias="healthcare-team", key_alias="any-key", model="gpt-4") assert PolicyMatcher.scope_matches(scope, context) is True def test_scope_does_not_match_team(self): """Test scope doesn't match when team doesn't match.""" scope = PolicyScope(teams=["healthcare-team"], keys=["*"], models=["*"]) - context = PolicyMatchContext( - team_alias="finance-team", key_alias="any-key", model="gpt-4" - ) + context = PolicyMatchContext(team_alias="finance-team", key_alias="any-key", model="gpt-4") assert PolicyMatcher.scope_matches(scope, context) is False def test_scope_matches_with_wildcard_patterns(self): """Test scope matches with wildcard patterns.""" scope = PolicyScope(teams=["*"], keys=["dev-key-*"], models=["bedrock/*"]) - context = PolicyMatchContext( - team_alias="any-team", key_alias="dev-key-123", model="bedrock/claude-3" - ) + context = PolicyMatchContext(team_alias="any-team", key_alias="dev-key-123", model="bedrock/claude-3") assert PolicyMatcher.scope_matches(scope, context) is True def test_scope_global_wildcard(self): """Test global scope with all wildcards.""" scope = PolicyScope(teams=["*"], keys=["*"], models=["*"]) - context = PolicyMatchContext( - team_alias="any-team", key_alias="any-key", model="any-model" - ) + context = PolicyMatchContext(team_alias="any-team", key_alias="any-key", model="any-model") assert PolicyMatcher.scope_matches(scope, context) is True @@ -138,9 +125,7 @@ class TestPolicyMatcherScopeMatchingWithTags: def test_scope_tags_and_team_combined(self): """Test scope with both tags and team — both must match (AND logic).""" - scope = PolicyScope( - teams=["team-a"], keys=["*"], models=["*"], tags=["healthcare"] - ) + scope = PolicyScope(teams=["team-a"], keys=["*"], models=["*"], tags=["healthcare"]) # Both match context_both = PolicyMatchContext( @@ -185,9 +170,7 @@ class TestPolicyMatcherWithAttachments: ) # Test matching via the registry directly - context = PolicyMatchContext( - team_alias="healthcare-team", key_alias="k", model="gpt-4" - ) + context = PolicyMatchContext(team_alias="healthcare-team", key_alias="k", model="gpt-4") attached = registry.get_attached_policies(context) assert "healthcare-policy" in attached @@ -202,9 +185,7 @@ class TestPolicyMatcherWithAttachments: ] ) - context = PolicyMatchContext( - team_alias="finance-team", key_alias="k", model="gpt-4" - ) + context = PolicyMatchContext(team_alias="finance-team", key_alias="k", model="gpt-4") attached = registry.get_attached_policies(context) assert "healthcare-policy" not in attached @@ -314,7 +295,7 @@ class TestGetMatchingPoliciesFallback: _MODELS: Final = ("gpt-4o", "gpt-5.5", "claude-opus-4-1") -def _policy_forest(draw: st.DrawFn) -> dict[str, Policy]: # mutable-ok: PolicyResolver takes dict[str, Policy] +def _policy_forest(draw: st.DrawFn) -> dict[str, Policy]: names: Final = tuple(f"p{i}" for i in range(draw(st.integers(min_value=1, max_value=6)))) return { name: Policy( @@ -329,7 +310,7 @@ def _policy_forest(draw: st.DrawFn) -> dict[str, Policy]: # mutable-ok: PolicyR @st.composite def _forest_and_request( draw: st.DrawFn, -) -> tuple[dict[str, Policy], tuple[str, ...], PolicyMatchContext]: # mutable-ok: PolicyResolver takes dict +) -> tuple[dict[str, Policy], tuple[str, ...], PolicyMatchContext]: policies: Final = _policy_forest(draw) attached: Final = tuple(draw(st.lists(st.sampled_from(sorted(policies)), unique=True))) context: Final = PolicyMatchContext(team_alias="t", key_alias="k", model=draw(st.sampled_from(_MODELS))) @@ -341,7 +322,7 @@ def _own_condition_applies(policy: Policy, context: PolicyMatchContext) -> bool: def _applicable_chain( - policies: dict[str, Policy], # mutable-ok: PolicyResolver takes dict[str, Policy] + policies: dict[str, Policy], name: str, context: PolicyMatchContext, ) -> tuple[str, ...]: @@ -354,7 +335,7 @@ class TestChainMatchingProperties: @settings(max_examples=400, deadline=None) def test_chain_matching_only_widens_to_applicable_ancestor_guardrails( self, - case: tuple[dict[str, Policy], tuple[str, ...], PolicyMatchContext], # mutable-ok: PolicyResolver takes dict + case: tuple[dict[str, Policy], tuple[str, ...], PolicyMatchContext], ): policies, attached, context = case head: Final = tuple( @@ -379,7 +360,7 @@ class TestChainMatchingProperties: class TestAncestorAdmissionLogging: @staticmethod - def _chain() -> dict[str, Policy]: # mutable-ok: PolicyResolver takes dict[str, Policy] + def _chain() -> dict[str, Policy]: return { "parent": Policy(guardrails=PolicyGuardrails(add=["g-parent"])), "child": Policy( diff --git a/tests/unit/router_utils/test_cooldown_cache.py b/tests/unit/router_utils/test_cooldown_cache.py index 98f2b254a3f..ab46da13a29 100644 --- a/tests/unit/router_utils/test_cooldown_cache.py +++ b/tests/unit/router_utils/test_cooldown_cache.py @@ -441,7 +441,7 @@ class SharedRedisDouble: """ def __init__(self) -> None: - self.store: dict = {} # mutable-ok: stands in for Redis' own mutable keyspace + self.store: dict = {} def set_cache(self, key, value, **kwargs): self.store[key] = value diff --git a/tests/unit/test_check_type_discipline.py b/tests/unit/test_check_type_discipline.py index 5b8ea7b56ac..ee598ff353d 100644 --- a/tests/unit/test_check_type_discipline.py +++ b/tests/unit/test_check_type_discipline.py @@ -151,12 +151,12 @@ def test_reasonless_ok_on_a_clean_line_is_lit005_not_lit013(tmp_path): def test_mutable_annotation_is_flagged(tmp_path): - assert "LIT001" in _codes(tmp_path, "x: dict[str, int]\n") + assert "LIT001" in _codes(tmp_path, "x: list[int]\n") def test_typing_alias_and_forward_ref_annotations_are_flagged(tmp_path): assert "LIT001" in _codes(tmp_path, "from typing import List\nx: List[int]\n") - assert "LIT001" in _codes(tmp_path, 'x: "dict[str, int]"\n') + assert "LIT001" in _codes(tmp_path, 'x: "list[int]"\n') def test_literal_string_args_are_values_not_forward_refs(tmp_path): @@ -166,7 +166,7 @@ def test_literal_string_args_are_values_not_forward_refs(tmp_path): 'from typing import Literal\ndef f(op: Literal["create", "list"] = "create") -> None:\n return None\n', ) assert "LIT001" not in _codes(tmp_path, 'import typing\nx: typing.Literal["dict"] = "dict"\n') - assert "LIT001" in _codes(tmp_path, 'from typing import Literal\nx: dict[str, Literal["a"]]\n') + assert "LIT001" in _codes(tmp_path, 'from typing import Literal\nx: set[Literal["a"]]\n') assert "LIT001" in _codes(tmp_path, "x: \"Literal['x'] | list[int]\"\n") @@ -175,6 +175,26 @@ def test_readonly_annotations_are_clean(tmp_path): assert "LIT001" not in _codes(tmp_path, f"from typing import Mapping, Sequence\nx: {ann}\n") +def test_mapping_annotations_are_allowed(tmp_path): + for ann in ( + "dict[str, int]", + "Dict[str, int]", + "DefaultDict[str, int]", + "MutableMapping[str, int]", + "defaultdict[str, int]", + "OrderedDict[str, int]", + ): + source = ( + "from collections import OrderedDict, defaultdict\n" + "from collections.abc import MutableMapping\n" + "from typing import DefaultDict, Dict\n" + f"x: {ann}\n" + ) + assert "LIT001" not in _codes(tmp_path, source), ann + assert "LIT001" not in _codes(tmp_path, 'x: "dict[str, int]"\n') + assert "LIT001" in _codes(tmp_path, "x: dict[str, list[int]]\n") + + # --------------------------------------------------------------------------- # # Casts (LIT006) # --------------------------------------------------------------------------- # @@ -619,23 +639,12 @@ def test_comprehension_ok_with_reason_suppresses_lit014(tmp_path: Path): def test_comprehension_ok_on_any_spanned_line_suppresses_lit014(tmp_path: Path): - src = ( - "y = [\n" - " x for a in xs\n" - " for x in a\n" - "] # comprehension-ok: cartesian product is the clearest form\n" - ) + src = "y = [\n x for a in xs\n for x in a\n] # comprehension-ok: cartesian product is the clearest form\n" assert "LIT014" not in _codes(tmp_path, src) def test_comprehension_ok_after_the_closing_line_does_not_suppress(tmp_path: Path): - src = ( - "y = [\n" - " x for a in xs\n" - " for x in a\n" - "]\n" - "# comprehension-ok: cartesian product is the clearest form\n" - ) + src = "y = [\n x for a in xs\n for x in a\n]\n# comprehension-ok: cartesian product is the clearest form\n" f = tmp_path / "snippet.py" f.write_text(src, encoding="utf-8") violations = checker.check_file(f) diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index cff1f5a8535..d1c9c21d3c7 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -202,9 +202,7 @@ async def test_url_with_format_param_openai(model, sync_mode): } ], } - with patch.object( - client.chat.completions.with_raw_response, "create" - ) as mock_client: + with patch.object(client.chat.completions.with_raw_response, "create") as mock_client: try: if sync_mode: response = completion(**args, client=client) @@ -431,9 +429,7 @@ def test_embedding_keeps_an_internal_prefixed_kwarg_out_of_the_provider_request( def test_custom_provider_with_extra_headers(): - with patch.object( - litellm.llms.custom_httpx.http_handler.HTTPHandler, "post" - ) as mock_post: + with patch.object(litellm.llms.custom_httpx.http_handler.HTTPHandler, "post") as mock_post: response = litellm.completion( model="custom/custom", messages=[{"role": "user", "content": "Hello, how are you?"}], @@ -447,9 +443,7 @@ def test_custom_provider_with_extra_headers(): def test_custom_provider_with_extra_body(): - with patch.object( - litellm.llms.custom_httpx.http_handler.HTTPHandler, "post" - ) as mock_post: + with patch.object(litellm.llms.custom_httpx.http_handler.HTTPHandler, "post") as mock_post: response = litellm.completion( model="custom/custom", messages=[{"role": "user", "content": "Hello, how are you?"}], @@ -476,9 +470,7 @@ def test_custom_provider_with_extra_body(): } # test that extra_body is not passed if not provided - with patch.object( - litellm.llms.custom_httpx.http_handler.HTTPHandler, "post" - ) as mock_post: + with patch.object(litellm.llms.custom_httpx.http_handler.HTTPHandler, "post") as mock_post: response = litellm.completion( model="custom/custom", messages=[{"role": "user", "content": "Hello, how are you?"}], @@ -509,9 +501,7 @@ def set_openrouter_api_key(): @pytest.mark.asyncio -async def test_extra_body_with_fallback( - respx_mock: respx.MockRouter, set_openrouter_api_key, monkeypatch -): +async def test_extra_body_with_fallback(respx_mock: respx.MockRouter, set_openrouter_api_key, monkeypatch): """ test regression for https://github.com/BerriAI/litellm/issues/8425. @@ -579,9 +569,7 @@ async def test_extra_body_with_fallback( # Verify the response assert response is not None - assert ( - len(respx_mock.calls) > 0 - ), "Mock was not called - check if aiohttp transport is properly disabled" + assert len(respx_mock.calls) > 0, "Mock was not called - check if aiohttp transport is properly disabled" # Get the request from the mock request: httpx.Request = respx_mock.calls[0].request @@ -605,9 +593,7 @@ async def test_extra_body_with_fallback( @pytest.mark.parametrize("env_base", ["OPENAI_BASE_URL", "OPENAI_API_BASE"]) @pytest.mark.asyncio @pytest.mark.flaky(retries=3, delay=1) -async def test_openai_env_base( - respx_mock: respx.MockRouter, env_base, openai_api_response, monkeypatch -): +async def test_openai_env_base(respx_mock: respx.MockRouter, env_base, openai_api_response, monkeypatch): "This tests OpenAI env variables are honored, including legacy OPENAI_API_BASE" # Ensure aiohttp transport is disabled to use httpx which respx can mock litellm.disable_aiohttp_transport = True @@ -622,9 +608,7 @@ async def test_openai_env_base( messages = [{"role": "user", "content": "Hello, how are you?"}] # Configure respx mock to intercept the request - mock_route = respx_mock.post( - url__regex=r"http://localhost:12345/v1/chat/completions.*" - ).mock( + mock_route = respx_mock.post(url__regex=r"http://localhost:12345/v1/chat/completions.*").mock( return_value=httpx.Response( status_code=200, json={ @@ -658,9 +642,7 @@ async def test_openai_env_base( assert response.choices[0].message.content == "Hello from mocked response!" # Verify the mock was called - assert ( - mock_route.called - ), "Mock route was not called - request may have bypassed respx" + assert mock_route.called, "Mock route was not called - request may have bypassed respx" finally: # Clean up to avoid affecting other tests litellm.disable_aiohttp_transport = False @@ -755,9 +737,7 @@ def test_return_raw_request_does_not_call_provider(respx_mock: respx.MockRouter) assert route.call_count == 0 assert request.get("error") is None assert request["raw_request_body"]["model"] == model - assert request["raw_request_body"]["messages"] == [ - {"role": "user", "content": "hi"} - ] + assert request["raw_request_body"]["messages"] == [{"role": "user", "content": "hi"}] def test_return_raw_request_ignores_turn_off_message_logging( @@ -790,9 +770,7 @@ def test_completion_forwards_verbosity_in_raw_request(respx_mock: respx.MockRout model = "gpt-5.2" messages = [{"role": "user", "content": "hi"}] - respx_mock.post("https://api.openai.com/v1/chat/completions").mock( - return_value=_mocked_openai_chat_response(model) - ) + respx_mock.post("https://api.openai.com/v1/chat/completions").mock(return_value=_mocked_openai_chat_response(model)) request = return_raw_request( endpoint=CallTypes.completion, @@ -809,9 +787,7 @@ def test_completion_forwards_verbosity_in_raw_request(respx_mock: respx.MockRout @pytest.mark.asyncio -async def test_acompletion_forwards_verbosity_to_provider_request( - respx_mock: respx.MockRouter, monkeypatch -): +async def test_acompletion_forwards_verbosity_to_provider_request(respx_mock: respx.MockRouter, monkeypatch): """Regression test: acompletion() must forward the verbosity param to the provider request body.""" original_disable_aiohttp = litellm.disable_aiohttp_transport try: @@ -872,9 +848,9 @@ def test_responses_api_bridge_check_gpt_5_4_pro(): model=model_name, custom_llm_provider="openai", ) - assert ( - model_info.get("mode") == "responses" - ), f"{model_name} should have mode='responses', got '{model_info.get('mode')}'" + assert model_info.get("mode") == "responses", ( + f"{model_name} should have mode='responses', got '{model_info.get('mode')}'" + ) def test_responses_api_bridge_check_gpt_5_4_tools_plus_reasoning_routes_to_responses(): @@ -1351,7 +1327,7 @@ def test_responses_api_bridge_check_openai_backed_custom_api_base_with_unset_eff tools=[{"type": "function", "function": {"name": "get_capital"}}], reasoning_effort=None, api_base=api_base, - ) + ) assert model == "gpt-5.6" assert model_info.get("mode") == "responses" @@ -1376,7 +1352,7 @@ def test_responses_api_bridge_check_lookalike_custom_api_base_with_unset_effort_ tools=[{"type": "function", "function": {"name": "get_capital"}}], reasoning_effort=None, api_base=api_base, - ) + ) assert model == "gpt-5.6" assert model_info.get("mode") != "responses" @@ -1396,7 +1372,7 @@ def test_responses_api_bridge_check_privatelink_api_base_via_env_with_unset_effo tools=[{"type": "function", "function": {"name": "get_capital"}}], reasoning_effort=None, api_base=None, - ) + ) assert model == "gpt-5.6" assert model_info.get("mode") == "responses" @@ -1894,9 +1870,7 @@ def test_responses_api_bridge_check_handles_exception(): with patch("litellm.main.get_model_info_helper") as mock_get_model_info: mock_get_model_info.side_effect = Exception("Model not found") - model_info, model = responses_api_bridge_check( - model="responses/custom-model", custom_llm_provider="custom" - ) + model_info, model = responses_api_bridge_check(model="responses/custom-model", custom_llm_provider="custom") assert model == "custom-model" assert model_info["mode"] == "responses" @@ -2739,9 +2713,7 @@ def test_image_edit_merges_headers_and_extra_headers(): mock_image_edit_config = MagicMock() mock_image_edit_config.get_supported_openai_params.return_value = set() - mock_image_edit_config.map_openai_params.side_effect = lambda **kwargs: dict( - kwargs["image_edit_optional_params"] - ) + mock_image_edit_config.map_openai_params.side_effect = lambda **kwargs: dict(kwargs["image_edit_optional_params"]) with ( patch( @@ -2857,7 +2829,7 @@ def test_mock_completion_infers_provider_when_called_directly_without_one(model: _ADMISSION_INPUT_TOKENS: Final = 51234 -def _admission_metadata(input_tokens: int) -> dict[str, object]: # mutable-ok: logging writes into metadata +def _admission_metadata(input_tokens: int) -> dict[str, object]: return {"user_api_key_budget_reservation": {"reserved_cost": 1.0, "input_tokens": input_tokens}} @@ -3152,10 +3124,7 @@ def test_mock_completion_stream_with_model_response(): # Verify the content is streamed correctly accumulated_content = "" for chunk in chunks: - if ( - hasattr(chunk.choices[0].delta, "content") - and chunk.choices[0].delta.content - ): + if hasattr(chunk.choices[0].delta, "content") and chunk.choices[0].delta.content: accumulated_content += chunk.choices[0].delta.content assert "This is a test response" in accumulated_content or len(chunks) > 0 @@ -3213,10 +3182,7 @@ async def test_async_mock_completion_stream_with_model_response(): # Verify the content is streamed correctly accumulated_content = "" for chunk in chunks: - if ( - hasattr(chunk.choices[0].delta, "content") - and chunk.choices[0].delta.content - ): + if hasattr(chunk.choices[0].delta, "content") and chunk.choices[0].delta.content: accumulated_content += chunk.choices[0].delta.content assert "This is an async test response" in accumulated_content or len(chunks) > 0 @@ -3283,9 +3249,7 @@ def test_stream_chunk_builder_text_completion_combines_text_and_usage(): ), ] - response = stream_chunk_builder_text_completion( - chunks=chunks, messages=[{"role": "user", "content": "say hello"}] - ) + response = stream_chunk_builder_text_completion(chunks=chunks, messages=[{"role": "user", "content": "say hello"}]) assert response.choices[0].text == "Hello world" assert response.choices[0].finish_reason == "stop" @@ -3733,10 +3697,7 @@ def _text_chunk(content, finish_reason=None, usage=None): def _priced_at(prompt_tokens, completion_tokens): prices = litellm.model_cost[STREAM_COST_MODEL] - return ( - prompt_tokens * prices["input_cost_per_token"] - + completion_tokens * prices["output_cost_per_token"] - ) + return prompt_tokens * prices["input_cost_per_token"] + completion_tokens * prices["output_cost_per_token"] @pytest.fixture @@ -3802,9 +3763,9 @@ def test_streaming_and_not_streaming_bill_the_same_usage_the_same(local_cost_map usage=STREAMED_USAGE, ) - assert litellm.completion_cost( - completion_response=rebuilt, model=STREAM_COST_MODEL - ) == pytest.approx(litellm.completion_cost(completion_response=whole, model=STREAM_COST_MODEL)) + assert litellm.completion_cost(completion_response=rebuilt, model=STREAM_COST_MODEL) == pytest.approx( + litellm.completion_cost(completion_response=whole, model=STREAM_COST_MODEL) + ) def test_a_stream_that_reported_no_usage_is_still_billed(local_cost_map): @@ -3823,9 +3784,7 @@ def test_a_stream_that_reported_no_usage_is_still_billed(local_cost_map): cost = litellm.completion_cost(completion_response=rebuilt, model=STREAM_COST_MODEL) assert cost > 0 - assert cost == pytest.approx( - _priced_at(rebuilt.usage.prompt_tokens, rebuilt.usage.completion_tokens) - ) + assert cost == pytest.approx(_priced_at(rebuilt.usage.prompt_tokens, rebuilt.usage.completion_tokens)) @pytest.mark.asyncio @@ -3953,7 +3912,9 @@ def _stream_builder_text_chunk(model: str, content: str, finish_reason: str | No created=1724900000, model=model, object="chat.completion.chunk", - choices=[StreamingChoices(finish_reason=finish_reason, index=0, delta=Delta(content=content, role="assistant"))], + choices=[ + StreamingChoices(finish_reason=finish_reason, index=0, delta=Delta(content=content, role="assistant")) + ], ) @@ -4239,9 +4200,7 @@ def test_groq_transcription_honors_base_url_alias(respx_mock: respx.MockRouter): assert response.text == "hello" -async def test_groq_atranscription_honors_base_url_alias( - respx_mock: respx.MockRouter, monkeypatch: pytest.MonkeyPatch -): +async def test_groq_atranscription_honors_base_url_alias(respx_mock: respx.MockRouter, monkeypatch: pytest.MonkeyPatch): monkeypatch.setattr(litellm, "disable_aiohttp_transport", True) route: Final = respx_mock.post(f"{GROQ_INTERNAL_BASE}/audio/transcriptions").mock( return_value=httpx.Response(200, json={"text": "hello"})