LLM authentication, provider routing, and tier management
Failure modes
| Symptom | Cause | Fix | Severity |
|---|---|---|---|
ValueError from get_model |
Provider other than "anthropic" passed |
Pass "anthropic"; this provider set is single today |
high |
get_model(...) returns None |
No model registered for that provider/tier | Check the tier value (ModelTier.CHEAP.value, not the enum) and guard the result |
high |
ValueError from get_tasks_for_tier |
Unknown tier argument | Pass a real ModelTier, not a raw string |
medium |
A task routes to CAPABLE unexpectedly |
The task string is unknown, so it defaulted | Call is_known_task first; add the task to the tier map if it should be classified |
medium |
AUTO returns API on a PRO account regardless of module size |
get_recommended_mode resolves by tier first; PRO/API_ONLY always return API |
Expected — only MAX/ENTERPRISE tiers use size-based selection |
medium |
Auth always picks the mode you set, ignoring AUTO logic |
default_mode is SUBSCRIPTION or API, not AUTO |
Set default_mode = AuthMode.AUTO so it defers to get_recommended_mode |
medium |
await error calling an executor's run |
LLMExecutor.run is async and was called without await |
await executor.run(...) or drive it with asyncio.run |
medium |
AdaptiveModelRouter returns the same model regardless of cost |
Fewer than MIN_SAMPLE_SIZE calls recorded |
Accumulate telemetry; until then it falls back to static defaults | low |
Risk areas
- Tier value vs enum. Registry lookups take the tier value
(
tier.value), while routing functions accept either aModelTieror its string. Mixing the two silently returnsNonefromget_model. Passtier.valuetoget_model. - Unknown tasks default silently.
get_tier_for_tasknever raises; an unrecognized task quietly becomesCAPABLE. Validate withis_known_taskwhen the task source is untrusted. - Cost fields are per-million on
ModelInfo, per-1k on the properties.input_cost_per_millionis the stored field;cost_per_1k_inputis the derived property. Don't mix the units when computing an estimate. get_provider_configis a lazy global. It loads once and caches; callreset_provider_config()after writing a new config file if you need the change to take effect in a long-lived process.
Diagnosis order
- Print the resolved tier:
get_tier_for_task(task)— confirms the classification before any model lookup. - Print the model:
get_model("anthropic", tier.value)—Nonemeans a registry gap or a tier passed as an enum instead of.value. - Inspect auth:
attune auth status --json— confirmssetup_completedand the active mode. - Inspect provider:
attune provider show— confirms the provider and mode the lookups will use. - For routing surprises, read
AdaptiveModelRouter.get_routing_stats( workflow, stage)to see sample size and per-model performance.