mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-13 08:52:02 +00:00
Merge 5d465d1202 into d32a6d75f8
This commit is contained in:
commit
fa960591f6
14 changed files with 446 additions and 4 deletions
|
|
@ -465,6 +465,7 @@ Set `LITELLM_PROXY_API_BASE` and `LITELLM_PROXY_API_KEY` and every model call th
|
|||
| [Together AI (`together_ai`)](https://docs.litellm.ai/docs/providers/togetherai) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
| [Topaz (`topaz`)](https://docs.litellm.ai/docs/providers/topaz) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
| [Triton (`triton`)](https://docs.litellm.ai/docs/providers/triton-inference-server) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
| [Tsubasa (`tsubasa`)](https://docs.litellm.ai/docs/providers/tsubasa) | Yes | | | | | | | | | |
|
||||
| [Typesafe Decisions API (`typesafe`)](https://docs.litellm.ai/docs/providers) | | | | | | | | | | |
|
||||
| [V0 (`v0`)](https://docs.litellm.ai/docs/providers/v0) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
| [Vercel AI Gateway (`vercel_ai_gateway`)](https://docs.litellm.ai/docs/providers/vercel_ai_gateway) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
|
|
|
|||
46
docs/my-website/docs/providers/tsubasa.md
Normal file
46
docs/my-website/docs/providers/tsubasa.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Tsubasa
|
||||
|
||||
Tsubasa uses LiteLLM's existing OpenAI-compatible Chat Completions transport
|
||||
|
||||
Use an active Tsubasa API key for text chat. This integration does not declare
|
||||
tool-calling, structured-output, vision, or reasoning support. Hosted inference
|
||||
has not been qualified through this integration
|
||||
|
||||
## API key
|
||||
|
||||
Set `TSUBASA_API_KEY` in the process environment. LiteLLM resolves the public
|
||||
endpoint automatically; no `api_base` override is required
|
||||
|
||||
## Completion
|
||||
|
||||
Select a public model with the `tsubasa/` provider prefix
|
||||
|
||||
```python
|
||||
from litellm import completion
|
||||
|
||||
response = completion(
|
||||
model="tsubasa/tsubasa-pro",
|
||||
messages=[{"role": "user", "content": "Explain binary search."}],
|
||||
max_tokens=256,
|
||||
)
|
||||
```
|
||||
|
||||
Use `tsubasa/tsubasa-fast` for the other public model. Both use
|
||||
`https://api.tsubasa.sh/v1/chat/completions`
|
||||
|
||||
## Streaming
|
||||
|
||||
Set `stream=True` to receive Chat Completions chunks
|
||||
|
||||
```python
|
||||
stream = completion(
|
||||
model="tsubasa/tsubasa-fast",
|
||||
messages=[{"role": "user", "content": "Explain a stack."}],
|
||||
stream=True,
|
||||
)
|
||||
for chunk in stream:
|
||||
print(chunk.choices[0].delta.content or "", end="")
|
||||
```
|
||||
|
||||
The integration does not declare Responses, embeddings, image, or audio support.
|
||||
See [Tsubasa's API documentation](https://tsubasa.sh/docs) for availability
|
||||
|
|
@ -1172,6 +1172,7 @@ openai_compatible_providers: Final[list[str]] = [
|
|||
"pinstripes", # Pinstripes - JSON-configured provider
|
||||
"openinfer",
|
||||
"darkbloom",
|
||||
"tsubasa",
|
||||
"meta", # Meta Model API (Muse Spark) - JSON-configured provider
|
||||
"cognition",
|
||||
"coralbricks",
|
||||
|
|
|
|||
|
|
@ -30,6 +30,13 @@
|
|||
"base_url": "https://api.scaleway.ai/v1",
|
||||
"api_key_env": "SCW_SECRET_KEY"
|
||||
},
|
||||
"tsubasa": {
|
||||
"base_url": "https://api.tsubasa.sh/v1",
|
||||
"api_key_env": "TSUBASA_API_KEY",
|
||||
"param_mappings": {
|
||||
"max_completion_tokens": "max_tokens"
|
||||
}
|
||||
},
|
||||
"synthetic": {
|
||||
"base_url": "https://api.synthetic.new/openai/v1",
|
||||
"api_key_env": "SYNTHETIC_API_KEY",
|
||||
|
|
|
|||
|
|
@ -49845,6 +49845,34 @@
|
|||
"mode": "embedding",
|
||||
"output_cost_per_token": 0.0
|
||||
},
|
||||
"tsubasa/tsubasa-fast": {
|
||||
"max_input_tokens": 32768,
|
||||
"max_output_tokens": 8192,
|
||||
"input_cost_per_token": 2.0000000000000002e-07,
|
||||
"output_cost_per_token": 1e-06,
|
||||
"litellm_provider": "tsubasa",
|
||||
"mode": "chat",
|
||||
"supports_system_messages": true,
|
||||
"supports_function_calling": false,
|
||||
"supports_parallel_function_calling": false,
|
||||
"supports_response_schema": false,
|
||||
"supports_vision": false,
|
||||
"supports_reasoning": false
|
||||
},
|
||||
"tsubasa/tsubasa-pro": {
|
||||
"max_input_tokens": 32768,
|
||||
"max_output_tokens": 16384,
|
||||
"input_cost_per_token": 5e-07,
|
||||
"output_cost_per_token": 4e-06,
|
||||
"litellm_provider": "tsubasa",
|
||||
"mode": "chat",
|
||||
"supports_system_messages": true,
|
||||
"supports_function_calling": false,
|
||||
"supports_parallel_function_calling": false,
|
||||
"supports_response_schema": false,
|
||||
"supports_vision": false,
|
||||
"supports_reasoning": false
|
||||
},
|
||||
"together_ai/BAAI/bge-base-en-v1.5": {
|
||||
"input_cost_per_token": 8e-09,
|
||||
"litellm_provider": "together_ai",
|
||||
|
|
|
|||
|
|
@ -4280,6 +4280,32 @@
|
|||
"generateContent": false
|
||||
}
|
||||
},
|
||||
"tsubasa": {
|
||||
"display_name": "Tsubasa (`tsubasa`)",
|
||||
"url": "https://docs.litellm.ai/docs/providers/tsubasa",
|
||||
"endpoints": {
|
||||
"chat_completions": true,
|
||||
"responses": true,
|
||||
"text_completion": true,
|
||||
"embeddings": false,
|
||||
"image_generations": false,
|
||||
"image_edits": false,
|
||||
"audio_transcriptions": false,
|
||||
"audio_speech": false,
|
||||
"rerank": false,
|
||||
"ocr": false,
|
||||
"search": false,
|
||||
"moderations": false,
|
||||
"realtime": false,
|
||||
"video_generations": false,
|
||||
"batches": false,
|
||||
"messages": true,
|
||||
"interactions": true,
|
||||
"systemone": false,
|
||||
"a2a": true,
|
||||
"generateContent": true
|
||||
}
|
||||
},
|
||||
"v0": {
|
||||
"display_name": "V0 (`v0`)",
|
||||
"url": "https://docs.litellm.ai/docs/providers/v0",
|
||||
|
|
|
|||
|
|
@ -4003,6 +4003,34 @@
|
|||
],
|
||||
"default_model_placeholder": "typesafe/jev-latest"
|
||||
},
|
||||
{
|
||||
"provider": "Tsubasa",
|
||||
"provider_display_name": "Tsubasa",
|
||||
"litellm_provider": "tsubasa",
|
||||
"credential_fields": [
|
||||
{
|
||||
"key": "api_key",
|
||||
"label": "API Key",
|
||||
"placeholder": null,
|
||||
"tooltip": "Your Tsubasa API key",
|
||||
"required": true,
|
||||
"field_type": "password",
|
||||
"options": null,
|
||||
"default_value": null
|
||||
},
|
||||
{
|
||||
"key": "api_base",
|
||||
"label": "API Base",
|
||||
"placeholder": "https://api.tsubasa.sh/v1",
|
||||
"tooltip": "Optional: override the Tsubasa API base URL",
|
||||
"required": false,
|
||||
"field_type": "text",
|
||||
"options": null,
|
||||
"default_value": "https://api.tsubasa.sh/v1"
|
||||
}
|
||||
],
|
||||
"default_model_placeholder": "tsubasa/tsubasa-pro"
|
||||
},
|
||||
{
|
||||
"provider": "V0",
|
||||
"provider_display_name": "V0",
|
||||
|
|
|
|||
|
|
@ -4791,6 +4791,7 @@ class LlmProviders(str, Enum):
|
|||
HOSTINGER_ROUTER = "hostinger_router"
|
||||
ZEROGPU = "zerogpu"
|
||||
DARKBLOOM = "darkbloom"
|
||||
TSUBASA = "tsubasa"
|
||||
META = "meta"
|
||||
SAIL = "sail"
|
||||
REKA = "reka"
|
||||
|
|
|
|||
|
|
@ -49845,6 +49845,34 @@
|
|||
"mode": "embedding",
|
||||
"output_cost_per_token": 0.0
|
||||
},
|
||||
"tsubasa/tsubasa-fast": {
|
||||
"max_input_tokens": 32768,
|
||||
"max_output_tokens": 8192,
|
||||
"input_cost_per_token": 2.0000000000000002e-07,
|
||||
"output_cost_per_token": 1e-06,
|
||||
"litellm_provider": "tsubasa",
|
||||
"mode": "chat",
|
||||
"supports_system_messages": true,
|
||||
"supports_function_calling": false,
|
||||
"supports_parallel_function_calling": false,
|
||||
"supports_response_schema": false,
|
||||
"supports_vision": false,
|
||||
"supports_reasoning": false
|
||||
},
|
||||
"tsubasa/tsubasa-pro": {
|
||||
"max_input_tokens": 32768,
|
||||
"max_output_tokens": 16384,
|
||||
"input_cost_per_token": 5e-07,
|
||||
"output_cost_per_token": 4e-06,
|
||||
"litellm_provider": "tsubasa",
|
||||
"mode": "chat",
|
||||
"supports_system_messages": true,
|
||||
"supports_function_calling": false,
|
||||
"supports_parallel_function_calling": false,
|
||||
"supports_response_schema": false,
|
||||
"supports_vision": false,
|
||||
"supports_reasoning": false
|
||||
},
|
||||
"together_ai/BAAI/bge-base-en-v1.5": {
|
||||
"input_cost_per_token": 8e-09,
|
||||
"litellm_provider": "together_ai",
|
||||
|
|
|
|||
|
|
@ -4280,6 +4280,32 @@
|
|||
"generateContent": false
|
||||
}
|
||||
},
|
||||
"tsubasa": {
|
||||
"display_name": "Tsubasa (`tsubasa`)",
|
||||
"url": "https://docs.litellm.ai/docs/providers/tsubasa",
|
||||
"endpoints": {
|
||||
"chat_completions": true,
|
||||
"responses": true,
|
||||
"text_completion": true,
|
||||
"embeddings": false,
|
||||
"image_generations": false,
|
||||
"image_edits": false,
|
||||
"audio_transcriptions": false,
|
||||
"audio_speech": false,
|
||||
"rerank": false,
|
||||
"ocr": false,
|
||||
"search": false,
|
||||
"moderations": false,
|
||||
"realtime": false,
|
||||
"video_generations": false,
|
||||
"batches": false,
|
||||
"messages": true,
|
||||
"interactions": true,
|
||||
"systemone": false,
|
||||
"a2a": true,
|
||||
"generateContent": true
|
||||
}
|
||||
},
|
||||
"v0": {
|
||||
"display_name": "V0 (`v0`)",
|
||||
"url": "https://docs.litellm.ai/docs/providers/v0",
|
||||
|
|
|
|||
183
tests/unit/llms/openai_like/test_tsubasa_provider.py
Normal file
183
tests/unit/llms/openai_like/test_tsubasa_provider.py
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
import json
|
||||
from typing import Final
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
from openai import AsyncOpenAI, OpenAI
|
||||
|
||||
from litellm import AuthenticationError, LlmProviders, RateLimitError, UnsupportedParamsError, acompletion, completion
|
||||
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
|
||||
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
|
||||
from litellm.utils import ProviderConfigManager, get_model_info, supports_response_schema
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model_id", ("tsubasa-fast", "tsubasa-pro"))
|
||||
def test_tsubasa_routes_named_models_and_resolves_credentials(model_id: str, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("TSUBASA_API_KEY", "test-credential")
|
||||
registration: Final = JSONProviderRegistry.get("tsubasa")
|
||||
assert registration is not None
|
||||
model, provider, api_key, api_base = get_llm_provider(model=f"tsubasa/{model_id}")
|
||||
assert (model, provider, api_key, api_base) == (
|
||||
model_id,
|
||||
registration.slug,
|
||||
"test-credential",
|
||||
registration.base_url,
|
||||
)
|
||||
config: Final = ProviderConfigManager.get_provider_chat_config(model=model_id, provider=LlmProviders.TSUBASA)
|
||||
assert config is not None
|
||||
assert (
|
||||
config.get_complete_url(api_base, api_key, model_id, {}, {}, stream=True)
|
||||
== f"{registration.base_url}/chat/completions"
|
||||
)
|
||||
assert config._get_openai_compatible_provider_info(None, "explicit-credential") == (
|
||||
registration.base_url,
|
||||
"explicit-credential",
|
||||
)
|
||||
assert config.map_openai_params({"max_completion_tokens": 64}, {}, model_id, False) == {"max_tokens": 64}
|
||||
info: Final = get_model_info(model=f"tsubasa/{model_id}")
|
||||
assert info["max_output_tokens"] <= info["max_input_tokens"]
|
||||
assert info["litellm_provider"] == registration.slug
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model_id", ("tsubasa-fast", "tsubasa-pro"))
|
||||
@pytest.mark.parametrize("stream", (False, True))
|
||||
def test_tsubasa_chat_transport(model_id: str, stream: bool) -> None:
|
||||
registration: Final = JSONProviderRegistry.get("tsubasa")
|
||||
assert registration is not None
|
||||
|
||||
def respond(request: httpx.Request) -> httpx.Response:
|
||||
assert str(request.url) == f"{registration.base_url}/chat/completions"
|
||||
assert request.headers["authorization"] == "Bearer test-credential"
|
||||
assert json.loads(request.content) == {
|
||||
"model": model_id,
|
||||
"messages": [{"role": "user", "content": "Hello"}],
|
||||
"max_tokens": 32,
|
||||
**({"stream": True, "stream_options": {"include_usage": True}} if stream else {}),
|
||||
}
|
||||
result: Final = {
|
||||
"id": "chatcmpl-contract",
|
||||
"created": 1,
|
||||
"model": model_id,
|
||||
"object": "chat.completion.chunk" if stream else "chat.completion",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"finish_reason": "stop",
|
||||
"delta" if stream else "message": {"role": "assistant", "content": "Hello"},
|
||||
}
|
||||
],
|
||||
"usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
|
||||
}
|
||||
if stream:
|
||||
return httpx.Response(
|
||||
200,
|
||||
request=request,
|
||||
headers={"content-type": "text/event-stream"},
|
||||
content=f"data: {json.dumps(result)}\n\ndata: [DONE]\n\n",
|
||||
)
|
||||
return httpx.Response(200, request=request, json=result)
|
||||
|
||||
with httpx.Client(transport=httpx.MockTransport(respond)) as client:
|
||||
result: Final = completion(
|
||||
model=f"tsubasa/{model_id}",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
api_key="test-credential",
|
||||
max_completion_tokens=32,
|
||||
stream=stream,
|
||||
stream_options={"include_usage": True} if stream else None,
|
||||
client=OpenAI(api_key="test-credential", base_url=registration.base_url, http_client=client),
|
||||
)
|
||||
if stream:
|
||||
chunks: Final = tuple(result)
|
||||
assert "".join(chunk.choices[0].delta.content or "" for chunk in chunks if chunk.choices) == "Hello"
|
||||
assert chunks[-1].usage.total_tokens == 2
|
||||
else:
|
||||
assert result.choices[0].message.content == "Hello"
|
||||
assert result.usage.total_tokens == 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize("status, error", ((401, AuthenticationError), (429, RateLimitError)))
|
||||
def test_tsubasa_preserves_upstream_error_status(status: int, error: type[Exception]) -> None:
|
||||
registration: Final = JSONProviderRegistry.get("tsubasa")
|
||||
assert registration is not None
|
||||
|
||||
def respond(request: httpx.Request) -> httpx.Response:
|
||||
return httpx.Response(status, request=request, json={"error": {"message": "Request rejected"}})
|
||||
|
||||
with httpx.Client(transport=httpx.MockTransport(respond)) as client, pytest.raises(error) as raised:
|
||||
completion(
|
||||
model="tsubasa/tsubasa-pro",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
api_key="test-credential",
|
||||
num_retries=0,
|
||||
client=OpenAI(api_key="test-credential", base_url=registration.base_url, http_client=client, max_retries=0),
|
||||
)
|
||||
assert raised.value.status_code == status
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model_id", ("tsubasa-fast", "tsubasa-pro"))
|
||||
def test_tsubasa_rejects_tools_while_qualification_is_pending(model_id: str) -> None:
|
||||
with pytest.raises(UnsupportedParamsError, match="tools"):
|
||||
completion(
|
||||
model=f"tsubasa/{model_id}",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
api_key="test-credential",
|
||||
tools=[{"type": "function", "function": {"name": "get_weather", "parameters": {"type": "object"}}}],
|
||||
)
|
||||
|
||||
|
||||
async def test_tsubasa_forwards_json_schema_without_declaring_qualified_support() -> None:
|
||||
registration: Final = JSONProviderRegistry.get("tsubasa")
|
||||
assert registration is not None
|
||||
response_format: Final = {
|
||||
"type": "json_schema",
|
||||
"json_schema": {
|
||||
"name": "answer",
|
||||
"strict": True,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"answer": {"type": "string"}},
|
||||
"required": ["answer"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def respond(request: httpx.Request) -> httpx.Response:
|
||||
assert str(request.url) == f"{registration.base_url}/chat/completions"
|
||||
assert request.headers["authorization"] == "Bearer test-credential"
|
||||
assert json.loads(request.content) == {
|
||||
"model": "tsubasa-pro",
|
||||
"messages": [{"role": "user", "content": "Hello"}],
|
||||
"response_format": response_format,
|
||||
}
|
||||
return httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"id": "chatcmpl-contract",
|
||||
"created": 1,
|
||||
"model": "tsubasa-pro",
|
||||
"object": "chat.completion",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"finish_reason": "stop",
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": '{"answer":"Hello"}',
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
async with httpx.AsyncClient(transport=httpx.MockTransport(respond)) as client:
|
||||
result: Final = await acompletion(
|
||||
model="tsubasa/tsubasa-pro",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
response_format=response_format,
|
||||
api_key="test-credential",
|
||||
client=AsyncOpenAI(api_key="test-credential", base_url=registration.base_url, http_client=client),
|
||||
)
|
||||
assert json.loads(result.choices[0].message.content) == {"answer": "Hello"}
|
||||
assert supports_response_schema("tsubasa/tsubasa-pro") is False
|
||||
46
tests/unit/proxy/public_endpoints/test_provider_fields.py
Normal file
46
tests/unit/proxy/public_endpoints/test_provider_fields.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
from typing import Final
|
||||
|
||||
import httpx
|
||||
from fastapi import FastAPI
|
||||
|
||||
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
|
||||
from litellm.proxy.public_endpoints.public_endpoints import router
|
||||
from litellm.types.proxy.public_endpoints.public_endpoints import ProviderCreateInfo, SupportedEndpointsResponse
|
||||
from litellm.utils import get_model_info
|
||||
|
||||
|
||||
async def test_tsubasa_dashboard_fields_resolve_to_a_registered_model() -> None:
|
||||
app: Final = FastAPI()
|
||||
app.include_router(router)
|
||||
async with httpx.AsyncClient(transport=httpx.ASGITransport(app), base_url="http://test") as client:
|
||||
response: Final = await client.get("/public/providers/fields")
|
||||
endpoints_response: Final = await client.get("/public/endpoints")
|
||||
assert response.status_code == 200, response.text
|
||||
providers: Final = tuple(ProviderCreateInfo.model_validate(entry) for entry in response.json())
|
||||
matches: Final = tuple(provider for provider in providers if provider.litellm_provider == "tsubasa")
|
||||
assert len(matches) == 1
|
||||
provider: Final = matches[0]
|
||||
assert provider.provider == provider.provider_display_name == "Tsubasa"
|
||||
assert provider.default_model_placeholder is not None
|
||||
assert get_model_info(provider.default_model_placeholder)["litellm_provider"] == provider.litellm_provider
|
||||
registration: Final = JSONProviderRegistry.get(provider.litellm_provider)
|
||||
assert registration is not None
|
||||
assert {
|
||||
field.key: (field.field_type, field.required, field.default_value) for field in provider.credential_fields
|
||||
} == {
|
||||
"api_key": ("password", True, None),
|
||||
"api_base": ("text", False, registration.base_url),
|
||||
}
|
||||
assert endpoints_response.status_code == 200, endpoints_response.text
|
||||
endpoints: Final = SupportedEndpointsResponse.model_validate(endpoints_response.json()).endpoints
|
||||
assert {
|
||||
endpoint.key for endpoint in endpoints if any(p.slug == provider.litellm_provider for p in endpoint.providers)
|
||||
} == {
|
||||
"chat_completions",
|
||||
"responses",
|
||||
"text_completion",
|
||||
"messages",
|
||||
"interactions",
|
||||
"a2a",
|
||||
"generateContent",
|
||||
}
|
||||
|
|
@ -11,16 +11,33 @@ import {
|
|||
resolveLitellmProviderSlug,
|
||||
} from "./provider_info_helpers";
|
||||
|
||||
const PROVIDER_CREATE_FIELDS: { provider: string; provider_display_name: string; litellm_provider: string }[] =
|
||||
JSON.parse(
|
||||
readFileSync(resolve(__dirname, "../../../../litellm/proxy/public_endpoints/provider_create_fields.json"), "utf8"),
|
||||
);
|
||||
const PROVIDER_CREATE_FIELDS: {
|
||||
provider: string;
|
||||
litellm_provider: string;
|
||||
default_model_placeholder: string;
|
||||
provider_display_name: string;
|
||||
}[] = JSON.parse(
|
||||
readFileSync(resolve(__dirname, "../../../../litellm/proxy/public_endpoints/provider_create_fields.json"), "utf8"),
|
||||
);
|
||||
|
||||
const BUNDLED_MODEL_MAP: Record<string, { litellm_provider?: string }> = JSON.parse(
|
||||
readFileSync(resolve(__dirname, "../../../../model_prices_and_context_window.json"), "utf8"),
|
||||
);
|
||||
|
||||
describe("provider_info_helpers", () => {
|
||||
it("resolves the Tsubasa dashboard selection to its models and placeholder", () => {
|
||||
const provider = PROVIDER_CREATE_FIELDS.find((entry) => entry.litellm_provider === "tsubasa")!;
|
||||
const models = {
|
||||
"tsubasa/tsubasa-fast": { litellm_provider: provider.litellm_provider },
|
||||
"tsubasa/tsubasa-pro": { litellm_provider: provider.litellm_provider },
|
||||
"openai/gpt-4o": { litellm_provider: "openai" },
|
||||
};
|
||||
|
||||
expect(getProviderModels(provider.provider, models)).toEqual(["tsubasa/tsubasa-fast", "tsubasa/tsubasa-pro"]);
|
||||
expect(getPlaceholder(provider.provider)).toBe(provider.default_model_placeholder);
|
||||
expect(getProviderLogoAndName(provider.litellm_provider).displayName).toBe(provider.provider_display_name);
|
||||
});
|
||||
|
||||
it("should identify the upstream LiteLLM Gateway consistently with provider metadata", () => {
|
||||
const metadata = PROVIDER_CREATE_FIELDS.find((provider) => provider.provider === "LITELLM_PROXY");
|
||||
expect(resolveLitellmProviderSlug("LITELLM_PROXY")).toBe(metadata?.litellm_provider);
|
||||
|
|
@ -282,6 +299,7 @@ describe("provider_info_helpers", () => {
|
|||
Providers.PREDIBASE,
|
||||
Providers.Sail,
|
||||
Providers.StreamLake,
|
||||
Providers.Tsubasa,
|
||||
Providers.WANDB,
|
||||
Providers.ZAI,
|
||||
Providers.ZeroGPU,
|
||||
|
|
|
|||
|
|
@ -193,6 +193,7 @@ export enum Providers {
|
|||
TogetherAI = "TogetherAI",
|
||||
TOPAZ = "Topaz",
|
||||
Triton = "Triton",
|
||||
Tsubasa = "Tsubasa",
|
||||
TypeSafe = "TypeSafe",
|
||||
Nace = "Nace",
|
||||
PARALLEL_AI = "Parallel AI",
|
||||
|
|
@ -330,6 +331,7 @@ export const provider_map: Record<string, string> = {
|
|||
TogetherAI: "together_ai",
|
||||
TOPAZ: "topaz",
|
||||
Triton: "triton",
|
||||
Tsubasa: "tsubasa",
|
||||
TypeSafe: "typesafe",
|
||||
Nace: "nace",
|
||||
PARALLEL_AI: "parallel_ai",
|
||||
|
|
@ -522,6 +524,7 @@ const providerPlaceholderMap: Partial<Record<Providers, string>> = {
|
|||
[Providers.Bespoke]: "bespoke/nimble",
|
||||
[Providers.Laya]: "laya/english",
|
||||
[Providers.Snowflake]: "snowflake/mistral-7b",
|
||||
[Providers.Tsubasa]: "tsubasa/tsubasa-pro",
|
||||
[Providers.StrandsDecider]: "strands_decider/strands-decider-2B-hobson-v19",
|
||||
[Providers.Tencent]: "tencent/deepseek-v4-pro",
|
||||
[Providers.TypeSafe]: "typesafe/jev-latest",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue