ModelSaar

Development preview — open source, no release tagged yet

One address for your local AI.

Connect the AI apps you already use. ModelSaar sends each request to a suitable available model, chosen from the task, verified model capabilities and the limits you set.

Spend less time switching models and configuring apps. Keep control of where your requests run.

Needs Python 3.10+ and an inference server you already run (Ollama is detected automatically). The installer pins one candidate commit (see the roadmap for its review status) and never downloads models.

curl -fsSL https://modelsaar.com/install.sh | sh

Prefer to read it first? Inspect the installer or install by hand.

decision req_02…auto → ollama/qwen3-coder:latest

“Fix the failing pytest in src/app.py” + tools: [bash]

  • task: coding
  • needs: tool calling
  • context: ~3 200 tokens
deploymenthard checks
qwen3-coder (30B) — selected✓ tools verified by probe
gemma4 (31B, thinking)✓ tools verified by probe
qwen3.6 (35B, thinking)✓ tools verified by probe

Tool calling is a hard requirement; every candidate passed the tools probe, so none is excluded. On coding tasks the measured calibration success favours qwen3-coder (2/2, versus 1/2 and 0/2 within the run's 400-token budget), and the request is forwarded unchanged — the client keeps its own agent loop and runs the tool itself.

Evidence: measured (calibration run). Illustration based on the three models of the 2026-10-11 run; not a live result.

Several good models, one app at a time

Once you run more than one local model, every app needs its own model setting, and every task needs you to remember which model handles code, which one can read an image, which one is fast enough for chat. Change a model in Ollama and you change it in four places.

ModelSaar sits between your apps and your inference servers. Apps connect once, to one address, and ask for a virtual model called auto. For each request the gateway checks which of your deployments can actually do the job, picks one on evidence, forwards the request unchanged and records why.

Three things stay distinct:

Apps
Open WebUI, OpenCode, agents, scripts — anything that speaks the OpenAI Chat Completions API. They keep their own tools, memory and loops.
Inference backends
Ollama, vLLM, llama.cpp server and similar. They load weights and run on your hardware. ModelSaar does not replace them.
Models
The weights those backends serve, with their own licences. ModelSaar does not ship models or lower their hardware requirements.

What a routing decision looks like

Every request produces a record like these. The scenarios below are scripted illustrations using the three models of a recorded test run — not live inference.

General chat a short question, no tools, no images
decision req_01…auto → ollama/qwen3-coder:latest

“Give me one sentence of encouragement before an exam.”

  • task: conversation
  • needs: nothing special
  • context: ~40 tokens
deploymenthard checks
qwen3-coder (30B) — selected✓ eligible
gemma4 (31B, thinking)✓ eligible
qwen3.6 (35B, thinking)✓ eligible

All three can answer. Measured evidence from the calibration run gives qwen3-coder the best conversation success at the lowest latency, so the quality profile picks it. Had the evidence been missing, the tie would resolve to the lowest-overhead deployment and the record would say uncalibrated.

Evidence: measured (calibration run). Illustration based on the three models of the 2026-10-11 run; not a live result.
Coding with tools an agent asks to fix a test and offers a shell tool
decision req_02…auto → ollama/qwen3-coder:latest

“Fix the failing pytest in src/app.py” + tools: [bash]

  • task: coding
  • needs: tool calling
  • context: ~3 200 tokens
deploymenthard checks
qwen3-coder (30B) — selected✓ tools verified by probe
gemma4 (31B, thinking)✓ tools verified by probe
qwen3.6 (35B, thinking)✓ tools verified by probe

Tool calling is a hard requirement; every candidate passed the tools probe, so none is excluded. On coding tasks the measured calibration success favours qwen3-coder (2/2, versus 1/2 and 0/2 within the run's 400-token budget), and the request is forwarded unchanged — the client keeps its own agent loop and runs the tool itself.

Evidence: measured (calibration run). Illustration based on the three models of the 2026-10-11 run; not a live result.
Question about an image the message carries a base64 image
decision req_03…auto → ollama/gemma4:31b

“What is in this picture?” + image

  • task: document_vision
  • needs: image input
  • context: ~1 250 tokens
deploymenthard checks
qwen3-coder (30B)✗ image input unsupported (backend metadata)
gemma4 (31B, thinking) — selected✓ vision verified by probe
qwen3.6 (35B, thinking)✓ vision verified by probe

A failed capability check can never be outscored: qwen3-coder is excluded with the reason recorded. Between the two vision-capable models the recorded run picked gemma4 on its measured evidence (calibration success 0.80 versus 0.47 overall). The image is never dropped or described away — if no deployment could take it, the request would fail with blockers and remedies instead.

Evidence: measured (calibration run). Illustration based on the three models of the 2026-10-11 run; not a live result.
Tool result comes back the second turn of a tool call issued by gemma4
decision req_04…auto → ollama/gemma4:31b

assistant: tool_call(id=call_7f2…) → tool: “14:05” → continue

  • task: agent
  • needs: tool calling + continuity
  • tool round trip: call_7f2…
deploymenthard checks
qwen3-coder (30B)✗ would hand the tool history to another model
gemma4 (31B, thinking) — selected✓ issued the tool call
qwen3.6 (35B, thinking)✗ would hand the tool history to another model

The gateway remembers which deployment issued the tool-call ids. The follow-up turn stays on it even when another candidate would score higher, and the record marks failover_allowed: false: if gemma4 fails now, the client gets the error rather than a silent switch to a model that never saw the tool call.

Evidence: continuity rule. Illustration based on the three models of the 2026-10-11 run; not a live result.
Nothing qualifies an image question when only qwen3-coder is configured
decision req_05…auto → no eligible deployment (HTTP 422)

“What is in this picture?” + image — with one deployment

  • task: document_vision
  • needs: image input
  • deployments: 1
deploymenthard checks
qwen3-coder (30B)✗ image input unsupported (backend metadata)

Automatic routing with one model simply uses that model whenever it qualifies. When it does not, the answer is an HTTP 422 with the blocker (image input required; capability vision is unsupported) and a remedy (use a deployment whose vision capability is supported) — never a degraded answer that pretends the image was read.

Evidence: hard filter. Illustration based on the three models of the 2026-10-11 run; not a live result.

Read the full pipeline — requirements, hard filters, ranking, continuity — in How it works.

Three steps to a first routed request

  1. Install ModelSaar

    Python 3.10 or newer on macOS, Linux or WSL2. The one-line installer uses uv, pipx or pip --user — whichever you have — and pins one candidate commit.

    curl -fsSL https://modelsaar.com/install.sh | sh
  2. Start the gateway

    The installer has already run modelsaar init: it found Ollama on 127.0.0.1:11434 (an inference server with at least one model must already be running), wrote a private config and keys, and listed the models it discovered. Now start the gateway and leave it running.

    modelsaar serve

    Installed from source instead? Run modelsaar init first (once; it refuses to overwrite an existing config), then modelsaar serve.

  3. Connect an app

    Point the app at the gateway and pick auto as the model. Print the API key in a terminal, then paste the printed line (it starts with msk_) into the app's key field:

    head -1 ~/.modelsaar/keys/api_keys
    Base URL:  http://127.0.0.1:8484/v1
    Model:     auto
    API key:   msk_…  (the line printed by the command above)

    One command per app prints its exact settings: modelsaar client-config open-webui, modelsaar client-config opencode, modelsaar client-config openclaw or modelsaar client-config hermes.

Verify from a second terminal with modelsaar test: it first identifies the gateway on the configured port (an unrelated service there is reported as a collision and receives no key), lists the models the gateway advertises and sends one chat completion through auto, printing which deployment answered and the request id — the routing attribution that shows ModelSaar handled it. Full details, including Docker and remote hosts, in Get started.

What works today, and how we know

Compatibility is a tested property of an exact client, protocol and backend combination. Each row says what kind of evidence backs it.

areawhatevidencenotes
InterfaceOpenAI-compatible Chat Completions (/v1/chat/completions, streaming SSE), /v1/models, /healthz, /readyzfixture tests liveResponses API and Anthropic Messages return an explicit 501.
Interface/v1/embeddings with identity-anchored alias contractsfixture testsReal-model embeddings NOT RUN; contract, identity anchoring, deadlines and output validation are fixture-tested only.
BackendOllama (discovery via /api/tags, /api/show, /api/ps; inference via its OpenAI-compatible layer)fixture tests liveOllama 0.32.13 with 8 models on an ASUS GX10, 2026-10-11.
BackendGeneric OpenAI-compatible server (vLLM, llama.cpp server, LM Studio, …) with declared models; vLLM /tokenize used for token countsfixture testsNot run against a real vLLM/llama.cpp server yet.
ClientOpen WebUIdocumentedConfiguration generated by modelsaar client-config open-webui; real client workflow not run.
ClientOpenClawdocumentedmodelsaar client-config openclaw; real client workflow not run.
ClientOpenCode (V1 and V2 config schemas)documentedmodelsaar client-config opencode (V1) or modelsaar client-config opencode-v2 (V2); real client workflow not run.
ClientHermes Agentdocumentedmodelsaar client-config hermes; real client workflow not run.
BehaviourTool calls and tool-result continuation on the same deployment; session pinning via X-ModelSaar-Session / X-OpenWebUI-Chat-Idfixture tests liveLive: direct HTTP checks (scripts/live_client_checks.py), not a client's own workflow.
BehaviourStrict JSON schema, image input and context-size routing; unsupported media rejected before dispatchfixture tests live
BehaviourPool memory budgets (incl. assumed residency between polls), uncertain-occupancy holds on stream/probe/embedding timeouts, probes under gateway admissionfixture testsNot exercised on real hardware; the live run predates the 2026-10-12 corrections.
PlatformmacOS (arm64) and Linux (x86_64): source install, gateway, probes, evaluationliveWindows: use WSL2; native Windows is untested.
PlatformDocker Compose (gateway only; models stay with your runtime)fixture testsFresh-volume start is checked by scripts/compose-smoke.sh (defined in CI; no CI run exists yet for the current revision).

live ran against a real backend; fixture tests are the repository's test suite against a controlled fake backend (run locally for every number on this site; the same suite runs in CI on push, and a CI run is reported only once one exists for the revision); documented means the configuration was written from the client's own documentation and has not been exercised. Product names identify compatible software; no partnership or endorsement is implied. Details per client and backend: Integrations.

Measured, not promised

In the recorded run of 2026-10-11 (three models on one machine, 32 held-out tasks, 400-token budget), automatic routing and the strongest single model each scored 30 of 32; automatic routing completed 32 requests versus 31 (the image task the fixed model cannot take), recorded zero capability violations versus one, and had a p95 end-to-end latency of 7.958 s versus 4.760 s because two requests went to a slower thinking model. Routing overhead — the decision itself, separate from the response — was 0.98 ms at p95.

That is one run on one machine with a small task set; the confidence intervals overlap. Read the methodology, numbers and limitations, or regenerate every aggregate from the raw per-request records.

Practical questions

What hardware do I need?

Whatever your inference backend needs to run the models you want; ModelSaar adds a small Python process and no GPU requirement of its own. It has been run on a MacBook Air talking to an ASUS GX10 over a private network, and on a plain Linux container against a test backend.

Do I need models already?

Yes. ModelSaar never downloads models and never accepts model licences on your behalf. Pull them with your backend (for example ollama pull gemma4:e4b) first. modelsaar demo starts a clearly labelled simulated backend if you want to try the commands without any model.

Does it use the internet?

Not for routing. The gateway binds to loopback, talks only to the backend addresses you configure, sends no telemetry and never fetches remote media. The installer downloads the ModelSaar source from GitHub at a pinned commit, and your package tool downloads its Python dependencies.

What about cloud models my backend forwards to?

By default (locality_mode: local_only) deployments that Ollama marks as cloud-forwarded are excluded, even when a client asks for them by name. Set allow_remote deliberately if you want them; the decision record shows the locality evidence either way.

I only have one model.

Then auto uses that model whenever it qualifies for the request, and you still get one address, generated keys, a health check and a decision record. Adding a second model later changes nothing in your apps.

Which models are supported?

Any model your backend serves. Capabilities (tools, image input, strict JSON, streaming) are discovered from the backend and verified with short probes; a capability that is unknown never qualifies a model for a request that needs it. A small catalogue supplies optional quality hints that are labelled as priors until you measure.

What happens when nothing qualifies?

You get an HTTP 422 with the blocker for every deployment and a remedy — never a silently truncated history, a dropped image or a weakened schema.

What does it cost? What is the licence?

ModelSaar is free, Apache-2.0 licensed open source. Model weights keep their own licences, which you accept through your backend.

How do I update or uninstall?

Re-run the installer: it pins the newer candidate commit and keeps your config, keys and state. modelsaar uninstall removes only the files ModelSaar created and lists anything it left in place; then remove the package with your package tool. Details in Installation.

How do I see why a model was chosen?

Every response carries X-ModelSaar-Request-Id and X-ModelSaar-Deployment. modelsaar decisions lists recent decisions, modelsaar explain -m "…" dry-runs a request, and /admin/ui shows the status page. Records hold metadata only, never your prompts.

Try it on the models you already have

Python and a running backend are all you need for a first routed request. If something does not work, the issue templates ask for exactly the decision record and doctor output that make it fixable.