This commit is contained in:
Vansh 2026-10-03 23:41:36 +08:00 • committed by GitHub
commit af89052431
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 437 additions and 0 deletions

View file

@ -33,6 +33,7 @@ from collections.abc import Mapping, Sequence
from typing import (
Any,
Callable,
cast,
Dict,
Final,
get_args,
@ -688,6 +689,7 @@ moonshot_models: Set = set()
publicai_models: Set = set()
darkbloom_models: Set = set()
v0_models: Set = set()
vispark_models: Set = set()
morph_models: Set = set()
lambda_ai_models: Set = set()
inception_models: Set = set()
@ -954,6 +956,9 @@ def _populate_provider_model_sets(model_cost_map: Dict) -> None:
darkbloom_models.add(key)
elif value.get("litellm_provider") == "v0":
v0_models.add(key)
elif value.get("litellm_provider") == "vispark":
# cast keeps the basedpyright reportUnknownArgumentType budget flat (keys are model-name strings)
vispark_models.add(cast(str, key)) # cast-ok: model keys are strings
elif value.get("litellm_provider") == "morph":
morph_models.add(key)
elif value.get("litellm_provider") == "lambda_ai":
@ -1116,6 +1121,7 @@ model_list = list(
| publicai_models
| darkbloom_models
| v0_models
| vispark_models
| morph_models
| lambda_ai_models
| inception_models
@ -1228,6 +1234,7 @@ def _build_models_by_provider() -> dict:
"publicai": publicai_models,
"darkbloom": darkbloom_models,
"v0": v0_models,
"vispark": vispark_models,
"morph": morph_models,
"lambda_ai": lambda_ai_models,
"inception": inception_models,

View file

@ -964,6 +964,7 @@ openai_compatible_endpoints: Final[list] = [
"https://api.cognition.ai/v1",
"https://api.cortecs.ai/v1",
"https://api.scx.ai/v1",
"https://api.lab.vispark.in/v1",
"https://api.prisminference.com/v1",
"https://gigachat.devices.sberbank.ru/api/v1",
]
@ -1035,6 +1036,7 @@ openai_compatible_providers: Final[list] = [
"ragflow",
"pinstripes", # Pinstripes - JSON-configured provider
"darkbloom",
"vispark", # Vispark Lab - JSON-configured provider
"meta", # Meta Model API (Muse Spark) - JSON-configured provider
"cognition",
"cortecs",
@ -1066,6 +1068,7 @@ openai_text_completion_compatible_providers: Final[list] = [ # providers that s
"poe",
"chutes",
"v0",
"vispark",
"lambda_ai",
"hyperbolic",
"wandb",

View file

@ -218,5 +218,14 @@
"api_key_env": "SAIL_API_KEY",
"api_base_env": "SAIL_API_BASE",
"supported_endpoints": ["/v1/chat/completions", "/v1/responses", "/v1/messages"]
},
"vispark": {
"base_url": "https://api.lab.vispark.in/v1",
"api_key_env": "VISPARK_LAB_API_KEY",
"api_base_env": "VISPARK_LAB_API_BASE",
"param_mappings": {
"max_completion_tokens": "max_tokens"
},
"supported_endpoints": ["/v1/chat/completions", "/v1/completions"]
}
}

View file

@ -47917,6 +47917,102 @@
"supports_tool_choice": true,
"supports_vision": true
},
"vispark/vision-small": {
"input_cost_per_token": 1.05e-06,
"litellm_provider": "vispark",
"max_input_tokens": 1000000,
"max_output_tokens": 65536,
"max_tokens": 65536,
"mode": "chat",
"output_cost_per_token": 3.16e-06,
"source": "https://lab.vispark.in/#vision",
"supported_endpoints": [
"/v1/chat/completions",
"/v1/completions"
],
"supported_modalities": [
"text",
"image",
"audio",
"video"
],
"supported_output_modalities": [
"text"
],
"supports_function_calling": true,
"supports_native_streaming": true,
"supports_parallel_function_calling": true,
"supports_pdf_input": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true,
"supports_vision": true
},
"vispark/vision-medium": {
"input_cost_per_token": 4.21e-06,
"litellm_provider": "vispark",
"max_input_tokens": 1000000,
"max_output_tokens": 65536,
"max_tokens": 65536,
"mode": "chat",
"output_cost_per_token": 1.263e-05,
"source": "https://lab.vispark.in/#vision",
"supported_endpoints": [
"/v1/chat/completions",
"/v1/completions"
],
"supported_modalities": [
"text",
"image",
"audio",
"video"
],
"supported_output_modalities": [
"text"
],
"supports_function_calling": true,
"supports_native_streaming": true,
"supports_parallel_function_calling": true,
"supports_pdf_input": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true,
"supports_vision": true
},
"vispark/vision-large": {
"input_cost_per_token": 7.37e-06,
"litellm_provider": "vispark",
"max_input_tokens": 1000000,
"max_output_tokens": 65536,
"max_tokens": 65536,
"mode": "chat",
"output_cost_per_token": 2.211e-05,
"source": "https://lab.vispark.in/#vision",
"supported_endpoints": [
"/v1/chat/completions",
"/v1/completions"
],
"supported_modalities": [
"text",
"image",
"audio",
"video"
],
"supported_output_modalities": [
"text"
],
"supports_function_calling": true,
"supports_native_streaming": true,
"supports_parallel_function_calling": true,
"supports_pdf_input": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true,
"supports_vision": true
},
"vercel_ai_gateway/alibaba/qwen-3-14b": {
"input_cost_per_token": 8e-08,
"litellm_provider": "vercel_ai_gateway",

View file

@ -3712,5 +3712,33 @@
}
],
"default_model_placeholder": "cursor/claude-4-sonnet"
},
{
"provider": "VISPARK",
"provider_display_name": "Vispark",
"litellm_provider": "vispark",
"credential_fields": [
{
"key": "api_base",
"label": "API Base",
"placeholder": "https://api.lab.vispark.in/v1",
"tooltip": null,
"required": false,
"field_type": "text",
"options": null,
"default_value": null
},
{
"key": "api_key",
"label": "API Key",
"placeholder": null,
"tooltip": null,
"required": true,
"field_type": "password",
"options": null,
"default_value": null
}
],
"default_model_placeholder": "vispark/vision-medium"
}
]

