Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 67 additions & 4 deletions core/src/main/java/com/google/adk/agents/InvocationContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,19 @@
import com.google.adk.artifacts.BaseArtifactService;
import com.google.adk.memory.BaseMemoryService;
import com.google.adk.models.LlmCallsLimitExceededException;
import com.google.adk.platform.TimeProvider;
import com.google.adk.platform.UuidProvider;
import com.google.adk.plugins.Plugin;
import com.google.adk.plugins.PluginManager;
import com.google.adk.sessions.BaseSessionService;
import com.google.adk.sessions.Session;
import com.google.adk.summarizer.EventsCompactionConfig;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.genai.types.Content;
import java.time.Instant;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.jspecify.annotations.Nullable;

Expand All @@ -52,6 +54,8 @@ public class InvocationContext {
@Nullable private final ContextCacheConfig contextCacheConfig;
private final InvocationCostManager invocationCostManager;
private final Map<String, Object> callbackContextData;
private final TimeProvider timeProvider;
private final UuidProvider uuidProvider;

@Nullable private String branch;
private BaseAgent agent;
Expand All @@ -78,6 +82,8 @@ protected InvocationContext(Builder builder) {
// invocation invocation so that Plugins can access the same data it during the invocation
// across all types of callbacks.
this.callbackContextData = builder.callbackContextData;
this.timeProvider = builder.timeProvider;
this.uuidProvider = builder.uuidProvider;
}

/** Returns a new {@link Builder} for creating {@link InvocationContext} instances. */
Expand Down Expand Up @@ -192,9 +198,34 @@ public String userId() {
return session.userId();
}

/** Returns the {@link TimeProvider} for this invocation. */
public TimeProvider timeProvider() {
return timeProvider;
}

/** Returns the {@link UuidProvider} for this invocation. */
public UuidProvider uuidProvider() {
return uuidProvider;
}

/** Returns the current time from this invocation's {@link TimeProvider}. */
public Instant now() {
return timeProvider.now();
}

/** Returns a new unique identifier from this invocation's {@link UuidProvider}. */
public String newUuid() {
return uuidProvider.newUuid();
}

/** Generates a new unique ID for an invocation context. */
public static String newInvocationContextId() {
return "e-" + UUID.randomUUID();
return newInvocationContextId(UuidProvider.SYSTEM);
}

/** Generates a new unique ID for an invocation context using the given {@link UuidProvider}. */
public static String newInvocationContextId(UuidProvider uuidProvider) {
return "e-" + uuidProvider.newUuid();
}

/**
Expand Down Expand Up @@ -275,6 +306,8 @@ private Builder(InvocationContext context) {
// invocation invocation so that Plugins can access the same data it during the invocation
// across all types of callbacks.
this.callbackContextData = context.callbackContextData;
this.timeProvider = context.timeProvider;
this.uuidProvider = context.uuidProvider;
}

private BaseSessionService sessionService;
Expand All @@ -294,6 +327,8 @@ private Builder(InvocationContext context) {
@Nullable private ContextCacheConfig contextCacheConfig;
private InvocationCostManager invocationCostManager = new InvocationCostManager();
private Map<String, Object> callbackContextData = new ConcurrentHashMap<>();
private TimeProvider timeProvider = TimeProvider.SYSTEM;
private UuidProvider uuidProvider = UuidProvider.SYSTEM;

/**
* Sets the session service for managing session state.
Expand Down Expand Up @@ -475,6 +510,30 @@ public Builder callbackContextData(Map<String, Object> callbackContextData) {
return this;
}

/**
* Sets the time provider for the invocation. Defaults to {@link TimeProvider#SYSTEM}.
*
* @param timeProvider the provider for the current time.
* @return this builder instance for chaining.
*/
@CanIgnoreReturnValue
public Builder timeProvider(TimeProvider timeProvider) {
this.timeProvider = timeProvider;
return this;
}

/**
* Sets the UUID provider for the invocation. Defaults to {@link UuidProvider#SYSTEM}.
*
* @param uuidProvider the provider for new unique identifiers.
* @return this builder instance for chaining.
*/
@CanIgnoreReturnValue
public Builder uuidProvider(UuidProvider uuidProvider) {
this.uuidProvider = uuidProvider;
return this;
}

/**
* Builds the {@link InvocationContext} instance.
*
Expand Down Expand Up @@ -531,7 +590,9 @@ public boolean equals(Object o) {
&& Objects.equals(eventsCompactionConfig, that.eventsCompactionConfig)
&& Objects.equals(contextCacheConfig, that.contextCacheConfig)
&& Objects.equals(invocationCostManager, that.invocationCostManager)
&& Objects.equals(callbackContextData, that.callbackContextData);
&& Objects.equals(callbackContextData, that.callbackContextData)
&& Objects.equals(timeProvider, that.timeProvider)
&& Objects.equals(uuidProvider, that.uuidProvider);
}

@Override
Expand All @@ -553,6 +614,8 @@ public int hashCode() {
eventsCompactionConfig,
contextCacheConfig,
invocationCostManager,
callbackContextData);
callbackContextData,
timeProvider,
uuidProvider);
}
}
20 changes: 12 additions & 8 deletions core/src/main/java/com/google/adk/flows/llmflows/BaseLlmFlow.java
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ private Flowable<Event> runOneStep(Context spanContext, InvocationContext contex

final Event mutableEventTemplate =
Event.builder()
.id(Event.generateEventId())
.id(context.newUuid())
.invocationId(context.invocationId())
.author(context.agent().name())
.branch(context.branch().orElse(null))
Expand All @@ -453,15 +453,15 @@ private Flowable<Event> runOneStep(Context spanContext, InvocationContext contex
.doFinally(
() -> {
String oldId = mutableEventTemplate.id();
String newId = Event.generateEventId();
String newId = context.newUuid();
logger.debug("Resetting event ID from {} to {}", oldId, newId);
mutableEventTemplate.setId(newId);
})
.concatMap(
event -> {
// Update event ID for the new resulting events
String oldId = event.id();
String newId = Event.generateEventId();
String newId = context.newUuid();
logger.debug("Resetting event ID from {} to {}", oldId, newId);
event = event.toBuilder().id(newId).build();
Flowable<Event> postProcessedEvents = Flowable.just(event);
Expand Down Expand Up @@ -555,7 +555,7 @@ public Flowable<Event> runLive(InvocationContext invocationContext) {
return Flowable.empty();
}

String eventIdForSendData = Event.generateEventId();
String eventIdForSendData = invocationContext.newUuid();
LlmAgent agent = (LlmAgent) invocationContext.agent();
BaseLlm llm =
agent.resolvedModel().model().isPresent()
Expand Down Expand Up @@ -647,7 +647,7 @@ public void onError(Throwable e) {
.flatMap(
llmResponse -> {
Event baseEventForThisLlmResponse =
liveEventBuilderTemplate.id(Event.generateEventId()).build();
liveEventBuilderTemplate.id(invocationContext.newUuid()).build();
return postprocess(
invocationContext,
baseEventForThisLlmResponse,
Expand Down Expand Up @@ -727,7 +727,7 @@ private Flowable<Event> buildPostprocessingEvents(
}

Event modelResponseEvent =
buildModelResponseEvent(baseEventForLlmResponse, llmRequest, updatedResponse);
buildModelResponseEvent(context, baseEventForLlmResponse, llmRequest, updatedResponse);
if (modelResponseEvent.functionCalls().isEmpty()) {
return processorEvents.concatWith(Flowable.just(modelResponseEvent));
}
Expand Down Expand Up @@ -772,9 +772,13 @@ private void traceCallLlm(
}

private Event buildModelResponseEvent(
Event baseEventForLlmResponse, LlmRequest llmRequest, LlmResponse llmResponse) {
InvocationContext context,
Event baseEventForLlmResponse,
LlmRequest llmRequest,
LlmResponse llmResponse) {
Event.Builder eventBuilder =
baseEventForLlmResponse.toBuilder()
.timestamp(context.now().toEpochMilli())
.content(llmResponse.content().orElse(null))
.partial(llmResponse.partial().orElse(null))
.errorCode(llmResponse.errorCode().orElse(null))
Expand All @@ -794,7 +798,7 @@ private Event buildModelResponseEvent(
logger.debug("event: {} functionCalls: {}", event, event.functionCalls());

if (!event.functionCalls().isEmpty()) {
Functions.populateClientFunctionCallId(event);
Functions.populateClientFunctionCallId(event, context.uuidProvider());
Set<String> longRunningToolIds =
Functions.getLongRunningFunctionCalls(event.functionCalls(), llmRequest.tools());
logger.debug("longRunningToolIds: {}", longRunningToolIds);
Expand Down
39 changes: 30 additions & 9 deletions core/src/main/java/com/google/adk/flows/llmflows/Functions.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.platform.UuidProvider;
import com.google.adk.telemetry.Instrumentation;
import com.google.adk.telemetry.Instrumentation.ToolExecution;
import com.google.adk.telemetry.Tracing;
Expand Down Expand Up @@ -58,7 +59,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;

Expand All @@ -75,7 +75,12 @@ public final class Functions {

/** Generates a unique ID for a function call. */
public static String generateClientFunctionCallId() {
return AF_FUNCTION_CALL_ID_PREFIX + UUID.randomUUID();
return generateClientFunctionCallId(UuidProvider.SYSTEM);
}

/** Generates a unique ID for a function call using the given {@link UuidProvider}. */
public static String generateClientFunctionCallId(UuidProvider uuidProvider) {
return AF_FUNCTION_CALL_ID_PREFIX + uuidProvider.newUuid();
}

/**
Expand All @@ -87,6 +92,18 @@ public static String generateClientFunctionCallId() {
* @param modelResponseEvent The event potentially containing function calls.
*/
public static void populateClientFunctionCallId(Event modelResponseEvent) {
populateClientFunctionCallId(modelResponseEvent, UuidProvider.SYSTEM);
}

/**
* Populates missing function call IDs in the provided event's content using the given {@link
* UuidProvider}.
*
* @param modelResponseEvent The event potentially containing function calls.
* @param uuidProvider The provider used to mint new function call IDs.
*/
public static void populateClientFunctionCallId(
Event modelResponseEvent, UuidProvider uuidProvider) {
Optional<Content> originalContentOptional = modelResponseEvent.content();
if (originalContentOptional.isEmpty()) {
return;
Expand All @@ -104,7 +121,7 @@ public static void populateClientFunctionCallId(Event modelResponseEvent) {
FunctionCall functionCall = part.functionCall().get();
if (functionCall.id().isEmpty() || functionCall.id().get().isEmpty()) {
FunctionCall updatedFunctionCall =
functionCall.toBuilder().id(generateClientFunctionCallId()).build();
functionCall.toBuilder().id(generateClientFunctionCallId(uuidProvider)).build();
newParts.add(part.toBuilder().functionCall(updatedFunctionCall).build());
modified = true;
} else {
Expand Down Expand Up @@ -169,7 +186,7 @@ public static Maybe<Event> handleFunctionCalls(
return Maybe.empty();
}
Optional<Event> maybeMergedEvent =
Functions.mergeParallelFunctionResponseEvents(events);
Functions.mergeParallelFunctionResponseEvents(invocationContext, events);
if (maybeMergedEvent.isEmpty()) {
return Maybe.empty();
}
Expand Down Expand Up @@ -233,7 +250,8 @@ public static Maybe<Event> handleFunctionCallsLive(
if (events.isEmpty()) {
return Maybe.empty();
}
return Maybe.fromOptional(Functions.mergeParallelFunctionResponseEvents(events));
return Maybe.fromOptional(
Functions.mergeParallelFunctionResponseEvents(invocationContext, events));
});
}

Expand Down Expand Up @@ -492,7 +510,7 @@ private static Maybe<Event> processFunctionResult(
}

private static Optional<Event> mergeParallelFunctionResponseEvents(
List<Event> functionResponseEvents) {
InvocationContext invocationContext, List<Event> functionResponseEvents) {
if (functionResponseEvents.isEmpty()) {
return Optional.empty();
}
Expand All @@ -516,7 +534,7 @@ private static Optional<Event> mergeParallelFunctionResponseEvents(

return Optional.of(
Event.builder()
.id(Event.generateEventId())
.id(invocationContext.newUuid())
.invocationId(baseEvent.invocationId())
.author(baseEvent.author())
.branch(baseEvent.branch().orElse(null))
Expand Down Expand Up @@ -667,7 +685,8 @@ private static Event buildResponseEvent(
.build();

return Event.builder()
.id(Event.generateEventId())
.id(invocationContext.newUuid())
.timestamp(invocationContext.now().toEpochMilli())
.invocationId(invocationContext.invocationId())
.author(invocationContext.agent().name())
.branch(invocationContext.branch().orElse(null))
Expand Down Expand Up @@ -712,7 +731,7 @@ public static Optional<Event> generateRequestConfirmationEvent(
functionCallsById.get(entry.getKey()),
"toolConfirmation",
entry.getValue()))
.id(generateClientFunctionCallId())
.id(generateClientFunctionCallId(invocationContext.uuidProvider()))
.build();

longRunningToolIds.add(requestConfirmationFunctionCall.id().get());
Expand All @@ -728,6 +747,8 @@ public static Optional<Event> generateRequestConfirmationEvent(

return Optional.of(
Event.builder()
.id(invocationContext.newUuid())
.timestamp(invocationContext.now().toEpochMilli())
.invocationId(invocationContext.invocationId())
.author(invocationContext.agent().name())
.branch(invocationContext.branch().orElse(null))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ public static Optional<String> getStructuredModelResponse(Event functionResponse
public static Event createFinalModelResponseEvent(
InvocationContext context, String jsonResponse) {
return Event.builder()
.id(Event.generateEventId())
.id(context.newUuid())
.timestamp(context.now().toEpochMilli())
.invocationId(context.invocationId())
.author(context.agent().name())
.branch(context.branch().orElse(null))
Expand Down
35 changes: 35 additions & 0 deletions core/src/main/java/com/google/adk/platform/TimeProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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
*
* 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.platform;

import java.time.Instant;

/**
* Supplies the current time for ADK-generated timestamps.
*
* <p>The default {@link #SYSTEM} provider reads the wall clock. Integrations that need custom
* timestamps can install a custom provider on an invocation; see {@code InvocationContext}.
*/
@FunctionalInterface
public interface TimeProvider {

/** A provider backed by the system wall clock ({@link Instant#now()}). */
TimeProvider SYSTEM = Instant::now;

/** Returns the current time. */
Instant now();
}
Loading