Module Deep Dive

Models & Providers

Where vendor credentials live, how a saved model becomes a live client, and why the same parameters behave differently on each vendor.

Overview

Read this first — the split between the two records is the thing everything else follows from.

Every model call in Derbee Studio resolves through two records. A Provider holds the vendor connection: the credential, the base URL, and whether it is reachable. A Model holds one specific configuration of one model at that vendor: which model name, what sampling parameters, what capabilities it has, and what it costs.

Keeping them apart is what makes a key rotation a one-record edit rather than a sweep across every model you have registered. It is also what lets the same vendor account back a cheap model for classification and an expensive one for final answers, with no duplicated credentials between them.

Nothing else in the platform talks to a vendor directly. Agents, teams, workflow steps, the classifier, guardrail checks and embedding jobs all name a model id and let the resolution layer turn it into a working client — which is why swapping a model is a configuration change and never a code change.

Two records, one client

Configuration is stored in two places and merged at call time.

Configuration Resolution Runtime Provider credential · base URL Model parameters · pricing Resolved config provider wins when linked Live client
Credentials live once on the provider; everything model-specific lives on the model

Resolution precedence

A model can carry its own credential, but a linked provider overrides it.

When a model is linked to a provider, the provider record wins. Its vendor type, credential and base URL replace whatever the model carries. Only an unlinked model falls back to its own stored key. That is what makes rotating a credential on the provider take effect everywhere immediately — and it means a stale key left on a linked model is simply never consulted.

Modules

Providers

A Provider is one vendor connection. It is the only place a credential is stored, and the only record that knows where a self-hosted endpoint lives.

providerType
Which vendor this is. Matched case-insensitively against a validation catalogue that also carries aliases, so claude resolves to Anthropic and google to Gemini.
connectionMethod
api_key for hosted vendors, endpoint_url for self-hosted servers. This is what decides which connection test runs.
apiKey
Encrypted at rest. Encrypted on save and on update, decrypted when the document is loaded — so the plaintext exists only in memory during a call.
baseUrl
Endpoint for self-hosted servers. Flows through to every model linked to this provider.
lastVerifiedAt
When the connection test last succeeded — so a provider that has quietly stopped working is visible rather than only discovered mid-run.
type
SYSTEM_DEFAULT or WORKFLOW_SELECTABLE.
tags status
Free-form labels for organising, plus a lifecycle status and an isActive soft-delete flag.
Connection testing

Testing takes one of two paths depending on the connection method.

Hosted vendors are verified by calling the vendor's own model-catalogue endpoint with the supplied credential. Each vendor in the catalogue declares its URL and how authentication is carried — a bearer header, a custom header, or a URL template — plus any extra headers the vendor requires, such as an API version. A real catalogue response is proof the key works; anything else is normalised into a specific error with a code and status rather than a raw exception.

Self-hosted endpoints are probed instead. Three candidate paths are tried in order — the Ollama tag listing, the OpenAI-compatible model listing used by tools such as LM Studio and LocalAI, and finally the bare root URL — each with a five-second timeout. The first one that responds successfully proves the server is reachable; if none do, the error names the URL that was tried.

Discovering what a provider offers

Beyond testing, a provider's live model list can be fetched using its stored credential, returning each model's name, type and — where the vendor publishes them — its token limits and parameter ranges. That is how the model form can offer real choices instead of a free-text field.

Models

A Model record is one usable configuration. The same vendor model can be registered several times with different parameters, different pricing, or scoped to a different environment — production, development or staging — and each is independently selectable.

Model families

The modelType field decides what a model is for and which of its fields mean anything. Two families matter most: generate for chat and reasoning, and embeddings for vectors.

