Tramai Logo
Tramai

Why TramAI

If you're evaluating AI libraries for the JVM, this page explains what TramAI does differently, where it fits, and where it doesn't.

AI as a 4th Software Layer

Every other JVM AI framework gives you a utility. A ChatClient. A chain builder. A prompt template engine. Something you call — not something you inject.

TramAI gives you a layer.

LayerStandard BackendWith TramAI
Presentation@Controller@Controller
Business Logic@Service@Service
AI@AiService ← NEW
Persistence@Repository@Repository

@AiService is an architectural annotation, not a library class. You put it on an interface, same way you put @Service on a business class or @Repository on a data access class. TramAI generates a proxy, wires it through Spring DI (via @EnableTramai), and injects it into your services — just like any other bean.

@AiService
interface InvoiceAnalyzer {
    @Operation(prompt = "Analyze this invoice", model = "gpt-4o")
    suspend fun analyze(invoiceText: String): InvoiceStatus
}

@Service
class BillingService(
    private val invoiceAnalyzer: InvoiceAnalyzer,
    private val invoiceRepository: InvoiceRepository,
) {
    fun process(invoiceId: String) {
        val invoice = invoiceRepository.findById(invoiceId)
        val status = invoiceAnalyzer.analyze(invoice.text)
    }
}

The InvoiceAnalyzer has the same architectural rigor as every other layer: typed contract, dependency injection, testability, observability, resilience, and security controls.

This is not a library call. This is a layer.

The name TramAI is Italian for I wove — the weft passes through the warp to create fabric. The AI layer weaves through your architecture without replacing it.

The Problem

Here's what JVM backend engineers actually experience when adding AI to their applications today:

  • You're parsing JSON from LLM responses with manual try-catch blocks. The model returns a string. You call a JSON parser. It fails because the model wrapped the JSON in markdown fences. You write a regex. It fails again.
  • Your AI calls are invisible in your monitoring stack. Your HTTP clients, database queries, and message queues all emit traces. Your AI calls are a black box.
  • You're writing retry logic by hand. The model returns malformed JSON. You catch the exception, tweak the prompt, and retry.
  • You can't test without hitting a live model. Your tests are slow, flaky, and cost money.
  • You're locked into a vendor SDK. Each provider has its own API and quirks, and your business logic starts depending on provider-specific types.

What TramAI Does Differently

ConcernTypical ApproachTramAI Approach
Programming ModelLearn framework abstractions like chains, prompts, or chat clients.Define your own interface. One annotation. Your code stays your code.
Type SafetyParse raw strings manually.Return typed Kotlin/Java objects. Engine handles extraction, parsing, and validation.
Structured OutputPrompt-engineer JSON output and often hand-write schema expectations.Automatic schema generation from your data class with retry on parse or validation failure.
ObservabilityNo tracing, or custom instrumentation you maintain yourself.Native OpenTelemetry spans and metrics for every AI call.
ResilienceManual retry logic in business code.Engine-owned retries, fallback routing, and circuit breaking.
TestingHit a live model in tests, or mock at the HTTP layer.Deterministic testing with tramai-testing.
Vendor Lock-inBusiness logic depends on vendor SDKs or opinionated framework types.Framework-agnostic core with thin provider adapters.
GraalVM Native ImageOften requires manual reflection config.Pre-generated reflection metadata.

Code Comparison: Before and After

Without TramAI

val client = HttpClient.newHttpClient()
val request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.openai.com/v1/chat/completions"))
    .header("Authorization", "Bearer $apiKey")
    .POST(HttpRequest.BodyPublishers.ofString(payload))
    .build()

var attempts = 0
while (attempts < 3) {
    try {
        val response = client.send(request, HttpResponse.BodyHandlers.ofString())
        val json = response.body()
        val cleaned = json.replace(Regex("```json\\s*|```"), "").trim()
        val result = Json.decodeFromString<Analysis>(cleaned)
        return result
    } catch (e: Exception) {
        attempts++
        if (attempts == 3) throw e
        Thread.sleep(1000L * attempts)
    }
}

With TramAI

@AiService
interface ReviewAnalyzer {
    @Operation(
        prompt = "Analyze this product review.",
        model = "gpt-4o",
    )
    suspend fun analyze(review: String): Analysis
}

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

val analyzer = tramai.create<ReviewAnalyzer>()
val result = analyzer.analyze("Great product, fast shipping.")

The difference is that TramAI moves HTTP, parsing, retries, and response cleanup into the engine instead of duplicating that infrastructure logic in application code.

What TramAI Is NOT

TramAI is deliberately scoped. Understanding what it does not do is as important as understanding what it does.

TramAI is not an agent framework. It does not build open-ended reasoning loops or autonomous tool-using agents.

TramAI is not a chain framework. It does not use pipeline objects or composable prompt-template abstractions as the core programming model.

TramAI is not primarily a RAG toolkit. Optional RAG modules exist, but they are additive. The core library has zero RAG dependencies.

TramAI is a typed integration layer between your JVM application and LLM providers. You define the contract, TramAI handles the execution.

When NOT to Use TramAI

  • You need LangChain-style chains and composable prompt templates.
  • You need autonomous agent swarms with open-ended reasoning.
  • You're building a Python ML pipeline.
  • You need streaming with structured partial results.
  • You need provider-native structured output modes such as OpenAI response_format.

Where to Go Next

  • Get Started — copy-paste your first typed AI service in 5 minutes.
  • Spring Boot — auto-configure TramAI in your existing Spring application.
  • Structured Output — validation annotations, retry behavior, and constraints.
  • Workflows, Not Chains — why TramAI's typed workflow DSL avoids chain-style abstractions.