From e5476573f4a5b6b3e4367bc613e6050a6b89ba90 Mon Sep 17 00:00:00 2001 From: Alex-wuhu Date: Wed, 20 May 2026 16:38:57 +0800 Subject: [PATCH] test(providers): align Novita provider coverage --- docs/configuration.md | 2 +- nanobot/providers/registry.py | 3 +- tests/config/test_model_presets.py | 5 +- tests/providers/test_novita_provider.py | 78 +++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 tests/providers/test_novita_provider.py diff --git a/docs/configuration.md b/docs/configuration.md index 17f73619e..f309f5376 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -148,7 +148,7 @@ ANTHROPIC_API_KEY="$(bw get password api/anthropic)" nanobot agent | `gemini` | LLM (Gemini direct) | [aistudio.google.com](https://aistudio.google.com) | | `aihubmix` | LLM (API gateway, access to all models) | [aihubmix.com](https://aihubmix.com) | | `siliconflow` | LLM (SiliconFlow/硅基流动) | [siliconflow.cn](https://siliconflow.cn) | -| `novita` | LLM (Novita AI, OpenAI-compatible gateway) | [novita.ai](https://novita.ai) | +| `novita` | LLM (NovitaAI Model API: 200+ models on an AI-native cloud for builders and agents) | [novita.ai](https://novita.ai) | | `dashscope` | LLM (Qwen) | [dashscope.console.aliyun.com](https://dashscope.console.aliyun.com) | | `moonshot` | LLM (Moonshot/Kimi) | [platform.moonshot.cn](https://platform.moonshot.cn) | | `zhipu` | LLM (Zhipu GLM) | [open.bigmodel.cn](https://open.bigmodel.cn) | diff --git a/nanobot/providers/registry.py b/nanobot/providers/registry.py index ab7e2cf1e..04a3d3757 100644 --- a/nanobot/providers/registry.py +++ b/nanobot/providers/registry.py @@ -199,7 +199,8 @@ PROVIDERS: tuple[ProviderSpec, ...] = ( default_api_base="https://api.siliconflow.cn/v1", ), - # Novita AI: OpenAI-compatible gateway for hosted model APIs. + # NovitaAI: AI-native cloud for builders and agents. Model API exposes + # 200+ models through an OpenAI-compatible gateway. ProviderSpec( name="novita", keywords=("novita",), diff --git a/tests/config/test_model_presets.py b/tests/config/test_model_presets.py index 4786326a9..fe01c2547 100644 --- a/tests/config/test_model_presets.py +++ b/tests/config/test_model_presets.py @@ -194,14 +194,15 @@ def test_match_provider_uses_preset_provider_when_forced() -> None: assert name == "anthropic" -def test_match_provider_routes_novita_prefixed_models() -> None: +def test_match_provider_routes_forced_novita_model_api_models() -> None: config = Config.model_validate({ "providers": { "novita": {"apiKey": "sk-test"}, }, "agents": { "defaults": { - "model": "novita/deepseek/deepseek-v4-pro", + "model": "deepseek-v4-pro", + "provider": "novita", } }, }) diff --git a/tests/providers/test_novita_provider.py b/tests/providers/test_novita_provider.py new file mode 100644 index 000000000..820529e34 --- /dev/null +++ b/tests/providers/test_novita_provider.py @@ -0,0 +1,78 @@ +"""Tests for the Novita AI provider registration.""" + +from unittest.mock import patch + +from nanobot.config.schema import Config, ProvidersConfig +from nanobot.providers.openai_compat_provider import OpenAICompatProvider +from nanobot.providers.registry import PROVIDERS, find_by_name + + +def test_novita_config_field_exists() -> None: + config = ProvidersConfig() + + assert hasattr(config, "novita") + + +def test_novita_provider_in_registry() -> None: + specs = {spec.name: spec for spec in PROVIDERS} + + assert "novita" in specs + novita = specs["novita"] + assert novita.backend == "openai_compat" + assert novita.env_key == "NOVITA_API_KEY" + assert novita.display_name == "Novita AI" + assert novita.is_gateway is True + assert novita.detect_by_base_keyword == "novita" + assert novita.default_api_base == "https://api.novita.ai/openai" + assert novita.strip_model_prefix is False + + +def test_find_by_name_novita() -> None: + spec = find_by_name("novita") + + assert spec is not None + assert spec.name == "novita" + + +def test_novita_forced_provider_uses_default_api_base() -> None: + config = Config.model_validate({ + "providers": { + "novita": { + "apiKey": "novita-key", + }, + }, + "agents": { + "defaults": { + "model": "deepseek-v4-pro", + "provider": "novita", + }, + }, + }) + + assert config.get_provider_name("deepseek-v4-pro") == "novita" + assert config.get_api_key("deepseek-v4-pro") == "novita-key" + assert config.get_api_base("deepseek-v4-pro") == "https://api.novita.ai/openai" + + +def test_novita_preserves_model_api_id() -> None: + spec = find_by_name("novita") + with patch("nanobot.providers.openai_compat_provider.AsyncOpenAI"): + provider = OpenAICompatProvider( + api_key="novita-key", + default_model="deepseek-v4-pro", + spec=spec, + ) + + kwargs = provider._build_kwargs( + messages=[{"role": "user", "content": "hi"}], + tools=None, + model="deepseek-v4-pro", + max_tokens=1024, + temperature=0.7, + reasoning_effort=None, + tool_choice=None, + ) + + assert kwargs["model"] == "deepseek-v4-pro" + assert kwargs["max_tokens"] == 1024 + assert "max_completion_tokens" not in kwargs