Merge pull request #33738 from BerriAI/litellm_lit_4116_drop_params_string_coerce

fix(utils): honor string drop_params values from config and DB deployments
This commit is contained in:
Mateo Wang 2026-09-08 15:17:54 -07:00 committed by GitHub
commit 13005cb831
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 511 additions and 67 deletions

View file

@ -47,6 +47,7 @@ from typing import (
)
from litellm.types.integrations.datadog import DatadogInitParams
from litellm.types.integrations.newrelic import NewRelicInitParams
from litellm.litellm_core_utils.core_helpers import drop_params_env_flag
from litellm._logging import (
set_verbose,
_turn_on_debug,
@ -238,7 +239,7 @@ token: Optional[str] = (
)
telemetry = True
max_tokens: int = DEFAULT_MAX_TOKENS # OpenAI Defaults
drop_params = bool(os.getenv("LITELLM_DROP_PARAMS", False))
drop_params = drop_params_env_flag(os.environ, verbose_logger)
modify_params = bool(os.getenv("LITELLM_MODIFY_PARAMS", False))
use_chat_completions_url_for_anthropic_messages: bool = bool(
os.getenv("LITELLM_USE_CHAT_COMPLETIONS_URL_FOR_ANTHROPIC_MESSAGES", False)

View file

@ -1,10 +1,12 @@
# What is this?
## Helper utilities
import copy
import logging
from collections.abc import Iterable, Mapping
from typing import TYPE_CHECKING, Any, Final, Literal
import httpx
from pydantic import TypeAdapter, ValidationError
from litellm._logging import verbose_logger
from litellm.types.llms.openai import AllMessageValues, OpenAIChatCompletionFinishReason
@ -37,6 +39,41 @@ def safe_divide_seconds(seconds: float, denominator: float, default: float | Non
return float(seconds / denominator)
_DROP_PARAMS_BOOL: Final = TypeAdapter(bool)
def normalize_drop_params(value: object) -> bool | None:
if value is None or isinstance(value, bool):
return value
try:
return _DROP_PARAMS_BOOL.validate_python(value.strip() if isinstance(value, str) else value)
except ValidationError:
return None
def drop_params_flag(value: object, source: str, logger: logging.Logger) -> bool:
normalized: Final = normalize_drop_params(value)
if normalized is None and value is not None:
logger.warning("%s=%r is not a flag value, treating it as off", source, value)
return bool(normalized)
DROP_PARAMS_ENV_VAR: Final = "LITELLM_DROP_PARAMS"
def drop_params_env_flag(environ: Mapping[str, str], logger: logging.Logger) -> bool:
configured: Final = environ.get(DROP_PARAMS_ENV_VAR, "").strip()
if configured == "":
return False
normalized: Final = normalize_drop_params(configured)
if normalized is None:
logger.warning(
"%s=%r is not a flag value, treating it as on. Set it to true or false", DROP_PARAMS_ENV_VAR, configured
)
return True
return normalized
def safe_divide(
numerator: float,
denominator: float,

View file

@ -2,6 +2,7 @@ from collections.abc import Mapping, MutableMapping
from types import MappingProxyType
from typing import Final
from litellm.litellm_core_utils.core_helpers import normalize_drop_params
from litellm.llms.openai.data_residency import infer_openai_data_residency
AWS_CREDENTIAL_KWARGS_KEYS: Final = frozenset(
@ -113,7 +114,7 @@ def get_litellm_params(
custom_prompt_dict: dict | None = None,
litellm_metadata: dict | None = None,
disable_add_transform_inline_image_block: bool | None = None,
drop_params: bool | None = None,
drop_params: bool | str | None = None,
prompt_id: str | None = None,
prompt_variables: dict | None = None,
async_call: bool | None = None,
@ -175,7 +176,7 @@ def get_litellm_params(
"custom_prompt_dict": custom_prompt_dict,
"litellm_metadata": litellm_metadata,
"disable_add_transform_inline_image_block": disable_add_transform_inline_image_block,
"drop_params": drop_params,
"drop_params": normalize_drop_params(drop_params),
"prompt_id": prompt_id,
"prompt_variables": prompt_variables,
"async_call": async_call,

View file

@ -278,6 +278,7 @@ from litellm.litellm_core_utils.asyncify import asyncify
from litellm.litellm_core_utils.audio_utils.utils import resolve_speech_media_type
from litellm.litellm_core_utils.core_helpers import (
_get_parent_otel_span_from_kwargs,
drop_params_flag,
get_litellm_metadata_from_kwargs,
)
from litellm.litellm_core_utils.credential_accessor import CredentialAccessor
@ -5519,6 +5520,8 @@ class ProxyConfig:
parse_budget_reset_time(value)
setattr(litellm, key, value)
elif key == "drop_params":
litellm.drop_params = drop_params_flag(value, "litellm_settings.drop_params", verbose_proxy_logger)
else:
verbose_proxy_logger.debug(
"%s setting litellm.%s=%s%s",

View file

@ -17,6 +17,7 @@ from litellm.completion_extras.litellm_responses_transformation.transformation i
from litellm.constants import request_timeout
from litellm.integrations.anthropic_cache_control_hook import CARRY_UNMATCHED_MESSAGE_POINTS
from litellm.litellm_core_utils.asyncify import run_async_function
from litellm.litellm_core_utils.core_helpers import normalize_drop_params
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
from litellm.litellm_core_utils.prompt_templates.common_utils import (
update_responses_input_with_model_file_ids,
@ -1257,7 +1258,7 @@ def responses(
responses_api_provider_config=responses_api_provider_config,
response_api_optional_params=response_api_optional_params,
allowed_openai_params=allowed_openai_params,
drop_params=request_drop_params if isinstance(request_drop_params, bool) else None,
drop_params=normalize_drop_params(request_drop_params),
)
litellm_logging_obj.update_from_kwargs(
@ -2085,7 +2086,7 @@ def compact_responses(
responses_api_provider_config=responses_api_provider_config,
response_api_optional_params=response_api_optional_params,
allowed_openai_params=None,
drop_params=request_drop_params if isinstance(request_drop_params, bool) else None,
drop_params=normalize_drop_params(request_drop_params),
)
# Pre Call logging

View file

@ -9420,6 +9420,12 @@ class Router:
#### VALIDATE MODEL ########
# Check if this is a prompt management model before validating as LLM provider
litellm_model: Final = deployment.litellm_params.model
if isinstance(deployment.litellm_params.drop_params, str):
verbose_router_logger.warning(
"model=%s drop_params=%r is not a flag value, treating it as unset",
deployment.model_name,
deployment.litellm_params.drop_params,
)
is_prompt_management_model = False
if "/" in litellm_model:

View file

@ -12,7 +12,9 @@ import httpx
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from typing_extensions import Protocol, ReadOnly, Required, TypedDict, runtime_checkable
from litellm._logging import verbose_logger
from litellm._uuid import uuid
from litellm.litellm_core_utils.core_helpers import normalize_drop_params
if TYPE_CHECKING:
from litellm.router import Router
@ -314,6 +316,7 @@ class GenericLiteLLMParams(CredentialLiteLLMParams, CustomPricingLiteLLMParams):
timeout: float | str | httpx.Timeout | None = None # if str, pass in as os.environ/
stream_timeout: float | str | None = None # timeout when making stream=True calls, if str, pass in as os.environ/
max_retries: int | None = None
drop_params: bool | str | None = None
organization: str | None = None # for openai orgs
configurable_clientside_auth_params: CONFIGURABLE_CLIENTSIDE_AUTH_PARAMS = None
litellm_credential_name: str | None = None
@ -404,6 +407,18 @@ class GenericLiteLLMParams(CredentialLiteLLMParams, CustomPricingLiteLLMParams):
return filtered
return data
@field_validator("drop_params", mode="before")
@classmethod
def coerce_drop_params(cls, value: object) -> bool | str | None:
normalized: Final = normalize_drop_params(value)
if normalized is not None:
return normalized
if isinstance(value, str):
return value
if value is not None:
verbose_logger.warning("drop_params=%r is not a flag value, treating it as unset", value)
return None
def __contains__(self, key) -> bool:
# Define custom behavior for the 'in' operator
return hasattr(self, key)

View file

@ -80,6 +80,7 @@ from litellm.constants import (
PROVIDERS_THAT_AUTHENTICATE_ON_PROVIDER_INFO,
TOOL_CHOICE_OBJECT_TOKEN_COUNT,
)
from litellm.litellm_core_utils.core_helpers import normalize_drop_params
from litellm.litellm_core_utils.fallback_generalizations import (
match_capability_generalizations,
)
@ -3239,7 +3240,7 @@ def get_optional_params_transcription(
passed_params.pop("OPENAI_TRANSCRIPTION_PARAMS")
custom_llm_provider = passed_params.pop("custom_llm_provider")
drop_params = passed_params.pop("drop_params")
drop_params = normalize_drop_params(passed_params.pop("drop_params"))
special_params: Final[Mapping[str, object]] = passed_params.pop("kwargs")
for k, v in special_params.items():
passed_params[k] = v
@ -3347,7 +3348,7 @@ def get_optional_params_image_gen(
model = passed_params.pop("model", None)
custom_llm_provider = passed_params.pop("custom_llm_provider")
provider_config = passed_params.pop("provider_config", None)
drop_params = passed_params.pop("drop_params", None)
drop_params = normalize_drop_params(passed_params.pop("drop_params", None))
additional_drop_params = passed_params.pop("additional_drop_params", None)
special_params: Final[Mapping[str, object]] = passed_params.pop("kwargs")
for k, v in special_params.items():
@ -3475,7 +3476,7 @@ def get_optional_params_embeddings(
custom_llm_provider = passed_params.pop("custom_llm_provider", None)
special_params: Final = passed_params.pop("kwargs")
drop_params = passed_params.pop("drop_params", None)
drop_params = normalize_drop_params(passed_params.pop("drop_params", None))
additional_drop_params = passed_params.pop("additional_drop_params", None)
allowed_openai_params = passed_params.pop("allowed_openai_params", None) or []
# Remove function objects from passed_params to avoid JSON serialization errors
@ -4202,6 +4203,7 @@ def get_optional_params(
base_model: str | None = None,
**kwargs,
):
drop_params = normalize_drop_params(drop_params) # rebind-ok: config and DB deployments pass "true" as a string
passed_params: Final = locals().copy()
special_params: Final = passed_params.pop("kwargs")
# Remove base_model from passed_params so it doesn't interfere with
@ -4279,20 +4281,20 @@ def get_optional_params(
model=model,
non_default_params=non_default_params,
optional_params=optional_params,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "anthropic_text":
optional_params = litellm.AnthropicTextConfig().map_openai_params(
model=model,
non_default_params=non_default_params,
optional_params=optional_params,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
optional_params = litellm.AnthropicTextConfig().map_openai_params(
model=model,
non_default_params=non_default_params,
optional_params=optional_params,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "cohere_chat" or custom_llm_provider == "cohere":
@ -4301,14 +4303,14 @@ def get_optional_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "triton":
optional_params = litellm.TritonConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=drop_params if drop_params is not None else False,
drop_params=bool(drop_params),
)
elif custom_llm_provider == "maritalk":
@ -4316,35 +4318,35 @@ def get_optional_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "replicate":
optional_params = litellm.ReplicateConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "predibase":
optional_params = litellm.PredibaseConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "huggingface":
optional_params = litellm.HuggingFaceChatConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "together_ai":
optional_params = litellm.TogetherAIChatConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "vertex_ai" and (
model in litellm.vertex_chat_models
@ -4358,7 +4360,7 @@ def get_optional_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "gemini":
@ -4366,21 +4368,21 @@ def get_optional_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "vertex_ai_beta" or (custom_llm_provider == "vertex_ai" and "gemini" in model):
optional_params = litellm.VertexGeminiConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif litellm.VertexAIAnthropicConfig.is_supported_model(model=model, custom_llm_provider=custom_llm_provider):
optional_params = litellm.VertexAIAnthropicConfig().map_openai_params(
model=model,
non_default_params=non_default_params,
optional_params=optional_params,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "vertex_ai":
if model in litellm.vertex_mistral_models:
@ -4389,35 +4391,35 @@ def get_optional_params(
model=model,
non_default_params=non_default_params,
optional_params=optional_params,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
else:
optional_params = litellm.MistralConfig().map_openai_params(
model=model,
non_default_params=non_default_params,
optional_params=optional_params,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif model in litellm.vertex_ai_ai21_models:
optional_params = litellm.VertexAIAi21Config().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif provider_config is not None:
optional_params = provider_config.map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
else: # use generic openai-like param mapping
optional_params = litellm.VertexAILlama3Config().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "sagemaker":
@ -4426,7 +4428,7 @@ def get_optional_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "bedrock":
BedrockModelInfo: Final = getattr(sys.modules[__name__], "BedrockModelInfo")
@ -4437,14 +4439,14 @@ def get_optional_params(
model=model,
non_default_params=non_default_params,
optional_params=optional_params,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif bedrock_route == "openai":
optional_params = litellm.AmazonBedrockOpenAIConfig().map_openai_params(
model=model,
non_default_params=non_default_params,
optional_params=optional_params,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif "anthropic" in bedrock_base_model and bedrock_route == "invoke":
if bedrock_base_model in litellm.AmazonAnthropicConfig.get_legacy_anthropic_model_names():
@ -4452,21 +4454,21 @@ def get_optional_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
else:
optional_params = litellm.AmazonAnthropicClaudeConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif provider_config is not None:
optional_params = provider_config.map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
if bedrock_route == "claude_platform":
optional_params = BedrockModelInfo.map_claude_platform_auth_params(
@ -4477,28 +4479,28 @@ def get_optional_params(
model=model,
non_default_params=non_default_params,
optional_params=optional_params,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "ollama":
optional_params = litellm.OllamaConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "ollama_chat":
optional_params = litellm.OllamaChatConfig().map_openai_params(
model=model,
non_default_params=non_default_params,
optional_params=optional_params,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "nlp_cloud":
optional_params = litellm.NLPCloudConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "petals":
@ -4506,35 +4508,35 @@ def get_optional_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "deepinfra":
optional_params = litellm.DeepInfraConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "perplexity" and provider_config is not None:
optional_params = provider_config.map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "mistral" or custom_llm_provider == "codestral":
optional_params = litellm.MistralConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "text-completion-codestral":
optional_params = litellm.CodestralTextCompletionConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "text-completion-inception":
@ -4542,7 +4544,7 @@ def get_optional_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "databricks":
@ -4550,21 +4552,21 @@ def get_optional_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "nvidia_nim":
optional_params = litellm.NvidiaNimConfig().map_openai_params(
model=model,
non_default_params=non_default_params,
optional_params=optional_params,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "cerebras":
optional_params = litellm.CerebrasConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "xai":
optional_params = litellm.XAIChatConfig().map_openai_params(
@ -4577,77 +4579,77 @@ def get_optional_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "fireworks_ai":
optional_params = litellm.FireworksAIConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "volcengine":
optional_params = litellm.VolcEngineConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "hosted_vllm":
optional_params = litellm.HostedVLLMChatConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "vllm":
optional_params = litellm.VLLMConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "groq":
optional_params = litellm.GroqChatConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "bedrock_mantle":
optional_params = litellm.BedrockMantleChatConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "deepseek":
optional_params = litellm.DeepSeekChatConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "tencent":
optional_params = litellm.TencentChatConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "openrouter":
optional_params = litellm.OpenrouterConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "watsonx":
optional_params = litellm.IBMWatsonXChatConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
# WatsonX-text param check
for param in passed_params:
@ -4660,21 +4662,21 @@ def get_optional_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "openai":
optional_params = litellm.OpenAIConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "nebius":
optional_params = litellm.NebiusConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif custom_llm_provider == "azure":
_azure_detection_model: Final = base_model or model
@ -4683,14 +4685,14 @@ def get_optional_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=_azure_detection_model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif litellm.AzureOpenAIGPT5Config.is_model_gpt_5_model(model=_azure_detection_model):
optional_params = litellm.AzureOpenAIGPT5Config().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=_azure_detection_model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
else:
verbose_logger.debug(
@ -4709,21 +4711,21 @@ def get_optional_params(
optional_params=optional_params,
model=_azure_detection_model,
api_version=api_version,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
elif provider_config is not None:
optional_params = provider_config.map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
else: # assume passing in params for openai-like api
optional_params = litellm.OpenAILikeChatConfig().map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
drop_params=bool(drop_params),
)
# if user passed in non-default kwargs for specific providers/models, pass them along
optional_params = add_provider_specific_params_to_optional_params(

View file

@ -1,11 +1,16 @@
"""Tests for litellm_core_utils.core_helpers module."""
import logging
import pytest
from litellm.litellm_core_utils.core_helpers import (
_FINISH_REASON_MAP,
drop_params_env_flag,
drop_params_flag,
get_or_create_metadata_bucket,
map_finish_reason,
normalize_drop_params,
reconstruct_model_name,
redact_nested_match_and_regex_keys,
)
@ -257,6 +262,73 @@ class TestRedactNestedMatchAndRegexKeys:
assert redact_nested_match_and_regex_keys("plain") == "plain"
@pytest.mark.parametrize(
"value, expected",
[
(True, True),
(False, False),
("true", True),
("True", True),
(" TRUE ", True),
("false", False),
("False", False),
("yes", True),
("off", False),
("1", True),
(1, True),
(0, False),
(None, None),
("", None),
("os.environ/DROP_PARAMS", None),
("v2:gcm:not-a-flag", None),
(2, None),
],
)
def test_normalize_drop_params(value, expected):
assert normalize_drop_params(value) is expected
@pytest.mark.parametrize("value, expected", [("true", True), ("off", False), (None, False)])
def test_drop_params_flag_returns_a_bool_without_a_warning(value, expected, caplog):
with caplog.at_level(logging.WARNING, logger="drop-params-test"):
assert drop_params_flag(value, "LITELLM_DROP_PARAMS", logging.getLogger("drop-params-test")) is expected
assert caplog.text == ""
@pytest.mark.parametrize("value", ["temperature", "ture", 2])
def test_drop_params_flag_treats_non_flag_values_as_off_with_a_warning(value, caplog):
with caplog.at_level(logging.WARNING, logger="drop-params-test"):
assert drop_params_flag(value, "LITELLM_DROP_PARAMS", logging.getLogger("drop-params-test")) is False
assert f"LITELLM_DROP_PARAMS={value!r} is not a flag value, treating it as off" in caplog.text
@pytest.mark.parametrize(
"environ, expected",
[
({}, False),
({"LITELLM_DROP_PARAMS": ""}, False),
({"LITELLM_DROP_PARAMS": " "}, False),
({"LITELLM_DROP_PARAMS": "true"}, True),
({"LITELLM_DROP_PARAMS": " False "}, False),
({"LITELLM_DROP_PARAMS": "0"}, False),
],
)
def test_drop_params_env_flag_reads_a_flag_without_a_warning(environ, expected, caplog):
with caplog.at_level(logging.WARNING, logger="drop-params-test"):
assert drop_params_env_flag(environ, logging.getLogger("drop-params-test")) is expected
assert caplog.text == ""
@pytest.mark.parametrize("configured", ["temperature", "temperature,top_p", "enabled"])
def test_drop_params_env_flag_keeps_a_non_flag_value_on_with_a_warning(configured, caplog):
with caplog.at_level(logging.WARNING, logger="drop-params-test"):
assert drop_params_env_flag({"LITELLM_DROP_PARAMS": configured}, logging.getLogger("drop-params-test")) is True
assert (
f"LITELLM_DROP_PARAMS={configured!r} is not a flag value, treating it as on. Set it to true or false"
in caplog.text
)
class TestIsExpectedClientError:
def test_status_ranges(self):
from litellm.litellm_core_utils.core_helpers import is_expected_client_error

View file

@ -215,3 +215,11 @@ class TestMetadataFallsBackToLitellmMetadata:
assert result["metadata"] is not litellm_metadata
result["metadata"].pop("trace_id")
assert litellm_metadata == {"trace_id": "trace-1"}
@pytest.mark.parametrize(
"value, expected",
[("true", True), ("false", False), (" TRUE ", True), (True, True), (None, None), ("os.environ/DROP_PARAMS", None)],
)
def test_drop_params_strings_reach_litellm_params_as_flags(value, expected):
assert get_litellm_params(drop_params=value)["drop_params"] is expected

View file

@ -30,7 +30,7 @@ from litellm.proxy.management_endpoints.model_management_endpoints import (
)
from litellm.proxy.utils import PrismaClient
from litellm.router import Router
from litellm.types.router import Deployment, LiteLLM_Params, updateDeployment
from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo, updateDeployment, updateLiteLLMParams
async def _passthrough_row(update_data):
@ -3070,6 +3070,31 @@ class TestUpdateDBModelBlocked:
assert "blocked" not in result
class TestUpdateDBModelKeepsLegacyDropParams:
def test_partial_patch_keeps_encrypted_string_drop_params(self, monkeypatch):
from litellm.proxy.common_utils.encrypt_decrypt_utils import decrypt_value_helper
from litellm.proxy.management_endpoints.model_management_endpoints import update_db_model
monkeypatch.setenv("LITELLM_SALT_KEY", "sk-1234")
legacy_row = Deployment(
model_name="gpt-5-nano",
litellm_params=LiteLLM_Params(
model="openai/gpt-5-nano",
api_key=encrypt_value_helper(value="sk-old"),
drop_params=encrypt_value_helper(value="true"),
),
model_info=ModelInfo(id="legacy-row"),
)
result = update_db_model(
db_model=legacy_row,
updated_patch=updateDeployment(litellm_params=updateLiteLLMParams(api_key="sk-new")),
)
stored = json.loads(result["litellm_params"])
assert decrypt_value_helper(value=stored["drop_params"], key="drop_params") == "true"
def _build_db_model_with_pricing():
"""Wildcard deployment with custom pricing in litellm_params; Deployment.__init__
mirrors SPECIAL_MODEL_INFO_PARAMS into model_info, so both blobs hold the rate."""

View file

@ -20,6 +20,7 @@ import pytest
import litellm
from litellm.proxy._types import CommonProxyErrors
from litellm.proxy.common_utils.encrypt_decrypt_utils import encrypt_value_helper
from litellm.proxy.proxy_server import (
ProxyConfig,
_is_remote_module_url,
@ -2428,6 +2429,111 @@ def test_ProxyConfig__add_deployment_resolves_env_refs_on_arbitrary_field(monkey
assert deployment.litellm_params.some_future_field == "resolved-custom-value"
@pytest.mark.parametrize(
"stored_drop_params",
["true", "os.environ/DROP_PARAMS_FLAG"],
)
def test_ProxyConfig__add_deployment_turns_stored_drop_params_string_into_bool(monkeypatch, stored_drop_params):
monkeypatch.setenv("LITELLM_SALT_KEY", "sk-1234")
monkeypatch.setenv("DROP_PARAMS_FLAG", "true")
fake_router = MagicMock()
fake_router.upsert_deployment = MagicMock(return_value=True)
monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", fake_router)
pc = ProxyConfig()
db_model = SimpleNamespace(
model_id="model-1",
model_name="gpt-5-nano",
model_info={"id": "model-1"},
litellm_params={
"model": encrypt_value_helper(value="openai/gpt-5-nano"),
"drop_params": encrypt_value_helper(value=stored_drop_params),
},
blocked=False,
)
added = pc._add_deployment(db_models=[db_model])
deployment = fake_router.upsert_deployment.call_args.kwargs["deployment"]
assert added == 1
assert deployment.litellm_params.drop_params is True
def test_ProxyConfig__add_deployment_keeps_loading_rows_after_a_non_flag_drop_params(monkeypatch):
monkeypatch.setenv("LITELLM_SALT_KEY", "sk-1234")
fake_router = MagicMock()
fake_router.upsert_deployment = MagicMock(return_value=True)
monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", fake_router)
pc = ProxyConfig()
def db_model(model_id, drop_params):
return SimpleNamespace(
model_id=model_id,
model_name="gpt-5-nano",
model_info={"id": model_id},
litellm_params={
"model": encrypt_value_helper(value="openai/gpt-5-nano"),
"drop_params": encrypt_value_helper(value=drop_params),
},
blocked=False,
)
added = pc._add_deployment(db_models=[db_model("bad-row", 2), db_model("good-after", "true")])
deployments = [call.kwargs["deployment"] for call in fake_router.upsert_deployment.call_args_list]
assert added == 2
assert [d.litellm_params.drop_params for d in deployments] == [None, True]
@pytest.mark.asyncio
@pytest.mark.parametrize("configured, expected", [("true", True), ("false", False)])
async def test_ProxyConfig_load_config_turns_litellm_settings_drop_params_string_into_bool(
tmp_path, monkeypatch, configured, expected
):
f = tmp_path / "c.yaml"
f.write_text(f'model_list: []\nlitellm_settings:\n drop_params: "{configured}"\n')
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None)
monkeypatch.setattr("litellm.proxy.proxy_server.store_model_in_db", False)
monkeypatch.delenv("LITELLM_CONFIG_BUCKET_NAME", raising=False)
monkeypatch.setattr(litellm, "drop_params", not expected)
await ProxyConfig().load_config(router=None, config_file_path=str(f))
assert litellm.drop_params is expected
@pytest.mark.asyncio
async def test_ProxyConfig_load_config_resolves_a_litellm_settings_drop_params_env_ref(tmp_path, monkeypatch):
f = tmp_path / "c.yaml"
f.write_text("model_list: []\nlitellm_settings:\n drop_params: os.environ/DROP_PARAMS_FROM_ENV\n")
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None)
monkeypatch.setattr("litellm.proxy.proxy_server.store_model_in_db", False)
monkeypatch.delenv("LITELLM_CONFIG_BUCKET_NAME", raising=False)
monkeypatch.setenv("DROP_PARAMS_FROM_ENV", "true")
monkeypatch.setattr(litellm, "drop_params", False)
await ProxyConfig().load_config(router=None, config_file_path=str(f))
assert litellm.drop_params is True
@pytest.mark.asyncio
async def test_ProxyConfig_load_config_warns_and_turns_off_a_non_flag_litellm_settings_drop_params(
tmp_path, monkeypatch, caplog
):
f = tmp_path / "c.yaml"
f.write_text("model_list: []\nlitellm_settings:\n drop_params: ture\n")
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None)
monkeypatch.setattr("litellm.proxy.proxy_server.store_model_in_db", False)
monkeypatch.delenv("LITELLM_CONFIG_BUCKET_NAME", raising=False)
monkeypatch.setattr(litellm, "drop_params", True)
with caplog.at_level(logging.WARNING, logger="LiteLLM Proxy"):
await ProxyConfig().load_config(router=None, config_file_path=str(f))
assert litellm.drop_params is False
assert "litellm_settings.drop_params='ture' is not a flag value, treating it as off" in caplog.text
# ---------------------------------------------------------------------------
# ProxyConfig.decrypt_model_list_from_db
# ---------------------------------------------------------------------------

View file

@ -246,8 +246,10 @@ async def test_aresponses_keeps_include_obfuscation_in_stream_options():
@pytest.mark.asyncio
@pytest.mark.parametrize("drop_params", [True, "true"])
async def test_aresponses_request_level_drop_params_drops_bedrock_mantle_service_tier(
monkeypatch,
drop_params,
):
"""
Request-level drop_params=True (as the proxy injects for agentic CLIs) must
@ -271,7 +273,7 @@ async def test_aresponses_request_level_drop_params_drops_bedrock_mantle_service
aws_region_name="us-east-1",
input="hi",
service_tier="priority",
drop_params=True,
drop_params=drop_params,
)
mock_post.assert_called_once()

View file

@ -0,0 +1,33 @@
import os
import subprocess
import sys
import pytest
def _import_litellm_with(configured: str) -> subprocess.CompletedProcess[str]:
return subprocess.run(
[sys.executable, "-c", "import litellm; print(litellm.drop_params)"],
env={**os.environ, "LITELLM_DROP_PARAMS": configured},
capture_output=True,
text=True,
check=True,
)
@pytest.mark.parametrize("configured, expected", [("false", "False"), ("true", "True"), ("", "False")])
def test_litellm_drop_params_env_var_is_parsed_as_a_flag(configured, expected):
result = _import_litellm_with(configured)
assert result.stdout.strip() == expected
assert "is not a flag value" not in result.stderr
def test_litellm_drop_params_env_var_non_flag_value_stays_on_with_a_warning():
result = _import_litellm_with("temperature")
assert result.stdout.strip() == "True"
assert (
"LITELLM_DROP_PARAMS='temperature' is not a flag value, treating it as on. Set it to true or false"
in result.stderr
)

View file

@ -14434,3 +14434,70 @@ async def test_router_max_parallel_requests_slot_released_when_stream_closed_ear
assert tracker.peak == 1
assert tracker.current == 0
@pytest.mark.asyncio
async def test_router_deployment_drop_params_string_true_is_honored(monkeypatch):
from litellm import Router
monkeypatch.setattr(litellm, "drop_params", False)
router = Router(
model_list=[
{
"model_name": "gpt-5-nano",
"litellm_params": {
"model": "openai/gpt-5-nano",
"api_key": "sk-fake",
"temperature": 1,
"reasoning_effort": "minimal",
"drop_params": "true",
"mock_response": "Hello, world!",
},
}
],
num_retries=0,
)
deployment = router.get_deployment_by_model_group_name(model_group_name="gpt-5-nano")
assert deployment is not None
assert deployment.litellm_params.drop_params is True
response = await router.acompletion(
model="gpt-5-nano",
messages=[{"role": "user", "content": "hi"}],
temperature=0.1,
)
assert response.choices[0].message.content == "Hello, world!"
@pytest.mark.parametrize("value", ["ture", "enabled"])
def test_router_warns_when_a_deployment_drop_params_string_is_not_a_flag(value, caplog):
with caplog.at_level(logging.WARNING, logger="LiteLLM Router"):
router = Router(
model_list=[
{
"model_name": "gpt-5-nano",
"litellm_params": {"model": "openai/gpt-5-nano", "api_key": "sk-fake", "drop_params": value},
}
]
)
deployment = router.get_deployment_by_model_group_name(model_group_name="gpt-5-nano")
assert deployment is not None
assert deployment.litellm_params.drop_params == value
assert f"model=gpt-5-nano drop_params={value!r} is not a flag value, treating it as unset" in caplog.text
@pytest.mark.parametrize("value", [True, "true", "off", None])
def test_router_stays_quiet_when_a_deployment_drop_params_is_a_flag(value, caplog):
with caplog.at_level(logging.WARNING, logger="LiteLLM Router"):
Router(
model_list=[
{
"model_name": "gpt-5-nano",
"litellm_params": {"model": "openai/gpt-5-nano", "api_key": "sk-fake", "drop_params": value},
}
]
)
assert "is not a flag value" not in caplog.text

View file

@ -6094,6 +6094,34 @@ class TestFinalOptionalParamsLineRedaction:
assert "'temperature': 0.25" in printed
class TestDropParamsStringCoercion:
@pytest.mark.parametrize("drop_params", ["true", "True", True])
def test_truthy_drop_params_drops_unsupported_temperature(self, drop_params, monkeypatch):
from litellm.utils import get_optional_params
monkeypatch.setattr(litellm, "drop_params", False)
result = get_optional_params(
model="gpt-5-nano",
custom_llm_provider="openai",
temperature=0.1,
drop_params=drop_params,
)
assert "temperature" not in result
@pytest.mark.parametrize("drop_params", ["false", False, None])
def test_falsy_drop_params_still_raises(self, drop_params, monkeypatch):
from litellm.utils import get_optional_params
monkeypatch.setattr(litellm, "drop_params", False)
with pytest.raises(litellm.UnsupportedParamsError):
get_optional_params(
model="gpt-5-nano",
custom_llm_provider="openai",
temperature=0.1,
drop_params=drop_params,
)
def _credential_warnings(caplog: pytest.LogCaptureFixture) -> list[str]:
return [record.getMessage() for record in caplog.records if "litellm_credential_name=" in record.getMessage()]

View file

@ -1,8 +1,11 @@
import logging
import pytest
from litellm.types.router import (
SPECIAL_MODEL_INFO_PARAMS,
Deployment,
GenericLiteLLMParams,
LiteLLM_Params,
ModelInfo,
)
@ -89,3 +92,33 @@ def test_pricing_strings_are_coerced_to_float():
def test_invalid_pricing_is_rejected():
with pytest.raises(ValueError, match='validation error for ModelInfo'):
ModelInfo(id="x", input_cost_per_token="free")
@pytest.mark.parametrize(
"value, expected",
[
(True, True),
("true", True),
(" False ", False),
("yes", True),
(None, None),
("os.environ/DROP_PARAMS", "os.environ/DROP_PARAMS"),
("v2:gcm:ciphertext-from-a-pre-fix-row", "v2:gcm:ciphertext-from-a-pre-fix-row"),
],
)
def test_drop_params_coerces_flags_and_keeps_unresolved_strings(value, expected):
assert GenericLiteLLMParams(drop_params=value).drop_params == expected
@pytest.mark.parametrize("value", [2, 2.5, [], {}])
def test_drop_params_ignores_non_flag_non_string_values_with_a_warning(value, caplog):
with caplog.at_level(logging.WARNING, logger="LiteLLM"):
assert GenericLiteLLMParams(drop_params=value).drop_params is None
assert f"drop_params={value!r} is not a flag value" in caplog.text
@pytest.mark.parametrize("value", [True, "true", None, "os.environ/DROP_PARAMS", "v2:gcm:ciphertext-from-a-pre-fix-row"])
def test_drop_params_flags_and_strings_log_nothing(value, caplog):
with caplog.at_level(logging.WARNING, logger="LiteLLM"):
GenericLiteLLMParams(drop_params=value)
assert caplog.text == ""

View file

@ -29449,6 +29449,8 @@ export interface components {
default_api_key_rpm_limit?: number | null;
/** Default Api Key Tpm Limit */
default_api_key_tpm_limit?: number | null;
/** Drop Params */
drop_params?: boolean | string | null;
/** Gcs Bucket Name */
gcs_bucket_name?: string | null;
/** Google Maps Grounding Cost Per Query */
@ -39632,6 +39634,8 @@ export interface components {
default_api_key_rpm_limit?: number | null;
/** Default Api Key Tpm Limit */
default_api_key_tpm_limit?: number | null;
/** Drop Params */
drop_params?: boolean | string | null;
/** Gcs Bucket Name */
gcs_bucket_name?: string | null;
/** Google Maps Grounding Cost Per Query */