From c38808259b0623b527da87d623d8e4a5e695472e Mon Sep 17 00:00:00 2001 From: Alessandro <155005371+3clyp50@users.noreply.github.com> Date: Mon, 15 Jun 2026 04:17:03 +0200 Subject: [PATCH] Make local LM Studio and Ollama Docker-friendly Add shipped local-provider defaults for LM Studio and Ollama so Dockerized Agent Zero can reach host-running services without manual api_base edits. Map host.docker.internal in the sample Compose file for Linux Docker, document the behavior, and cover the provider defaults and compose mapping with focused regression tests. --- conf/model_providers.yaml | 12 +++++- docker/run/docker-compose.yml | 4 +- docs/setup/installation.md | 4 +- tests/test_model_config_api_keys.py | 66 +++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 4 deletions(-) diff --git a/conf/model_providers.yaml b/conf/model_providers.yaml index 12235e58f..95ca2bf8e 100644 --- a/conf/model_providers.yaml +++ b/conf/model_providers.yaml @@ -14,7 +14,7 @@ # # Optional fields: # kwargs: A dictionary of extra parameters to pass to LiteLLM. -# This is useful for `api_base`, `extra_headers`, etc. +# This is useful for `api_base`, `extra_headers`, non-secret local placeholders, etc. # # Optional model listing fields (used by the Model Configuration plugin): # models_list: @@ -83,6 +83,9 @@ chat: models_list: endpoint_url: "/v1/models" default_base: "http://host.docker.internal:1234" + kwargs: + api_base: "http://host.docker.internal:1234/v1" + api_key: "lm-studio" mistral: name: Mistral AI litellm_provider: mistral @@ -107,6 +110,8 @@ chat: endpoint_url: "/api/tags" format: "ollama" default_base: "http://host.docker.internal:11434" + kwargs: + api_base: "http://host.docker.internal:11434" ollama_cloud: name: Ollama Cloud litellm_provider: openai @@ -186,12 +191,17 @@ embedding: lm_studio: name: LM Studio litellm_provider: lm_studio + kwargs: + api_base: "http://host.docker.internal:1234/v1" + api_key: "lm-studio" mistral: name: Mistral AI litellm_provider: mistral ollama: name: Ollama litellm_provider: ollama + kwargs: + api_base: "http://host.docker.internal:11434" openai: name: OpenAI litellm_provider: openai diff --git a/docker/run/docker-compose.yml b/docker/run/docker-compose.yml index cc48f3f1b..ce11aebef 100644 --- a/docker/run/docker-compose.yml +++ b/docker/run/docker-compose.yml @@ -5,4 +5,6 @@ services: volumes: - ./agent-zero:/a0 ports: - - "50080:80" \ No newline at end of file + - "50080:80" + extra_hosts: + - "host.docker.internal:host-gateway" diff --git a/docs/setup/installation.md b/docs/setup/installation.md index a966b9191..b6688d22f 100644 --- a/docs/setup/installation.md +++ b/docs/setup/installation.md @@ -527,13 +527,13 @@ Replace `` with the name of the model you want to use. For example: 1. Once you've downloaded your model(s), select it in the Settings page of the GUI. 2. Within the Chat model, Utility model, or Embedding model section, choose **Ollama** as provider. 3. Write your model code as expected by Ollama, in the format `llama3.2` or `qwen2.5:7b` -4. Provide your API base URL to your Ollama API endpoint, usually `http://host.docker.internal:11434` +4. Agent Zero includes Docker-friendly defaults for Ollama on the host at `http://host.docker.internal:11434`. Override the API base URL only if your Ollama server runs somewhere else. 5. Click `Save` to confirm your settings. ![ollama](../res/setup/settings/4-local-models.png) > [!NOTE] -> If Agent Zero runs in Docker and Ollama runs on the host, ensure port **11434** is reachable from the container. If both services are in the same Docker network, you can use `http://:11434` instead of `host.docker.internal`. +> If Agent Zero runs in Docker and Ollama runs on the host, ensure port **11434** is reachable from the container. The shipped Docker Compose file maps `host.docker.internal` to the host gateway for Linux Docker. If both services are in the same Docker network, you can use `http://:11434` instead of `host.docker.internal`. ### Managing Downloaded Models diff --git a/tests/test_model_config_api_keys.py b/tests/test_model_config_api_keys.py index 7f20b88c2..fa212a54b 100644 --- a/tests/test_model_config_api_keys.py +++ b/tests/test_model_config_api_keys.py @@ -276,3 +276,69 @@ def test_provider_key_modes_for_local_and_ollama_cloud(): assert model_config.provider_requires_api_key("lm_studio") is False assert model_config.provider_requires_api_key("other") is False assert model_config.provider_requires_api_key("ollama_cloud") is True + + +def test_local_provider_defaults_are_docker_friendly(): + import yaml + + provider_path = PROJECT_ROOT / "conf" / "model_providers.yaml" + provider_config = yaml.safe_load(provider_path.read_text(encoding="utf-8")) + + assert provider_config["chat"]["lm_studio"]["kwargs"]["api_base"] == ( + "http://host.docker.internal:1234/v1" + ) + assert provider_config["chat"]["lm_studio"]["kwargs"]["api_key"] == "lm-studio" + assert provider_config["chat"]["lm_studio"]["models_list"]["default_base"] == ( + "http://host.docker.internal:1234" + ) + assert provider_config["chat"]["ollama"]["kwargs"]["api_base"] == ( + "http://host.docker.internal:11434" + ) + assert provider_config["chat"]["ollama"]["models_list"]["default_base"] == ( + "http://host.docker.internal:11434" + ) + assert provider_config["embedding"]["lm_studio"]["kwargs"]["api_base"] == ( + "http://host.docker.internal:1234/v1" + ) + assert provider_config["embedding"]["lm_studio"]["kwargs"]["api_key"] == "lm-studio" + assert provider_config["embedding"]["ollama"]["kwargs"]["api_base"] == ( + "http://host.docker.internal:11434" + ) + + +def test_local_provider_runtime_defaults_and_overrides(monkeypatch): + monkeypatch.setattr(models, "get_api_key", lambda provider: "None") + + lm_chat = models.get_chat_model("lm_studio", "local-chat-model") + assert lm_chat.model_name == "lm_studio/local-chat-model" + assert lm_chat.kwargs["api_base"] == "http://host.docker.internal:1234/v1" + assert lm_chat.kwargs["api_key"] == "lm-studio" + + lm_embedding = models.get_embedding_model("lm_studio", "nomic-embed-text") + assert lm_embedding.model_name == "lm_studio/nomic-embed-text" + assert lm_embedding.kwargs["api_base"] == "http://host.docker.internal:1234/v1" + assert lm_embedding.kwargs["api_key"] == "lm-studio" + + custom_lm_embedding = models.get_embedding_model( + "lm_studio", + "nomic-embed-text", + api_base="http://127.0.0.1:1234/v1", + api_key="real-local-key", + ) + assert custom_lm_embedding.kwargs["api_base"] == "http://127.0.0.1:1234/v1" + assert custom_lm_embedding.kwargs["api_key"] == "real-local-key" + + ollama_embedding = models.get_embedding_model("ollama", "nomic-embed-text") + assert ollama_embedding.model_name == "ollama/nomic-embed-text" + assert ollama_embedding.kwargs["api_base"] == "http://host.docker.internal:11434" + assert "api_key" not in ollama_embedding.kwargs + + +def test_docker_compose_maps_host_docker_internal_for_local_models(): + import yaml + + compose_path = PROJECT_ROOT / "docker" / "run" / "docker-compose.yml" + compose = yaml.safe_load(compose_path.read_text(encoding="utf-8")) + service = compose["services"]["agent-zero"] + + assert "host.docker.internal:host-gateway" in service["extra_hosts"]