mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-01 07:03:02 +00:00
fix(streamlake): complete provider discovery and test request flows
This commit is contained in:
parent
18e5b988f6
commit
2b17f1ec88
6 changed files with 178 additions and 1 deletions
|
|
@ -2606,6 +2606,23 @@
|
|||
"messages": true,
|
||||
"responses": true
|
||||
}
|
||||
},
|
||||
"streamlake": {
|
||||
"display_name": "StreamLake (`streamlake`)",
|
||||
"url": "https://docs.litellm.ai/docs/providers/openai_compatible",
|
||||
"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
|
||||
}
|
||||
}
|
||||
},
|
||||
"endpoints": {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import json
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
import respx
|
||||
|
||||
import litellm
|
||||
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
|
||||
|
|
@ -215,6 +218,89 @@ class TestStreamlakeProviderConfig:
|
|||
assert "reasoning_effort" not in supported
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"is_async, api_base",
|
||||
(
|
||||
(False, None),
|
||||
(False, "https://custom.streamlake.example/v1"),
|
||||
(False, "https://custom.streamlake.example/v1/"),
|
||||
(True, None),
|
||||
(True, "https://custom.streamlake.example/v1"),
|
||||
(True, "https://custom.streamlake.example/v1/"),
|
||||
),
|
||||
ids=(
|
||||
"sync-default",
|
||||
"sync-override",
|
||||
"sync-override-trailing-slash",
|
||||
"async-default",
|
||||
"async-override",
|
||||
"async-override-trailing-slash",
|
||||
),
|
||||
)
|
||||
async def test_streamlake_completion_uses_openai_compatible_http(
|
||||
is_async: bool, api_base: str | None, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
response_body: Final[dict[str, object]] = {
|
||||
"id": "chatcmpl-streamlake-test",
|
||||
"object": "chat.completion",
|
||||
"created": 1710000000,
|
||||
"model": "ep-test-endpoint",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {"role": "assistant", "content": "StreamLake response"},
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
],
|
||||
"usage": {"prompt_tokens": 3, "completion_tokens": 2, "total_tokens": 5},
|
||||
}
|
||||
expected_url: Final[str] = f"{(api_base or STREAMLAKE_BASE_URL).rstrip('/')}/chat/completions"
|
||||
expected_request_body: Final[dict[str, object]] = {
|
||||
"messages": [{"role": "user", "content": "hello"}],
|
||||
"model": "ep-test-endpoint",
|
||||
"max_tokens": 512,
|
||||
}
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
assert request.method == "POST"
|
||||
assert str(request.url) == expected_url
|
||||
assert request.headers["authorization"] == "Bearer sk-streamlake-test"
|
||||
assert request.headers["content-type"] == "application/json"
|
||||
assert json.loads(request.content) == expected_request_body
|
||||
return httpx.Response(200, json=response_body, request=request)
|
||||
|
||||
monkeypatch.setattr(litellm, "disable_aiohttp_transport", True)
|
||||
litellm.in_memory_llm_clients_cache.flush_cache()
|
||||
with respx.mock(assert_all_called=True) as respx_mock:
|
||||
route: Final = respx_mock.post(expected_url).mock(side_effect=handler)
|
||||
if is_async:
|
||||
response: Final = await litellm.acompletion(
|
||||
model="streamlake/ep-test-endpoint",
|
||||
messages=[{"role": "user", "content": "hello"}],
|
||||
api_key="sk-streamlake-test",
|
||||
api_base=api_base,
|
||||
max_completion_tokens=512,
|
||||
)
|
||||
else:
|
||||
response: Final = litellm.completion(
|
||||
model="streamlake/ep-test-endpoint",
|
||||
messages=[{"role": "user", "content": "hello"}],
|
||||
api_key="sk-streamlake-test",
|
||||
api_base=api_base,
|
||||
max_completion_tokens=512,
|
||||
)
|
||||
|
||||
assert route.call_count == 1
|
||||
assert len(response.choices) == 1
|
||||
assert response.choices[0].message.role == "assistant"
|
||||
assert response.choices[0].message.content == "StreamLake response"
|
||||
assert response.usage is not None
|
||||
assert response.usage.prompt_tokens == 3
|
||||
assert response.usage.completion_tokens == 2
|
||||
assert response.usage.total_tokens == 5
|
||||
|
||||
|
||||
class TestStreamlakeNoHardcodedEndpointIds:
|
||||
def test_provider_config_never_hardcodes_an_endpoint_id(self):
|
||||
path = Path(litellm.__file__).parent / "llms" / "openai_like" / "providers.json"
|
||||
|
|
|
|||
|
|
@ -963,6 +963,29 @@ def test_get_supported_endpoints_chat_completions_present(reset_endpoints_cache)
|
|||
assert len(chat["providers"]) > 0
|
||||
|
||||
|
||||
def test_get_supported_endpoints_streamlake_only_advertises_chat_completions(
|
||||
reset_endpoints_cache: None,
|
||||
) -> None:
|
||||
app: Final[FastAPI] = FastAPI()
|
||||
app.include_router(router)
|
||||
client: Final[TestClient] = TestClient(app)
|
||||
with client:
|
||||
response: Final = client.get("/public/endpoints")
|
||||
assert response.status_code == 200, response.text
|
||||
|
||||
endpoints: Final = response.json()["endpoints"]
|
||||
streamlake_endpoints: Final = {
|
||||
endpoint["key"]
|
||||
for endpoint in endpoints
|
||||
if any(provider["slug"] == "streamlake" for provider in endpoint["providers"])
|
||||
}
|
||||
assert streamlake_endpoints == {"chat_completions"}
|
||||
|
||||
chat_completions: Final = next(endpoint for endpoint in endpoints if endpoint["key"] == "chat_completions")
|
||||
assert chat_completions["endpoint"] == "/chat/completions"
|
||||
assert "streamlake" in {provider["slug"] for provider in chat_completions["providers"]}
|
||||
|
||||
|
||||
def test_get_supported_endpoints_display_names_have_no_slug_suffix(
|
||||
reset_endpoints_cache,
|
||||
):
|
||||
|
|
|
|||
|
|
@ -22,7 +22,13 @@ vi.mock("@/app/(dashboard)/hooks/uiSettings/usePtuCostAttributionEnabled", () =>
|
|||
usePtuCostAttributionEnabled: () => mockPtuEnabled(),
|
||||
}));
|
||||
|
||||
vi.mock("@/app/(dashboard)/hooks/models/useModelCostMap", () => ({ useModelCostMap: () => ({ data: {} }) }));
|
||||
vi.mock("@/app/(dashboard)/hooks/models/useModelCostMap", () => ({
|
||||
useModelCostMap: () => ({
|
||||
data: {
|
||||
"streamlake/GLM-5.3": { litellm_provider: "streamlake" },
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("@/app/(dashboard)/hooks/credentials/useCredentials", () => ({
|
||||
useCredentials: () => ({ data: { credentials: [] } }),
|
||||
|
|
@ -60,6 +66,13 @@ vi.mock("@/app/(dashboard)/hooks/providers/useProviderFields", () => ({
|
|||
{ key: "api_base", label: "API Base", field_type: "text", required: false },
|
||||
],
|
||||
},
|
||||
{
|
||||
provider: "StreamLake",
|
||||
provider_display_name: "StreamLake",
|
||||
litellm_provider: "streamlake",
|
||||
default_model_placeholder: "streamlake/<model-or-endpoint>",
|
||||
credential_fields: [{ key: "api_key", label: "API Key", field_type: "password", required: false }],
|
||||
},
|
||||
],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
|
|
@ -154,6 +167,27 @@ describe("AddModelPanel submit payload contract", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("creates a StreamLake model from a public model selection and API key", async () => {
|
||||
const { user, submit } = await setup();
|
||||
|
||||
await user.click(screen.getByRole("combobox", { name: /provider/i }));
|
||||
await user.click(await screen.findByText("StreamLake"));
|
||||
await user.click(screen.getByRole("combobox", { name: "Select models" }));
|
||||
await user.click(await screen.findByText("streamlake/GLM-5.3"));
|
||||
await user.type(screen.getByLabelText("API Key"), "sk-streamlake-test");
|
||||
await submit();
|
||||
|
||||
expect(lastCreatedModel()).toStrictEqual({
|
||||
model_name: "streamlake/GLM-5.3",
|
||||
litellm_params: {
|
||||
model: "streamlake/GLM-5.3",
|
||||
custom_llm_provider: "streamlake",
|
||||
api_key: "sk-streamlake-test",
|
||||
},
|
||||
model_info: { ...baseModelInfo },
|
||||
});
|
||||
});
|
||||
|
||||
it("registers four more keys as undefined once Advanced Settings opens", async () => {
|
||||
const { openAdvanced, fillRequired, submit } = await setup();
|
||||
await fillRequired();
|
||||
|
|
|
|||
|
|
@ -176,6 +176,7 @@ describe("provider_info_helpers", () => {
|
|||
Providers.PETALS,
|
||||
Providers.PG_VECTOR,
|
||||
Providers.PREDIBASE,
|
||||
Providers.StreamLake,
|
||||
Providers.WANDB,
|
||||
Providers.ZAI,
|
||||
];
|
||||
|
|
@ -328,6 +329,19 @@ describe("provider_info_helpers", () => {
|
|||
expect(result).toEqual(["gpt-3.5-turbo", "gpt-4"]);
|
||||
});
|
||||
|
||||
it("should return StreamLake models and exclude models from other providers", () => {
|
||||
const modelMap = {
|
||||
"streamlake/GLM-5.3": { litellm_provider: "streamlake" },
|
||||
"streamlake/DeepSeek-V3": { litellm_provider: "streamlake" },
|
||||
"gpt-4o": { litellm_provider: "openai" },
|
||||
};
|
||||
const result = getProviderModels(Providers.StreamLake, modelMap);
|
||||
|
||||
expect(provider_map.StreamLake).toBe("streamlake");
|
||||
expect(result).toEqual(["streamlake/GLM-5.3", "streamlake/DeepSeek-V3"]);
|
||||
expect(result).not.toContain("gpt-4o");
|
||||
});
|
||||
|
||||
it("should return models whose litellm_provider is a prefix-anchored variant of the provider", () => {
|
||||
const modelMap = {
|
||||
"anthropic-text-model": { litellm_provider: "anthropic_text" },
|
||||
|
|
|
|||
|
|
@ -163,6 +163,7 @@ export enum Providers {
|
|||
SCX_AI = "SCX.ai",
|
||||
Snowflake = "Snowflake",
|
||||
Soniox = "Soniox",
|
||||
StreamLake = "StreamLake",
|
||||
TEXT_COMPLETION_CODESTRAL = "Text-Completion-Codestral",
|
||||
TogetherAI = "TogetherAI",
|
||||
TOPAZ = "Topaz",
|
||||
|
|
@ -280,6 +281,7 @@ export const provider_map: Record<string, string> = {
|
|||
SCX_AI: "scx-ai",
|
||||
Snowflake: "snowflake",
|
||||
Soniox: "soniox",
|
||||
StreamLake: "streamlake",
|
||||
TEXT_COMPLETION_CODESTRAL: "text-completion-codestral",
|
||||
TogetherAI: "together_ai",
|
||||
TOPAZ: "topaz",
|
||||
|
|
@ -445,6 +447,7 @@ const providerPlaceholderMap: Partial<Record<Providers, string>> = {
|
|||
[Providers.SageMaker]: "sagemaker/jumpstart-dft-meta-textgeneration-llama-2-7b",
|
||||
[Providers.SCX_AI]: "scx-ai/GLM-5.2",
|
||||
[Providers.Snowflake]: "snowflake/mistral-7b",
|
||||
[Providers.StreamLake]: "streamlake/<model-or-endpoint>",
|
||||
[Providers.Vertex_AI]: "gemini-pro",
|
||||
[Providers.VolcEngine]: "volcengine/<any-model-on-volcengine>",
|
||||
[Providers.Voyage]: "voyage/",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue