TramAI - governed AI workflows for Java and Kotlin

Provider Reference

A provider is a ModelProvider. TramAI never guesses which one to use: routing is always explicit, either through @Operation(provider = ...), through a model-to-provider mapping, or through a configured default provider.

All provider adapters in 0.6.0 are at preview maturity — see API Stability. The behavior below is the shipped behavior, not a stability promise.

Published Adapters

ModuleClassProvider idTransportToolsVisionStructuredStreamingSpring properties
tramai-openaiOpenAiProvideropenaiHTTP✓✓✓✓tramai.providers.openai.*
tramai-openaiOpenAiCompatibleProviderproviderName (default openai-compatible)HTTP✓✓✓✓tramai.providers.openai-compatible.*
tramai-anthropicAnthropicProvideranthropicHTTP✓✓✓✓tramai.providers.anthropic.*
tramai-ollamaOllamaProviderollamaHTTP✗✓✗✓tramai.providers.ollama.*
tramai-geminiGeminiProvidergeminiHTTP✓✓✓✓—
tramai-azure-openaiAzureOpenAiProviderazure-openaiHTTP✓✓✓✓—
tramai-bedrockBedrockProviderbedrockAWS SDK✓✓✓✓—
tramai-deepseekDeepSeekProviderdeepseekHTTP✓✓✓✓—

The capability columns are the sets pinned by the shared provider TCK, not marketing claims — a provider cannot widen its advertised capabilities without a reviewer changing the TCK harness. Ollama pins vision and streaming only: tool calling and structured output are not claimed for it.

Minimal Snippets

OpenAI

val tramai = Tramai {
    provider(OpenAiProvider(System.getenv("OPENAI_API_KEY")), name = "openai")
    model("gpt-4o", "openai")
}

Auth modes on OpenAiProvider: a static API key, an explicit bearer token via OpenAiProvider.bearerToken(...), or the experimental Codex auth file via OpenAiProvider.codexAuth(...). Under Spring, credentials can come from a secret reference instead of a literal value.

OpenAI-compatible

Use this for any backend that exposes an OpenAI-shaped /chat/completions endpoint.

val tramai = Tramai {
    provider(
        OpenAiCompatibleProvider.bearerToken(
            bearerToken = System.getenv("COMPATIBLE_API_TOKEN"),
            baseUrl = "https://compatible.example.com/v1",
            providerName = "compatible",
        ),
        name = "compatible",
    )
    model("my-compatible-model", "compatible")
}

Anthropic

val tramai = Tramai {
    provider(AnthropicProvider(System.getenv("ANTHROPIC_API_KEY")), name = "anthropic")
    model("claude-sonnet-4-20250514", "anthropic")
}

Ollama

val tramai = Tramai {
    provider(OllamaProvider("http://localhost:11434"), name = "ollama")
    model("llama3.2", "ollama")
}

Ollama is the local-first option and the usual target for the sovereign runtime, where the trust zone for a local model provider is LOCAL.

Gemini

val tramai = Tramai {
    provider(GeminiProvider(System.getenv("GEMINI_API_KEY")), name = "gemini")
    model("gemini-2.0-flash", "gemini")
}

Azure OpenAI

val tramai = Tramai {
    provider(
        AzureOpenAiProvider(
            resourceName = System.getenv("AZURE_OPENAI_RESOURCE"),
            deploymentId = System.getenv("AZURE_OPENAI_DEPLOYMENT"),
            apiKey = System.getenv("AZURE_OPENAI_API_KEY"),
        ),
        name = "azure-openai",
    )
    model("gpt-4o", "azure-openai")
}

Either an API key or an Entra ID access-token source must be supplied; startup checks that.

Bedrock

val tramai = Tramai {
    provider(BedrockProvider(region = "eu-west-1"), name = "bedrock")
    model("anthropic.claude-3-sonnet-20240229-v1:0", "bedrock")
}

