mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
Merge a7d2fccf66 into 40be7fa1a8
This commit is contained in:
commit
a7c0de8e1c
16 changed files with 237 additions and 0 deletions
1
.github/workflows/test-unit.yml
vendored
1
.github/workflows/test-unit.yml
vendored
|
|
@ -384,6 +384,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
|
||||
|
|
|
|||
|
|
@ -473,6 +473,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) | | | | ✅ | | | | | | |
|
||||
| [ZeroGPU (`zerogpu`)](https://docs.litellm.ai/docs/providers/zerogpu) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
|
|
|
|||
|
|
@ -696,6 +696,7 @@ opencode_go_models: Final[set[str]] = set() # mutable-ok: populated when the sh
|
|||
lambda_ai_models: Set = set()
|
||||
inception_models: Set = set()
|
||||
hyperbolic_models: Set = 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()
|
||||
|
|
@ -1001,6 +1002,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":
|
||||
|
|
@ -1310,6 +1313,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,
|
||||
|
|
@ -2305,6 +2309,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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -359,6 +359,7 @@ LLM_CONFIG_NAMES: Final = (
|
|||
"LambdaAIChatConfig",
|
||||
"InceptionChatConfig",
|
||||
"HyperbolicChatConfig",
|
||||
"WebwrightChatConfig",
|
||||
"VercelAIGatewayConfig",
|
||||
"EdenAIChatConfig",
|
||||
"EdenAIResponsesAPIConfig",
|
||||
|
|
@ -1325,6 +1326,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",
|
||||
|
|
|
|||
|
|
@ -1078,6 +1078,7 @@ openai_compatible_endpoints: Final[list[str]] = [
|
|||
"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",
|
||||
|
|
@ -1160,6 +1161,7 @@ openai_compatible_providers: Final[list[str]] = [
|
|||
"lambda_ai",
|
||||
"inception",
|
||||
"hyperbolic",
|
||||
"webwright",
|
||||
"vercel_ai_gateway",
|
||||
"aiml",
|
||||
"edenai",
|
||||
|
|
@ -1209,6 +1211,7 @@ openai_text_completion_compatible_providers: Final[list] = [ # providers that s
|
|||
"v0",
|
||||
"lambda_ai",
|
||||
"hyperbolic",
|
||||
"webwright",
|
||||
"wandb",
|
||||
]
|
||||
_openai_like_providers: Final[list[str]] = [
|
||||
|
|
|
|||
|
|
@ -412,6 +412,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" # 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"
|
||||
dynamic_api_key = get_secret_str("VERCEL_AI_GATEWAY_API_KEY")
|
||||
|
|
@ -951,6 +954,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, # 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":
|
||||
(
|
||||
api_base,
|
||||
|
|
|
|||
0
litellm/llms/webwright/__init__.py
Normal file
0
litellm/llms/webwright/__init__.py
Normal file
0
litellm/llms/webwright/chat/__init__.py
Normal file
0
litellm/llms/webwright/chat/__init__.py
Normal file
59
litellm/llms/webwright/chat/transformation.py
Normal file
59
litellm/llms/webwright/chat/transformation.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
"""
|
||||
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
|
||||
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 resolved_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[str]: # mutable-ok: matches the base provider signature
|
||||
"""
|
||||
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
|
||||
"model", # Required
|
||||
"stream", # Optional
|
||||
"temperature", # Optional
|
||||
"top_p", # Optional
|
||||
"max_tokens", # Optional
|
||||
"stop", # Optional
|
||||
"tools", # Optional
|
||||
"tool_choice", # Optional
|
||||
"response_format", # Optional
|
||||
]
|
||||
|
|
@ -2071,6 +2071,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": "wren-r1"
|
||||
},
|
||||
{
|
||||
"provider": "Infinity",
|
||||
"provider_display_name": "Infinity",
|
||||
|
|
|
|||
|
|
@ -4732,6 +4732,7 @@ class LlmProviders(str, Enum):
|
|||
OPENSEARCH = "opensearch"
|
||||
HELICONE = "helicone"
|
||||
HYPERBOLIC = "hyperbolic"
|
||||
WEBWRIGHT = "webwright"
|
||||
RECRAFT = "recraft"
|
||||
FAL_AI = "fal_ai"
|
||||
STABILITY = "stability"
|
||||
|
|
|
|||
|
|
@ -9936,6 +9936,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: (
|
||||
|
|
|
|||
|
|
@ -4437,6 +4437,24 @@
|
|||
"generateContent": 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",
|
||||
|
|
|
|||
0
tests/unit/llms/webwright/__init__.py
Normal file
0
tests/unit/llms/webwright/__init__.py
Normal file
0
tests/unit/llms/webwright/chat/__init__.py
Normal file
0
tests/unit/llms/webwright/chat/__init__.py
Normal file
105
tests/unit/llms/webwright/chat/test_webwright_transformation.py
Normal file
105
tests/unit/llms/webwright/chat/test_webwright_transformation.py
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
"""
|
||||
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",
|
||||
"top_p",
|
||||
"max_tokens",
|
||||
"stop",
|
||||
"tools",
|
||||
"tool_choice",
|
||||
"response_format",
|
||||
):
|
||||
assert param in supported_params
|
||||
|
||||
for unsupported in ("frequency_penalty", "presence_penalty", "n", "seed", "user", "reasoning_effort"):
|
||||
assert unsupported not 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!"
|
||||
Loading…
Add table
Reference in a new issue