Embedding models have their sampling fields forced to null. On both create and update, an embeddings model has temperature, topP, topK, input and output token limits, web search and image analysis cleared automatically — they are meaningless for a vector call, so the record is normalised rather than left holding values that would never be used. A legacy generation type is silently rewritten to generate at the same point.
What a model record holds
modelName
The vendor's own identifier, sent verbatim in the call.
title description
Human-facing name and notes — what people pick from, as distinct from what the vendor is sent.
providerId
Link to a provider. When set, its credentials take precedence over the model's own.
dimension
Embedding output width. Null means the provider's native default.
webSearch
Whether this model can use vendor web search.
imageAnalysis
Whether the model accepts images. This single flag is what the runtime reports as vision support — there is no vendor sniffing, so the record is the source of truth.
apiKey
Optional per-model credential for an unlinked model. Encrypted exactly like a provider's.
sharedWith…
Share a model with named users or whole roles, the same as other objects.
A note on embedding dimensions. Gemini embedding models return their native width — often larger than you want to store. Rather than ignore the configured dimension, vectors are truncated to it and then re-normalised, which is mathematically what the vendor's own output-dimension option produces. Truncation only ever shortens: if the native vector is already at or below the target, it is left alone.

Parameters & Vendor Mapping

You configure one set of parameters. What actually reaches the vendor differs, because the vendors themselves differ — and the mapping layer handles that rather than leaving you to discover it through failed calls.

Unset means unset. A parameter left null is not sent at all, so the vendor applies its own default. Only explicitly configured values are forwarded, which is why a fresh model behaves exactly like the vendor's own defaults until you change something.

The vendor-specific rules
Gemini
Temperature and topP are mutually exclusive on some models, so topP is dropped whenever temperature is set. Supports topK. Output limit maps to the vendor's max-output-tokens field.
Anthropic
Sampling parameters are not forwarded at all. Newer models reject temperature and topP outright, so the model's own defaults are used and only the output limit is sent. A long client timeout is configured for slow completions.
OpenAI
Temperature and topP supported, with topP dropped when temperature is set. No topK.
Ollama
The only vendor that takes a base URL. Temperature, topP and topK all forwarded; the output limit maps to its prediction-count field. The base URL is normalised to the API root, because a saved OpenAI-compatible path would otherwise break its native calls.
Output caps and truncation

There is an optional platform-wide ceiling on output tokens, applied identically to every vendor. It defaults to zero, meaning no code-level cap — the model governs its own limit and whatever it returns is passed through verbatim.

This replaced a real bug. The Anthropic path used to hard-cap output at 8192 tokens in code, which silently truncated large responses — deeply nested JSON in particular. No vendor gets special treatment now; the cap lives in configuration only and applies to all of them or none.

Truncation is detected rather than guessed. Each vendor signals it differently — OpenAI and Ollama report a length stop, Anthropic reports hitting max tokens, Gemini reports its own equivalent — and all of them are normalised to a single truncated flag on the response. Every response also carries the finish reason, the ceiling that was actually requested, and the provider and model it came from, so a truncated answer is diagnosable from the trace instead of appearing as a mysteriously short reply.

Pricing & Cost

Rates live on the model record, expressed per one million tokens in USD. Three figures are held: input price, output price, and a separate rate for cached tokens.

Cost is computed once, at run time, and stored next to the token counts. Editing a rate later changes what future runs cost — it does not silently rewrite history. A run's recorded spend stays frozen at the rates that were in force when it happened, which is the only way a cost report can be trusted after a price change.
How a run's cost is built

Token usage is tracked per phase, not just as a single total — classification, generation, safety checks, routing and embeddings are counted separately. Each phase is then priced individually, so the summary shows not only what a run cost but where the money went.

A pricing-configured flag travels with the summary. That distinguishes a genuine zero from a model that simply has no rates entered — without it, an unpriced model would look free rather than unmeasured. A model counts as priced when any one of its three rates is non-zero.

Bulk rate updates
  • Gated behind its own pricing permission
  • Capped at 500 rows per request
  • Only the four pricing fields are touched
  • Matched by model name, case-insensitively
  • Optional title narrows ambiguous matches
  • Every matching record is updated, not just the first
  • Rows with nothing to change are reported as skipped

System Defaults & Governance

Some parts of the platform need a model without anyone choosing one — the classifier, template matching, semantic search and every embedding job. That is what SYSTEM_DEFAULT is for, and it is enforced rather than merely conventional.

