Skip to main content

Overview

Tool is the base class for creating tools that can be invoked by LLMs during agent execution. Tools enable agents to interact with external systems, perform computations, retrieve data, and take actions.
Never call tool.execute() directly in agent code. Always use the AIAgentEnvironment interface to ensure proper event handling, feature pipeline integration, and testing support.

Class Definition

public abstract class Tool<TArgs, TResult>(
    public val argsSerializer: KSerializer<TArgs>,
    public val resultSerializer: KSerializer<TResult>,
    public val descriptor: ToolDescriptor,
    public val metadata: Map<String, String> = emptyMap()
)

Type Parameters

TArgs
Generic
The type of arguments the tool accepts
TResult
Generic
The type of result the tool returns

Constructor Parameters

argsSerializer
KSerializer<TArgs>
required
KotlinX Serialization serializer for encoding/decoding tool arguments
resultSerializer
KSerializer<TResult>
required
KotlinX Serialization serializer for encoding/decoding tool results
descriptor
ToolDescriptor
required
Schema describing the tool’s name, description, and parameters for the LLM
metadata
Map<String, String>
default:"emptyMap()"
Arbitrary metadata associated with the tool

Convenience Constructor

Simplified constructor that generates the descriptor automatically:
public constructor(
    argsSerializer: KSerializer<TArgs>,
    resultSerializer: KSerializer<TResult>,
    name: String,
    description: String
)
name
String
required
The name of the tool (used by LLM to invoke it)
description
String
required
Human-readable explanation of what the tool does (helps LLM understand when to use it)

Properties

name
String
The name of the tool from the descriptor
argsSerializer
KSerializer<TArgs>
Serializer for tool arguments
resultSerializer
KSerializer<TResult>
Serializer for tool results
descriptor
ToolDescriptor
Tool schema for LLM consumption
metadata
Map<String, String>
Tool metadata
json
Json
JSON configuration used for encoding/decoding (can be overridden)

Abstract Methods

execute

Executes the tool’s logic with the provided arguments.
public abstract suspend fun execute(args: TArgs): TResult
args
TArgs
required
The input arguments required to execute the tool
return
TResult
The result of the tool’s execution
In agent code, use environment.executeTool() instead of calling this method directly.

Encoding/Decoding Methods

decodeArgs

Decodes raw JSON arguments into typed arguments.
public fun decodeArgs(rawArgs: JsonObject): TArgs

encodeArgs

Encodes typed arguments into JSON.
public fun encodeArgs(args: TArgs): JsonObject

decodeResult

Decodes raw JSON result into typed result.
public fun decodeResult(rawResult: JsonElement): TResult

encodeResult

Encodes typed result into JSON.
public fun encodeResult(result: TResult): JsonElement

encodeResultToString

Encodes result to string representation for LLM.
public open fun encodeResultToString(result: TResult): String
Override this method to customize how results are presented to the LLM.

Implementation Examples

Basic Tool

import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer

class CalculatorTool : Tool<CalculatorTool.Args, Int>(
    argsSerializer = serializer(),
    resultSerializer = serializer(),
    name = "calculator",
    description = "Performs arithmetic operations on two numbers"
) {
    @Serializable
    data class Args(
        val operation: Operation,
        val a: Int,
        val b: Int
    )
    
    enum class Operation { ADD, SUBTRACT, MULTIPLY, DIVIDE }
    
    override suspend fun execute(args: Args): Int {
        return when (args.operation) {
            Operation.ADD -> args.a + args.b
            Operation.SUBTRACT -> args.a - args.b
            Operation.MULTIPLY -> args.a * args.b
            Operation.DIVIDE -> args.a / args.b
        }
    }
}

Tool with Custom Result

@Serializable
data class SearchResult(
    val title: String,
    val url: String,
    val snippet: String
)

class WebSearchTool : Tool<WebSearchTool.Args, List<SearchResult>>(
    argsSerializer = serializer(),
    resultSerializer = serializer(),
    name = "web_search",
    description = "Searches the web for information"
) {
    @Serializable
    data class Args(
        val query: String,
        val maxResults: Int = 5
    )
    
    override suspend fun execute(args: Args): List<SearchResult> {
        // Perform web search
        return searchEngine.search(args.query, args.maxResults)
    }
    
    override fun encodeResultToString(result: List<SearchResult>): String {
        // Custom formatting for LLM
        return result.joinToString("\n\n") { searchResult ->
            """Title: ${searchResult.title}
            |URL: ${searchResult.url}
            |Snippet: ${searchResult.snippet}""".trimMargin()
        }
    }
}

SimpleTool (String Results)

For tools that return string results:
class WeatherTool : SimpleTool<WeatherTool.Args>(
    argsSerializer = serializer(),
    name = "get_weather",
    description = "Gets current weather for a location"
) {
    @Serializable
    data class Args(
        val location: String,
        val units: String = "celsius"
    )
    
    override suspend fun execute(args: Args): String {
        val weather = weatherApi.getCurrentWeather(args.location)
        return "Temperature: ${weather.temp}°${args.units}, " +
               "Conditions: ${weather.conditions}"
    }
}

Tool with External API

