On this page

On this page

Providers

vLLM

vLLM serves open-source (and some custom) models through an OpenAI-compatible HTTP API. OpenClaw connects using the openai-completions API and can auto-discover models when you opt in with VLLM_API_KEY.

Property Value
Provider ID vllm
API openai-completions (OpenAI-compatible)
Auth VLLM_API_KEY environment variable
Default base URL http://127.0.0.1:8000/v1
Streaming usage Supported (stream_options.include_usage)

Getting started

  • Start vLLM with an OpenAI-compatible server

    Your base URL must expose /v1 endpoints (/v1/models, /v1/chat/completions). Start the server with the model you want to serve:

    bash
    vllm serve <model-id>

    See the vLLM online serving docs for flags. vLLM commonly runs on:

    text
    http://127.0.0.1:8000/v1
  • Set the API key environment variable

    Any non-empty value works if your server does not enforce auth:

    bash
    export VLLM_API_KEY="vllm-local"
  • Select a model

    Replace with one of your vLLM model IDs:

    json5
    {  agents: {    defaults: {      model: { primary: "vllm/your-model-id" },    },  },}
  • Verify the model is available

    bash
    openclaw models list --provider vllm
  • Model discovery (implicit provider)

    When VLLM_API_KEY is set (or an auth profile exists) and models.providers.vllm is not defined, OpenClaw queries GET http://127.0.0.1:8000/v1/models and converts the returned IDs into model entries.

    Explicit configuration

    Configure explicitly when vLLM runs on a different host or port, you want to pin contextWindow/maxTokens, your server requires a real API key, or you connect to a trusted loopback, LAN, or Tailscale endpoint:

    json5
    {  models: {    providers: {      vllm: {        baseUrl: "http://127.0.0.1:8000/v1",        apiKey: "${VLLM_API_KEY}",        api: "openai-completions",        timeoutSeconds: 300, // Optional: extend request timeout for slow local models        models: [          {            id: "your-model-id",            name: "Local vLLM Model",            reasoning: false,            input: ["text"],            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },            contextWindow: 128000,            maxTokens: 8192,          },        ],      },    },  },}

    To keep the provider dynamic without listing every model, add a wildcard to the visible model catalog:

    json5
    {  agents: {    defaults: {      models: {        "vllm/*": {},      },    },  },}

    Advanced configuration

    Prioritize interactive requests

    When interactive and background work share one vLLM engine, enable native priority scheduling on the server:

    bash
    vllm serve <model-id> --scheduling-policy priority

    Then opt in for that model in OpenClaw:

    json5
    {  agents: {    defaults: {      models: {        "vllm/your-model-id": { params: { priorityScheduling: true } },      },    },  },}

    priorityScheduling is an optional boolean, disabled by default. It applies only to the selected vllm/* model's params; agent-specific model params can override it. Global or agent-wide params do not enable it. Other providers and fallback models do not inherit the opt-in.

    Request Native priority
    Interactive user turn or manual /compact -100
    Delegated or unclassified work 0
    Cron, heartbeat, memory, or simple helper completion 100

    Run-triggered compaction inherits the originating urgency. Independent automatic compaction uses background priority. Simple helper completions include utility summaries and tool-side model calls, even when a user is waiting for their result.

    Lower numbers are scheduled first under contention; arrival order breaks ties. OpenClaw sends the native field and does not maintain another queue, reserve capacity, or isolate model memory. See the vLLM scheduler reference.

    When enabled, dynamic priority takes precedence over an explicit extraBody.priority or extra_body.priority. When disabled, OpenClaw adds no scheduling field and preserves existing literal priority overrides, including zero. Numeric priority: 0 never enables scheduling.

    The vLLM request contract requires priority scheduling for nonzero priorities. Before switching the server back to FCFS, disable priorityScheduling and remove any explicit nonzero priority overrides. This new optional field needs no Doctor migration.

    Proxy-style behavior

    vLLM is treated as a proxy-style OpenAI-compatible /v1 backend, not a native OpenAI endpoint:

    Behavior Applied?
    Native OpenAI request shaping No
    service_tier Not sent
    Responses store Not sent
    Prompt-cache hints Not sent
    OpenAI reasoning-compat payload shaping Not applied
    Hidden OpenClaw attribution headers Not injected on custom base URLs
    Qwen thinking controls

    For Qwen models, set compat.thinkingFormat: "qwen-chat-template" on the model row when the server expects Qwen chat-template kwargs. These models expose a binary /think profile (off, on) because Qwen chat-template thinking is an on/off flag, not an OpenAI-style effort ladder.

    json5
    {  models: {    providers: {      vllm: {        models: [          {            id: "Qwen/Qwen3-8B",            name: "Qwen3 8B",            reasoning: true,            compat: { thinkingFormat: "qwen-chat-template" },          },        ],      },    },  },}

    OpenClaw maps /think off to:

    json
    {  "chat_template_kwargs": {    "enable_thinking": false,    "preserve_thinking": true  }}

    Non-off thinking levels send enable_thinking: true. If your endpoint expects DashScope-style top-level flags instead, use compat.thinkingFormat: "qwen" to send enable_thinking at the request root.

    If your served template accepts effort levels, declare them in compat.supportedReasoningEfforts, for example ["low", "medium", "xhigh"]. OpenClaw then exposes those /think choices plus off. The shared reasoning resolver maps the selected level to the declared wire value. With qwen-chat-template, that value goes in chat_template_kwargs.reasoning_effort; with qwen, it goes in root reasoning_effort.

    Provider-native values are case-sensitive. Use compat.reasoningEffortMap, such as { low: "LOW", high: "HIGH" }, to map logical choices to a declared native list such as ["LOW", "HIGH"]. Unmapped native labels are not advertised as effort choices. Missing, empty, or unusable lists keep binary thinking, as does compat.supportsReasoningEffort: false.

    The plugin prepares these mappings as model capabilities before session setup, so advanced choices such as xhigh and max also survive session-level clamping when their native wire labels differ.

    The default remains off, including after upgrading an existing configured model. An explicit enabled level now sends its declared effort instead of silently using the template's default. Ordinary binary Qwen models keep their existing request shape. Per-model params.extra_body remains the final request-body override.

    DeepSeek V4 thinking controls

    For vLLM model IDs containing deepseek-v4 or deepseek_v4, configure reasoning: true. OpenClaw sends the selected effort through chat_template_kwargs.reasoning_effort, with both thinking and enable_thinking set to true. Declared efforts and reasoningEffortMap use the same shared resolver as other OpenAI-compatible models.

    /think off sends both template flags as false, because vLLM enables DeepSeek thinking when either flag is true. Hosted DeepSeek's root thinking object and root reasoning_effort are removed. Existing explicit template kwargs and the final params.extra_body override remain authoritative. Explicit Qwen thinking formats take precedence over the model-name match.

    This request shaping does not enable reasoning for catalog rows marked reasoning: false or change discovery heuristics. Configure the model explicitly if discovery does not recognize its reasoning capability.

    Nemotron 3 thinking controls

    For vllm/nemotron-3-* models with thinking off, the bundled plugin sends:

    json
    {  "chat_template_kwargs": {    "enable_thinking": false,    "force_nonempty_content": true  }}

    To customize these values, set chat_template_kwargs under the model params. If you also set params.extra_body.chat_template_kwargs, that value wins because extra_body is the last request-body override.

    json5
    {  agents: {    defaults: {      models: {        "vllm/nemotron-3-super": {          params: {            chat_template_kwargs: {              enable_thinking: false,              force_nonempty_content: true,            },          },        },      },    },  },}
    Qwen tool calls appear as text

    First confirm vLLM was started with the right tool-call parser and chat template for the model. vLLM documents hermes for Qwen2.5 models and qwen3_xml for Qwen3-Coder models.

    Symptoms: skills/tools never run, the assistant prints raw JSON/XML such as {"name":"read","arguments":...}, or vLLM returns an empty tool_calls array when OpenClaw sends tool_choice: "auto".

    Some Qwen/vLLM combinations return structured tool calls only when the request uses tool_choice: "required". Force it per model with params.extra_body:

    json5
    {  agents: {    defaults: {      models: {        "vllm/Qwen-Qwen2.5-Coder-32B-Instruct": {          params: {            extra_body: {              tool_choice: "required",            },          },        },      },    },  },}

    Replace the model id with the exact id from openclaw models list --provider vllm, or apply the same override from the CLI:

    bash
    openclaw config set agents.defaults.models '{"vllm/Qwen-Qwen2.5-Coder-32B-Instruct":{"params":{"extra_body":{"tool_choice":"required"}}}}' --strict-json --merge

    This is an opt-in workaround: it forces every turn with tools to make a tool call, so use it only for a dedicated model entry where that is acceptable. Do not set it as a global default for all vLLM models, and do not pair it with a proxy that converts arbitrary assistant text into executable tool calls.

    Custom base URL

    If your vLLM server runs on a non-default host or port, set baseUrl in the explicit provider config:

    json5
    {  models: {    providers: {      vllm: {        baseUrl: "http://192.168.1.50:9000/v1",        apiKey: "${VLLM_API_KEY}",        api: "openai-completions",        timeoutSeconds: 300,        models: [          {            id: "my-custom-model",            name: "Remote vLLM Model",            reasoning: false,            input: ["text"],            contextWindow: 64000,            maxTokens: 4096,          },        ],      },    },  },}

    Troubleshooting

    Slow first response or remote server timeout

    For large local models, remote LAN hosts, or tailnet links, set a provider-scoped request timeout:

    json5
    {  models: {    providers: {      vllm: {        baseUrl: "http://192.168.1.50:8000/v1",        apiKey: "${VLLM_API_KEY}",        api: "openai-completions",        timeoutSeconds: 300,        models: [{ id: "your-model-id", name: "Local vLLM Model" }],      },    },  },}

    timeoutSeconds applies to vLLM model HTTP requests only: connection setup, response headers, body streaming, and the total guarded-fetch abort. It also raises the LLM idle/stream watchdog ceiling above the implicit ~120s default for this provider. Prefer this over increasing agents.defaults.timeoutSeconds, which controls each model attempt's execution budget. A lower agent or run-specific timeout still caps the attempt; each configured fallback gets a fresh budget.

    Server not reachable

    Check that the vLLM server is running and accessible:

    bash
    curl http://127.0.0.1:8000/v1/models

    If you see a connection error, verify the host, port, and that vLLM started in OpenAI-compatible server mode. OpenClaw trusts the exact configured models.providers.vllm.baseUrl origin for guarded model requests on loopback, LAN, and Tailscale endpoints. Metadata, link-local, and local-use NAT64 (64:ff9b:1::/48) origins remain blocked without explicit opt-in. Set models.providers.vllm.request.allowPrivateNetwork: true only when vLLM requests must reach another private origin, or false to opt out of exact-origin trust.

    Auth errors on requests

    If requests fail with auth errors, set a real VLLM_API_KEY that matches your server configuration, or configure the provider explicitly under models.providers.vllm.

    No models discovered

    Auto-discovery requires VLLM_API_KEY to be set. If you have defined models.providers.vllm, OpenClaw uses only your declared models unless agents.defaults.models includes "vllm/*": {}.

    Tools render as raw text

    If a Qwen model prints JSON/XML tool syntax instead of executing a skill:

    • Start vLLM with the correct parser/template for that model.
    • Confirm the exact model id with openclaw models list --provider vllm.
    • Add a dedicated per-model params.extra_body.tool_choice: "required" override only if tool_choice: "auto" still returns empty or text-only tool calls.
    Was this useful?