RRTutors
Android & AI Module 1: Topic 3

Building Custom GenAI Tools via KSP Compiler Processing

By RR Tutors Editorial Updated July 2026

To interface an AI agent with the real world, you must expose execution functions—commonly referred to as Tool Calling. Google ADK leverages Kotlin Symbol Processing (KSP) to automatically convert plain Kotlin functions into JSON schema payloads that LLMs understand at compile time.

The Danger of Manual Schema Definitions

In historical frameworks, developers had to manually write and map rigorous JSON schemas to explain native code function signatures to language models. If a parameter type shifted, the schema broke, introducing run-time exceptions. KSP removes this layer entirely by shifting schema generation to the compiler lifecycle.

Implementation: Writing an Auto-Generated Custom Tool

Let's build a type-safe tool using the @Tool annotation. This tool will run safely within our application framework to perform a mock data lookup or arithmetic validation.

package com.example.adkdemoapp.tools

import com.google.adk.kt.annotations.Tool
import com.google.adk.kt.annotations.ToolParam

class DeviceUtilityTools {

    @Tool(
        name = "calculate_system_efficiency",
        description = "Computes real-time execution bounds based on current index input and hardware coefficient."
    )
    fun calculateEfficiency(
        @ToolParam(description = "The baseline index value retrieved from local device storage.")
        baseIndex: Double,
        @ToolParam(description = "Coefficient scaler based on industrial configuration profile.")
        coefficient: Int
    ): String {
        // Deterministic, secure native code execution execution layer
        val result = baseIndex * coefficient
        return "Calculated system processing metric value is: $result%"
    }
}

Compiling and Injecting the Tool

When you execute your project build (./gradlew assembleDebug), KSP processes the @Tool metadata hooks and compiles the structural configuration needed by the ADK engine under the hood. You then simply bind this to your agent profile:

// Attaching the compiled tool definition seamlessly
val systemAgent = LlmAgent(
    name = "utility_agent",
    model = Gemini(name = "gemini-2.5-flash", apiKey = BuildConfig.GEMINI_API_KEY),
    tools = listOf(DeviceUtilityTools().toAdkTool()) // Auto-generated helper mapping extension
)