Skip to content

Commit 55144ac

Browse files
google-genai-botcopybara-github
authored andcommitted
feat: Support function calls in LLM event summarizer
The summarizer now correctly formats `FunctionCall` and `FunctionResponse` parts into the conversation history, which allows the model to be aware of tool usage when summarizing the conversation. PiperOrigin-RevId: 860168537
1 parent 957892e commit 55144ac

2 files changed

Lines changed: 93 additions & 45 deletions

File tree

core/src/main/java/com/google/adk/summarizer/LlmEventSummarizer.java

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@
1919
import static java.util.function.Predicate.not;
2020
import static java.util.stream.Collectors.joining;
2121

22+
import com.google.adk.JsonBaseModel;
2223
import com.google.adk.events.Event;
2324
import com.google.adk.events.EventActions;
2425
import com.google.adk.events.EventCompaction;
2526
import com.google.adk.models.BaseLlm;
2627
import com.google.adk.models.LlmRequest;
2728
import com.google.common.collect.ImmutableList;
29+
import com.google.common.collect.ImmutableMap;
2830
import com.google.genai.types.Content;
31+
import com.google.genai.types.FunctionCall;
32+
import com.google.genai.types.FunctionResponse;
2933
import com.google.genai.types.Part;
3034
import io.reactivex.rxjava3.core.Maybe;
3135
import java.util.List;
@@ -108,10 +112,40 @@ private String formatEventsForPrompt(List<Event> events) {
108112
event ->
109113
event.content().flatMap(Content::parts).stream()
110114
.flatMap(List::stream)
111-
.map(Part::text)
112-
.flatMap(Optional::stream)
113-
.filter(not(String::isEmpty))
114-
.map(text -> event.author() + ": " + text))
115-
.collect(joining("\\n"));
115+
.map(
116+
part ->
117+
formatPartForPrompt(
118+
event.content().flatMap(Content::role).orElse(event.author()),
119+
part))
120+
.flatMap(Optional::stream))
121+
.collect(joining("\n"));
122+
}
123+
124+
private String toJson(Object object) {
125+
try {
126+
return JsonBaseModel.getMapper().writeValueAsString(object);
127+
} catch (Exception e) {
128+
return String.valueOf(object);
129+
}
130+
}
131+
132+
private Optional<String> formatPartForPrompt(String role, Part part) {
133+
return part.text()
134+
.filter(not(String::isEmpty))
135+
.map(t -> role + ": " + t)
136+
.or(() -> part.functionCall().map(f -> formatFunctionCallToString(role, f)))
137+
.or(() -> part.functionResponse().map(f -> formatFunctionResponseToString(role, f)));
138+
}
139+
140+
private String formatFunctionCallToString(String role, FunctionCall f) {
141+
return String.format(
142+
"%s: [FUNCTION_CALL: %s(%s)]",
143+
role, f.name().orElse("unknown"), toJson(f.args().orElse(ImmutableMap.of())));
144+
}
145+
146+
private String formatFunctionResponseToString(String role, FunctionResponse f) {
147+
return String.format(
148+
"%s: [FUNCTION_RESPONSE: %s -> %s]",
149+
role, f.name().orElse("unknown"), toJson(f.response().orElse(ImmutableMap.of())));
116150
}
117151
}

core/src/test/java/com/google/adk/summarizer/LlmEventSummarizerTest.java

