feat(agent): first-class llama.cpp provider with zero-config dashboard surfacing - #21531
feat(agent): first-class llama.cpp provider with zero-config dashboard surfacing#21531Abd0r wants to merge 2 commits into
Conversation
eda9656 to
85eab65
Compare
85eab65 to
9431c8e
Compare
llama.cpp's `llama-server` already speaks OpenAI chat-completions, so users could already point Hermes at it via `--provider custom`. But "custom" means they have to set OPENAI_BASE_URL by hand, the model picker doesn't list it, and the dashboard has no way to surface the running server. This PR makes llama.cpp a discoverable, zero-config backend. What ships ========== * `plugins/model-providers/llama-cpp/` — new ProviderProfile with default base_url `http://127.0.0.1:8088/v1`, aliases `llamacpp` / `llama.cpp` / `llama_cpp` / `llama-server`, and an offline-tolerant fetch_models override (returns None instead of raising when the local server is down). * `hermes_cli/auth.py` — adds llama-cpp to PROVIDER_REGISTRY (modeled on the lmstudio entry: api_key auth_type with optional LLAMA_CPP_API_KEY + LLAMA_CPP_BASE_URL env vars). Removes the old llama.cpp/llamacpp/llama-cpp hardcoded aliases that pointed at `custom`, so the plugin's aliases win. * `hermes_cli/models.py` — adds the same alias mappings to _PROVIDER_ALIASES so `--provider llama.cpp` resolves correctly through the CLI parser path. * `hermes_cli/model_switch.py` — adds a probe-and-surface block in list_authenticated_providers, mirroring the existing lmstudio pattern. Three surfacing modes: 1. Live probe: `${LLAMA_CPP_BASE_URL}/models` with a 300 ms cold-discovery timeout. If `llama-server` responds, the row appears with the loaded model. This is what makes the dashboard "magically" pick up a running server with no config. 2. Hint mode: LLAMA_CPP_API_KEY or LLAMA_CPP_BASE_URL set, or current provider matches one of the aliases — 1.5 s timeout. 3. Sticky current: when llama-cpp is the user's selected provider but the server is offline, the row still appears with current_model so the user doesn't lose access after restart. When no env vars, no current selection, and no live server, the row is not injected — keeps the picker tidy for non-llama.cpp users. * `plugins/model-providers/custom/__init__.py` — drops the llamacpp / llama.cpp / llama-cpp aliases from the generic `custom` profile (they now belong to the dedicated provider). * `scripts/start-llama-server.sh` — turnkey llama-server launcher whose default port (8088) lines up with the plugin's default base_url, so the end-to-end UX is just: ./scripts/start-llama-server.sh ~/models/foo.gguf hermes chat --provider llama-cpp Prints an alignment hint when PORT/HOST diverge from the plugin default. * `tests/providers/test_llama_cpp_profile.py` — 12 tests covering plugin registration, alias resolution end-to-end through hermes_cli.auth, CANONICAL_PROVIDERS auto-injection, PROVIDER_REGISTRY entry shape, picker surfacing in three modes (current+offline, no-clutter, alias resolution), and the offline-graceful fetch_models override. * `tests/providers/test_plugin_discovery.py` — bumped expected profile count 33 → 34. * `website/docs/guides/local-llamacpp-setup.md` — user-facing setup guide modeled on the existing local-ollama-setup.md. * `website/docs/reference/environment-variables.md` — documents LLAMA_CPP_API_KEY / LLAMA_CPP_BASE_URL and adds llama-cpp to the HERMES_INFERENCE_PROVIDER accepted-values list. Test plan ========= pytest tests/providers/ # 90 passed pytest tests/providers/test_llama_cpp_profile.py -v # 12 passed pytest tests/hermes_cli/test_model_switch_custom_providers.py \ tests/hermes_cli/test_user_providers_model_switch.py \ tests/hermes_cli/test_custom_provider_model_switch.py \ tests/hermes_cli/test_api_key_providers.py \ tests/hermes_cli/test_auth_provider_gate.py # 221 passed Tested on macOS 26.4 (arm64). The launcher uses `nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 8` so it works on Linux + macOS + WSL2; not exercised on native Windows. Notes ===== * Existing PR NousResearch#19607 also adds `scripts/start-llama-server.sh`. The version in this PR supersedes that one — it's stripped of the Qwen-specific detection branches (this PR is intentionally generic-llama.cpp only) and reworded around the new `llama-cpp` provider's defaults. Whichever PR lands second will need a one-line conflict resolution. * Does not include `tools/local_web_tools.py` — that's orthogonal web-search work and remains in NousResearch#19607.
9431c8e to
8ae2050
Compare
N_GPU_LAYERS previously defaulted to 0 (CPU-only) even on machines with a capable GPU, and the launcher had no way to tell a user apart from a binary that silently can't offload at all — the official llama.cpp Linux releases ship separate cpu/vulkan/rocm/sycl builds with no prebuilt CUDA build for Linux, so `-ngl 999` against a CPU-only binary does nothing with zero error. Hit this exact case hands-on: GPU sat idle, model ran on CPU, no warning. Now: if N_GPU_LAYERS is unset, detect both whether a GPU exists (nvidia-smi/Metal/rocm-smi/lspci) and whether the llama-server binary has a GPU backend compiled in (libggml-cuda/vulkan/metal/hip/sycl next to it), and only auto-offload when both are true. Warns loudly on the mismatch case (GPU present, no backend in the binary) whether N_GPU_LAYERS was left to auto-detect or set explicitly. Explicit N_GPU_LAYERS=0 is still respected with no warning. Doc updated to explain the Linux-has-no-prebuilt-CUDA gap and point at the Vulkan build as the easy path (works with the normal display driver, no CUDA toolkit install).
|
Thanks for the cohesive provider, launcher, documentation, and test work. The generic custom-provider path and the launcher-focused prior discussion are useful context, but this change cannot land in this repository as an in-tree provider integration.
Please publish the provider as a standalone plugin repository using the existing model-provider discovery surface; it can then be shared through the Nous Research Discord Closed as not-planned per standing maintainer policy ( |
What does this PR do?
Two coherent pieces of work landing together so a llama.cpp user has an end-to-end zero-config path:
A. First-class
llama-cppprovider. Today users can already point Hermes atllama-servervia--provider custom, but that path requires hand-settingOPENAI_BASE_URL, the model picker doesn't list it, and the dashboard has no way to surface a running server. This PR adds a properllama-cppprovider plugin so the picker, the dashboard,--providerflag,HERMES_INFERENCE_PROVIDER, and config.yaml all know about it natively. Crucially, it includes a probe-and-surface block inlist_authenticated_providersthat picks up a runningllama-serverautomatically — no env vars required.B.
scripts/start-llama-server.sh— explicit resubmission per @teknium1's invitation in#19796 (comment):The launcher's defaults (port
8088) line up with the new provider's default base URL, so the end-to-end flow becomes:Related Issues / PRs
customflow. Underlying bug in the custom-endpoint code is unchanged.04193cf71); SearXNG shipped viacd2cbc73b/5c906d702. The launcher was the one piece deferred there, and it's resubmitted here per teknium's invitation. No content collision withtools/web_providers/— that package is web-search only; this PR adds a model-provider plugin underplugins/model-providers/.local_web_tools.pyis obsolete (replaced bytools/web_providers/post-merge); only the launcher needed to land, which this PR carries forward — stripped of model-family-specific detection so it's pure llama.cpp.Not claiming
Closeson any of these — feature requests partially delivered should stay open for maintainers to retitle/scope.Type of Change
Changes Made
Provider plugin
plugins/model-providers/llama-cpp/(new) —LlamaCppProfilewith default base_urlhttp://127.0.0.1:8088/v1, aliasesllamacpp/llama.cpp/llama_cpp/llama-server, and an offline-tolerantfetch_modelsoverride that returnsNoneinstead of raising when the local server is down.plugins/model-providers/custom/__init__.py— drops thellamacpp/llama.cpp/llama-cppaliases from the genericcustomprofile so the dedicated provider's aliases win.Auth + CLI alias resolution
hermes_cli/auth.py— addsllama-cpptoPROVIDER_REGISTRY(modeled on thelmstudioentry:auth_type="api_key"with optionalLLAMA_CPP_API_KEY+LLAMA_CPP_BASE_URLenv vars). Removes the old hardcodedllama.cpp/llamacpp/llama-cpp → customalias mappings so the plugin's auto-extend wins.hermes_cli/models.py— adds the same alias mappings to_PROVIDER_ALIASESso--provider llama.cppresolves correctly through the CLI parser path.Dashboard / picker probe
hermes_cli/model_switch.py— adds a probe-and-surface block inlist_authenticated_providers, mirroring the existinglmstudiopattern. Three surfacing modes:${LLAMA_CPP_BASE_URL}/modelswith a 300 ms cold-discovery timeout. Ifllama-serverresponds, the row appears with the loaded model. This is the "magical" path.LLAMA_CPP_API_KEY/LLAMA_CPP_BASE_URLset, or current provider matches one of the aliases → 1.5 s timeout.current_model) so users don't lose access after a server restart.When no env vars, no current selection, and no live server, the row is not injected — keeps the picker tidy for non-llama.cpp users.
Launcher (resubmit per @teknium1's invitation)
scripts/start-llama-server.sh(new) — turnkey llama-server launcher whose default port (8088) lines up with the plugin's default base_url. Prints an alignment hint whenPORT/HOSTdiverge from the default. Generic — no model-family-specific detection.Tests + docs
tests/providers/test_llama_cpp_profile.py(new) — 12 tests covering plugin registration, alias resolution end-to-end throughhermes_cli.auth.resolve_provider,CANONICAL_PROVIDERSauto-injection,PROVIDER_REGISTRYentry shape, picker surfacing in three modes (current+offline, no-clutter, alias resolution), and the offline-gracefulfetch_modelsoverride.tests/providers/test_plugin_discovery.py— bumped expected profile count33 → 34.website/docs/guides/local-llamacpp-setup.md(new) — user-facing setup guide modeled on the existinglocal-ollama-setup.md.website/docs/reference/environment-variables.md— documentsLLAMA_CPP_API_KEY/LLAMA_CPP_BASE_URLand addsllama-cppto theHERMES_INFERENCE_PROVIDERaccepted-values list.How to Test
Targeted suites (no venv setup needed for these):
End-to-end UX (requires a GGUF model and
llama-serveron PATH):chmod +x scripts/start-llama-server.sh ./scripts/start-llama-server.sh ~/models/your-model.ggufThen in another shell:
hermes model # llama.cpp row should appear with the loaded model hermes chat --provider llama-cppPicker UI without running server:
LLAMA_CPP_*env vars and no llama-cpp selection inconfig.yaml→ llama.cpp should NOT appear (no clutter).model.provider: llama-cppinconfig.yaml→ row appears withcurrent_modeleven when the server is offline.Alias resolution:
All should resolve to the same
llama-cppprovider.Checklist
Code
feat(agent):)tests/providers/+ relevanttests/hermes_cli/) — 316 tests across the affected modules pass. The full suite viascripts/run_tests.shrequires auvvenv I haven't set up locally; happy to defer to CI as the source of truth.tests/providers/test_llama_cpp_profile.py)nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 8so it works on Linux + macOS + WSL2; not exercised on native Windows.Documentation & Housekeeping
website/docs/guides/local-llamacpp-setup.md(new) andwebsite/docs/reference/environment-variables.mdcli-config.yaml.examplekeys added; new env vars are optional and documented in the env-vars referencepython -m llama_cpp.serverand setLLAMA_CPP_BASE_URLto point Hermes at it; the provider plugin itself is pure Python and platform-neutral.Why "first-class" instead of just
--provider customThe
customflow requires users to setOPENAI_BASE_URL, doesn't show in the model picker, and provides no dashboard auto-detection. For a backend as common as llama.cpp, the registration boilerplate is a small price for: discoverability inhermes model/hermes setup, dashboard surfacing, alias resolution, and (most importantly) zero-config UX once the launcher is running.