diff --git a/README.md b/README.md
index d15db3ee..03be9477 100644
--- a/README.md
+++ b/README.md
@@ -957,6 +957,7 @@ IMAP_PASSWORD=your-password-here
| `zhipu` | LLM (Zhipu GLM) | [open.bigmodel.cn](https://open.bigmodel.cn) |
| `mimo` | LLM (MiMo) | [platform.xiaomimimo.com](https://platform.xiaomimimo.com) |
| `ollama` | LLM (local, Ollama) | — |
+| `lm_studio` | LLM (local, LM Studio) | — |
| `mistral` | LLM | [docs.mistral.ai](https://docs.mistral.ai/) |
| `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) |
@@ -1044,7 +1045,7 @@ nanobot agent -c ~/.nanobot-telegram/config.json -w /tmp/nanobot-telegram-test -
Custom Provider (Any OpenAI-compatible API)
-Connects directly to any OpenAI-compatible endpoint — LM Studio, llama.cpp, Together AI, Fireworks, Azure OpenAI, or any self-hosted server. Model name is passed as-is.
+Connects directly to any OpenAI-compatible endpoint — llama.cpp, Together AI, Fireworks, Azure OpenAI, or any self-hosted server. Model name is passed as-is.
```json
{
@@ -1062,7 +1063,7 @@ Connects directly to any OpenAI-compatible endpoint — LM Studio, llama.cpp, To
}
```
-> For local servers that don't require a key, set `apiKey` to any non-empty string (e.g. `"no-key"`).
+> For local servers that don't require authentication, set `apiKey` to `null`.
>
> `custom` is the right choice for providers that expose an OpenAI-compatible **chat completions** API. It does **not** force third-party endpoints onto the OpenAI/Azure **Responses API**.
>
@@ -1121,6 +1122,40 @@ ollama run llama3.2
+
+LM Studio (local)
+
+[LM Studio](https://lmstudio.ai/) provides a local OpenAI-compatible server for running LLMs. Download models through the LM Studio UI, then start the local server.
+
+**1. Start LM Studio server:**
+- Launch LM Studio
+- Go to the "Local Server" tab
+- Load a model (e.g., Llama, Mistral, Qwen)
+- Click "Start Server" (default port: 1234)
+
+**2. Add to config** (partial — merge into `~/.nanobot/config.json`):
+```json
+{
+ "providers": {
+ "lm_studio": {
+ "apiKey": null,
+ "apiBase": "http://localhost:1234/v1"
+ }
+ },
+ "agents": {
+ "defaults": {
+ "provider": "lm_studio",
+ "model": "local-model"
+ }
+ }
+}
+```
+
+> **Note:** Set `apiKey` to `null` for LM Studio since it runs locally and doesn't require authentication. The model name should match what's shown in the LM Studio UI.
+> `provider: "auto"` also works when `providers.lm_studio.apiBase` is configured, but setting `"provider": "lm_studio"` is the clearest option.
+
+
+
OpenVINO Model Server (local / OpenAI-compatible)
@@ -1208,12 +1243,12 @@ vllm serve meta-llama/Llama-3.1-8B-Instruct --port 8000
**2. Add to config** (partial — merge into `~/.nanobot/config.json`):
-*Provider (key can be any non-empty string for local):*
+*Provider (set API key to null for local servers):*
```json
{
"providers": {
"vllm": {
- "apiKey": "dummy",
+ "apiKey": null,
"apiBase": "http://localhost:8000/v1"
}
}
diff --git a/nanobot/config/schema.py b/nanobot/config/schema.py
index 00b32acb..a0f1f60a 100644
--- a/nanobot/config/schema.py
+++ b/nanobot/config/schema.py
@@ -96,7 +96,7 @@ class AgentsConfig(Base):
class ProviderConfig(Base):
"""LLM provider configuration."""
- api_key: str = ""
+ api_key: str | None = None
api_base: str | None = None
extra_headers: dict[str, str] | None = None # Custom headers (e.g. APP-Code for AiHubMix)
@@ -115,6 +115,7 @@ class ProvidersConfig(Base):
dashscope: ProviderConfig = Field(default_factory=ProviderConfig)
vllm: ProviderConfig = Field(default_factory=ProviderConfig)
ollama: ProviderConfig = Field(default_factory=ProviderConfig) # Ollama local models
+ lm_studio: ProviderConfig = Field(default_factory=ProviderConfig) # LM Studio local models
ovms: ProviderConfig = Field(default_factory=ProviderConfig) # OpenVINO Model Server (OVMS)
gemini: ProviderConfig = Field(default_factory=ProviderConfig)
moonshot: ProviderConfig = Field(default_factory=ProviderConfig)
diff --git a/nanobot/providers/registry.py b/nanobot/providers/registry.py
index f4ea9fe2..be098731 100644
--- a/nanobot/providers/registry.py
+++ b/nanobot/providers/registry.py
@@ -337,6 +337,17 @@ PROVIDERS: tuple[ProviderSpec, ...] = (
detect_by_base_keyword="11434",
default_api_base="http://localhost:11434/v1",
),
+ # LM Studio (local, OpenAI-compatible)
+ ProviderSpec(
+ name="lm_studio",
+ keywords=("lm-studio", "lmstudio", "lm_studio"),
+ env_key="LM_STUDIO_API_KEY",
+ display_name="LM Studio",
+ backend="openai_compat",
+ is_local=True,
+ detect_by_base_keyword="1234",
+ default_api_base="http://localhost:1234/v1",
+ ),
# === OpenVINO Model Server (direct, local, OpenAI-compatible at /v3) ===
ProviderSpec(
name="ovms",