From 99415afe044c57df832e15dfab9493a82df841ff Mon Sep 17 00:00:00 2001 From: Simon Su Date: Thu, 18 Dec 2025 19:39:37 +1100 Subject: [PATCH] Fix rearrangeEventsForAsyncFunctionResponsesInHistory to ensure function responses are merged --- .../google/adk/flows/llmflows/Contents.java | 3 +- .../adk/flows/llmflows/ContentsTest.java | 72 +++++++++++++++++-- 2 files changed, 67 insertions(+), 8 deletions(-) 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 0c415f1a..3001ab89 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 @@ -483,8 +483,7 @@ private static List rearrangeEventsForAsyncFunctionResponsesInHistory( for (int i = 0; i < events.size(); i++) { Event event = events.get(i); - // Skip response events that will be processed via responseEventsBuffer - if (processedResponseIndices.contains(i)) { + if (!event.functionResponses().isEmpty()) { continue; } 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 85895088..ff162e0b 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 @@ -203,9 +203,11 @@ public void rearrangeHistory_asyncFR_returnsRearrangedList() { public void rearrangeHistory_multipleFRsForSameFC_returnsMergedFR() { Event fcEvent = createFunctionCallEvent("fc1", "tool1", "call1"); Event frEvent1 = - createFunctionResponseEvent("fr1", "tool1", "call1", ImmutableMap.of("status", "running")); + createFunctionResponseEvent("fr1", "tool1", "call1", ImmutableMap.of("status", "pending")); Event frEvent2 = - createFunctionResponseEvent("fr2", "tool1", "call1", ImmutableMap.of("status", "done")); + createFunctionResponseEvent("fr2", "tool1", "call1", ImmutableMap.of("status", "running")); + Event frEvent3 = + createFunctionResponseEvent("fr3", "tool1", "call1", ImmutableMap.of("status", "done")); ImmutableList inputEvents = ImmutableList.of( createUserEvent("u1", "Query"), @@ -213,17 +215,75 @@ public void rearrangeHistory_multipleFRsForSameFC_returnsMergedFR() { createUserEvent("u2", "Wait"), frEvent1, createUserEvent("u3", "Done?"), - frEvent2); + frEvent2, + frEvent3, + createUserEvent("u4", "Follow up query")); List result = runContentsProcessor(inputEvents); - assertThat(result).hasSize(3); // u1, fc1, merged_fr + assertThat(result).hasSize(6); // u1, fc1, merged_fr, u2, u3, u4 assertThat(result.get(0)).isEqualTo(inputEvents.get(0).content().get()); - assertThat(result.get(1)).isEqualTo(inputEvents.get(1).content().get()); // Check merged event + assertThat(result.get(1)).isEqualTo(inputEvents.get(1).content().get()); // Check fcEvent Content mergedContent = result.get(2); assertThat(mergedContent.parts().get()).hasSize(1); assertThat(mergedContent.parts().get().get(0).functionResponse().get().response().get()) - .containsExactly("status", "done"); // Last FR wins + .containsExactly("status", "done"); // Last FR wins (frEvent3) + assertThat(result.get(3)).isEqualTo(inputEvents.get(2).content().get()); // u2 + assertThat(result.get(4)).isEqualTo(inputEvents.get(4).content().get()); // u3 + assertThat(result.get(5)).isEqualTo(inputEvents.get(7).content().get()); // u4 + } + + @Test + public void rearrangeHistory_multipleFRsForMultipleFC_returnsMergedFR() { + Event fcEvent1 = createFunctionCallEvent("fc1", "tool1", "call1"); + Event fcEvent2 = createFunctionCallEvent("fc2", "tool1", "call2"); + + Event frEvent1 = + createFunctionResponseEvent("fr1", "tool1", "call1", ImmutableMap.of("status", "pending")); + Event frEvent2 = + createFunctionResponseEvent("fr2", "tool1", "call1", ImmutableMap.of("status", "done")); + + Event frEvent3 = + createFunctionResponseEvent("fr3", "tool1", "call2", ImmutableMap.of("status", "pending")); + Event frEvent4 = + createFunctionResponseEvent("fr4", "tool1", "call2", ImmutableMap.of("status", "done")); + + ImmutableList inputEvents = + ImmutableList.of( + createUserEvent("u1", "I"), + fcEvent1, + createUserEvent("u2", "am"), + frEvent1, + createUserEvent("u3", "waiting"), + frEvent2, + createUserEvent("u4", "for"), + fcEvent2, + createUserEvent("u5", "you"), + frEvent3, + createUserEvent("u6", "to"), + frEvent4, + createUserEvent("u7", "Follow up query")); + + List result = runContentsProcessor(inputEvents); + + assertThat(result).hasSize(11); // u1, fc1, frEvent2, u2, u3, u4, fc2, frEvent4, u5, u6, u7 + assertThat(result.get(0)).isEqualTo(inputEvents.get(0).content().get()); // u1 + assertThat(result.get(1)).isEqualTo(inputEvents.get(1).content().get()); // fc1 + Content mergedContent = result.get(2); + assertThat(mergedContent.parts().get()).hasSize(1); + assertThat(mergedContent.parts().get().get(0).functionResponse().get().response().get()) + .containsExactly("status", "done"); // Last FR wins (frEvent2) + assertThat(result.get(3)).isEqualTo(inputEvents.get(2).content().get()); // u2 + assertThat(result.get(4)).isEqualTo(inputEvents.get(4).content().get()); // u3 + assertThat(result.get(5)).isEqualTo(inputEvents.get(6).content().get()); // u4 + assertThat(result.get(6)).isEqualTo(inputEvents.get(7).content().get()); // fc2 + Content mergedContent2 = result.get(7); + assertThat(mergedContent2.parts().get()).hasSize(1); + assertThat(mergedContent2.parts().get().get(0).functionResponse().get().response().get()) + .containsExactly("status", "done"); // Last FR wins (frEvent4) + assertThat(result.get(8)).isEqualTo(inputEvents.get(8).content().get()); // u5 + assertThat(result.get(9)).isEqualTo(inputEvents.get(10).content().get()); // u6 + assertThat(result.get(10)).isEqualTo(inputEvents.get(12).content().get()); // u7 } @Test