mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
feat(parasail): add Parasail as a JSON-configured OpenAI-compatible provider (#29842)
* feat(parasail): add Parasail as a JSON-configured OpenAI-compatible provider Registers parasail in the openai_like JSON provider loader with both /v1/chat/completions and /v1/responses support. Parasail's Responses API rejects store:true and any request that omits store, so the loader gains a force_store_false special_handling flag; the parasail entry sets it and the generated Responses config overrides store=false on every call. This keeps callers from hitting "State storage not supported" and matches what Parasail's docs require. Adds the PARASAIL enum value, listing under openai_compatible_providers, provider documentation at docs/my-website/docs/providers/parasail.md, and a focused unit test file under tests/test_litellm/llms/parasail/ that covers JSON registration, chat URL construction, Responses URL construction with PARASAIL_API_BASE override, and the force_store_false regression in both the caller-sent-store=true and caller-omitted cases. * fix(parasail): register in provider_endpoints_support, drop in-repo docs Greptile review feedback. The provider doc belongs in the litellm-docs repo, not this one's docs/my-website tree; removing it here. Adds the parasail entry to provider_endpoints_support.json so the check_provider_folders_documented.py CI check passes (chat_completions and responses true; others false).
This commit is contained in:
parent
0806a3e54f
commit
f087de5c8f
6 changed files with 219 additions and 0 deletions
|
|
@ -831,6 +831,7 @@ openai_compatible_providers: List = [
|
|||
"nano-gpt", # Nano-GPT - JSON-configured provider
|
||||
"poe", # Poe - JSON-configured provider
|
||||
"chutes", # Chutes - JSON-configured provider
|
||||
"parasail", # Parasail - JSON-configured provider
|
||||
"featherless_ai",
|
||||
"nscale",
|
||||
"nebius",
|
||||
|
|
|
|||
|
|
@ -187,6 +187,7 @@ def create_responses_config_class(provider: SimpleProviderConfig):
|
|||
from litellm.llms.openai_like.responses.transformation import (
|
||||
OpenAILikeResponsesConfig,
|
||||
)
|
||||
from litellm.types.llms.openai import ResponseInputParam
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
class JSONProviderResponsesConfig(OpenAILikeResponsesConfig):
|
||||
|
|
@ -223,5 +224,23 @@ def create_responses_config_class(provider: SimpleProviderConfig):
|
|||
api_base = api_base.rstrip("/")
|
||||
return f"{api_base}/responses"
|
||||
|
||||
def transform_responses_api_request(
|
||||
self,
|
||||
model: str,
|
||||
input: Union[str, ResponseInputParam],
|
||||
response_api_optional_request_params: dict,
|
||||
litellm_params: GenericLiteLLMParams,
|
||||
headers: dict,
|
||||
) -> dict:
|
||||
if provider.special_handling.get("force_store_false"):
|
||||
response_api_optional_request_params["store"] = False
|
||||
return super().transform_responses_api_request(
|
||||
model=model,
|
||||
input=input,
|
||||
response_api_optional_request_params=response_api_optional_request_params,
|
||||
litellm_params=litellm_params,
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
_responses_config_cache[provider.slug] = JSONProviderResponsesConfig
|
||||
return JSONProviderResponsesConfig
|
||||
|
|
|
|||
|
|
@ -132,5 +132,14 @@
|
|||
"param_mappings": {
|
||||
"max_completion_tokens": "max_tokens"
|
||||
}
|
||||
},
|
||||
"parasail": {
|
||||
"base_url": "https://api.parasail.io/v1",
|
||||
"api_key_env": "PARASAIL_API_KEY",
|
||||
"api_base_env": "PARASAIL_API_BASE",
|
||||
"supported_endpoints": ["/v1/chat/completions", "/v1/responses"],
|
||||
"special_handling": {
|
||||
"force_store_false": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3392,6 +3392,7 @@ class LlmProviders(str, Enum):
|
|||
POE = "poe"
|
||||
CHUTES = "chutes"
|
||||
NEOSANTARA = "neosantara"
|
||||
PARASAIL = "parasail"
|
||||
XIAOMI_MIMO = "xiaomi_mimo"
|
||||
TENSORMESH = "tensormesh"
|
||||
LITELLM_AGENT = "litellm_agent"
|
||||
|
|
|
|||
|
|
@ -1834,6 +1834,23 @@
|
|||
"search": true
|
||||
}
|
||||
},
|
||||
"parasail": {
|
||||
"display_name": "Parasail (`parasail`)",
|
||||
"url": "https://docs.litellm.ai/docs/providers/parasail",
|
||||
"endpoints": {
|
||||
"chat_completions": true,
|
||||
"messages": false,
|
||||
"responses": true,
|
||||
"embeddings": false,
|
||||
"image_generations": false,
|
||||
"audio_transcriptions": false,
|
||||
"audio_speech": false,
|
||||
"moderations": false,
|
||||
"batches": false,
|
||||
"rerank": false,
|
||||
"a2a": false
|
||||
}
|
||||
},
|
||||
"perplexity": {
|
||||
"display_name": "Perplexity AI (`perplexity`)",
|
||||
"url": "https://docs.litellm.ai/docs/providers/perplexity",
|
||||
|
|
|
|||
172
tests/test_litellm/llms/parasail/test_parasail.py
Normal file
172
tests/test_litellm/llms/parasail/test_parasail.py
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
import os
|
||||
from unittest.mock import patch
|
||||
|
||||
PARASAIL_API_BASE = "https://api.parasail.io/v1"
|
||||
PARASAIL_RESPONSES_GATEWAY = "https://api-webflux.saas.parasail.io/v1"
|
||||
|
||||
|
||||
def test_parasail_json_registry():
|
||||
import litellm
|
||||
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
|
||||
|
||||
assert litellm.LlmProviders.PARASAIL.value == "parasail"
|
||||
assert litellm.LlmProviders("parasail") == litellm.LlmProviders.PARASAIL
|
||||
assert JSONProviderRegistry.exists("parasail")
|
||||
config = JSONProviderRegistry.get("parasail")
|
||||
assert config is not None
|
||||
assert config.base_url == PARASAIL_API_BASE
|
||||
assert config.api_key_env == "PARASAIL_API_KEY"
|
||||
assert config.api_base_env == "PARASAIL_API_BASE"
|
||||
assert "/v1/chat/completions" in config.supported_endpoints
|
||||
assert "/v1/responses" in config.supported_endpoints
|
||||
assert config.special_handling.get("force_store_false") is True
|
||||
|
||||
|
||||
def test_parasail_listed_in_openai_compatible_providers():
|
||||
from litellm.constants import openai_compatible_providers
|
||||
|
||||
assert "parasail" in openai_compatible_providers
|
||||
|
||||
|
||||
def test_parasail_dynamic_config_env_vars():
|
||||
from litellm.llms.openai_like.dynamic_config import create_config_class
|
||||
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
|
||||
|
||||
config = create_config_class(JSONProviderRegistry.get("parasail"))()
|
||||
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"PARASAIL_API_KEY": "test-key",
|
||||
"PARASAIL_API_BASE": PARASAIL_RESPONSES_GATEWAY,
|
||||
},
|
||||
):
|
||||
api_base, api_key = config._get_openai_compatible_provider_info(None, None)
|
||||
|
||||
assert api_base == PARASAIL_RESPONSES_GATEWAY
|
||||
assert api_key == "test-key"
|
||||
|
||||
|
||||
def test_parasail_provider_detection_by_prefix():
|
||||
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
|
||||
|
||||
model, provider, _, api_base = get_llm_provider(
|
||||
"parasail/parasail-llama-33-70b-fp8"
|
||||
)
|
||||
|
||||
assert model == "parasail-llama-33-70b-fp8"
|
||||
assert provider == "parasail"
|
||||
assert api_base == PARASAIL_API_BASE
|
||||
|
||||
|
||||
def test_parasail_chat_complete_url():
|
||||
from litellm.llms.openai_like.dynamic_config import create_config_class
|
||||
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
|
||||
|
||||
config = create_config_class(JSONProviderRegistry.get("parasail"))()
|
||||
|
||||
assert (
|
||||
config.get_complete_url(
|
||||
api_base=None,
|
||||
api_key=None,
|
||||
model="parasail-llama-33-70b-fp8",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
== f"{PARASAIL_API_BASE}/chat/completions"
|
||||
)
|
||||
|
||||
|
||||
def test_parasail_responses_api_config():
|
||||
from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig
|
||||
from litellm.utils import ProviderConfigManager
|
||||
|
||||
config = ProviderConfigManager.get_provider_responses_api_config(
|
||||
provider="parasail",
|
||||
model="parasail-kimi-k25-elicit",
|
||||
)
|
||||
|
||||
assert isinstance(config, OpenAIResponsesAPIConfig)
|
||||
assert config.custom_llm_provider == "parasail"
|
||||
assert (
|
||||
config.get_complete_url(api_base=None, litellm_params={})
|
||||
== f"{PARASAIL_API_BASE}/responses"
|
||||
)
|
||||
|
||||
|
||||
def test_parasail_responses_api_honors_api_base_override():
|
||||
from litellm.utils import ProviderConfigManager
|
||||
|
||||
config = ProviderConfigManager.get_provider_responses_api_config(
|
||||
provider="parasail",
|
||||
model="parasail-kimi-k25-elicit",
|
||||
)
|
||||
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{"PARASAIL_API_BASE": PARASAIL_RESPONSES_GATEWAY},
|
||||
):
|
||||
url = config.get_complete_url(api_base=None, litellm_params={})
|
||||
|
||||
assert url == f"{PARASAIL_RESPONSES_GATEWAY}/responses"
|
||||
|
||||
|
||||
def test_parasail_responses_api_forces_store_false_when_caller_sets_true():
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
from litellm.utils import ProviderConfigManager
|
||||
|
||||
config = ProviderConfigManager.get_provider_responses_api_config(
|
||||
provider="parasail",
|
||||
model="parasail-kimi-k25-elicit",
|
||||
)
|
||||
|
||||
request_params: dict = {"store": True, "temperature": 0.2}
|
||||
transformed = config.transform_responses_api_request(
|
||||
model="parasail-kimi-k25-elicit",
|
||||
input="hello",
|
||||
response_api_optional_request_params=request_params,
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert transformed["store"] is False
|
||||
assert transformed["temperature"] == 0.2
|
||||
|
||||
|
||||
def test_parasail_responses_api_forces_store_false_when_caller_omits_store():
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
from litellm.utils import ProviderConfigManager
|
||||
|
||||
config = ProviderConfigManager.get_provider_responses_api_config(
|
||||
provider="parasail",
|
||||
model="parasail-kimi-k25-elicit",
|
||||
)
|
||||
|
||||
transformed = config.transform_responses_api_request(
|
||||
model="parasail-kimi-k25-elicit",
|
||||
input="hello",
|
||||
response_api_optional_request_params={},
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert transformed["store"] is False
|
||||
|
||||
|
||||
def test_parasail_responses_api_validate_environment_sets_bearer_token():
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
from litellm.utils import ProviderConfigManager
|
||||
|
||||
config = ProviderConfigManager.get_provider_responses_api_config(
|
||||
provider="parasail",
|
||||
model="parasail-kimi-k25-elicit",
|
||||
)
|
||||
|
||||
with patch.dict(os.environ, {"PARASAIL_API_KEY": "secret-from-env"}):
|
||||
headers = config.validate_environment(
|
||||
headers={},
|
||||
model="parasail-kimi-k25-elicit",
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
)
|
||||
|
||||
assert headers["Authorization"] == "Bearer secret-from-env"
|
||||
Loading…
Add table
Reference in a new issue