Lines changed: 54 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@
2828
import com.google.adk.models.LlmRequest;
2929
import com.google.adk.models.LlmResponse;
3030
import com.google.common.collect.ImmutableList;
31+
import com.google.common.collect.ImmutableMap;
3132
import com.google.genai.types.Content;
3233
import com.google.genai.types.FunctionCall;
3334
import com.google.genai.types.FunctionResponse;
3435
import com.google.genai.types.Part;
3536
import io.reactivex.rxjava3.core.Flowable;
36-
import java.util.HashMap;
37+
import java.util.Map;
3738
import org.junit.Before;
3839
import org.junit.Rule;
3940
import org.junit.Test;
@@ -74,7 +75,7 @@ public void setUp() {
7475
public void summarizeEvents_success() {
7576
ImmutableList<Event> events =
7677
ImmutableList.of(createEvent(1L, "Hello", "user"), createEvent(2L, "Hi there!", "model"));
77-
String expectedConversationHistory = "user: Hello\\nmodel: Hi there!";
78+
String expectedConversationHistory = "user: Hello\nmodel: Hi there!";
7879
String expectedPrompt =
7980
DEFAULT_PROMPT_TEMPLATE.replace("{conversation_history}", expectedConversationHistory);
8081
LlmResponse mockLlmResponse =
@@ -148,47 +149,25 @@ public void summarizeEvents_formatsEventsForPromptCorrectly() {
148149
.invocationId("id2")
149150
.build(),
150151
// Event with function call
151-
Event.builder()
152-
.timestamp(7L)
153-
.author("model")
154-
.content(
155-
Content.builder()
156-
.parts(
157-
ImmutableList.of(
158-
Part.builder()
159-
.functionCall(
160-
FunctionCall.builder()
161-
.name("tool")
162-
.args(new HashMap<>())
163-
.build())
164-
.build()))
165-
.build())
166-
.invocationId("id3")
167-
.build(),
152+
createFunctionCallEvent(7L, "id3", "tool", ImmutableMap.of("key", "value")),
168153
// Event with function response
169-
Event.builder()
170-
.timestamp(8L)
171-
.author("model")
172-
.content(
173-
Content.builder()
174-
.parts(
175-
ImmutableList.of(
176-
Part.builder()
177-
.functionResponse(
178-
FunctionResponse.builder()
179-
.name("tool")
180-
.response(new HashMap<>())
181-
.build())
182-
.build()))
183-
.build())
184-
.invocationId("id4")
185-
.build());
154+
createFunctionResponseEvent(8L, "id4", "tool", ImmutableMap.of("status", "ok")),
155+
// Event with function call (add)
156+
createFunctionCallEvent(9L, "id5", "add", ImmutableMap.of("a", 20, "b", 22)),
157+
// Event with primitive function response
158+
createFunctionResponseEvent(10L, "id6", "add", ImmutableMap.of("result", 42)));
186159

187160
String expectedFormattedHistory =
188-
"user: User says...\\n"
189-
+ "model: Model replies...\\n"
190-
+ "user: Another user input\\n"
191-
+ "model: More model text";
161+
"""
162+
user: User says...
163+
model: Model replies...
164+
user: Another user input
165+
model: More model text
166+
model: [FUNCTION_CALL: tool({"key":"value"})]
167+
model: [FUNCTION_RESPONSE: tool -> {"status":"ok"}]
168+
model: [FUNCTION_CALL: add({"a":20,"b":22})]
169+
model: [FUNCTION_RESPONSE: add -> {"result":42}]\
170+
""";
192171
String expectedPrompt =
193172
DEFAULT_PROMPT_TEMPLATE.replace("{conversation_history}", expectedFormattedHistory);
194173

@@ -215,4 +194,39 @@ private Event createEvent(long timestamp, String text, String author) {
215194
.invocationId(Event.generateEventId())
216195
.build();
217196
}
197+
198+
private Event createFunctionCallEvent(
199+
long timestamp, String invocationId, String name, Map<String, Object> args) {
200+
return Event.builder()
201+
.timestamp(timestamp)
202+
.author("model")
203+
.content(
204+
Content.builder()
205+
.parts(
206+
ImmutableList.of(
207+
Part.builder()
208+
.functionCall(FunctionCall.builder().name(name).args(args).build())
209+
.build()))
210+
.build())
211+
.invocationId(invocationId)
212+
.build();
213+
}
214+
215+
private Event createFunctionResponseEvent(
216+
long timestamp, String invocationId, String name, Map<String, Object> response) {
217+
return Event.builder()
218+
.timestamp(timestamp)
219+
.author("model")
220+
.content(
221+
Content.builder()
222+
.parts(
223+
ImmutableList.of(
224+
Part.builder()
225+
.functionResponse(
226+
FunctionResponse.builder().name(name).response(response).build())
227+
.build()))
228+
.build())
229+
.invocationId(invocationId)
230+
.build();
231+
}
218232
}

0 commit comments

Comments
 (0)