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 f45461626..b5f80fbfe 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 @@ -406,6 +406,11 @@ private static boolean isEventBelongsToBranch(Optional invocationBranchO * @return A new list of events with the appropriate rearrangement. */ private static List rearrangeEventsForLatestFunctionResponse(List events) { + if (events.size() < 2) { + // No need to process, since there is no function_call. + return events; + } + // TODO: b/412663475 - Handle parallel function calls within the same event. Currently, this // throws an error. if (events.isEmpty() || Iterables.getLast(events).functionResponses().isEmpty()) { diff --git a/core/src/test/java/com/google/adk/flows/llmflows/ContentsTest.java b/core/src/test/java/com/google/adk/flows/llmflows/ContentsTest.java index b5df658ba..d555525f4 100644 --- a/core/src/test/java/com/google/adk/flows/llmflows/ContentsTest.java +++ b/core/src/test/java/com/google/adk/flows/llmflows/ContentsTest.java @@ -142,7 +142,9 @@ public void rearrangeLatest_multipleFRsForSameFCAsync_returnsMergedFR() { @Test public void rearrangeLatest_missingFCEvent_throwsException() { Event frEvent = createFunctionResponseEvent("fr1", "tool1", "call1"); - ImmutableList events = ImmutableList.of(createUserEvent("u1", "Query"), frEvent); + Event frEvent2 = createFunctionResponseEvent("fr2", "tool1", "call1"); + ImmutableList events = + ImmutableList.of(createUserEvent("u1", "Query"), frEvent, frEvent2); assertThrows(IllegalStateException.class, () -> runContentsProcessor(events)); } @@ -473,10 +475,12 @@ public void processRequest_includeContentsNone_asyncFRAcrossTurns_throwsExceptio Event fc1 = createFunctionCallEvent("fc1", "tool1", "call1"); Event u2 = createUserEvent("u2", "Query 2"); Event fr1 = createFunctionResponseEvent("fr1", "tool1", "call1"); // FR for fc1 + Event fr2 = createFunctionResponseEvent("fr2", "tool2", "call1"); // FR for fc2 - ImmutableList events = ImmutableList.of(u1, fc1, u2, fr1); + ImmutableList events = ImmutableList.of(u1, fc1, u2, fr1, fr2); - // The current turn starts from u2. fc1 is not in the sublist [u2, fr1], so rearrangement fails. + // The current turn starts from u2. fc1 is not in the sublist [u2, fr1, fr2], so rearrangement + // fails. IllegalStateException e = assertThrows( IllegalStateException.class, @@ -486,6 +490,19 @@ public void processRequest_includeContentsNone_asyncFRAcrossTurns_throwsExceptio .contains("No function call event found for function response IDs: [call1]"); } + @Test + public void processRequest_notEnoughEvents_returnsOriginalList() { + Event fr1 = + createFunctionCallAndResponseEvent( + "fr1", "tool1", "call1", ImmutableMap.of("result", "ok"), "user"); + + ImmutableList events = ImmutableList.of(fr1); + + List result = + runContentsProcessorWithIncludeContents(events, LlmAgent.IncludeContents.NONE, "A2A-agent"); + assertThat(result).isEmpty(); + } + @Test public void processRequest_includeContentsNone_asyncFRWithinTurn() { Event u1 = createUserEvent("u1", "Query 1"); @@ -883,13 +900,40 @@ private static Event createFunctionResponseEvent( .build(); } + private static Event createFunctionCallAndResponseEvent( + String id, String toolName, String callId, Map response, String author) { + return Event.builder() + .id(id) + .author(author) + .invocationId("invocationId") + .content( + Content.fromParts( + Part.builder() + .functionCall(FunctionCall.builder().name(toolName).id(callId).build()) + .build(), + Part.builder() + .functionResponse( + FunctionResponse.builder() + .name(toolName) + .id(callId) + .response(response) + .build()) + .build())) + .build(); + } + private List runContentsProcessor(List events) { return runContentsProcessorWithIncludeContents(events, LlmAgent.IncludeContents.DEFAULT); } private List runContentsProcessorWithIncludeContents( List events, LlmAgent.IncludeContents includeContents) { - LlmAgent agent = LlmAgent.builder().name(AGENT).includeContents(includeContents).build(); + return runContentsProcessorWithIncludeContents(events, includeContents, AGENT); + } + + private List runContentsProcessorWithIncludeContents( + List events, LlmAgent.IncludeContents includeContents, String agentName) { + LlmAgent agent = LlmAgent.builder().name(agentName).includeContents(includeContents).build(); Session session = sessionService.createSession("test-app", "test-user", null, "test-session").blockingGet(); session.events().addAll(events);