Add oMLX local provider support
Add oMLX as a first-class local chat and embedding provider using the Docker-friendly host.docker.internal:8000/v1 default and LiteLLM hosted_vllm wiring. Mark it as a no-key local provider, include it in onboarding with a bundled logo, document Apple Silicon setup including paged SSD cache, and cover the provider defaults, model search behavior, and onboarding metadata with focused tests.
Alessandro committed
Jun 15, 2026 at 04:54 UTC
0450098117612bff1babfb85220c8a2b2a2998f0
12 files changed
+145
-5
conf/model_providers.yaml
+15
@@ -112,6 +112,15 @@ chat:
112
default_base: "http://host.docker.internal:11434"
113
kwargs:
114
api_base: "http://host.docker.internal:11434"
115
+ omlx:
116
+ name: oMLX
117
+ litellm_provider: hosted_vllm
118
+ models_list:
119
+ endpoint_url: "/v1/models"
120
+ default_base: "http://host.docker.internal:8000"
121
+ kwargs:
122
+ api_base: "http://host.docker.internal:8000/v1"
123
+ api_key: "omlx"
124
ollama_cloud:
125
name: Ollama Cloud
126
litellm_provider: openai
@@ -202,6 +211,12 @@ embedding:
211
litellm_provider: ollama
212
kwargs:
213
api_base: "http://host.docker.internal:11434"
214
+ omlx:
215
+ name: oMLX
216
+ litellm_provider: hosted_vllm
217
+ kwargs:
218
+ api_base: "http://host.docker.internal:8000/v1"
219
+ api_key: "omlx"
220
openai:
221
name: OpenAI
222
litellm_provider: openai
docs/setup/installation.md
+43
@@ -438,6 +438,7 @@ Use the naming format required by your selected provider:
438
| OpenAI | Model name only | `claude-sonnet-4-5` |
439
| OpenRouter | Provider prefix mostly required | `anthropic/claude-sonnet-4-5` |
440
| Ollama | Model name only | `gpt-oss:20b` |
441
+| oMLX | API-visible model name from `/v1/models` | `Qwen3-0.6B-4bit` |
442
443
> [!TIP]
444
> If you see "Invalid model ID," verify the provider and naming format on the provider website, or search the web for "<name-of-ai-model> model naming".
@@ -461,6 +462,48 @@ Use the naming format required by your selected provider:
462
463
---
464
465
+## Installing and Using oMLX (Apple Silicon Local Models)
466
+
467
+oMLX is a local inference server for Apple Silicon Macs. It serves MLX models through an OpenAI-compatible API and supports chat, embeddings, and model listing endpoints.
468
+
469
+> [!NOTE]
470
+> oMLX requires Apple Silicon and macOS 15+. On 16 GB machines, start with small quantized MLX models.
471
+
472
+### macOS oMLX Installation
473
+
474
+**Using Homebrew:**
475
+
476
+```bash
477
+brew tap jundot/omlx https://github.com/jundot/omlx
478
+brew install omlx
479
+omlx start
480
+```
481
+
482
+**Using the macOS App:**
483
+
484
+Download the oMLX app from the [official website](https://omlx.ai/) and follow the welcome flow to choose a model directory, start the server, and download or discover models.
485
+
486
+By default, oMLX serves its OpenAI-compatible API at `http://localhost:8000/v1`.
487
+
488
+To run a foreground server with oMLX's paged SSD cache enabled:
489
+
490
+```bash
491
+omlx serve --model-dir ~/.omlx/models --paged-ssd-cache-dir ~/.omlx/cache
492
+```
493
+
494
+### Configuring oMLX in Agent Zero
495
+
496
+1. Start oMLX and make sure at least one model is available in the oMLX dashboard or model directory.
497
+2. In Agent Zero Settings, choose **oMLX** as the Chat model, Utility model, or Embedding model provider.
498
+3. Use the model name shown by oMLX's model list or dashboard.
499
+4. Agent Zero includes Docker-friendly defaults for oMLX on the host at `http://host.docker.internal:8000/v1`. Override the API base URL only if your oMLX server runs somewhere else.
500
+5. Click `Save` to confirm your settings.
501
+
502
+> [!NOTE]
503
+> If Agent Zero runs in Docker and oMLX runs on the Mac host, ensure port **8000** is reachable from the container. The shipped Docker Compose file maps `host.docker.internal` to the host gateway for Linux Docker. Docker Desktop for macOS provides this hostname automatically.
504
+
505
+---
506
+
507
## Installing and Using Ollama (Local Models)
508
509
Ollama is a powerful tool that allows you to run various large language models locally.
plugins/_browser/helpers/connector_runtime.py
+1
-1
@@ -58,7 +58,7 @@ HOST_BROWSER_PROFILE_MODE_KEY = getattr(
58
"host_browser_profile_mode",
59
)
60
get_browser_config = browser_config.get_browser_config
61
-_LOCAL_PROVIDERS = {"ollama", "lm_studio"}
61
+_LOCAL_PROVIDERS = {"ollama", "lm_studio", "omlx"}
62
_LOCAL_HOSTS = {"localhost", "127.0.0.1", "::1", "host.docker.internal"}
63
_SENSITIVE_ACTIONS = {"content", "detail", "evaluate", "screenshot", "screenshot_file"}
64
_KEY_ALIASES = {
plugins/_model_config/api/model_search.py
+1
-1
@@ -179,7 +179,7 @@ class ModelSearch(ApiHandler):
179
elif provider == "azure":
180
if has_key:
181
headers["api-key"] = api_key
182
- elif provider not in ("ollama", "lm_studio"):
182
+ elif provider not in ("ollama", "lm_studio", "omlx"):
183
if has_key:
184
headers["Authorization"] = f"Bearer {api_key}"
185
plugins/_model_config/extensions/python/banners/_20_missing_api_key.py
+1
-1
@@ -6,7 +6,7 @@ from plugins._model_config.helpers import model_config
6
class MissingApiKeyCheck(Extension):
7
"""Check if API keys are configured for selected model providers."""
8
9
- LOCAL_PROVIDERS = {"ollama", "lm_studio"}
9
+ LOCAL_PROVIDERS = {"ollama", "lm_studio", "omlx"}
10
CONFIGURE_MODEL_SETTINGS_LINK = (
11
"""<div class="onboarding-banner-btn-container" style="margin-top: 12px;">"""
12
"""<button class="btn btn-ok" onclick="window.openModal('/plugins/_onboarding/webui/onboarding.html');return false;">"""
plugins/_model_config/helpers/model_config.py
+1
-1
@@ -32,7 +32,7 @@ IMPLICIT_PRESET_SLOT_DEFAULTS = {
32
"kwargs": {},
33
},
34
}
35
-LOCAL_PROVIDERS = {"ollama", "lm_studio"}
35
+LOCAL_PROVIDERS = {"ollama", "lm_studio", "omlx"}
36
LOCAL_EMBEDDING = {"huggingface"}
37
_PROVIDER_METADATA_CACHE: dict | None = None
38
plugins/_model_config/provider_metadata.yaml
+4
@@ -3,6 +3,8 @@ chat:
3
api_key_mode: none
4
ollama:
5
api_key_mode: none
6
+ omlx:
7
+ api_key_mode: none
8
other:
9
api_key_mode: optional
10
@@ -13,5 +15,7 @@ embedding:
15
api_key_mode: none
16
ollama:
17
api_key_mode: none
18
+ omlx:
19
+ api_key_mode: none
20
other:
21
api_key_mode: optional
plugins/_onboarding/webui/assets/provider-logos/omlx.svg
new
+19
@@ -0,0 +1,19 @@
1
+<svg xmlns="http://www.w3.org/2000/svg" width="160" height="160" viewBox="0 0 160 160" role="img" aria-labelledby="omlx-title">
2
+ <title id="omlx-title">oMLX</title>
3
+ <!-- Source: oMLX project icon, Apache-2.0. -->
4
+ <defs>
5
+ <filter id="omlx-shadow" x="-10%" y="-10%" width="130%" height="130%">
6
+ <feDropShadow dx="0" dy="2" stdDeviation="6" flood-color="#000" flood-opacity="0.3"/>
7
+ </filter>
8
+ <linearGradient id="omlx-bg" x1="0" y1="0" x2="0" y2="1">
9
+ <stop offset="0%" stop-color="#2d2d2d"/>
10
+ <stop offset="100%" stop-color="#1a1a1a"/>
11
+ </linearGradient>
12
+ </defs>
13
+ <rect x="10" y="10" width="140" height="140" rx="32" fill="url(#omlx-bg)" filter="url(#omlx-shadow)"/>
14
+ <g transform="translate(25, 25) scale(0.0221)">
15
+ <g transform="translate(0,4970) scale(1,-1)" fill="#fff" stroke="none">
16
+ <path d="M2275 4349c-408-39-769-207-1056-492-196-194-333-428-418-715-47-158-67-281-101-617-66-662-116-944-245-1387-102-352-271-774-420-1051-19-35-35-70-35-76 0-8 50-11 163-11h164l80 168c168 348 303 739 408 1175 73 307 109 532 155 982 52 500 72 627 122 785 162 507 570 860 1096 951 155 26 389 26 544 0 221-38 440-129 620-258 45-32 152-126 237-209 86-82 178-165 204-183 106-72 312-150 495-186l32-7-86-54c-110-69-170-117-267-212-93-91-143-154-191-243-105-191-130-406-75-623 29-115 81-239 217-516 234-480 343-769 411-1091 36-172 48-252 57-381l7-98h158 158l-4 28c-2 15-6 66-9 113-14 218-95 560-201 849-81 220-165 407-363 810-120 245-147 320-161 443-38 338 202 621 766 906 131 65 166 126 106 183-33 31-86 47-288 88-177 36-274 61-370 97-140 52-190 88-377 270-140 137-202 189-300 254-378 250-782 351-1233 308zM3050 3391c-57-11-122-53-154-99-41-57-49-158-18-218 29-56 66-92 120-117 153-69 323 37 325 203 1 147-131 259-273 231zM1985 1391c-68-31-70-40-66-271 1-114-2-243-9-290-40-307-124-555-255-754-25-38-45-71-45-72 0-2 79-4 176-4h175l54 113c117 247 182 512 201 812 7 126-9 319-32 374-26 63-86 111-136 111-13 0-41-9-63-19z"/>
17
+ </g>
18
+ </g>
19
+</svg>
plugins/_onboarding/webui/onboarding-providers.js
+10
-1
@@ -25,7 +25,7 @@ export const MORE_CLOUD_PROVIDER_IDS = [
25
"other",
26
];
27
28
-export const LOCAL_PROVIDER_IDS = ["ollama", "lm_studio", "other"];
28
+export const LOCAL_PROVIDER_IDS = ["ollama", "lm_studio", "omlx", "other"];
29
30
export const ONBOARDING_PROVIDER_OVERRIDES = {
31
a0_venice: {
@@ -172,6 +172,15 @@ export const ONBOARDING_PROVIDER_OVERRIDES = {
172
model_list_autoload: true,
173
short_description: "Ollama cloud models through a hosted endpoint.",
174
},
175
+ omlx: {
176
+ logo: "/plugins/_onboarding/webui/assets/provider-logos/omlx.svg",
177
+ setup_url: "https://omlx.ai/",
178
+ docs_url: "https://github.com/jundot/omlx#readme",
179
+ default_api_base: "http://host.docker.internal:8000/v1",
180
+ api_key_mode: "none",
181
+ model_list_autoload: true,
182
+ short_description: "Apple Silicon local inference with MLX.",
183
+ },
184
openai: {
185
logo: "https://openai.com/favicon.ico",
186
setup_url: "https://platform.openai.com/",
tests/test_model_config_api_keys.py
+35
@@ -274,8 +274,10 @@ def test_provider_key_modes_for_local_and_ollama_cloud():
274
275
assert model_config.provider_requires_api_key("ollama") is False
276
assert model_config.provider_requires_api_key("lm_studio") is False
277
+ assert model_config.provider_requires_api_key("omlx") is False
278
assert model_config.provider_requires_api_key("other") is False
279
assert model_config.provider_requires_api_key("ollama_cloud") is True
280
+ assert "omlx" in missing_key_banner.MissingApiKeyCheck.LOCAL_PROVIDERS
281
282
283
def test_local_provider_defaults_are_docker_friendly():
@@ -297,6 +299,15 @@ def test_local_provider_defaults_are_docker_friendly():
299
assert provider_config["chat"]["ollama"]["models_list"]["default_base"] == (
300
"http://host.docker.internal:11434"
301
)
302
+ assert provider_config["chat"]["omlx"]["litellm_provider"] == "hosted_vllm"
303
+ assert provider_config["chat"]["omlx"]["kwargs"]["api_base"] == (
304
+ "http://host.docker.internal:8000/v1"
305
+ )
306
+ assert provider_config["chat"]["omlx"]["kwargs"]["api_key"] == "omlx"
307
+ assert provider_config["chat"]["omlx"]["models_list"]["default_base"] == (
308
+ "http://host.docker.internal:8000"
309
+ )
310
+ assert provider_config["chat"]["omlx"]["models_list"]["endpoint_url"] == "/v1/models"
311
assert provider_config["embedding"]["lm_studio"]["kwargs"]["api_base"] == (
312
"http://host.docker.internal:1234/v1"
313
)
@@ -304,6 +315,11 @@ def test_local_provider_defaults_are_docker_friendly():
315
assert provider_config["embedding"]["ollama"]["kwargs"]["api_base"] == (
316
"http://host.docker.internal:11434"
317
)
318
+ assert provider_config["embedding"]["omlx"]["litellm_provider"] == "hosted_vllm"
319
+ assert provider_config["embedding"]["omlx"]["kwargs"]["api_base"] == (
320
+ "http://host.docker.internal:8000/v1"
321
+ )
322
+ assert provider_config["embedding"]["omlx"]["kwargs"]["api_key"] == "omlx"
323
324
325
def test_local_provider_runtime_defaults_and_overrides(monkeypatch):
@@ -333,6 +349,25 @@ def test_local_provider_runtime_defaults_and_overrides(monkeypatch):
349
assert ollama_embedding.kwargs["api_base"] == "http://host.docker.internal:11434"
350
assert "api_key" not in ollama_embedding.kwargs
351
352
+ omlx_chat = models.get_chat_model("omlx", "local-chat-model")
353
+ assert omlx_chat.model_name == "hosted_vllm/local-chat-model"
354
+ assert omlx_chat.kwargs["api_base"] == "http://host.docker.internal:8000/v1"
355
+ assert omlx_chat.kwargs["api_key"] == "omlx"
356
+
357
+ omlx_embedding = models.get_embedding_model("omlx", "local-embedding-model")
358
+ assert omlx_embedding.model_name == "hosted_vllm/local-embedding-model"
359
+ assert omlx_embedding.kwargs["api_base"] == "http://host.docker.internal:8000/v1"
360
+ assert omlx_embedding.kwargs["api_key"] == "omlx"
361
+
362
+ custom_omlx_chat = models.get_chat_model(
363
+ "omlx",
364
+ "local-chat-model",
365
+ api_base="http://127.0.0.1:8000/v1",
366
+ api_key="real-local-key",
367
+ )
368
+ assert custom_omlx_chat.kwargs["api_base"] == "http://127.0.0.1:8000/v1"
369
+ assert custom_omlx_chat.kwargs["api_key"] == "real-local-key"
370
+
371
372
def test_docker_compose_maps_host_docker_internal_for_local_models():
373
import yaml
tests/test_model_search.py
+6
@@ -66,6 +66,12 @@ def test_model_search_resolves_v1_base_without_duplicate_v1():
66
assert fmt == "openai"
67
68
69
+def test_model_search_omits_auth_header_for_omlx_placeholder_key():
70
+ handler = _handler()
71
+
72
+ assert handler._build_headers("omlx", "omlx", {}) == {}
73
+
74
+
75
def test_model_search_filters_non_chat_models():
76
handler = _handler()
77
tests/test_onboarding_static.py
+9
@@ -65,11 +65,16 @@ def test_onboarding_provider_grid_names_are_present_in_metadata():
65
assert 'docs_url: "https://docs.venice.ai/guides/getting-started/generating-api-key"' in provider_ui
66
assert 'docs_url: "https://docs.tokenfactory.nebius.com/api-reference/introduction"' in provider_ui
67
assert 'docs_url: "https://lmstudio.ai/docs/developer/core/authentication"' in provider_ui
68
+ assert 'logo: "/plugins/_onboarding/webui/assets/provider-logos/omlx.svg"' in provider_ui
69
+ assert 'docs_url: "https://github.com/jundot/omlx#readme"' in provider_ui
70
+ assert 'default_api_base: "http://host.docker.internal:8000/v1"' in provider_ui
71
assert 'docs_url: ""' in provider_ui
72
assert "api_key_mode: none" in model_metadata
73
assert "api_key_mode: optional" in model_metadata
74
assert "Ollama Cloud" in provider_yaml
75
assert "https://ollama.com/v1" in provider_yaml
76
+ assert "oMLX" in provider_yaml
77
+ assert "http://host.docker.internal:8000/v1" in provider_yaml
78
assert "Nebius Token Factory" in provider_yaml
79
assert "https://api.tokenfactory.nebius.com/v1" in provider_yaml
80
assert not (PROJECT_ROOT / "plugins/_model_config/conf/model_providers.yaml").exists()
@@ -87,6 +92,7 @@ def test_onboarding_provider_grid_names_are_present_in_metadata():
92
"Z.AI",
93
"Mistral AI",
94
"Azure OpenAI",
95
+ "oMLX",
96
]:
97
assert name in provider_yaml + provider_ui
98
@@ -117,9 +123,12 @@ def test_onboarding_provider_grid_names_are_present_in_metadata():
123
"cometapi.ico",
124
"github-copilot.svg",
125
"zai-logo.svg",
126
+ "omlx.svg",
127
]:
128
assert logo in provider_ui
129
130
+ assert (PROJECT_ROOT / "plugins/_onboarding/webui/assets/provider-logos/omlx.svg").exists()
131
+
132
133
def test_nebius_provider_config_uses_openai_compatible_token_factory_endpoint():
134
provider_path = PROJECT_ROOT / "conf/model_providers.yaml"