mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-05 07:37:50 +00:00
feat(providers): add Phala as an OpenAI-compatible provider
This commit is contained in:
parent
dddbaa803d
commit
412c5f82cd
7 changed files with 204 additions and 0 deletions
|
|
@ -1057,6 +1057,7 @@ openai_compatible_endpoints: Final[list[str]] = [
|
|||
"https://api.cognition.ai/v1",
|
||||
"https://inference.coralbricks.ai/v1",
|
||||
"https://api.cortecs.ai/v1",
|
||||
"https://inference.phala.com/v1",
|
||||
"https://api.scx.ai/v1",
|
||||
"https://api.prisminference.com/v1",
|
||||
"https://api.reka.ai/v1",
|
||||
|
|
@ -1137,6 +1138,7 @@ openai_compatible_providers: Final[list[str]] = [
|
|||
"cognition",
|
||||
"coralbricks",
|
||||
"cortecs",
|
||||
"phala",
|
||||
"scx-ai",
|
||||
"prism",
|
||||
"sail",
|
||||
|
|
|
|||
|
|
@ -192,6 +192,15 @@
|
|||
"api_base_env": "CORTECS_API_BASE",
|
||||
"supported_endpoints": ["/v1/chat/completions", "/v1/responses", "/v1/messages"]
|
||||
},
|
||||
"phala": {
|
||||
"base_url": "https://inference.phala.com/v1",
|
||||
"api_key_env": "PHALA_AI_API_KEY",
|
||||
"api_base_env": "PHALA_API_BASE",
|
||||
"provider_info": {
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"supported_endpoints": ["/v1/chat/completions"]
|
||||
},
|
||||
"pinstripes": {
|
||||
"base_url": "https://pinstripes.io/v1",
|
||||
"api_key_env": "PINSTRIPES_API_KEY",
|
||||
|
|
|
|||
|
|
@ -636,6 +636,24 @@
|
|||
"interactions": false
|
||||
}
|
||||
},
|
||||
"phala": {
|
||||
"display_name": "Phala (`phala`)",
|
||||
"url": "https://docs.phala.com/phala-cloud/confidential-ai/confidential-model/confidential-ai-api",
|
||||
"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,
|
||||
"interactions": false
|
||||
}
|
||||
},
|
||||
"cortecs": {
|
||||
"display_name": "Cortecs (`cortecs`)",
|
||||
"url": "https://docs.litellm.ai/docs/providers/cortecs",
|
||||
|
|
|
|||
|
|
@ -3119,6 +3119,34 @@
|
|||
],
|
||||
"default_model_placeholder": "gpt-3.5-turbo"
|
||||
},
|
||||
{
|
||||
"provider": "PHALA",
|
||||
"provider_display_name": "Phala",
|
||||
"litellm_provider": "phala",
|
||||
"credential_fields": [
|
||||
{
|
||||
"key": "api_base",
|
||||
"label": "API Base",
|
||||
"placeholder": "https://inference.phala.com/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": "phala/z-ai/glm-5.3"
|
||||
},
|
||||
{
|
||||
"provider": "PREDIBASE",
|
||||
"provider_display_name": "Predibase",
|
||||
|
|
|
|||
|
|
@ -4578,6 +4578,7 @@ class LlmProviders(str, Enum):
|
|||
COGNITION = "cognition"
|
||||
CORALBRICKS = "coralbricks"
|
||||
CORTECS = "cortecs"
|
||||
PHALA = "phala"
|
||||
SCX_AI = "scx-ai"
|
||||
PRISM = "prism"
|
||||
OPENINFER = "openinfer"
|
||||
|
|
|
|||
|
|
@ -2166,6 +2166,24 @@
|
|||
"a2a": false
|
||||
}
|
||||
},
|
||||
"phala": {
|
||||
"display_name": "Phala (`phala`)",
|
||||
"url": "https://docs.phala.com/phala-cloud/confidential-ai/confidential-model/confidential-ai-api",
|
||||
"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,
|
||||
"interactions": false
|
||||
}
|
||||
},
|
||||
"pinstripes": {
|
||||
"display_name": "Pinstripes (`pinstripes`)",
|
||||
"url": "https://docs.litellm.ai/docs/providers/pinstripes",
|
||||
|
|
|
|||
128
tests/unit/llms/openai_like/test_phala_provider.py
Normal file
128
tests/unit/llms/openai_like/test_phala_provider.py
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
import json
|
||||
from typing import Final
|
||||
|
||||
import pytest
|
||||
import respx
|
||||
|
||||
import litellm
|
||||
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
|
||||
|
||||
PHALA_API_BASE: Final = "https://inference.phala.com/v1"
|
||||
PHALA_MODEL_ID: Final = "z-ai/glm-5.3"
|
||||
PHALA_MODEL: Final = f"phala/{PHALA_MODEL_ID}"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("custom_provider", (None, "phala"))
|
||||
def test_phala_resolves_environment_credentials(monkeypatch: pytest.MonkeyPatch, custom_provider: str | None) -> None:
|
||||
monkeypatch.setenv("PHALA_AI_API_KEY", "phala-env-key")
|
||||
|
||||
model, provider, api_key, api_base = get_llm_provider(
|
||||
model=PHALA_MODEL if custom_provider is None else PHALA_MODEL_ID,
|
||||
custom_llm_provider=custom_provider,
|
||||
)
|
||||
|
||||
assert (model, provider, api_key, api_base) == (PHALA_MODEL_ID, "phala", "phala-env-key", PHALA_API_BASE)
|
||||
|
||||
|
||||
def test_phala_detected_from_api_base(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("PHALA_AI_API_KEY", "phala-env-key")
|
||||
|
||||
model, provider, api_key, api_base = get_llm_provider(model=PHALA_MODEL_ID, api_base=PHALA_API_BASE)
|
||||
|
||||
assert (model, provider, api_key, api_base) == (PHALA_MODEL_ID, "phala", "phala-env-key", PHALA_API_BASE)
|
||||
|
||||
|
||||
def test_phala_explicit_credentials_override_environment(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("PHALA_AI_API_KEY", "phala-env-key")
|
||||
monkeypatch.setenv("PHALA_API_BASE", "https://environment.example/v1")
|
||||
|
||||
model, provider, api_key, api_base = get_llm_provider(
|
||||
model=PHALA_MODEL,
|
||||
api_base="https://explicit.example/v1",
|
||||
api_key="phala-explicit-key",
|
||||
)
|
||||
|
||||
assert (model, provider, api_key, api_base) == (
|
||||
PHALA_MODEL_ID,
|
||||
"phala",
|
||||
"phala-explicit-key",
|
||||
"https://explicit.example/v1",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("is_async", (False, True))
|
||||
@pytest.mark.parametrize("api_base", (PHALA_API_BASE, "https://override.example/v1"))
|
||||
async def test_phala_chat_completion_request(monkeypatch: pytest.MonkeyPatch, is_async: bool, api_base: str) -> None:
|
||||
monkeypatch.setenv("PHALA_AI_API_KEY", "phala-test-key")
|
||||
monkeypatch.setenv("PHALA_API_BASE", api_base)
|
||||
monkeypatch.setattr(litellm, "disable_aiohttp_transport", True)
|
||||
with respx.mock() as upstream:
|
||||
route: Final = upstream.post(f"{api_base}/chat/completions").respond(
|
||||
200,
|
||||
json={
|
||||
"id": "chatcmpl_phala",
|
||||
"object": "chat.completion",
|
||||
"created": 1_789_550_000,
|
||||
"model": PHALA_MODEL_ID,
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {"role": "assistant", "content": "Hello from Phala"},
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
],
|
||||
"usage": {"prompt_tokens": 4, "completion_tokens": 3, "total_tokens": 7},
|
||||
},
|
||||
)
|
||||
response: Final = (
|
||||
await litellm.acompletion(
|
||||
model=PHALA_MODEL,
|
||||
messages=[{"role": "user", "content": "Say hello"}],
|
||||
reasoning_effort="low",
|
||||
reasoning={"effort": "low"},
|
||||
)
|
||||
if is_async
|
||||
else litellm.completion(
|
||||
model=PHALA_MODEL,
|
||||
messages=[{"role": "user", "content": "Say hello"}],
|
||||
reasoning_effort="low",
|
||||
reasoning={"effort": "low"},
|
||||
)
|
||||
)
|
||||
|
||||
request: Final = route.calls.last.request
|
||||
assert route.call_count == 1
|
||||
assert request.headers["authorization"] == "Bearer phala-test-key"
|
||||
assert json.loads(request.content) == {
|
||||
"model": PHALA_MODEL_ID,
|
||||
"messages": [{"role": "user", "content": "Say hello"}],
|
||||
"reasoning_effort": "low",
|
||||
"reasoning": {"effort": "low"},
|
||||
}
|
||||
assert isinstance(response, litellm.ModelResponse)
|
||||
assert response.choices[0].message.content == "Hello from Phala"
|
||||
assert response.usage.total_tokens == 7
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_phala_available_in_add_model() -> None:
|
||||
from litellm.proxy.public_endpoints.public_endpoints import get_provider_fields, get_supported_providers
|
||||
|
||||
assert "phala" in await get_supported_providers()
|
||||
providers: Final = await get_provider_fields()
|
||||
phala: Final = next(provider for provider in providers if provider.litellm_provider == "phala")
|
||||
|
||||
assert phala.provider_display_name == "Phala"
|
||||
assert phala.default_model_placeholder == PHALA_MODEL
|
||||
assert {field.key: field.required for field in phala.credential_fields} == {"api_base": False, "api_key": True}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_phala_listed_for_chat_completions() -> None:
|
||||
from litellm.proxy.public_endpoints.public_endpoints import get_supported_endpoints
|
||||
|
||||
endpoints: Final = await get_supported_endpoints()
|
||||
chat: Final = next(endpoint for endpoint in endpoints.endpoints if endpoint.key == "chat_completions")
|
||||
|
||||
assert any(provider.slug == "phala" and provider.display_name == "Phala" for provider in chat.providers)
|
||||
Loading…
Add table
Reference in a new issue