From 3d79ba461bb03398987d86fbdeba344497994071 Mon Sep 17 00:00:00 2001 From: T145 Date: Thu, 15 Oct 2026 20:38:44 +0000 Subject: [PATCH 01/11] feat(llms): add WebWright provider for the wren-r1 model WebWright exposes an OpenAI-compatible chat completions API at https://webwright.ai/v1 and serves the wren-r1 model. Add a WebwrightChatConfig that builds on the shared OpenAI-like transformation, modeled on the existing hyperbolic provider. - litellm/llms/webwright/chat/transformation.py: base URL and API key handling - register the provider in constants, the LlmProviders enum, the provider config manager, get_llm_provider, and the lazy import registry - tests/unit/llms/webwright/chat: unit tests for provider resolution, provider info, provider lists, supported params, and a mocked completion call --- litellm/__init__.py | 7 ++ litellm/_lazy_imports_registry.py | 5 + litellm/constants.py | 3 + .../get_llm_provider_logic.py | 8 ++ litellm/llms/webwright/__init__.py | 0 litellm/llms/webwright/chat/__init__.py | 0 litellm/llms/webwright/chat/transformation.py | 61 ++++++++++++ litellm/types/utils.py | 1 + litellm/utils.py | 1 + tests/unit/llms/webwright/__init__.py | 0 tests/unit/llms/webwright/chat/__init__.py | 0 .../chat/test_webwright_transformation.py | 99 +++++++++++++++++++ 12 files changed, 185 insertions(+) create mode 100644 litellm/llms/webwright/__init__.py create mode 100644 litellm/llms/webwright/chat/__init__.py create mode 100644 litellm/llms/webwright/chat/transformation.py create mode 100644 tests/unit/llms/webwright/__init__.py create mode 100644 tests/unit/llms/webwright/chat/__init__.py create mode 100644 tests/unit/llms/webwright/chat/test_webwright_transformation.py diff --git a/litellm/__init__.py b/litellm/__init__.py index ff901ede512..7132a34e0c0 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -701,6 +701,7 @@ morph_models: Set = set() lambda_ai_models: Set = set() inception_models: Set = set() hyperbolic_models: Set = set() +webwright_models: Set = set() black_forest_labs_models: Set = set() recraft_models: Set = set() cometapi_models: Set = set() @@ -975,6 +976,8 @@ def _populate_provider_model_sets(model_cost_map: Mapping[str, Mapping[str, obje inception_models.add(key) elif value.get("litellm_provider") == "hyperbolic": hyperbolic_models.add(key) + elif value.get("litellm_provider") == "webwright": + webwright_models.add(key) elif value.get("litellm_provider") == "black_forest_labs": black_forest_labs_models.add(key) elif value.get("litellm_provider") == "recraft": @@ -1252,6 +1255,7 @@ def _build_models_by_provider() -> dict: "lambda_ai": lambda_ai_models, "inception": inception_models, "hyperbolic": hyperbolic_models, + "webwright": webwright_models, "black_forest_labs": black_forest_labs_models, "recraft": recraft_models, "cometapi": cometapi_models, @@ -2213,6 +2217,9 @@ if TYPE_CHECKING: from .llms.hyperbolic.chat.transformation import ( HyperbolicChatConfig as HyperbolicChatConfig, ) + from .llms.webwright.chat.transformation import ( + WebwrightChatConfig as WebwrightChatConfig, + ) from .llms.vercel_ai_gateway.chat.transformation import ( VercelAIGatewayConfig as VercelAIGatewayConfig, ) diff --git a/litellm/_lazy_imports_registry.py b/litellm/_lazy_imports_registry.py index d47d72ef7a8..6d5883d1b06 100644 --- a/litellm/_lazy_imports_registry.py +++ b/litellm/_lazy_imports_registry.py @@ -349,6 +349,7 @@ LLM_CONFIG_NAMES: Final = ( "LambdaAIChatConfig", "InceptionChatConfig", "HyperbolicChatConfig", + "WebwrightChatConfig", "VercelAIGatewayConfig", "EdenAIChatConfig", "EdenAIResponsesAPIConfig", @@ -1302,6 +1303,10 @@ _LLM_CONFIGS_IMPORT_MAP: Final = { ".llms.hyperbolic.chat.transformation", "HyperbolicChatConfig", ), + "WebwrightChatConfig": ( + ".llms.webwright.chat.transformation", + "WebwrightChatConfig", + ), "VercelAIGatewayConfig": ( ".llms.vercel_ai_gateway.chat.transformation", "VercelAIGatewayConfig", diff --git a/litellm/constants.py b/litellm/constants.py index 1f9e81443f7..83bb1708dfc 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -1007,6 +1007,7 @@ openai_compatible_endpoints: Final[list] = [ "https://api.lambda.ai/v1", "https://api.inceptionlabs.ai/v1", "https://api.hyperbolic.xyz/v1", + "https://webwright.ai/v1", "https://ai-gateway.helicone.ai/", "https://ai-gateway.vercel.sh/v1", "https://api.edenai.run/v3", @@ -1082,6 +1083,7 @@ openai_compatible_providers: Final[list] = [ "lambda_ai", "inception", "hyperbolic", + "webwright", "vercel_ai_gateway", "aiml", "edenai", @@ -1127,6 +1129,7 @@ openai_text_completion_compatible_providers: Final[list] = [ # providers that s "v0", "lambda_ai", "hyperbolic", + "webwright", "wandb", ] _openai_like_providers: Final[list[str]] = [ diff --git a/litellm/litellm_core_utils/get_llm_provider_logic.py b/litellm/litellm_core_utils/get_llm_provider_logic.py index 74d9344a172..d047c71b55e 100644 --- a/litellm/litellm_core_utils/get_llm_provider_logic.py +++ b/litellm/litellm_core_utils/get_llm_provider_logic.py @@ -391,6 +391,9 @@ def get_llm_provider( elif endpoint == "https://api.hyperbolic.xyz/v1": custom_llm_provider = "hyperbolic" dynamic_api_key = get_secret_str("HYPERBOLIC_API_KEY") + elif endpoint == "https://webwright.ai/v1": + custom_llm_provider = "webwright" + dynamic_api_key = get_secret_str("WEBWRIGHT_API_KEY") elif endpoint == "https://ai-gateway.vercel.sh/v1": custom_llm_provider = "vercel_ai_gateway" dynamic_api_key = get_secret_str("VERCEL_AI_GATEWAY_API_KEY") @@ -913,6 +916,11 @@ def _get_openai_compatible_provider_info( api_base, dynamic_api_key, ) = litellm.HyperbolicChatConfig().get_openai_compatible_provider_info(api_base, api_key) + elif custom_llm_provider == "webwright": + ( + api_base, + dynamic_api_key, + ) = litellm.WebwrightChatConfig().get_openai_compatible_provider_info(api_base, api_key) elif custom_llm_provider == "vercel_ai_gateway": ( api_base, diff --git a/litellm/llms/webwright/__init__.py b/litellm/llms/webwright/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/litellm/llms/webwright/chat/__init__.py b/litellm/llms/webwright/chat/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/litellm/llms/webwright/chat/transformation.py b/litellm/llms/webwright/chat/transformation.py new file mode 100644 index 00000000000..7ac3806ed5c --- /dev/null +++ b/litellm/llms/webwright/chat/transformation.py @@ -0,0 +1,61 @@ +""" +Translate from OpenAI's `/v1/chat/completions` to WebWright's `/v1/chat/completions` +""" + +from typing import Final + +from litellm.secret_managers.main import get_secret_str + +from ...openai_like.chat.transformation import OpenAILikeChatConfig + + +class WebwrightChatConfig(OpenAILikeChatConfig): + """ + WebWright is OpenAI-compatible with standard endpoints. + """ + + @property + def custom_llm_provider(self) -> str | None: + return "webwright" + + def _get_openai_compatible_provider_info( + self, api_base: str | None, api_key: str | None + ) -> tuple[str | None, str | None]: + # WebWright is openai compatible, we just need to set the api_base + api_base = ( + api_base + or get_secret_str("WEBWRIGHT_API_BASE") + or "https://webwright.ai/v1" # Default WebWright API base URL + ) + dynamic_api_key: Final = api_key or get_secret_str("WEBWRIGHT_API_KEY") + return api_base, dynamic_api_key + + def get_openai_compatible_provider_info( + self, + api_base: str | None, + api_key: str | None, + ) -> tuple[str | None, str | None]: + return self._get_openai_compatible_provider_info(api_base, api_key) + + def get_supported_openai_params(self, model: str) -> list: + """ + WebWright supports the standard OpenAI chat completion parameters. + Reference: https://webwright.ai/v1 + """ + return [ + "messages", # Required + "model", # Required + "stream", # Optional + "temperature", # Optional + "top_p", # Optional + "max_tokens", # Optional + "frequency_penalty", # Optional + "presence_penalty", # Optional + "stop", # Optional + "n", # Optional + "tools", # Optional + "tool_choice", # Optional + "response_format", # Optional + "seed", # Optional + "user", # Optional + ] diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 5f97c25d3d2..daf89c03ea0 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -4342,6 +4342,7 @@ class LlmProviders(str, Enum): MONGODB = "mongodb" HELICONE = "helicone" HYPERBOLIC = "hyperbolic" + WEBWRIGHT = "webwright" RECRAFT = "recraft" FAL_AI = "fal_ai" STABILITY = "stability" diff --git a/litellm/utils.py b/litellm/utils.py index 1bc3b55992c..3425e5b586c 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -8762,6 +8762,7 @@ class ProviderConfigManager: LlmProviders.HEROKU: (lambda: litellm.HerokuChatConfig(), False), LlmProviders.OCI: (lambda: litellm.OCIChatConfig(), False), LlmProviders.HYPERBOLIC: (lambda: litellm.HyperbolicChatConfig(), False), + LlmProviders.WEBWRIGHT: (lambda: litellm.WebwrightChatConfig(), False), LlmProviders.OVHCLOUD: (lambda: litellm.OVHCloudChatConfig(), False), LlmProviders.AMAZON_NOVA: (lambda: litellm.AmazonNovaChatConfig(), False), LlmProviders.LANGGRAPH: ( diff --git a/tests/unit/llms/webwright/__init__.py b/tests/unit/llms/webwright/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/unit/llms/webwright/chat/__init__.py b/tests/unit/llms/webwright/chat/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/unit/llms/webwright/chat/test_webwright_transformation.py b/tests/unit/llms/webwright/chat/test_webwright_transformation.py new file mode 100644 index 00000000000..4b272c69ecb --- /dev/null +++ b/tests/unit/llms/webwright/chat/test_webwright_transformation.py @@ -0,0 +1,99 @@ +""" +Unit tests for the WebWright provider configuration. + +WebWright exposes an OpenAI-compatible chat completions API at +https://webwright.ai/v1 and serves the `wren-r1` model, so the provider is a +thin configuration over the shared OpenAI-like transformation. +""" + +import os +from unittest import mock + +import litellm + +from litellm.llms.webwright.chat.transformation import WebwrightChatConfig + + +def test_get_llm_provider_webwright() -> None: + """Test that get_llm_provider correctly identifies WebWright""" + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + # Test with webwright/model-name format + model, provider, api_key, api_base = get_llm_provider("webwright/wren-r1") + assert model == "wren-r1" + assert provider == "webwright" + + # Test with api_base containing the WebWright endpoint + model, provider, api_key, api_base = get_llm_provider("wren-r1", api_base="https://webwright.ai/v1") + assert model == "wren-r1" + assert provider == "webwright" + assert api_base == "https://webwright.ai/v1" + + +def test_webwright_config_initialization() -> None: + """Test WebwrightChatConfig initializes correctly""" + config = WebwrightChatConfig() + assert config.custom_llm_provider == "webwright" + + +def test_webwright_get_openai_compatible_provider_info() -> None: + """Test WebWright API base and key handling""" + config = WebwrightChatConfig() + + with mock.patch.dict(os.environ, {}, clear=True): + api_base, api_key = config.get_openai_compatible_provider_info(None, None) + assert api_base == "https://webwright.ai/v1" + assert api_key is None + + with mock.patch.dict( + os.environ, + { + "WEBWRIGHT_API_KEY": "env-key", + "WEBWRIGHT_API_BASE": "https://env.webwright.ai/v1", + }, + ): + api_base, api_key = config.get_openai_compatible_provider_info(None, None) + assert api_base == "https://env.webwright.ai/v1" + assert api_key == "env-key" + + # Explicit arguments take precedence over the environment + api_base, api_key = config.get_openai_compatible_provider_info("https://custom.webwright.ai/v1", "param-key") + assert api_base == "https://custom.webwright.ai/v1" + assert api_key == "param-key" + + +def test_webwright_in_provider_lists() -> None: + """Test that webwright is registered in all necessary provider lists""" + assert "webwright" in litellm.openai_compatible_providers + assert "webwright" in litellm.openai_text_completion_compatible_providers + assert "webwright" in litellm.provider_list + assert "https://webwright.ai/v1" in litellm.openai_compatible_endpoints + + +def test_webwright_supported_params() -> None: + """Test that supported OpenAI parameters are correctly configured""" + config = WebwrightChatConfig() + supported_params = config.get_supported_openai_params("webwright/wren-r1") + + for param in ( + "messages", + "model", + "stream", + "temperature", + "max_tokens", + "tools", + "tool_choice", + ): + assert param in supported_params + + +def test_webwright_completion_call() -> None: + """Test the completion call structure for WebWright with a mocked response""" + response = litellm.completion( + model="webwright/wren-r1", + messages=[{"role": "user", "content": "Hello!"}], + api_key="test-key", + mock_response="Hi there!", + ) + assert response is not None + assert response.choices[0].message.content == "Hi there!" From 7bc7a31c955140dfc558876bae8bdcfd8af7cc27 Mon Sep 17 00:00:00 2001 From: T145 Date: Thu, 15 Oct 2026 20:47:36 +0000 Subject: [PATCH 02/11] chore(llms): register the webwright provider in CI coverage and provider listings - add tests/unit/llms/webwright to the llms-providers test shard so the new test file is invoked by a job - add the webwright provider to provider_endpoints_support.json - add a WebWright row to the supported providers table in README.md --- .github/workflows/test-unit.yml | 1 + README.md | 1 + provider_endpoints_support.json | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+) diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index 19daab5a759..c3411ca3d70 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -356,6 +356,7 @@ jobs: tests/unit/llms/voyage tests/unit/llms/wandb tests/unit/llms/watsonx + tests/unit/llms/webwright tests/unit/llms/xai tests/unit/llms/xinference tests/unit/llms/you_com diff --git a/README.md b/README.md index 3bd0fd54a65..7f32afea09f 100644 --- a/README.md +++ b/README.md @@ -424,6 +424,7 @@ Set `LITELLM_PROXY_API_BASE` and `LITELLM_PROXY_API_KEY` and every model call th | [VoyageAI by MongoDB (`voyage`)](https://docs.litellm.ai/docs/providers/voyage) | | | | ✅ | | | | | | | | [WandB Inference (`wandb`)](https://docs.litellm.ai/docs/providers/wandb_inference) | ✅ | ✅ | ✅ | | | | | | | | | [Watsonx Text (`watsonx_text`)](https://docs.litellm.ai/docs/providers/watsonx) | ✅ | ✅ | ✅ | | | | | | | | +| [WebWright (`webwright`)](https://docs.litellm.ai/docs/providers/webwright) | ✅ | ✅ | ✅ | | | | | | | | | [xAI (`xai`)](https://docs.litellm.ai/docs/providers/xai) | ✅ | ✅ | ✅ | | | | | | | | | [Xinference (`xinference`)](https://docs.litellm.ai/docs/providers/xinference) | | | | ✅ | | | | | | | diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index 45104d40018..91646aa34ff 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -2803,6 +2803,24 @@ "interactions": true } }, + "webwright": { + "display_name": "WebWright (`webwright`)", + "url": "https://docs.litellm.ai/docs/providers/webwright", + "endpoints": { + "chat_completions": true, + "messages": true, + "responses": true, + "embeddings": false, + "image_generations": false, + "audio_transcriptions": false, + "audio_speech": false, + "moderations": false, + "batches": false, + "rerank": false, + "a2a": false, + "interactions": false + } + }, "watsonx_text": { "display_name": "Watsonx Text (`watsonx_text`)", "url": "https://docs.litellm.ai/docs/providers/watsonx", From 3dc98b4d019bfe94b6ee2af314a2133ceaf715bc Mon Sep 17 00:00:00 2001 From: T145 Date: Thu, 15 Oct 2026 22:23:11 +0000 Subject: [PATCH 03/11] fix(llms): register webwright in the Add Model catalog and satisfy the type-discipline gate - Add a webwright entry to provider_create_fields.json so the provider appears in the Add Model dropdown instead of drifting into the frozen unlisted set. - Bind a new resolved_api_base name instead of re-binding the api_base parameter, annotate the return as list[str], and give the remaining parameter re-binds in the shared dispatch a reason. --- .../get_llm_provider_logic.py | 6 ++-- litellm/llms/webwright/chat/transformation.py | 6 ++-- .../provider_create_fields.json | 28 +++++++++++++++++++ 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/litellm/litellm_core_utils/get_llm_provider_logic.py b/litellm/litellm_core_utils/get_llm_provider_logic.py index d047c71b55e..a509fbc4754 100644 --- a/litellm/litellm_core_utils/get_llm_provider_logic.py +++ b/litellm/litellm_core_utils/get_llm_provider_logic.py @@ -392,7 +392,7 @@ def get_llm_provider( custom_llm_provider = "hyperbolic" dynamic_api_key = get_secret_str("HYPERBOLIC_API_KEY") elif endpoint == "https://webwright.ai/v1": - custom_llm_provider = "webwright" + custom_llm_provider = "webwright" # rebind-ok: mirrors the sibling OpenAI-compatible endpoint dispatch dynamic_api_key = get_secret_str("WEBWRIGHT_API_KEY") elif endpoint == "https://ai-gateway.vercel.sh/v1": custom_llm_provider = "vercel_ai_gateway" @@ -918,8 +918,8 @@ def _get_openai_compatible_provider_info( ) = litellm.HyperbolicChatConfig().get_openai_compatible_provider_info(api_base, api_key) elif custom_llm_provider == "webwright": ( - api_base, - dynamic_api_key, + api_base, # rebind-ok: mirrors the sibling OpenAI-compatible provider dispatch + dynamic_api_key, # rebind-ok: mirrors the sibling OpenAI-compatible provider dispatch ) = litellm.WebwrightChatConfig().get_openai_compatible_provider_info(api_base, api_key) elif custom_llm_provider == "vercel_ai_gateway": ( diff --git a/litellm/llms/webwright/chat/transformation.py b/litellm/llms/webwright/chat/transformation.py index 7ac3806ed5c..0d07facf979 100644 --- a/litellm/llms/webwright/chat/transformation.py +++ b/litellm/llms/webwright/chat/transformation.py @@ -22,13 +22,13 @@ class WebwrightChatConfig(OpenAILikeChatConfig): self, api_base: str | None, api_key: str | None ) -> tuple[str | None, str | None]: # WebWright is openai compatible, we just need to set the api_base - api_base = ( + resolved_api_base: Final = ( api_base or get_secret_str("WEBWRIGHT_API_BASE") or "https://webwright.ai/v1" # Default WebWright API base URL ) dynamic_api_key: Final = api_key or get_secret_str("WEBWRIGHT_API_KEY") - return api_base, dynamic_api_key + return resolved_api_base, dynamic_api_key def get_openai_compatible_provider_info( self, @@ -37,7 +37,7 @@ class WebwrightChatConfig(OpenAILikeChatConfig): ) -> tuple[str | None, str | None]: return self._get_openai_compatible_provider_info(api_base, api_key) - def get_supported_openai_params(self, model: str) -> list: + def get_supported_openai_params(self, model: str) -> list[str]: # mutable-ok: matches the base provider signature that returns a plain list """ WebWright supports the standard OpenAI chat completion parameters. Reference: https://webwright.ai/v1 diff --git a/litellm/proxy/public_endpoints/provider_create_fields.json b/litellm/proxy/public_endpoints/provider_create_fields.json index 4a6a88f9a86..d6fe8adaeec 100644 --- a/litellm/proxy/public_endpoints/provider_create_fields.json +++ b/litellm/proxy/public_endpoints/provider_create_fields.json @@ -1921,6 +1921,34 @@ ], "default_model_placeholder": "gpt-3.5-turbo" }, + { + "provider": "WEBWRIGHT", + "provider_display_name": "WebWright", + "litellm_provider": "webwright", + "credential_fields": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": null, + "tooltip": null, + "required": false, + "field_type": "text", + "options": null, + "default_value": null + }, + { + "key": "api_key", + "label": "API Key", + "placeholder": null, + "tooltip": null, + "required": false, + "field_type": "password", + "options": null, + "default_value": null + } + ], + "default_model_placeholder": "gpt-3.5-turbo" + }, { "provider": "Infinity", "provider_display_name": "Infinity", From cb8259ca1085e97fd95e827117059d655374e78b Mon Sep 17 00:00:00 2001 From: T145 Date: Thu, 15 Oct 2026 23:05:16 +0000 Subject: [PATCH 04/11] style(llms): apply ruff format to the webwright provider files Wrap the two over-length lines flagged by the python job's ruff format check so the CI gate passes. No behavior change. --- litellm/litellm_core_utils/get_llm_provider_logic.py | 4 +++- litellm/llms/webwright/chat/transformation.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/litellm/litellm_core_utils/get_llm_provider_logic.py b/litellm/litellm_core_utils/get_llm_provider_logic.py index a509fbc4754..5aad7c148ce 100644 --- a/litellm/litellm_core_utils/get_llm_provider_logic.py +++ b/litellm/litellm_core_utils/get_llm_provider_logic.py @@ -392,7 +392,9 @@ def get_llm_provider( custom_llm_provider = "hyperbolic" dynamic_api_key = get_secret_str("HYPERBOLIC_API_KEY") elif endpoint == "https://webwright.ai/v1": - custom_llm_provider = "webwright" # rebind-ok: mirrors the sibling OpenAI-compatible endpoint dispatch + custom_llm_provider = ( + "webwright" # rebind-ok: mirrors the sibling OpenAI-compatible endpoint dispatch + ) dynamic_api_key = get_secret_str("WEBWRIGHT_API_KEY") elif endpoint == "https://ai-gateway.vercel.sh/v1": custom_llm_provider = "vercel_ai_gateway" diff --git a/litellm/llms/webwright/chat/transformation.py b/litellm/llms/webwright/chat/transformation.py index 0d07facf979..33d823e5b4f 100644 --- a/litellm/llms/webwright/chat/transformation.py +++ b/litellm/llms/webwright/chat/transformation.py @@ -37,7 +37,9 @@ class WebwrightChatConfig(OpenAILikeChatConfig): ) -> tuple[str | None, str | None]: return self._get_openai_compatible_provider_info(api_base, api_key) - def get_supported_openai_params(self, model: str) -> list[str]: # mutable-ok: matches the base provider signature that returns a plain list + def get_supported_openai_params( + self, model: str + ) -> list[str]: # mutable-ok: matches the base provider signature that returns a plain list """ WebWright supports the standard OpenAI chat completion parameters. Reference: https://webwright.ai/v1 From 900ecaa5f94181fdc219a24d8913d822358b833d Mon Sep 17 00:00:00 2001 From: T145 Date: Thu, 15 Oct 2026 23:16:15 +0000 Subject: [PATCH 05/11] style(llms): keep the webwright rebind-ok and mutable-ok reasons inline The previous format pass wrapped the two over-length lines, which moved the `# rebind-ok` reason off the re-bound assignment line and tripped the type discipline gate (LIT011 +1, LIT013 +1). Shorten each reason so the whole line stays under the 120-char limit with the suppression comment inline, which satisfies both ruff format and the gate. No behavior change. --- litellm/litellm_core_utils/get_llm_provider_logic.py | 4 +--- litellm/llms/webwright/chat/transformation.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/litellm/litellm_core_utils/get_llm_provider_logic.py b/litellm/litellm_core_utils/get_llm_provider_logic.py index 5aad7c148ce..fd8a9f59996 100644 --- a/litellm/litellm_core_utils/get_llm_provider_logic.py +++ b/litellm/litellm_core_utils/get_llm_provider_logic.py @@ -392,9 +392,7 @@ def get_llm_provider( custom_llm_provider = "hyperbolic" dynamic_api_key = get_secret_str("HYPERBOLIC_API_KEY") elif endpoint == "https://webwright.ai/v1": - custom_llm_provider = ( - "webwright" # rebind-ok: mirrors the sibling OpenAI-compatible endpoint dispatch - ) + custom_llm_provider = "webwright" # rebind-ok: mirrors the sibling endpoint dispatch dynamic_api_key = get_secret_str("WEBWRIGHT_API_KEY") elif endpoint == "https://ai-gateway.vercel.sh/v1": custom_llm_provider = "vercel_ai_gateway" diff --git a/litellm/llms/webwright/chat/transformation.py b/litellm/llms/webwright/chat/transformation.py index 33d823e5b4f..b156875da2d 100644 --- a/litellm/llms/webwright/chat/transformation.py +++ b/litellm/llms/webwright/chat/transformation.py @@ -37,9 +37,7 @@ class WebwrightChatConfig(OpenAILikeChatConfig): ) -> tuple[str | None, str | None]: return self._get_openai_compatible_provider_info(api_base, api_key) - def get_supported_openai_params( - self, model: str - ) -> list[str]: # mutable-ok: matches the base provider signature that returns a plain list + def get_supported_openai_params(self, model: str) -> list[str]: # mutable-ok: matches the base provider signature """ WebWright supports the standard OpenAI chat completion parameters. Reference: https://webwright.ai/v1 From a55052716d750e94ba591ad8b816086305f02805 Mon Sep 17 00:00:00 2001 From: T145 Date: Thu, 15 Oct 2026 23:30:22 +0000 Subject: [PATCH 06/11] fix(llms): type the webwright model set concretely The new `webwright_models: Set = set()` used a bare `Set`, which adds one reportMissingTypeArgument, one reportUnknownVariableType and one reportUnknownMemberType over the merge-base and breaches the basedpyright delta gate. Annotate it as `set[str]` so the new line is type-clean. No behavior change. --- litellm/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/__init__.py b/litellm/__init__.py index 7132a34e0c0..25e3ef29bf6 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -701,7 +701,7 @@ morph_models: Set = set() lambda_ai_models: Set = set() inception_models: Set = set() hyperbolic_models: Set = set() -webwright_models: Set = set() +webwright_models: set[str] = set() black_forest_labs_models: Set = set() recraft_models: Set = set() cometapi_models: Set = set() From 3fef97881925bce3f23465cf49eab88610a2e8c6 Mon Sep 17 00:00:00 2001 From: T145 Date: Thu, 15 Oct 2026 23:37:45 +0000 Subject: [PATCH 07/11] fix(llms): satisfy both gates for the webwright model set A bare `Set` annotation breaches the basedpyright delta gate (reportMissingTypeArgument, reportUnknownVariableType, reportUnknownMemberType), while a concrete `set[str]` breaches the type discipline gate (LIT001 mutable collection). Annotate it `set[str]` with a `# mutable-ok` reason so the new line passes both gates. No behavior change. --- litellm/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/__init__.py b/litellm/__init__.py index 25e3ef29bf6..10d7af73a87 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -701,7 +701,7 @@ morph_models: Set = set() lambda_ai_models: Set = set() inception_models: Set = set() hyperbolic_models: Set = set() -webwright_models: set[str] = set() +webwright_models: set[str] = set() # mutable-ok: mirrors the sibling provider model sets black_forest_labs_models: Set = set() recraft_models: Set = set() cometapi_models: Set = set() From 51aa24dc4e540ffea53aa0207c7d7ce835a3e848 Mon Sep 17 00:00:00 2001 From: T145 Date: Sat, 17 Oct 2026 08:29:20 +0000 Subject: [PATCH 08/11] fix(llms): mark the webwright audio transcription endpoint supported POST /v1/audio/transcriptions now returns 200 on the live endpoint, and litellm routes audio transcriptions through the OpenAI-compatible transport for openai_compatible_providers, which includes webwright. --- provider_endpoints_support.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index 91646aa34ff..5c8203e96d6 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -2812,7 +2812,7 @@ "responses": true, "embeddings": false, "image_generations": false, - "audio_transcriptions": false, + "audio_transcriptions": true, "audio_speech": false, "moderations": false, "batches": false, From 23ac1aaab3763b31265b670a22087d200652233a Mon Sep 17 00:00:00 2001 From: T145 Date: Sun, 18 Oct 2026 06:21:03 +0000 Subject: [PATCH 09/11] fix(webwright): align supported parameters with the published abilities --- litellm/llms/webwright/chat/transformation.py | 11 ++++------- .../public_endpoints/provider_create_fields.json | 2 +- provider_endpoints_support.json | 2 +- .../webwright/chat/test_webwright_transformation.py | 7 +++++++ 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/litellm/llms/webwright/chat/transformation.py b/litellm/llms/webwright/chat/transformation.py index b156875da2d..9f552def8eb 100644 --- a/litellm/llms/webwright/chat/transformation.py +++ b/litellm/llms/webwright/chat/transformation.py @@ -39,8 +39,9 @@ class WebwrightChatConfig(OpenAILikeChatConfig): def get_supported_openai_params(self, model: str) -> list[str]: # mutable-ok: matches the base provider signature """ - WebWright supports the standard OpenAI chat completion parameters. - Reference: https://webwright.ai/v1 + WebWright supports the OpenAI-compatible chat completion parameters that + its published model abilities reference declares. + Reference: https://webwright.ai/models """ return [ "messages", # Required @@ -49,13 +50,9 @@ class WebwrightChatConfig(OpenAILikeChatConfig): "temperature", # Optional "top_p", # Optional "max_tokens", # Optional - "frequency_penalty", # Optional - "presence_penalty", # Optional "stop", # Optional - "n", # Optional "tools", # Optional "tool_choice", # Optional "response_format", # Optional - "seed", # Optional - "user", # Optional + "reasoning_effort", # Optional ] diff --git a/litellm/proxy/public_endpoints/provider_create_fields.json b/litellm/proxy/public_endpoints/provider_create_fields.json index d6fe8adaeec..e17e081ee11 100644 --- a/litellm/proxy/public_endpoints/provider_create_fields.json +++ b/litellm/proxy/public_endpoints/provider_create_fields.json @@ -1947,7 +1947,7 @@ "default_value": null } ], - "default_model_placeholder": "gpt-3.5-turbo" + "default_model_placeholder": "wren-r1" }, { "provider": "Infinity", diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index 5c8203e96d6..91646aa34ff 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -2812,7 +2812,7 @@ "responses": true, "embeddings": false, "image_generations": false, - "audio_transcriptions": true, + "audio_transcriptions": false, "audio_speech": false, "moderations": false, "batches": false, diff --git a/tests/unit/llms/webwright/chat/test_webwright_transformation.py b/tests/unit/llms/webwright/chat/test_webwright_transformation.py index 4b272c69ecb..e1181787e83 100644 --- a/tests/unit/llms/webwright/chat/test_webwright_transformation.py +++ b/tests/unit/llms/webwright/chat/test_webwright_transformation.py @@ -80,12 +80,19 @@ def test_webwright_supported_params() -> None: "model", "stream", "temperature", + "top_p", "max_tokens", + "stop", "tools", "tool_choice", + "response_format", + "reasoning_effort", ): assert param in supported_params + for unsupported in ("frequency_penalty", "presence_penalty", "n", "seed", "user"): + assert unsupported not in supported_params + def test_webwright_completion_call() -> None: """Test the completion call structure for WebWright with a mocked response""" From 191e8633712c0f6c46da13700725632680cd7440 Mon Sep 17 00:00:00 2001 From: T145 Date: Mon, 19 Oct 2026 08:18:59 +0000 Subject: [PATCH 10/11] fix(webwright): stop advertising undocumented reasoning_effort --- litellm/llms/webwright/chat/transformation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/litellm/llms/webwright/chat/transformation.py b/litellm/llms/webwright/chat/transformation.py index 9f552def8eb..3f5c99f09c0 100644 --- a/litellm/llms/webwright/chat/transformation.py +++ b/litellm/llms/webwright/chat/transformation.py @@ -42,6 +42,8 @@ class WebwrightChatConfig(OpenAILikeChatConfig): WebWright supports the OpenAI-compatible chat completion parameters that its published model abilities reference declares. Reference: https://webwright.ai/models + Reasoning is declared in the abilities reference, but no caller-facing + reasoning field or allowed values are published, so it is not advertised. """ return [ "messages", # Required @@ -54,5 +56,4 @@ class WebwrightChatConfig(OpenAILikeChatConfig): "tools", # Optional "tool_choice", # Optional "response_format", # Optional - "reasoning_effort", # Optional ] From a7d2fccf66cbc4c9f14549e1d3812bb03f467c68 Mon Sep 17 00:00:00 2001 From: T145 Date: Mon, 19 Oct 2026 08:28:55 +0000 Subject: [PATCH 11/11] test(webwright): expect reasoning_effort to be unsupported --- .../unit/llms/webwright/chat/test_webwright_transformation.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit/llms/webwright/chat/test_webwright_transformation.py b/tests/unit/llms/webwright/chat/test_webwright_transformation.py index e1181787e83..468230d2dab 100644 --- a/tests/unit/llms/webwright/chat/test_webwright_transformation.py +++ b/tests/unit/llms/webwright/chat/test_webwright_transformation.py @@ -86,11 +86,10 @@ def test_webwright_supported_params() -> None: "tools", "tool_choice", "response_format", - "reasoning_effort", ): assert param in supported_params - for unsupported in ("frequency_penalty", "presence_penalty", "n", "seed", "user"): + for unsupported in ("frequency_penalty", "presence_penalty", "n", "seed", "user", "reasoning_effort"): assert unsupported not in supported_params