From 9ebdec69bc5cc78b14a3e0e2df17e9a86405932f Mon Sep 17 00:00:00 2001 From: YuqiGuo105 Date: Thu, 26 Feb 2026 23:06:38 -0700 Subject: [PATCH 01/23] fix: memory leak in InMemorySessionService.deleteSession() - clean up empty parent maps When deleteSession() removes the last session for a userId, the now-empty userId map remained in the parent appName map. Similarly, if all users under an appName were deleted, the empty appName map remained in the top-level sessions map. This caused linear memory growth with the number of unique users/apps, eventually leading to OutOfMemoryError. Replace the manual get+remove pattern with computeIfPresent() calls that atomically remove parent map entries when they become empty. When the remapping function returns null, ConcurrentHashMap automatically removes the key - this is thread-safe without additional locking. Before: sessions = { appName: { userId: {} } } <-- empty maps leak After: sessions = {} <-- fully cleaned up Add two new tests: - deleteSession_cleansUpEmptyParentMaps: verifies via reflection that the internal sessions map is completely empty after deleting the only session - deleteSession_doesNotRemoveUserMapWhenOtherSessionsExist: verifies that a userId entry is NOT pruned when the user still has remaining sessions Fixes #687 --- .../adk/sessions/InMemorySessionService.java | 17 ++++--- .../sessions/InMemorySessionServiceTest.java | 48 +++++++++++++++++++ 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/com/google/adk/sessions/InMemorySessionService.java b/core/src/main/java/com/google/adk/sessions/InMemorySessionService.java index b2a584b11..345811633 100644 --- a/core/src/main/java/com/google/adk/sessions/InMemorySessionService.java +++ b/core/src/main/java/com/google/adk/sessions/InMemorySessionService.java @@ -192,13 +192,16 @@ public Completable deleteSession(String appName, String userId, String sessionId Objects.requireNonNull(userId, "userId cannot be null"); Objects.requireNonNull(sessionId, "sessionId cannot be null"); - ConcurrentMap> appSessionsMap = sessions.get(appName); - if (appSessionsMap != null) { - ConcurrentMap userSessionsMap = appSessionsMap.get(userId); - if (userSessionsMap != null) { - userSessionsMap.remove(sessionId); - } - } + sessions.computeIfPresent(appName, (app, appSessionsMap) -> { + appSessionsMap.computeIfPresent(userId, (user, userSessionsMap) -> { + userSessionsMap.remove(sessionId); + // If userSessionsMap is now empty, return null to automatically remove the userId key + return userSessionsMap.isEmpty() ? null : userSessionsMap; + }); + // If appSessionsMap is now empty, return null to automatically remove the appName key + return appSessionsMap.isEmpty() ? null : appSessionsMap; + }); + return Completable.complete(); } diff --git a/core/src/test/java/com/google/adk/sessions/InMemorySessionServiceTest.java b/core/src/test/java/com/google/adk/sessions/InMemorySessionServiceTest.java index 41e156ffd..53380d072 100644 --- a/core/src/test/java/com/google/adk/sessions/InMemorySessionServiceTest.java +++ b/core/src/test/java/com/google/adk/sessions/InMemorySessionServiceTest.java @@ -20,6 +20,7 @@ import com.google.adk.events.Event; import com.google.adk.events.EventActions; import io.reactivex.rxjava3.core.Single; +import java.lang.reflect.Field; import java.util.HashMap; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; @@ -247,4 +248,51 @@ public void sequentialAgents_shareTempState() { assertThat(retrievedSession.state()).doesNotContainKey("temp:agent1_output"); assertThat(retrievedSession.state()).containsEntry("temp:agent2_output", "processed_data"); } + + @Test + public void deleteSession_cleansUpEmptyParentMaps() throws Exception { + InMemorySessionService sessionService = new InMemorySessionService(); + + Session session = sessionService.createSession("app-name", "user-id").blockingGet(); + + sessionService.deleteSession(session.appName(), session.userId(), session.id()).blockingAwait(); + + // Use reflection to access the private 'sessions' field + Field field = InMemorySessionService.class.getDeclaredField("sessions"); + field.setAccessible(true); + ConcurrentMap sessions = (ConcurrentMap) field.get(sessionService); + + // After deleting the only session for "user-id" under "app-name", + // both the userId map and the appName map should have been removed + assertThat(sessions).isEmpty(); + } + + @Test + public void deleteSession_doesNotRemoveUserMapWhenOtherSessionsExist() throws Exception { + InMemorySessionService sessionService = new InMemorySessionService(); + + Session session1 = sessionService.createSession("app-name", "user-id").blockingGet(); + Session session2 = sessionService.createSession("app-name", "user-id").blockingGet(); + + // Delete only one of the two sessions + sessionService.deleteSession(session1.appName(), session1.userId(), session1.id()).blockingAwait(); + + // session2 should still be retrievable + assertThat( + sessionService + .getSession(session2.appName(), session2.userId(), session2.id(), Optional.empty()) + .blockingGet()) + .isNotNull(); + + // The userId entry should still exist (not pruned) because session2 remains + Field field = InMemorySessionService.class.getDeclaredField("sessions"); + field.setAccessible(true); + @SuppressWarnings("unchecked") + ConcurrentMap>> sessions = + (ConcurrentMap>>) field.get(sessionService); + + assertThat(sessions.get("app-name")).isNotNull(); + assertThat(sessions.get("app-name").get("user-id")).isNotNull(); + assertThat(sessions.get("app-name").get("user-id")).hasSize(1); + } } From 6bb721b9a6000dac9dfda498fb6dd2c45862e25c Mon Sep 17 00:00:00 2001 From: Google Team Member Date: Fri, 13 Feb 2026 10:41:15 -0800 Subject: [PATCH 02/23] docs: add pull request template PiperOrigin-RevId: 869797377 --- .github/pull_request_template.md | 54 ++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..0159bac4d --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,54 @@ +**Please ensure you have read the [contribution guide](./CONTRIBUTING.md) before creating a pull request.** + +### Link to Issue or Description of Change + +**1. Link to an existing issue (if applicable):** + +- Closes: #_issue_number_ +- Related: #_issue_number_ + +**2. Or, if no issue exists, describe the change:** + +_If applicable, please follow the issue templates to provide as much detail as +possible._ + +**Problem:** +_A clear and concise description of what the problem is._ + +**Solution:** +_A clear and concise description of what you want to happen and why you choose +this solution._ + +### Testing Plan + +_Please describe the tests that you ran to verify your changes. This is required +for all PRs that are not small documentation or typo fixes._ + +**Unit Tests:** + +- [ ] I have added or updated unit tests for my change. +- [ ] All unit tests pass locally. + +_Please include a summary of passed java test results._ + +**Manual End-to-End (E2E) Tests:** + +_Please provide instructions on how to manually test your changes, including any +necessary setup or configuration. Please provide logs or screenshots to help +reviewers better understand the fix._ + +### Checklist + +- [ ] I have read the [CONTRIBUTING.md](./CONTRIBUTING.md) document. +- [ ] My pull request contains a single commit. +- [ ] I have performed a self-review of my own code. +- [ ] I have commented my code, particularly in hard-to-understand areas. +- [ ] I have added tests that prove my fix is effective or that my feature works. +- [ ] New and existing unit tests pass locally with my changes. +- [ ] I have manually tested my changes end-to-end. +- [ ] Any dependent changes have been merged and published in downstream modules. + +### Additional context + +_Add any other context or screenshots about the feature request here._ + From d02d25c93dccc359c2ebfa2ecf9651623c28f073 Mon Sep 17 00:00:00 2001 From: yongkiy-google Date: Wed, 25 Mar 2026 15:46:03 +0100 Subject: [PATCH 03/23] Update InMemorySessionService.java --- .../adk/sessions/InMemorySessionService.java | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/com/google/adk/sessions/InMemorySessionService.java b/core/src/main/java/com/google/adk/sessions/InMemorySessionService.java index 3519fe0f6..3e96f959e 100644 --- a/core/src/main/java/com/google/adk/sessions/InMemorySessionService.java +++ b/core/src/main/java/com/google/adk/sessions/InMemorySessionService.java @@ -186,16 +186,21 @@ public Completable deleteSession(String appName, String userId, String sessionId Objects.requireNonNull(userId, "userId cannot be null"); Objects.requireNonNull(sessionId, "sessionId cannot be null"); - sessions.computeIfPresent(appName, (app, appSessionsMap) -> { - appSessionsMap.computeIfPresent(userId, (user, userSessionsMap) -> { - userSessionsMap.remove(sessionId); - // If userSessionsMap is now empty, return null to automatically remove the userId key - return userSessionsMap.isEmpty() ? null : userSessionsMap; + sessions.computeIfPresent( + appName, + (app, appSessionsMap) -> { + appSessionsMap.computeIfPresent( + userId, + (user, userSessionsMap) -> { + userSessionsMap.remove(sessionId); + // If userSessionsMap is now empty, return null to automatically remove the userId + // key + return userSessionsMap.isEmpty() ? null : userSessionsMap; + }); + // If appSessionsMap is now empty, return null to automatically remove the appName key + return appSessionsMap.isEmpty() ? null : appSessionsMap; }); - // If appSessionsMap is now empty, return null to automatically remove the appName key - return appSessionsMap.isEmpty() ? null : appSessionsMap; - }); - + return Completable.complete(); } From 316702ef323dc924a54b3168de3d4f7fc3b6d31d Mon Sep 17 00:00:00 2001 From: yongkiy-google Date: Wed, 25 Mar 2026 15:48:02 +0100 Subject: [PATCH 04/23] Update InMemorySessionServiceTest.java --- .../google/adk/sessions/InMemorySessionServiceTest.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/com/google/adk/sessions/InMemorySessionServiceTest.java b/core/src/test/java/com/google/adk/sessions/InMemorySessionServiceTest.java index aa763b691..c260f6695 100644 --- a/core/src/test/java/com/google/adk/sessions/InMemorySessionServiceTest.java +++ b/core/src/test/java/com/google/adk/sessions/InMemorySessionServiceTest.java @@ -274,7 +274,9 @@ public void deleteSession_cleansUpEmptyParentMaps() throws Exception { Session session = sessionService.createSession("app-name", "user-id").blockingGet(); - sessionService.deleteSession(session.appName(), session.userId(), session.id()).blockingAwait(); + sessionService + .deleteSession(session1.appName(), session1.userId(), session1.id()) + .blockingAwait(); // Use reflection to access the private 'sessions' field Field field = InMemorySessionService.class.getDeclaredField("sessions"); @@ -308,7 +310,8 @@ public void deleteSession_doesNotRemoveUserMapWhenOtherSessionsExist() throws Ex field.setAccessible(true); @SuppressWarnings("unchecked") ConcurrentMap>> sessions = - (ConcurrentMap>>) field.get(sessionService); + (ConcurrentMap>>) + field.get(sessionService); assertThat(sessions.get("app-name")).isNotNull(); assertThat(sessions.get("app-name").get("user-id")).isNotNull(); From d1e05caf524b7cafb3f321550659296ea70d9286 Mon Sep 17 00:00:00 2001 From: Google Team Member Date: Wed, 25 Mar 2026 09:17:26 -0700 Subject: [PATCH 05/23] fix: Ensure callbackContextData is preserved across session update PiperOrigin-RevId: 889281595 --- .../java/com/google/adk/runner/Runner.java | 39 ++++++++++--------- .../com/google/adk/runner/RunnerTest.java | 23 +++++++++++ 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/com/google/adk/runner/Runner.java b/core/src/main/java/com/google/adk/runner/Runner.java index 2bfbca881..82f342a81 100644 --- a/core/src/main/java/com/google/adk/runner/Runner.java +++ b/core/src/main/java/com/google/adk/runner/Runner.java @@ -477,13 +477,8 @@ protected Flowable runAsyncImpl( session.appName(), session.userId(), session.id(), Optional.empty()) .flatMapPublisher( updatedSession -> - runAgentWithFreshSession( - session, - updatedSession, - event, - invocationId, - runConfig, - rootAgent)) + runAgentWithUpdatedSession( + initialContext, updatedSession, event, rootAgent)) .compose(Tracing.withContext(capturedContext)); }); }) @@ -495,19 +490,27 @@ protected Flowable runAsyncImpl( }); } - private Flowable runAgentWithFreshSession( - Session session, - Session updatedSession, - Event event, - String invocationId, - RunConfig runConfig, - BaseAgent rootAgent) { + /** + * Runs the agent with the updated session state. + * + *

