From 1e64174fcebd23be87fc354aab47062f92bb1247 Mon Sep 17 00:00:00 2001 From: devarakondasrikanth Date: Sun, 12 Apr 2026 09:38:05 -0700 Subject: [PATCH] feat(vercel_ai_gateway): convert reasoning_effort to Vercel AI Gateway extra_body format Automatically maps reasoning_effort to extra_body.reasoning = {effort, enabled: true} as required by Vercel AI Gateway. Adds reasoning_effort to supported params, 2 unit tests, and provider documentation. Fixes #25510 Signed-off-by: devarakondasrikanth --- .../docs/providers/vercel_ai_gateway.md | 30 ++++++ .../vercel_ai_gateway/chat/transformation.py | 16 ++- .../test_vercel_ai_gateway_transformation.py | 99 ++++++++++++++----- 3 files changed, 114 insertions(+), 31 deletions(-) diff --git a/docs/my-website/docs/providers/vercel_ai_gateway.md b/docs/my-website/docs/providers/vercel_ai_gateway.md index 3ff007171ed..6d17d49a129 100644 --- a/docs/my-website/docs/providers/vercel_ai_gateway.md +++ b/docs/my-website/docs/providers/vercel_ai_gateway.md @@ -246,6 +246,36 @@ curl http://localhost:4000/v1/chat/completions \ For more detailed information on using the LiteLLM Proxy, see the [LiteLLM Proxy documentation](../providers/litellm_proxy). +## Reasoning Effort + +LiteLLM automatically converts the standard `reasoning_effort` parameter into the format required by Vercel AI Gateway. + +**Input (standard LiteLLM format):** +```python showLineNumbers title="reasoning_effort with Vercel AI Gateway" +import litellm + +response = litellm.completion( + model="vercel_ai_gateway/anthropic/claude-sonnet-4-5", + messages=[{"role": "user", "content": "Explain quantum entanglement"}], + reasoning_effort="high" +) +``` + +LiteLLM converts this to the Vercel AI Gateway `extra_body` format automatically: + +```json +{ + "extra_body": { + "reasoning": { + "effort": "high", + "enabled": true + } + } +} +``` + +Supported values for `reasoning_effort`: `"low"`, `"medium"`, `"high"`. + ## Additional Resources - [Vercel AI Gateway Documentation](https://vercel.com/docs/ai-gateway) diff --git a/litellm/llms/vercel_ai_gateway/chat/transformation.py b/litellm/llms/vercel_ai_gateway/chat/transformation.py index 81a1688b909..4a01ece40d9 100644 --- a/litellm/llms/vercel_ai_gateway/chat/transformation.py +++ b/litellm/llms/vercel_ai_gateway/chat/transformation.py @@ -28,6 +28,8 @@ class VercelAIGatewayConfig(OpenAIGPTConfig): base_params = super().get_supported_openai_params(model) if "extra_body" not in base_params: base_params.append("extra_body") + if "reasoning_effort" not in base_params: + base_params.append("reasoning_effort") return base_params def _get_openai_compatible_provider_info( @@ -56,16 +58,20 @@ class VercelAIGatewayConfig(OpenAIGPTConfig): non_default_params, optional_params, model, drop_params ) - # Vercel AI Gateway-only parameters - extra_body = {} + # Vercel AI Gateway-only parameters — seed from any extra_body the super call already set + extra_body = mapped_openai_params.pop("extra_body", {}) provider_options = non_default_params.pop("providerOptions", None) if provider_options is not None: extra_body["providerOptions"] = provider_options - mapped_openai_params[ - "extra_body" - ] = extra_body # openai client supports `extra_body` param + reasoning_effort = mapped_openai_params.pop("reasoning_effort", None) + if reasoning_effort is not None: + extra_body["reasoning"] = {"effort": reasoning_effort, "enabled": True} + + mapped_openai_params["extra_body"] = ( + extra_body # openai client supports `extra_body` param + ) return mapped_openai_params def transform_request( diff --git a/tests/test_litellm/llms/vercel_ai_gateway/chat/test_vercel_ai_gateway_transformation.py b/tests/test_litellm/llms/vercel_ai_gateway/chat/test_vercel_ai_gateway_transformation.py index 2121473d95c..15d6eb7fb15 100644 --- a/tests/test_litellm/llms/vercel_ai_gateway/chat/test_vercel_ai_gateway_transformation.py +++ b/tests/test_litellm/llms/vercel_ai_gateway/chat/test_vercel_ai_gateway_transformation.py @@ -2,8 +2,6 @@ import os import sys from unittest.mock import patch -import pytest - sys.path.insert( 0, os.path.abspath("../../../../..") ) # Adds the parent directory to the system path @@ -21,16 +19,17 @@ def test_vercel_ai_gateway_extra_body_transformation(): messages=[{"role": "user", "content": "Hello, world!"}], optional_params={ "extra_body": { - "providerOptions": { - "gateway": {"order": ["azure", "openai"]} - } + "providerOptions": {"gateway": {"order": ["azure", "openai"]}} } }, litellm_params={}, headers={}, ) - assert transformed_request["extra_body"]["providerOptions"]["gateway"]["order"] == ["azure", "openai"] + assert transformed_request["extra_body"]["providerOptions"]["gateway"]["order"] == [ + "azure", + "openai", + ] assert transformed_request["messages"] == [ {"role": "user", "content": "Hello, world!"} ] @@ -39,28 +38,31 @@ def test_vercel_ai_gateway_extra_body_transformation(): def test_vercel_ai_gateway_provider_options_mapping(): """Test that providerOptions from non_default_params is moved to extra_body""" config = VercelAIGatewayConfig() - + non_default_params = { - "providerOptions": { - "gateway": {"order": ["azure", "openai"]} - } + "providerOptions": {"gateway": {"order": ["azure", "openai"]}} } optional_params = {} model = "vercel_ai_gateway/openai/gpt-4o" - + result = config.map_openai_params( non_default_params, optional_params, model, drop_params=False ) - - assert result["extra_body"]["providerOptions"]["gateway"]["order"] == ["azure", "openai"] + + assert result["extra_body"]["providerOptions"]["gateway"]["order"] == [ + "azure", + "openai", + ] assert "providerOptions" not in result def test_vercel_ai_gateway_get_supported_openai_params(): """Test that extra_body is included in supported params""" config = VercelAIGatewayConfig() - supported_params = config.get_supported_openai_params("vercel_ai_gateway/openai/gpt-4o") - + supported_params = config.get_supported_openai_params( + "vercel_ai_gateway/openai/gpt-4o" + ) + assert "extra_body" in supported_params assert "temperature" in supported_params assert "max_tokens" in supported_params @@ -70,7 +72,7 @@ def test_vercel_ai_gateway_get_supported_openai_params(): def test_vercel_ai_gateway_get_openai_compatible_provider_info(): """Test provider info retrieval with environment variables""" config = VercelAIGatewayConfig() - + with patch.dict( "os.environ", { @@ -86,27 +88,72 @@ def test_vercel_ai_gateway_get_openai_compatible_provider_info(): def test_vercel_ai_gateway_error_class(): """Test error class creation""" config = VercelAIGatewayConfig() - + error_message = "Test error" status_code = 400 headers = {"Content-Type": "application/json"} - + error_class = config.get_error_class(error_message, status_code, headers) - + assert isinstance(error_class, VercelAIGatewayException) assert error_class.message == error_message assert error_class.status_code == status_code assert error_class.headers == headers +def test_vercel_ai_gateway_reasoning_effort_mapping(): + """Test that reasoning_effort is converted to extra_body.reasoning format""" + config = VercelAIGatewayConfig() + + non_default_params = {"reasoning_effort": "high"} + optional_params = {} + model = "vercel_ai_gateway/openai/gpt-4o" + + result = config.map_openai_params( + non_default_params, optional_params, model, drop_params=False + ) + + assert result["extra_body"]["reasoning"] == {"effort": "high", "enabled": True} + assert "reasoning_effort" not in result + + +def test_vercel_ai_gateway_reasoning_effort_with_existing_extra_body(): + """Test that reasoning_effort merges with existing extra_body instead of overwriting it""" + config = VercelAIGatewayConfig() + + non_default_params = { + "reasoning_effort": "high", + "providerOptions": {"gateway": {"order": ["azure", "openai"]}}, + } + optional_params = {} + model = "vercel_ai_gateway/openai/gpt-4o" + + result = config.map_openai_params( + non_default_params, optional_params, model, drop_params=False + ) + + # Both keys must coexist in extra_body — neither should be silently dropped + assert result["extra_body"]["reasoning"] == {"effort": "high", "enabled": True} + assert result["extra_body"]["providerOptions"] == { + "gateway": {"order": ["azure", "openai"]} + } + assert "reasoning_effort" not in result + + +def test_vercel_ai_gateway_reasoning_effort_in_supported_params(): + """Test that reasoning_effort is included in supported params""" + config = VercelAIGatewayConfig() + supported_params = config.get_supported_openai_params( + "vercel_ai_gateway/openai/gpt-4o" + ) + + assert "reasoning_effort" in supported_params + + def test_vercel_ai_gateway_exception_inheritance(): """Test that VercelAIGatewayException inherits from BaseLLMException""" from litellm.llms.base_llm.chat.transformation import BaseLLMException - - exception = VercelAIGatewayException( - message="test", - status_code=500, - headers={} - ) - + + exception = VercelAIGatewayException(message="test", status_code=500, headers={}) + assert isinstance(exception, BaseLLMException)