From 48e76ab45ca98ba3dbe5899dfb8a21253507adfa Mon Sep 17 00:00:00 2001 From: netarz Date: Sun, 25 Oct 2026 17:24:43 +0400 Subject: [PATCH] feat(gisoo): add Gisoo as an OpenAI-compatible provider --- .../src/exception_mapping_utils/original.rs | 1 + litellm/constants.py | 2 + litellm/llms/openai_like/providers.json | 10 ++ .../provider_endpoints_support_backup.json | 17 +++ .../provider_create_fields.json | 28 +++++ litellm/types/utils.py | 1 + provider_endpoints_support.json | 17 +++ .../llms/openai_like/test_gisoo_provider.py | 117 ++++++++++++++++++ 8 files changed, 193 insertions(+) create mode 100644 tests/unit/llms/openai_like/test_gisoo_provider.py diff --git a/litellm-rust/crates/core-utils/src/exception_mapping_utils/original.rs b/litellm-rust/crates/core-utils/src/exception_mapping_utils/original.rs index 4de67e6e7b5..e19d6e5dfae 100644 --- a/litellm-rust/crates/core-utils/src/exception_mapping_utils/original.rs +++ b/litellm-rust/crates/core-utils/src/exception_mapping_utils/original.rs @@ -95,6 +95,7 @@ const OPENAI_COMPATIBLE_PROVIDERS: &[&str] = &[ "ragflow", "pinstripes", "openinfer", + "gisoo", "darkbloom", "meta", "cognition", diff --git a/litellm/constants.py b/litellm/constants.py index d1daf2ee2d1..ae98989bc20 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -1062,6 +1062,7 @@ openai_compatible_endpoints: Final[list[str]] = [ "https://api.reka.ai/v1", "https://gigachat.devices.sberbank.ru/api/v1", "https://api.openinfer.ai/v1", + "https://gisoo.pro/api/v1", ] @@ -1141,6 +1142,7 @@ openai_compatible_providers: Final[list[str]] = [ "prism", "sail", "reka", + "gisoo", ] OPENAI_AUDIO_TRANSCRIPTION_PROVIDERS: Final = frozenset({"openai"} | frozenset(openai_compatible_providers)) diff --git a/litellm/llms/openai_like/providers.json b/litellm/llms/openai_like/providers.json index 36337fddbdc..cdaf1133bbf 100644 --- a/litellm/llms/openai_like/providers.json +++ b/litellm/llms/openai_like/providers.json @@ -225,6 +225,16 @@ "api_base_env": "SAIL_API_BASE", "supported_endpoints": ["/v1/chat/completions", "/v1/responses", "/v1/messages"] }, + "gisoo": { + "base_url": "https://gisoo.pro/api/v1", + "api_key_env": "GISOO_API_KEY", + "api_base_env": "GISOO_API_BASE", + "base_class": "openai_gpt", + "provider_info": { + "supports_function_calling": true + }, + "supported_endpoints": ["/v1/chat/completions", "/v1/responses"] + }, "reka": { "base_url": "https://api.reka.ai/v1", "api_key_env": "REKA_API_KEY", diff --git a/litellm/provider_endpoints_support_backup.json b/litellm/provider_endpoints_support_backup.json index f95b553c2b4..e7d21c2e21b 100644 --- a/litellm/provider_endpoints_support_backup.json +++ b/litellm/provider_endpoints_support_backup.json @@ -2748,6 +2748,23 @@ "rerank": false, "a2a": false } + }, + "gisoo": { + "display_name": "Gisoo (`gisoo`)", + "url": "https://gisoo.pro/docs/litellm", + "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 + } } }, "endpoints": { diff --git a/litellm/proxy/public_endpoints/provider_create_fields.json b/litellm/proxy/public_endpoints/provider_create_fields.json index 19ee465c7dc..9e02152ae27 100644 --- a/litellm/proxy/public_endpoints/provider_create_fields.json +++ b/litellm/proxy/public_endpoints/provider_create_fields.json @@ -2791,6 +2791,34 @@ ], "default_model_placeholder": "openinfer/@oi/Qwen3.5-27B" }, + { + "provider": "GISOO", + "provider_display_name": "Gisoo", + "litellm_provider": "gisoo", + "credential_fields": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": "https://gisoo.pro/api/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": "gisoo/claude-sonnet-5-5" + }, { "provider": "OPENAI_LIKE", "provider_display_name": "Openai Like", diff --git a/litellm/types/utils.py b/litellm/types/utils.py index cc7ef851111..d3d4b01633d 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -4586,6 +4586,7 @@ class LlmProviders(str, Enum): META = "meta" SAIL = "sail" REKA = "reka" + GISOO = "gisoo" LITELLM_AGENT = "litellm_agent" CURSOR = "cursor" BEDROCK_MANTLE = "bedrock_mantle" diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index d65da3f41fd..0cdb4c0da1a 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -3272,6 +3272,23 @@ "rerank": false, "a2a": false } + }, + "gisoo": { + "display_name": "Gisoo (`gisoo`)", + "url": "https://gisoo.pro/docs/litellm", + "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 + } } }, "endpoints": { diff --git a/tests/unit/llms/openai_like/test_gisoo_provider.py b/tests/unit/llms/openai_like/test_gisoo_provider.py new file mode 100644 index 00000000000..0b586d0687b --- /dev/null +++ b/tests/unit/llms/openai_like/test_gisoo_provider.py @@ -0,0 +1,117 @@ +import json +from pathlib import Path +from typing import Final + +import pytest +import respx + +import litellm + +_CHAT_URL: Final = "https://gisoo.pro/api/v1/chat/completions" +_CHAT_RESPONSE: Final = { + "id": "chatcmpl_gisoo", + "object": "chat.completion", + "created": 1_792_000_000, + "model": "claude-sonnet-5-5", + "choices": [{"index": 0, "message": {"role": "assistant", "content": "Hello from Gisoo"}, "finish_reason": "stop"}], + "usage": {"prompt_tokens": 4, "completion_tokens": 3, "total_tokens": 7}, +} + + +def test_gisoo_is_a_registered_provider(): + assert litellm.LlmProviders.GISOO.value == "gisoo" + assert "gisoo" in litellm.provider_list + assert "gisoo" in litellm.constants.openai_compatible_providers + + +def test_gisoo_provider_resolution(monkeypatch: pytest.MonkeyPatch): + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + monkeypatch.delenv("GISOO_API_BASE", raising=False) + monkeypatch.setenv("GISOO_API_KEY", "gisoo-test-key") + + model, provider, api_key, api_base = get_llm_provider( + model="gisoo/claude-sonnet-5-5", custom_llm_provider=None, api_base=None, api_key=None + ) + + assert model == "claude-sonnet-5-5" + assert provider == "gisoo" + assert api_key == "gisoo-test-key" + assert api_base == "https://gisoo.pro/api/v1" + + +def test_gisoo_api_base_env_overrides_default(monkeypatch: pytest.MonkeyPatch): + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + monkeypatch.setenv("GISOO_API_KEY", "gisoo-test-key") + monkeypatch.setenv("GISOO_API_BASE", "https://gisoo.example/api/v1") + + _, _, _, api_base = get_llm_provider( + model="gisoo/claude-sonnet-5-5", custom_llm_provider=None, api_base=None, api_key=None + ) + + assert api_base == "https://gisoo.example/api/v1" + + +def test_gisoo_chat_completion_request(): + with respx.mock() as upstream: + route: Final = upstream.post(_CHAT_URL).respond(200, json=_CHAT_RESPONSE) + response: Final = litellm.completion( + model="gisoo/claude-sonnet-5-5", + messages=[{"role": "user", "content": "Say hello"}], + api_key="gisoo-test-key", + ) + + request: Final = route.calls.last.request + body: Final = json.loads(request.content) + assert str(request.url) == _CHAT_URL + assert request.headers["authorization"] == "Bearer gisoo-test-key" + assert body["model"] == "claude-sonnet-5-5" + assert response.choices[0].message.content == "Hello from Gisoo" + + +def test_gisoo_responses_request(): + with respx.mock() as upstream: + route: Final = upstream.post("https://gisoo.pro/api/v1/responses").respond( + 200, + json={ + "id": "resp_gisoo", + "object": "response", + "created_at": 1_792_000_000, + "model": "claude-sonnet-5-5", + "status": "completed", + "output": [ + { + "id": "msg_gisoo", + "type": "message", + "role": "assistant", + "status": "completed", + "content": [{"type": "output_text", "text": "Hello from Gisoo", "annotations": []}], + } + ], + "usage": {"input_tokens": 4, "output_tokens": 3, "total_tokens": 7}, + }, + ) + response: Final = litellm.responses(model="gisoo/claude-sonnet-5-5", input="Say hello", api_key="gisoo-test-key") + + body: Final = json.loads(route.calls.last.request.content) + assert route.call_count == 1 + assert body["model"] == "claude-sonnet-5-5" + assert response.output[0].content[0].text == "Hello from Gisoo" + + +def test_gisoo_is_available_in_add_model_form(): + fields_path = Path(litellm.__file__).parent / "proxy" / "public_endpoints" / "provider_create_fields.json" + gisoo = next(p for p in json.loads(fields_path.read_text()) if p["litellm_provider"] == "gisoo") + + assert gisoo["provider"] == "GISOO" + assert {f["key"]: f["required"] for f in gisoo["credential_fields"]} == {"api_base": False, "api_key": True} + + +def test_gisoo_supported_endpoints(): + root = Path(litellm.__file__).parent + for path in (root.parent / "provider_endpoints_support.json", root / "provider_endpoints_support_backup.json"): + endpoints = json.loads(path.read_text())["providers"]["gisoo"]["endpoints"] + assert endpoints["chat_completions"] is True + assert endpoints["responses"] is True + assert endpoints["messages"] is False