Spring Boot Integration
What it is: tramai-spring-boot-starter is the canonical Spring Boot dependency. It aggregates tramai-spring-core (profile-neutral Spring integration) and tramai-spring-sovereign (sovereign runtime integration) into one artifact. @AiService interfaces become Spring beans with no manual wiring, and tramai.profile — not a different dependency and not an annotation — selects which runtime the application runs on.
When to use it: You have a Spring Boot application and want tramai services injected as standard beans with YAML-driven configuration.
Property-driven provider configuration comes from separate adapter artifacts (tramai-spring-provider-openai, -anthropic, -ollama). The raw tramai-openai / tramai-anthropic / tramai-ollama modules are provider implementations, not Spring property adapters.
Minimum Setup
Step 1: Add the dependency
dependencies {
implementation(platform("dev.tramai:tramai-bom:0.6.0"))
implementation("dev.tramai:tramai-spring-boot-starter") // standard + sovereign
implementation("dev.tramai:tramai-spring-provider-openai") // provider adapter for Spring
}
<dependencyManagement>
<dependencies>
<dependency>
<groupId>dev.tramai</groupId>
<artifactId>tramai-bom</artifactId>
<version>0.6.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>dev.tramai</groupId>
<artifactId>tramai-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>dev.tramai</groupId>
<artifactId>tramai-spring-provider-openai</artifactId>
</dependency>
</dependencies>
Depend on the starter only. tramai-spring-core and tramai-spring-sovereign arrive transitively — do not compose them by hand.
Step 2: Configure in YAML
tramai:
default-provider: openai
models:
gpt-4o: openai
providers:
openai:
api-key: ${OPENAI_API_KEY}
Step 3: Inject and use
@Service
class BillingService(
private val invoiceAnalyzer: InvoiceAnalyzer, // injected Spring bean
) {
suspend fun process(invoiceText: String) =
invoiceAnalyzer.analyze(invoiceText)
}
No annotation is required. Under Spring Boot the starter's auto-configuration discovers tramai automatically: it binds tramai.*, builds the runtime, registers every @AiService interface as a bean, and scans @AiTool methods.
@EnableTramai is not required. In 0.6.0 the annotation exists for annotation-driven, non-Boot Spring contexts, and it selects the Spring programming model — not a runtime profile. tramai.profile remains the only thing that selects a runtime. Leaving @EnableTramai on a Boot application is harmless (it coexists with auto-configuration without duplicate beans); it is simply not part of the setup.
Runtime Profiles
tramai.profile selects the runtime. Application code is identical under both profiles.
tramai.profile | Runtime | Behavior |
|---|---|---|
| missing | standard | configurable general-purpose runtime |
standard | standard | same as missing |
sovereign | sovereign | fail-closed governed execution, model/provider allowlists, tool metadata enforcement |
- The value is case-insensitive. Any other value fails at startup.
- There is no implicit sovereign selection.
- Exactly one runtime authority is active — never both.
Provider Adapters
| Adapter artifact | Bound property namespace |
|---|---|
tramai-spring-provider-openai | tramai.providers.openai.*, tramai.providers.openai-compatible.* |
tramai-spring-provider-anthropic | tramai.providers.anthropic.* |
tramai-spring-provider-ollama | tramai.providers.ollama.* |
Add the adapter matching the provider you configure. Adapters contribute the provider to the runtime under both profiles with identical semantics; you can also register a ModelProvider bean directly, and a bean-backed provider wins over a property-defined one with the same name.
InvoiceAnalyzer Example
import dev.tramai.core.annotations.AiService
import dev.tramai.core.annotations.Operation
// 1. Define your typed result
data class InvoiceVerdict(
val status: String, // "approved" | "needs_review" | "rejected"
val reason: String,
val missingFields: List<String>,
)
// 2. Define your AI service interface
@AiService
interface InvoiceAnalyzer {
@Operation(
prompt = "Analyze the invoice. Return status, reason, and any missing required fields.",
model = "gpt-4o",
)
suspend fun analyze(invoiceText: String): InvoiceVerdict
}
// 3. Inject and use it anywhere
@Service
class BillingService(
private val analyzer: InvoiceAnalyzer, // auto-wired by Spring
) {
suspend fun process(invoiceText: String): InvoiceVerdict =
analyzer.analyze(invoiceText)
}
// 4. Expose it
@RestController
class InvoiceController(
private val billingService: BillingService,
) {
@PostMapping("/invoices/analyze")
suspend fun analyze(@RequestBody text: String): InvoiceVerdict =
billingService.process(text)
}
The model = "gpt-4o" name must appear as a key under tramai.models (or tramai.sovereign.models in the sovereign profile). Tramai handles the LLM call, JSON extraction, parsing, validation, and retries; your code never touches HTTP clients or JSON parsers.
Java Callers
Kotlin suspend methods are the normal contract. For Java, declare an explicit blocking method — the generated proxy wraps non-suspend methods in runBlocking:
@AiService
public interface BlockingInvoiceAnalyzer {
@Operation(
prompt = "Analyze the invoice and return a short status.",
model = "gpt-4o"
)
String analyze(String invoiceText);
}
Keep blocking Java contracts explicit; tramai does not generate *Blocking companion methods.
Multiple Providers and Fallback Routing
tramai:
default-provider: openai
models:
gpt-4o: openai
gpt-4o-mini: openai
llama3.2: ollama
fallbacks:
gpt-4o:
- provider: openai
model: gpt-4o-mini
- provider: ollama
model: llama3.2
providers:
openai:
api-key: ${OPENAI_API_KEY}
ollama:
base-url: http://localhost:11434
Fallback order is explicit and taken from tramai.fallbacks. The engine does not guess. Operations reference models by name in @Operation(model = "gpt-4o-mini").
YAML Reference
Settings bound by tramai-spring-core:
| Property | Description | Default |
|---|---|---|
tramai.default-provider | Provider used when no model mapping matches | none |
tramai.models.<model> | Model-to-provider mapping | none |
tramai.fallbacks.<model>[].provider | Fallback provider for a model | none |
tramai.fallbacks.<model>[].model | Fallback model name | none |
tramai.resilience.circuit-breaker.enabled | Enable circuit breaking | false |
tramai.resilience.circuit-breaker.failure-threshold | Failures before the circuit opens | 3 |
tramai.resilience.circuit-breaker.open-duration-millis | How long the circuit stays open | 30000 |
tramai.resilience.retry.max-retry-after-millis | Cap on provider-requested retry delay | 30000 |
tramai.resilience.retry.jitter-ratio | Retry jitter ratio | 0.2 |
tramai.cost.token-budget.hard-max-tokens-per-attempt | Hard per-attempt token cap | none |
tramai.cost.token-budget.hard-max-tokens-per-operation | Hard per-operation token cap | none |
tramai.cost.token-budget.soft-max-tokens-per-operation | Soft per-operation token cap | none |
tramai.cache.in-memory.enabled | In-memory cache for cacheable operations | false |
tramai.cache.in-memory.max-entries | Cache entry cap | 1000 |
tramai.security.model-registry.enabled | Enforce the approved model registry | false |
tramai.security.classification.enabled | Enable data-classification rules | false |
tramai.security.classification.default-classification | Classification applied when no rule matches | INTERNAL |
tramai.security.classification.max-text-length | Maximum text length examined for classification | 100000 |
tramai.security.classification.rules[] | Rules with id, classification, priority, pattern, metadata-equals | none |
Provider settings come from the adapter modules:
| Property | Module |
|---|---|
tramai.providers.openai.api-key / .api-key-secret-ref / .bearer-token / .base-url / .organization / .project | tramai-spring-provider-openai |
tramai.providers.openai-compatible.provider-name / .api-key / .bearer-token / .base-url | tramai-spring-provider-openai |
tramai.providers.anthropic.api-key / .api-key-secret-ref / .base-url | tramai-spring-provider-anthropic |
tramai.providers.ollama.base-url | tramai-spring-provider-ollama |
Resilience, Cost, Cache, Security
tramai:
resilience:
circuit-breaker:
enabled: true
failure-threshold: 3
open-duration-millis: 30000
retry:
max-retry-after-millis: 20000
jitter-ratio: 0.1
cost:
token-budget:
hard-max-tokens-per-attempt: 4000
hard-max-tokens-per-operation: 12000
soft-max-tokens-per-operation: 8000
cache:
in-memory:
enabled: true
max-entries: 1000
Token budgets are enforced from provider-reported usage. Caching still requires @Operation(cacheable = true) on the operation itself; tool-enabled operations are never cached.
Secret References
Resolve credentials by reference instead of embedding them:
tramai:
providers:
openai:
api-key-secret-ref: env:OPENAI_API_KEY
Built-in schemes:
env:NAMEfile:/absolute/path/to/secret.txtvault:path[#field]withtramai-spring-secrets-vaulton the classpath (configured undertramai.secrets.vault.*)aws-secretsmanager:secret-id[#field]withtramai-spring-secrets-awson the classpath (configured undertramai.secrets.aws-secrets-manager.*)
You can also provide your own SecretValueResolver bean.
Sovereign Profile
tramai.profile: sovereign switches to the sovereign runtime: fail-closed governance, explicit provider/model allowlists, and tool metadata enforcement. The @AiService interfaces, constructor injection, and @AiTool methods do not change.
tramai:
profile: sovereign
sovereign:
allowed-models: [local-invoice-model]
allowed-providers: [deterministic-local-provider]
allowed-tools: [schedule-payment]
allowed-permissions: [payment.schedule]
provider-zones:
deterministic-local-provider: LOCAL
models:
local-invoice-model: deterministic-local-provider
| Property | Description |
|---|---|
tramai.sovereign.allowed-models | Models that may be invoked; each must have a route in models |
tramai.sovereign.allowed-providers | Providers that may be used; each needs a trust zone |
tramai.sovereign.allowed-tools | Tools whose execution is permitted |
tramai.sovereign.allowed-permissions | Tool permissions granted |
tramai.sovereign.provider-zones.<provider> | Trust zone per provider: LOCAL, EU_CLOUD, or GLOBAL_CLOUD |
tramai.sovereign.models.<model> | Model-to-provider route; keys must be a subset of allowed-models |
Configuration is validated at startup: allowed models and providers must be non-empty, every allowed provider needs an explicit zone, every zone must reference an allowed provider, and every allowed model needs a route to an allowed provider. Violations fail startup with a typed message rather than degrading to a permissive runtime.
tramai.sovereign.enabled: false is rejected at startup — tramai.profile is the sole runtime selector. To run standard, remove the key (or set tramai.profile: standard).
Optional sovereign add-ons, all separate artifacts:
| Artifact | Purpose |
|---|---|
tramai-spring-boot-starter-sovereign-persistence-file / -jdbc | Durable approval and checkpoint persistence |
tramai-spring-boot-starter-sovereign-ops (+ -rest, -actuator, -micrometer, -observability) | Approval operations, control-plane endpoints, metrics |
tramai-spring-secrets-file / -vault / -aws | Secret backends |
tramai-spring-boot-starter-local-provider-openai | Configure OpenAI-compatible local endpoints under tramai.providers.<name> |
The 0.4.0/0.5.0 coordinate tramai-spring-boot-starter-sovereign does not exist in 0.6.0. Migrating is the unified starter plus tramai.profile: sovereign.
What Auto-Configuration Provides
- Runtime creation: Binds
tramai.*, creates theTramairuntime (or the sovereign runtime undertramai.profile: sovereign), and closes it with the context - Service bean registration: Scans for
@AiServiceinterfaces and registers proxy beans — no manualTramai.create() - Tool discovery: Scans beans for
@AiToolmethods and registers them as tools - Provider wiring: Merges property-defined providers from adapter modules with explicit
ModelProviderbeans - Secret resolution: Resolves
env:andfile:references natively, plus Vault and AWS Secrets Manager through their modules - Resilience and cost: Applies circuit breaker, retry pacing, and token budgets from YAML
- Cache: Optional in-memory response cache
Limitations
- No advanced bean scopes: AI service beans are standard singleton proxies.
- No per-bean provider overrides outside annotations: Provider selection is
@Operation(provider = ...)or the model mapping registry. - No automatic test slices:
@WebMvcTestand friends do not isolate tramai. Replace the provider with a deterministicModelProviderbean, or usetramai-testing. - Thin adapter: The Spring integration focuses on wiring. It adds no Spring-specific abstraction on top of the engine.
Next Steps
- Get Started — the standalone path and the 5-minute typed service
- Providers & Routing — provider registration, model mapping, and fallback strategy
- Sovereign Mode — what the sovereign profile guarantees
- tramai-spring-boot-starter — module card and artifact composition
- Structured Output — validation annotations and retry behavior
- Testing — deterministic testing with mock providers
