Google’s Agent Development Kit for Java is described as “an open-source, code-first Java toolkit for building, evaluating, and deploying sophisticated AI agents with flexibility and control”. In this article we’re going to see how it can be consumed in Kotlin code (specifically in the agents module in the ClimateTraceKMP KMP sample).

The first thing we need to do is add the following gradle dependencies (0.2.0 being the latest version at time of writing this).

1
2
implementation("com.google.adk:google-adk:0.2.0")
implementation("com.google.adk:google-adk-dev:0.2.0")

ADK works with the following LLM providers and shown below is an example of creating a Gemini based model.

  • Google
  • OpenAI
  • Anthropic
  • OpenRouter
  • Ollama
1
2
3
4
5
6
val model = Gemini(
    "gemini-1.5-pro",
    Client.builder()
        .apiKey(apiKeyGoogle)
        .build()
)

With the model created we can now create our AI agent. We’re also providing tools to the agent….more about that in a later section.

1
2
3
4
5
6
7
val agent = LlmAgent.builder()
    .name(NAME)
    .model(model)
    .description("Agent to answer climate emissions related questions.")
    .instruction("You are an agent that provides climate emissions related information. Use 3 letter country codes.")
    .tools(tools)
    .build()

The following then shows how we can run the agent with the given prompt (we’re only demonstrating use here as a single-run agent).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
fun main() {
    val runner = InMemoryRunner(initAgent())
    val session = runner
        .sessionService()
        .createSession(NAME, USER_ID)
        .blockingGet()

    val prompt =
        """
            Get emission data for EU countries in 2024.
            Use units of millions for the emissions data.
            Show result in a grid of decreasing order of emissions.
            """.trimIndent()

    val userMsg = Content.fromParts(Part.fromText(prompt))
    val events = runner.runAsync(USER_ID, session.id(), userMsg)

    events.blockingForEach(Consumer { event: Event ->
        event.content().get().parts().getOrNull()?.forEach { part ->
            part.text().getOrNull()?.let { println(it) }
            part.functionCall().getOrNull()?.let { println(it) }
            part.functionResponse().getOrNull()?.let { println(it) }
        }
        if (event.errorCode().isPresent || event.errorMessage().isPresent) {
            println("error: ${event.errorCode().get()}, ${event.errorMessage().get()}")
        }
    })
}


Tools

A Tool in this context “represents a specific capability provided to an AI agent, enabling it to perform actions and interact with the world beyond its core text generation and reasoning abilities”. Shown below are examples of creating both MCP and “Function Tools”.

MCP Tools

1
2
3
4
5
6
val tools = McpToolset(
    ServerParameters
        .builder("java")
        .args("-jar", "<path to climate trace mcp server jar file>", "--stdio")
        .build()
).loadTools().join()

Function Tools

We can also provide what are known as function tools (just local funtions that provide some functionality to our AI agent). Here we need to call Kotlin suspend functions from those tools and, as such, are specifically using ADKs Long Running Function Tools. These need to return an RxJava Single so we need to use the following dependency to allow us to wrap invocation of our Kotlin suspend functions with rxSingle.

1
implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-rx3:1.10.2")

And these are the tools we’re using. The methods need to be static to be consumed by the Java based ADK code so we include in a companion object and annotate with @JvmStatic. Note were invoking shared KMP code here (ClimateTraceRepository) managed using the Koin DI framework.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ClimateTraceTool {
    companion object {
        val climateTraceRepository = koin.get<ClimateTraceRepository>()

        @JvmStatic
        fun getCountries(): Single<Map<String, String>> {
            return rxSingle {
                mapOf("countries" to climateTraceRepository.fetchCountries().toString())
            }
        }

        @JvmStatic
        fun getEmissions(countryCode: String, year: String): Single<Map<String, String>> {
            return rxSingle {
                mapOf("emissions" to climateTraceRepository.fetchCountryEmissionsInfo(countryCode, year).toString())
            }
        }
    }
}
1
2
3
val getCountriesTool = LongRunningFunctionTool.create(ClimateTraceTool::class.java, "getCountries")
val getEmissionsTool = LongRunningFunctionTool.create(ClimateTraceTool::class.java, "getEmissions")
val tools = listOf(getCountriesTool, getEmissionsTool)


ADK Dev UI

ADK also includes a developer web UI to help with agent develoment and debugging. It works by scanning a list of source folders provided to it for agents and this setup right now requires that we provide a Java file such as following.

Agent.java

1
2
3
public class Agent {
    public static BaseAgent ROOT_AGENT = initAgent();
}

We’ve also created a gradle task to allow launching that dev UI.

1
2
3
4
5
6
7
tasks.register<JavaExec>("devUi") {
    group = "application"
    description = "Start the ADK Dev UI server"
    mainClass.set("com.google.adk.web.AdkWebServer")
    classpath = sourceSets["main"].runtimeClasspath
    args = listOf("--adk.agents.source-dir=src/main/java")
}

ADK Dev UI