class GitHubTool(
    private val apiToken: String
) : Tool<GitHubTool.Args, GitHubTool.Result>(
    argsSerializer = serializer(),
    resultSerializer = serializer(),
    name = "github_get_repo",
    description = "Fetches information about a GitHub repository"
) {
    @Serializable
    data class Args(
        val owner: String,
        val repo: String
    )
    
    @Serializable
    data class Result(
        val name: String,
        val description: String,
        val stars: Int,
        val forks: Int,
        val language: String
    )
    
    override suspend fun execute(args: Args): Result {
        val client = HttpClient {
            install(ContentNegotiation) {
                json()
            }
        }
        
        try {
            val response = client.get(
                "https://api.github.com/repos/${args.owner}/${args.repo}"
            ) {
                header("Authorization", "token $apiToken")
            }
            
            return response.body()
        } finally {
            client.close()
        }
    }
}

Tool with Error Handling

class DatabaseQueryTool(
    private val database: Database
) : SimpleTool<DatabaseQueryTool.Args>(
    argsSerializer = serializer(),
    name = "query_database",
    description = "Executes a SQL query against the database"
) {
    @Serializable
    data class Args(
        val query: String
    )
    
    override suspend fun execute(args: Args): String {
        return try {
            val results = database.query(args.query)
            "Query successful: ${results.size} rows returned\n" +
            results.joinToString("\n")
        } catch (e: SQLException) {
            "Database error: ${e.message}"
        } catch (e: Exception) {
            "Unexpected error: ${e.message}"
        }
    }
}

Tool with Metadata

class FileOperationTool : Tool<FileOperationTool.Args, String>(
    argsSerializer = serializer(),
    resultSerializer = String.serializer(),
    name = "file_operation",
    description = "Performs file system operations",
    metadata = mapOf(
        "category" to "filesystem",
        "riskLevel" to "high",
        "requiresPermission" to "true"
    )
) {
    @Serializable
    data class Args(
        val operation: String,
        val path: String
    )
    
    override suspend fun execute(args: Args): String {
        // Check metadata before execution
        if (metadata["requiresPermission"] == "true") {
            checkPermissions()
        }
        
        // Perform operation
        return performFileOperation(args.operation, args.path)
    }
}

Usage in Agent

Register Tools

val toolRegistry = ToolRegistry {
    tool(CalculatorTool())
    tool(WebSearchTool())
    tool(WeatherTool())
}

val agent = AIAgent(
    promptExecutor = executor,
    agentConfig = config,
    strategy = strategy,
    toolRegistry = toolRegistry
)

Execute Through Environment

class MyStrategy : AIAgentGraphStrategy<String, String> {
    override val name = "my-strategy"
    
    override suspend fun execute(
        context: AIAgentGraphContext,
        input: String
    ): String? {
        val response = context.llm.prompt {
            user(input)
        }.execute()
        
        // LLM decides to use a tool
        if (response is Message.Assistant.ToolCall) {
            // Execute through environment (correct way)
            val results = context.environment.executeTools(
                response.toolCalls
            )
            
            // Send results back to LLM
            return context.llm.prompt {
                results.forEach { result ->
                    tool(result)
                }
            }.execute().content
        }
        
        return response.content
    }
}

Testing Tools

Unit Test a Tool

@Test
fun testCalculatorTool() = runTest {
    val tool = CalculatorTool()
    
    val result = tool.execute(
        CalculatorTool.Args(
            operation = CalculatorTool.Operation.ADD,
            a = 5,
            b = 3
        )
    )
    
    assertEquals(8, result)
}

Mock Tools in Agents

val agent = AIAgent(...) {
    withTesting()
    
    mockTool(WeatherTool) alwaysReturns "Sunny, 25°C"
    
    mockTool(SearchTool) returns SearchResult(...) onArgumentsMatching {
        args.query.contains("important")
    }
}

Best Practices

Tool Design
  • Keep tool names descriptive and lowercase with underscores
  • Write clear descriptions that explain when to use the tool
  • Use @Serializable data classes for arguments and complex results
  • Handle errors gracefully and return meaningful error messages
  • Consider timeout limits for long-running operations
Security Considerations
  • Validate all tool arguments before execution
  • Sanitize inputs to prevent injection attacks
  • Use metadata to mark dangerous operations
  • Implement permission checks for sensitive tools
  • Never trust LLM-provided data without validation

Advanced Features

Custom JSON Configuration

class CustomTool : Tool<Args, Result>(...) {
    override val json = Json {
        ignoreUnknownKeys = true
        encodeDefaults = false
        prettyPrint = false
    }
}

Dynamic Tool Generation

Generate tools from functions using reflection (JVM only):
fun searchWeb(query: String, maxResults: Int = 5): List<SearchResult> {
    // Implementation
}

val tool = ToolFromCallable(::searchWeb)

Tool Sets

Group related tools into sets (JVM only):
class FileToolSet {
    fun readFile(path: String): String { /* ... */ }
    fun writeFile(path: String, content: String) { /* ... */ }
    fun deleteFile(path: String) { /* ... */ }
}

val toolSet = FileToolSet()
val tools = toolSet.asTools()

Source Reference

Defined in: agents-tools/src/commonMain/kotlin/ai/koog/agents/core/tools/Tool.kt

Build docs developers (and LLMs) love