showcase/shell-docs/src/content/ag-ui/sdk/kotlin/tools/overview.mdx
The kotlin-tools module provides a framework for executing client-side tools that agents can call during conversations. Tools run locally on the client device, enabling secure access to device capabilities while maintaining privacy.
dependencies {
implementation("com.agui:kotlin-tools:0.2.1")
}
Note: The tools module is automatically included when using kotlin-client.
Interface for implementing custom tools that agents can call.
Learn more about ToolExecutor →
Manages and executes registered tools.
Learn more about ToolRegistry →
Tools execute on the client device, not on the agent's server:
class CalculatorToolExecutor : ToolExecutor {
override val tool = Tool(
name = "calculator",
description = "Perform basic calculations",
parameters = buildJsonObject {
put("type", "object")
put("properties", buildJsonObject {
put("expression", buildJsonObject {
put("type", "string")
put("description", "Mathematical expression to evaluate")
})
})
},
required = listOf("expression")
)
override suspend fun execute(context: ToolExecutionContext): ToolExecutionResult {
val expression = context.toolCall.function.arguments.jsonObject["expression"]?.jsonPrimitive?.content
?: return ToolExecutionResult.failure("Missing expression parameter")
return try {
val result = evaluateExpression(expression)
ToolExecutionResult.success(
result = JsonPrimitive(result),
message = "$expression = $result"
)
} catch (e: Exception) {
ToolExecutionResult.failure("Calculation error: ${e.message}")
}
}
private fun evaluateExpression(expression: String): Double {
// Simple calculator implementation
return when {
"+" in expression -> {
val parts = expression.split("+")
parts[0].trim().toDouble() + parts[1].trim().toDouble()
}
// Add more operations...
else -> expression.toDouble()
}
}
}
// Create tool registry
val toolRegistry = toolRegistry {
addTool(CalculatorToolExecutor())
addTool(WeatherToolExecutor())
addTool(FileToolExecutor())
}
// Use with agent
val agent = agentWithTools(
url = "https://api.example.com/agent",
toolRegistry = toolRegistry
) {
bearerToken = "your-token"
}
// Agent can now call tools during conversation
agent.sendMessage("What's 15% of 200?").collect { event ->
when (event) {
is ToolCallStartEvent -> {
println("Agent is using tool: ${event.toolCallName}")
}
is ToolResultEvent -> {
println("Tool result: ${event.content}")
}
is TextMessageContentEvent -> {
print(event.delta) // Agent response using tool result
}
}
}
Tools can validate arguments before execution:
override fun validate(toolCall: ToolCall): ToolValidationResult {
val args = toolCall.function.arguments.jsonObject
if (!args.containsKey("location")) {
return ToolValidationResult.failure("Missing required parameter: location")
}
return ToolValidationResult.success()
}
Configure maximum execution time:
override fun getMaxExecutionTimeMs(): Long? = 30_000 // 30 seconds
Handle different types of errors:
override suspend fun execute(context: ToolExecutionContext): ToolExecutionResult {
return try {
// Tool execution logic
performOperation()
ToolExecutionResult.success(result = JsonPrimitive("success"))
} catch (e: IllegalArgumentException) {
// Validation error
ToolExecutionResult.failure("Invalid arguments: ${e.message}")
} catch (e: IOException) {
// Network/IO error
ToolExecutionResult.failure("Network error: ${e.message}")
} catch (e: Exception) {
// Unrecoverable error
throw ToolExecutionException("Tool failed", e, tool.name, context.toolCall.id)
}
}
Track tool performance:
val stats = toolRegistry.getToolStats("calculator")
println("Executions: ${stats?.executionCount}")
println("Success rate: ${stats?.successRate}")
println("Average time: ${stats?.averageExecutionTimeMs}ms")
Inspect registered tools:
// Get all registered tools
val allTools = toolRegistry.getAllTools()
allTools.forEach { tool ->
println("Tool: ${tool.name} - ${tool.description}")
}
// Check if tool exists
if (toolRegistry.isToolRegistered("calculator")) {
println("Calculator tool is available")
}
// Good: Descriptive error messages
ToolExecutionResult.failure("Invalid email format: must contain @ symbol")
// Bad: Generic errors
ToolExecutionResult.failure("Error")
class FileToolExecutor : AbstractToolExecutor(fileTool) {
override suspend fun executeInternal(context: ToolExecutionContext): ToolExecutionResult {
var fileStream: InputStream? = null
return try {
fileStream = openFile(filename)
val content = fileStream.readText()
ToolExecutionResult.success(JsonPrimitive(content))
} finally {
fileStream?.close() // Always clean up resources
}
}
}
class LocationToolExecutor : ToolExecutor {
override val tool = Tool(
name = "get_location",
description = "Get current device location",
parameters = buildJsonObject {
put("type", "object")
put("properties", buildJsonObject {})
}
)
override suspend fun execute(context: ToolExecutionContext): ToolExecutionResult {
return try {
val location = getCurrentLocation() // Platform-specific implementation
ToolExecutionResult.success(
result = buildJsonObject {
put("latitude", location.latitude)
put("longitude", location.longitude)
}
)
} catch (e: SecurityException) {
ToolExecutionResult.failure("Location permission denied")
}
}
}