Skip to content

Commit 185c6bf

Browse files
authored
Merge branch 'main' into feat/missing-tool-resolution-strategy
2 parents 787a7c2 + 089a9cb commit 185c6bf

File tree

31 files changed

+1384
-515
lines changed

31 files changed

+1384
-515
lines changed

core/src/main/java/com/google/adk/agents/InvocationContext.java

Lines changed: 396 additions & 102 deletions
Large diffs are not rendered by default.

core/src/main/java/com/google/adk/agents/LlmAgent.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import com.google.errorprone.annotations.CanIgnoreReturnValue;
5454
import com.google.genai.types.Content;
5555
import com.google.genai.types.GenerateContentConfig;
56+
import com.google.genai.types.Part;
5657
import com.google.genai.types.Schema;
5758
import io.reactivex.rxjava3.core.Flowable;
5859
import io.reactivex.rxjava3.core.Maybe;
@@ -567,10 +568,11 @@ protected BaseLlmFlow determineLlmFlow() {
567568

568569
private void maybeSaveOutputToState(Event event) {
569570
if (outputKey().isPresent() && event.finalResponse() && event.content().isPresent()) {
570-
// Concatenate text from all parts.
571+
// Concatenate text from all parts, excluding thoughts.
571572
Object output;
572573
String rawResult =
573574
event.content().flatMap(Content::parts).orElse(ImmutableList.of()).stream()
575+
.filter(part -> !isThought(part))
574576
.map(part -> part.text().orElse(""))
575577
.collect(joining());
576578

@@ -602,9 +604,22 @@ private void maybeSaveOutputToState(Event event) {
602604
}
603605
}
604606

607+
private static boolean isThought(Part part) {
608+
return part.thought().isPresent() && part.thought().get();
609+
}
610+
605611
@Override
606612
protected Flowable<Event> runAsyncImpl(InvocationContext invocationContext) {
607-
return llmFlow.run(invocationContext).doOnNext(this::maybeSaveOutputToState);
613+
return llmFlow
614+
.run(invocationContext)
615+
.concatMap(
616+
event -> {
617+
this.maybeSaveOutputToState(event);
618+
if (invocationContext.shouldPauseInvocation(event)) {
619+
return Flowable.just(event).concatWith(Flowable.empty());
620+
}
621+
return Flowable.just(event);
622+
});
608623
}
609624

610625
@Override

core/src/main/java/com/google/adk/events/Event.java

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public class Event extends JsonBaseModel {
6161
private Optional<Boolean> interrupted = Optional.empty();
6262
private Optional<String> branch = Optional.empty();
6363
private Optional<GroundingMetadata> groundingMetadata = Optional.empty();
64+
private Optional<String> modelVersion = Optional.empty();
6465
private long timestamp;
6566

6667
private Event() {}
@@ -241,6 +242,16 @@ public void setGroundingMetadata(Optional<GroundingMetadata> groundingMetadata)
241242
this.groundingMetadata = groundingMetadata;
242243
}
243244

245+
/** The model version used to generate the response. */
246+
@JsonProperty("modelVersion")
247+
public Optional<String> modelVersion() {
248+
return modelVersion;
249+
}
250+
251+
public void setModelVersion(Optional<String> modelVersion) {
252+
this.modelVersion = modelVersion;
253+
}
254+
244255
/** The timestamp of the event. */
245256
@JsonProperty("timestamp")
246257
public long timestamp() {
@@ -336,6 +347,7 @@ public static class Builder {
336347
private Optional<Boolean> interrupted = Optional.empty();
337348
private Optional<String> branch = Optional.empty();
338349
private Optional<GroundingMetadata> groundingMetadata = Optional.empty();
350+
private Optional<String> modelVersion = Optional.empty();
339351
private Optional<Long> timestamp = Optional.empty();
340352

341353
@JsonCreator
@@ -558,6 +570,23 @@ Optional<GroundingMetadata> groundingMetadata() {
558570
return groundingMetadata;
559571
}
560572

573+
@CanIgnoreReturnValue
574+
@JsonProperty("modelVersion")
575+
public Builder modelVersion(@Nullable String value) {
576+
this.modelVersion = Optional.ofNullable(value);
577+
return this;
578+
}
579+
580+
@CanIgnoreReturnValue
581+
public Builder modelVersion(Optional<String> value) {
582+
this.modelVersion = value;
583+
return this;
584+
}
585+
586+
Optional<String> modelVersion() {
587+
return modelVersion;
588+
}
589+
561590
public Event build() {
562591
Event event = new Event();
563592
event.setId(id);
@@ -575,6 +604,7 @@ public Event build() {
575604
event.setInterrupted(interrupted);
576605
event.branch(branch);
577606
event.setGroundingMetadata(groundingMetadata);
607+
event.setModelVersion(modelVersion);
578608
event.setActions(actions().orElse(EventActions.builder().build()));
579609
event.setTimestamp(timestamp().orElse(Instant.now().toEpochMilli()));
580610
return event;
@@ -609,7 +639,8 @@ public Builder toBuilder() {
609639
.avgLogprobs(this.avgLogprobs)
610640
.interrupted(this.interrupted)
611641
.branch(this.branch)
612-
.groundingMetadata(this.groundingMetadata);
642+
.groundingMetadata(this.groundingMetadata)
643+
.modelVersion(this.modelVersion);
613644
if (this.timestamp != 0) {
614645
builder.timestamp(this.timestamp);
615646
}
@@ -640,7 +671,8 @@ public boolean equals(Object obj) {
640671
&& Objects.equals(avgLogprobs, other.avgLogprobs)
641672
&& Objects.equals(interrupted, other.interrupted)
642673
&& Objects.equals(branch, other.branch)
643-
&& Objects.equals(groundingMetadata, other.groundingMetadata);
674+
&& Objects.equals(groundingMetadata, other.groundingMetadata)
675+
&& Objects.equals(modelVersion, other.modelVersion);
644676
}
645677

646678
@Override
@@ -667,6 +699,7 @@ public int hashCode() {
667699
interrupted,
668700
branch,
669701
groundingMetadata,
702+
modelVersion,
670703
timestamp);
671704
}
672705
}

core/src/main/java/com/google/adk/flows/llmflows/AgentTransfer.java

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,27 @@ public Single<RequestProcessor.RequestProcessingResult> processRequest(
5454
request.toBuilder()
5555
.appendInstructions(
5656
ImmutableList.of(buildTargetAgentsInstructions(agent, transferTargets)));
57+
58+
// Note: this tool is not exposed to the LLM in GenerateContent request. It is there only to
59+
// serve as a backwards-compatible instance for users who depend on the exact name of
60+
// "transferToAgent".
61+
builder.appendTools(ImmutableList.of(createTransferToAgentTool("legacyTransferToAgent")));
62+
63+
FunctionTool agentTransferTool = createTransferToAgentTool("transferToAgent");
64+
agentTransferTool.processLlmRequest(builder, ToolContext.builder(context).build());
65+
return Single.just(
66+
RequestProcessor.RequestProcessingResult.create(builder.build(), ImmutableList.of()));
67+
}
68+
69+
private FunctionTool createTransferToAgentTool(String methodName) {
5770
Method transferToAgentMethod;
5871
try {
5972
transferToAgentMethod =
60-
AgentTransfer.class.getMethod("transferToAgent", String.class, ToolContext.class);
73+
AgentTransfer.class.getMethod(methodName, String.class, ToolContext.class);
6174
} catch (NoSuchMethodException e) {
6275
throw new IllegalStateException(e);
6376
}
64-
FunctionTool agentTransferTool = FunctionTool.create(transferToAgentMethod);
65-
agentTransferTool.processLlmRequest(builder, ToolContext.builder(context).build());
66-
return Single.just(
67-
RequestProcessor.RequestProcessingResult.create(builder.build(), ImmutableList.of()));
77+
return FunctionTool.create(transferToAgentMethod);
6878
}
6979

7080
/** Builds a string with the target agent’s name and description. */
@@ -159,4 +169,18 @@ public static void transferToAgent(
159169
EventActions eventActions = toolContext.eventActions();
160170
toolContext.setActions(eventActions.toBuilder().transferToAgent(agentName).build());
161171
}
172+
173+
/**
174+
* Backwards compatible transferToAgent that uses camel-case naming instead of the ADK's
175+
* snake_case convention.
176+
*
177+
* <p>It exists only to support users who already use literal "transferToAgent" function call to
178+
* instruct ADK to transfer the question to another agent.
179+
*/
180+
@Schema(name = "transferToAgent")
181+
public static void legacyTransferToAgent(
182+
@Schema(name = "agentName") String agentName,
183+
@Schema(optional = true) ToolContext toolContext) {
184+
transferToAgent(agentName, toolContext);
185+
}
162186
}

core/src/main/java/com/google/adk/flows/llmflows/BaseLlmFlow.java

Lines changed: 53 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -156,36 +156,9 @@ protected Flowable<Event> postprocess(
156156
}
157157

158158
return currentLlmResponse.flatMapPublisher(
159-
updatedResponse -> {
160-
Flowable<Event> processorEvents = Flowable.fromIterable(Iterables.concat(eventIterables));
161-
162-
if (updatedResponse.content().isEmpty()
163-
&& updatedResponse.errorCode().isEmpty()
164-
&& !updatedResponse.interrupted().orElse(false)
165-
&& !updatedResponse.turnComplete().orElse(false)) {
166-
return processorEvents;
167-
}
168-
169-
Event modelResponseEvent =
170-
buildModelResponseEvent(baseEventForLlmResponse, llmRequest, updatedResponse);
171-
172-
Flowable<Event> modelEventStream = Flowable.just(modelResponseEvent);
173-
174-
if (modelResponseEvent.functionCalls().isEmpty()) {
175-
return processorEvents.concatWith(modelEventStream);
176-
}
177-
178-
Maybe<Event> maybeFunctionCallEvent;
179-
if (context.runConfig().streamingMode() == StreamingMode.BIDI) {
180-
maybeFunctionCallEvent =
181-
Functions.handleFunctionCallsLive(context, modelResponseEvent, llmRequest.tools());
182-
} else {
183-
maybeFunctionCallEvent =
184-
Functions.handleFunctionCalls(context, modelResponseEvent, llmRequest.tools());
185-
}
186-
187-
return processorEvents.concatWith(modelEventStream).concatWith(maybeFunctionCallEvent);
188-
});
159+
updatedResponse ->
160+
buildPostprocessingEvents(
161+
updatedResponse, eventIterables, context, baseEventForLlmResponse, llmRequest));
189162
}
190163

191164
/**
@@ -231,15 +204,18 @@ private Flowable<LlmResponse> callLlm(
231204
.runOnModelErrorCallback(
232205
new CallbackContext(
233206
context, eventForCallbackUsage.actions()),
234-
llmRequest,
207+
llmRequestBuilder,
235208
exception)
236209
.switchIfEmpty(Single.error(exception))
237210
.toFlowable())
238211
.doOnNext(
239212
llmResp -> {
240213
try (Scope innerScope = llmCallSpan.makeCurrent()) {
241214
Telemetry.traceCallLlm(
242-
context, eventForCallbackUsage.id(), llmRequest, llmResp);
215+
context,
216+
eventForCallbackUsage.id(),
217+
llmRequestBuilder.build(),
218+
llmResp);
243219
}
244220
})
245221
.doOnError(
@@ -269,7 +245,7 @@ private Single<Optional<LlmResponse>> handleBeforeModelCallback(
269245
CallbackContext callbackContext = new CallbackContext(context, callbackEvent.actions());
270246

271247
Maybe<LlmResponse> pluginResult =
272-
context.pluginManager().runBeforeModelCallback(callbackContext, llmRequestBuilder.build());
248+
context.pluginManager().runBeforeModelCallback(callbackContext, llmRequestBuilder);
273249

274250
LlmAgent agent = (LlmAgent) context.agent();
275251

@@ -623,6 +599,45 @@ public void onError(Throwable e) {
623599
*
624600
* @return A fully constructed {@link Event} representing the LLM response.
625601
*/
602+
private Flowable<Event> buildPostprocessingEvents(
603+
LlmResponse updatedResponse,
604+
List<Iterable<Event>> eventIterables,
605+
InvocationContext context,
606+
Event baseEventForLlmResponse,
607+
LlmRequest llmRequest) {
608+
Flowable<Event> processorEvents = Flowable.fromIterable(Iterables.concat(eventIterables));
609+
if (updatedResponse.content().isEmpty()
610+
&& updatedResponse.errorCode().isEmpty()
611+
&& !updatedResponse.interrupted().orElse(false)
612+
&& !updatedResponse.turnComplete().orElse(false)) {
613+
return processorEvents;
614+
}
615+
616+
Event modelResponseEvent =
617+
buildModelResponseEvent(baseEventForLlmResponse, llmRequest, updatedResponse);
618+
if (modelResponseEvent.functionCalls().isEmpty()) {
619+
return processorEvents.concatWith(Flowable.just(modelResponseEvent));
620+
}
621+
622+
Maybe<Event> maybeFunctionResponseEvent =
623+
context.runConfig().streamingMode() == StreamingMode.BIDI
624+
? Functions.handleFunctionCallsLive(context, modelResponseEvent, llmRequest.tools())
625+
: Functions.handleFunctionCalls(context, modelResponseEvent, llmRequest.tools());
626+
627+
Flowable<Event> functionEvents =
628+
maybeFunctionResponseEvent.flatMapPublisher(
629+
functionResponseEvent -> {
630+
Optional<Event> toolConfirmationEvent =
631+
Functions.generateRequestConfirmationEvent(
632+
context, modelResponseEvent, functionResponseEvent);
633+
return toolConfirmationEvent.isPresent()
634+
? Flowable.just(toolConfirmationEvent.get(), functionResponseEvent)
635+
: Flowable.just(functionResponseEvent);
636+
});
637+
638+
return processorEvents.concatWith(Flowable.just(modelResponseEvent)).concatWith(functionEvents);
639+
}
640+
626641
private Event buildModelResponseEvent(
627642
Event baseEventForLlmResponse, LlmRequest llmRequest, LlmResponse llmResponse) {
628643
Event.Builder eventBuilder =
@@ -636,14 +651,18 @@ private Event buildModelResponseEvent(
636651
.groundingMetadata(llmResponse.groundingMetadata())
637652
.avgLogprobs(llmResponse.avgLogprobs())
638653
.finishReason(llmResponse.finishReason())
639-
.usageMetadata(llmResponse.usageMetadata());
654+
.usageMetadata(llmResponse.usageMetadata())
655+
.modelVersion(llmResponse.modelVersion());
640656

641657
Event event = eventBuilder.build();
642658

659+
logger.info("event: {} functionCalls: {}", event, event.functionCalls());
660+
643661
if (!event.functionCalls().isEmpty()) {
644662
Functions.populateClientFunctionCallId(event);
645663
Set<String> longRunningToolIds =
646664
Functions.getLongRunningFunctionCalls(event.functionCalls(), llmRequest.tools());
665+
logger.info("longRunningToolIds: {}", longRunningToolIds);
647666
if (!longRunningToolIds.isEmpty()) {
648667
event.setLongRunningToolIds(Optional.of(longRunningToolIds));
649668
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.adk.flows.llmflows;
17+
18+
/**
19+
* An app contains Resumability configuration for the agents.
20+
*
21+
* @param isResumable Whether the app is resumable.
22+
*/
23+
public record ResumabilityConfig(boolean isResumable) {
24+
25+
/** Creates a new {@code ResumabilityConfig} with resumability disabled. */
26+
public ResumabilityConfig() {
27+
this(false);
28+
}
29+
}

core/src/main/java/com/google/adk/models/LlmResponse.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ public abstract class LlmResponse extends JsonBaseModel {
102102
@JsonProperty("usageMetadata")
103103
public abstract Optional<GenerateContentResponseUsageMetadata> usageMetadata();
104104

105+
/** The model version used to generate the response. */
106+
@JsonProperty("modelVersion")
107+
public abstract Optional<String> modelVersion();
108+
105109
public abstract Builder toBuilder();
106110

107111
/** Builder for constructing {@link LlmResponse} instances. */
@@ -166,6 +170,11 @@ public abstract Builder usageMetadata(
166170
public abstract Builder usageMetadata(
167171
Optional<GenerateContentResponseUsageMetadata> usageMetadata);
168172

173+
@JsonProperty("modelVersion")
174+
public abstract Builder modelVersion(@Nullable String modelVersion);
175+
176+
public abstract Builder modelVersion(Optional<String> modelVersion);
177+
169178
@CanIgnoreReturnValue
170179
public final Builder response(GenerateContentResponse response) {
171180
Optional<List<Candidate>> candidatesOpt = response.candidates();
@@ -193,6 +202,7 @@ public final Builder response(GenerateContentResponse response) {
193202
}
194203
}
195204
this.usageMetadata(response.usageMetadata());
205+
this.modelVersion(response.modelVersion());
196206
return this;
197207
}
198208

0 commit comments

Comments
 (0)