From 964be1033b4763c0d6be62ba60135b4a28321f10 Mon Sep 17 00:00:00 2001 From: kamilbenkirane Date: Thu, 19 Mar 2026 00:03:02 -0700 Subject: [PATCH 1/2] fix: warn when unsupported parameters are silently ignored Fixes #210 Add UnsupportedParameterWarning emitted via warnings.warn() when a user passes a parameter that has no mapper for the current provider/model. --- src/celeste/__init__.py | 2 + src/celeste/client.py | 17 ++++++++- src/celeste/exceptions.py | 5 +++ tests/unit_tests/test_client.py | 67 ++++++++++++++++++++++++++++++++- 4 files changed, 88 insertions(+), 3 deletions(-) diff --git a/src/celeste/__init__.py b/src/celeste/__init__.py index 919f397..eb90db0 100644 --- a/src/celeste/__init__.py +++ b/src/celeste/__init__.py @@ -29,6 +29,7 @@ StreamNotExhaustedError, UnsupportedCapabilityError, UnsupportedParameterError, + UnsupportedParameterWarning, UnsupportedProviderError, ValidationError, ) @@ -272,6 +273,7 @@ def create_client( "StrictRefResolvingJsonSchemaGenerator", "UnsupportedCapabilityError", "UnsupportedParameterError", + "UnsupportedParameterWarning", "UnsupportedProviderError", "Usage", "UsageField", diff --git a/src/celeste/client.py b/src/celeste/client.py index ed92449..1f8064e 100644 --- a/src/celeste/client.py +++ b/src/celeste/client.py @@ -1,5 +1,6 @@ """Base client for modality-specific AI operations.""" +import warnings from abc import ABC, abstractmethod from collections.abc import AsyncIterator from json import JSONDecodeError @@ -10,7 +11,7 @@ from celeste.auth import Authentication from celeste.core import Modality, Provider -from celeste.exceptions import StreamingNotSupportedError +from celeste.exceptions import StreamingNotSupportedError, UnsupportedParameterWarning from celeste.http import HTTPClient, get_http_client from celeste.io import Chunk as ChunkBase from celeste.io import FinishReason, Input, Output, Usage @@ -407,10 +408,22 @@ def _build_request( _ = streaming # Passed through to provider mixins request = self._init_request(inputs) - for mapper in self.parameter_mappers(): + mappers = self.parameter_mappers() + mapped_names = {m.name for m in mappers} + + for mapper in mappers: value = parameters.get(mapper.name) request = mapper.map(request, value, self.model) + for name, value in parameters.items(): + if value is not None and name not in mapped_names: + warnings.warn( + f"Parameter '{name}' is not supported by model " + f"'{self.model.id}' and will be ignored.", + UnsupportedParameterWarning, + stacklevel=4, + ) + if extra_body: self._deep_merge(request, extra_body) diff --git a/src/celeste/exceptions.py b/src/celeste/exceptions.py index d18799d..4db48b9 100644 --- a/src/celeste/exceptions.py +++ b/src/celeste/exceptions.py @@ -245,6 +245,10 @@ def __init__(self, parameter: str, model_id: str) -> None: ) +class UnsupportedParameterWarning(UserWarning): + """Emitted when a parameter is not supported by a provider and will be ignored.""" + + __all__ = [ "ClientNotFoundError", "ConstraintViolationError", @@ -259,5 +263,6 @@ def __init__(self, parameter: str, model_id: str) -> None: "StreamingNotSupportedError", "UnsupportedCapabilityError", "UnsupportedParameterError", + "UnsupportedParameterWarning", "UnsupportedProviderError", ] diff --git a/tests/unit_tests/test_client.py b/tests/unit_tests/test_client.py index c1ee894..e927963 100644 --- a/tests/unit_tests/test_client.py +++ b/tests/unit_tests/test_client.py @@ -11,7 +11,7 @@ from celeste.auth import APIKey from celeste.client import ModalityClient from celeste.core import Modality, Provider -from celeste.exceptions import StreamingNotSupportedError +from celeste.exceptions import StreamingNotSupportedError, UnsupportedParameterWarning from celeste.io import Chunk, Input, Output, Usage from celeste.models import Model, Operation from celeste.parameters import ParameterMapper, Parameters @@ -222,6 +222,71 @@ def parameter_mappers(cls) -> list[ParameterMapper[str]]: assert request["first_param"] == "first" assert request["second_param"] == "second" + def test_build_request_warns_on_unsupported_parameter( + self, text_model: Model, api_key: str + ) -> None: + """_build_request emits UnsupportedParameterWarning for unmapped parameters.""" + + class ClientWithOneMapper(ConcreteModalityClient): + @classmethod + def parameter_mappers(cls) -> list[ParameterMapper[str]]: + return [_create_test_mapper(ParamEnum.FIRST_PARAM)] + + client = ClientWithOneMapper( + modality=Modality.TEXT, + model=text_model, + provider=text_model.provider, + auth=APIKey(secret=SecretStr(api_key)), + ) + + inputs = _TestInput(prompt="test") + + with pytest.warns(UnsupportedParameterWarning, match="second_param.*gpt-4"): + client._build_request(inputs, first_param="ok", second_param="unsupported") + + def test_build_request_no_warning_for_supported_parameters( + self, text_model: Model, api_key: str + ) -> None: + """_build_request does not warn when all parameters have mappers.""" + import warnings + + class ClientWithMapper(ConcreteModalityClient): + @classmethod + def parameter_mappers(cls) -> list[ParameterMapper[str]]: + return [_create_test_mapper(ParamEnum.TEST_PARAM)] + + client = ClientWithMapper( + modality=Modality.TEXT, + model=text_model, + provider=text_model.provider, + auth=APIKey(secret=SecretStr(api_key)), + ) + + inputs = _TestInput(prompt="test") + + with warnings.catch_warnings(): + warnings.simplefilter("error", UnsupportedParameterWarning) + client._build_request(inputs, test_param="supported") + + def test_build_request_no_warning_for_none_unsupported_parameter( + self, text_model: Model, api_key: str + ) -> None: + """_build_request does not warn when unsupported parameter value is None.""" + import warnings + + client = ConcreteModalityClient( + modality=Modality.TEXT, + model=text_model, + provider=text_model.provider, + auth=APIKey(secret=SecretStr(api_key)), + ) + + inputs = _TestInput(prompt="test") + + with warnings.catch_warnings(): + warnings.simplefilter("error", UnsupportedParameterWarning) + client._build_request(inputs, test_param=None) + @pytest.mark.parametrize( "param_value,expected_output", [ From 1a021119add209f4d93cf0879ef9ce244979a958 Mon Sep 17 00:00:00 2001 From: kamilbenkirane Date: Thu, 19 Mar 2026 00:37:09 -0700 Subject: [PATCH 2/2] refactor: use pop pattern in _build_request for cleaner unsupported param detection Each mapper claims its parameter via pop(). Unclaimed params are naturally what remains, removing the need for a separate set + loop. --- src/celeste/client.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/celeste/client.py b/src/celeste/client.py index 1f8064e..da30dd8 100644 --- a/src/celeste/client.py +++ b/src/celeste/client.py @@ -408,15 +408,12 @@ def _build_request( _ = streaming # Passed through to provider mixins request = self._init_request(inputs) - mappers = self.parameter_mappers() - mapped_names = {m.name for m in mappers} - - for mapper in mappers: - value = parameters.get(mapper.name) + for mapper in self.parameter_mappers(): + value = parameters.pop(mapper.name, None) request = mapper.map(request, value, self.model) for name, value in parameters.items(): - if value is not None and name not in mapped_names: + if value is not None: warnings.warn( f"Parameter '{name}' is not supported by model " f"'{self.model.id}' and will be ignored.",