feat: add Atomic Chat as OpenAI-compatible local provider

Register atomic_chat in the provider registry with default base URL
http://localhost:1337/v1, schema field, docs, and config tests.
This commit is contained in:
yanalialiuk 2026-05-11 19:26:54 +03:00 committed by Xubin Ren
parent 9ccef018c2
commit 18072856ec
4 changed files with 67 additions and 0 deletions

View File

@ -80,6 +80,7 @@ IMAP_PASSWORD=your-password-here
| `longcat` | LLM (LongCat) | [longcat.chat](https://longcat.chat/platform/docs/zh/) | | `longcat` | LLM (LongCat) | [longcat.chat](https://longcat.chat/platform/docs/zh/) |
| `ollama` | LLM (local, Ollama) | — | | `ollama` | LLM (local, Ollama) | — |
| `lm_studio` | LLM (local, LM Studio) | — | | `lm_studio` | LLM (local, LM Studio) | — |
| `atomic_chat` | LLM (local, [Atomic Chat](https://atomic.chat/)) | — |
| `mistral` | LLM | [docs.mistral.ai](https://docs.mistral.ai/) | | `mistral` | LLM | [docs.mistral.ai](https://docs.mistral.ai/) |
| `stepfun` | LLM (Step Fun/阶跃星辰) | [platform.stepfun.com](https://platform.stepfun.com) | | `stepfun` | LLM (Step Fun/阶跃星辰) | [platform.stepfun.com](https://platform.stepfun.com) |
| `ovms` | LLM (local, OpenVINO Model Server) | [docs.openvino.ai](https://docs.openvino.ai/2026/model-server/ovms_docs_llm_quickstart.html) | | `ovms` | LLM (local, OpenVINO Model Server) | [docs.openvino.ai](https://docs.openvino.ai/2026/model-server/ovms_docs_llm_quickstart.html) |
@ -502,6 +503,36 @@ ollama run llama3.2
</details> </details>
<details>
<summary><b>Atomic Chat (local)</b></summary>
[Atomic Chat](https://atomic.chat/) is a local-first desktop app that exposes an **OpenAI-compatible** HTTP API (default `http://localhost:1337/v1`). Start Atomic Chat and enable the local API server, then point nanobot at it.
**1. Add to config** (partial — merge into `~/.nanobot/config.json`):
```json
{
"providers": {
"atomic_chat": {
"apiKey": null,
"apiBase": "http://localhost:1337/v1"
}
},
"agents": {
"defaults": {
"provider": "atomic_chat",
"model": "your-model-id-from-atomic-chat"
}
}
}
```
> **Note:** Set `apiKey` to `null` if your Atomic Chat server does not require a key. If it does, set `apiKey` (or the `ATOMIC_CHAT_API_KEY` environment variable) to the value Atomic Chat expects. The `model` string must match the model id Atomic Chat exposes on its OpenAI-compatible endpoint.
> `provider: "auto"` also works when `providers.atomic_chat.apiBase` is configured, but setting `"provider": "atomic_chat"` is the clearest option.
</details>
<details> <details>
<summary><b>OpenVINO Model Server (local / OpenAI-compatible)</b></summary> <summary><b>OpenVINO Model Server (local / OpenAI-compatible)</b></summary>

View File

@ -197,6 +197,7 @@ class ProvidersConfig(Base):
vllm: ProviderConfig = Field(default_factory=ProviderConfig) vllm: ProviderConfig = Field(default_factory=ProviderConfig)
ollama: ProviderConfig = Field(default_factory=ProviderConfig) # Ollama local models ollama: ProviderConfig = Field(default_factory=ProviderConfig) # Ollama local models
lm_studio: ProviderConfig = Field(default_factory=ProviderConfig) # LM Studio local models lm_studio: ProviderConfig = Field(default_factory=ProviderConfig) # LM Studio local models
atomic_chat: ProviderConfig = Field(default_factory=ProviderConfig) # Atomic Chat local models
ovms: ProviderConfig = Field(default_factory=ProviderConfig) # OpenVINO Model Server (OVMS) ovms: ProviderConfig = Field(default_factory=ProviderConfig) # OpenVINO Model Server (OVMS)
gemini: ProviderConfig = Field(default_factory=ProviderConfig) gemini: ProviderConfig = Field(default_factory=ProviderConfig)
moonshot: ProviderConfig = Field(default_factory=ProviderConfig) moonshot: ProviderConfig = Field(default_factory=ProviderConfig)

View File

@ -422,6 +422,17 @@ PROVIDERS: tuple[ProviderSpec, ...] = (
detect_by_base_keyword="1234", detect_by_base_keyword="1234",
default_api_base="http://localhost:1234/v1", default_api_base="http://localhost:1234/v1",
), ),
# Atomic Chat (local, OpenAI-compatible) — https://atomic.chat/
ProviderSpec(
name="atomic_chat",
keywords=("atomic-chat", "atomic_chat", "atomicchat"),
env_key="ATOMIC_CHAT_API_KEY",
display_name="Atomic Chat",
backend="openai_compat",
is_local=True,
detect_by_base_keyword="1337",
default_api_base="http://localhost:1337/v1",
),
# === OpenVINO Model Server (direct, local, OpenAI-compatible at /v3) === # === OpenVINO Model Server (direct, local, OpenAI-compatible at /v3) ===
ProviderSpec( ProviderSpec(
name="ovms", name="ovms",

View File

@ -371,6 +371,28 @@ def test_config_accepts_lm_studio_without_api_key_and_uses_default_localhost_api
assert config.get_api_base() == "http://localhost:1234/v1" assert config.get_api_base() == "http://localhost:1234/v1"
def test_config_accepts_atomic_chat_without_api_key_and_uses_default_localhost_api_base():
config = Config.model_validate(
{
"agents": {
"defaults": {
"provider": "atomic_chat",
"model": "local-model",
}
},
"providers": {
"atomicChat": {
"apiKey": None,
}
},
}
)
assert config.get_provider_name() == "atomic_chat"
assert config.get_api_key() is None
assert config.get_api_base() == "http://localhost:1337/v1"
def test_find_by_name_accepts_camel_case_and_hyphen_aliases(): def test_find_by_name_accepts_camel_case_and_hyphen_aliases():
assert find_by_name("volcengineCodingPlan") is not None assert find_by_name("volcengineCodingPlan") is not None
assert find_by_name("volcengineCodingPlan").name == "volcengine_coding_plan" assert find_by_name("volcengineCodingPlan").name == "volcengine_coding_plan"
@ -378,6 +400,8 @@ def test_find_by_name_accepts_camel_case_and_hyphen_aliases():
assert find_by_name("github-copilot").name == "github_copilot" assert find_by_name("github-copilot").name == "github_copilot"
assert find_by_name("longcat") is not None assert find_by_name("longcat") is not None
assert find_by_name("longcat").name == "longcat" assert find_by_name("longcat").name == "longcat"
assert find_by_name("atomic-chat") is not None
assert find_by_name("atomic-chat").name == "atomic_chat"
def test_config_explicit_longcat_provider_resolves_provider_name(): def test_config_explicit_longcat_provider_resolves_provider_name():