AI Providers
Ce contenu n’est pas encore disponible dans votre langue.
The AI Providers page (/admin/ai-providers) is the server-wide provider catalog: the list of AI endpoints this instance can route a request to. A catalog entry is one endpoint with one model, named by a short id. That id is what a group’s provider override, a guardrail’s classifier and hit models, and the embedding and chunk-context settings all point at.
Nothing here is per user. Personal provider keys that a desktop client syncs are a different thing entirely and never appear on this page.
Two sources, one list
Section titled “Two sources, one list”The catalog is filled from two places at once, and the grid shows both together.
| Source | Comes from | Editable here | Takes effect |
|---|---|---|---|
| defined in config | Ai:Providers in appsettings.json, environment variables, or .env |
No — it belongs to the deployment | On restart |
| Database | Rows you add on this page | Yes | On the next request |
An id may exist in only one of them, and ids are compared without regard to case — LAN-Ollama and lan-ollama are the same id. Creating a row is refused if that id is already defined in configuration, and refused again if it already belongs to another row. If a configuration entry later appears with the id of an existing row, configuration wins: the row is badged Shadowed and stops resolving, but is never deleted for you. Rename the row or drop the configuration entry.
The list
Section titled “The list”One row per entry, sorted by name.
| Column | Reads |
|---|---|
| Name | The label shown wherever a provider is picked |
| Id | The stable reference other settings store |
| Type | One of OpenAI, AzureOpenAI, Ollama, OpenRouter, OpenAICompatible, Mistral, VLlm, Anthropic — or Linkup, which is a web search provider rather than a chat model |
| Model | The single model this entry serves. On a Linkup row it holds the search depth instead |
| Endpoint | The host only, never the full URL. (invalid) if the stored value will not parse |
| Source | defined in config with a lock, or Database |
| Status | Resolvable, or Disabled / Invalid / Shadowed — hover the badge for the reason |
| API key | key set — SHA-256 XXXXXXXX… or not set |
A configured entry has a Test button and nothing else — no edit, no delete. Editing it would mean editing a deployment, which is not something this page can do. A database row also has Edit, Disable / Enable and Delete.
An empty page says No providers yet — that means neither source produced an entry, not that the page failed.
Adding a provider
Section titled “Adding a provider”-
Press Add provider.
-
Give it an Id. At most 64 characters, letters, digits,
.,-and_, starting with a letter, digit or_. It is immutable after create — group settings reference it — so pick something you can live with, likelan-ollama. -
Give it a Name. This is the label operators see in every picker.
-
Choose the Provider type, then fill in Endpoint and Model. The endpoint must be an absolute
http://orhttps://URL, and it may not carry credentials: ahttps://user:secret@hostform is refused, because the endpoint is stored unencrypted and would never be sent as authentication anyway.Choosing the type fills the Endpoint in for you, with the base URL of the service you picked —
https://api.openai.com,https://openrouter.ai/api,https://api.mistral.ai,http://localhost:11434for Ollama,http://localhost:8000for vLLM. A value you typed yourself is never overwritten, and neither is the one an existing row already carries.AzureOpenAIandOpenAICompatiblehave no default — the endpoint is your own resource or deployment — so picking either clears a prefill rather than leaving the previous service’s URL under it.Give the base URL, not the versioned one: the server appends the version and path each provider needs, so
https://api.openai.comand nothttps://api.openai.com/v1. A trailing/v1is tolerated on the chat path but breaks Ollama embeddings, which are served from/api/embeddings.Two types ask for one more field, and the field only appears for that type: Azure deployment name for
AzureOpenAI, Mistral agent id forMistral. -
Optionally pick a Reasoning effort —
None,Minimal,Low,Medium,HighorXHigh. Leave it on (provider default) to send no preference at all.Noneis not the same thing: it is an explicit instruction to the upstream not to think. Only the types that own a real off-switch deliver it —Ollama,VLlm, aMistralrow on a reasoning-capable model (mistral-small-latestormistral-medium-3.5) with no agent id and Web search off, and anOpenAICompatibleendpoint with the switch in step 6 turned on. OnOpenAI,AzureOpenAIandOpenRouterit is omitted instead, because those models reject the value outright. AnAnthropicrow maps the whole ladder ontooutput_config.effort— the one type whereXHighsurvives rather than collapsing toHigh— and deliversNoneby turning thinking off outright.VLlmis the one exception to the sentence before that: it always pinschat_template_kwargs.enable_thinking, so on that type (provider default) andNoneare the same setting, and neither leaves the model to its own default. -
Set the capability switches: Web search, Vision, Upstream honors
reasoning_effort, Upstream accepts a response schema, and Cache the prompt prefix (5 min). Thereasoning_effortone only matters for anOpenAICompatibleendpoint — turn it on when the server accepts the flat OpenAI-style field, and leave it off for strict servers, which reject unknown fields outright.Upstream accepts a response schema lets Pia constrain a reply to a JSON schema where it needs an exact shape — today that is guardrail classification, where the schema’s
labelis an enum of the guard’s own labels, so an out-of-vocabulary verdict becomes impossible rather than being caught afterwards. Leave it off unless Test says otherwise: the probe sends a real two-value schema and reports A JSON response schema is honoured or is not honoured, which is a measurement rather than a guess. Support does not follow from the provider type — one measured gateway acceptsjson_schemaand rejectsjson_objectoutright, and another may take onlyjson_object. If you tick it wrongly, a guardrail call retries once without the schema and logs an error naming this switch, so classification degrades rather than failing every guarded request.Cache the prompt prefix (5 min) is live on
Anthropiconly, the one type whose handler places cache breakpoints; everywhere else caching is either automatic and outside your control or absent entirely. With it on, the system prompt and the last turn are marked cacheable for five minutes, so the next turn of a conversation re-reads its history instead of paying for it again. Leave it off unless the prefix is both long and repeated: a cache write costs more than the input it replaces, and a prompt under the model’s minimum — between 512 and 4096 tokens depending on the model — is never stored at all, so a short prompt pays the premium and reads nothing back.Web search is greyed out on the four types whose handlers have no native search —
AzureOpenAI,Ollama,OpenAICompatibleandVLlm— with the note This provider type has no native web search — give it a web search provider below instead. Under it, whenever the switch is off, a Web search provider picker appears; see Web search providers. -
Optionally set a Chat max_tokens cap — the longest answer this provider may return. Leave it empty and the provider follows the server-wide Chat max_tokens cap on Limits & Policies, which is where every provider started before this field existed.
Fill it in when the model’s own ceiling is not the server’s. Models differ by more than an order of magnitude, and one number cannot fit them all: set above what the upstream accepts, the request comes back as an error; set far below, answers stop mid-sentence. The value here wins in both directions — a provider may sit above the server-wide number as well as below it, so raising one model’s ceiling does not mean raising everyone’s.
A client may still ask for less, and gets it. It can never ask for more than the cap in force.
The field is not offered on a
Linkuprow, which answers no chats. -
Optionally price the credit rates: four fields, each in credits per million tokens, for input, output, cached input and connector. All three model rates default to
1000— the anchor, where one credit is 1,000 tokens — and connector defaults to0, which keeps provider-side web search out of the budget entirely.Enter them the way the provider publishes its price list. A model that costs four times the reference for input and eight times for output is
4000and8000; a provider that bills cache reads at a tenth of input is400against a4000input rate. Leaving all four alone charges every token at the anchor with no cache discount, which is what the token-denominated limits charged before credits existed.The card in force when a request is answered is stamped onto that usage row, so re-pricing changes this provider’s future traffic and never rewrites a window a member has already lived through. It changes only what a request costs against the member’s group credit ceiling: Token Usage keeps reporting the raw tokens the provider actually billed.
The fields are not offered on a
Linkuprow — a search provider is billed by its own connector line, not by a model rate card. -
Type the API key if the endpoint needs one, and press Save. A LAN Ollama needs none; leave it blank. Paste carefully — a key that arrives with a line break in it is refused rather than stored broken.
What the form refuses
Section titled “What the form refuses”A save that breaks one of these is refused with the reason shown in the dialog, and nothing is written — the key you typed included, so a corrected retry still carries it.
| Field | Limit |
|---|---|
| Id | 1–64 characters, unique across both sources ignoring case |
| Name | 1–128 characters |
| Model | 1–128 characters |
| Endpoint | Up to 2048 characters, absolute http(s), no embedded credentials |
| API key | Up to 2048 bytes in UTF-8 — bytes, not characters, so a key holding accented or non-Latin text reaches the limit sooner — and no control characters, because the key ends up in an HTTP header where a line break would split the request |
| Web search | Refused on a type whose handler ignores it: ‘AzureOpenAI’ has no native web search; leave the switch off and pick a web search provider instead. |
| Cache the prompt prefix | Refused on any type but Anthropic: ‘OpenAI’ cannot mark a prompt cacheable; leave the switch off. |
| Search depth | On a Linkup row the model field holds the depth, and anything else is refused with A search depth must be fast, standard or deep. A Linkup row carrying any chat-only field is refused with A search provider carries only a name, an endpoint, an API key and a search depth. |
| Web search provider | An id of at most 64 characters from the same charset as Id, and never the row’s own id — A provider cannot delegate web search to itself. |
| Chat max_tokens cap | Empty, or a whole number from 1 to 128000. Refused, never clamped, for the same reason as a rate: the number you typed is the one your model takes, and quietly substituting another is how a truncated answer gets blamed on the model. A Linkup row must leave it empty |
| Credit rates | Each rate is a finite number from 0 to 10000000 per million tokens, and the cached input rate may not exceed the input rate — a cache hit cannot cost more than the plain input token it already is. Anything else is refused, never clamped, because a clamped rate silently charges a price nobody set. A Linkup row must leave all four at their defaults, and is refused by the search-provider rule above if it does not |
Edit reopens the same form with the id locked. Saving rewrites everything except the key.
If the endpoint is plain http:// and a key is involved, the form warns that the key will travel unencrypted. It does not stop you — an Ollama on your own network is the case that makes http legitimate — but on anything reachable beyond that, treat the warning as an error.
Web search providers
Section titled “Web search providers”Four of the seven chat types — AzureOpenAI, Ollama, OpenAICompatible and VLlm — have no web search of their own. Those are the on-premise and bring-your-own-endpoint types, so the model an operator picks when the data boundary matters is also the model that cannot look anything up. A web search provider closes that gap: a separate catalog entry that a chat entry may delegate searching to. The model gets a search_web tool, the server runs the search, and the results come back as short titled excerpts the model quotes from.
Nothing is preconfigured. Until you add a row and enter your own key, no model on this instance can search, and no search leaves the server.
Adding a Linkup row
Section titled “Adding a Linkup row”-
Press Add provider and choose Provider type
Linkup. Choosing it fills in the Endpoint for you —https://api.linkup.so/v1/search. Unlike a chat row that is the full search path, not a base URL. A value you typed yourself is never overwritten. -
Give it an Id and a Name as for any other entry. The name is what the picker shows.
-
Pick a Search depth. This replaces the model field, because depth is the thing a search service offers variants of:
Depth What you get fastThe query is sent as typed, no interpretation. Cheapest, and the wrong contract for a query a model wrote in prose standardThe default, and the one to keep deep5–30 seconds and about ten times the price. A deliberate choice, not a default -
Enter your API key and press Save. The key is stored exactly like any other provider key — encrypted, never rendered back.
A Linkup row carries nothing else. It has no reasoning effort, no vision switch and no delegate of its own, and the form refuses a save that tries to set one.
Pointing a chat entry at it
Section titled “Pointing a chat entry at it”On any chat entry whose Web search switch is off, a Web search provider picker appears beneath the switches, listing the resolvable search rows. Leave it on (none — this model cannot search the web) and the model answers as it does today, from what it already knows.
On the four types without native search the switch is greyed out and the picker is the only option. On OpenAI, Mistral, OpenRouter and Anthropic the switch works and is the better choice — turning it on hides the picker, because a model that searches natively must not be handed a second, worse way to do it.
What a search costs, and what bounds it
Section titled “What a search costs, and what bounds it”Each search is one paid call against your own account, so the spend is bounded in three places at once:
| Setting | Default | Bounds |
|---|---|---|
AiProxy:WebSearchMaxCallsPerRequest |
4 |
Searches one chat request may make. Past it the model is told the budget is exhausted and answers with what it has |
AiProxy:WebSearchMaxResults |
5 |
Results per search. A model may ask for fewer; a larger figure is clamped to this one |
AiProxy:WebSearchResultContentMaxChars |
1200 |
Characters kept per result, so four searches cannot crowd out the conversation they were meant to inform |
Set them in appsettings.json or the environment — they hot-reload, and they are deliberately not on Limits & Policies, like the other AI payload caps that belong to a deploy rather than to a shift.
An unattended operator run multiplies the first figure by Operators:MaxSteps (default 8), so the worst case for one assignment is 32 searches. Per-user token budgets do not bound this: a search is billed in searches, not tokens.
Every call writes one row on Token usage with the template websearch:search_web, charged against the search provider’s id with zero tokens, and counts toward that request’s Tools figure.
Test connection
Section titled “Test connection”Test sends a real request to that entry’s endpoint with its stored key: a one-word completion, then the same call with a tool definition attached, then a streaming call, then one constrained to a two-value JSON schema. The result bar reports reachability and what the endpoint supports, e.g. Connection successful. … Supports tool calling and streaming. A JSON response schema is honoured — tick ‘Upstream accepts a response schema’.
The schema phase runs whatever that switch currently says, because its job is to tell you whether the switch is right. It also distinguishes accepting the field from obeying it: an endpoint that ignores an unknown field answers 200 with prose, so only a reply that actually respects the enum counts as honoured.
It works the same on a configured entry and on a database row. Two limits are deliberate:
- Only one test runs at a time, so every Test button greys out while one is in flight.
- An entry that is not Resolvable cannot be tested — there is nothing to build a request from.
On a web search provider the button reads Test (1 credit) and does something different: it runs a real search rather than a completion, because a search endpoint has no completion to run. It uses the cheapest call the service offers — depth fast, one result — and the result bar says so: Search successful. Provider ‘Linkup’ answered with 1 result(s). This test spent one search credit. A failure names the reason: Search failed: the API key was rejected. or Search failed: the rate limit was hit, or the account is out of credits.
Disabling and deleting
Section titled “Disabling and deleting”Disable parks a row without losing it. The row stops resolving, drops out of every picker, and keeps its endpoint, model and key for whenever you press Enable. This is the reversible option, and the right one for “not now”.
Delete removes the row. The confirmation first lists everything that routes to that id today:
- each group’s default provider, per-mode override, and per-persona-type override,
- each group’s guardrail classifier, hit and miss provider,
- each entry that delegates image reading or web search to it,
Embeddings:ProviderIdandChunking:Context:ProviderIdfrom configuration.
How soon a change takes effect
Section titled “How soon a change takes effect”Saving is enough. There is no restart, and no Apply step.
The catalog is cached, and every save on this page drops the cache, so the change is live on the next request this instance serves. If you run more than one instance behind a load balancer, the others notice within about 30 seconds — they re-read the catalog on that interval. A configured entry is the exception: it is read at startup, so changing one still means a restart.
If the database is briefly unreachable, the catalog falls back to the configured entries alone rather than failing requests. Database rows reappear when it recovers.
Server-wide defaults
Section titled “Server-wide defaults”Below the catalog, a read-only strip shows the two layers a request uses before any override applies: Default (Ai:Default) and one card per mode configured under Ai:Modes — set in configuration, changed only by editing it and restarting. Each card carries a Test button, working the same way as a catalog entry’s: a one-word completion, the same call with a tool definition, then a streaming call, reporting back reachability and what the endpoint supports.
A mode with no Ai:Modes entry, or one with no API key, gets no card here — instead it shows as a small Mode → Default tag, so an admin can tell “not configured” apart from “configured but keyless” at a glance.
Provider assignment
Section titled “Provider assignment”Below that, Provider assignment lets an admin point Optimize, Assistant, and Research at a catalog entry directly — the one part of this cascade that changes without a restart, because it is a database row rather than configuration.
-
For the mode you want to change, pick a provider from its dropdown — only resolvable entries are offered, same reason as everywhere else on this page.
-
Press Save on that mode’s card. Each mode saves independently.
-
To clear an assignment, pick (none — falls back to Ai:Modes, then Default) and save.
If the assigned provider is later disabled, deleted, or otherwise stops resolving, the request falls through to the next layer down (Ai:Modes, then Ai:Default) and a warning is logged naming the id — the same non-blocking behaviour as every other layer in the cascade.
Audit trail
Section titled “Audit trail”Every action here writes to the audit log:
| Event | Written when |
|---|---|
AiProvider.Created |
A row is added |
AiProvider.Updated |
A row’s fields are saved |
AiProvider.KeyChanged |
An existing row’s key is replaced |
AiProvider.Toggled |
A row is disabled or enabled |
AiProvider.Deleted |
A row is removed |
AiProvider.ModeOverrideChanged |
A mode assignment is set or cleared |
For the catalog events, the entry records the id, the name, and at most an eight-character fingerprint prefix — a key is never written to the audit log or to a log line, in any form. AiProvider.ModeOverrideChanged records the mode and the assigned provider id instead, or no id on a clear.
Troubleshooting
Section titled “Troubleshooting”A row says Shadowed. A configured entry now claims the same id, and configuration always wins. Rename the row, or remove the configuration entry and restart.
A row says Invalid. Hover the badge for the reason. Unknown provider type and Unknown reasoning effort mean the stored value is not one this build knows — usually a downgrade. The endpoint is not an absolute http(s) URL, or it embeds credentials means the stored endpoint is unusable. The stored API key could not be decrypted means the Data Protection keyring that encrypted it is gone; edit the row and type the key again. An invalid row is skipped, never fatal — the server starts and answers normally, and anything pointing at that id falls through the cascade.
A provider I added does not appear in a group’s picker. The picker lists resolvable entries only. Check the Status column on this page: a disabled, invalid or shadowed row is excluded by design.
Test says Connection failed. The message carries the upstream’s status code and body. A 401 or 403 is the key; a timeout or a DNS failure is the endpoint. Compare the Endpoint column against what the provider documents — the column shows only the host, so a wrong path will not be visible there.
My change is not visible on another instance. Give it 30 seconds. The instance you saved on is already current; the others re-read the catalog on that interval.