One default per family
Promoting a model demotes the previous default within the same family — embeddings and generation defaults are tracked separately, so setting one never disturbs the other.
Defaults cannot be deleted
Deletion is refused with an explanation: promote a replacement first, then regenerate embeddings for anything that depends on the old one. Agents and vector search would otherwise be left pointing at nothing.
Embedding models are immutable
Once created, a non-default embedding model cannot be edited. Changing its settings would invalidate every vector already produced with it, so the answer is a new model plus a re-embed — stated in the error rather than left to be discovered.
Cached, but not stale
The resolved default embedding model is cached per tenant on a 60-second TTL to keep hot paths off the database, and the cache is invalidated explicitly whenever a model is created, updated or deleted — so an admin change takes effect immediately rather than on the next expiry.
Generation fallback
If no generation default is set, the first available generation model is used rather than failing — availability is preferred over correctness for the utility path, and the absence is logged.
Embedding has no fallback
With no default embedding model, the call raises a clear error naming the fix. Silently picking a different embedding model would produce vectors incomparable to everything already indexed.

How Models Resolve — Flow Examples

Four diagrams: resolution, vendor mapping, cost, and the rules that protect defaults.

Input / output Data handling Decision Rule / gate Result

Example 1 — From a model id to a live client Resolution

What happens between an agent naming a model and a vendor call going out. The linked-provider branch is where credentials are decided.

linked — provider wins unlinked 0 = model decides 2 retries Agent names a model Load model record Linked to a provider? Provider credentials Model's own key Merge parameters Apply output cap Build vendor client Call the vendor Response · usage · finish reason
One id in, one client out — credentials decided by whether a provider is linked

Example 2 — What each vendor actually receives Mapping

The same configured parameters, four different outcomes. Filled rows are forwarded; greyed rows are not sent at all.

GEMINI ANTHROPIC OPENAI OLLAMA temperature topP topK max output base URL topP dropped if temperature is set temperature topP topK max output base URL sampling params never forwarded temperature topP topK max output base URL topP dropped if temperature is set temperature topP topK max output base URL base URL normalised to the API root
Configure once; the mapping layer sends each vendor only what it accepts

Example 3 — How a run's cost is built Cost

Each phase is priced separately, then frozen alongside the token counts so later rate edits cannot rewrite it.

unpriced models flagged, not counted as free Run completes Token usage by phase classifier generation safety Rates per 1M tokens Cost frozen with the run
Per-phase pricing shows where the money went, not just how much

Example 4 — The rules that protect defaults Governance

What the platform enforces when a model is promoted, and the one edit it refuses outright.

same family only promote a replacement first 60-second TTL Set as System Default Others demoted Cannot be deleted Cache invalidated Embedding models immutable after creation create a new one and re-embed
Promotion is automatic; deletion and embedding edits are refused with an explanation

Reference Tables

Vendor parameter support, the model record, and how cost is stored.

Parameter support by vendor

ParameterGeminiAnthropicOpenAIOllama
temperatureYesNot sentYesYes
topPDropped if temperature setNot sentDropped if temperature setYes
topKYesNot sent—Yes
outputTokensmax output tokensmax tokensmax tokensprediction count
baseUrl———Required, normalised

Model record

FieldPurpose
Identity
titleHuman-facing name shown in pickers
modelNameVendor identifier, sent verbatim
modelTypeFamily — generate or embeddings; a legacy generation value is rewritten
providerVendor type: gemini, ollama, anthropic or openai
providerIdLink to a provider record; when set, its credentials win
environmentproduction, development or staging
Behaviour
temperatureSampling randomness; null means the vendor default
topP topKSampling limits, where the vendor supports them
inputTokensRecorded context ceiling
outputTokensOutput ceiling requested from the vendor
dimensionEmbedding width; null means the provider's native size
webSearchWhether vendor web search is available
imageAnalysisVision support — the sole source of truth at runtime
Commercial & governance
inputPricePerMillionInput rate per 1M tokens
outputPricePerMillionOutput rate per 1M tokens
cachedTokensPriceRate for cached tokens
currencyDefaults to USD
typeSYSTEM_DEFAULT or WORKFLOW_SELECTABLE
isActiveSoft-delete flag
sharedWithUsers sharedWithRolesAccess grants

Cost summary

FieldMeaning
totalRun cost, computed at run time and frozen
currencyFrom the model record; USD for now
pricingConfiguredFalse when the model had no rates — distinguishes unmeasured from free
breakdownPer-phase cost: classifier, generation, safety, routing, embeddings