Model Provider Integration#
Agenta routes LLM calls by matching a model string to stored vault credentials and building a LiteLLM-compatible kwargs dict at runtime. Two credential kinds exist: standard (a plain API key for hosted providers like OpenAI or Anthropic) and custom (structured settings for providers that need more than a key — AWS Bedrock, Azure, Vertex AI, or any OpenAI-compatible endpoint). The full resolution pipeline runs inside the SDK on every inference call, after the backend injects the project's vault secrets into the request context.
Data Model#
Provider credentials are defined in api/oss/src/core/secrets/dtos.py and validated by enums in api/oss/src/core/secrets/enums.py.
SecretKind : provider_key or custom_provider.
Standard secrets (SecretKind.provider_key) use StandardProviderDTO and require only a key string. Recognized providers are enumerated in StandardProviderKind: OpenAI, Anthropic, Gemini, Groq, Cohere, Mistral AI, Together AI, OpenRouter, and others .
Custom secrets (SecretKind.custom_provider) use CustomProviderDTO, which adds url, version, optional key, and a freeform extras dict, plus a list of CustomModelSettingsDTO entries. CustomProviderKind extends the standard set with custom, azure, bedrock, sagemaker, and vertex .
provider_slug is automatically set from the connection's header name during creation . On read, SecretResponseDTO.build_up_model_keys constructs composite model key strings of the form {provider_slug}/{kind}/{model_slug} — e.g., mybedrock/bedrock/anthropic.claude-3-5-sonnet. These composite keys are the model identifiers passed at inference time.
SDK Credential Resolution#
SecretsManager.get_provider_settings(model) is the SDK entry point. It runs four steps:
- Pull secrets from route context — reads the secrets list injected by the backend into
routing_context. - Parse into standard vs. custom lists —
_parse_secretssplits bykind, and for custom secrets mergesapi_baseandapi_versionfrom the provider'surl/versionfields intoextras. - Resolve provider name — looks up the model in
model_to_provider_mapping, a dict built fromsupported_llm_modelsinsdk/agenta/sdk/assets.py. If absent, falls back to_custom_providers_getto find a matching custom provider by scanningmodelslists . - Build kwargs — for standard secrets, copies
api_key; for custom secrets, spreadsextras(AWS creds,api_base, etc.) into the kwargs dict .
Custom model string conversion. Before the kwargs are built, _get_compatible_model strips the provider_slug/ prefix so LiteLLM receives a valid model string. OpenAI-compatible endpoints (kind custom) are rewritten from {slug}/custom/{model} to openai/{model}, since LiteLLM routes them via the OpenAI adapter.
The standard provider list in assets.py covers Anthropic, Cohere, DeepInfra, Gemini, Groq, Mistral AI, OpenAI, OpenRouter, Perplexity AI, Together AI, and Vertex AI .
Frontend: Connect-Model Gate & Provider Drawer#
PR #5096 redesigned the model-connection UI. Key behaviors:
- Connect-a-model gate now fires only when all three conditions are true: vault credential count is zero, the agent is not
self_managed, and a browser-locallocalStorageflag (agenta:provider-key-setup-done, backed by JotaiatomWithStorageinagenta-entities/src/secret/state/atoms.ts) is unset . Previously it fired on every load. - Drawer layout is Harness → Model → Provider credentials. Selecting a model auto-highlights its provider in the credentials rail. The rail is model-scoped: it shows only the selected model's provider, any custom providers whose
modelslist includes that model, and relevant "Add …" rows (Azure, Bedrock, Vertex AI, custom) . - Connection slug threading. Model selection now passes the connection slug explicitly through the UI stack instead of guessing it from the model ID string, eliminating a class of stale-slug runtime failures .
- Provider family normalization (e.g., converting
together_aito a canonical form) was extracted into a shared@agenta/sharedhelper to remove per-call-site duplication . CustomProviderFormwas extracted fromConfigureProviderDrawerContentinto@agenta/entity-ui/secretProviderso the drawer and the inline pane share one implementation .
Known Issues#
Custom provider shadows standard key (open, issue #5117)#
When a custom-provider connection includes a model name that a standard provider key also serves (e.g., gpt-4o-mini), the custom connection resolves first because the candidate pool ranks model-name matches above provider-family matches for slug-less default connections. The run uses the custom provider's credentials (which may be wrong) instead of the standard key .
Ambiguous connection silently drops credentials (open, issue #5117)#
When connection resolution encounters two equally valid connections and raises an AmbiguousConnectionError (a subclass of ConnectionResolutionError), the service-layer catch block degrades to credential_mode="runtime_provided" with an empty env — the same path taken for a genuinely missing connection. The run proceeds with no credentials, and the user sees a generic "add your key" error with no indication that the real problem is a conflict between two existing connections .
Bedrock concurrent credential leakage (partially fixed, issue #4244 / PR #4797)#
Concurrent Bedrock calls in the same worker could cross-contaminate AWS credentials because per-request keys were installed into os.environ around an await point. The fix removes the os.environ mutation entirely and passes AWS credentials as request-scoped kwargs to LiteLLM, eliminating the leak for Bedrock, Bedrock Converse, and Sagemaker paths .
Old-format prompt model path (issue #4933)#
Old prompts stored the model at prompt.model instead of prompt.llm_config.model. The evaluator LLM loop in handlers.py did not call get_provider_settings_from_workflow(), so custom provider models fell through with no api_base or api_key. Creating a new prompt with the correct llm_config.model path resolves the issue; the legacy path requires a migration/normalization step in the frontend .