models.think picks the model that decides what to say and when to call a tool.
SLNG serves this role as a proxy with a cache in front. The
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.- Quickstart - the smallest binding that works
- Every key a think binding takes - the full shape
- Why
reasoning_effortis there - the line that stops a 400 - LiveKit Responses API - one target’s own client
- Native Gemini locations - Google Vertex with your API key
- Pipecat and LiveKit - the vendors each target can construct
- When your provider is not listed - two different answers
- Fallback - what to try when the first call fails
- Troubleshooting - the refusals that come up most
Quickstart
agent.yaml
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.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.
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.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.
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.number
Sampling temperature on a
think entry. Provider-defined values and limits; omitted
leaves the provider default.number
Nucleus sampling value on a
think entry. Provider-defined values and limits; omitted
leaves the provider default.integer
Sampling count on a
think entry. Provider-defined values and limits; omitted leaves
the provider default.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.
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.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.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.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.
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.
string
An author note. It reaches no generated artifact, so it is for the next
person reading
agent.yaml.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.
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.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:agent.yaml
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.
GOOGLE_API_KEY in the package’s .env. params.location selects the
inference endpoint, independently of the worker’s deployment region.
These are Google’s endpoint rules.
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.
boolean
Set to
true for the Vertex API-key path. Omit it for the Gemini Developer API.string
Required with
vertexai: true. Use us, eu, an individual Google region,
or explicitly choose global. A location without Vertex is refused.object
Google thinking options. Pipecat receives these as its native
ThinkingConfig.string
This example uses
minimal. Allowed levels depend on the selected model.Pipecat
LiveKit
The differences that matter
This is the role where the two lists diverge most. LiveKit addsaws, 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 withendpoint_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 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:
agent.yaml
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.provideris forwarded verbatim, so you can accept one. Withonlyset, 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-32bthat is 131,072 on Groq and 40,960 on Nebius, for the same model. Check the one you pinned.
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.
endpoint_env it is refused, and the error lists what the role does take:
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:
agent.yaml
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:
listen
role.
Troubleshooting
Every turn comes back as HTTP 400
A reasoning model was sent function tools withoutreasoning_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: removefallback: 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; the two targets answer it differently.Where to go next
Turn detection
Knowing when the caller has finished.
Live model
One model that listens, thinks and speaks, on Pipecat.