Documentation Index
Fetch the complete documentation index at: https://mintlify.com/JetBrains/koog/llms.txt
Use this file to discover all available pages before exploring further.
Tools are the actions your AI agent can perform. Koog provides built-in tools and makes it easy to create custom ones.
A tool is a Kotlin class that implements Tool<TArgs, TResult> with:
- Arguments (
TArgs): Parameters the LLM provides when calling the tool
- Result (
TResult): What the tool returns to the LLM
- Annotations: Descriptions that help the LLM understand when and how to use the tool
The easiest way to create tools is with the @Tool annotation and ToolSet:
import ai.koog.agents.core.tools.annotations.Tool
import ai.koog.agents.core.tools.annotations.LLMDescription
import ai.koog.agents.core.tools.reflect.ToolSet
class WeatherTools(private val apiClient: OpenMeteoClient) : ToolSet {
@Tool
@LLMDescription("Get weather forecast for a location and date range")
suspend fun getWeatherForecast(
@LLMDescription("Location name (e.g., 'New York', 'London', 'Paris')")
location: String,
@LLMDescription("ISO 3166-1 alpha-2 country code (e.g., 'US', 'GB', 'FR')")
countryCodeISO2: String,
@LLMDescription("Start date in ISO format (e.g., '2023-05-20')")
startDate: String,
@LLMDescription("End date in ISO format (e.g., '2023-05-20')")
endDate: String,
@LLMDescription("Forecast granularity")
granularity: ForecastGranularity
): String {
val location = apiClient.searchLocation(location)
.find { it.countryCode == countryCodeISO2 }
?: return "Location not found"
val forecast = apiClient.getWeatherForecast(
latitude = location.latitude,
longitude = location.longitude,
startDate = LocalDate.parse(startDate),
endDate = LocalDate.parse(endDate),
granularity = granularity
)
return formatForecast(forecast)
}
}
enum class ForecastGranularity {
DAILY,
HOURLY
}
Convert your ToolSet to tools and register them:
import ai.koog.agents.core.tools.ToolRegistry
import ai.koog.agents.core.tools.reflect.asTools
val apiClient = OpenMeteoClient()
val toolRegistry = ToolRegistry {
tools(WeatherTools(apiClient).asTools())
}
Koog provides ready-to-use tools for common operations:
import ai.koog.agents.ext.tool.file.*
import ai.koog.rag.base.files.JVMFileSystemProvider
val toolRegistry = ToolRegistry {
tool(ListDirectoryTool(JVMFileSystemProvider.ReadOnly))
tool(ReadFileTool(JVMFileSystemProvider.ReadOnly))
tool(EditFileTool(JVMFileSystemProvider.ReadWrite))
}
import ai.koog.agents.ext.tool.shell.*
val toolRegistry = ToolRegistry {
tool(ExecuteShellCommandTool(
executor = JvmShellCommandExecutor(),
confirmationHandler = PrintShellCommandConfirmationHandler()
))
}
import ai.koog.agents.ext.tool.SayToUser
val toolRegistry = ToolRegistry {
tool(SayToUser)
}
For more control, implement the Tool interface directly:
import ai.koog.agents.core.tools.Tool
import ai.koog.agents.core.tools.ToolResult
import kotlinx.serialization.Serializable
object CalculatorTool : Tool<CalculatorTool.Args, ToolResult.Text> {
@Serializable
data class Args(
val operation: String,
val a: Double,
val b: Double
)
override val name = "calculator"
override val description = "Perform basic math operations"
override val argsType = typeOf<Args>()
override val resultType = typeOf<ToolResult.Text>()
override suspend fun execute(args: Args): ToolResult.Text {
val result = when (args.operation) {
"add" -> args.a + args.b
"subtract" -> args.a - args.b
"multiply" -> args.a * args.b
"divide" -> args.a / args.b
else -> throw IllegalArgumentException("Unknown operation")
}
return ToolResult.Text("Result: $result")
}
}
Tools can maintain state for tracking operations:
class OrderTools(private val orderStore: OrderStore) : ToolSet {
@Tool
@LLMDescription("Create a new order")
suspend fun createOrder(
@LLMDescription("Customer ID")
customerId: String,
@LLMDescription("List of items")
items: List<String>
): String {
val order = Order(
id = UUID.randomUUID().toString(),
customerId = customerId,
items = items,
status = OrderStatus.PENDING
)
orderStore.save(order)
return "Order ${order.id} created successfully"
}
@Tool
@LLMDescription("Get order status")
suspend fun getOrderStatus(
@LLMDescription("Order ID")
orderId: String
): String {
val order = orderStore.get(orderId)
?: return "Order not found"
return "Order ${order.id} status: ${order.status}"
}
}
Clear Descriptions
Provide detailed descriptions to help the LLM understand when to use each tool:
@Tool
@LLMDescription(
"Search for products in the inventory by name, category, or SKU. " +
"Returns up to 10 matching products with pricing and availability."
)
suspend fun searchProducts(
@LLMDescription("Search query (product name, category, or SKU)")
query: String,
@LLMDescription("Maximum number of results (1-50, default 10)")
limit: Int = 10
): List<Product>
Error Handling
Handle errors gracefully and return meaningful messages:
@Tool
@LLMDescription("Fetch user account details")
suspend fun getUserAccount(
@LLMDescription("User ID")
userId: String
): String {
return try {
val account = accountService.getAccount(userId)
"Account found: ${account.email}, Balance: ${account.balance}"
} catch (e: NotFoundException) {
"User account not found for ID: $userId"
} catch (e: Exception) {
"Error fetching account: ${e.message}"
}
}
Type Safety
Use serializable data classes for complex arguments:
@Serializable
data class PaymentRequest(
val amount: Double,
val currency: String,
val method: PaymentMethod
)
@Serializable
enum class PaymentMethod {
CREDIT_CARD,
DEBIT_CARD,
PAYPAL,
BANK_TRANSFER
}
@Tool
@LLMDescription("Process a payment")
suspend fun processPayment(
@LLMDescription("Payment details")
request: PaymentRequest
): String {
// Process payment...
}
Structured Outputs
Return structured data when useful:
@Serializable
data class WeatherForecast(
val location: String,
val temperature: Double,
val condition: String,
val humidity: Int
)
@Tool
@LLMDescription("Get current weather for a location")
suspend fun getCurrentWeather(
@LLMDescription("City name")
city: String
): WeatherForecast {
val data = weatherApi.getCurrentWeather(city)
return WeatherForecast(
location = data.name,
temperature = data.main.temp,
condition = data.weather.first().description,
humidity = data.main.humidity
)
}
Combine multiple tool registries:
val fileTools = ToolRegistry {
tool(ReadFileTool(JVMFileSystemProvider.ReadOnly))
tool(EditFileTool(JVMFileSystemProvider.ReadWrite))
}
val weatherTools = ToolRegistry {
tools(WeatherTools(apiClient).asTools())
}
// Merge registries
val allTools = fileTools + weatherTools
Next Steps
Real-World Example
Here’s a complete example with multiple tools:
class SwitchTools(private val switch: Switch) : ToolSet {
@Tool
@LLMDescription("Turn the switch on")
suspend fun turnOn(): String {
switch.turnOn()
return "Switch turned on"
}
@Tool
@LLMDescription("Turn the switch off")
suspend fun turnOff(): String {
switch.turnOff()
return "Switch turned off"
}
@Tool
@LLMDescription("Get current switch state")
suspend fun getState(): String {
return "Switch is ${if (switch.isOn) "on" else "off"}"
}
}
// Usage
val switch = Switch()
val toolRegistry = ToolRegistry {
tools(SwitchTools(switch).asTools())
}
val agent = AIAgent(
promptExecutor = executor,
llmModel = OpenAIModels.Chat.GPT4oMini,
systemPrompt = "You control a switch. Help users operate it.",
toolRegistry = toolRegistry
)
agent.run("Is the switch on? If not, turn it on.")