diff --git a/core/src/main/java/com/google/adk/flows/llmflows/Functions.java b/core/src/main/java/com/google/adk/flows/llmflows/Functions.java index 0653052f2..3f3b8ef86 100644 --- a/core/src/main/java/com/google/adk/flows/llmflows/Functions.java +++ b/core/src/main/java/com/google/adk/flows/llmflows/Functions.java @@ -29,6 +29,7 @@ import com.google.adk.events.Event; import com.google.adk.events.EventActions; import com.google.adk.events.ToolConfirmation; +import com.google.adk.models.FunctionCallIds; import com.google.adk.telemetry.Instrumentation; import com.google.adk.telemetry.Instrumentation.ToolExecution; import com.google.adk.telemetry.Tracing; @@ -60,7 +61,6 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -72,12 +72,11 @@ public final class Functions { /** Session state key for storing the security policy outcomes for tool calls. */ public static final String TOOL_CALL_SECURITY_STATES = "adk_tool_call_security_states"; - private static final String AF_FUNCTION_CALL_ID_PREFIX = "adk-"; private static final Logger logger = LoggerFactory.getLogger(Functions.class); /** Generates a unique ID for a function call. */ public static String generateClientFunctionCallId() { - return AF_FUNCTION_CALL_ID_PREFIX + UUID.randomUUID(); + return FunctionCallIds.generateClientFunctionCallId(); } /** diff --git a/core/src/main/java/com/google/adk/models/FunctionCallIds.java b/core/src/main/java/com/google/adk/models/FunctionCallIds.java new file mode 100644 index 000000000..ba6a89b06 --- /dev/null +++ b/core/src/main/java/com/google/adk/models/FunctionCallIds.java @@ -0,0 +1,38 @@ +/* + * 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.models; + +import java.util.UUID; + +/** Constants and helpers for ADK-generated function call IDs. */ +public final class FunctionCallIds { + + /** Prefix marking function call IDs the ADK generated client-side. */ + private static final String AF_FUNCTION_CALL_ID_PREFIX = "adk-"; + + /** Returns a new client-side function call ID with the ADK prefix. */ + public static String generateClientFunctionCallId() { + return AF_FUNCTION_CALL_ID_PREFIX + UUID.randomUUID(); + } + + /** Returns whether {@code id} was generated client-side by the ADK. */ + public static boolean isClientGeneratedFunctionCallId(String id) { + return id != null && id.startsWith(AF_FUNCTION_CALL_ID_PREFIX); + } + + private FunctionCallIds() {} +} diff --git a/core/src/main/java/com/google/adk/models/Gemini.java b/core/src/main/java/com/google/adk/models/Gemini.java index 16781e857..36267551c 100644 --- a/core/src/main/java/com/google/adk/models/Gemini.java +++ b/core/src/main/java/com/google/adk/models/Gemini.java @@ -43,7 +43,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import okhttp3.OkHttpClient; @@ -401,13 +400,9 @@ private static List ensureFunctionCallIds(List parts) { return result; } - /** - * Generates a unique client-side function-call ID. Format matches {@code - * com.google.adk.flows.llmflows.Functions#generateClientFunctionCallId()} so downstream code - * that already sees IDs with the {@code "adk-"} prefix continues to work. - */ + /** Generates a unique client-side function-call ID. */ private static String generateClientFunctionCallId() { - return "adk-" + UUID.randomUUID(); + return FunctionCallIds.generateClientFunctionCallId(); } /** diff --git a/core/src/main/java/com/google/adk/models/GeminiUtil.java b/core/src/main/java/com/google/adk/models/GeminiUtil.java index 540d0e06e..ab508be92 100644 --- a/core/src/main/java/com/google/adk/models/GeminiUtil.java +++ b/core/src/main/java/com/google/adk/models/GeminiUtil.java @@ -38,8 +38,6 @@ public final class GeminiUtil { "Continue output. DO NOT look at this line. ONLY look at the content before this line and" + " system instruction."; - private static final String AF_FUNCTION_CALL_ID_PREFIX = "adk-"; - private GeminiUtil() {} /** @@ -174,14 +172,15 @@ public static LlmRequest removeClientFunctionCallId(LlmRequest llmRequest) { private static Part removeClientFunctionCallIdFromPart(Part part) { if (part.functionCall().isPresent() && part.functionCall().get().id().isPresent() - && part.functionCall().get().id().get().startsWith(AF_FUNCTION_CALL_ID_PREFIX)) { + && FunctionCallIds.isClientGeneratedFunctionCallId(part.functionCall().get().id().get())) { return part.toBuilder() .functionCall(part.functionCall().get().toBuilder().clearId().build()) .build(); } if (part.functionResponse().isPresent() && part.functionResponse().get().id().isPresent() - && part.functionResponse().get().id().get().startsWith(AF_FUNCTION_CALL_ID_PREFIX)) { + && FunctionCallIds.isClientGeneratedFunctionCallId( + part.functionResponse().get().id().get())) { return part.toBuilder() .functionResponse(part.functionResponse().get().toBuilder().clearId().build()) .build(); diff --git a/core/src/test/java/com/google/adk/models/FunctionCallIdsTest.java b/core/src/test/java/com/google/adk/models/FunctionCallIdsTest.java new file mode 100644 index 000000000..a56878e14 --- /dev/null +++ b/core/src/test/java/com/google/adk/models/FunctionCallIdsTest.java @@ -0,0 +1,52 @@ +/* + * 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.models; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class FunctionCallIdsTest { + + @Test + public void generatedId_isRecognizedAsClientGenerated() { + String id = FunctionCallIds.generateClientFunctionCallId(); + + assertThat(FunctionCallIds.isClientGeneratedFunctionCallId(id)).isTrue(); + } + + @Test + public void generateClientFunctionCallId_returnsUniqueIds() { + String first = FunctionCallIds.generateClientFunctionCallId(); + String second = FunctionCallIds.generateClientFunctionCallId(); + + assertThat(first).isNotEqualTo(second); + } + + @Test + public void isClientGeneratedFunctionCallId_falseForModelGeneratedId() { + assertThat(FunctionCallIds.isClientGeneratedFunctionCallId("call_123")).isFalse(); + } + + @Test + public void isClientGeneratedFunctionCallId_falseForNullOrEmpty() { + assertThat(FunctionCallIds.isClientGeneratedFunctionCallId(null)).isFalse(); + assertThat(FunctionCallIds.isClientGeneratedFunctionCallId("")).isFalse(); + } +}