Annotation Reference
TramAI's annotation surface lives in dev.tramai.core.annotations and is small on purpose. All annotations are runtime-retained: the engine reads them through reflection when it builds the service proxy, so there is no code generation step.
The annotation module is part of tramai-core, which is the stable tier of the 0.6.0 release line. See API Stability for the per-module tiers.
@AiService
Marks an interface as a TramAI service contract.
@AiService
interface Summarizer {
@Operation(prompt = "Summarize the incident.", model = "gpt-4o")
suspend fun summarize(incident: String): String
}
- Target:
AnnotationTarget.CLASS. - Apply it to interfaces, not to implementation classes. TramAI creates the implementation.
- In Spring, every
@AiServiceinterface on the scanned classpath becomes a bean during auto-configuration. In standalone code you create it yourself withtramai.create<Summarizer>().
@Operation
Declares one AI-backed method on an @AiService interface.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
prompt | String | "" | Prompt template sent as the base user instruction |
model | String | (required) | Logical model name requested by the operation |
provider | String | "" | Explicit provider id, bypassing registry model resolution |
tools | Array<String> | [] | Tool names available to this operation |
maxRetries | Int | 2 | Structured-output retries after validation failures |
providerRetries | Int | 3 | Provider retries after transient transport or API failures |
timeoutMillis | Long | 30_000 | Maximum duration of a single provider attempt |
cacheable | Boolean | false | Allow the engine to cache successful non-streaming responses |
cacheTtlMillis | Long | 60_000 | Cache TTL when cacheable is enabled |
@Operation(
prompt = "Summarize the incident",
model = "gpt-4o",
provider = "openai",
tools = ["vendor_lookup"],
maxRetries = 3,
providerRetries = 2,
timeoutMillis = 15_000,
cacheable = true,
cacheTtlMillis = 60_000,
)
suspend fun summarize(incident: String): IncidentSummary
maxRetries and providerRetries are different budgets. maxRetries counts re-asks after a structured-output validation failure; providerRetries counts re-attempts of the same provider route after retryable transport or API failures. For streaming operations, providerRetries applies only while no token has been emitted to the caller — once output is visible, TramAI does not retry that provider or fall back mid-stream.
@SystemPrompt
Declares a service-wide system prompt applied to every operation on the interface.
@AiService
@SystemPrompt("You are a precise billing assistant.")
interface BillingAnalyzer {
@Operation(prompt = "Return the invoice status.", model = "gpt-4o")
suspend fun analyze(invoice: String): String
}
- Target:
AnnotationTarget.CLASS. - Use it when every method on the service should share the same role framing. If methods need genuinely different system behavior, split the interface instead.
@System
Defines the system-level instruction for a single operation.
@Operation(prompt = "Summarize account {accountId}.", model = "gpt-4o")
@System("You are reviewing account {accountId}. Never speculate beyond the provided data.")
suspend fun summarize(accountId: String, notes: String): String
- Target:
AnnotationTarget.FUNCTION;valuecarries the template. - The value is sent as the
systemrole message. Multiple@Systemannotations on the same function are concatenated with newlines in declaration order. {param}markers interpolate method parameter values.- If
@Systemis absent, the engine uses the class-level@SystemPrompt. If both are present,@Systemwins and a warning is logged. If neither exists, a default system message is derived from the interface and method signature.
@User
Defines a user-role message for an operation.
@Operation(model = "gpt-4o")
@User("Analyze invoice {invoiceId} for duplicates.")
@User("Report confidence for every finding.")
suspend fun analyze(invoiceId: String): DuplicateAnalysis
- Target:
AnnotationTarget.FUNCTION;valuecarries the template with{param}interpolation. - Multiple
@Userannotations become separate user messages, in order. - If neither
@Usernor@Operation(prompt = ...)is present, the engine builds a default user message from the method signature.
@ConversationId
Marks the method parameter that identifies the conversation for multi-turn memory.
@Operation(prompt = "Continue the account review.", model = "gpt-4o")
suspend fun review(@ConversationId sessionId: String, message: String): String
- Target:
AnnotationTarget.VALUE_PARAMETER. - Only meaningful when you configure chat memory (the standalone builder's
memory(...)or the Spring memory configuration). The annotated argument'stoString()is used as the conversation key. - A null annotated argument fails fast. If no parameter is annotated, the engine falls back to its
ConversationIdProvider(a UUID provider by default), which means each call is its own conversation.
@AiTool
Marks a method for discovery as a portable tool by framework adapters.
@Component
class VendorTools {
@AiTool(
name = "vendor_lookup",
description = "Looks up vendor reliability and payment terms.",
idempotent = true,
permission = "vendor.read",
sideEffectLevel = SideEffectLevel.READ_ONLY, // NONE | READ_ONLY | WRITE | UNKNOWN
)
fun lookupVendor(input: VendorLookupInput): VendorDetails = TODO()
}
- Target:
AnnotationTarget.FUNCTION. name— explicit tool name; empty means the adapter may fall back to the method name.description— injected into model tool definitions. Describes what the tool does, not how.idempotent— whether repeating execution with the same logical input is safe. This describes repetition semantics only; it is not an instruction to retry on failure.sideEffectLevel—SideEffectLevel:NONE,READ_ONLY,WRITE,UNKNOWN. DefaultUNKNOWN. Use it to describe whether the tool reads data, writes data, or has unknown external effects.permission— the permission required to expose and execute the tool. Default is empty, which preserves the legacy contract: noToolSecurityMetadatais produced, and the standard runtime exposes the tool as before. Secure profiles such as sovereign TramAI reject tools whosepermissionis empty.risk—RiskLevel:LOW,MEDIUM,HIGH,CRITICAL. DefaultHIGH. Used by secure profiles whenpermissionis set.approval—ApprovalMode:AUTO,HUMAN_REQUIRED,HUMAN_REQUIRED_WITH_TIMEOUT. DefaultHUMAN_REQUIRED. Used by secure profiles whenpermissionis set.managedNetworkEgress—ManagedNetworkEgress:ALLOW,DENY,ALLOWLIST_ONLY. DefaultDENY. Covers TramAI-managed egress only; it does not sandbox arbitrary networking inside the method.audit—AuditDetail:MINIMAL,DECISION_ONLY,FULL. DefaultFULL.
The governance defaults are deliberately conservative. Opting into governance never silently weakens the declared posture — if you set permission and say nothing else, you get high risk, human approval, denied managed egress, and full audit detail.
Structured Output Property Annotations
These three apply to non-String return types and shape schema generation and validation. They target PROPERTY, FIELD, and VALUE_PARAMETER.
@AiDescription
Adds a human-readable description to a property exposed in the generated schema.
data class SpendAnalysis(
@property:AiDescription("Total spend in USD")
val totalSpend: Double,
)
@AiRange
Constrains a numeric property to a closed range. Both bounds are inclusive and required.
data class Confidence(
@property:AiRange(min = 0.0, max = 1.0)
val confidence: Double,
)
@AiMinItems
Declares the minimum allowed element count for a collection property.
data class RecommendationSet(
@property:AiMinItems(1)
val actions: List<String>,
)
Use it when an empty collection would be semantically invalid.
Spring Wiring Annotation
@EnableTramai
Optional. In Spring Boot applications auto-configuration is normally sufficient; @EnableTramai is for annotation-driven Spring contexts, where it establishes the application package used for @AiService discovery and loads the profile configurations contributed by modules on the classpath.
It does not select a runtime profile. tramai.profile is the sole runtime selector: missing or standard gives the standard runtime, sovereign gives the sovereign runtime. See Configuration.
Current Boundaries
TramAI does not use dedicated annotations for:
- streaming selection — it is inferred from the return type (
Flow<StreamChunk>) - provider-native structured-output toggles
- tool-calling opt-in per operation — use
@Operation(tools = [...])and@AiTool - conversation state — use chat memory plus
@ConversationId(see /modules/tramai-memory)
