Browse the docs

Docs / Model runtime

Model capabilities and model listings

Advertise images, tools and context length in /v1/models, automatically or by hand.

capabilities describes what a model can accept or produce. llama-swap reports it through /v1/models so clients can pick a compatible model. This is listing metadata only: it does not change routing or how requests are proxied. In particular tools: true makes the listing report capabilities.function_calling: true and advertise tools and tool_choice as supported parameters. It does not enable tool calling by itself; the model still needs a compatible chat template and runtime.

Automatic discovery

llama-swap asks the upstream server what it supports the first time a model becomes ready, and caches the answer. Anything you do not set yourself is filled in from that answer.

models:
  vision-model:
    cmd: llama-server -m vision.gguf --mmproj mmproj.gguf --port ${PORT}
    # no capabilities block needed

After this model has been loaded once, /v1/models reports its context length and image input on its own.

The context length reported is the window one request can actually use, not the one the model was trained for. A llama-server started with a smaller --ctx-size than the model supports advertises the smaller number, and a server whose slots are capped advertises the per-slot window rather than the total it loaded.

What each server can report:

llama-server vLLM halogen
context length yes, the loaded n_ctx yes, max_model_len yes
image input yes no yes
audio or video input yes no no
tools yes, from the chat template no yes
decision model output yes, from /v1/models no no
audio output (speech) yes, from /v1/models no no
reranker no no no
embeddings no no no

llama-server is read from /props, and halogen-flash-server from its /health, which that project documents as the authoritative report of what the running build accepts. Both vary per deployment: halogen serves images only when it was started with a vision tower, and that is read rather than assumed.

What a model outputs is the one thing /props cannot show, so it is read from the output_modalities in llama-server's own /v1/models. Decision models answer /v1/systemone and generate no text; llama-server marks them with ["decisions"], and llama-swap reports out: [decisions] and no tools for them, whatever their chat template says. Speech models report audio: LFM Audio reports ["text", "audio"] and gets out: [text, audio], and a speech-only model reports ["audio"] and gets out: [audio]. Audio output alone does not mean a model can hold a /v1/realtime conversation (the Playground's Realtime tab); that also needs a model that chats, such as LFM Audio.

vLLM is the thin one. Nothing it serves says whether it was started with --enable-auto-tool-choice, or whether the model takes images, so set those by hand on vLLM models. Other servers whose /v1/models lists a context length, such as Gufo and yamz (max_context), are read the same way: context length only.

llama-swap picks the right reader from the owned_by field in the server's own /v1/models: llamacpp, vllm or halogen. To check what a server will be detected as, ask it directly:

curl -s localhost:PORT/v1/models | jq -r '.data[0].owned_by'

Servers reporting anything else are left alone, including image, speech and transcription servers, which have no capability surface to read. A llama.cpp fork that keeps the upstream API reports llamacpp and is read the same way.

Setting capabilities by hand

Values you write win, field by field, as long as they are not the zero value for their type. Discovery only fills the gaps, so you can correct one field without giving up the rest:

models:
  vllm-model:
    cmd: vllm serve my-model --port ${PORT} --enable-auto-tool-choice
    capabilities:
      # vLLM cannot report this, so say it here
      tools: true
      # context is still filled in automatically from max_model_len

Set only what the model really supports. A false claim makes clients send requests that fail or get ignored.

Marking embedding and rerank models

Embedding servers have nothing to probe: no endpoint tells an embedding model from a chat model without sending a real request, so discovery never fills this in. The same is true for a reranker. Set the flag by hand on both:

models:
  gte-modernbert:
    cmd: llama-server -m gte-modernbert-F16.gguf --embedding --pooling cls --port ${PORT}
    capabilities:
      embeddings: true
      context: 8192
  gte-reranker:
    cmd: llama-server -m gte-reranker-F16.gguf --embedding --pooling rank --port ${PORT}
    capabilities:
      reranker: true
      context: 8192

The flags only change listing metadata: /v1/models reports capabilities.embeddings: true or capabilities.reranker: true so clients can tell these models apart from chat models, filter for them, and show a badge. Neither flag starts, stops or reroutes anything; what the server actually serves is decided by its command line. Without the flag an embedding model that reports text -> text from /props is indistinguishable from a chat model in the listing.

Turning discovery off

A field set to its zero value (false, 0, []) cannot be told apart from one you never wrote, so tools: false will not override a discovered true. To take full control of a model's listing, turn discovery off:

models:
  my-model:
    cmd: llama-server -m model.gguf --port ${PORT}
    capabilities:
      disableAuto: true
      in: [text]
      out: [text]
      context: 8192

With disableAuto: true the model advertises exactly what you wrote, and nothing else. On its own, with no other fields, it advertises nothing.

Setting the voice list for a speech model

GET /v1/audio/voices?model={id} normally goes to the upstream server. capabilities.voices is an override: set it to answer the request from the config instead:

models:
  tts:
    cmd: my-tts-server --port ${PORT}
    capabilities:
      in: [text]
      out: [audio]
      voices: ["voicea", "voiceb", "voicec"]
curl -s 'localhost:8080/v1/audio/voices?model=tts'
# {"model":"tts","voices":["voicea","voiceb","voicec"]}

The response is a JSON object with a voices array and the model id. OpenAI has no endpoint for listing voices, so this is a llama-swap convention, not an OpenAI format. The Playground's speech tab reads it. The model is not started to answer, and voices are not shown in /v1/models. When voices is empty or unset there is no override, and the request goes to the upstream server as before, which starts the model if it is not running. This applies to local models only, not peers.

How the Playground uses this

Each Playground tab looks for the modalities it needs and lists those models first, under "Matching Capabilities":

tab wants
Chat in has text or image, out has text
Images out has image
Speech in has text, out has audio
Transcription in has audio, out has text
Rerank reranker: true
Help's Docs agent tools: true

Discovery fills most of this in on its own once a model has been loaded, so a llama-server model usually sorts itself. Models that report nothing are still listed, further down under "Local", so nothing disappears from the selector for want of configuration. Image, speech and transcription servers are the ones worth setting by hand: nothing is read from them, so without an in/out block they never sort to the top of their own tab.

What goes wrong

A model that has never been loaded shows nothing. The server has to be running to be asked. Load the model once and the values appear, including after it unloads again.

Discovered values do not survive a restart unless you configure a store. The cache lives in llama-swap's SQLite database, which is in memory unless store.path is set:

store:
  path: /var/lib/llama-swap/llama-swap.db

Without it, every restart starts from an empty cache and each model has to be loaded again before its capabilities reappear.

The cache can be stale. Values are refreshed each time a model starts, so between loads they describe the last run. Changing a model's cmd invalidates its entry immediately, so shrinking --ctx-size will not keep advertising the old number, but other changes to the same command (a different --mmproj, for example) only take effect the next time the model loads.

Tool support is read from the chat template. llama-server reports what the template can do, not what the model was trained for. A template that renders a tool list is advertised as supporting tools even if the model is bad at using them.