Skip to main content

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): for com.google.adk:google-adk (RxJava3 Flowable<Event> streams, BaseLlm, BasePlugin).
  • dokimos-adk-kotlin (dev.dokimos.adk.kt): for com.google.adk:google-adk-kotlin-core-jvm (Kotlin Coroutines Flow<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 Kotlin suspend (String) -> String lambda into a Dokimos JudgeLM with AdkSupport.asJudge(...) (or .asJudge() in Kotlin).
  • Sync, async, and measured tasks: wrap any ADK model, Runner, or BaseAgent into a Dokimos Task, AsyncTask, or MeasuredTask (with token usage, latency, and PriceTable cost).
  • Agent trace & tool-call capture: fold ADK Event streams or Session histories into an AgentTrace with AdkSupport.toAgentTrace(...) (or .toAgentTrace(...) / suspend fun BaseAgent.runTrace(...) in Kotlin), or attach the passive AdkTraceCollector plugin to your Runner / InMemoryRunner / App to capture ToolCalls and full ToolDefinition schemas automatically.
  • RAG context extraction: AdkSupport.ragTask(...) and AdkSupport.extractTexts(...) pull retrieved context from Gemini GroundingMetadata chunks (retrievedContext, ragChunk, web) and retrieval tool FunctionResponses (VertexAiRagRetrieval or custom search tools).
  • Multi-turn conversations & simulation: convert ADK Sessions into ConversationTrajectory instances and adapt any Runner or BaseAgent as a ConversationalApplication for ConversationSimulator.

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.

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")
Third-party models (OpenAI, Anthropic, Ollama, etc.)

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 (LangChain4j wrapping any LangChain4j ChatModel such as OpenAiChatModel) or com.google.adk:google-adk-spring-ai, as well as built-in Claude.
  • ADK for Kotlin: via com.google.adk:google-adk-kotlin-integrations-openai-jvm (ChatCompletions for OpenAI and OpenAI-compatible APIs) or com.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):

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();

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.

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);

Evaluate tool-calling agents​

You can capture an AgentTrace from an ADK agent in two ways:

  1. Direct extraction (AdkSupport.toAgentTrace / .toAgentTrace(...) / suspend fun runTrace(...)): run a Runner or BaseAgent (or pass a Session or List<Event>) and convert its events into an AgentTrace.
  2. Passive plugin (AdkTraceCollector): register AdkTraceCollector on your Runner / InMemoryRunner / App, run your agent as usual, and read collector.trace() and collector.toolDefinitions() afterward.

Option 1: Direct trace extraction​

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);

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.

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();

Evaluate RAG pipelines​

ADK agents retrieve grounding context in two ways:

  1. Built-in Gemini Grounding / Vertex AI RAG, which populates Event.groundingMetadata (GroundingChunk -> retrievedContext.text, ragChunk.text, or web).
  2. Retrieval tools such as VertexAiRagRetrieval or custom search FunctionTools, which return chunks in FunctionResponse maps (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.

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();

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:

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();

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!

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);

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.

For AI agentsView as Markdown