This method is called after the user message has been persistent in the session. It creates + * a final {@link InvocationContext} that inherits state from the {@code initialContext} but uses + * the {@code updatedSession} to ensure the agent can access the latest conversation history. + * + * @param initialContext the context from the start of the invocation, used to preserve metadata + * and callback data. + * @param updatedSession the session object containing the latest message. + * @param event the event representing the user message that was just appended. + * @param rootAgent the agent to be executed. + * @return a stream of events from the agent execution and subsequent plugin callbacks. + */ + private Flowable runAgentWithUpdatedSession( + InvocationContext initialContext, Session updatedSession, Event event, BaseAgent rootAgent) { // Create context with updated session for beforeRunCallback InvocationContext contextWithUpdatedSession = - newInvocationContextBuilder(updatedSession) - .invocationId(invocationId) + initialContext.toBuilder() + .session(updatedSession) .agent(this.findAgentToRun(updatedSession, rootAgent)) - .runConfig(runConfig) .userContent(event.content().orElseGet(Content::fromParts)) .build(); @@ -536,7 +539,7 @@ private Flowable runAgentWithFreshSession( .flatMap( registeredEvent -> { // TODO: remove this hack after deprecating runAsync with Session. - copySessionStates(updatedSession, session); + copySessionStates(updatedSession, initialContext.session()); return contextWithUpdatedSession .pluginManager() .onEventCallback(contextWithUpdatedSession, registeredEvent) diff --git a/core/src/test/java/com/google/adk/runner/RunnerTest.java b/core/src/test/java/com/google/adk/runner/RunnerTest.java index b68b6ff5f..36530faf2 100644 --- a/core/src/test/java/com/google/adk/runner/RunnerTest.java +++ b/core/src/test/java/com/google/adk/runner/RunnerTest.java @@ -591,6 +591,29 @@ public void onEventCallback_success() { verify(plugin).onEventCallback(any(), any()); } + @Test + public void callbackContextData_preservedAcrossInvocation() { + String testKey = "testKey"; + String testValue = "testValue"; + + when(plugin.onUserMessageCallback(any(), any())) + .thenAnswer( + invocation -> { + InvocationContext context = invocation.getArgument(0); + context.callbackContextData().put(testKey, testValue); + return Maybe.empty(); + }); + + ArgumentCaptor contextCaptor = + ArgumentCaptor.forClass(InvocationContext.class); + when(plugin.afterRunCallback(contextCaptor.capture())).thenReturn(Completable.complete()); + + var unused = + runner.runAsync("user", session.id(), createContent("test")).toList().blockingGet(); + + assertThat(contextCaptor.getValue().callbackContextData()).containsEntry(testKey, testValue); + } + @Test public void runAsync_withSessionKey_success() { var events = From 678b49653fa93606e2e57926213f0facaf9d6666 Mon Sep 17 00:00:00 2001 From: Maciej Szwaja Date: Wed, 25 Mar 2026 09:18:31 -0700 Subject: [PATCH 06/23] chore: set version to 1.0.0-rc.2 Release-As: 1.0.0-rc.2 PiperOrigin-RevId: 889282143 --- .release-please-manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 802e9d13f..bcb8fe618 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,4 @@ { ".": "1.0.0-rc.1" } + From b06453912ac36ba6c7bb91d3130dd50a9a4c3bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Sobczyk?= Date: Thu, 26 Mar 2026 03:10:48 -0700 Subject: [PATCH 07/23] ADK changes PiperOrigin-RevId: 889726165 --- .../main/java/com/google/adk/agents/ActiveStreamingTool.java | 2 +- .../main/java/com/google/adk/agents/ConfigAgentUtils.java | 2 +- .../main/java/com/google/adk/agents/InvocationContext.java | 2 +- core/src/main/java/com/google/adk/agents/LiveRequest.java | 2 +- core/src/main/java/com/google/adk/agents/LlmAgent.java | 2 +- core/src/main/java/com/google/adk/agents/RunConfig.java | 2 +- core/src/main/java/com/google/adk/agents/ToolResolver.java | 2 +- core/src/main/java/com/google/adk/apps/App.java | 2 +- core/src/main/java/com/google/adk/events/Event.java | 2 +- .../main/java/com/google/adk/events/ToolConfirmation.java | 2 +- .../main/java/com/google/adk/flows/llmflows/Contents.java | 2 +- core/src/main/java/com/google/adk/memory/MemoryEntry.java | 2 +- core/src/main/java/com/google/adk/models/LlmResponse.java | 2 +- .../main/java/com/google/adk/models/VertexCredentials.java | 2 +- .../adk/plugins/agentanalytics/BigQueryLoggerConfig.java | 2 +- core/src/main/java/com/google/adk/runner/Runner.java | 2 +- .../java/com/google/adk/sessions/BaseSessionService.java | 2 +- .../java/com/google/adk/sessions/SessionJsonConverter.java | 2 +- core/src/main/java/com/google/adk/sessions/State.java | 2 +- .../main/java/com/google/adk/sessions/VertexAiClient.java | 2 +- .../com/google/adk/summarizer/EventsCompactionConfig.java | 2 +- core/src/main/java/com/google/adk/tools/BaseToolset.java | 2 +- core/src/main/java/com/google/adk/tools/FunctionTool.java | 5 ++--- .../java/com/google/adk/tools/LongRunningFunctionTool.java | 2 +- core/src/main/java/com/google/adk/tools/ToolContext.java | 2 +- core/src/main/java/com/google/adk/tools/ToolPredicate.java | 2 +- .../applicationintegrationtoolset/CredentialsHelper.java | 2 +- .../GoogleCredentialsHelper.java | 2 +- .../main/java/com/google/adk/tools/mcp/McpAsyncToolset.java | 2 +- .../java/com/google/adk/tools/mcp/SseServerParameters.java | 2 +- .../java/com/google/adk/tools/mcp/StdioServerParameters.java | 2 +- .../google/adk/tools/mcp/StreamableHttpServerParameters.java | 2 +- .../com/google/adk/tools/retrieval/VertexAiRagRetrieval.java | 2 +- 33 files changed, 34 insertions(+), 35 deletions(-) diff --git a/core/src/main/java/com/google/adk/agents/ActiveStreamingTool.java b/core/src/main/java/com/google/adk/agents/ActiveStreamingTool.java index 78e7a471e..31b64e9c6 100644 --- a/core/src/main/java/com/google/adk/agents/ActiveStreamingTool.java +++ b/core/src/main/java/com/google/adk/agents/ActiveStreamingTool.java @@ -18,7 +18,7 @@ package com.google.adk.agents; import io.reactivex.rxjava3.disposables.Disposable; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Manages streaming tool related resources during invocation. */ public class ActiveStreamingTool { diff --git a/core/src/main/java/com/google/adk/agents/ConfigAgentUtils.java b/core/src/main/java/com/google/adk/agents/ConfigAgentUtils.java index c489c9b0d..893353f27 100644 --- a/core/src/main/java/com/google/adk/agents/ConfigAgentUtils.java +++ b/core/src/main/java/com/google/adk/agents/ConfigAgentUtils.java @@ -35,7 +35,7 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/core/src/main/java/com/google/adk/agents/InvocationContext.java b/core/src/main/java/com/google/adk/agents/InvocationContext.java index 365f4f8c1..f3a3cf3b0 100644 --- a/core/src/main/java/com/google/adk/agents/InvocationContext.java +++ b/core/src/main/java/com/google/adk/agents/InvocationContext.java @@ -33,7 +33,7 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** The context for an agent invocation. */ public class InvocationContext { diff --git a/core/src/main/java/com/google/adk/agents/LiveRequest.java b/core/src/main/java/com/google/adk/agents/LiveRequest.java index df1ada6a5..416ffa794 100644 --- a/core/src/main/java/com/google/adk/agents/LiveRequest.java +++ b/core/src/main/java/com/google/adk/agents/LiveRequest.java @@ -26,7 +26,7 @@ import com.google.genai.types.Blob; import com.google.genai.types.Content; import java.util.Optional; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Represents a request to be sent to a live connection to the LLM model. */ @AutoValue diff --git a/core/src/main/java/com/google/adk/agents/LlmAgent.java b/core/src/main/java/com/google/adk/agents/LlmAgent.java index 077068283..c8199e8a2 100644 --- a/core/src/main/java/com/google/adk/agents/LlmAgent.java +++ b/core/src/main/java/com/google/adk/agents/LlmAgent.java @@ -71,7 +71,7 @@ import java.util.Optional; import java.util.concurrent.Executor; import java.util.function.Function; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/core/src/main/java/com/google/adk/agents/RunConfig.java b/core/src/main/java/com/google/adk/agents/RunConfig.java index 1ca203eaf..661a5cddf 100644 --- a/core/src/main/java/com/google/adk/agents/RunConfig.java +++ b/core/src/main/java/com/google/adk/agents/RunConfig.java @@ -22,7 +22,7 @@ import com.google.genai.types.AudioTranscriptionConfig; import com.google.genai.types.Modality; import com.google.genai.types.SpeechConfig; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/core/src/main/java/com/google/adk/agents/ToolResolver.java b/core/src/main/java/com/google/adk/agents/ToolResolver.java index b48b25310..09a3d79c1 100644 --- a/core/src/main/java/com/google/adk/agents/ToolResolver.java +++ b/core/src/main/java/com/google/adk/agents/ToolResolver.java @@ -32,7 +32,7 @@ import java.lang.reflect.Modifier; import java.util.List; import java.util.Optional; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/core/src/main/java/com/google/adk/apps/App.java b/core/src/main/java/com/google/adk/apps/App.java index a087b738c..689e8c3b1 100644 --- a/core/src/main/java/com/google/adk/apps/App.java +++ b/core/src/main/java/com/google/adk/apps/App.java @@ -24,7 +24,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.List; import java.util.regex.Pattern; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Represents an LLM-backed agentic application. diff --git a/core/src/main/java/com/google/adk/events/Event.java b/core/src/main/java/com/google/adk/events/Event.java index 2677b635d..454cd591e 100644 --- a/core/src/main/java/com/google/adk/events/Event.java +++ b/core/src/main/java/com/google/adk/events/Event.java @@ -39,7 +39,7 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; // TODO - b/413761119 update Agent.java when resolved. /** Represents an event in a session. */ diff --git a/core/src/main/java/com/google/adk/events/ToolConfirmation.java b/core/src/main/java/com/google/adk/events/ToolConfirmation.java index 167286094..ae0104aa1 100644 --- a/core/src/main/java/com/google/adk/events/ToolConfirmation.java +++ b/core/src/main/java/com/google/adk/events/ToolConfirmation.java @@ -21,7 +21,7 @@ import com.google.adk.JsonBaseModel; import com.google.auto.value.AutoValue; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Represents a tool confirmation configuration. */ @AutoValue diff --git a/core/src/main/java/com/google/adk/flows/llmflows/Contents.java b/core/src/main/java/com/google/adk/flows/llmflows/Contents.java index 840a370c6..876f3a206 100644 --- a/core/src/main/java/com/google/adk/flows/llmflows/Contents.java +++ b/core/src/main/java/com/google/adk/flows/llmflows/Contents.java @@ -42,7 +42,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** {@link RequestProcessor} that populates content in request for LLM flows. */ public final class Contents implements RequestProcessor { diff --git a/core/src/main/java/com/google/adk/memory/MemoryEntry.java b/core/src/main/java/com/google/adk/memory/MemoryEntry.java index ef310f627..65f1dadc3 100644 --- a/core/src/main/java/com/google/adk/memory/MemoryEntry.java +++ b/core/src/main/java/com/google/adk/memory/MemoryEntry.java @@ -22,7 +22,7 @@ import com.google.auto.value.AutoValue; import com.google.genai.types.Content; import java.time.Instant; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Represents one memory entry. */ @AutoValue diff --git a/core/src/main/java/com/google/adk/models/LlmResponse.java b/core/src/main/java/com/google/adk/models/LlmResponse.java index 1ca381e23..560e2abcc 100644 --- a/core/src/main/java/com/google/adk/models/LlmResponse.java +++ b/core/src/main/java/com/google/adk/models/LlmResponse.java @@ -33,7 +33,7 @@ import com.google.genai.types.GroundingMetadata; import java.util.List; import java.util.Optional; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Represents a response received from the LLM. */ @AutoValue diff --git a/core/src/main/java/com/google/adk/models/VertexCredentials.java b/core/src/main/java/com/google/adk/models/VertexCredentials.java index 93dc05ec1..0ae81c2a4 100644 --- a/core/src/main/java/com/google/adk/models/VertexCredentials.java +++ b/core/src/main/java/com/google/adk/models/VertexCredentials.java @@ -19,7 +19,7 @@ import com.google.auth.oauth2.GoogleCredentials; import com.google.auto.value.AutoValue; import java.util.Optional; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Credentials for accessing Gemini models through Vertex. */ @AutoValue diff --git a/core/src/main/java/com/google/adk/plugins/agentanalytics/BigQueryLoggerConfig.java b/core/src/main/java/com/google/adk/plugins/agentanalytics/BigQueryLoggerConfig.java index aa5bf37de..a7bae007f 100644 --- a/core/src/main/java/com/google/adk/plugins/agentanalytics/BigQueryLoggerConfig.java +++ b/core/src/main/java/com/google/adk/plugins/agentanalytics/BigQueryLoggerConfig.java @@ -25,7 +25,7 @@ import java.util.Map; import java.util.Optional; import java.util.function.BiFunction; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Configuration for the BigQueryAgentAnalyticsPlugin. */ @AutoValue diff --git a/core/src/main/java/com/google/adk/runner/Runner.java b/core/src/main/java/com/google/adk/runner/Runner.java index 82f342a81..120465f31 100644 --- a/core/src/main/java/com/google/adk/runner/Runner.java +++ b/core/src/main/java/com/google/adk/runner/Runner.java @@ -64,7 +64,7 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** The main class for the GenAI Agents runner. */ public class Runner { diff --git a/core/src/main/java/com/google/adk/sessions/BaseSessionService.java b/core/src/main/java/com/google/adk/sessions/BaseSessionService.java index 7a0885544..8596f8eb6 100644 --- a/core/src/main/java/com/google/adk/sessions/BaseSessionService.java +++ b/core/src/main/java/com/google/adk/sessions/BaseSessionService.java @@ -28,7 +28,7 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Defines the contract for managing {@link Session}s and their associated {@link Event}s. Provides diff --git a/core/src/main/java/com/google/adk/sessions/SessionJsonConverter.java b/core/src/main/java/com/google/adk/sessions/SessionJsonConverter.java index 0c2b33704..adc84fbb6 100644 --- a/core/src/main/java/com/google/adk/sessions/SessionJsonConverter.java +++ b/core/src/main/java/com/google/adk/sessions/SessionJsonConverter.java @@ -39,7 +39,7 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/core/src/main/java/com/google/adk/sessions/State.java b/core/src/main/java/com/google/adk/sessions/State.java index 70d2dfbf2..577559f85 100644 --- a/core/src/main/java/com/google/adk/sessions/State.java +++ b/core/src/main/java/com/google/adk/sessions/State.java @@ -24,7 +24,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** A {@link State} object that also keeps track of the changes to the state. */ @SuppressWarnings("ShouldNotSubclass") diff --git a/core/src/main/java/com/google/adk/sessions/VertexAiClient.java b/core/src/main/java/com/google/adk/sessions/VertexAiClient.java index 1168d1166..2d8a13f2f 100644 --- a/core/src/main/java/com/google/adk/sessions/VertexAiClient.java +++ b/core/src/main/java/com/google/adk/sessions/VertexAiClient.java @@ -18,8 +18,8 @@ import java.util.List; import java.util.Map; import java.util.concurrent.TimeoutException; -import javax.annotation.Nullable; import okhttp3.ResponseBody; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/core/src/main/java/com/google/adk/summarizer/EventsCompactionConfig.java b/core/src/main/java/com/google/adk/summarizer/EventsCompactionConfig.java index 39698c3db..db462466c 100644 --- a/core/src/main/java/com/google/adk/summarizer/EventsCompactionConfig.java +++ b/core/src/main/java/com/google/adk/summarizer/EventsCompactionConfig.java @@ -18,7 +18,7 @@ import com.google.auto.value.AutoBuilder; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Configuration for event compaction. diff --git a/core/src/main/java/com/google/adk/tools/BaseToolset.java b/core/src/main/java/com/google/adk/tools/BaseToolset.java index 40167aa75..76369e5b9 100644 --- a/core/src/main/java/com/google/adk/tools/BaseToolset.java +++ b/core/src/main/java/com/google/adk/tools/BaseToolset.java @@ -19,7 +19,7 @@ import com.google.adk.agents.ReadonlyContext; import io.reactivex.rxjava3.core.Flowable; import java.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Base interface for toolsets. */ public interface BaseToolset extends AutoCloseable { diff --git a/core/src/main/java/com/google/adk/tools/FunctionTool.java b/core/src/main/java/com/google/adk/tools/FunctionTool.java index 4323b4569..ea67d40d4 100644 --- a/core/src/main/java/com/google/adk/tools/FunctionTool.java +++ b/core/src/main/java/com/google/adk/tools/FunctionTool.java @@ -36,7 +36,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -222,8 +222,7 @@ public Method func() { } /** Returns the underlying function's {@link Object} instance if present. */ - @Nullable - Object instance() { + @Nullable Object instance() { return instance; } diff --git a/core/src/main/java/com/google/adk/tools/LongRunningFunctionTool.java b/core/src/main/java/com/google/adk/tools/LongRunningFunctionTool.java index 68b7c242b..23733c4eb 100644 --- a/core/src/main/java/com/google/adk/tools/LongRunningFunctionTool.java +++ b/core/src/main/java/com/google/adk/tools/LongRunningFunctionTool.java @@ -19,7 +19,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.google.adk.utils.ComponentRegistry; import java.lang.reflect.Method; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** A function tool that returns the result asynchronously. */ public class LongRunningFunctionTool extends FunctionTool { diff --git a/core/src/main/java/com/google/adk/tools/ToolContext.java b/core/src/main/java/com/google/adk/tools/ToolContext.java index 5192d19ff..974d1f017 100644 --- a/core/src/main/java/com/google/adk/tools/ToolContext.java +++ b/core/src/main/java/com/google/adk/tools/ToolContext.java @@ -24,7 +24,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.reactivex.rxjava3.core.Single; import java.util.Optional; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** ToolContext object provides a structured context for executing tools or functions. */ public class ToolContext extends CallbackContext { diff --git a/core/src/main/java/com/google/adk/tools/ToolPredicate.java b/core/src/main/java/com/google/adk/tools/ToolPredicate.java index 6adf53c18..5c8dd9893 100644 --- a/core/src/main/java/com/google/adk/tools/ToolPredicate.java +++ b/core/src/main/java/com/google/adk/tools/ToolPredicate.java @@ -18,7 +18,7 @@ import com.google.adk.agents.ReadonlyContext; import java.util.Optional; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Functional interface to decide whether a tool should be exposed to the LLM based on the current diff --git a/core/src/main/java/com/google/adk/tools/applicationintegrationtoolset/CredentialsHelper.java b/core/src/main/java/com/google/adk/tools/applicationintegrationtoolset/CredentialsHelper.java index 60bf6fc8d..c6c26d0af 100644 --- a/core/src/main/java/com/google/adk/tools/applicationintegrationtoolset/CredentialsHelper.java +++ b/core/src/main/java/com/google/adk/tools/applicationintegrationtoolset/CredentialsHelper.java @@ -5,7 +5,7 @@ import java.net.http.HttpRequest; import java.util.List; import java.util.Map; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * This interface provides a method to convert a service account JSON string to a Google Credentials diff --git a/core/src/main/java/com/google/adk/tools/applicationintegrationtoolset/GoogleCredentialsHelper.java b/core/src/main/java/com/google/adk/tools/applicationintegrationtoolset/GoogleCredentialsHelper.java index ca036d21b..c78d91159 100644 --- a/core/src/main/java/com/google/adk/tools/applicationintegrationtoolset/GoogleCredentialsHelper.java +++ b/core/src/main/java/com/google/adk/tools/applicationintegrationtoolset/GoogleCredentialsHelper.java @@ -7,7 +7,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; public final class GoogleCredentialsHelper implements CredentialsHelper { diff --git a/core/src/main/java/com/google/adk/tools/mcp/McpAsyncToolset.java b/core/src/main/java/com/google/adk/tools/mcp/McpAsyncToolset.java index bcc786d69..5f4c2164b 100644 --- a/core/src/main/java/com/google/adk/tools/mcp/McpAsyncToolset.java +++ b/core/src/main/java/com/google/adk/tools/mcp/McpAsyncToolset.java @@ -35,7 +35,7 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Mono; diff --git a/core/src/main/java/com/google/adk/tools/mcp/SseServerParameters.java b/core/src/main/java/com/google/adk/tools/mcp/SseServerParameters.java index 67ea7ef4b..3b12f7064 100644 --- a/core/src/main/java/com/google/adk/tools/mcp/SseServerParameters.java +++ b/core/src/main/java/com/google/adk/tools/mcp/SseServerParameters.java @@ -23,7 +23,7 @@ import com.google.common.collect.ImmutableMap; import java.time.Duration; import java.util.Map; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Parameters for establishing a MCP Server-Sent Events (SSE) connection. */ @AutoValue diff --git a/core/src/main/java/com/google/adk/tools/mcp/StdioServerParameters.java b/core/src/main/java/com/google/adk/tools/mcp/StdioServerParameters.java index b5632594f..50c07c840 100644 --- a/core/src/main/java/com/google/adk/tools/mcp/StdioServerParameters.java +++ b/core/src/main/java/com/google/adk/tools/mcp/StdioServerParameters.java @@ -25,7 +25,7 @@ import io.modelcontextprotocol.client.transport.ServerParameters; import java.util.List; import java.util.Map; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Parameters for establishing a MCP stdio connection. */ @AutoValue diff --git a/core/src/main/java/com/google/adk/tools/mcp/StreamableHttpServerParameters.java b/core/src/main/java/com/google/adk/tools/mcp/StreamableHttpServerParameters.java index f09c8c5e7..e9f8a3ac8 100644 --- a/core/src/main/java/com/google/adk/tools/mcp/StreamableHttpServerParameters.java +++ b/core/src/main/java/com/google/adk/tools/mcp/StreamableHttpServerParameters.java @@ -21,7 +21,7 @@ import java.time.Duration; import java.util.Collections; import java.util.Map; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Server parameters for Streamable HTTP client transport. */ public class StreamableHttpServerParameters { diff --git a/core/src/main/java/com/google/adk/tools/retrieval/VertexAiRagRetrieval.java b/core/src/main/java/com/google/adk/tools/retrieval/VertexAiRagRetrieval.java index 16f11a1f8..02f06829f 100644 --- a/core/src/main/java/com/google/adk/tools/retrieval/VertexAiRagRetrieval.java +++ b/core/src/main/java/com/google/adk/tools/retrieval/VertexAiRagRetrieval.java @@ -40,7 +40,7 @@ import java.util.List; import java.util.Map; import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; From 5f34d598435a2a8d875a5dbb14344c201db0e75f Mon Sep 17 00:00:00 2001 From: Mateusz Krawiec Date: Thu, 26 Mar 2026 05:50:34 -0700 Subject: [PATCH 08/23] fix: revert changes to AbstractMcpTool, maintaining backwards compatible text_output field in the response PiperOrigin-RevId: 889787667 --- .../java/com/google/adk/models/Claude.java | 29 ++--------- .../google/adk/tools/mcp/AbstractMcpTool.java | 51 ++++++++++++++++++- .../com/google/adk/models/ClaudeTest.java | 24 ++------- .../adk/tools/mcp/AbstractMcpToolTest.java | 7 +-- 4 files changed, 60 insertions(+), 51 deletions(-) diff --git a/core/src/main/java/com/google/adk/models/Claude.java b/core/src/main/java/com/google/adk/models/Claude.java index 01feda1d4..79201c261 100644 --- a/core/src/main/java/com/google/adk/models/Claude.java +++ b/core/src/main/java/com/google/adk/models/Claude.java @@ -172,17 +172,11 @@ private ContentBlockParam partToAnthropicMessageBlock(Part part) { if (part.functionResponse().get().response().isPresent()) { Map responseData = part.functionResponse().get().response().get(); - Object contentObj = responseData.get("content"); Object resultObj = responseData.get("result"); - - if (contentObj instanceof List list && !list.isEmpty()) { - // Native MCP format: list of content blocks - content = extractMcpContentBlocks(list); - } else if (resultObj != null) { - // ADK tool result object - content = resultObj instanceof String s ? s : serializeToJson(resultObj); - } else if (!responseData.isEmpty()) { - // Fallback: arbitrary JSON structure + if (resultObj != null) { + content = resultObj.toString(); + } else { + // Fallback to json serialization of the function response. content = serializeToJson(responseData); } } @@ -196,21 +190,6 @@ private ContentBlockParam partToAnthropicMessageBlock(Part part) { throw new UnsupportedOperationException("Not supported yet."); } - private String extractMcpContentBlocks(List list) { - List textBlocks = new ArrayList<>(); - for (Object item : list) { - if (item instanceof Map m && "text".equals(m.get("type"))) { - Object textObj = m.get("text"); - textBlocks.add(textObj != null ? String.valueOf(textObj) : ""); - } else if (item instanceof String s) { - textBlocks.add(s); - } else { - textBlocks.add(serializeToJson(item)); - } - } - return String.join("\n", textBlocks); - } - private String serializeToJson(Object obj) { try { return JsonBaseModel.getMapper().writeValueAsString(obj); diff --git a/core/src/main/java/com/google/adk/tools/mcp/AbstractMcpTool.java b/core/src/main/java/com/google/adk/tools/mcp/AbstractMcpTool.java index 3b0c3d70a..d01b7c54e 100644 --- a/core/src/main/java/com/google/adk/tools/mcp/AbstractMcpTool.java +++ b/core/src/main/java/com/google/adk/tools/mcp/AbstractMcpTool.java @@ -16,6 +16,7 @@ package com.google.adk.tools.mcp; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.adk.tools.BaseTool; @@ -23,9 +24,13 @@ import com.google.common.collect.ImmutableMap; import com.google.genai.types.FunctionDeclaration; import io.modelcontextprotocol.spec.McpSchema.CallToolResult; +import io.modelcontextprotocol.spec.McpSchema.Content; import io.modelcontextprotocol.spec.McpSchema.JsonSchema; +import io.modelcontextprotocol.spec.McpSchema.TextContent; import io.modelcontextprotocol.spec.McpSchema.Tool; import io.modelcontextprotocol.spec.McpSchema.ToolAnnotations; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -110,7 +115,51 @@ protected static Map wrapCallResult( if (callResult == null) { return ImmutableMap.of("error", "MCP framework error: CallToolResult was null"); } + List contents = callResult.content(); + Boolean isToolError = callResult.isError(); - return objectMapper.convertValue(callResult, new TypeReference>() {}); + if (isToolError != null && isToolError) { + String errorMessage = "Tool execution failed."; + if (contents != null + && !contents.isEmpty() + && contents.get(0) instanceof TextContent textContent) { + if (textContent.text() != null && !textContent.text().isEmpty()) { + errorMessage += " Details: " + textContent.text(); + } + } + return ImmutableMap.of("error", errorMessage); + } + + if (contents == null || contents.isEmpty()) { + return ImmutableMap.of(); + } + + List textOutputs = new ArrayList<>(); + for (Content content : contents) { + if (content instanceof TextContent textContent) { + if (textContent.text() != null) { + textOutputs.add(textContent.text()); + } + } + } + + if (textOutputs.isEmpty()) { + return ImmutableMap.of( + "error", + "Tool '" + mcpToolName + "' returned content that is not TextContent.", + "content_details", + contents.toString()); + } + + List> resultMaps = new ArrayList<>(); + for (String textOutput : textOutputs) { + try { + resultMaps.add( + objectMapper.readValue(textOutput, new TypeReference>() {})); + } catch (JsonProcessingException e) { + resultMaps.add(ImmutableMap.of("text", textOutput)); + } + } + return ImmutableMap.of("text_output", resultMaps); } } diff --git a/core/src/test/java/com/google/adk/models/ClaudeTest.java b/core/src/test/java/com/google/adk/models/ClaudeTest.java index 677d40627..febcaf4be 100644 --- a/core/src/test/java/com/google/adk/models/ClaudeTest.java +++ b/core/src/test/java/com/google/adk/models/ClaudeTest.java @@ -21,7 +21,6 @@ import com.anthropic.client.AnthropicClient; import com.anthropic.models.messages.ContentBlockParam; import com.anthropic.models.messages.ToolResultBlockParam; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.genai.types.FunctionResponse; import com.google.genai.types.Part; @@ -51,11 +50,9 @@ public void setUp() throws Exception { } @Test - public void testPartToAnthropicMessageBlock_mcpNativeFormat() throws Exception { + public void testPartToAnthropicMessageBlock_mcpTool_legacyTextOutputKey() throws Exception { Map responseData = - ImmutableMap.of( - "content", - ImmutableList.of(ImmutableMap.of("type", "text", "text", "Extracted native MCP text"))); + ImmutableMap.of("text_output", ImmutableMap.of("text", "Legacy result text")); FunctionResponse funcParam = FunctionResponse.builder().name("test_tool").response(responseData).id("call_123").build(); Part part = Part.builder().functionResponse(funcParam).build(); @@ -64,21 +61,8 @@ public void testPartToAnthropicMessageBlock_mcpNativeFormat() throws Exception { (ContentBlockParam) partToAnthropicMessageBlockMethod.invoke(claude, part); ToolResultBlockParam toolResult = result.asToolResult(); - assertThat(toolResult.content().get().asString()).isEqualTo("Extracted native MCP text"); - } - - @Test - public void testPartToAnthropicMessageBlock_legacyResultKey() throws Exception { - Map responseData = ImmutableMap.of("result", "Legacy result text"); - FunctionResponse funcParam = - FunctionResponse.builder().name("test_tool").response(responseData).id("call_123").build(); - Part part = Part.builder().functionResponse(funcParam).build(); - - ContentBlockParam result = - (ContentBlockParam) partToAnthropicMessageBlockMethod.invoke(claude, part); - - ToolResultBlockParam toolResult = result.asToolResult(); - assertThat(toolResult.content().get().asString()).isEqualTo("Legacy result text"); + assertThat(toolResult.content().get().asString()) + .isEqualTo("{\"text_output\":{\"text\":\"Legacy result text\"}}"); } @Test diff --git a/core/src/test/java/com/google/adk/tools/mcp/AbstractMcpToolTest.java b/core/src/test/java/com/google/adk/tools/mcp/AbstractMcpToolTest.java index e8d9ea631..7a8b97bda 100644 --- a/core/src/test/java/com/google/adk/tools/mcp/AbstractMcpToolTest.java +++ b/core/src/test/java/com/google/adk/tools/mcp/AbstractMcpToolTest.java @@ -49,14 +49,11 @@ public void testWrapCallResult_success() { Map map = AbstractMcpTool.wrapCallResult(objectMapper, "my_tool", result); - assertThat(map).containsKey("content"); - List content = (List) map.get("content"); + assertThat(map).containsKey("text_output"); + List content = (List) map.get("text_output"); assertThat(content).hasSize(1); Map contentItem = (Map) content.get(0); - assertThat(contentItem).containsEntry("type", "text"); assertThat(contentItem).containsEntry("text", "success"); - - assertThat(map).containsEntry("isError", false); } } From 29da75dac2ae1ef47322e95d44a24ba0583997da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Sobczyk?= Date: Thu, 26 Mar 2026 06:00:03 -0700 Subject: [PATCH 09/23] refactor: add precondition null check to SchemaUtils PiperOrigin-RevId: 889790941 --- core/src/main/java/com/google/adk/SchemaUtils.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/java/com/google/adk/SchemaUtils.java b/core/src/main/java/com/google/adk/SchemaUtils.java index 644aa6ded..200865d33 100644 --- a/core/src/main/java/com/google/adk/SchemaUtils.java +++ b/core/src/main/java/com/google/adk/SchemaUtils.java @@ -17,6 +17,7 @@ package com.google.adk; import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.base.Preconditions; import com.google.genai.types.Schema; import com.google.genai.types.Type; import java.util.HashMap; @@ -87,6 +88,7 @@ private static Boolean matchType(Object value, Schema schema, Boolean isInput) { * @throws IllegalArgumentException If the map does not match the schema. */ public static void validateMapOnSchema(Map args, Schema schema, Boolean isInput) { + Preconditions.checkNotNull(isInput, "IsInput cannot be null"); Map properties = schema.properties().get(); for (Entry arg : args.entrySet()) { // Check if the argument is in the schema. From 15f7ec97fd656424c971a650a4736d568437d842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Sobczyk?= Date: Fri, 27 Mar 2026 02:42:56 -0700 Subject: [PATCH 10/23] refactor: remove usage of @NonNull annotations PiperOrigin-RevId: 890333876 --- core/src/main/java/com/google/adk/tools/BaseTool.java | 5 ++--- .../java/com/google/adk/tools/SetModelResponseTool.java | 3 +-- .../google/adk/tools/retrieval/VertexAiRagRetrieval.java | 9 ++++----- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/com/google/adk/tools/BaseTool.java b/core/src/main/java/com/google/adk/tools/BaseTool.java index 01a399920..e7c15af20 100644 --- a/core/src/main/java/com/google/adk/tools/BaseTool.java +++ b/core/src/main/java/com/google/adk/tools/BaseTool.java @@ -40,7 +40,6 @@ import java.util.Map; import java.util.Optional; import java.util.function.Function; -import javax.annotation.Nonnull; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,11 +51,11 @@ public abstract class BaseTool { private final boolean isLongRunning; private final HashMap customMetadata; - protected BaseTool(@Nonnull String name, @Nonnull String description) { + protected BaseTool(String name, String description) { this(name, description, /* isLongRunning= */ false); } - protected BaseTool(@Nonnull String name, @Nonnull String description, boolean isLongRunning) { + protected BaseTool(String name, String description, boolean isLongRunning) { this.name = name; this.description = description; this.isLongRunning = isLongRunning; diff --git a/core/src/main/java/com/google/adk/tools/SetModelResponseTool.java b/core/src/main/java/com/google/adk/tools/SetModelResponseTool.java index 3b0e411b4..94569dd7d 100644 --- a/core/src/main/java/com/google/adk/tools/SetModelResponseTool.java +++ b/core/src/main/java/com/google/adk/tools/SetModelResponseTool.java @@ -22,7 +22,6 @@ import io.reactivex.rxjava3.core.Single; import java.util.Map; import java.util.Optional; -import javax.annotation.Nonnull; /** * Internal tool used for output schema workaround. @@ -36,7 +35,7 @@ public class SetModelResponseTool extends BaseTool { private final Schema outputSchema; - public SetModelResponseTool(@Nonnull Schema outputSchema) { + public SetModelResponseTool(Schema outputSchema) { super( NAME, "Set your final response using the required output schema. " diff --git a/core/src/main/java/com/google/adk/tools/retrieval/VertexAiRagRetrieval.java b/core/src/main/java/com/google/adk/tools/retrieval/VertexAiRagRetrieval.java index 02f06829f..a2720aae5 100644 --- a/core/src/main/java/com/google/adk/tools/retrieval/VertexAiRagRetrieval.java +++ b/core/src/main/java/com/google/adk/tools/retrieval/VertexAiRagRetrieval.java @@ -39,7 +39,6 @@ import io.reactivex.rxjava3.core.Single; import java.util.List; import java.util.Map; -import javax.annotation.Nonnull; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,10 +59,10 @@ public class VertexAiRagRetrieval extends BaseRetrievalTool { private final RetrieveContextsRequest.VertexRagStore apiVertexRagStore; public VertexAiRagRetrieval( - @Nonnull String name, - @Nonnull String description, - @Nonnull VertexRagServiceClient vertexRagServiceClient, - @Nonnull String parent, + String name, + String description, + VertexRagServiceClient vertexRagServiceClient, + String parent, @Nullable List ragResources, @Nullable Double vectorDistanceThreshold) { super(name, description); From e6157011da38dc6ffa9ca1d19b825ef6ad179e1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Sobczyk?= Date: Fri, 27 Mar 2026 02:59:49 -0700 Subject: [PATCH 11/23] refactor: add null annotations in appropriate places Add @Nullable annotations and move existing ones to appropriate places. It was influenced by Recommentations for making ADK Java more idiomatic, but it was later reevaluated to use different approach towards wrapper vs primitive types and mostly stay with existing solution. The changes are supposed to make the code more consistent in places where @Nullable is either used or unused. PiperOrigin-RevId: 890340769 --- .../com/google/adk/agents/ActiveStreamingTool.java | 10 ++++------ core/src/main/java/com/google/adk/agents/LlmAgent.java | 9 ++++----- core/src/main/java/com/google/adk/apps/App.java | 4 ++-- core/src/main/java/com/google/adk/events/Event.java | 4 ++-- .../main/java/com/google/adk/events/EventActions.java | 4 ++-- 5 files changed, 14 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/com/google/adk/agents/ActiveStreamingTool.java b/core/src/main/java/com/google/adk/agents/ActiveStreamingTool.java index 31b64e9c6..b0dff0c10 100644 --- a/core/src/main/java/com/google/adk/agents/ActiveStreamingTool.java +++ b/core/src/main/java/com/google/adk/agents/ActiveStreamingTool.java @@ -22,8 +22,8 @@ /** Manages streaming tool related resources during invocation. */ public class ActiveStreamingTool { - @Nullable private Disposable task; - @Nullable private LiveRequestQueue stream; + private @Nullable Disposable task; + private @Nullable LiveRequestQueue stream; public ActiveStreamingTool(Disposable task) { this(task, null); @@ -41,8 +41,7 @@ public ActiveStreamingTool(Disposable task, LiveRequestQueue stream) { public ActiveStreamingTool() {} /** Returns the active task of this streaming tool. */ - @Nullable - public Disposable task() { + public @Nullable Disposable task() { return task; } @@ -52,8 +51,7 @@ public void task(@Nullable Disposable task) { } /** Returns the active stream of this streaming tool. */ - @Nullable - public LiveRequestQueue stream() { + public @Nullable LiveRequestQueue stream() { return stream; } diff --git a/core/src/main/java/com/google/adk/agents/LlmAgent.java b/core/src/main/java/com/google/adk/agents/LlmAgent.java index c8199e8a2..98bba4606 100644 --- a/core/src/main/java/com/google/adk/agents/LlmAgent.java +++ b/core/src/main/java/com/google/adk/agents/LlmAgent.java @@ -129,10 +129,10 @@ protected LlmAgent(Builder builder) { requireNonNullElse(builder.globalInstruction, new Instruction.Static("")); this.generateContentConfig = Optional.ofNullable(builder.generateContentConfig); this.includeContents = requireNonNullElse(builder.includeContents, IncludeContents.DEFAULT); - this.planning = builder.planning != null && builder.planning; + this.planning = requireNonNullElse(builder.planning, false); this.maxSteps = Optional.ofNullable(builder.maxSteps); - this.disallowTransferToParent = builder.disallowTransferToParent; - this.disallowTransferToPeers = builder.disallowTransferToPeers; + this.disallowTransferToParent = requireNonNullElse(builder.disallowTransferToParent, false); + this.disallowTransferToPeers = requireNonNullElse(builder.disallowTransferToPeers, false); this.beforeModelCallback = requireNonNullElse(builder.beforeModelCallback, ImmutableList.of()); this.afterModelCallback = requireNonNullElse(builder.afterModelCallback, ImmutableList.of()); this.onModelErrorCallback = @@ -566,8 +566,7 @@ public Builder codeExecutor(BaseCodeExecutor codeExecutor) { return this; } - @Nullable - private static ImmutableList convertCallbacks( + private static @Nullable ImmutableList convertCallbacks( @Nullable List callbacks, Function converter, String callbackType) { return Optional.ofNullable(callbacks) .map( diff --git a/core/src/main/java/com/google/adk/apps/App.java b/core/src/main/java/com/google/adk/apps/App.java index 689e8c3b1..500c29253 100644 --- a/core/src/main/java/com/google/adk/apps/App.java +++ b/core/src/main/java/com/google/adk/apps/App.java @@ -40,8 +40,8 @@ public class App { private final String name; private final BaseAgent rootAgent; private final ImmutableList plugins; - @Nullable private final EventsCompactionConfig eventsCompactionConfig; - @Nullable private final ContextCacheConfig contextCacheConfig; + private final @Nullable EventsCompactionConfig eventsCompactionConfig; + private final @Nullable ContextCacheConfig contextCacheConfig; private App( String name, diff --git a/core/src/main/java/com/google/adk/events/Event.java b/core/src/main/java/com/google/adk/events/Event.java index 454cd591e..cde4e4452 100644 --- a/core/src/main/java/com/google/adk/events/Event.java +++ b/core/src/main/java/com/google/adk/events/Event.java @@ -348,7 +348,7 @@ public static class Builder { private String invocationId; private String author; private @Nullable Content content; - private EventActions actions; + private @Nullable EventActions actions; private @Nullable Set longRunningToolIds; private @Nullable Boolean partial; private @Nullable Boolean turnComplete; @@ -399,7 +399,7 @@ public Builder content(@Nullable Content value) { @CanIgnoreReturnValue @JsonProperty("actions") - public Builder actions(EventActions value) { + public Builder actions(@Nullable EventActions value) { this.actions = value; return this; } diff --git a/core/src/main/java/com/google/adk/events/EventActions.java b/core/src/main/java/com/google/adk/events/EventActions.java index 105300aa0..9c977240c 100644 --- a/core/src/main/java/com/google/adk/events/EventActions.java +++ b/core/src/main/java/com/google/adk/events/EventActions.java @@ -281,7 +281,7 @@ private Builder(EventActions eventActions) { @CanIgnoreReturnValue @JsonProperty("skipSummarization") - public Builder skipSummarization(boolean skipSummarization) { + public Builder skipSummarization(@Nullable Boolean skipSummarization) { this.skipSummarization = skipSummarization; return this; } @@ -324,7 +324,7 @@ public Builder transferToAgent(@Nullable String agentId) { @CanIgnoreReturnValue @JsonProperty("escalate") - public Builder escalate(boolean escalate) { + public Builder escalate(@Nullable Boolean escalate) { this.escalate = escalate; return this; } From 4b53375610979175397068c5add8ac200bb41076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Sobczyk?= Date: Fri, 27 Mar 2026 05:51:51 -0700 Subject: [PATCH 12/23] refactor: standarize AutoValue Builder setter names Updates AutoValue Builders to use the `property(value)` naming convention for setters, instead of the `setProperty(value)` convention. This change promotes consistency across the codebase. Deprecated `setProperty()` methods are retained where necessary for backward compatibility, and now delegate to the new `property()` methods. PiperOrigin-RevId: 890404260 --- .../java/com/google/adk/agents/RunConfig.java | 104 +++++-- .../adk/memory/SearchMemoryResponse.java | 23 +- .../google/adk/models/VertexCredentials.java | 28 +- .../agentanalytics/BigQueryLoggerConfig.java | 260 +++++++++++++++--- .../com/google/adk/tools/ExampleTool.java | 26 +- 5 files changed, 362 insertions(+), 79 deletions(-) diff --git a/core/src/main/java/com/google/adk/agents/RunConfig.java b/core/src/main/java/com/google/adk/agents/RunConfig.java index 661a5cddf..3ec02e1da 100644 --- a/core/src/main/java/com/google/adk/agents/RunConfig.java +++ b/core/src/main/java/com/google/adk/agents/RunConfig.java @@ -76,59 +76,115 @@ public enum ToolExecutionMode { public static Builder builder() { return new AutoValue_RunConfig.Builder() - .setSaveInputBlobsAsArtifacts(false) - .setResponseModalities(ImmutableList.of()) - .setStreamingMode(StreamingMode.NONE) - .setToolExecutionMode(ToolExecutionMode.NONE) - .setMaxLlmCalls(500) - .setAutoCreateSession(false); + .saveInputBlobsAsArtifacts(false) + .responseModalities(ImmutableList.of()) + .streamingMode(StreamingMode.NONE) + .toolExecutionMode(ToolExecutionMode.NONE) + .maxLlmCalls(500) + .autoCreateSession(false); } public static Builder builder(RunConfig runConfig) { return new AutoValue_RunConfig.Builder() - .setSaveInputBlobsAsArtifacts(runConfig.saveInputBlobsAsArtifacts()) - .setStreamingMode(runConfig.streamingMode()) - .setToolExecutionMode(runConfig.toolExecutionMode()) - .setMaxLlmCalls(runConfig.maxLlmCalls()) - .setResponseModalities(runConfig.responseModalities()) - .setSpeechConfig(runConfig.speechConfig()) - .setOutputAudioTranscription(runConfig.outputAudioTranscription()) - .setInputAudioTranscription(runConfig.inputAudioTranscription()) - .setAutoCreateSession(runConfig.autoCreateSession()); + .saveInputBlobsAsArtifacts(runConfig.saveInputBlobsAsArtifacts()) + .streamingMode(runConfig.streamingMode()) + .toolExecutionMode(runConfig.toolExecutionMode()) + .maxLlmCalls(runConfig.maxLlmCalls()) + .responseModalities(runConfig.responseModalities()) + .speechConfig(runConfig.speechConfig()) + .outputAudioTranscription(runConfig.outputAudioTranscription()) + .inputAudioTranscription(runConfig.inputAudioTranscription()) + .autoCreateSession(runConfig.autoCreateSession()); } /** Builder for {@link RunConfig}. */ @AutoValue.Builder public abstract static class Builder { + @Deprecated @CanIgnoreReturnValue - public abstract Builder setSpeechConfig(@Nullable SpeechConfig speechConfig); + public final Builder setSpeechConfig(@Nullable SpeechConfig speechConfig) { + return speechConfig(speechConfig); + } + + @CanIgnoreReturnValue + public abstract Builder speechConfig(@Nullable SpeechConfig speechConfig); + + @Deprecated + @CanIgnoreReturnValue + public final Builder setResponseModalities(Iterable responseModalities) { + return responseModalities(responseModalities); + } + + @CanIgnoreReturnValue + public abstract Builder responseModalities(Iterable responseModalities); + @Deprecated @CanIgnoreReturnValue - public abstract Builder setResponseModalities(Iterable responseModalities); + public final Builder setSaveInputBlobsAsArtifacts(boolean saveInputBlobsAsArtifacts) { + return saveInputBlobsAsArtifacts(saveInputBlobsAsArtifacts); + } @CanIgnoreReturnValue - public abstract Builder setSaveInputBlobsAsArtifacts(boolean saveInputBlobsAsArtifacts); + public abstract Builder saveInputBlobsAsArtifacts(boolean saveInputBlobsAsArtifacts); + @Deprecated @CanIgnoreReturnValue - public abstract Builder setStreamingMode(StreamingMode streamingMode); + public final Builder setStreamingMode(StreamingMode streamingMode) { + return streamingMode(streamingMode); + } @CanIgnoreReturnValue - public abstract Builder setToolExecutionMode(ToolExecutionMode toolExecutionMode); + public abstract Builder streamingMode(StreamingMode streamingMode); + @Deprecated @CanIgnoreReturnValue - public abstract Builder setOutputAudioTranscription( + public final Builder setToolExecutionMode(ToolExecutionMode toolExecutionMode) { + return toolExecutionMode(toolExecutionMode); + } + + @CanIgnoreReturnValue + public abstract Builder toolExecutionMode(ToolExecutionMode toolExecutionMode); + + @Deprecated + @CanIgnoreReturnValue + public final Builder setOutputAudioTranscription( + @Nullable AudioTranscriptionConfig outputAudioTranscription) { + return outputAudioTranscription(outputAudioTranscription); + } + + @CanIgnoreReturnValue + public abstract Builder outputAudioTranscription( @Nullable AudioTranscriptionConfig outputAudioTranscription); + @Deprecated + @CanIgnoreReturnValue + public final Builder setInputAudioTranscription( + @Nullable AudioTranscriptionConfig inputAudioTranscription) { + return inputAudioTranscription(inputAudioTranscription); + } + @CanIgnoreReturnValue - public abstract Builder setInputAudioTranscription( + public abstract Builder inputAudioTranscription( @Nullable AudioTranscriptionConfig inputAudioTranscription); + @Deprecated @CanIgnoreReturnValue - public abstract Builder setMaxLlmCalls(int maxLlmCalls); + public final Builder setMaxLlmCalls(int maxLlmCalls) { + return maxLlmCalls(maxLlmCalls); + } + + @CanIgnoreReturnValue + public abstract Builder maxLlmCalls(int maxLlmCalls); + + @Deprecated + @CanIgnoreReturnValue + public final Builder setAutoCreateSession(boolean autoCreateSession) { + return autoCreateSession(autoCreateSession); + } @CanIgnoreReturnValue - public abstract Builder setAutoCreateSession(boolean autoCreateSession); + public abstract Builder autoCreateSession(boolean autoCreateSession); abstract RunConfig autoBuild(); diff --git a/core/src/main/java/com/google/adk/memory/SearchMemoryResponse.java b/core/src/main/java/com/google/adk/memory/SearchMemoryResponse.java index b282bdfc8..b14e1d156 100644 --- a/core/src/main/java/com/google/adk/memory/SearchMemoryResponse.java +++ b/core/src/main/java/com/google/adk/memory/SearchMemoryResponse.java @@ -18,6 +18,7 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.List; /** Represents the response from a memory search. */ @@ -29,18 +30,32 @@ public abstract class SearchMemoryResponse { /** Creates a new builder for {@link SearchMemoryResponse}. */ public static Builder builder() { - return new AutoValue_SearchMemoryResponse.Builder().setMemories(ImmutableList.of()); + return new AutoValue_SearchMemoryResponse.Builder().memories(ImmutableList.of()); } /** Builder for {@link SearchMemoryResponse}. */ @AutoValue.Builder public abstract static class Builder { - abstract Builder setMemories(ImmutableList memories); + @Deprecated + @CanIgnoreReturnValue + public final Builder setMemories(ImmutableList memories) { + return memories(memories); + } /** Sets the list of memory entries using a list. */ - public Builder setMemories(List memories) { - return setMemories(ImmutableList.copyOf(memories)); + @Deprecated + @CanIgnoreReturnValue + public final Builder setMemories(List memories) { + return memories(ImmutableList.copyOf(memories)); + } + + @CanIgnoreReturnValue + public abstract Builder memories(ImmutableList memories); + + @CanIgnoreReturnValue + public Builder memories(List memories) { + return memories(ImmutableList.copyOf(memories)); } /** Builds the immutable {@link SearchMemoryResponse} object. */ diff --git a/core/src/main/java/com/google/adk/models/VertexCredentials.java b/core/src/main/java/com/google/adk/models/VertexCredentials.java index 0ae81c2a4..2c069b0dd 100644 --- a/core/src/main/java/com/google/adk/models/VertexCredentials.java +++ b/core/src/main/java/com/google/adk/models/VertexCredentials.java @@ -18,6 +18,7 @@ import com.google.auth.oauth2.GoogleCredentials; import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Optional; import org.jspecify.annotations.Nullable; @@ -39,11 +40,32 @@ public static Builder builder() { @AutoValue.Builder public abstract static class Builder { - public abstract Builder setProject(@Nullable String value); + @Deprecated + @CanIgnoreReturnValue + public final Builder setProject(@Nullable String value) { + return project(value); + } - public abstract Builder setLocation(@Nullable String value); + @CanIgnoreReturnValue + public abstract Builder project(@Nullable String value); - public abstract Builder setCredentials(@Nullable GoogleCredentials value); + @Deprecated + @CanIgnoreReturnValue + public final Builder setLocation(@Nullable String value) { + return location(value); + } + + @CanIgnoreReturnValue + public abstract Builder location(@Nullable String value); + + @Deprecated + @CanIgnoreReturnValue + public final Builder setCredentials(@Nullable GoogleCredentials value) { + return credentials(value); + } + + @CanIgnoreReturnValue + public abstract Builder credentials(@Nullable GoogleCredentials value); public abstract VertexCredentials build(); } diff --git a/core/src/main/java/com/google/adk/plugins/agentanalytics/BigQueryLoggerConfig.java b/core/src/main/java/com/google/adk/plugins/agentanalytics/BigQueryLoggerConfig.java index a7bae007f..22ced137e 100644 --- a/core/src/main/java/com/google/adk/plugins/agentanalytics/BigQueryLoggerConfig.java +++ b/core/src/main/java/com/google/adk/plugins/agentanalytics/BigQueryLoggerConfig.java @@ -20,6 +20,7 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.time.Duration; import java.util.List; import java.util.Map; @@ -104,66 +105,208 @@ public abstract class BigQueryLoggerConfig { public static Builder builder() { return new AutoValue_BigQueryLoggerConfig.Builder() - .setEnabled(true) - .setMaxContentLength(500 * 1024) - .setDatasetId("agent_analytics") - .setTableName("events") - .setClusteringFields(ImmutableList.of("event_type", "agent", "user_id")) - .setLogMultiModalContent(true) - .setRetryConfig(RetryConfig.builder().build()) - .setBatchSize(1) - .setBatchFlushInterval(Duration.ofSeconds(1)) - .setShutdownTimeout(Duration.ofSeconds(10)) - .setQueueMaxSize(10000) - .setLogSessionMetadata(true) - .setCustomTags(ImmutableMap.of()) + .enabled(true) + .maxContentLength(500 * 1024) + .datasetId("agent_analytics") + .tableName("events") + .clusteringFields(ImmutableList.of("event_type", "agent", "user_id")) + .logMultiModalContent(true) + .retryConfig(RetryConfig.builder().build()) + .batchSize(1) + .batchFlushInterval(Duration.ofSeconds(1)) + .shutdownTimeout(Duration.ofSeconds(10)) + .queueMaxSize(10000) + .logSessionMetadata(true) + .customTags(ImmutableMap.of()) // TODO(b/491851868): Enable auto-schema upgrade once implemented. - .setAutoSchemaUpgrade(false); + .autoSchemaUpgrade(false); } /** Builder for {@link BigQueryLoggerConfig}. */ @AutoValue.Builder public abstract static class Builder { - public abstract Builder setEnabled(boolean enabled); - public abstract Builder setEventAllowlist(@Nullable List eventAllowlist); + @Deprecated + @CanIgnoreReturnValue + public final Builder setEnabled(boolean enabled) { + return enabled(enabled); + } + + @CanIgnoreReturnValue + public abstract Builder enabled(boolean enabled); + + @Deprecated + @CanIgnoreReturnValue + public final Builder setEventAllowlist(@Nullable List eventAllowlist) { + return eventAllowlist(eventAllowlist); + } + + @CanIgnoreReturnValue + public abstract Builder eventAllowlist(@Nullable List eventAllowlist); + + @Deprecated + @CanIgnoreReturnValue + public final Builder setEventDenylist(@Nullable List eventDenylist) { + return eventDenylist(eventDenylist); + } + + @CanIgnoreReturnValue + public abstract Builder eventDenylist(@Nullable List eventDenylist); + + @Deprecated + @CanIgnoreReturnValue + public final Builder setMaxContentLength(int maxContentLength) { + return maxContentLength(maxContentLength); + } + + @CanIgnoreReturnValue + public abstract Builder maxContentLength(int maxContentLength); - public abstract Builder setEventDenylist(@Nullable List eventDenylist); + @Deprecated + @CanIgnoreReturnValue + public final Builder setProjectId(String projectId) { + return projectId(projectId); + } - public abstract Builder setMaxContentLength(int maxContentLength); + @CanIgnoreReturnValue + public abstract Builder projectId(String projectId); - public abstract Builder setProjectId(String projectId); + @Deprecated + @CanIgnoreReturnValue + public final Builder setDatasetId(String datasetId) { + return datasetId(datasetId); + } - public abstract Builder setDatasetId(String datasetId); + @CanIgnoreReturnValue + public abstract Builder datasetId(String datasetId); - public abstract Builder setTableName(String tableName); + @Deprecated + @CanIgnoreReturnValue + public final Builder setTableName(String tableName) { + return tableName(tableName); + } - public abstract Builder setClusteringFields(List clusteringFields); + @CanIgnoreReturnValue + public abstract Builder tableName(String tableName); - public abstract Builder setLogMultiModalContent(boolean logMultiModalContent); + @Deprecated + @CanIgnoreReturnValue + public final Builder setClusteringFields(List clusteringFields) { + return clusteringFields(clusteringFields); + } - public abstract Builder setRetryConfig(RetryConfig retryConfig); + @CanIgnoreReturnValue + public abstract Builder clusteringFields(List clusteringFields); - public abstract Builder setBatchSize(int batchSize); + @Deprecated + @CanIgnoreReturnValue + public final Builder setLogMultiModalContent(boolean logMultiModalContent) { + return logMultiModalContent(logMultiModalContent); + } - public abstract Builder setBatchFlushInterval(Duration batchFlushInterval); + @CanIgnoreReturnValue + public abstract Builder logMultiModalContent(boolean logMultiModalContent); - public abstract Builder setShutdownTimeout(Duration shutdownTimeout); + @Deprecated + @CanIgnoreReturnValue + public final Builder setRetryConfig(RetryConfig retryConfig) { + return retryConfig(retryConfig); + } - public abstract Builder setQueueMaxSize(int queueMaxSize); + @CanIgnoreReturnValue + public abstract Builder retryConfig(RetryConfig retryConfig); + + @Deprecated + @CanIgnoreReturnValue + public final Builder setBatchSize(int batchSize) { + return batchSize(batchSize); + } + + @CanIgnoreReturnValue + public abstract Builder batchSize(int batchSize); + + @Deprecated + @CanIgnoreReturnValue + public final Builder setBatchFlushInterval(Duration batchFlushInterval) { + return batchFlushInterval(batchFlushInterval); + } - public abstract Builder setContentFormatter( + @CanIgnoreReturnValue + public abstract Builder batchFlushInterval(Duration batchFlushInterval); + + @Deprecated + @CanIgnoreReturnValue + public final Builder setShutdownTimeout(Duration shutdownTimeout) { + return shutdownTimeout(shutdownTimeout); + } + + @CanIgnoreReturnValue + public abstract Builder shutdownTimeout(Duration shutdownTimeout); + + @Deprecated + @CanIgnoreReturnValue + public final Builder setQueueMaxSize(int queueMaxSize) { + return queueMaxSize(queueMaxSize); + } + + @CanIgnoreReturnValue + public abstract Builder queueMaxSize(int queueMaxSize); + + @Deprecated + @CanIgnoreReturnValue + public final Builder setContentFormatter( + @Nullable BiFunction contentFormatter) { + return contentFormatter(contentFormatter); + } + + @CanIgnoreReturnValue + public abstract Builder contentFormatter( @Nullable BiFunction contentFormatter); - public abstract Builder setConnectionId(String connectionId); + @Deprecated + @CanIgnoreReturnValue + public final Builder setConnectionId(String connectionId) { + return connectionId(connectionId); + } - public abstract Builder setLogSessionMetadata(boolean logSessionMetadata); + @CanIgnoreReturnValue + public abstract Builder connectionId(String connectionId); - public abstract Builder setCustomTags(Map customTags); + @Deprecated + @CanIgnoreReturnValue + public final Builder setLogSessionMetadata(boolean logSessionMetadata) { + return logSessionMetadata(logSessionMetadata); + } - public abstract Builder setAutoSchemaUpgrade(boolean autoSchemaUpgrade); + @CanIgnoreReturnValue + public abstract Builder logSessionMetadata(boolean logSessionMetadata); - public abstract Builder setCredentials(Credentials credentials); + @Deprecated + @CanIgnoreReturnValue + public final Builder setCustomTags(Map customTags) { + return customTags(customTags); + } + + @CanIgnoreReturnValue + public abstract Builder customTags(Map customTags); + + @Deprecated + @CanIgnoreReturnValue + public final Builder setAutoSchemaUpgrade(boolean autoSchemaUpgrade) { + return autoSchemaUpgrade(autoSchemaUpgrade); + } + + @CanIgnoreReturnValue + public abstract Builder autoSchemaUpgrade(boolean autoSchemaUpgrade); + + @Deprecated + @CanIgnoreReturnValue + public final Builder setCredentials(Credentials credentials) { + return credentials(credentials); + } + + @CanIgnoreReturnValue + public abstract Builder credentials(Credentials credentials); public abstract BigQueryLoggerConfig build(); } @@ -181,22 +324,51 @@ public abstract static class RetryConfig { public static Builder builder() { return new AutoValue_BigQueryLoggerConfig_RetryConfig.Builder() - .setMaxRetries(3) - .setInitialDelay(Duration.ofSeconds(1)) - .setMultiplier(2.0) - .setMaxDelay(Duration.ofSeconds(10)); + .maxRetries(3) + .initialDelay(Duration.ofSeconds(1)) + .multiplier(2.0) + .maxDelay(Duration.ofSeconds(10)); } /** Builder for {@link RetryConfig}. */ @AutoValue.Builder public abstract static class Builder { - public abstract Builder setMaxRetries(int maxRetries); - - public abstract Builder setInitialDelay(Duration initialDelay); - - public abstract Builder setMultiplier(double multiplier); - public abstract Builder setMaxDelay(Duration maxDelay); + @Deprecated + @CanIgnoreReturnValue + public final Builder setMaxRetries(int maxRetries) { + return maxRetries(maxRetries); + } + + @CanIgnoreReturnValue + public abstract Builder maxRetries(int maxRetries); + + @Deprecated + @CanIgnoreReturnValue + public final Builder setInitialDelay(Duration initialDelay) { + return initialDelay(initialDelay); + } + + @CanIgnoreReturnValue + public abstract Builder initialDelay(Duration initialDelay); + + @Deprecated + @CanIgnoreReturnValue + public final Builder setMultiplier(double multiplier) { + return multiplier(multiplier); + } + + @CanIgnoreReturnValue + public abstract Builder multiplier(double multiplier); + + @Deprecated + @CanIgnoreReturnValue + public final Builder setMaxDelay(Duration maxDelay) { + return maxDelay(maxDelay); + } + + @CanIgnoreReturnValue + public abstract Builder maxDelay(Duration maxDelay); public abstract RetryConfig build(); } diff --git a/core/src/main/java/com/google/adk/tools/ExampleTool.java b/core/src/main/java/com/google/adk/tools/ExampleTool.java index d03c2e4f1..c11259c52 100644 --- a/core/src/main/java/com/google/adk/tools/ExampleTool.java +++ b/core/src/main/java/com/google/adk/tools/ExampleTool.java @@ -102,7 +102,7 @@ public static ExampleTool fromConfig(ToolArgsConfig args, String configAbsPath) var maybeExamplesProvider = args.getOrEmpty("examples", new TypeReference() {}); if (maybeExamplesProvider.isPresent()) { BaseExampleProvider provider = resolveExampleProvider(maybeExamplesProvider.get()); - return ExampleTool.builder().setExampleProvider(provider).build(); + return ExampleTool.builder().exampleProvider(provider).build(); } var maybeListOfExamples = args.getOrEmpty("examples", new TypeReference>() {}); if (maybeListOfExamples.isPresent()) { @@ -166,14 +166,26 @@ public static final class Builder { private String description = "Adds few-shot examples to the request"; private Optional provider = Optional.empty(); + @Deprecated @CanIgnoreReturnValue - public Builder setName(String name) { + public final Builder setName(String name) { + return name(name); + } + + @CanIgnoreReturnValue + public Builder name(String name) { this.name = name; return this; } + @Deprecated @CanIgnoreReturnValue - public Builder setDescription(String description) { + public final Builder setDescription(String description) { + return description(description); + } + + @CanIgnoreReturnValue + public Builder description(String description) { this.description = description; return this; } @@ -184,8 +196,14 @@ public Builder addExample(Example ex) { return this; } + @Deprecated + @CanIgnoreReturnValue + public final Builder setExampleProvider(BaseExampleProvider provider) { + return exampleProvider(provider); + } + @CanIgnoreReturnValue - public Builder setExampleProvider(BaseExampleProvider provider) { + public Builder exampleProvider(BaseExampleProvider provider) { this.provider = Optional.ofNullable(provider); return this; } From 24f8d5e2562e1c0812ce6e248500797d9801fafd Mon Sep 17 00:00:00 2001 From: Guillaume Laforge Date: Fri, 27 Mar 2026 15:39:21 +0100 Subject: [PATCH 13/23] feat: add `InMemoryArtifactService` to `AgentExecutor` and update `pom.xml` dependencies - Set `artifactService` in `AgentExecutorProducer` with `InMemoryArtifactService` - Update `pom.xml` to specify `quarkus.native.builder-image` and fix groupId for `quarkus-bom` --- contrib/samples/a2a_server/pom.xml | 3 ++- .../com/google/adk/samples/a2aagent/AgentExecutorProducer.java | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/contrib/samples/a2a_server/pom.xml b/contrib/samples/a2a_server/pom.xml index 61c44bc97..42e9e00ab 100644 --- a/contrib/samples/a2a_server/pom.xml +++ b/contrib/samples/a2a_server/pom.xml @@ -23,12 +23,13 @@ 0.3.0.Beta1 3.30.6 0.8 + graalvm - io.quarkus + io.quarkus.platform quarkus-bom ${quarkus.platform.version} pom diff --git a/contrib/samples/a2a_server/src/main/java/com/google/adk/samples/a2aagent/AgentExecutorProducer.java b/contrib/samples/a2a_server/src/main/java/com/google/adk/samples/a2aagent/AgentExecutorProducer.java index 4ecd2517d..a367f87b6 100644 --- a/contrib/samples/a2a_server/src/main/java/com/google/adk/samples/a2aagent/AgentExecutorProducer.java +++ b/contrib/samples/a2a_server/src/main/java/com/google/adk/samples/a2aagent/AgentExecutorProducer.java @@ -1,6 +1,7 @@ package com.google.adk.samples.a2aagent; import com.google.adk.a2a.executor.AgentExecutorConfig; +import com.google.adk.artifacts.InMemoryArtifactService; import com.google.adk.samples.a2aagent.agent.Agent; import com.google.adk.sessions.InMemorySessionService; import io.a2a.server.agentexecution.AgentExecutor; @@ -18,10 +19,12 @@ public class AgentExecutorProducer { @Produces public AgentExecutor agentExecutor() { InMemorySessionService sessionService = new InMemorySessionService(); + InMemoryArtifactService artifactService = new InMemoryArtifactService(); return new com.google.adk.a2a.executor.AgentExecutor.Builder() .agent(Agent.ROOT_AGENT) .appName(appName) .sessionService(sessionService) + .artifactService(artifactService) .agentExecutorConfig(AgentExecutorConfig.builder().build()) .build(); } From dd1c94184835838fa47de024cf458c2ea0786aff Mon Sep 17 00:00:00 2001 From: Maciej Szwaja Date: Mon, 30 Mar 2026 03:19:16 -0700 Subject: [PATCH 14/23] chore: set release version to 1.0.0 Release-As: 1.0.0 PiperOrigin-RevId: 891615346 --- .release-please-manifest.json | 1 - 1 file changed, 1 deletion(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index bcb8fe618..802e9d13f 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,4 +1,3 @@ { ".": "1.0.0-rc.1" } - From 5adc3fa06319a73523860d09a41b2e36c789d0f6 Mon Sep 17 00:00:00 2001 From: adk-java-releases-bot Date: Mon, 30 Mar 2026 12:20:26 +0200 Subject: [PATCH 15/23] chore(main): release 1.0.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 34 +++++++++++++++++++ README.md | 4 +-- a2a/pom.xml | 2 +- contrib/firestore-session-service/pom.xml | 6 ++-- contrib/langchain4j/pom.xml | 2 +- contrib/samples/a2a_basic/pom.xml | 2 +- contrib/samples/a2a_server/pom.xml | 2 +- contrib/samples/configagent/pom.xml | 2 +- contrib/samples/helloworld/pom.xml | 2 +- contrib/samples/mcpfilesystem/pom.xml | 2 +- contrib/samples/pom.xml | 2 +- contrib/spring-ai/pom.xml | 2 +- core/pom.xml | 2 +- .../src/main/java/com/google/adk/Version.java | 2 +- dev/pom.xml | 2 +- maven_plugin/examples/custom_tools/pom.xml | 2 +- maven_plugin/examples/simple-agent/pom.xml | 2 +- maven_plugin/pom.xml | 2 +- pom.xml | 2 +- tutorials/city-time-weather/pom.xml | 2 +- tutorials/live-audio-single-agent/pom.xml | 2 +- 22 files changed, 57 insertions(+), 25 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 802e9d13f..1772e6fa2 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.0.0-rc.1" + ".": "1.0.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ef000794..c8ce38d1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,39 @@ # Changelog +## [1.0.0](https://github.com/google/adk-java/compare/v1.0.0-rc.1...v1.0.0) (2026-03-30) + + +### Features + +* add `InMemoryArtifactService` to `AgentExecutor` and update `pom.xml` dependencies ([24f8d5e](https://github.com/google/adk-java/commit/24f8d5e2562e1c0812ce6e248500797d9801fafd)) +* enabling output_schema and tools to coexist ([40ca6a7](https://github.com/google/adk-java/commit/40ca6a7c5163f711e02a54163d6066f7cd86e64d)) + + +### Bug Fixes + +* add media/image support in Spring AI MessageConverter ([8ab7f07](https://github.com/google/adk-java/commit/8ab7f072cdaa363e07b7a786044376c021c4c009)), closes [#705](https://github.com/google/adk-java/issues/705) +* add schema validation to SetModelResponseTool (issue [#587](https://github.com/google/adk-java/issues/587) already implemented, but adding tests from PR [#603](https://github.com/google/adk-java/issues/603)) ([cdc5199](https://github.com/google/adk-java/commit/cdc5199eb0f92cb95db2ee7ff139d67317968457)) +* Ensure callbackContextData is preserved across session update ([d1e05ca](https://github.com/google/adk-java/commit/d1e05caf524b7cafb3f321550659296ea70d9286)) +* **firestore:** Remove hardcoded dependency version ([6a5a55e](https://github.com/google/adk-java/commit/6a5a55eb3e531c6f8a7083712308c4800f680ca5)) +* Fixing tracing for function calls ([84dff10](https://github.com/google/adk-java/commit/84dff10a3ee7f47e30a40409e56b5e9365c69815)) +* handle null `AiMessage.text()` to prevent NPE and add unit test (PR [#1035](https://github.com/google/adk-java/issues/1035)) ([3e21e7a](https://github.com/google/adk-java/commit/3e21e7ac46b634341819b3543388a38caef85516)) +* parallel agent execution ([677b6d7](https://github.com/google/adk-java/commit/677b6d7452aa28fab42d554d18c150d59ca88eec)) +* Removing deprecated methods from Runner ([3633a7d](https://github.com/google/adk-java/commit/3633a7dd071265087ea2ff148d419969b0c888ef)) +* resolve MCP tool parsing errors in Claude integration ([5a2abbf](https://github.com/google/adk-java/commit/5a2abbfe6f9e4e1ebdd5b918e34fcdb144603b5a)) +* revert changes to AbstractMcpTool, maintaining backwards compatible text_output field in the response ([5f34d59](https://github.com/google/adk-java/commit/5f34d598435a2a8d875a5dbb14344c201db0e75f)) +* Using App conformant agent names ([f3eb936](https://github.com/google/adk-java/commit/f3eb936772740b7dc7a803a40d0d39fdbccc4af4)) + + +### Documentation + +* add pull request template ([6bb721b](https://github.com/google/adk-java/commit/6bb721b9a6000dac9dfda498fb6dd2c45862e25c)) + + +### Miscellaneous Chores + +* set release version to 1.0.0 ([dd1c941](https://github.com/google/adk-java/commit/dd1c94184835838fa47de024cf458c2ea0786aff)) +* set version to 1.0.0-rc.2 ([678b496](https://github.com/google/adk-java/commit/678b49653fa93606e2e57926213f0facaf9d6666)) + ## [1.0.0-rc.1](https://github.com/google/adk-java/compare/v0.9.0...v1.0.0-rc.1) (2026-03-20) diff --git a/README.md b/README.md index 169c78564..58307be2b 100644 --- a/README.md +++ b/README.md @@ -50,13 +50,13 @@ If you're using Maven, add the following to your dependencies: com.google.adk google-adk - 1.0.0-rc.1 + 1.0.0 com.google.adk google-adk-dev - 1.0.0-rc.1 + 1.0.0 ``` diff --git a/a2a/pom.xml b/a2a/pom.xml index 97c186606..28269fa52 100644 --- a/a2a/pom.xml +++ b/a2a/pom.xml @@ -5,7 +5,7 @@ com.google.adk google-adk-parent - 1.0.1-rc.1-SNAPSHOT + 1.0.0 google-adk-a2a diff --git a/contrib/firestore-session-service/pom.xml b/contrib/firestore-session-service/pom.xml index 34b577984..8dbe834f1 100644 --- a/contrib/firestore-session-service/pom.xml +++ b/contrib/firestore-session-service/pom.xml @@ -14,15 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. --> - + 4.0.0 com.google.adk google-adk-parent - 1.0.1-rc.1-SNAPSHOT + 1.0.0 ../../pom.xml diff --git a/contrib/langchain4j/pom.xml b/contrib/langchain4j/pom.xml index e88174849..155905696 100644 --- a/contrib/langchain4j/pom.xml +++ b/contrib/langchain4j/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-parent - 1.0.1-rc.1-SNAPSHOT + 1.0.0 ../../pom.xml diff --git a/contrib/samples/a2a_basic/pom.xml b/contrib/samples/a2a_basic/pom.xml index 80881842b..093fc930b 100644 --- a/contrib/samples/a2a_basic/pom.xml +++ b/contrib/samples/a2a_basic/pom.xml @@ -5,7 +5,7 @@ com.google.adk google-adk-samples - 1.0.1-rc.1-SNAPSHOT + 1.0.0 .. diff --git a/contrib/samples/a2a_server/pom.xml b/contrib/samples/a2a_server/pom.xml index 42e9e00ab..d815255e7 100644 --- a/contrib/samples/a2a_server/pom.xml +++ b/contrib/samples/a2a_server/pom.xml @@ -5,7 +5,7 @@ com.google.adk google-adk-samples - 1.0.1-rc.1-SNAPSHOT + 1.0.0 .. diff --git a/contrib/samples/configagent/pom.xml b/contrib/samples/configagent/pom.xml index 8f57b7f9e..280925743 100644 --- a/contrib/samples/configagent/pom.xml +++ b/contrib/samples/configagent/pom.xml @@ -5,7 +5,7 @@ com.google.adk google-adk-samples - 1.0.1-rc.1-SNAPSHOT + 1.0.0 .. diff --git a/contrib/samples/helloworld/pom.xml b/contrib/samples/helloworld/pom.xml index 676a2bc96..b1bc9094a 100644 --- a/contrib/samples/helloworld/pom.xml +++ b/contrib/samples/helloworld/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-samples - 1.0.1-rc.1-SNAPSHOT + 1.0.0 .. diff --git a/contrib/samples/mcpfilesystem/pom.xml b/contrib/samples/mcpfilesystem/pom.xml index 7275313ab..b9f116db0 100644 --- a/contrib/samples/mcpfilesystem/pom.xml +++ b/contrib/samples/mcpfilesystem/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-parent - 1.0.1-rc.1-SNAPSHOT + 1.0.0 ../../.. diff --git a/contrib/samples/pom.xml b/contrib/samples/pom.xml index ff48d6bd3..ab2b84c41 100644 --- a/contrib/samples/pom.xml +++ b/contrib/samples/pom.xml @@ -5,7 +5,7 @@ com.google.adk google-adk-parent - 1.0.1-rc.1-SNAPSHOT + 1.0.0 ../.. diff --git a/contrib/spring-ai/pom.xml b/contrib/spring-ai/pom.xml index 5f7300896..2cb730491 100644 --- a/contrib/spring-ai/pom.xml +++ b/contrib/spring-ai/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-parent - 1.0.1-rc.1-SNAPSHOT + 1.0.0 ../../pom.xml diff --git a/core/pom.xml b/core/pom.xml index 8559f396d..4012c562f 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-parent - 1.0.1-rc.1-SNAPSHOT + 1.0.0 google-adk diff --git a/core/src/main/java/com/google/adk/Version.java b/core/src/main/java/com/google/adk/Version.java index d6e18c5ad..b32afce6f 100644 --- a/core/src/main/java/com/google/adk/Version.java +++ b/core/src/main/java/com/google/adk/Version.java @@ -22,7 +22,7 @@ */ public final class Version { // Don't touch this, release-please should keep it up to date. - public static final String JAVA_ADK_VERSION = "1.0.0-rc.1"; // x-release-please-released-version + public static final String JAVA_ADK_VERSION = "1.0.0"; // x-release-please-released-version private Version() {} } diff --git a/dev/pom.xml b/dev/pom.xml index 5468a1187..895bd2b39 100644 --- a/dev/pom.xml +++ b/dev/pom.xml @@ -18,7 +18,7 @@ com.google.adk google-adk-parent - 1.0.1-rc.1-SNAPSHOT + 1.0.0 google-adk-dev diff --git a/maven_plugin/examples/custom_tools/pom.xml b/maven_plugin/examples/custom_tools/pom.xml index aa273d732..fe0577dbd 100644 --- a/maven_plugin/examples/custom_tools/pom.xml +++ b/maven_plugin/examples/custom_tools/pom.xml @@ -4,7 +4,7 @@ com.example custom-tools-example - 1.0.1-rc.1-SNAPSHOT + 1.0.0 jar ADK Custom Tools Example diff --git a/maven_plugin/examples/simple-agent/pom.xml b/maven_plugin/examples/simple-agent/pom.xml index 34aeb8c1c..eb324f619 100644 --- a/maven_plugin/examples/simple-agent/pom.xml +++ b/maven_plugin/examples/simple-agent/pom.xml @@ -4,7 +4,7 @@ com.example simple-adk-agent - 1.0.1-rc.1-SNAPSHOT + 1.0.0 jar Simple ADK Agent Example diff --git a/maven_plugin/pom.xml b/maven_plugin/pom.xml index d0feb41e3..555a35770 100644 --- a/maven_plugin/pom.xml +++ b/maven_plugin/pom.xml @@ -5,7 +5,7 @@ com.google.adk google-adk-parent - 1.0.1-rc.1-SNAPSHOT + 1.0.0 ../pom.xml diff --git a/pom.xml b/pom.xml index bdd2f33b1..6a0f8e90d 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ com.google.adk google-adk-parent - 1.0.1-rc.1-SNAPSHOT + 1.0.0 pom Google Agent Development Kit Maven Parent POM diff --git a/tutorials/city-time-weather/pom.xml b/tutorials/city-time-weather/pom.xml index 19ef08a2d..1480c60e7 100644 --- a/tutorials/city-time-weather/pom.xml +++ b/tutorials/city-time-weather/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-parent - 1.0.1-rc.1-SNAPSHOT + 1.0.0 ../../pom.xml diff --git a/tutorials/live-audio-single-agent/pom.xml b/tutorials/live-audio-single-agent/pom.xml index 99243893b..c2fee3d0c 100644 --- a/tutorials/live-audio-single-agent/pom.xml +++ b/tutorials/live-audio-single-agent/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-parent - 1.0.1-rc.1-SNAPSHOT + 1.0.0 ../../pom.xml From aebe6dc0c8dfcdd288f3f89e2b7572bd656ab423 Mon Sep 17 00:00:00 2001 From: adk-java-releases-bot Date: Mon, 30 Mar 2026 13:07:13 +0200 Subject: [PATCH 16/23] chore(main): release 1.0.1-SNAPSHOT --- a2a/pom.xml | 2 +- contrib/firestore-session-service/pom.xml | 2 +- contrib/langchain4j/pom.xml | 2 +- contrib/samples/a2a_basic/pom.xml | 2 +- contrib/samples/a2a_server/pom.xml | 2 +- contrib/samples/configagent/pom.xml | 2 +- contrib/samples/helloworld/pom.xml | 2 +- contrib/samples/mcpfilesystem/pom.xml | 2 +- contrib/samples/pom.xml | 2 +- contrib/spring-ai/pom.xml | 2 +- core/pom.xml | 2 +- dev/pom.xml | 2 +- maven_plugin/examples/custom_tools/pom.xml | 2 +- maven_plugin/examples/simple-agent/pom.xml | 2 +- maven_plugin/pom.xml | 2 +- pom.xml | 2 +- tutorials/city-time-weather/pom.xml | 2 +- tutorials/live-audio-single-agent/pom.xml | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/a2a/pom.xml b/a2a/pom.xml index 28269fa52..38d1edafa 100644 --- a/a2a/pom.xml +++ b/a2a/pom.xml @@ -5,7 +5,7 @@ com.google.adk google-adk-parent - 1.0.0 + 1.0.1-SNAPSHOT google-adk-a2a diff --git a/contrib/firestore-session-service/pom.xml b/contrib/firestore-session-service/pom.xml index 8dbe834f1..917e23a0c 100644 --- a/contrib/firestore-session-service/pom.xml +++ b/contrib/firestore-session-service/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-parent - 1.0.0 + 1.0.1-SNAPSHOT ../../pom.xml diff --git a/contrib/langchain4j/pom.xml b/contrib/langchain4j/pom.xml index 155905696..51815af03 100644 --- a/contrib/langchain4j/pom.xml +++ b/contrib/langchain4j/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-parent - 1.0.0 + 1.0.1-SNAPSHOT ../../pom.xml diff --git a/contrib/samples/a2a_basic/pom.xml b/contrib/samples/a2a_basic/pom.xml index 093fc930b..6ed3d6475 100644 --- a/contrib/samples/a2a_basic/pom.xml +++ b/contrib/samples/a2a_basic/pom.xml @@ -5,7 +5,7 @@ com.google.adk google-adk-samples - 1.0.0 + 1.0.1-SNAPSHOT .. diff --git a/contrib/samples/a2a_server/pom.xml b/contrib/samples/a2a_server/pom.xml index d815255e7..496106315 100644 --- a/contrib/samples/a2a_server/pom.xml +++ b/contrib/samples/a2a_server/pom.xml @@ -5,7 +5,7 @@ com.google.adk google-adk-samples - 1.0.0 + 1.0.1-SNAPSHOT .. diff --git a/contrib/samples/configagent/pom.xml b/contrib/samples/configagent/pom.xml index 280925743..c83cc9e2b 100644 --- a/contrib/samples/configagent/pom.xml +++ b/contrib/samples/configagent/pom.xml @@ -5,7 +5,7 @@ com.google.adk google-adk-samples - 1.0.0 + 1.0.1-SNAPSHOT .. diff --git a/contrib/samples/helloworld/pom.xml b/contrib/samples/helloworld/pom.xml index b1bc9094a..39ec6f736 100644 --- a/contrib/samples/helloworld/pom.xml +++ b/contrib/samples/helloworld/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-samples - 1.0.0 + 1.0.1-SNAPSHOT .. diff --git a/contrib/samples/mcpfilesystem/pom.xml b/contrib/samples/mcpfilesystem/pom.xml index b9f116db0..15c05eb7e 100644 --- a/contrib/samples/mcpfilesystem/pom.xml +++ b/contrib/samples/mcpfilesystem/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-parent - 1.0.0 + 1.0.1-SNAPSHOT ../../.. diff --git a/contrib/samples/pom.xml b/contrib/samples/pom.xml index ab2b84c41..ab84e33ee 100644 --- a/contrib/samples/pom.xml +++ b/contrib/samples/pom.xml @@ -5,7 +5,7 @@ com.google.adk google-adk-parent - 1.0.0 + 1.0.1-SNAPSHOT ../.. diff --git a/contrib/spring-ai/pom.xml b/contrib/spring-ai/pom.xml index 2cb730491..eb7abde49 100644 --- a/contrib/spring-ai/pom.xml +++ b/contrib/spring-ai/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-parent - 1.0.0 + 1.0.1-SNAPSHOT ../../pom.xml diff --git a/core/pom.xml b/core/pom.xml index 4012c562f..5670d425f 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-parent - 1.0.0 + 1.0.1-SNAPSHOT google-adk diff --git a/dev/pom.xml b/dev/pom.xml index 895bd2b39..7f246d2f6 100644 --- a/dev/pom.xml +++ b/dev/pom.xml @@ -18,7 +18,7 @@ com.google.adk google-adk-parent - 1.0.0 + 1.0.1-SNAPSHOT google-adk-dev diff --git a/maven_plugin/examples/custom_tools/pom.xml b/maven_plugin/examples/custom_tools/pom.xml index fe0577dbd..9421f9bee 100644 --- a/maven_plugin/examples/custom_tools/pom.xml +++ b/maven_plugin/examples/custom_tools/pom.xml @@ -4,7 +4,7 @@ com.example custom-tools-example - 1.0.0 + 1.0.1-SNAPSHOT jar ADK Custom Tools Example diff --git a/maven_plugin/examples/simple-agent/pom.xml b/maven_plugin/examples/simple-agent/pom.xml index eb324f619..514ce45a3 100644 --- a/maven_plugin/examples/simple-agent/pom.xml +++ b/maven_plugin/examples/simple-agent/pom.xml @@ -4,7 +4,7 @@ com.example simple-adk-agent - 1.0.0 + 1.0.1-SNAPSHOT jar Simple ADK Agent Example diff --git a/maven_plugin/pom.xml b/maven_plugin/pom.xml index 555a35770..8cc365dfd 100644 --- a/maven_plugin/pom.xml +++ b/maven_plugin/pom.xml @@ -5,7 +5,7 @@ com.google.adk google-adk-parent - 1.0.0 + 1.0.1-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 6a0f8e90d..b80734b2c 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ com.google.adk google-adk-parent - 1.0.0 + 1.0.1-SNAPSHOT pom Google Agent Development Kit Maven Parent POM diff --git a/tutorials/city-time-weather/pom.xml b/tutorials/city-time-weather/pom.xml index 1480c60e7..8d1cb7976 100644 --- a/tutorials/city-time-weather/pom.xml +++ b/tutorials/city-time-weather/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-parent - 1.0.0 + 1.0.1-SNAPSHOT ../../pom.xml diff --git a/tutorials/live-audio-single-agent/pom.xml b/tutorials/live-audio-single-agent/pom.xml index c2fee3d0c..0c3dc454b 100644 --- a/tutorials/live-audio-single-agent/pom.xml +++ b/tutorials/live-audio-single-agent/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-parent - 1.0.0 + 1.0.1-SNAPSHOT ../../pom.xml From 55becb81b6dcc15a9a82ec842a0096132813ae64 Mon Sep 17 00:00:00 2001 From: Google Team Member Date: Mon, 30 Mar 2026 10:08:17 -0700 Subject: [PATCH 17/23] feat: Add ChatCompletionsResponse object This is part of a larger chain of commits for adding chat completion API support to the Apigee model. PiperOrigin-RevId: 891777579 --- .../adk/models/ChatCompletionsResponse.java | 224 ++++++++++++ .../models/ChatCompletionsResponseTest.java | 328 ++++++++++++++++++ 2 files changed, 552 insertions(+) create mode 100644 core/src/main/java/com/google/adk/models/ChatCompletionsResponse.java create mode 100644 core/src/test/java/com/google/adk/models/ChatCompletionsResponseTest.java diff --git a/core/src/main/java/com/google/adk/models/ChatCompletionsResponse.java b/core/src/main/java/com/google/adk/models/ChatCompletionsResponse.java new file mode 100644 index 000000000..fe5cdd116 --- /dev/null +++ b/core/src/main/java/com/google/adk/models/ChatCompletionsResponse.java @@ -0,0 +1,224 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.adk.models; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import java.util.Map; + +/** + * Data Transfer Objects for Chat Completion and Chat Completion Chunk API responses. + * + *

These classes are used for deserializing JSON responses from the `/chat/completions` endpoint. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +final class ChatCompletionsResponse { + + private ChatCompletionsResponse() {} + + @JsonIgnoreProperties(ignoreUnknown = true) + static class ChatCompletion { + public String id; + public List choices; + public Long created; + public String model; + public String object; + + @JsonProperty("service_tier") + public String serviceTier; + + @JsonProperty("system_fingerprint") + public String systemFingerprint; + + public Usage usage; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static class Choice { + @JsonProperty("finish_reason") + public String finishReason; + + public Integer index; + public Logprobs logprobs; + public Message message; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static class ChatCompletionChunk { + public String id; + public List choices; + public Long created; + public String model; + public String object; + + @JsonProperty("service_tier") + public String serviceTier; + + @JsonProperty("system_fingerprint") + public String systemFingerprint; + + public Usage usage; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static class ChunkChoice { + @JsonProperty("finish_reason") + public String finishReason; + + public Integer index; + public Logprobs logprobs; + public Message delta; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static class Message { + public String content; + public String refusal; + public String role; + + @JsonProperty("tool_calls") + public List toolCalls; + + // function_call is not supported in ChatCompletionChunk and ChatCompletion support is + // deprecated. + @JsonProperty("function_call") + public Function functionCall; // Fallback for deprecated top-level function calls + + public List annotations; + public Audio audio; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static class ToolCall { + // Index is only used in ChatCompletionChunk. + public Integer index; + public String id; + public String type; + public Function function; + public Custom custom; + + @JsonProperty("extra_content") + public Map extraContent; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static class Function { + public String name; + public String arguments; // JSON string + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static class Custom { + public String input; + public String name; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static class Logprobs { + public List content; + public List refusal; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + static class TokenLogprob { + public String token; + public List bytes; + public Double logprob; + + @JsonProperty("top_logprobs") + public List topLogprobs; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static class Usage { + @JsonProperty("completion_tokens") + public Integer completionTokens; + + @JsonProperty("prompt_tokens") + public Integer promptTokens; + + @JsonProperty("total_tokens") + public Integer totalTokens; + + @JsonProperty("thoughts_token_count") + public Integer thoughtsTokenCount; // Gemini-specific extension + + @JsonProperty("completion_tokens_details") + public CompletionTokensDetails completionTokensDetails; + + @JsonProperty("prompt_tokens_details") + public PromptTokensDetails promptTokensDetails; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static class CompletionTokensDetails { + @JsonProperty("accepted_prediction_tokens") + public Integer acceptedPredictionTokens; + + @JsonProperty("audio_tokens") + public Integer audioTokens; + + @JsonProperty("reasoning_tokens") + public Integer reasoningTokens; + + @JsonProperty("rejected_prediction_tokens") + public Integer rejectedPredictionTokens; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static class PromptTokensDetails { + @JsonProperty("audio_tokens") + public Integer audioTokens; + + @JsonProperty("cached_tokens") + public Integer cachedTokens; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static class Annotation { + public String type; + + @JsonProperty("url_citation") + public UrlCitation urlCitation; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static class UrlCitation { + @JsonProperty("end_index") + public Integer endIndex; + + @JsonProperty("start_index") + public Integer startIndex; + + public String title; + public String url; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static class Audio { + public String id; + public String data; + + @JsonProperty("expires_at") + public Long expiresAt; + + public String transcript; + } +} diff --git a/core/src/test/java/com/google/adk/models/ChatCompletionsResponseTest.java b/core/src/test/java/com/google/adk/models/ChatCompletionsResponseTest.java new file mode 100644 index 000000000..53fcdfbdf --- /dev/null +++ b/core/src/test/java/com/google/adk/models/ChatCompletionsResponseTest.java @@ -0,0 +1,328 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.adk.models; + +import static com.google.common.truth.Truth.assertThat; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.adk.models.ChatCompletionsResponse.ChatCompletion; +import com.google.adk.models.ChatCompletionsResponse.ChatCompletionChunk; +import java.util.Map; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class ChatCompletionsResponseTest { + + private ObjectMapper objectMapper; + + @Before + public void setUp() { + objectMapper = new ObjectMapper(); + } + + @Test + public void testDeserializeChatCompletion_standardResponse() throws Exception { + String json = + """ + { + "id": "chatcmpl-123", + "object": "chat.completion", + "created": 1677652288, + "model": "gpt-4o-mini", + "choices": [{ + "index": 0, + "message": { + "role": "assistant", + "content": "Hello!" + }, + "finish_reason": "stop" + }], + "usage": { + "prompt_tokens": 9, + "completion_tokens": 12, + "total_tokens": 21 + } + } + """; + + ChatCompletion completion = objectMapper.readValue(json, ChatCompletion.class); + + assertThat(completion.id).isEqualTo("chatcmpl-123"); + assertThat(completion.object).isEqualTo("chat.completion"); + assertThat(completion.created).isEqualTo(1677652288L); + assertThat(completion.model).isEqualTo("gpt-4o-mini"); + assertThat(completion.choices).hasSize(1); + assertThat(completion.choices.get(0).index).isEqualTo(0); + assertThat(completion.choices.get(0).message.role).isEqualTo("assistant"); + assertThat(completion.choices.get(0).message.content).isEqualTo("Hello!"); + assertThat(completion.choices.get(0).finishReason).isEqualTo("stop"); + assertThat(completion.usage.promptTokens).isEqualTo(9); + assertThat(completion.usage.completionTokens).isEqualTo(12); + assertThat(completion.usage.totalTokens).isEqualTo(21); + } + + @Test + public void testDeserializeChatCompletion_withFunctionCallFallback() throws Exception { + String json = + """ + { + "id": "chatcmpl-123", + "choices": [{ + "message": { + "role": "assistant", + "function_call": { + "name": "get_current_weather", + "arguments": "{\\"location\\": \\"Boston\\"}" + } + } + }] + } + """; + + ChatCompletion completion = objectMapper.readValue(json, ChatCompletion.class); + + assertThat(completion.choices.get(0).message.functionCall).isNotNull(); + assertThat(completion.choices.get(0).message.functionCall.name) + .isEqualTo("get_current_weather"); + assertThat(completion.choices.get(0).message.functionCall.arguments) + .isEqualTo("{\"location\": \"Boston\"}"); + } + + @Test + public void testDeserializeChatCompletion_withThoughtSignatureAndGeminiTokens() throws Exception { + String json = + """ + { + "choices": [{ + "message": { + "role": "assistant", + "tool_calls": [{ + "id": "call_abc", + "type": "function", + "extra_content": { + "google": { + "thought_signature": "c2lnbmF0dXJl" + } + } + }] + } + }], + "usage": { + "thoughts_token_count": 50 + } + } + """; + + ChatCompletion completion = objectMapper.readValue(json, ChatCompletion.class); + + assertThat(completion.choices.get(0).message.toolCalls).hasSize(1); + assertThat(completion.choices.get(0).message.toolCalls.get(0).extraContent).isNotNull(); + Map extraContentMap = + (Map) completion.choices.get(0).message.toolCalls.get(0).extraContent; + @SuppressWarnings("unchecked") // This code won't run in production and it's is a JSON object. + Map googleMap = (Map) extraContentMap.get("google"); + assertThat(googleMap.get("thought_signature")).isEqualTo("c2lnbmF0dXJl"); + assertThat(completion.usage.thoughtsTokenCount).isEqualTo(50); + } + + @Test + public void testDeserializeChatCompletion_withArbitraryExtraContent() throws Exception { + String json = + """ + { + "choices": [{ + "message": { + "role": "assistant", + "tool_calls": [{ + "id": "call_abc", + "type": "function", + "extra_content": { + "custom_key": "custom_value", + "nested": { + "key": 123 + } + } + }] + } + }] + } + """; + + ChatCompletion got = objectMapper.readValue(json, ChatCompletion.class); + + assertThat(got.choices.get(0).message.toolCalls).hasSize(1); + Map extraContent = + (Map) got.choices.get(0).message.toolCalls.get(0).extraContent; + assertThat(extraContent.get("custom_key")).isEqualTo("custom_value"); + @SuppressWarnings("unchecked") // This code won't run in production and it's is a JSON object. + Map nested = (Map) extraContent.get("nested"); + assertThat(nested.get("key")).isEqualTo(123); + } + + @Test + public void testDeserializeChatCompletion_withAudio() throws Exception { + String json = + """ + { + "choices": [{ + "message": { + "role": "assistant", + "content": "Hello", + "annotations": [{ + "type": "url_citation", + "url_citation": { + "end_index": 5, + "start_index": 0, + "title": "Example Title", + "url": "https://example.com" + } + }], + "audio": { + "id": "audio_123", + "data": "base64data", + "expires_at": 1234567890, + "transcript": "Hello" + } + } + }] + } + """; + + ChatCompletionsResponse.ChatCompletion completion = + objectMapper.readValue(json, ChatCompletionsResponse.ChatCompletion.class); + + assertThat(completion.choices.get(0).message.annotations).hasSize(1); + ChatCompletionsResponse.Annotation annotation = + completion.choices.get(0).message.annotations.get(0); + assertThat(annotation.type).isEqualTo("url_citation"); + assertThat(annotation.urlCitation.title).isEqualTo("Example Title"); + assertThat(annotation.urlCitation.url).isEqualTo("https://example.com"); + + assertThat(completion.choices.get(0).message.audio).isNotNull(); + assertThat(completion.choices.get(0).message.audio.id).isEqualTo("audio_123"); + assertThat(completion.choices.get(0).message.audio.data).isEqualTo("base64data"); + } + + @Test + public void testDeserializeChatCompletion_withCustomToolCall() throws Exception { + String json = + """ + { + "choices": [{ + "message": { + "role": "assistant", + "tool_calls": [{ + "id": "call_custom", + "type": "custom", + "custom": { + "input": "{\\\"arg\\\":\\\"val\\\"}", + "name": "custom_tool" + } + }] + } + }] + } + """; + + ChatCompletionsResponse.ChatCompletion completion = + objectMapper.readValue(json, ChatCompletionsResponse.ChatCompletion.class); + + assertThat(completion.choices.get(0).message.toolCalls).hasSize(1); + ChatCompletionsResponse.ToolCall toolCall = completion.choices.get(0).message.toolCalls.get(0); + assertThat(toolCall.type).isEqualTo("custom"); + assertThat(toolCall.custom.name).isEqualTo("custom_tool"); + assertThat(toolCall.custom.input).isEqualTo("{\"arg\":\"val\"}"); + } + + @Test + public void testDeserializeChatCompletionChunk_streamingResponse() throws Exception { + String json = + """ + { + "id": "chatcmpl-123", + "object": "chat.completion.chunk", + "created": 1694268190, + "choices": [{ + "index": 0, + "delta": { + "content": "Hello" + } + }] + } + """; + + ChatCompletionChunk chunk = objectMapper.readValue(json, ChatCompletionChunk.class); + + assertThat(chunk.id).isEqualTo("chatcmpl-123"); + assertThat(chunk.object).isEqualTo("chat.completion.chunk"); + assertThat(chunk.choices).hasSize(1); + assertThat(chunk.choices.get(0).delta.content).isEqualTo("Hello"); + } + + @Test + public void testDeserializeChatCompletionChunk_withToolCallDelta() throws Exception { + String json = + """ + { + "choices": [{ + "delta": { + "tool_calls": [{ + "index": 1, + "id": "call_abc", + "type": "function", + "function": { + "name": "get_weather", + "arguments": "{\\\"location\\\":\\\"Boston\\\"}" + }, + "extra_content": { + "google": { + "thought_signature": "sig" + } + } + }] + } + }], + "usage": { + "completion_tokens": 10, + "prompt_tokens": 5, + "total_tokens": 15 + } + } + """; + + ChatCompletionChunk chunk = objectMapper.readValue(json, ChatCompletionChunk.class); + + assertThat(chunk.choices.get(0).delta.toolCalls).hasSize(1); + ChatCompletionsResponse.ToolCall toolCall = chunk.choices.get(0).delta.toolCalls.get(0); + assertThat(toolCall.index).isEqualTo(1); + assertThat(toolCall.id).isEqualTo("call_abc"); + assertThat(toolCall.type).isEqualTo("function"); + assertThat(toolCall.function.name).isEqualTo("get_weather"); + assertThat(toolCall.function.arguments).isEqualTo("{\"location\":\"Boston\"}"); + @SuppressWarnings("unchecked") // This code won't run in production and it's is a JSON object. + Map google = (Map) toolCall.extraContent.get("google"); + assertThat(google).containsEntry("thought_signature", "sig"); + + assertThat(chunk.usage).isNotNull(); + assertThat(chunk.usage.completionTokens).isEqualTo(10); + assertThat(chunk.usage.promptTokens).isEqualTo(5); + assertThat(chunk.usage.totalTokens).isEqualTo(15); + } +} From d0e108510487d97d186052caa164649e1c90f176 Mon Sep 17 00:00:00 2001 From: Google Team Member Date: Tue, 31 Mar 2026 04:50:15 -0700 Subject: [PATCH 18/23] feat: Update event IDs in BaseLlmFlow's post processing section PiperOrigin-RevId: 892263598 --- .../adk/flows/llmflows/BaseLlmFlow.java | 5 + .../ToolRequestConfirmationActionTest.java | 132 ++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 core/src/test/java/com/google/adk/flows/llmflows/ToolRequestConfirmationActionTest.java diff --git a/core/src/main/java/com/google/adk/flows/llmflows/BaseLlmFlow.java b/core/src/main/java/com/google/adk/flows/llmflows/BaseLlmFlow.java index aa62b9f31..48d141819 100644 --- a/core/src/main/java/com/google/adk/flows/llmflows/BaseLlmFlow.java +++ b/core/src/main/java/com/google/adk/flows/llmflows/BaseLlmFlow.java @@ -415,6 +415,11 @@ private Flowable runOneStep(Context spanContext, InvocationContext contex }) .concatMap( event -> { + // Update event ID for the new resulting events + String oldId = event.id(); + String newId = Event.generateEventId(); + logger.debug("Resetting event ID from {} to {}", oldId, newId); + event = event.toBuilder().id(newId).build(); Flowable postProcessedEvents = Flowable.just(event); if (event.actions().transferToAgent().isPresent()) { String agentToTransfer = diff --git a/core/src/test/java/com/google/adk/flows/llmflows/ToolRequestConfirmationActionTest.java b/core/src/test/java/com/google/adk/flows/llmflows/ToolRequestConfirmationActionTest.java new file mode 100644 index 000000000..6e34095c2 --- /dev/null +++ b/core/src/test/java/com/google/adk/flows/llmflows/ToolRequestConfirmationActionTest.java @@ -0,0 +1,132 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.adk.flows.llmflows; + +import static com.google.adk.testing.TestUtils.createInvocationContext; +import static com.google.adk.testing.TestUtils.createLlmResponse; +import static com.google.adk.testing.TestUtils.createTestAgentBuilder; +import static com.google.adk.testing.TestUtils.createTestLlm; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.truth.Truth.assertThat; + +import com.google.adk.agents.InvocationContext; +import com.google.adk.agents.LlmAgent; +import com.google.adk.events.Event; +import com.google.adk.runner.InMemoryRunner; +import com.google.adk.runner.Runner; +import com.google.adk.sessions.Session; +import com.google.adk.tools.BaseTool; +import com.google.adk.tools.ToolContext; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.genai.types.Content; +import com.google.genai.types.FunctionDeclaration; +import com.google.genai.types.Part; +import com.google.genai.types.Schema; +import io.reactivex.rxjava3.core.Flowable; +import io.reactivex.rxjava3.core.Single; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class ToolRequestConfirmationActionTest { + + private static class ToolRequestConfirmationTool extends BaseTool { + ToolRequestConfirmationTool() { + super("request_confirmation_tool", "Requests confirmation."); + } + + @Override + public Optional declaration() { + return Optional.of( + FunctionDeclaration.builder() + .name(name()) + .description(description()) + .parameters(Schema.builder().type("OBJECT").build()) // No parameters needed + .build()); + } + + @Override + public Single> runAsync(Map args, ToolContext toolContext) { + toolContext.requestConfirmation("Please confirm this action"); + return Single.just(ImmutableMap.of()); + } + } + + @Test + public void toolRequestConfirmation_eventHasNonNullId() { + Content requestConfirmationCallContent = + Content.fromParts(Part.fromFunctionCall("request_confirmation_tool", ImmutableMap.of())); + Content response1 = Content.fromParts(Part.fromText("response1")); + Content response2 = Content.fromParts(Part.fromText("response2")); + + var testLlm = + createTestLlm( + Flowable.just(createLlmResponse(requestConfirmationCallContent)), + Flowable.just(createLlmResponse(response1)), + Flowable.just(createLlmResponse(response2))); + + LlmAgent rootAgent = + createTestAgentBuilder(testLlm) + .name("root_agent") + .tools(ImmutableList.of(new ToolRequestConfirmationTool())) + .build(); + InvocationContext invocationContext = createInvocationContext(rootAgent); + + Runner runner = getRunnerAndCreateSession(rootAgent, invocationContext.session()); + + ImmutableList confirmationEvents = + runRunner(runner, invocationContext).stream() + .filter( + e -> + e.functionCalls().stream() + .anyMatch( + f -> + Objects.equals( + f.name().orElse(""), + Functions.REQUEST_CONFIRMATION_FUNCTION_CALL_NAME))) + .collect(toImmutableList()); + + assertThat(confirmationEvents).isNotEmpty(); + assertThat(confirmationEvents.get(0).id()).isNotNull(); + } + + private Runner getRunnerAndCreateSession(LlmAgent agent, Session session) { + Runner runner = new InMemoryRunner(agent, session.appName()); + + var unused = + runner + .sessionService() + .createSession(session.appName(), session.userId(), session.state(), session.id()) + .blockingGet(); + + return runner; + } + + private List runRunner(Runner runner, InvocationContext invocationContext) { + Session session = invocationContext.session(); + return runner + .runAsync(session.userId(), session.id(), invocationContext.userContent().orElse(null)) + .toList() + .blockingGet(); + } +} From 18555848cdc292ac1ebc97dfd12817c79a688f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Sobczyk?= Date: Tue, 31 Mar 2026 06:14:27 -0700 Subject: [PATCH 19/23] refactor: use new setter methods in place of old ones PiperOrigin-RevId: 892296560 --- .../adk/memory/InMemoryMemoryService.java | 2 +- .../java/com/google/adk/runner/Runner.java | 9 ++++--- .../BigQueryAgentAnalyticsPluginTest.java | 24 +++++++++---------- .../com/google/adk/tools/ExampleToolTest.java | 2 +- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/core/src/main/java/com/google/adk/memory/InMemoryMemoryService.java b/core/src/main/java/com/google/adk/memory/InMemoryMemoryService.java index 200c6df55..ff2995c74 100644 --- a/core/src/main/java/com/google/adk/memory/InMemoryMemoryService.java +++ b/core/src/main/java/com/google/adk/memory/InMemoryMemoryService.java @@ -130,7 +130,7 @@ public Single searchMemory(String appName, String userId, } return SearchMemoryResponse.builder() - .setMemories(ImmutableList.copyOf(matchingMemories)) + .memories(ImmutableList.copyOf(matchingMemories)) .build(); }); } diff --git a/core/src/main/java/com/google/adk/runner/Runner.java b/core/src/main/java/com/google/adk/runner/Runner.java index 120465f31..d6ad38561 100644 --- a/core/src/main/java/com/google/adk/runner/Runner.java +++ b/core/src/main/java/com/google/adk/runner/Runner.java @@ -582,19 +582,18 @@ private InvocationContext newInvocationContextForLive( if (liveRequestQueue != null) { // Default to AUDIO modality if not specified. if (CollectionUtils.isNullOrEmpty(runConfig.responseModalities())) { - runConfigBuilder.setResponseModalities( - ImmutableList.of(new Modality(Modality.Known.AUDIO))); + runConfigBuilder.responseModalities(ImmutableList.of(new Modality(Modality.Known.AUDIO))); if (runConfig.outputAudioTranscription() == null) { - runConfigBuilder.setOutputAudioTranscription(AudioTranscriptionConfig.builder().build()); + runConfigBuilder.outputAudioTranscription(AudioTranscriptionConfig.builder().build()); } } else if (!runConfig.responseModalities().contains(new Modality(Modality.Known.TEXT))) { if (runConfig.outputAudioTranscription() == null) { - runConfigBuilder.setOutputAudioTranscription(AudioTranscriptionConfig.builder().build()); + runConfigBuilder.outputAudioTranscription(AudioTranscriptionConfig.builder().build()); } } // Need input transcription for agent transferring in live mode. if (runConfig.inputAudioTranscription() == null) { - runConfigBuilder.setInputAudioTranscription(AudioTranscriptionConfig.builder().build()); + runConfigBuilder.inputAudioTranscription(AudioTranscriptionConfig.builder().build()); } } InvocationContext.Builder builder = diff --git a/core/src/test/java/com/google/adk/plugins/agentanalytics/BigQueryAgentAnalyticsPluginTest.java b/core/src/test/java/com/google/adk/plugins/agentanalytics/BigQueryAgentAnalyticsPluginTest.java index 8147c5cc6..0822c2cae 100644 --- a/core/src/test/java/com/google/adk/plugins/agentanalytics/BigQueryAgentAnalyticsPluginTest.java +++ b/core/src/test/java/com/google/adk/plugins/agentanalytics/BigQueryAgentAnalyticsPluginTest.java @@ -96,15 +96,15 @@ public void setUp() throws Exception { fakeAgent = new FakeAgent("agent_name"); config = BigQueryLoggerConfig.builder() - .setEnabled(true) - .setProjectId("project") - .setDatasetId("dataset") - .setTableName("table") - .setBatchSize(10) - .setBatchFlushInterval(Duration.ofSeconds(10)) - .setAutoSchemaUpgrade(false) - .setCredentials(mock(Credentials.class)) - .setCustomTags(ImmutableMap.of("global_tag", "global_value")) + .enabled(true) + .projectId("project") + .datasetId("dataset") + .tableName("table") + .batchSize(10) + .batchFlushInterval(Duration.ofSeconds(10)) + .autoSchemaUpgrade(false) + .credentials(mock(Credentials.class)) + .customTags(ImmutableMap.of("global_tag", "global_value")) .build(); when(mockBigQuery.getOptions()) @@ -167,9 +167,9 @@ public void afterRunCallback_flushesAndAppends() throws Exception { public void getStreamName_returnsCorrectFormat() { BigQueryLoggerConfig config = BigQueryLoggerConfig.builder() - .setProjectId("test-project") - .setDatasetId("test-dataset") - .setTableName("test-table") + .projectId("test-project") + .datasetId("test-dataset") + .tableName("test-table") .build(); String streamName = plugin.getStreamName(config); diff --git a/core/src/test/java/com/google/adk/tools/ExampleToolTest.java b/core/src/test/java/com/google/adk/tools/ExampleToolTest.java index 55e5d8f93..e56afe60b 100644 --- a/core/src/test/java/com/google/adk/tools/ExampleToolTest.java +++ b/core/src/test/java/com/google/adk/tools/ExampleToolTest.java @@ -73,7 +73,7 @@ public void processLlmRequest_withInlineExamples_appendsFewShot() { @Test public void processLlmRequest_withProvider_appendsFewShot() { - ExampleTool tool = ExampleTool.builder().setExampleProvider(ProviderHolder.EXAMPLES).build(); + ExampleTool tool = ExampleTool.builder().exampleProvider(ProviderHolder.EXAMPLES).build(); InvocationContext ctx = buildInvocationContext(); LlmRequest.Builder builder = LlmRequest.builder().model("gemini-2.0-flash"); From 88c8b0e5a4863fa623fa17ff616d13570b60c4d0 Mon Sep 17 00:00:00 2001 From: Jinni Gu Date: Fri, 27 Mar 2026 22:25:04 -0700 Subject: [PATCH 20/23] feat: Support Sub-agent Escalation event in Parallel Agent (Issue #561) --- .../com/google/adk/agents/ParallelAgent.java | 3 +- .../agents/ParallelAgentEscalationTest.java | 137 ++++++++++++++++++ 2 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 core/src/test/java/com/google/adk/agents/ParallelAgentEscalationTest.java diff --git a/core/src/main/java/com/google/adk/agents/ParallelAgent.java b/core/src/main/java/com/google/adk/agents/ParallelAgent.java index 2593ec13a..1e98dbf50 100644 --- a/core/src/main/java/com/google/adk/agents/ParallelAgent.java +++ b/core/src/main/java/com/google/adk/agents/ParallelAgent.java @@ -148,7 +148,8 @@ protected Flowable runAsyncImpl(InvocationContext invocationContext) { for (BaseAgent subAgent : currentSubAgents) { agentFlowables.add(subAgent.runAsync(updatedInvocationContext).subscribeOn(scheduler)); } - return Flowable.merge(agentFlowables); + return Flowable.merge(agentFlowables) + .takeUntil((Event event) -> event.actions().escalate().orElse(false)); } /** diff --git a/core/src/test/java/com/google/adk/agents/ParallelAgentEscalationTest.java b/core/src/test/java/com/google/adk/agents/ParallelAgentEscalationTest.java new file mode 100644 index 000000000..42db353c8 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/ParallelAgentEscalationTest.java @@ -0,0 +1,137 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.adk.agents; + +import static com.google.adk.testing.TestUtils.createInvocationContext; +import static com.google.common.truth.Truth.assertThat; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import com.google.adk.events.Event; +import com.google.adk.events.EventActions; +import com.google.common.collect.ImmutableList; +import com.google.genai.types.Content; +import com.google.genai.types.Part; +import io.reactivex.rxjava3.core.Flowable; +import io.reactivex.rxjava3.core.Scheduler; +import io.reactivex.rxjava3.schedulers.TestScheduler; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class ParallelAgentEscalationTest { + + static class TestAgent extends BaseAgent { + private final long delayMillis; + private final Scheduler scheduler; + private final String content; + private final EventActions actions; + + private TestAgent(String name, long delayMillis, Scheduler scheduler, String content) { + this(name, delayMillis, scheduler, content, null); + } + + private TestAgent( + String name, long delayMillis, Scheduler scheduler, String content, EventActions actions) { + super(name, "Test Agent", ImmutableList.of(), null, null); + this.delayMillis = delayMillis; + this.scheduler = scheduler; + this.content = content; + this.actions = actions; + } + + @Override + protected Flowable runAsyncImpl(InvocationContext invocationContext) { + Flowable event = + Flowable.fromCallable( + () -> { + Event.Builder builder = + Event.builder() + .author(name()) + .branch(invocationContext.branch().orElse(null)) + .invocationId(invocationContext.invocationId()) + .content(Content.fromParts(Part.fromText(content))); + + if (actions != null) { + builder.actions(actions); + } + return builder.build(); + }); + + if (delayMillis > 0) { + return event.delay(delayMillis, MILLISECONDS, scheduler); + } + return event; + } + + @Override + protected Flowable runLiveImpl(InvocationContext invocationContext) { + throw new UnsupportedOperationException("Not implemented"); + } + } + + @Test + public void runAsync_escalationEvent_shortCircuitsOtherAgents() { + TestScheduler testScheduler = new TestScheduler(); + + TestAgent escalatingAgent = + new TestAgent( + "escalating_agent", + 100, + testScheduler, + "Escalating!", + EventActions.builder().escalate(true).build()); + TestAgent slowAgent = new TestAgent("slow_agent", 500, testScheduler, "Finished"); + TestAgent fastAgent = new TestAgent("fast_agent", 50, testScheduler, "Finished"); + + ParallelAgent parallelAgent = + ParallelAgent.builder() + .name("parallel_agent") + .subAgents(fastAgent, escalatingAgent, slowAgent) + .scheduler(testScheduler) + .build(); + + InvocationContext invocationContext = createInvocationContext(parallelAgent); + + var subscriber = parallelAgent.runAsync(invocationContext).test(); + + // Fast agent completes at 50ms (before the escalation) + testScheduler.advanceTimeBy(50, MILLISECONDS); + subscriber.assertValueCount(1); + assertThat(subscriber.values().get(0).author()).isEqualTo("fast_agent"); + + // Escalating agent completes at 100ms + testScheduler.advanceTimeBy(50, MILLISECONDS); + subscriber.assertValueCount(2); + + Event event1 = subscriber.values().get(0); + assertThat(event1.author()).isEqualTo("fast_agent"); + + Event event2 = subscriber.values().get(1); + assertThat(event2.author()).isEqualTo("escalating_agent"); + assertThat(event2.actions().escalate()).hasValue(true); + + subscriber.assertComplete(); + + // Slow agent would complete at 500ms, but test scheduler advances time to prove + // sequence was forcibly terminated! + testScheduler.advanceTimeBy(400, MILLISECONDS); + + // Test RxJava Disposal behavior: SlowAgent won't emit anything + subscriber.assertValueCount(2); + } +} From d62b6363dc1c3b7191d9a862117c973a76047fb4 Mon Sep 17 00:00:00 2001 From: yongkiy-google Date: Wed, 1 Apr 2026 12:06:38 +0200 Subject: [PATCH 21/23] Update InMemorySessionServiceTest.java --- .../com/google/adk/sessions/InMemorySessionServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/com/google/adk/sessions/InMemorySessionServiceTest.java b/core/src/test/java/com/google/adk/sessions/InMemorySessionServiceTest.java index c260f6695..bfb2c272e 100644 --- a/core/src/test/java/com/google/adk/sessions/InMemorySessionServiceTest.java +++ b/core/src/test/java/com/google/adk/sessions/InMemorySessionServiceTest.java @@ -275,7 +275,7 @@ public void deleteSession_cleansUpEmptyParentMaps() throws Exception { Session session = sessionService.createSession("app-name", "user-id").blockingGet(); sessionService - .deleteSession(session1.appName(), session1.userId(), session1.id()) + .deleteSession(session.appName(), session.userId(), session.id()) .blockingAwait(); // Use reflection to access the private 'sessions' field From 0cf001fa5a12075dd7a2bac7e4d7b4113d2a7894 Mon Sep 17 00:00:00 2001 From: yongkiy-google Date: Wed, 1 Apr 2026 12:20:25 +0200 Subject: [PATCH 22/23] Update InMemorySessionService.java --- .../java/com/google/adk/sessions/InMemorySessionService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/main/java/com/google/adk/sessions/InMemorySessionService.java b/core/src/main/java/com/google/adk/sessions/InMemorySessionService.java index 3e96f959e..72a14cc4d 100644 --- a/core/src/main/java/com/google/adk/sessions/InMemorySessionService.java +++ b/core/src/main/java/com/google/adk/sessions/InMemorySessionService.java @@ -200,7 +200,6 @@ public Completable deleteSession(String appName, String userId, String sessionId // If appSessionsMap is now empty, return null to automatically remove the appName key return appSessionsMap.isEmpty() ? null : appSessionsMap; }); - return Completable.complete(); } From fe30b2a66d18f86ab1e5484a873589dea7e9a952 Mon Sep 17 00:00:00 2001 From: yongkiy-google Date: Wed, 1 Apr 2026 12:22:10 +0200 Subject: [PATCH 23/23] Update InMemorySessionServiceTest.java --- .../google/adk/sessions/InMemorySessionServiceTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/test/java/com/google/adk/sessions/InMemorySessionServiceTest.java b/core/src/test/java/com/google/adk/sessions/InMemorySessionServiceTest.java index bfb2c272e..6a271efac 100644 --- a/core/src/test/java/com/google/adk/sessions/InMemorySessionServiceTest.java +++ b/core/src/test/java/com/google/adk/sessions/InMemorySessionServiceTest.java @@ -274,9 +274,7 @@ public void deleteSession_cleansUpEmptyParentMaps() throws Exception { Session session = sessionService.createSession("app-name", "user-id").blockingGet(); - sessionService - .deleteSession(session.appName(), session.userId(), session.id()) - .blockingAwait(); + sessionService.deleteSession(session.appName(), session.userId(), session.id()).blockingAwait(); // Use reflection to access the private 'sessions' field Field field = InMemorySessionService.class.getDeclaredField("sessions"); @@ -296,7 +294,9 @@ public void deleteSession_doesNotRemoveUserMapWhenOtherSessionsExist() throws Ex Session session2 = sessionService.createSession("app-name", "user-id").blockingGet(); // Delete only one of the two sessions - sessionService.deleteSession(session1.appName(), session1.userId(), session1.id()).blockingAwait(); + sessionService + .deleteSession(session1.appName(), session1.userId(), session1.id()) + .blockingAwait(); // session2 should still be retrievable assertThat(