From 7d21770d995f708a3e299bf0ac0b11c3c29f09e0 Mon Sep 17 00:00:00 2001 From: giulio-leone Date: Sun, 1 Mar 2026 16:41:50 +0100 Subject: [PATCH 1/3] fix(featherless_ai): use correct FEATHERLESS_AI_API_KEY env var name The transformation.py file was using FEATHERLESS_API_KEY (missing _AI_) while the rest of the codebase (get_llm_provider_logic.py, utils.py) correctly uses FEATHERLESS_AI_API_KEY. This caused 401 auth errors when the user set FEATHERLESS_AI_API_KEY as documented. Now checks FEATHERLESS_AI_API_KEY first (canonical name) with fallback to FEATHERLESS_API_KEY (legacy compatibility). Same fix applied to FEATHERLESS_AI_API_BASE. Refs: #22490 --- .../featherless_ai/chat/transformation.py | 7 +++- .../test_featherless_chat_transformation.py | 32 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/litellm/llms/featherless_ai/chat/transformation.py b/litellm/llms/featherless_ai/chat/transformation.py index 96702cf886..e62108624d 100644 --- a/litellm/llms/featherless_ai/chat/transformation.py +++ b/litellm/llms/featherless_ai/chat/transformation.py @@ -103,10 +103,15 @@ class FeatherlessAIConfig(OpenAIGPTConfig): # FeatherlessAI is openai compatible, set to custom_openai and use FeatherlessAI's endpoint api_base = ( api_base + or get_secret_str("FEATHERLESS_AI_API_BASE") or get_secret_str("FEATHERLESS_API_BASE") or "https://api.featherless.ai/v1" ) - dynamic_api_key = api_key or get_secret_str("FEATHERLESS_API_KEY") + dynamic_api_key = ( + api_key + or get_secret_str("FEATHERLESS_AI_API_KEY") + or get_secret_str("FEATHERLESS_API_KEY") + ) return api_base, dynamic_api_key def validate_environment( diff --git a/tests/test_litellm/llms/featherless_ai/chat/test_featherless_chat_transformation.py b/tests/test_litellm/llms/featherless_ai/chat/test_featherless_chat_transformation.py index b4ef78b913..666cfbd948 100644 --- a/tests/test_litellm/llms/featherless_ai/chat/test_featherless_chat_transformation.py +++ b/tests/test_litellm/llms/featherless_ai/chat/test_featherless_chat_transformation.py @@ -149,6 +149,38 @@ class TestFeatherlessAIConfig: ) assert "Featherless AI doesn't support tools=" in str(excinfo.value) + def test_get_provider_info_with_featherless_ai_api_key(self): + """Test that FEATHERLESS_AI_API_KEY env var is picked up correctly""" + config = FeatherlessAIConfig() + with patch.dict(os.environ, {"FEATHERLESS_AI_API_KEY": "key-from-ai-env"}, clear=False): + api_base, api_key = config._get_openai_compatible_provider_info( + api_base=None, api_key=None + ) + assert api_key == "key-from-ai-env" + assert api_base == "https://api.featherless.ai/v1" + + def test_get_provider_info_with_legacy_featherless_api_key(self): + """Test that legacy FEATHERLESS_API_KEY env var still works""" + config = FeatherlessAIConfig() + with patch.dict(os.environ, {"FEATHERLESS_API_KEY": "key-from-legacy-env"}, clear=False): + api_base, api_key = config._get_openai_compatible_provider_info( + api_base=None, api_key=None + ) + assert api_key == "key-from-legacy-env" + assert api_base == "https://api.featherless.ai/v1" + + def test_get_provider_info_prefers_featherless_ai_key_over_legacy(self): + """Test that FEATHERLESS_AI_API_KEY takes precedence over FEATHERLESS_API_KEY""" + config = FeatherlessAIConfig() + with patch.dict(os.environ, { + "FEATHERLESS_AI_API_KEY": "preferred-key", + "FEATHERLESS_API_KEY": "legacy-key", + }, clear=False): + _, api_key = config._get_openai_compatible_provider_info( + api_base=None, api_key=None + ) + assert api_key == "preferred-key" + def test_default_api_base(self): """Test that default API base is used when none is provided""" config = FeatherlessAIConfig() From 7934810e21b734c247d4934e663281611c40022e Mon Sep 17 00:00:00 2001 From: giulio-leone Date: Sun, 1 Mar 2026 17:26:16 +0100 Subject: [PATCH 2/3] fix(tests): isolate env vars in featherless AI tests Use clear=True with patch.dict to prevent pre-set FEATHERLESS_* env vars from leaking into tests and causing false results. Refs: #22497 --- .../chat/test_featherless_chat_transformation.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/test_litellm/llms/featherless_ai/chat/test_featherless_chat_transformation.py b/tests/test_litellm/llms/featherless_ai/chat/test_featherless_chat_transformation.py index 666cfbd948..52d654c968 100644 --- a/tests/test_litellm/llms/featherless_ai/chat/test_featherless_chat_transformation.py +++ b/tests/test_litellm/llms/featherless_ai/chat/test_featherless_chat_transformation.py @@ -152,7 +152,9 @@ class TestFeatherlessAIConfig: def test_get_provider_info_with_featherless_ai_api_key(self): """Test that FEATHERLESS_AI_API_KEY env var is picked up correctly""" config = FeatherlessAIConfig() - with patch.dict(os.environ, {"FEATHERLESS_AI_API_KEY": "key-from-ai-env"}, clear=False): + env = {k: v for k, v in os.environ.items() if not k.startswith("FEATHERLESS")} + env["FEATHERLESS_AI_API_KEY"] = "key-from-ai-env" + with patch.dict(os.environ, env, clear=True): api_base, api_key = config._get_openai_compatible_provider_info( api_base=None, api_key=None ) @@ -162,7 +164,9 @@ class TestFeatherlessAIConfig: def test_get_provider_info_with_legacy_featherless_api_key(self): """Test that legacy FEATHERLESS_API_KEY env var still works""" config = FeatherlessAIConfig() - with patch.dict(os.environ, {"FEATHERLESS_API_KEY": "key-from-legacy-env"}, clear=False): + env = {k: v for k, v in os.environ.items() if not k.startswith("FEATHERLESS")} + env["FEATHERLESS_API_KEY"] = "key-from-legacy-env" + with patch.dict(os.environ, env, clear=True): api_base, api_key = config._get_openai_compatible_provider_info( api_base=None, api_key=None ) @@ -172,10 +176,10 @@ class TestFeatherlessAIConfig: def test_get_provider_info_prefers_featherless_ai_key_over_legacy(self): """Test that FEATHERLESS_AI_API_KEY takes precedence over FEATHERLESS_API_KEY""" config = FeatherlessAIConfig() - with patch.dict(os.environ, { - "FEATHERLESS_AI_API_KEY": "preferred-key", - "FEATHERLESS_API_KEY": "legacy-key", - }, clear=False): + env = {k: v for k, v in os.environ.items() if not k.startswith("FEATHERLESS")} + env["FEATHERLESS_AI_API_KEY"] = "preferred-key" + env["FEATHERLESS_API_KEY"] = "legacy-key" + with patch.dict(os.environ, env, clear=True): _, api_key = config._get_openai_compatible_provider_info( api_base=None, api_key=None ) From a8adbee6c9de5e3e544f2947d0fb0547817ca463 Mon Sep 17 00:00:00 2001 From: giulio-leone Date: Sun, 1 Mar 2026 23:46:15 +0100 Subject: [PATCH 3/3] fix(tests): use monkeypatch for env var isolation in featherless tests Replace patch.dict with pytest monkeypatch for idiomatic env var isolation. Remove unused typing and unittest.mock imports (ruff F401). Refs: #22497 --- .../test_featherless_chat_transformation.py | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/tests/test_litellm/llms/featherless_ai/chat/test_featherless_chat_transformation.py b/tests/test_litellm/llms/featherless_ai/chat/test_featherless_chat_transformation.py index 52d654c968..a1240705fd 100644 --- a/tests/test_litellm/llms/featherless_ai/chat/test_featherless_chat_transformation.py +++ b/tests/test_litellm/llms/featherless_ai/chat/test_featherless_chat_transformation.py @@ -7,8 +7,6 @@ Featherless AI is an OpenAI-compatible provider with a few customizations. import os import sys -from typing import Dict, List, Optional -from unittest.mock import patch import pytest @@ -149,40 +147,43 @@ class TestFeatherlessAIConfig: ) assert "Featherless AI doesn't support tools=" in str(excinfo.value) - def test_get_provider_info_with_featherless_ai_api_key(self): + def test_get_provider_info_with_featherless_ai_api_key(self, monkeypatch): """Test that FEATHERLESS_AI_API_KEY env var is picked up correctly""" config = FeatherlessAIConfig() - env = {k: v for k, v in os.environ.items() if not k.startswith("FEATHERLESS")} - env["FEATHERLESS_AI_API_KEY"] = "key-from-ai-env" - with patch.dict(os.environ, env, clear=True): - api_base, api_key = config._get_openai_compatible_provider_info( - api_base=None, api_key=None - ) + for key in ("FEATHERLESS_AI_API_KEY", "FEATHERLESS_API_KEY", + "FEATHERLESS_AI_API_BASE", "FEATHERLESS_API_BASE"): + monkeypatch.delenv(key, raising=False) + monkeypatch.setenv("FEATHERLESS_AI_API_KEY", "key-from-ai-env") + api_base, api_key = config._get_openai_compatible_provider_info( + api_base=None, api_key=None + ) assert api_key == "key-from-ai-env" assert api_base == "https://api.featherless.ai/v1" - def test_get_provider_info_with_legacy_featherless_api_key(self): + def test_get_provider_info_with_legacy_featherless_api_key(self, monkeypatch): """Test that legacy FEATHERLESS_API_KEY env var still works""" config = FeatherlessAIConfig() - env = {k: v for k, v in os.environ.items() if not k.startswith("FEATHERLESS")} - env["FEATHERLESS_API_KEY"] = "key-from-legacy-env" - with patch.dict(os.environ, env, clear=True): - api_base, api_key = config._get_openai_compatible_provider_info( - api_base=None, api_key=None - ) + for key in ("FEATHERLESS_AI_API_KEY", "FEATHERLESS_API_KEY", + "FEATHERLESS_AI_API_BASE", "FEATHERLESS_API_BASE"): + monkeypatch.delenv(key, raising=False) + monkeypatch.setenv("FEATHERLESS_API_KEY", "key-from-legacy-env") + api_base, api_key = config._get_openai_compatible_provider_info( + api_base=None, api_key=None + ) assert api_key == "key-from-legacy-env" assert api_base == "https://api.featherless.ai/v1" - def test_get_provider_info_prefers_featherless_ai_key_over_legacy(self): + def test_get_provider_info_prefers_featherless_ai_key_over_legacy(self, monkeypatch): """Test that FEATHERLESS_AI_API_KEY takes precedence over FEATHERLESS_API_KEY""" config = FeatherlessAIConfig() - env = {k: v for k, v in os.environ.items() if not k.startswith("FEATHERLESS")} - env["FEATHERLESS_AI_API_KEY"] = "preferred-key" - env["FEATHERLESS_API_KEY"] = "legacy-key" - with patch.dict(os.environ, env, clear=True): - _, api_key = config._get_openai_compatible_provider_info( - api_base=None, api_key=None - ) + for key in ("FEATHERLESS_AI_API_KEY", "FEATHERLESS_API_KEY", + "FEATHERLESS_AI_API_BASE", "FEATHERLESS_API_BASE"): + monkeypatch.delenv(key, raising=False) + monkeypatch.setenv("FEATHERLESS_AI_API_KEY", "preferred-key") + monkeypatch.setenv("FEATHERLESS_API_KEY", "legacy-key") + _, api_key = config._get_openai_compatible_provider_info( + api_base=None, api_key=None + ) assert api_key == "preferred-key" def test_default_api_base(self):