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
The type of arguments the tool accepts
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
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
)
The name of the tool (used by LLM to invoke it)
Human-readable explanation of what the tool does (helps LLM understand when to use it)
Properties
The name of the tool from the descriptor
Serializer for tool arguments
Serializer for tool results
Tool schema for LLM consumption
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
The input arguments required to execute the tool
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
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
}
}
}
@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()
}
}
}
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}"
}
}
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()
}
}
}
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}"
}
}
}
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
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
}
}
@Test
fun testCalculatorTool() = runTest {
val tool = CalculatorTool()
val result = tool.execute(
CalculatorTool.Args(
operation = CalculatorTool.Operation.ADD,
a = 5,
b = 3
)
)
assertEquals(8, result)
}
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
}
}
Generate tools from functions using reflection (JVM only):
fun searchWeb(query: String, maxResults: Int = 5): List<SearchResult> {
// Implementation
}
val tool = ToolFromCallable(::searchWeb)
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