View file

@ -4069,6 +4069,7 @@ class LlmProviders(str, Enum):
MOONSHOT = "moonshot"
PUBLICAI = "publicai"
V0 = "v0"
VISPARK = "vispark"
MORPH = "morph"
LAMBDA_AI = "lambda_ai"
INCEPTION = "inception"

View file

@ -47917,6 +47917,102 @@
"supports_tool_choice": true,
"supports_vision": true
},
"vispark/vision-small": {
"input_cost_per_token": 1.05e-06,
"litellm_provider": "vispark",
"max_input_tokens": 1000000,
"max_output_tokens": 65536,
"max_tokens": 65536,
"mode": "chat",
"output_cost_per_token": 3.16e-06,
"source": "https://lab.vispark.in/#vision",
"supported_endpoints": [
"/v1/chat/completions",
"/v1/completions"
],
"supported_modalities": [
"text",
"image",
"audio",
"video"
],
"supported_output_modalities": [
"text"
],
"supports_function_calling": true,
"supports_native_streaming": true,
"supports_parallel_function_calling": true,
"supports_pdf_input": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true,
"supports_vision": true
},
"vispark/vision-medium": {
"input_cost_per_token": 4.21e-06,
"litellm_provider": "vispark",
"max_input_tokens": 1000000,
"max_output_tokens": 65536,
"max_tokens": 65536,
"mode": "chat",
"output_cost_per_token": 1.263e-05,
"source": "https://lab.vispark.in/#vision",
"supported_endpoints": [
"/v1/chat/completions",
"/v1/completions"
],
"supported_modalities": [
"text",
"image",
"audio",
"video"
],
"supported_output_modalities": [
"text"
],
"supports_function_calling": true,
"supports_native_streaming": true,
"supports_parallel_function_calling": true,
"supports_pdf_input": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true,
"supports_vision": true
},
"vispark/vision-large": {
"input_cost_per_token": 7.37e-06,
"litellm_provider": "vispark",
"max_input_tokens": 1000000,
"max_output_tokens": 65536,
"max_tokens": 65536,
"mode": "chat",
"output_cost_per_token": 2.211e-05,
"source": "https://lab.vispark.in/#vision",
"supported_endpoints": [
"/v1/chat/completions",
"/v1/completions"
],
"supported_modalities": [
"text",
"image",
"audio",
"video"
],
"supported_output_modalities": [
"text"
],
"supports_function_calling": true,
"supports_native_streaming": true,
"supports_parallel_function_calling": true,
"supports_pdf_input": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true,
"supports_vision": true
},
"vercel_ai_gateway/alibaba/qwen-3-14b": {
"input_cost_per_token": 8e-08,
"litellm_provider": "vercel_ai_gateway",

View file

@ -2641,6 +2641,23 @@
"interactions": true
}
},
"vispark": {
"display_name": "Vispark (`vispark`)",
"url": "https://lab.vispark.in/#vision",
"endpoints": {
"chat_completions": true,
"messages": false,
"responses": false,
"embeddings": false,
"image_generations": false,
"audio_transcriptions": false,
"audio_speech": false,
"moderations": false,
"batches": false,
"rerank": false,
"a2a": false
}
},
"vercel_ai_gateway": {
"display_name": "Vercel AI Gateway (`vercel_ai_gateway`)",
"url": "https://docs.litellm.ai/docs/providers/vercel_ai_gateway",

View file

@ -0,0 +1,180 @@
"""
Tests for Vispark provider configuration and integration.
"""
import litellm
class TestVisparkProviderConfig:
def test_vispark_in_provider_list(self):
from litellm import LlmProviders
assert hasattr(LlmProviders, "VISPARK")
assert LlmProviders.VISPARK.value == "vispark"
assert "vispark" in litellm.provider_list
def test_vispark_json_config_exists(self):
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
assert JSONProviderRegistry.exists("vispark")
vispark = JSONProviderRegistry.get("vispark")
assert vispark is not None
assert vispark.base_url == "https://api.lab.vispark.in/v1"
assert vispark.api_key_env == "VISPARK_LAB_API_KEY"
assert vispark.api_base_env == "VISPARK_LAB_API_BASE"
assert vispark.param_mappings.get("max_completion_tokens") == "max_tokens"
def test_vispark_in_openai_compatible_providers(self):
from litellm.constants import (
openai_compatible_providers,
openai_text_completion_compatible_providers,
)
assert "vispark" in openai_compatible_providers
assert "vispark" in openai_text_completion_compatible_providers
def test_vispark_provider_resolution(self):
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
model, provider, api_key, api_base = get_llm_provider(
model="vispark/vision-medium",
custom_llm_provider=None,
api_base=None,
api_key=None,
)
assert model == "vision-medium"
assert provider == "vispark"
assert api_base == "https://api.lab.vispark.in/v1"
def test_vispark_api_base_override(self):
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
model, provider, api_key, api_base = get_llm_provider(
model="vispark/vision-medium",
custom_llm_provider=None,
api_base="https://custom.vispark.in/v1",
api_key="sk-test",
)
assert provider == "vispark"
assert api_base == "https://custom.vispark.in/v1"
assert api_key == "sk-test"
def test_vispark_url_autodetection(self):
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
model, provider, api_key, api_base = get_llm_provider(
model="vision-medium",
custom_llm_provider=None,
api_base="https://api.lab.vispark.in/v1",
api_key=None,
)
assert provider == "vispark"
assert api_base == "https://api.lab.vispark.in/v1"
def test_vispark_max_completion_tokens_mapped(self):
from litellm.llms.openai_like.dynamic_config import create_config_class
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
provider = JSONProviderRegistry.get("vispark")
assert provider is not None
config = create_config_class(provider)()
optional_params = config.map_openai_params(
non_default_params={"max_completion_tokens": 256},
optional_params={},
model="vision-medium",
drop_params=False,
)
assert optional_params["max_tokens"] == 256
assert "max_completion_tokens" not in optional_params
def test_vispark_router_config(self):
from litellm import Router
router = Router(
model_list=[
{
"model_name": "vispark-chat",
"litellm_params": {
"model": "vispark/vision-medium",
"api_key": "test-key",
},
}
]
)
assert len(router.model_list) == 1
assert router.model_list[0]["model_name"] == "vispark-chat"
class TestVisparkModelMetadata:
VISPARK_MODELS = (
"vispark/vision-small",
"vispark/vision-medium",
"vispark/vision-large",
)
@staticmethod
def _load(path_parts):
import json
from pathlib import Path
json_path = Path(__file__).parents[4].joinpath(*path_parts)
with open(json_path) as f:
return json.load(f)
def test_vispark_models_registered_with_correct_metadata(self):
model_cost = self._load(("model_prices_and_context_window.json",))
for model in self.VISPARK_MODELS:
info = model_cost.get(model)
assert info is not None, f"{model} missing from model_prices_and_context_window.json"
assert info["litellm_provider"] == "vispark"
assert info["mode"] == "chat"
assert info["input_cost_per_token"] > 0
assert info["output_cost_per_token"] > 0
assert info["supports_function_calling"] is True
assert info["supports_tool_choice"] is True
assert info["supports_reasoning"] is True
assert info["supports_response_schema"] is True
assert info["supports_vision"] is True
assert info["max_output_tokens"] == 65536
assert info["max_tokens"] == info["max_output_tokens"]
assert info["max_input_tokens"] == 1000000
def test_vispark_models_synced_to_backup(self):
model_cost = self._load(("model_prices_and_context_window.json",))
backup = self._load(("litellm", "model_prices_and_context_window_backup.json"))
for model in self.VISPARK_MODELS:
assert model in backup, f"{model} missing from backup json"
assert backup[model] == model_cost[model], f"{model} differs between root and backup json"
class TestVisparkDashboardRegistration:
@staticmethod
def _provider_create_fields():
import json
from pathlib import Path
import litellm
path = Path(litellm.__file__).parent / "proxy" / "public_endpoints" / "provider_create_fields.json"
with open(path) as f:
return json.load(f)
def test_vispark_is_selectable_in_the_add_model_form(self):
entries = [e for e in self._provider_create_fields() if e["litellm_provider"] == "vispark"]
assert len(entries) == 1, "vispark must appear exactly once in provider_create_fields.json"
entry = entries[0]
assert entry["provider"] == "VISPARK"
assert entry["provider_display_name"] == "Vispark"
assert entry["default_model_placeholder"].startswith("vispark/")
fields = {f["key"]: f for f in entry["credential_fields"]}
assert fields["api_key"]["required"] is True
assert fields["api_key"]["field_type"] == "password"
assert fields["api_base"]["required"] is False