VernLLMVernLLM
Guides

Per-call Overrides

Using different models, temperatures, or token limits for individual calls

A VernLLM instance is configured once with a default model (and other options like defaultMaxTokens), but almost every generation-related field on CallParams can be overridden for an individual call() without creating a second instance.

per-call-model-override.ts
const llm = new VernLLM({
  client: openai,
  model: 'gpt-4o-mini', // cheap default for most calls
});

// Uses gpt-4o-mini, the instance default
const summary = await llm.call({
  userContent: 'Summarize this in one sentence.',
});

// Overrides the model for just this call
const analysis = await llm.call({
  model: 'gpt-4o',
  temperature: 0.2,
  userContent: 'Do a careful multi-step analysis of this contract.',
});

What can be overridden per call

FieldFalls back to
modelThe instance's model option
maxTokensThe instance's defaultMaxTokens (1000)
temperature0.2, used only when both the per-call value and defaultTemperature are undefined. Either one set to null omits temperature instead.
reasoningEffortThe per-call value, then the fallback target's own defaultReasoningEffort (if this call lands on a fallback target), then the instance's defaultReasoningEffort. Omitted from the request only when none of those is set, in which case the provider's own default applies.
budgetTokensThe per-call value, then the fallback target's own defaultBudgetTokens (if this call lands on a fallback target), then the instance's defaultBudgetTokens. Omitted from the request only when none of those is set.
chunkIdleTimeoutMsThe instance's chunkIdleTimeoutMs (30000). Only applies when stream: true. See Per-chunk idle timeout.

temperature isn't left unset when you don't pass it — VernLLM defaults it to 0.2 itself. Pass temperature: null on a call, or defaultTemperature: null on the instance, to omit temperature from the request entirely and let the provider apply its own default instead.

Claude Opus 4.7 and later, Claude Opus 5, and every Claude 5 tier model (Sonnet 5, Fable 5, Mythos 5) reject temperature on a non-default value with a 400 error. Use temperature: null for those models, or defaultTemperature: null on the instance if you route calls to one, and describe the desired behavior in the prompt instead. Separately, fromAnthropic/fromBedrock omit temperature automatically whenever budgetTokens/reasoningEffort is set, on any model, since Anthropic rejects temperature alongside any thinking mode, manual or adaptive.

reasoningEffort and budgetTokens each resolve through the same three layers every other per-call option does: the per-call value, then the fallback target's own default, then the instance-level default, first one set wins. See Call Params for the full conversion table between the two, and per-provider behavior.

jsonMode, jsonSchema, and schema aren't instance-level options at all — they only ever exist per call, so there's nothing to "override" at the instance level, you just set them on the calls that need them.

Getting Started has a quick-start version of this same pattern, including switching to a reasoning model for a single call:

reasoning-effort-override.ts
const llm = new VernLLM({ client: openai, model: 'gpt-4o-mini' });

await llm.call({
  systemPrompt: '...',
  userContent: '...',
  model: 'o3',
  reasoningEffort: 'high', // passed through as `reasoning_effort` for supported models
});

This page goes into more depth on when and how to reach for overrides.

Retry/timeout/circuit-breaker behavior (maxRetries, timeoutMs, baseDelayMs, nonRetryableStatus, circuitBreaker) is instance-level only and cannot be changed per call. If different calls need different resilience settings (a different threshold or cooldownMs per model), use separate VernLLM instances that share the same underlying client. chunkIdleTimeoutMs is the one exception, it's overridable per call since stream: true calls often need a different idle allowance than the instance default, see Per-chunk idle timeout. If instead you just want one model's failures not to open the circuit for another model on the same instance, without different threshold/cooldown values, circuitBreaker: { isolateByModel: true } does that without a second instance, see Per-model isolation.

Common pattern: cheap default, expensive escalation

A typical setup uses a fast/cheap model by default and escalates to a stronger model only for calls that need it, rather than paying for the expensive model on every request:

cheap-default-escalation.ts
async function extractCandidate(resumeText: string) {
  return llm.call({
    model: 'gpt-4o-mini',
    userContent: resumeText,
    schema: CandidateSchema,
  });
}

async function reviewFlaggedResume(resumeText: string) {
  // Harder task, worth the stronger model
  return llm.call({
    model: 'gpt-4o',
    temperature: 0.1,
    userContent: resumeText,
    schema: CandidateSchema,
  });
}

Per-user or per-tenant model selection

Because model is just a string on CallParams, it can come from anywhere at call time — a config lookup, a feature flag, a per-tenant setting:

per-tenant-model-selection.ts
async function callForTenant(tenantId: string, userContent: string) {
  const model = await getTenantModelPreference(tenantId); // e.g. 'gpt-4o' or 'gpt-4o-mini'

  return llm.call({ model, userContent });
}

Interaction with usage tracking

onUsage reports the model that actually served the request, including a per-call override, not the instance's default. This matters if you bill different models at different rates. The same applies to onUsageFailure, since both share the same TokenUsage shape:

usage-tracking-model-override.ts
const llm = new VernLLM({
  client: openai,
  model: 'gpt-4o-mini',
  onUsage: ({ model, promptTokens, completionTokens }) => {
    // `model` reflects the override, e.g. 'gpt-4o', not 'gpt-4o-mini'
    billing.record({ model, promptTokens, completionTokens });
  },
});

See Usage Tracking for the full onUsage shape.

Interaction with caching

If you cache calls that use different model or temperature overrides under the same cacheKey, you'll get a cached result from whichever call happened to populate the cache first, regardless of which override the current call is using. Fold all inputs that affect the output into the key:

const cacheKey = `analysis:${docId}:${documentHash}:${model}:${temperature}:schema-v2`;

Include a stable document revision/content hash and any generation settings that affect the output, such as model, temperature, and schema version.

See Caching for more on choosing cache keys.

On this page