The default model id is anthropic.claude-3-sonnet-20240229-v1:0; pass modelId = ... to override it.

Bedrock uses the AWS SDK credential chain. The SDK transport is driven through its client-factory seam in the TCK: HTTP wire-level behavior such as status mapping and Retry-After does not apply, and AWS SDK types never enter TramAI's stable public API.

DeepSeek

val tramai = Tramai {
    provider(DeepSeekProvider(System.getenv("DEEPSEEK_API_KEY")), name = "deepseek")
    model("deepseek-chat", "deepseek")
}

Provider Registration In Spring

tramai-spring-provider-openai, -anthropic, and -ollama register providers from properties. Gemini, Azure OpenAI, Bedrock, and DeepSeek are standalone adapters: in Spring, register them as ModelProvider beans — each bean provider is registered under its own providerId(), and tramai.models references that name.

Provider beans and property-defined providers are merged into one registry; when a name collides with a unique bean provider, the bean wins.

Registry-Based Resolution

The engine resolves every operation against an immutable ProviderRoutingPlan, exposed through dev.tramai.core.provider.ProviderRegistry.

Resolution order:

  1. explicit @Operation(provider = "...") — bypasses model resolution
  2. the model-to-provider route, plus any configured fallback routes in order
  3. the default provider

Standalone code builds the registry through the standalone builder. If you need the registry directly:

val registry = ProviderRegistry.builder()
    .provider("openai", OpenAiProvider(System.getenv("OPENAI_API_KEY")), default = true)
    .model("gpt-4o", "openai")
    .fallbackModel("gpt-4o", "gpt-4o-mini", "openai")
    .build()

ProviderRegistry.singleProvider(provider) is the one-provider shorthand, and ProviderRegistry.from(plan) wraps an existing plan.

Routing validation is fail-closed at build time:

  • every provider and model name must be non-blank and free of surrounding whitespace
  • a duplicate provider name or a second primary route for the same model fails
  • a model with fallback routes must have a primary route
  • every primary and fallback route must target a registered provider
  • a fallback route may not duplicate the primary route

There is no model-prefix inference. gpt-*, claude-*, and llama-* mean nothing to the router; you map names explicitly. That is what makes aliases and compatible endpoints predictable.

Trust Zones And Classification Routing

dev.tramai.security.ProviderTrustZone classifies where a provider runs:

ZoneMeaning
LOCALSame host or isolated network boundary (Ollama, vLLM, llama.cpp)
EU_CLOUDCloud providers inside the EU trust boundary
GLOBAL_CLOUDCloud providers outside EU jurisdiction

ProviderRoutingConfiguration(providerZones, rules, enabled) maps provider ids to zones and each data classification to which zones may serve it, for primary invocation and for fallback. ClassificationRoutingRule(allowedZones, allowedFallbackZones) must keep the fallback zones a subset of the primary zones — an empty fallback set denies all fallback for that classification, which is the default for restricted data.

The sovereign runtime wires this through its own allowlist block; see Configuration.

Experimental Surface

Two things in the provider layer are explicitly outside any stability promise:

  • the Codex/ChatGPT auth-file path — @ExperimentalCodexAuth, OpenAiProvider.codexAuth(...), OpenAiCompatibleProvider.codexAuth(...), and the matching Spring properties. It reads a token from a local Codex auth.json; use it for local experiments, not production credentials.
  • transport helpers in dev.tramai.core.provider.transport, marked @ExperimentalProviderTransportApi. They are public only because the built-in adapters consume them across module boundaries, and they may move or change in any release.

What Is Not Implemented Across The Provider Layer

  • provider-native structured output modes — structured output goes through TramAI's schema-in-prompt pipeline for every provider
  • externalized provider-level retry-policy configuration
  • externalized provider-level timeout-policy configuration
  • per-provider timeouts — the per-attempt timeout comes from @Operation(timeoutMillis = ...)