From e2bc9d9ec3c2e8cd91911006637711b7b1c79f44 Mon Sep 17 00:00:00 2001 From: wangzengwei <523041966@qq.com> Date: Wed, 3 Jun 2026 22:36:12 +0800 Subject: [PATCH] feat: add Nanobridge as OpenAI-compatible provider --- docs/my-website/docs/providers/nanobridge.md | 93 +++++++++++++++++++ litellm/constants.py | 3 + litellm/llms/openai_like/providers.json | 5 + litellm/types/utils.py | 1 + provider_endpoints_support.json | 38 ++++---- .../openai_like/test_nanobridge_provider.py | 76 +++++++++++++++ 6 files changed, 197 insertions(+), 19 deletions(-) create mode 100644 docs/my-website/docs/providers/nanobridge.md create mode 100644 tests/test_litellm/llms/openai_like/test_nanobridge_provider.py diff --git a/docs/my-website/docs/providers/nanobridge.md b/docs/my-website/docs/providers/nanobridge.md new file mode 100644 index 00000000000..0d544fd6241 --- /dev/null +++ b/docs/my-website/docs/providers/nanobridge.md @@ -0,0 +1,93 @@ +# Nanobridge + +| | | +|---|---| +| **Provider** | `nanobridge` | +| **API key env** | `NANOBRIDGE_API_KEY` | +| **Base override env** | `NANOBRIDGE_API_BASE` (optional) | + +Nanobridge is an OpenAI-compatible API gateway for Tencent Hunyuan model routes (DeepSeek, GLM, MiniMax, etc.). It also exposes an Anthropic Messages-compatible base for Claude Code and similar agents. + +## Quick Start + +```bash +export NANOBRIDGE_API_KEY="your-api-key" +``` + +```python +import litellm + +response = litellm.completion( + model="nanobridge/deepseek-v4-flash", + messages=[{"role": "user", "content": "Hello"}], +) +print(response.choices[0].message.content) +``` + +## Regional endpoints + +Three regions share the same API key and billing. Pick the closest node for lower latency. + +| Region | OpenAI base (`/v1`) | Anthropic base | +|--------|---------------------|----------------| +| Germany (default) | `https://api.nanobridge.net/v1` | `https://api.nanobridge.net/anthropic` | +| Singapore | `https://api-sg.nanobridge.net/v1` | `https://api-sg.nanobridge.net/anthropic` | +| United States | `https://api-us.nanobridge.net/v1` | `https://api-us.nanobridge.net/anthropic` | + +Override the default Germany URL: + +```bash +export NANOBRIDGE_API_BASE="https://api-sg.nanobridge.net/v1" +``` + +## Supported models + +Use `nanobridge/` in LiteLLM: + +| Model ID | Notes | +|----------|--------| +| `deepseek-v4-flash` | Recommended default | +| `deepseek-v4-pro` | | +| `deepseek-v3.2` | | +| `glm-5.1` | Alias: `glm-5-1` | +| `minimax-m2.7` | Alias: `minimax-m-2-7` | + +## API paths + +- Chat: `POST /chat/completions` or `POST /v1/chat/completions` +- Models: `GET /v1/models` +- Anthropic: `POST /v1/messages` on the Anthropic base +- Streaming: `stream: true` (SSE) +- Auth: `Authorization: Bearer ` + +## LiteLLM Proxy + +```yaml +model_list: + - model_name: nanobridge-deepseek-flash + litellm_params: + model: nanobridge/deepseek-v4-flash + api_key: os.environ/NANOBRIDGE_API_KEY + # api_base: os.environ/NANOBRIDGE_API_BASE +``` + +## OpenAI Python SDK + +```python +from openai import OpenAI +import os + +client = OpenAI( + api_key=os.environ["NANOBRIDGE_API_KEY"], + base_url="https://api.nanobridge.net/v1", +) +client.chat.completions.create( + model="deepseek-v4-flash", + messages=[{"role": "user", "content": "Hello"}], +) +``` + +## Links + +- [Nanobridge API documentation](https://platform.nanobridge.net/#/api-docs) +- [Platform console](https://platform.nanobridge.net) diff --git a/litellm/constants.py b/litellm/constants.py index df15050e652..a8590f9a0d7 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -774,6 +774,7 @@ openai_compatible_endpoints: List = [ "https://serverless.tensormesh.ai/v1", "https://api.stima.tech/v1", "https://nano-gpt.com/api/v1", + "https://api.nanobridge.net/v1", "https://api.poe.com/v1", "https://llm.chutes.ai/v1/", "https://api.v0.dev/v1", @@ -824,6 +825,7 @@ openai_compatible_providers: List = [ "tensormesh", # Tensormesh - JSON-configured provider "apertis", # Apertis - JSON-configured provider "nano-gpt", # Nano-GPT - JSON-configured provider + "nanobridge", # Nanobridge - JSON-configured provider "poe", # Poe - JSON-configured provider "chutes", # Chutes - JSON-configured provider "featherless_ai", @@ -860,6 +862,7 @@ openai_text_completion_compatible_providers: List = ( "tensormesh", "apertis", "nano-gpt", + "nanobridge", "poe", "chutes", "v0", diff --git a/litellm/llms/openai_like/providers.json b/litellm/llms/openai_like/providers.json index c9257677fd1..2e3af61d420 100644 --- a/litellm/llms/openai_like/providers.json +++ b/litellm/llms/openai_like/providers.json @@ -1,4 +1,9 @@ { + "nanobridge": { + "base_url": "https://api.nanobridge.net/v1", + "api_key_env": "NANOBRIDGE_API_KEY", + "api_base_env": "NANOBRIDGE_API_BASE" + }, "publicai": { "base_url": "https://api.publicai.co/v1", "api_key_env": "PUBLICAI_API_KEY", diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 63c2513aed2..56a5be9b7bb 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -3369,6 +3369,7 @@ class LlmProviders(str, Enum): SYNTHETIC = "synthetic" APERTIS = "apertis" NANOGPT = "nano-gpt" + NANOBRIDGE = "nanobridge" POE = "poe" CHUTES = "chutes" XIAOMI_MIMO = "xiaomi_mimo" diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index 3a01541060f..74ebf04b0ae 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -488,9 +488,7 @@ "audio_speech": false, "moderations": false, "batches": false, - "rerank": false, - "a2a": false, - "interactions": false + "rerank": false } }, "chutes": { @@ -1592,6 +1590,24 @@ "interactions": true } }, + "nanobridge": { + "display_name": "Nanobridge (`nanobridge`)", + "url": "https://platform.nanobridge.net/#/api-docs", + "endpoints": { + "chat_completions": true, + "messages": true, + "responses": false, + "embeddings": false, + "image_generations": false, + "audio_transcriptions": false, + "audio_speech": false, + "moderations": false, + "batches": false, + "rerank": false, + "a2a": false, + "interactions": false + } + }, "nvidia_nim": { "display_name": "Nvidia NIM (`nvidia_nim`)", "url": "https://docs.litellm.ai/docs/providers/nvidia_nim", @@ -2664,22 +2680,6 @@ "batches": false, "rerank": false } - }, - "charity_engine": { - "display_name": "Charity Engine (`charity_engine`)", - "url": "https://docs.litellm.ai/docs/providers/charity_engine", - "endpoints": { - "chat_completions": true, - "messages": true, - "responses": true, - "embeddings": false, - "image_generations": false, - "audio_transcriptions": false, - "audio_speech": false, - "moderations": false, - "batches": false, - "rerank": false - } } }, "endpoints": { diff --git a/tests/test_litellm/llms/openai_like/test_nanobridge_provider.py b/tests/test_litellm/llms/openai_like/test_nanobridge_provider.py new file mode 100644 index 00000000000..67389517461 --- /dev/null +++ b/tests/test_litellm/llms/openai_like/test_nanobridge_provider.py @@ -0,0 +1,76 @@ +""" +Unit tests for Nanobridge JSON-configured OpenAI-compatible provider. +""" + +import os +import sys + +try: + import pytest +except ImportError: + pytest = None + +workspace_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../..")) +sys.path.insert(0, workspace_path) + +import litellm +from litellm import LlmProviders +from litellm.llms.openai_like.json_loader import JSONProviderRegistry +from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + +class TestNanobridgeProvider: + def test_nanobridge_in_llm_providers_enum(self): + assert hasattr(LlmProviders, "NANOBRIDGE") + assert LlmProviders.NANOBRIDGE.value == "nanobridge" + + def test_nanobridge_json_config_exists(self): + assert JSONProviderRegistry.exists("nanobridge") + cfg = JSONProviderRegistry.get("nanobridge") + assert cfg is not None + assert cfg.base_url == "https://api.nanobridge.net/v1" + assert cfg.api_key_env == "NANOBRIDGE_API_KEY" + assert cfg.api_base_env == "NANOBRIDGE_API_BASE" + + def test_nanobridge_provider_resolution(self): + model, provider, _api_key, api_base = get_llm_provider( + model="nanobridge/deepseek-v4-flash", + custom_llm_provider=None, + api_base=None, + api_key=None, + ) + assert model == "deepseek-v4-flash" + assert provider == "nanobridge" + assert api_base == "https://api.nanobridge.net/v1" + + def test_nanobridge_api_base_env_override(self, monkeypatch): + monkeypatch.setenv("NANOBRIDGE_API_BASE", "https://api-sg.nanobridge.net/v1") + _model, _provider, _api_key, api_base = get_llm_provider( + model="nanobridge/deepseek-v4-flash", + custom_llm_provider=None, + api_base=None, + api_key=None, + ) + assert api_base == "https://api-sg.nanobridge.net/v1" + + def test_nanobridge_completion_mocked(self): + response = litellm.completion( + model="nanobridge/deepseek-v4-flash", + messages=[{"role": "user", "content": "ping"}], + mock_response="PONG", + ) + assert response.choices[0].message.content == "PONG" + + +@pytest.mark.skipif( + not os.environ.get("NANOBRIDGE_API_KEY"), + reason="NANOBRIDGE_API_KEY not set", +) +class TestNanobridgeLive: + def test_nanobridge_live_chat_completions(self): + response = litellm.completion( + model="nanobridge/deepseek-v4-flash", + messages=[{"role": "user", "content": "Reply with exactly: ok"}], + max_tokens=16, + ) + assert response.choices[0].message.content