mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-24 05:45:11 +00:00
Merge 09b91accbc into 24123269cc
This commit is contained in:
commit
eb84c6835c
6 changed files with 160 additions and 36 deletions
|
|
@ -94,6 +94,7 @@ def create_config_class(provider: SimpleProviderConfig):
|
|||
from litellm.utils import supports_function_calling, supports_reasoning
|
||||
|
||||
supported_params = super().get_supported_openai_params(model=model)
|
||||
supported_params = list(dict.fromkeys([*supported_params, *provider.extra_supported_params]))
|
||||
|
||||
_supports_fc = supports_function_calling(model=model, custom_llm_provider=provider.slug)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class SimpleProviderConfig:
|
|||
self.api_base_env = data.get("api_base_env")
|
||||
self.base_class = data.get("base_class", "openai_gpt")
|
||||
self.param_mappings = data.get("param_mappings", {})
|
||||
self.extra_supported_params = data.get("extra_supported_params", [])
|
||||
self.constraints = data.get("constraints", {})
|
||||
self.special_handling = data.get("special_handling", {})
|
||||
self.supported_endpoints = data.get("supported_endpoints", [])
|
||||
|
|
|
|||
|
|
@ -183,5 +183,12 @@
|
|||
"max_completion_tokens": "max_tokens"
|
||||
},
|
||||
"supported_endpoints": ["/v1/chat/completions", "/v1/responses", "/v1/embeddings"]
|
||||
},
|
||||
"app_nz": {
|
||||
"base_url": "https://app.nz/v1",
|
||||
"api_key_env": "APP_NZ_API_KEY",
|
||||
"api_base_env": "APP_NZ_API_BASE",
|
||||
"base_class": "openai_gpt",
|
||||
"extra_supported_params": ["reasoning_effort"]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,6 +177,24 @@
|
|||
"interactions": true
|
||||
}
|
||||
},
|
||||
"app_nz": {
|
||||
"display_name": "app.nz (`app_nz`)",
|
||||
"url": "https://docs.litellm.ai/docs/providers/app_nz",
|
||||
"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": true,
|
||||
"interactions": true
|
||||
}
|
||||
},
|
||||
"apertis": {
|
||||
"display_name": "Apertis (`apertis`)",
|
||||
"endpoints": {
|
||||
|
|
|
|||
106
tests/test_litellm/llms/openai_like/test_appnz_provider.py
Normal file
106
tests/test_litellm/llms/openai_like/test_appnz_provider.py
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
"""
|
||||
Tests for app.nz provider configuration and resolution.
|
||||
"""
|
||||
|
||||
import litellm
|
||||
|
||||
|
||||
class TestAppNZProviderConfig:
|
||||
"""Test app.nz JSON-configured provider"""
|
||||
|
||||
def test_appnz_json_config_exists(self):
|
||||
"""app_nz is registered in providers.json with the expected shape"""
|
||||
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
|
||||
|
||||
assert JSONProviderRegistry.exists("app_nz")
|
||||
|
||||
app_nz = JSONProviderRegistry.get("app_nz")
|
||||
assert app_nz is not None
|
||||
assert app_nz.base_url == "https://app.nz/v1"
|
||||
assert app_nz.api_key_env == "APP_NZ_API_KEY"
|
||||
assert app_nz.api_base_env == "APP_NZ_API_BASE"
|
||||
assert app_nz.base_class == "openai_gpt"
|
||||
assert app_nz.extra_supported_params == ["reasoning_effort"]
|
||||
|
||||
def test_appnz_provider_resolution(self):
|
||||
"""Resolving an app_nz/* model returns the default app.nz base URL"""
|
||||
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
|
||||
|
||||
model, provider, api_key, api_base = get_llm_provider(
|
||||
model="app_nz/app/auto",
|
||||
custom_llm_provider=None,
|
||||
api_base=None,
|
||||
api_key=None,
|
||||
)
|
||||
|
||||
assert model == "app/auto"
|
||||
assert provider == "app_nz"
|
||||
assert api_base == "https://app.nz/v1"
|
||||
|
||||
def test_appnz_api_base_override(self):
|
||||
"""An explicit api_base / api_key overrides the app.nz defaults"""
|
||||
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
|
||||
|
||||
model, provider, api_key, api_base = get_llm_provider(
|
||||
model="app_nz/app/auto-code",
|
||||
custom_llm_provider=None,
|
||||
api_base="https://custom.example.com/v1",
|
||||
api_key="app_live_test",
|
||||
)
|
||||
|
||||
assert provider == "app_nz"
|
||||
assert api_base == "https://custom.example.com/v1"
|
||||
assert api_key == "app_live_test"
|
||||
|
||||
def test_appnz_dynamic_config(self):
|
||||
"""The generated dynamic config resolves to the app.nz base URL"""
|
||||
from litellm.llms.openai_like.dynamic_config import create_config_class
|
||||
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
|
||||
|
||||
provider = JSONProviderRegistry.get("app_nz")
|
||||
config_class = create_config_class(provider)
|
||||
config = config_class()
|
||||
|
||||
api_base, _ = config._get_openai_compatible_provider_info(None, None)
|
||||
assert api_base == "https://app.nz/v1"
|
||||
|
||||
def test_appnz_complete_url_appends_endpoint(self):
|
||||
"""get_complete_url appends the chat completions path to the app.nz base"""
|
||||
from litellm.llms.openai_like.dynamic_config import create_config_class
|
||||
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
|
||||
|
||||
provider = JSONProviderRegistry.get("app_nz")
|
||||
config_class = create_config_class(provider)
|
||||
config = config_class()
|
||||
|
||||
url = config.get_complete_url(
|
||||
api_base="https://app.nz/v1",
|
||||
api_key="app_live_test",
|
||||
model="app_nz/app/auto",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
stream=False,
|
||||
)
|
||||
|
||||
assert url == "https://app.nz/v1/chat/completions"
|
||||
|
||||
def test_appnz_supports_reasoning_effort(self):
|
||||
"""app.nz forwards its documented reasoning_effort parameter"""
|
||||
from litellm.llms.openai_like.dynamic_config import create_config_class
|
||||
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
|
||||
|
||||
provider = JSONProviderRegistry.get("app_nz")
|
||||
config_class = create_config_class(provider)
|
||||
config = config_class()
|
||||
|
||||
supported_params = config.get_supported_openai_params(model="app/auto")
|
||||
assert "reasoning_effort" in supported_params
|
||||
|
||||
optional_params = config.map_openai_params(
|
||||
non_default_params={"reasoning_effort": "auto"},
|
||||
optional_params={},
|
||||
model="app/auto",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
assert optional_params["reasoning_effort"] == "auto"
|
||||
|
|
@ -52,9 +52,7 @@ class TestJSONProviderLoader:
|
|||
assert api_base == "https://api.publicai.co/v1"
|
||||
|
||||
# Test with custom base
|
||||
api_base, api_key = config._get_openai_compatible_provider_info(
|
||||
"https://custom.api.com", "test-key"
|
||||
)
|
||||
api_base, api_key = config._get_openai_compatible_provider_info("https://custom.api.com", "test-key")
|
||||
assert api_base == "https://custom.api.com"
|
||||
assert api_key == "test-key"
|
||||
|
||||
|
|
@ -70,9 +68,7 @@ class TestJSONProviderLoader:
|
|||
# Test parameter mapping
|
||||
optional_params = {}
|
||||
non_default_params = {"max_completion_tokens": 100, "temperature": 0.7}
|
||||
result = config.map_openai_params(
|
||||
non_default_params, optional_params, "gpt-4", False
|
||||
)
|
||||
result = config.map_openai_params(non_default_params, optional_params, "gpt-4", False)
|
||||
|
||||
# max_completion_tokens should be mapped to max_tokens
|
||||
assert "max_tokens" in result
|
||||
|
|
@ -98,6 +94,19 @@ class TestJSONProviderLoader:
|
|||
assert isinstance(supported, list)
|
||||
assert len(supported) > 0
|
||||
|
||||
def test_extra_supported_params(self):
|
||||
"""Test JSON providers can extend supported OpenAI params"""
|
||||
from litellm.llms.openai_like.dynamic_config import create_config_class
|
||||
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
|
||||
|
||||
provider = JSONProviderRegistry.get("app_nz")
|
||||
config_class = create_config_class(provider)
|
||||
config = config_class()
|
||||
|
||||
supported = config.get_supported_openai_params("app/auto")
|
||||
|
||||
assert "reasoning_effort" in supported
|
||||
|
||||
def test_tool_params_excluded_when_function_calling_not_supported(self):
|
||||
"""Test that tool-related params are excluded for models that don't support
|
||||
function calling. Regression test for https://github.com/BerriAI/litellm/issues/21125
|
||||
|
|
@ -121,9 +130,9 @@ class TestJSONProviderLoader:
|
|||
"parallel_tool_calls",
|
||||
]
|
||||
for param in tool_params:
|
||||
assert (
|
||||
param not in supported
|
||||
), f"'{param}' should not be in supported params when function calling is not supported"
|
||||
assert param not in supported, (
|
||||
f"'{param}' should not be in supported params when function calling is not supported"
|
||||
)
|
||||
|
||||
# Non-tool params should still be present
|
||||
assert "temperature" in supported
|
||||
|
|
@ -168,9 +177,7 @@ class TestJSONProviderLoader:
|
|||
from litellm import LlmProviders
|
||||
from litellm.utils import ProviderConfigManager
|
||||
|
||||
config = ProviderConfigManager.get_provider_chat_config(
|
||||
model="gpt-4", provider=LlmProviders.PUBLICAI
|
||||
)
|
||||
config = ProviderConfigManager.get_provider_chat_config(model="gpt-4", provider=LlmProviders.PUBLICAI)
|
||||
|
||||
assert config is not None
|
||||
assert config.custom_llm_provider == "publicai"
|
||||
|
|
@ -218,9 +225,7 @@ class TestPinstripes:
|
|||
api_base, api_key = config._get_openai_compatible_provider_info(None, None)
|
||||
assert api_base == "https://pinstripes.io/v1"
|
||||
|
||||
api_base, api_key = config._get_openai_compatible_provider_info(
|
||||
"https://custom.pinstripes.io/v1", "test-key"
|
||||
)
|
||||
api_base, api_key = config._get_openai_compatible_provider_info("https://custom.pinstripes.io/v1", "test-key")
|
||||
assert api_base == "https://custom.pinstripes.io/v1"
|
||||
assert api_key == "test-key"
|
||||
|
||||
|
|
@ -235,9 +240,7 @@ class TestPinstripes:
|
|||
|
||||
optional_params = {}
|
||||
non_default_params = {"max_completion_tokens": 100, "temperature": 0.7}
|
||||
result = config.map_openai_params(
|
||||
non_default_params, optional_params, "ps/glm-4.5-air", False
|
||||
)
|
||||
result = config.map_openai_params(non_default_params, optional_params, "ps/glm-4.5-air", False)
|
||||
|
||||
assert "max_tokens" in result
|
||||
assert result["max_tokens"] == 100
|
||||
|
|
@ -282,9 +285,7 @@ class TestDarkbloom:
|
|||
api_base, api_key = config._get_openai_compatible_provider_info(None, None)
|
||||
assert api_base == "https://api.darkbloom.dev/v1"
|
||||
|
||||
api_base, api_key = config._get_openai_compatible_provider_info(
|
||||
"https://custom.darkbloom.dev/v1", "test-key"
|
||||
)
|
||||
api_base, api_key = config._get_openai_compatible_provider_info("https://custom.darkbloom.dev/v1", "test-key")
|
||||
assert api_base == "https://custom.darkbloom.dev/v1"
|
||||
assert api_key == "test-key"
|
||||
|
||||
|
|
@ -311,17 +312,13 @@ class TestDarkbloom:
|
|||
from litellm import LlmProviders
|
||||
from litellm.utils import ProviderConfigManager
|
||||
|
||||
config = ProviderConfigManager.get_provider_chat_config(
|
||||
model="gemma-4-26b", provider=LlmProviders.DARKBLOOM
|
||||
)
|
||||
config = ProviderConfigManager.get_provider_chat_config(model="gemma-4-26b", provider=LlmProviders.DARKBLOOM)
|
||||
|
||||
assert config is not None
|
||||
assert config.custom_llm_provider == "darkbloom"
|
||||
|
||||
def test_darkbloom_model_cost_map(self):
|
||||
with open(
|
||||
os.path.join(workspace_path, "model_prices_and_context_window.json")
|
||||
) as f:
|
||||
with open(os.path.join(workspace_path, "model_prices_and_context_window.json")) as f:
|
||||
model_cost = json.load(f)
|
||||
|
||||
expected_models = {
|
||||
|
|
@ -373,9 +370,7 @@ class TestPublicAIIntegration:
|
|||
content = response.choices[0].message.content.lower()
|
||||
assert len(content) > 0
|
||||
|
||||
print(
|
||||
f"✓ PublicAI completion successful: {response.choices[0].message.content}"
|
||||
)
|
||||
print(f"✓ PublicAI completion successful: {response.choices[0].message.content}")
|
||||
|
||||
except Exception as e:
|
||||
if pytest:
|
||||
|
|
@ -403,9 +398,7 @@ class TestPublicAIIntegration:
|
|||
chunks = []
|
||||
for chunk in response:
|
||||
assert chunk is not None
|
||||
if hasattr(chunk.choices[0], "delta") and hasattr(
|
||||
chunk.choices[0].delta, "content"
|
||||
):
|
||||
if hasattr(chunk.choices[0], "delta") and hasattr(chunk.choices[0].delta, "content"):
|
||||
if chunk.choices[0].delta.content:
|
||||
chunks.append(chunk.choices[0].delta.content)
|
||||
|
||||
|
|
@ -461,9 +454,7 @@ class TestPublicAIIntegration:
|
|||
# Send message with content as list (should be converted to string)
|
||||
response = litellm.completion(
|
||||
model="publicai/swiss-ai/apertus-8b-instruct",
|
||||
messages=[
|
||||
{"role": "user", "content": [{"type": "text", "text": "Say hello"}]}
|
||||
],
|
||||
messages=[{"role": "user", "content": [{"type": "text", "text": "Say hello"}]}],
|
||||
max_tokens=10,
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue