> ## Documentation Index
> Fetch the complete documentation index at: https://unmute.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Unmute compiles to exactly three targets. Pipecat and LiveKit are code targets: compile writes a Python project you run. SLNG is a hosted target: compile writes a deployment body and SLNG runs the agent, so it has no `unmute dev`. Those three are the only values `provider` accepts in `targets.yaml`. Deepgram and ElevenLabs appear in these docs as model vendors, which is not the same thing as a target, and `slng` is both.
> The Go structs in `internal/spec` and `internal/ir` are the schema truth. Check a field against them, or run `unmute validate`, rather than against what you remember.

# Reasoning model

> The think binding: every key it takes, the vendors each target can construct, and what happens when yours is not listed.

`models.think` picks the model that decides what to say and when to call a tool.

<Note>
  **SLNG serves this role as a proxy with a cache in front.** The
  [Context Router](/optimization/context-router) answers the turns it judges
  repeatable and calls your model for the rest. The model stays yours to
  choose, and so does the provider that serves it, so one `SLNG_API_KEY` can
  sit in front of any host you already use.
</Note>

On this page:

* [Quickstart](#quickstart) - the smallest binding that works
* [Every key a think binding takes](#every-key-a-think-binding-takes) - the full shape
* [Why `reasoning_effort` is there](#why-reasoning_effort-is-there) - the line that stops a 400
* [LiveKit Responses API](#livekit-responses-api) - one target's own client
* [Native Gemini locations](#native-gemini-locations) - Google Vertex with your API key
* [Pipecat](#pipecat) and [LiveKit](#livekit) - the vendors each target can construct
* [When your provider is not listed](#when-your-provider-is-not-listed) - two different answers
* [Fallback](#fallback) - what to try when the first call fails
* [Troubleshooting](#troubleshooting) - the refusals that come up most

## Quickstart

```yaml agent.yaml theme={null}
models:
  think:
    reasoning:
      provider: openai
      model: gpt-5.6-terra
      params:
        reasoning_effort: "none"
```

```sh theme={null}
unmute validate my-agent
```

<Info>
  Model ids are forwarded to the vendor exactly as you write them. Unmute keeps
  no allowlist, so a typo surfaces as a provider error at run time. The LiveKit
  Responses mode below is the narrow exception: `api` selects the generated
  client and `reasoning_effort` becomes the API's nested reasoning setting.
</Info>

## Every key a think binding takes

`provider:` and `model:` carry a plain binding. The rest shape the request, or
put the Context Router in front.

<ParamField path="provider" type="string">
  A provider supported for this role and target, listed on the model pages. Required for
  an API binding; there is no inferred API provider. `local` selects local placement.
</ParamField>

<ParamField path="model" type="string">
  Provider model id, passed through as written. Required where the selected integration
  requires a model; otherwise its default applies. No model id is checked against a provider catalog.
</ParamField>

<ParamField path="params" type="object">
  Provider parameter names and values. Omit to add no extra parameters. Provider limits
  apply; Unmute does not define a universal accepted set. SLNG `params.world_part`
  is checked against its supported regions.
</ParamField>

<ParamField path="temperature" type="number">
  Sampling temperature on a `think` entry. Provider-defined values and limits; omitted
  leaves the provider default.
</ParamField>

<ParamField path="top_p" type="number">
  Nucleus sampling value on a `think` entry. Provider-defined values and limits; omitted
  leaves the provider default.
</ParamField>

<ParamField path="top_k" type="integer">
  Sampling count on a `think` entry. Provider-defined values and limits; omitted leaves
  the provider default.
</ParamField>

<ParamField path="endpoint_env" type="an environment variable name">
  Points at a variable holding an OpenAI-compatible endpoint URL, for a binding
  that talks to your gateway directly. On Pipecat this is what lets an unlisted
  provider through.
</ParamField>

<ParamField path="placement" type="api | local">
  Whether the model is called over the network or runs in the agent's own
  process. Left out, it is `api` whenever the entry names a provider or a
  model. `local` is refused on the slng target.
</ParamField>

<ParamField path="fallback" type="list of strings">
  Names from the same `think` or `listen` section, in retry order. Omit for no fallback
  chain. Cycles and other roles are refused; Pipecat refuses generated fallback.
</ParamField>

<ParamField path="prompt_suffix" type="string, up to 512 characters">
  Literal text appended to every system prompt this binding sends: each agent's,
  each task's, and the summarizer's. No `{{variables}}`. It exists for models
  that take instructions no parameter can carry. A per-target override cannot
  name a different value.
</ParamField>

<ParamField path="agent_id" type="string">
  Scopes the Context Router's cache. One stable value per package, written by
  you, with a version suffix you bump after a prompt change you judge
  meaningful. Router bindings only, and refused on any other.
</ParamField>

<ParamField path="upstream" type="block: provider, url, key_env">
  Where the router actually calls the model and whose credentials pay for it.
  Required on a router think binding, because the configuration travels inline
  on every request. Router bindings only.
</ParamField>

<ParamField path="description" type="string">
  An author note. It reaches no generated artifact, so it is for the next
  person reading `agent.yaml`.
</ParamField>

## Why `reasoning_effort` is there

`gpt-5.6-terra` is a reasoning model, and OpenAI rejects a chat completions
request that carries function tools unless the request also sets
`reasoning_effort`. Leaving the line out is not the same as leaving the value
alone: the server applies its own default, and every turn comes back as HTTP
400\. This model takes `none`, `low`, `medium`, `high`, and `xhigh`. It rejects
`minimal`.

Every shared example profile writes `none`, and so does `unmute init`. Keep the
line whenever the agent has tools; a target-specific Responses override is the
exception below.

<Note>
  On LiveKit the line matters for a second reason. `livekit-plugins-openai`
  1.8.1 injects `reasoning_effort="minimal"` by itself for several older ids in
  the same GPT-5 family, which is the same 400 once the agent has tools. Setting
  the param yourself is what avoids it.
</Note>

One shared authoring line lowers correctly to both targets. LiveKit passes it as a
constructor argument, `openai.LLM(..., reasoning_effort="none")`. Pipecat's
settings class has no field for it, so it rides the service's `extra` field,
`OpenAILLMService.Settings(..., extra={"reasoning_effort": "none"})`, which
Pipecat merges into the request body as written.

## LiveKit Responses API

Ask for the Responses API on the shared think binding when a package needs it:

```yaml agent.yaml theme={null}
models:
  think:
    reasoning:
      provider: openai
      model: gpt-5.6-terra
      params:
        api: responses
        reasoning_effort: none
        use_websocket: true
```

Unmute emits `openai.responses.LLM` and turns `reasoning_effort` into the API's
nested reasoning setting when it is present. Use that field instead of a raw
`reasoning` map.

`api` and `use_websocket` are the two params only LiveKit can act on: one picks
the class, the other is a kwarg that class has and the chat completions one
does not. Write them here rather than in a target override. A per-target
`models:` entry replaces the base entry instead of merging into it. An override
would have to repeat `provider`, `model` and `reasoning_effort` to keep them,
and two copies of one binding is a binding somebody edits on one side only.
Pipecat drops both and builds `OpenAILLMService` either way, and `unmute
validate` prints a warning per param naming the target, so you are told what
did not apply. `salon-concierge-single-prompt` shows this binding;
`salon-concierge` uses native Gemini through Google's EU Vertex endpoint.

`use_websocket: true` keeps a WebSocket connection for Responses requests.
HTTP clients can also reuse connections, so compare latency on the package's
own prompts and tools before choosing a transport.

## Native Gemini locations

`provider: google` uses the native Google plugin on both targets. `gemini` is
also accepted. On LiveKit this uses `livekit-plugins-google`, with no LiveKit
Inference request.

```yaml theme={null}
models:
  think:
    reasoning:
      provider: google
      model: gemini-3.5-flash-lite
      params:
        vertexai: true
        location: us
        thinking_config:
          thinking_level: minimal
```

Set `GOOGLE_API_KEY` in the package's `.env`. `params.location` selects the
inference endpoint, independently of the worker's deployment region.

| Location                                 | Inference endpoint                              |
| ---------------------------------------- | ----------------------------------------------- |
| `us`                                     | `https://aiplatform.us.rep.googleapis.com`      |
| `eu`                                     | `https://aiplatform.eu.rep.googleapis.com`      |
| `global`                                 | `https://aiplatform.googleapis.com`             |
| Individual region, such as `us-central1` | `https://us-central1-aiplatform.googleapis.com` |

These are [Google's endpoint rules](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/locations).
Unmute checks the location's format without keeping a region allowlist.
Google determines whether the requested model and API key work at that location.
An unsupported location or unavailable model fails at the requested endpoint;
there is no fallback to global or another region.

The pinned plugins handle streaming and tools; a small adapter supplies their
Google GenAI client because their Vertex constructors expect OAuth credentials.
Other provider parameters, including `thinking_config`, still reach the native
plugin. Omit `vertexai` and `location` to keep using the Gemini Developer API
with `GOOGLE_API_KEY`.

<ParamField path="params.vertexai" type="boolean">
  Set to `true` for the Vertex API-key path. Omit it for the Gemini Developer API.
</ParamField>

<ParamField path="params.location" type="string">
  Required with `vertexai: true`. Use `us`, `eu`, an individual Google region,
  or explicitly choose `global`. A location without Vertex is refused.
</ParamField>

<ParamField path="params.thinking_config" type="object">
  Google thinking options. Pipecat receives these as its native `ThinkingConfig`.
</ParamField>

<ParamField path="params.thinking_config.thinking_level" type="string">
  This example uses `minimal`. Allowed levels depend on the selected model.
</ParamField>

## Pipecat

| Provider     | Notes                                                                                                                                                                                                                                                                           |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `slng`       | the [Context Router](/optimization/context-router): a cache in front of your own model, for the turns it judges repeatable                                                                                                                                                      |
| `anthropic`  | Since Pipecat 1.10.0 `temperature`, `top_p` and `top_k` travel in the request body rather than as call parameters, because the Anthropic SDK's 1.x line dropped them. They reach the API unchanged and keep their names and meaning, so a package that sets them needs no edit. |
| `deepseek`   | Since Pipecat 1.10.0 the reasoning pass is off unless you ask for it: V4 models otherwise reason before every answer, which delays the first spoken word. `params: {thinking: {type: enabled}}` turns it on.                                                                    |
| `google`     | native Gemini; also accepted as `gemini`                                                                                                                                                                                                                                        |
| `groq`       |                                                                                                                                                                                                                                                                                 |
| `mistral`    |                                                                                                                                                                                                                                                                                 |
| `openai`     |                                                                                                                                                                                                                                                                                 |
| `openrouter` |                                                                                                                                                                                                                                                                                 |
| `qwen`       |                                                                                                                                                                                                                                                                                 |

## LiveKit

| Provider     | Notes                                                                                  |
| ------------ | -------------------------------------------------------------------------------------- |
| `slng`       | the [Context Router](/optimization/context-router): caching in front of your own model |
| `anthropic`  |                                                                                        |
| `aws`        | credentials come from the AWS SDK environment                                          |
| `azure`      |                                                                                        |
| `google`     | native Gemini; also accepted as `gemini`                                               |
| `groq`       |                                                                                        |
| `mistralai`  | also accepted as `mistral`                                                             |
| `openai`     |                                                                                        |
| `openrouter` |                                                                                        |
| `sarvam`     |                                                                                        |

## The differences that matter

This is the role where the two lists diverge most. LiveKit adds `aws`, `azure`,
and `sarvam`. Pipecat adds `deepseek` and `qwen`. LiveKit spells the
Mistral integration `mistralai`.

## When your provider is not listed

The two targets answer this differently, and the difference is worth knowing before
you pick one.

**On Pipecat**, an unlisted provider is legal for any role on one condition: a
genuinely OpenAI-compatible endpoint, named with `endpoint_env`.

### Behind the Context Router: `openai-compat`

`endpoint_env` above is for a think binding that talks to your gateway directly.
When the [Context Router](/optimization/context-router) is in front, the upstream
travels inline in the request body instead, and an OpenAI-compatible host is the
`openai-compat` kind. OpenRouter is the worked example:

```yaml agent.yaml theme={null}
secrets:
  - SLNG_API_KEY
  - OPENROUTER_API_KEY

models:
  think:
    reasoning:
      provider: slng
      model: qwen/qwen3-32b               # the slug the host publishes
      agent_id: salon-concierge-v1
      upstream:
        provider: openai-compat
        url: https://openrouter.ai/api/v1
        key_env: OPENROUTER_API_KEY
      params:
        world_part: eu-west               # where the router serves you
        provider:                         # forwarded to OpenRouter
          only: ["groq"]
```

`url` and `key_env` are both required here: there is no default to fall back on,
and your key travels to the router on every think request, so a package has to say
whose it is.

Two things this shape buys, both worth knowing before you copy it:

* **A host pin.** OpenRouter serves one model from several providers, and they do
  not behave the same. `params.provider` is forwarded verbatim, so you can accept
  one. With `only` set, an unavailable host is a 404 with the message intact rather
  than a quiet fall-back to a provider that breaks your tools.
* **The context ceiling is the host's, not the model's.** A model card advertises
  the largest window any provider offers. On `qwen/qwen3-32b` that is 131,072 on
  Groq and 40,960 on Nebius, for the same model. Check the one you pinned.

**Measure the host you pin, and measure two things separately.** On
`qwen/qwen3-32b` the four providers spanned a wide range at both the median and
the tail. Latency did not predict correctness: the second-fastest spoke a
fragment of its own tool-call template into every reply. A provider's published
figures are aggregated over everybody's traffic, mostly short prompts with no
tools, so they will not match a voice agent's request. Send your own prompt and
your own tool schemas, and count tool calls as well as milliseconds.

A model whose behaviour no parameter reaches may still need
[`prompt_suffix`](/optimization/context-router#prompt_suffix-for-what-no-parameter-reaches).

```yaml theme={null}
models:
  think:
    house_model:
      provider: my-gateway
      model: my-model
      endpoint_env: MY_GATEWAY_URL
```

Without `endpoint_env` it is refused, and the error lists what the role does take:

```text wrap theme={null}
pipecat reason binding provider "my-gateway" has no slot; reason providers on pipecat: anthropic, deepseek, google, groq, mistral, openai, openrouter, qwen; an unlisted provider needs endpoint_env (an OpenAI-compatible endpoint)
```

**On LiveKit**, an unlisted reasoning provider routes through LiveKit Inference:
managed models billed through LiveKit Cloud, with no provider key of their own.
That path needs `LIVEKIT_API_KEY` and `LIVEKIT_API_SECRET`, in local development
as well as in production.
Writing `provider: livekit` passes your model string through verbatim.

## Fallback

`fallback:` on a `think` entry names other think entries, tried in order when
the primary model's call fails:

```yaml agent.yaml theme={null}
models:
  think:
    assistant_model:
      provider: openai
      model: gpt-5.6-terra
      params:
        reasoning_effort: none
      fallback:
        - backup_model
    backup_model:
      provider: anthropic
      model: claude-sonnet-5
```

`fallback` is legal on `think` and `listen` models only. Naming it on a `speak`
or `turn` entry is refused, by name. A name the section does not have, or a
chain that loops back on itself, is refused too, naming the loop.

What that one block compiles to depends on the target:

<CodeGroup>
  ```python LiveKit theme={null}
  # one constructor call, wrapped in llm.FallbackAdapter
  llm=llm.FallbackAdapter(
      llm=[
          openai.LLM(
              api_key=os.environ["OPENAI_API_KEY"],
              model="gpt-5.6-terra",
              reasoning_effort="none",
          ),
          anthropic.LLM(
              api_key=os.environ["ANTHROPIC_API_KEY"], model="claude-sonnet-5"
          ),
      ]
  ),
  ```

  ```text Pipecat theme={null}
  # refused before a file is written
  pipecat: the Pipecat driver does not emit generated fallback yet
  ```

  ```json SLNG theme={null}
  // on the push body, in the same shape the primary binding uses: the driver
  // joins each entry's provider and model. See /targets/slng#model-names.
  "fallbacks": {
    "llm": ["<provider>/<model>", "<provider>/<model>"]
  }
  ```
</CodeGroup>

Pipecat does not emit a generated fallback yet, so validating the same package
against a Pipecat target refuses it. The SLNG list uses the same
[vendor/model shape](/targets/slng#model-names) as the primary binding.

[Speech to text](/models/stt#fallback) documents the same field on the `listen`
role.

## Troubleshooting

### Every turn comes back as HTTP 400

A reasoning model was sent function tools without `reasoning_effort`. **Fix:**
set it in `params:`, as the Quickstart does. On LiveKit it also stops the
plugin injecting a value of its own.

### `agent_id` or `upstream` is refused

Both belong to a think binding routed through the Context Router, and the
message names the provider the binding actually has. **Fix:** set
`provider: slng` on that binding, or remove the key.

### `prompt_suffix` is refused on a listen or speak binding

It appends to a system prompt, and only a think model sends one. **Fix:** move
it to the think binding those prompts run on.

### The package compiles for LiveKit and is refused for Pipecat

Pipecat does not emit generated fallback yet. **Fix:** remove `fallback:` from
the `think` section, or keep that package on LiveKit or slng.

### A provider is refused and the message lists other names

The vendor is not on your target's list for this role. **Fix:** see [When your
provider is not listed](#when-your-provider-is-not-listed); the two targets
answer it differently.

## Where to go next

<Columns cols={2}>
  <Card title="Turn detection" icon="mic" href="/models/turn-detection">
    Knowing when the caller has finished.
  </Card>

  <Card title="Live model" icon="waveform-lines" href="/models/live">
    One model that listens, thinks and speaks, on Pipecat.
  </Card>
</Columns>
