Fluent DSL and Kotlin DSL for building autonomous agents with planning capabilities on the JVM, featuring annotation-based and programmatic configuration for agentic flows with Spring Boot integration
The TypedOps API provides a high-level, type-safe interface for transforming data between types using agentic systems. It enables declarative transformations where you specify input and output types, and the agent platform determines how to accomplish the transformation.
Core interface for type-safe transformations between arbitrary types.
interface TypedOps {
/**
* Transform between these two types if possible.
*/
fun <I : Any, O> transform(
input: I,
outputClass: Class<O>,
processOptions: ProcessOptions = ProcessOptions(),
): O
/**
* Return a reusable function that performs this transformation.
* Validates whether it's possible and include metadata.
*/
fun <I : Any, O> asFunction(
outputClass: Class<O>,
): AgentFunction<I, O>
/**
* Return a reusable function for a specific named agent.
*/
@Throws(NoSuchAgentException::class)
fun <I : Any, O> asFunction(
outputClass: Class<O>,
agentName: String,
): AgentFunction<I, O>
/**
* Transform user input into the target type
*/
fun <O> handleUserInput(
intent: String,
outputClass: Class<O>,
processOptions: ProcessOptions = ProcessOptions(),
): O
}Basic Transformation Example:
import com.embabel.agent.api.common.TypedOps
import com.embabel.agent.api.common.AgentPlatformTypedOps
import com.embabel.agent.core.ProcessOptions
import com.embabel.agent.domain.io.UserInput
val typedOps: TypedOps = AgentPlatformTypedOps(agentPlatform)
// Transform user input to specific type
val result: CustomerOrder = typedOps.transform(
input = UserInput("I want to order 3 pizzas for delivery"),
outputClass = CustomerOrder::class.java,
processOptions = ProcessOptions()
)
data class CustomerOrder(
val item: String,
val quantity: Int,
val deliveryMethod: String
)Type-safe extension functions with reified generics for cleaner Kotlin syntax.
/**
* Transform with reified output type
*/
inline fun <I : Any, reified O : Any> TypedOps.transform(
input: I,
processOptions: ProcessOptions = ProcessOptions(),
): O
/**
* Transform with named parameter order
*/
fun <I : Any, O : Any> TypedOps.transform(
input: I,
processOptions: ProcessOptions = ProcessOptions(),
outputClass: Class<O>,
): O
/**
* Create function with reified output type
*/
inline fun <I : Any, reified O> TypedOps.asFunction(): AgentFunction<I, O>Reified Type Transform Example:
import com.embabel.agent.api.common.transform
import com.embabel.agent.domain.io.UserInput
// Use reified generics - no need to pass class explicitly
val summary: DocumentSummary = typedOps.transform<UserInput, DocumentSummary>(
input = UserInput("Summarize the Q4 financial report"),
processOptions = ProcessOptions()
)
data class DocumentSummary(
val title: String,
val keyPoints: List<String>,
val recommendations: List<String>
)Reusable transformation function that can be invoked multiple times with different inputs and process options.
interface AgentFunction<I, O> : BiFunction<I, ProcessOptions, O> {
/** The agent scope where this function executes */
val agentScope: AgentScope
/** The output type this function produces */
val outputClass: Class<O>
}Reusable Function Example:
import com.embabel.agent.api.common.asFunction
import com.embabel.agent.domain.io.UserInput
import com.embabel.agent.domain.library.HasContent
import com.embabel.agent.core.ProcessOptions
// Create reusable function
val contentGenerator: AgentFunction<UserInput, HasContent> =
typedOps.asFunction()
// Use multiple times with different inputs
val blog1 = contentGenerator.apply(
UserInput("Write about machine learning"),
ProcessOptions()
)
val blog2 = contentGenerator.apply(
UserInput("Write about cloud computing"),
ProcessOptions()
)
// Can also use as Java BiFunction
val results = listOf(
UserInput("Topic 1"),
UserInput("Topic 2"),
UserInput("Topic 3")
).map { input ->
contentGenerator.apply(input, ProcessOptions())
}Create agent functions bound to specific deployed agents by name.
@Throws(NoSuchAgentException::class)
fun <I : Any, O> TypedOps.asFunction(
outputClass: Class<O>,
agentName: String,
): AgentFunction<I, O>
class NoSuchAgentException(
val agentName: String,
val knownAgents: String,
) : IllegalArgumentExceptionNamed Agent Example:
import com.embabel.agent.domain.io.UserInput
import com.embabel.agent.domain.library.HasContent
// Bind to specific deployed agent
val starFinderFunction = typedOps.asFunction<UserInput, HasContent>(
outputClass = HasContent::class.java,
agentName = "TestStarNewsFinder"
)
val horoscope = starFinderFunction.apply(
UserInput("Lynda is a Scorpio, find some news for her"),
ProcessOptions()
)
// Error handling for unknown agents
try {
val unknownFunction = typedOps.asFunction<UserInput, Report>(
outputClass = Report::class.java,
agentName = "NonExistentAgent"
)
} catch (e: NoSuchAgentException) {
println("Agent '${e.agentName}' not found")
println("Available agents: ${e.knownAgents}")
}Simplified API for transforming natural language user input into structured types.
fun <O> TypedOps.handleUserInput(
intent: String,
outputClass: Class<O>,
processOptions: ProcessOptions = ProcessOptions(),
): OUser Input Example:
// Transform natural language to structured data
val searchQuery = typedOps.handleUserInput(
intent = "Find all customers who purchased in the last 30 days",
outputClass = DatabaseQuery::class.java,
processOptions = ProcessOptions()
)
data class DatabaseQuery(
val table: String,
val filters: List<Filter>,
val timeRange: TimeRange
)
// Another example with booking
val reservation = typedOps.handleUserInput(
intent = "Book a table for 4 people tomorrow at 7pm",
outputClass = RestaurantBooking::class.java
)
data class RestaurantBooking(
val partySize: Int,
val dateTime: LocalDateTime,
val specialRequests: String?
)Platform-level implementation of TypedOps that uses the agent platform to perform transformations.
class AgentPlatformTypedOps(
private val agentPlatform: AgentPlatform,
) : TypedOps {
override fun <I : Any, O> asFunction(
outputClass: Class<O>,
): AgentFunction<I, O>
override fun <I : Any, O> asFunction(
outputClass: Class<O>,
agentName: String,
): AgentFunction<I, O>
}Platform Integration Example:
import com.embabel.agent.api.common.AgentPlatformTypedOps
import com.embabel.agent.core.AgentPlatform
@Service
class DataTransformationService(
private val agentPlatform: AgentPlatform
) {
private val typedOps = AgentPlatformTypedOps(agentPlatform)
fun processUserRequest(request: String): ProcessedResult {
// Use TypedOps to transform user input
return typedOps.handleUserInput(
intent = request,
outputClass = ProcessedResult::class.java
)
}
fun convertFormat(data: RawData): CleanData {
// Direct transformation
return typedOps.transform(
input = data,
outputClass = CleanData::class.java
)
}
}Process multiple items through agent transformations.
import com.embabel.agent.api.common.transform
import com.embabel.agent.core.ProcessOptions
data class EmailThread(val messages: List<String>)
data class ThreadSummary(val summary: String, val actionItems: List<String>)
val threads: List<EmailThread> = loadEmailThreads()
// Create reusable function
val summarizer = typedOps.asFunction<EmailThread, ThreadSummary>()
// Transform all threads
val summaries = threads.map { thread ->
summarizer.apply(thread, ProcessOptions())
}
// With custom process options
val detailedSummaries = threads.map { thread ->
summarizer.apply(
thread,
ProcessOptions(
maxSteps = 20,
timeout = 60000
)
)
}Chain transformations for complex workflows.
import com.embabel.agent.domain.io.UserInput
data class CustomerQuery(val question: String, val customerId: String)
data class EnrichedQuery(val query: CustomerQuery, val customerData: CustomerProfile)
data class Response(val answer: String, val suggestedActions: List<String>)
// Step 1: Parse user input to structured query
val parseFunction = typedOps.asFunction<UserInput, CustomerQuery>()
// Step 2: Enrich with customer data
val enrichFunction = typedOps.asFunction<CustomerQuery, EnrichedQuery>()
// Step 3: Generate response
val responseFunction = typedOps.asFunction<EnrichedQuery, Response>()
// Execute pipeline
fun handleCustomerSupport(userMessage: String): Response {
val userInput = UserInput(userMessage)
val query = parseFunction.apply(userInput, ProcessOptions())
val enriched = enrichFunction.apply(query, ProcessOptions())
val response = responseFunction.apply(enriched, ProcessOptions())
return response
}
// Usage
val response = handleCustomerSupport(
"Hi, I'm customer #12345 and I have a question about my recent order"
)Choose agents dynamically based on input characteristics.
import com.embabel.agent.domain.io.UserInput
import com.embabel.agent.domain.library.HasContent
class ContentProcessingService(
private val typedOps: TypedOps
) {
private val agentMap = mapOf(
"news" to "NewsAnalysisAgent",
"technical" to "TechnicalDocAgent",
"creative" to "CreativeWritingAgent"
)
fun processContent(input: String, contentType: String): HasContent {
val agentName = agentMap[contentType]
?: throw IllegalArgumentException("Unknown content type: $contentType")
val function = typedOps.asFunction<UserInput, HasContent>(
outputClass = HasContent::class.java,
agentName = agentName
)
return function.apply(
UserInput(input),
ProcessOptions()
)
}
}
// Usage
val service = ContentProcessingService(typedOps)
val newsArticle = service.processContent(
"Market analysis for Q1",
"news"
)
val techDoc = service.processContent(
"API integration guide",
"technical"
)Handle transformation errors gracefully.
import com.embabel.agent.api.common.NoSuchAgentException
import com.embabel.agent.core.AgentProcessStatusCode
fun safeTransform<I : Any, O>(
typedOps: TypedOps,
input: I,
outputClass: Class<O>,
agentName: String? = null
): Result<O> {
return try {
val function = if (agentName != null) {
typedOps.asFunction(outputClass, agentName)
} else {
typedOps.asFunction(outputClass)
}
val result = function.apply(input, ProcessOptions())
Result.success(result)
} catch (e: NoSuchAgentException) {
Result.failure(Exception("Agent not found: ${e.agentName}"))
} catch (e: Exception) {
Result.failure(Exception("Transformation failed: ${e.message}", e))
}
}
// Usage with error handling
val result = safeTransform(
typedOps = typedOps,
input = UserInput("Analyze customer sentiment"),
outputClass = SentimentReport::class.java,
agentName = "SentimentAnalyzer"
)
result.fold(
onSuccess = { report ->
println("Analysis complete: ${report.overallSentiment}")
},
onFailure = { error ->
println("Analysis failed: ${error.message}")
}
)Fine-tune transformation behavior with process options.
import com.embabel.agent.core.ProcessOptions
import com.embabel.agent.api.common.PlannerType
data class ComplexAnalysis(val findings: List<String>, val confidence: Double)
// Create function with custom options for expensive operations
val analyzerFunction = typedOps.asFunction<DataSet, ComplexAnalysis>()
// Quick analysis with time limit
val quickResult = analyzerFunction.apply(
dataSet,
ProcessOptions(
maxSteps = 5,
timeout = 30000, // 30 seconds
plannerType = PlannerType.UTILITY
)
)
// Deep analysis with more resources
val deepResult = analyzerFunction.apply(
dataSet,
ProcessOptions(
maxSteps = 50,
timeout = 300000, // 5 minutes
plannerType = PlannerType.GOAL_BASED
)
)Use TypedOps in Spring-managed services.
import org.springframework.stereotype.Service
import com.embabel.agent.api.common.AgentPlatformTypedOps
import com.embabel.agent.core.AgentPlatform
@Service
class AgentTransformationService(
agentPlatform: AgentPlatform
) {
private val typedOps = AgentPlatformTypedOps(agentPlatform)
fun <I : Any, O> createTransformer(
outputClass: Class<O>
): AgentFunction<I, O> {
return typedOps.asFunction(outputClass)
}
fun <I : Any, O> transform(
input: I,
outputClass: Class<O>,
options: ProcessOptions = ProcessOptions()
): O {
return typedOps.transform(input, outputClass, options)
}
}
@RestController
@RequestMapping("/api/transform")
class TransformationController(
private val transformService: AgentTransformationService
) {
@PostMapping("/analyze")
fun analyzeText(@RequestBody request: TextRequest): AnalysisResult {
return transformService.transform(
input = UserInput(request.text),
outputClass = AnalysisResult::class.java
)
}
}
data class TextRequest(val text: String)
data class AnalysisResult(
val sentiment: String,
val topics: List<String>,
val summary: String
)import com.embabel.agent.core.AgentScope
import com.embabel.agent.core.ProcessOptions
import com.embabel.agent.domain.io.UserInput
import java.util.function.BiFunction
/**
* Reusable AgentFunction.
* Allows use of AgentPlatform as a function from I to O,
* with different process options.
*/
interface AgentFunction<I, O> : BiFunction<I, ProcessOptions, O> {
val agentScope: AgentScope
val outputClass: Class<O>
}
/**
* Exception thrown when agent cannot be found by name
*/
class NoSuchAgentException(
val agentName: String,
val knownAgents: String,
) : IllegalArgumentExceptionimport com.embabel.agent.api.common.TypedOps
import com.embabel.agent.api.common.AgentPlatformTypedOps
import com.embabel.agent.api.common.AgentFunction
import com.embabel.agent.api.common.NoSuchAgentException
import com.embabel.agent.api.common.transform
import com.embabel.agent.api.common.asFunction
import com.embabel.agent.core.AgentPlatform
import com.embabel.agent.core.ProcessOptions
import com.embabel.agent.domain.io.UserInputInstall with Tessl CLI
npx tessl i tessl/maven-com-embabel-agent--embabel-agent-apidocs