From 82be053f96a3ce4e22e243771c7eeab4cb7fcc71 Mon Sep 17 00:00:00 2001 From: Terrajlz Date: Fri, 22 May 2026 13:56:37 -0400 Subject: [PATCH] fix(responses): use blocklist instead of allowlist for kwargs filtering Addresses review concerns: - Switch from litellm_-prefix allowlist to a blocklist of Responses-API-only params. This preserves valid completion kwargs (api_key, mock_response, num_retries, drop_params, etc.) that don't have a litellm_ prefix. - Fix sync path to use filtered completion_args instead of raw **kwargs. - Add tests for api_key passthrough and valid completion kwargs. --- .../handler.py | 24 ++++++--- .../test_responses_api_client_metadata.py | 49 ++++++++++++++----- 2 files changed, 55 insertions(+), 18 deletions(-) diff --git a/litellm/responses/litellm_completion_transformation/handler.py b/litellm/responses/litellm_completion_transformation/handler.py index 0b9c34b5f9d..e2d4bc047c2 100644 --- a/litellm/responses/litellm_completion_transformation/handler.py +++ b/litellm/responses/litellm_completion_transformation/handler.py @@ -19,17 +19,29 @@ from litellm.types.llms.openai import ( ) from litellm.types.utils import ModelResponse +_RESPONSES_API_ONLY_PARAMS = frozenset( + { + "client_metadata", + "include", + "instructions", + "parallel_tool_calls", + "previous_response_id", + "truncation", + "user", + } +) + def _filter_kwargs_for_completion(kwargs: dict) -> dict: """ - Filter kwargs to only include litellm-internal params that are safe to pass - to litellm.completion() / litellm.acompletion(). + Filter kwargs to exclude Responses-API-only params that are NOT valid + litellm.completion() / litellm.acompletion() arguments. - Responses API requests may contain client-specific params (e.g. client_metadata) - that are NOT valid litellm.completion() arguments. Passing them through causes - 'unexpected keyword argument' errors at the provider SDK level. + We use a blocklist (not an allowlist) so that valid completion kwargs like + api_key, mock_response, num_retries, drop_params, and litellm_* params + all pass through. """ - return {k: v for k, v in kwargs.items() if k.startswith("litellm_")} + return {k: v for k, v in kwargs.items() if k not in _RESPONSES_API_ONLY_PARAMS} class LiteLLMCompletionTransformationHandler: diff --git a/tests/test_litellm/test_responses_api_client_metadata.py b/tests/test_litellm/test_responses_api_client_metadata.py index 81a15d6b7d5..555a2143653 100644 --- a/tests/test_litellm/test_responses_api_client_metadata.py +++ b/tests/test_litellm/test_responses_api_client_metadata.py @@ -5,8 +5,8 @@ Responses API bridge to litellm.completion()/acompletion(). The Responses API handler merges **kwargs into the completion args dict. When a client sends client_metadata (an OpenAI Responses API param), it flows through kwargs and causes 'unexpected keyword argument' errors at the provider -SDK level. The fix filters kwargs to only pass litellm-internal params -(prefixed with 'litellm_'). +SDK level. The fix filters kwargs via a blocklist of Responses-API-only params, +preserving valid completion kwargs like api_key, mock_response, and litellm_*. """ import pytest @@ -17,7 +17,7 @@ from litellm.responses.litellm_completion_transformation.handler import ( class TestFilterKwargsForCompletion: - """Verify _filter_kwargs_for_completion strips non-litellm kwargs.""" + """Verify _filter_kwargs_for_completion strips Responses-API-only kwargs.""" def test_strips_client_metadata(self): """client_metadata from the Responses API must not pass through.""" @@ -42,13 +42,43 @@ class TestFilterKwargsForCompletion: filtered = _filter_kwargs_for_completion(kwargs) assert filtered == kwargs - def test_strips_all_non_litellm_kwargs(self): - """Any non-litellm_* kwarg should be stripped.""" + def test_preserves_api_key(self): + """api_key must pass through for BYOK proxy support.""" + kwargs = { + "api_key": "sk-user-provided-key", + "litellm_metadata": {}, + "client_metadata": {"x": 1}, + } + filtered = _filter_kwargs_for_completion(kwargs) + assert filtered["api_key"] == "sk-user-provided-key" + assert "client_metadata" not in filtered + + def test_preserves_valid_completion_kwargs(self): + """Valid litellm.completion() kwargs like mock_response, num_retries, drop_params + must pass through (they don't have a litellm_ prefix).""" + kwargs = { + "mock_response": "hello", + "num_retries": 3, + "drop_params": True, + "input_cost_per_token": 0.01, + "client_metadata": {"x": 1}, + } + filtered = _filter_kwargs_for_completion(kwargs) + assert filtered["mock_response"] == "hello" + assert filtered["num_retries"] == 3 + assert filtered["drop_params"] is True + assert filtered["input_cost_per_token"] == 0.01 + assert "client_metadata" not in filtered + + def test_strips_responses_api_only_params(self): + """Responses-API-only params should be stripped.""" kwargs = { "litellm_metadata": {}, "client_metadata": {"x": 1}, - "some_other_param": True, - "prompt_id": "p-1", + "include": ["file_search_results"], + "instructions": "Be helpful", + "previous_response_id": "resp_abc", + "truncation": "auto", } filtered = _filter_kwargs_for_completion(kwargs) assert filtered == {"litellm_metadata": {}} @@ -56,8 +86,3 @@ class TestFilterKwargsForCompletion: def test_empty_kwargs(self): """Empty kwargs should return empty dict.""" assert _filter_kwargs_for_completion({}) == {} - - def test_no_litellm_kwargs(self): - """If only non-litellm kwargs, return empty dict.""" - kwargs = {"client_metadata": {}, "extra_param": "value"} - assert _filter_kwargs_for_completion(kwargs) == {}