Google ADK Integration
This page shows you how to evaluate agents built with the Google Agent Development Kit (ADK) for Java (com.google.adk:google-adk) and Google ADK for Kotlin (com.google.adk:google-adk-kotlin-core-jvm) using Dokimos.
Dokimos ships two dedicated modules:
dokimos-adk-java(dev.dokimos.adk): forcom.google.adk:google-adk(RxJava3Flowable<Event>streams,BaseLlm,BasePlugin).dokimos-adk-kotlin(dev.dokimos.adk.kt): forcom.google.adk:google-adk-kotlin-core-jvm(Kotlin CoroutinesFlow<Event>,Model,Plugin, suspend functions, and idiomatic Kotlin extension functions).
What you get
- One-line judge: turn an ADK model (
Gemini/BaseLlm/Model),Runner,BaseAgent(LlmAgent), or Kotlinsuspend (String) -> Stringlambda into a DokimosJudgeLMwithAdkSupport.asJudge(...)(or.asJudge()in Kotlin). - Sync, async, and measured tasks: wrap any ADK model,
Runner, orBaseAgentinto a DokimosTask,AsyncTask, orMeasuredTask(with token usage, latency, andPriceTablecost). - Agent trace & tool-call capture: fold ADK
Eventstreams orSessionhistories into anAgentTracewithAdkSupport.toAgentTrace(...)(or.toAgentTrace(...)/suspend fun BaseAgent.runTrace(...)in Kotlin), or attach the passiveAdkTraceCollectorplugin to yourRunner/InMemoryRunner/Appto captureToolCalls and fullToolDefinitionschemas automatically. - RAG context extraction:
AdkSupport.ragTask(...)andAdkSupport.extractTexts(...)pull retrieved context from GeminiGroundingMetadatachunks (retrievedContext,ragChunk,web) and retrieval toolFunctionResponses (VertexAiRagRetrievalor custom search tools). - Multi-turn conversations & simulation: convert ADK
Sessions intoConversationTrajectoryinstances and adapt anyRunnerorBaseAgentas aConversationalApplicationforConversationSimulator.
Setup
Add dokimos-adk-java (for ADK Java) or dokimos-adk-kotlin (for ADK Kotlin) to your project. The ADK artifacts are provided dependencies so you bring the ADK version your application already uses.
- Java (ADK for Java)
- Kotlin (ADK for Kotlin)
Maven
<dependency>
<groupId>dev.dokimos</groupId>
<artifactId>dokimos-adk-java</artifactId>
<version>${dokimos.version}</version>
</dependency>
<dependency>
<groupId>com.google.adk</groupId>
<artifactId>google-adk</artifactId>
<version>1.12.0</version>
</dependency>
Gradle
implementation("dev.dokimos:dokimos-adk-java:${dokimosVersion}")
implementation("com.google.adk:google-adk:1.12.0")
Maven
<dependency>
<groupId>dev.dokimos</groupId>
<artifactId>dokimos-adk-kotlin</artifactId>
<version>${dokimos.version}</version>
</dependency>
<dependency>
<groupId>com.google.adk</groupId>
<artifactId>google-adk-kotlin-core-jvm</artifactId>
<version>1.4.0</version>
</dependency>
Gradle (Kotlin DSL)
implementation("dev.dokimos:dokimos-adk-kotlin:${dokimosVersion}")
implementation("com.google.adk:google-adk-kotlin-core-jvm:1.4.0")
Because Dokimos operates on ADK's BaseLlm / Model, Runner, Event, and Plugin abstractions, agents backed by non-Gemini models work out of the box:
- ADK for Java: via
com.google.adk:google-adk-langchain4j(LangChain4jwrapping any LangChain4jChatModelsuch asOpenAiChatModel) orcom.google.adk:google-adk-spring-ai, as well as built-inClaude. - ADK for Kotlin: via
com.google.adk:google-adk-kotlin-integrations-openai-jvm(ChatCompletionsfor OpenAI and OpenAI-compatible APIs) orcom.google.adk:google-adk-kotlin-integrations-spring(SpringAi/SpringAiStreaming).
Use an ADK model or agent as a judge
Pass a BaseLlm / Model (e.g., Gemini), Runner, or BaseAgent to AdkSupport.asJudge(...) (or use .asJudge() in Kotlin):
- Java
- Kotlin
import com.google.adk.models.Gemini;
import dev.dokimos.adk.AdkSupport;
import dev.dokimos.core.EvalTestCaseParam;
import dev.dokimos.core.Evaluator;
import dev.dokimos.core.JudgeLM;
import dev.dokimos.core.evaluators.LLMJudgeEvaluator;
import java.util.List;
Gemini gemini = Gemini.builder()
.modelName("gemini-3.8-flash")
.build();
JudgeLM judge = AdkSupport.asJudge(gemini);
Evaluator correctness = LLMJudgeEvaluator.builder()
.name("Answer Correctness")
.criteria("Is the answer factually accurate and helpful?")
.evaluationParams(List.of(EvalTestCaseParam.INPUT, EvalTestCaseParam.ACTUAL_OUTPUT))
.judge(judge)
.threshold(0.8)
.build();
import com.google.adk.kt.models.Gemini
import dev.dokimos.adk.kt.asJudge
import dev.dokimos.kotlin.dsl.llmJudge
val gemini = Gemini(name = "gemini-3.8-flash")
val judge = gemini.asJudge()
val correctness = llmJudge(judge) {
name = "Answer Correctness"
criteria = "Is the answer factually accurate and helpful?"
threshold = 0.8
}
Evaluate Q&A and agent tasks
Use simpleTask when you only need the final text response under "output", or agentTask when your evaluators inspect "toolCalls" and "reasoningSteps" alongside "output". Each example executes in an isolated session.
- Java
- Kotlin
import com.google.adk.agents.LlmAgent;
import dev.dokimos.adk.AdkSupport;
import dev.dokimos.core.*;
LlmAgent agent = LlmAgent.builder()
.name("support_agent")
.model("gemini-3.8-flash")
.instruction("Answer customer questions concisely.")
.build();
// Simple text output under "output"
Task qaTask = AdkSupport.simpleTask(agent);
// Full AgentTrace output map ("output", "toolCalls", "reasoningSteps")
Task traceTask = AdkSupport.agentTask(agent);
// Reactive async variants backed by Runner.runAsync
AsyncTask asyncQaTask = AdkSupport.asyncTask(agent);
AsyncTask asyncTraceTask = AdkSupport.asyncAgentTask(agent);
import com.google.adk.kt.agents.Instruction
import com.google.adk.kt.agents.LlmAgent
import com.google.adk.kt.models.Gemini
import dev.dokimos.adk.kt.AdkSupport
val agent = LlmAgent(
name = "support_agent",
model = Gemini(name = "gemini-3.8-flash"),
instruction = Instruction("Answer customer questions concisely."),
)
// Simple text output under "output"
val qaTask = AdkSupport.simpleTask(agent)
// Full AgentTrace output map ("output", "toolCalls", "reasoningSteps")
val traceTask = AdkSupport.agentTask(agent)
// Coroutine-backed async variants bridging Flow<Event> to CompletableFuture
val asyncQaTask = AdkSupport.asyncTask(agent)
val asyncTraceTask = AdkSupport.asyncAgentTask(agent)
Evaluate tool-calling agents
You can capture an AgentTrace from an ADK agent in two ways:
- Direct extraction (
AdkSupport.toAgentTrace/.toAgentTrace(...)/suspend fun runTrace(...)): run aRunnerorBaseAgent(or pass aSessionorList<Event>) and convert its events into anAgentTrace. - Passive plugin (
AdkTraceCollector): registerAdkTraceCollectoron yourRunner/InMemoryRunner/App, run your agent as usual, and readcollector.trace()andcollector.toolDefinitions()afterward.
Option 1: Direct trace extraction
- Java
- Kotlin
import com.google.adk.agents.LlmAgent;
import com.google.adk.tools.FunctionTool;
import dev.dokimos.adk.AdkSupport;
import dev.dokimos.core.*;
import dev.dokimos.core.agents.*;
import dev.dokimos.core.evaluators.agents.*;
import java.util.List;
FunctionTool weatherTool = FunctionTool.create(WeatherTools.class, "getWeather");
LlmAgent agent = LlmAgent.builder()
.name("weather_agent")
.model("gemini-3.8-flash")
.instruction("Use get_weather to answer weather questions.")
.tools(weatherTool)
.build();
// Run the agent in an isolated session and extract the trace
AgentTrace trace = AdkSupport.toAgentTrace(agent, "What is the weather in Paris?");
// Extract ToolDefinitions (with full JSON Schema) from the agent's tools
List<ToolDefinition> tools = AdkSupport.toToolDefinitions(agent);
EvalTestCase testCase = trace.toTestCase("What is the weather in Paris?", tools);
EvalResult validity = ToolCallValidityEvaluator.builder().build().evaluate(testCase);
EvalResult efficiency = ToolEfficiencyEvaluator.builder().build().evaluate(testCase);
import com.google.adk.kt.agents.Instruction
import com.google.adk.kt.agents.LlmAgent
import com.google.adk.kt.models.Gemini
import dev.dokimos.adk.kt.toAgentTrace
import dev.dokimos.adk.kt.toToolDefinitions
import dev.dokimos.core.evaluators.agents.ToolCallValidityEvaluator
import dev.dokimos.core.evaluators.agents.ToolEfficiencyEvaluator
val agent = LlmAgent(
name = "weather_agent",
model = Gemini(name = "gemini-3.8-flash"),
instruction = Instruction("Use get_weather to answer weather questions."),
tools = listOf(weatherTool),
)
// Blocking helper (or use suspend `agent.runTrace(...)` inside a coroutine)
val trace = agent.toAgentTrace("What is the weather in Paris?")
val tools = agent.toToolDefinitions()
val testCase = trace.toTestCase("What is the weather in Paris?", tools)
val validity = ToolCallValidityEvaluator.builder().build().evaluate(testCase)
val efficiency = ToolEfficiencyEvaluator.builder().build().evaluate(testCase)
Option 2: Passive trace collection with AdkTraceCollector
AdkTraceCollector implements ADK's plugin interface (BasePlugin in Java, com.google.adk.kt.plugins.Plugin in Kotlin). Because ADK passes the live BaseTool instance to tool callbacks, collector.toolDefinitions() preserves each observed tool's full description and parameter JSON schema automatically.
- Java
- Kotlin
import com.google.adk.agents.RunConfig;
import com.google.adk.runner.Runner;
import com.google.genai.types.Content;
import com.google.genai.types.Part;
import dev.dokimos.adk.AdkSupport;
import dev.dokimos.adk.AdkTraceCollector;
import dev.dokimos.core.EvalTestCase;
import dev.dokimos.core.agents.AgentTrace;
import dev.dokimos.core.agents.ToolDefinition;
import java.util.List;
// 1. Attach AdkTraceCollector to your Runner.Builder
Runner.Builder builder = Runner.builder().agent(agent).appName("weather-app");
AdkTraceCollector collector = AdkSupport.attach(builder);
Runner runner = builder.build();
// 2. Run the agent as usual
String userQuery = "What is the weather in Paris?";
runner.runAsync(
"user-1",
"session-1",
Content.fromParts(Part.fromText(userQuery)),
RunConfig.builder().autoCreateSession(true).build())
.toList()
.blockingGet();
// 3. Read the trace and full tool definitions from the collector
AgentTrace trace = collector.trace();
List<ToolDefinition> tools = AdkSupport.toToolDefinitions(collector);
EvalTestCase testCase = trace.toTestCase(userQuery, tools);
// Call reset() before reusing the collector for another independent run
collector.reset();
import com.google.adk.kt.runners.InMemoryRunner
import com.google.adk.kt.types.Content
import com.google.adk.kt.types.Role
import dev.dokimos.adk.kt.AdkTraceCollector
import kotlinx.coroutines.flow.collect
// 1. Register AdkTraceCollector on InMemoryRunner (or via AdkSupport.attach(builder))
val collector = AdkTraceCollector()
val runner = InMemoryRunner(
agent = agent,
appName = "weather-app",
plugins = listOf(collector),
)
// 2. Run the agent as usual in a coroutine
val userQuery = "What is the weather in Paris?"
runner.runAsync(
userId = "user-1",
sessionId = "session-1",
newMessage = Content.fromText(Role.USER, userQuery),
).collect()
// 3. Read the trace and full tool definitions from the collector
val trace = collector.trace()
val tools = collector.toolDefinitions()
val testCase = trace.toTestCase(userQuery, tools)
// Call reset() before reusing the collector for another independent run
collector.reset()
Evaluate RAG pipelines
ADK agents retrieve grounding context in two ways:
- Built-in Gemini Grounding / Vertex AI RAG, which populates
Event.groundingMetadata(GroundingChunk->retrievedContext.text,ragChunk.text, orweb). - Retrieval tools such as
VertexAiRagRetrievalor custom searchFunctionTools, which return chunks inFunctionResponsemaps (under keys like"response","context","documents","chunks", or"results").
AdkSupport.ragTask(...) (and asyncRagTask / measuredRagTask) extracts context from both sources and writes it under "context" alongside "output", ready for FaithfulnessEvaluator and ContextualRelevancyEvaluator.
- Java
- Kotlin
import dev.dokimos.adk.AdkSupport;
import dev.dokimos.core.*;
import dev.dokimos.core.evaluators.ContextualRelevancyEvaluator;
import dev.dokimos.core.evaluators.FaithfulnessEvaluator;
import java.util.List;
// Extracts context from GroundingMetadata and/or retrieval tool responses
Task ragTask = AdkSupport.ragTask(ragAgent);
// Or restrict FunctionResponse context extraction to specific retrieval tool names
Task filteredRagTask = AdkSupport.ragTask(ragAgent, "search_policy");
JudgeLM judge = AdkSupport.asJudge(gemini);
ExperimentResult result = Experiment.builder()
.name("ADK RAG Evaluation")
.dataset(dataset)
.task(ragTask)
.evaluators(List.of(
FaithfulnessEvaluator.builder().judge(judge).threshold(0.8).build(),
ContextualRelevancyEvaluator.builder().judge(judge).threshold(0.7).build()
))
.build()
.run();
import dev.dokimos.adk.kt.AdkSupport
import dev.dokimos.adk.kt.asJudge
import dev.dokimos.kotlin.dsl.experiment
import dev.dokimos.kotlin.dsl.faithfulness
val ragTask = AdkSupport.ragTask(ragAgent)
val judge = gemini.asJudge()
val result = experiment {
name = "ADK Kotlin RAG Evaluation"
dataset(dataset)
task(ragTask)
evaluators {
faithfulness(judge) { threshold = 0.8 }
}
}.run()
Multi-turn conversations and simulation
Convert an ADK Session (or List<Event>) into a ConversationTrajectory with AdkSupport.toConversationTrajectory(...) (or session.toConversationTrajectory(...) in Kotlin), or adapt a Runner / BaseAgent as a ConversationalApplication to run multi-turn persona simulations with ConversationSimulator:
- Java
- Kotlin
import dev.dokimos.adk.AdkSupport;
import dev.dokimos.core.conversation.*;
// Adapt an ADK agent for multi-turn simulation
ConversationalApplication app = AdkSupport.asConversationalApp(agent);
SimulatedUser user = LLMSimulatedUser.builder()
.judge(judge)
.persona("An impatient traveler asking about flight delays")
.scenario("Resolve a delayed flight rebooking")
.build();
ConversationTrajectory trajectory = ConversationSimulator.builder()
.application(app)
.simulatedUser(user)
.maxTurns(4)
.build()
.simulate();
import dev.dokimos.adk.kt.asConversationalApp
import dev.dokimos.core.conversation.ConversationSimulator
import dev.dokimos.core.conversation.LLMSimulatedUser
val app = agent.asConversationalApp()
val user = LLMSimulatedUser.builder()
.judge(judge)
.persona("An impatient traveler asking about flight delays")
.scenario("Resolve a delayed flight rebooking")
.build()
val trajectory = ConversationSimulator.builder()
.application(app)
.simulatedUser(user)
.maxTurns(4)
.build()
.simulate()
Cost, tokens, and latency
ADK attaches UsageMetadata (promptTokenCount, candidatesTokenCount, and thoughtsTokenCount) to each model Event. AdkSupport.measuredTask, measuredAgentTask, measuredRagTask, and AdkTraceCollector.callMetrics sum token counts across all non-partial events in a run (tokensOut = candidatesTokenCount + thoughtsTokenCount), measure wall-clock latency, and compute USD cost via a PriceTable.
In dokimos-adk-kotlin, LlmAgent.model.name is exposed publicly so modelId is automatically inferred when omitted!
- Java
- Kotlin
import dev.dokimos.adk.AdkSupport;
import dev.dokimos.core.MeasuredTask;
import dev.dokimos.core.PriceTable;
PriceTable prices = (model, tokensIn, tokensOut) ->
(tokensIn * 0.00015 / 1000.0) + (tokensOut * 0.00060 / 1000.0);
MeasuredTask task = AdkSupport.measuredAgentTask(agent, "gemini-3.8-flash", prices);
import dev.dokimos.adk.kt.AdkSupport
import dev.dokimos.core.PriceTable
val prices = PriceTable { _, tokensIn, tokensOut ->
(tokensIn * 0.00015 / 1000.0) + (tokensOut * 0.00060 / 1000.0)
}
// modelId is inferred automatically from agent.model.name ("gemini-3.8-flash")
val task = AdkSupport.measuredAgentTask(agent, priceTable = prices)
See Cost and Pricing for details on PriceTable and CallMetrics.
Complete examples
See AdkAgentEvaluationExample.java and AdkKotlinAgentEvaluationExample.kt in dokimos-examples for self-contained runnable examples demonstrating AdkTraceCollector, tool-call evaluation, and measuredAgentTask / asyncAgentTask.