Add a Provider

Corresponding source

Extension stepSource
Configuration fields and Provider namenanobot/config/schema.py, nanobot/providers/registry.py
Provider construction dispatchnanobot/providers/factory.py::_make_provider_core, make_provider
Common response, Tool Call, streaming, and retry contractnanobot/providers/base.py::LLMProvider, LLMResponse, ToolCallRequest
Settings-page synchronizationnanobot/webui/settings_api.py, webui/src/lib/provider-brand.ts
Existing Provider test patternstests/providers/

Learning objectives

After reading this chapter, you should be able to:

  • decide whether configuring the existing custom Provider is sufficient;
  • register an OpenAI-compatible service with the smallest necessary change;
  • recognize when a new Provider backend is required;
  • design normalization tests for messages, Tool Calls, streaming, reasoning, usage, and errors.

Prerequisites

  • Understand Provider resolution and fallback.
  • Know that “OpenAI-compatible” only means the API shapes are similar; it does not guarantee identical fields or stream events.
  • Be able to read asynchronous HTTP or SDK calls.

1. Choose the smallest extension layer first

flowchart TD
    A["Integrate a model service"] --> B{"Can an existing Provider be configured?"}
    B -->|"Yes"| C["Add only config or a preset"]
    B -->|"No"| D{"Is it OpenAI-compatible?"}
    D -->|"Largely compatible"| E["ProviderSpec + ProvidersConfig"]
    D -->|"Different protocol, auth, or response"| F["New backend + factory branch"]

DeepSeek is already present in the current registry, so it does not require a new Provider.

2. Configure custom only

If a service accepts a custom apiBase, API key, and model, and OpenAICompatProvider can handle it correctly, try provider: custom first.

This layer does not modify nanobot source code. It suits local vLLM deployments, enterprise gateways, and endpoints that have not yet been added to the registry. Add a ProviderSpec only when you need automatic detection, status reporting, a default base URL, or service-specific request adaptation.

3. Minimum changes for an OpenAI-compatible Provider

The current official development documentation identifies two required changes.

nanobot/providers/registry.py

ProviderSpec(
    name="myprovider",
    keywords=("myprovider", "mymodel"),
    env_key="MYPROVIDER_API_KEY",
    display_name="My Provider",
    default_api_base="https://api.myprovider.example/v1",
)

nanobot/config/schema.py

Add a field to ProvidersConfig:

myprovider: ProviderConfig = Field(default_factory=ProviderConfig)

The registry then supplies shared metadata for environment variables, automatic detection, status reporting, and some WebUI credential surfaces.

4. ProviderSpec is not merely display metadata

Its fields affect real requests. Common fields include:

  • backend
  • default_api_base
  • is_gateway
  • is_local
  • is_direct
  • is_oauth
  • detect_by_key_prefix
  • detect_by_base_keyword
  • strip_model_prefix
  • supports_max_completion_tokens
  • model_overrides
  • thinking_style
  • gateway_reasoning_style
  • reasoning_as_content
  • reasoning_effort_remap
  • fields that control reasoning-history handling.

The order of PROVIDERS also affects match priority. Source comments explicitly require gateways to come first. A new keyword must not be overly broad, or another model may be misclassified as this Provider.

5. When a new backend is necessary

The following requirements usually exceed what ProviderSpec can express:

  • a completely different authentication protocol;
  • non-OpenAI message or Tool schemas;
  • unique streaming events;
  • behavior that only a native SDK handles correctly;
  • Tool Call, reasoning, or usage shapes that the existing adapter cannot normalize;
  • dedicated request signing, region/profile selection, or an OAuth lifecycle.

In that case:

  1. implement an LLMProvider subclass;
  2. implement at least chat() and get_default_model();
  3. override chat_stream() when necessary;
  4. declare the new backend in ProviderSpec.backend;
  5. add a construction branch to nanobot/providers/factory.py::_make_provider_core();
  6. update exports, configuration, tests, and documentation.

6. Responses that must be normalized

A Provider ultimately constructs an LLMResponse. Its important fields include:

  • content
  • tool_calls
  • finish_reason
  • usage
  • reasoning_content
  • error status, code, and retry metadata
  • recovery clues such as a stream stall.

The Runner should not need to know whether an upstream API calls a tool invocation function_call, content_block, or something else. The adapter layer exists to contain those differences at the Provider boundary.

7. Tool Call tests

At minimum, verify that the Provider can:

  1. send nanobot’s function schema;
  2. parse one complete Tool Call;
  3. reassemble streamed argument fragments correctly;
  4. preserve stable IDs and names across multiple Tool Calls;
  5. send tool-result history back to the model;
  6. turn invalid arguments into a diagnosable error rather than silently dropping them.

A “hello → text response” test does not establish that a Provider can support an Agent.

8. Reasoning and thinking

A model may:

  • return a separate reasoning_content field;
  • place thinking in content blocks;
  • require an explicit request switch;
  • reject OpenAI’s reasoning_effort;
  • require reasoning fields to be retained in, or removed from, history.

Determine the wire shape from the service’s official API documentation and real fixtures before choosing ProviderSpec fields. Never infer thinking compatibility from the model name alone.

9. Errors and retries

A Provider should populate structured information whenever possible:

  • HTTP status;
  • Provider error code or type;
  • retry-after;
  • error_should_retry.

This information lets LLMProvider._run_with_retry() and FallbackProvider distinguish among:

  • transient 429 or 5xx responses;
  • authentication failures;
  • quota or billing failures;
  • content filtering;
  • context-length failures;
  • malformed requests.

Converting every exception to plain text discards information needed for retry and fallback decisions.

10. Keep configuration and UI in sync

A minimal OpenAI-compatible Provider usually touches:

  • ProviderSpec
  • ProvidersConfig
  • registry and configuration tests
  • Provider or configuration documentation.

A dedicated brand, model list, OAuth flow, or special settings may additionally involve:

  • nanobot/webui/settings_api.py
  • webui/src/lib/provider-brand.ts
  • corresponding Python and TypeScript tests
  • i18n strings.

Add those branches only when the product genuinely needs a specialized UI. General-purpose Providers should remain registry-driven where possible.

11. Test references

Simple registry Providers:

  • tests/providers/test_novita_provider.py
  • tests/providers/test_longcat_provider.py
  • tests/providers/test_mistral_provider.py

General adaptation:

  • tests/providers/test_openai_compat_timeout.py
  • tests/providers/test_provider_retry.py
  • tests/providers/test_reasoning_content.py

Native backends:

  • tests/providers/test_azure_openai_provider.py
  • tests/providers/test_bedrock_provider.py
  • tests/providers/test_openai_codex_provider.py

12. Completion criteria

  • Configuration parses successfully.
  • The factory selects the intended backend.
  • API key, base URL, and model are correct.
  • Non-streaming and streaming text both work.
  • A Tool Call can make a complete round trip.
  • Reasoning and usage are normalized correctly.
  • Error metadata can drive retry and fallback decisions.
  • Unit tests pass without sending real credentials.
  • Only then is one minimal real-request smoke test performed.

Check your understanding

  1. Why might an OpenAI-compatible endpoint still be unusable by an Agent without adaptation?
  2. Why does the order of ProviderSpec entries matter?
  3. Which differences belong inside a Provider instead of Runner branches?
  4. Why must Provider tests cover Tool Calls rather than only text responses?

Next: Add a Channel