From e3261e8601c2ea192701e0b535d748c71fb68321 Mon Sep 17 00:00:00 2001 From: Terrajlz Date: Fri, 22 May 2026 13:56:36 -0400 Subject: [PATCH] fix(responses): filter out non-litellm kwargs in Responses API completion bridge When the Responses API bridge forwards requests to litellm.completion(), it merges all **kwargs into the completion args. Client-specific params like `client_metadata` (sent by the OpenAI SDK) are not valid litellm.completion() arguments and cause 'unexpected keyword argument' errors at the provider SDK level. Filter kwargs to only pass litellm-internal params (prefixed with `litellm_`) to the completion/acompletion calls. Fixes #28539 --- .../handler.py | 16 ++++- .../test_responses_api_client_metadata.py | 63 +++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 tests/test_litellm/test_responses_api_client_metadata.py diff --git a/litellm/responses/litellm_completion_transformation/handler.py b/litellm/responses/litellm_completion_transformation/handler.py index 03a2f339bea..0b9c34b5f9d 100644 --- a/litellm/responses/litellm_completion_transformation/handler.py +++ b/litellm/responses/litellm_completion_transformation/handler.py @@ -20,6 +20,18 @@ from litellm.types.llms.openai import ( from litellm.types.utils import ModelResponse +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(). + + 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. + """ + return {k: v for k, v in kwargs.items() if k.startswith("litellm_")} + + class LiteLLMCompletionTransformationHandler: def response_api_handler( self, @@ -59,7 +71,7 @@ class LiteLLMCompletionTransformationHandler: ) completion_args = {} - completion_args.update(kwargs) + completion_args.update(_filter_kwargs_for_completion(kwargs)) completion_args.update(litellm_completion_request) litellm_completion_response: Union[ @@ -109,7 +121,7 @@ class LiteLLMCompletionTransformationHandler: ) acompletion_args = {} - acompletion_args.update(kwargs) + acompletion_args.update(_filter_kwargs_for_completion(kwargs)) acompletion_args.update(litellm_completion_request) litellm_completion_response: Union[ diff --git a/tests/test_litellm/test_responses_api_client_metadata.py b/tests/test_litellm/test_responses_api_client_metadata.py new file mode 100644 index 00000000000..81a15d6b7d5 --- /dev/null +++ b/tests/test_litellm/test_responses_api_client_metadata.py @@ -0,0 +1,63 @@ +""" +Unit tests for fix #28539: client_metadata should NOT be forwarded from +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_'). +""" + +import pytest + +from litellm.responses.litellm_completion_transformation.handler import ( + _filter_kwargs_for_completion, +) + + +class TestFilterKwargsForCompletion: + """Verify _filter_kwargs_for_completion strips non-litellm kwargs.""" + + def test_strips_client_metadata(self): + """client_metadata from the Responses API must not pass through.""" + kwargs = { + "litellm_logging_obj": "mock_obj", + "litellm_metadata": {"key": "value"}, + "client_metadata": {"session": "abc123"}, + } + filtered = _filter_kwargs_for_completion(kwargs) + assert "client_metadata" not in filtered + assert filtered["litellm_logging_obj"] == "mock_obj" + assert filtered["litellm_metadata"] == {"key": "value"} + + def test_preserves_all_litellm_prefixed_kwargs(self): + """All litellm_* kwargs should be preserved.""" + kwargs = { + "litellm_call_id": "call-123", + "litellm_metadata": {}, + "litellm_logging_obj": None, + "litellm_parent_otel_span": "span", + } + filtered = _filter_kwargs_for_completion(kwargs) + assert filtered == kwargs + + def test_strips_all_non_litellm_kwargs(self): + """Any non-litellm_* kwarg should be stripped.""" + kwargs = { + "litellm_metadata": {}, + "client_metadata": {"x": 1}, + "some_other_param": True, + "prompt_id": "p-1", + } + filtered = _filter_kwargs_for_completion(kwargs) + assert filtered == {"litellm_metadata": {}} + + 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) == {}