AI Provider Configuration
AVA's AI layer is provider-agnostic by design. Every AI call in the
product runs through the Vercel AI SDK behind a small factory, so the models —
and the vendor behind them — are a configuration choice, not a code fork. You
can run AVA on Anthropic (the default), OpenAI, Google Gemini, or OpenRouter,
switch between them with a one-line env change and a restart, and even mix two
providers in the same deployment.
This matters for an MSP in three practical ways:
- No vendor lock-in. If your pricing, compliance, or data-residency
requirements change, you change an env string — not the product. - Cost control. The heavy-traffic AI features run on a separate "fast"
model slot from the "quality" slot, so you can pair a cheap fast model with
a stronger quality model and tune spend independently. - One key, not four. You only need an API key for the provider(s) you
actually configure — the container validates exactly that at boot.
The two model roles
AVA never names a model in code. It asks for one of two logical roles, each
resolved from env at call time:
| Role | Env var | Default | What runs on it |
|---|---|---|---|
| Fast | LLM_FAST | anthropic/claude-haiku-4-5-20251001 | AI triage of every new chat (priority + issue classification + summary), similar-resolution suggestions, trend summaries, and wrap-up field summarization. |
| Quality | LLM_QUALITY | anthropic/claude-sonnet-4-6 | Long-form transcript summarization — the higher-stakes prose path. |
The fast role is where nearly all of AVA's day-to-day AI traffic happens —
it fires on every incoming chat (triage) and on several engineer-dashboard
features — so the fast model choice dominates your AI cost. The quality
role backs the transcript summarizer; note that in the shipped close flow the
ticket notes are engineer-authored and the full chat transcript is posted to
AutoTask verbatim, so quality-tier traffic is minimal in practice.
Call sites, precisely
| Feature | Role | Shape |
|---|---|---|
| New-chat triage (priority P1–P4, AutoTask issue/sub-issue mapping from your live picklists, topic summary) | fast | Structured output (generateObject + schema validation — no brittle JSON parsing of free text). |
| Similar-resolution suggestions (surfaces how past tickets like this one were resolved) | fast | Structured output. |
| Trend summaries (analytics dashboard) | fast | Text generation. |
| Wrap-up field summarization (condensing a field in the engineer's close form) | fast | Text generation. |
| Transcript summarization | quality | Text generation, transcript capped at ~24k chars (most recent messages kept). |
The provider/model string format
provider/model string formatBoth LLM_FAST and LLM_QUALITY take a single string of the form:
<provider>/<model-id>
The string is split on the first slash only: everything before it selects
the provider adapter, everything after it is passed to that provider verbatim
as the model ID. That means model IDs that themselves contain slashes work
naturally — which is exactly what OpenRouter needs:
# Anthropic native (default)
LLM_FAST=anthropic/claude-haiku-4-5-20251001
# OpenAI
LLM_FAST=openai/gpt-4o-mini
# Google Gemini
LLM_FAST=google/gemini-2.0-flash
# OpenRouter — note the model ID itself contains a slash; that's fine
LLM_FAST=openrouter/anthropic/claude-haiku-4-5A value with no slash, or an empty provider/model half, is rejected as invalid
at call time. Unset or blank values fall back to the defaults above.
Provider matrix
| Provider prefix | Backend | API key env var | Notes |
|---|---|---|---|
anthropic | Anthropic API (native adapter) | ANTHROPIC_API_KEY | The default for both roles. |
openai | OpenAI API | OPENAI_API_KEY | |
google | Google Generative AI (Gemini) | GOOGLE_GENERATIVE_AI_API_KEY | |
openrouter | OpenRouter (chat completions) | OPENROUTER_API_KEY | One key, hundreds of models across vendors — useful for evaluation or for models AVA has no native adapter for. |
All AI calls are server-side only — no AI key is ever exposed to the
browser or baked into the client bundle.
Conditional key requirements — validated at boot
You do not set all four keys. The container's startup validation reads
your LLM_FAST / LLM_QUALITY values (applying the same defaults the app
uses), derives which provider(s) they reference, and requires exactly those
providers' keys — nothing more. A missing required key aborts startup with an
error that names both the missing key and the tier that demanded it, e.g.:
ERROR: Missing OPENAI_API_KEY in /app/.env.local, required by: LLM_FAST=openai/gpt-4o-mini
An unrecognized provider prefix (a typo, or a provider AVA doesn't
support) does not block startup — the entrypoint warns and skips the key
check, and the app raises a clear error at the first AI call instead. Watch
the boot log for that warning after changing these vars.
Mixing providers
The two roles are fully independent — a common pattern is a cheap
high-throughput model for LLM_FAST and a stronger model for LLM_QUALITY:
LLM_FAST=google/gemini-2.0-flash
LLM_QUALITY=anthropic/claude-sonnet-4-6Set both providers' keys in that case (GOOGLE_GENERATIVE_AI_API_KEY and
ANTHROPIC_API_KEY here); the boot check enforces exactly that pair.
Timeouts
| Variable | Type | Default | Effect |
|---|---|---|---|
ANTHROPIC_TIMEOUT_MS | int (ms) | 15000 | Per-request timeout applied to every AI call. |
Naming caveat: despite the ANTHROPIC_ prefix, this timeout applies to
all providers — it predates the multi-provider layer and the name was kept
for backward compatibility. There is no per-provider timeout var; this one
value governs OpenAI, Gemini, and OpenRouter calls too.
Failure behavior — AI never blocks the chat
Every AI call site has a non-AI fallback, so a provider outage, a timed-out
request, or malformed model output degrades features rather than breaking the
product:
- Triage failure → the chat still starts and the AutoTask ticket is still
created, with a safe default classification (normal priority, the raw client
description as the summary). - Similar-resolutions / trends / field-summarization failure → the
dashboard feature returns a structured error or empty state; chat flow is
untouched. - Transcript summarization failure → the raw transcript is returned
instead of a summary.
Triage additionally uses schema-validated structured output, so a model that
returns malformed JSON is caught and routed down the same fallback path — no
partial or corrupted classifications reach AutoTask.
Tuning triage for your clientele
The triage prompt includes a configurable one-line description of who your
clients are (COMPANY_PROFILE, default: "a managed service provider (MSP)
supporting business clients"). Setting it to something specific — e.g.
an MSP serving medical and dental practices — measurably sharpens issue
classification for your domain. See
white-label.md for this and the other identity knobs.
See also
configuration.md— the complete env-var reference.white-label.md— branding, includingCOMPANY_PROFILE.quickstart.md— bring-up order, including where the AI
key fits.
Updated about 1 hour ago