mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-13 08:52:02 +00:00
fix(proxy)!: honor litellm_settings.drop_params for Claude Code and Codex requests (#49058)
* fix(proxy): honor litellm_settings.drop_params for Claude Code and Codex requests The agentic CLI drop_params default checked the operator's config with isinstance(config, dict), but ProxyConfig.config is a MappingProxyType, so the check never matched and litellm_settings.drop_params could not turn the default off. Read it as a Mapping so an operator who sets drop_params, true or false, keeps the decision on /v1/messages, chat, and count_tokens * fix(proxy): keep the Claude Code and Codex drop_params default unless the admin's value reads as false A drop_params: true saved through /config/update never reaches the global setting, so treating the key's presence as an opt-out made Claude Code and Codex requests answer 400 there. Only a value that reads as false now turns the default off; true, empty, or missing keeps it. * test(proxy): cover string drop_params values in the Claude Code and Codex default rows Adds rows for "false", " False ", "true" and an unreadable "maybe" under litellm_settings, so swapping the value check for a plain is-not-False comparison fails the suite * docs(proxy): say only a false operator drop_params turns off the agentic CLI default --------- Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
This commit is contained in:
parent
4a4c9cf7e3
commit
2e5cc9e08f
3 changed files with 56 additions and 27 deletions
|
|
@ -49,7 +49,7 @@ from litellm.constants import (
|
|||
X_LITELLM_DISABLE_CALLBACKS,
|
||||
)
|
||||
from litellm.integrations.custom_guardrail import without_server_streaming_classification
|
||||
from litellm.litellm_core_utils.core_helpers import is_codex_user_agent
|
||||
from litellm.litellm_core_utils.core_helpers import is_codex_user_agent, normalize_drop_params
|
||||
from litellm.litellm_core_utils.credential_accessor import CredentialAccessor
|
||||
from litellm.litellm_core_utils.initialize_dynamic_callback_params import (
|
||||
TRUSTED_CALLBACK_VARS_FIELD,
|
||||
|
|
@ -1102,18 +1102,17 @@ def should_auto_drop_params_for_agentic_cli(
|
|||
) -> bool:
|
||||
"""drop_params defaults to on for agentic CLIs so their client-specific
|
||||
params (e.g. Claude Code's thinking, Codex's service_tier) don't fail
|
||||
requests routed to providers that reject them. An explicit drop_params
|
||||
from the caller or in the operator's ``litellm_settings`` always wins
|
||||
over this default."""
|
||||
requests routed to providers that reject them. A drop_params from the
|
||||
caller wins over this default, and the operator's ``litellm_settings``
|
||||
turns it off only with a value that reads as false."""
|
||||
from litellm.llms.anthropic.common_utils import is_claude_code_user_agent
|
||||
|
||||
if not (is_claude_code_user_agent(user_agent) or is_codex_user_agent(user_agent)):
|
||||
return False
|
||||
if caller_set_drop_params:
|
||||
return False
|
||||
config: Final = getattr(proxy_config, "config", None)
|
||||
litellm_settings: Final = config.get("litellm_settings") if isinstance(config, dict) else None
|
||||
return not (isinstance(litellm_settings, dict) and "drop_params" in litellm_settings)
|
||||
litellm_settings: Final = _request_fields(proxy_config.config.get("litellm_settings"))
|
||||
return normalize_drop_params(litellm_settings.get("drop_params")) is not False
|
||||
|
||||
|
||||
def safe_add_api_version_from_query_params(data: dict, request: Request):
|
||||
|
|
|
|||
|
|
@ -695,6 +695,32 @@ async def test_count_tokens_drops_malformed_tools_when_drop_params_is_set(
|
|||
assert counted.messages == messages
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_count_tokens_keeps_claude_code_malformed_tools_when_operator_sets_drop_params_false() -> None:
|
||||
import litellm.proxy.anthropic_endpoints.endpoints as ep
|
||||
import litellm.proxy.proxy_server as proxy_server
|
||||
from litellm.proxy._types import UserAPIKeyAuth
|
||||
|
||||
operator_config: Final = proxy_server.ProxyConfig()
|
||||
operator_config.update_config_state(config={"litellm_settings": {"drop_params": False}})
|
||||
request: Final = MagicMock()
|
||||
request.headers = {"user-agent": "claude-cli/2.1.0 (external, cli)"}
|
||||
counter: Final = AsyncMock(return_value=_SEVEN_TOKENS)
|
||||
body: Final = {"model": "claude-sonnet", "messages": [{"role": "user", "content": "hi"}], "tools": 5}
|
||||
|
||||
with (
|
||||
patch.object(ep, "read_request_body", new=AsyncMock(return_value=body)), # test-quality-ok: endpoint reads the body via a module function; no injection seam
|
||||
patch.object(proxy_server, "count_request_tokens", new=counter), # test-quality-ok: module global imported at call time; the test asserts it is never reached
|
||||
patch.object(proxy_server, "llm_router", new=_claude_router()), # test-quality-ok: module global imported at call time; no injection seam on this route
|
||||
patch.object(proxy_server, "proxy_config", new=operator_config), # test-quality-ok: module global imported at call time; no injection seam on this route
|
||||
):
|
||||
response: Final = await ep.count_tokens(request=request, user_api_key_dict=UserAPIKeyAuth())
|
||||
|
||||
assert response.status_code == 400
|
||||
assert json.loads(response.body)["error"]["type"] == "invalid_request_error"
|
||||
counter.assert_not_awaited()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_count_tokens_drops_malformed_tools_the_counted_deployment_drops_even_with_fallbacks() -> None:
|
||||
import litellm.proxy.anthropic_endpoints.endpoints as ep
|
||||
|
|
|
|||
|
|
@ -7024,24 +7024,30 @@ def _make_request_mock(path: str, headers: dict) -> MagicMock:
|
|||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"user_agent, request_drop_params, operator_drop_params, expected_drop_params",
|
||||
"user_agent, request_drop_params, operator_settings, expected_drop_params",
|
||||
[
|
||||
("claude-cli/2.0.69 (external, cli)", None, None, True),
|
||||
("claude-cli/1.0.44 (external, sdk-py)", None, None, True),
|
||||
("claude-cli/2.0.69 (external, cli)", False, None, False),
|
||||
("claude-cli/2.0.69 (external, cli)", None, False, None),
|
||||
("claude-cli/2.0.69 (external, cli)", None, True, None),
|
||||
("codex_cli_rs/0.144.5 (Mac OS 26.4.0; arm64) WezTerm", None, None, True),
|
||||
("codex_exec/0.144.5 (Mac OS 26.4.0; arm64) WarpTerminal (codex_exec; 0.144.5)", None, None, True),
|
||||
("codex_vscode/0.144.5 (Mac OS 26.4.0; arm64) vscode/1.104.1", None, None, True),
|
||||
("codex_exec/0.144.5 (Mac OS 26.4.0; arm64)", False, None, False),
|
||||
("codex_exec/0.144.5 (Mac OS 26.4.0; arm64)", None, True, None),
|
||||
("PostmanRuntime/7.53.0", None, None, None),
|
||||
(None, None, None, None),
|
||||
("claude-cli/2.0.69 (external, cli)", None, {}, True),
|
||||
("claude-cli/1.0.44 (external, sdk-py)", None, {}, True),
|
||||
("claude-cli/2.0.69 (external, cli)", False, {}, False),
|
||||
("claude-cli/2.0.69 (external, cli)", None, {"drop_params": False}, None),
|
||||
("claude-cli/2.0.69 (external, cli)", None, {"drop_params": True}, True),
|
||||
("claude-cli/2.0.69 (external, cli)", None, {"drop_params": None}, True),
|
||||
("claude-cli/2.0.69 (external, cli)", None, {"drop_params": "false"}, None),
|
||||
("claude-cli/2.0.69 (external, cli)", None, {"drop_params": "maybe"}, True),
|
||||
("codex_cli_rs/0.144.5 (Mac OS 26.4.0; arm64) WezTerm", None, {}, True),
|
||||
("codex_exec/0.144.5 (Mac OS 26.4.0; arm64) WarpTerminal (codex_exec; 0.144.5)", None, {}, True),
|
||||
("codex_vscode/0.144.5 (Mac OS 26.4.0; arm64) vscode/1.104.1", None, {}, True),
|
||||
("codex_exec/0.144.5 (Mac OS 26.4.0; arm64)", False, {}, False),
|
||||
("codex_exec/0.144.5 (Mac OS 26.4.0; arm64)", None, {"drop_params": False}, None),
|
||||
("codex_exec/0.144.5 (Mac OS 26.4.0; arm64)", None, {"drop_params": True}, True),
|
||||
("codex_exec/0.144.5 (Mac OS 26.4.0; arm64)", None, {"drop_params": " False "}, None),
|
||||
("codex_exec/0.144.5 (Mac OS 26.4.0; arm64)", None, {"drop_params": "true"}, True),
|
||||
("PostmanRuntime/7.53.0", None, {}, None),
|
||||
(None, None, {}, None),
|
||||
],
|
||||
)
|
||||
async def test_add_litellm_data_to_request_agentic_cli_drop_params(
|
||||
user_agent, request_drop_params, operator_drop_params, expected_drop_params
|
||||
user_agent, request_drop_params, operator_settings, expected_drop_params
|
||||
):
|
||||
"""Claude Code sends Anthropic-specific params and Codex sends
|
||||
service_tier, both of which fail on providers that reject them, so those
|
||||
|
|
@ -7058,12 +7064,10 @@ async def test_add_litellm_data_to_request_agentic_cli_drop_params(
|
|||
if request_drop_params is not None:
|
||||
data["drop_params"] = request_drop_params
|
||||
|
||||
proxy_config = MagicMock()
|
||||
proxy_config.config = (
|
||||
{"litellm_settings": {"drop_params": operator_drop_params}}
|
||||
if operator_drop_params is not None
|
||||
else {"litellm_settings": {}}
|
||||
)
|
||||
from litellm.proxy.proxy_server import ProxyConfig
|
||||
|
||||
proxy_config = ProxyConfig()
|
||||
proxy_config.update_config_state(config={"litellm_settings": operator_settings})
|
||||
|
||||
updated = await add_litellm_data_to_request(
|
||||
data=data,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue