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"