Skip to content
Merged
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
29 changes: 27 additions & 2 deletions core/src/main/java/com/google/adk/events/EventActions.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class EventActions {
private ConcurrentMap<String, ToolConfirmation> requestedToolConfirmations =
new ConcurrentHashMap<>();
private Optional<Boolean> endInvocation = Optional.empty();
private Optional<EventCompaction> compaction = Optional.empty();

/** Default constructor for Jackson. */
public EventActions() {}
Expand Down Expand Up @@ -139,6 +140,15 @@ public void setEndInvocation(boolean endInvocation) {
this.endInvocation = Optional.of(endInvocation);
}

@JsonProperty("compaction")
public Optional<EventCompaction> compaction() {
return compaction;
}

public void setCompaction(Optional<EventCompaction> compaction) {
this.compaction = compaction;
}

public static Builder builder() {
return new Builder();
}
Expand All @@ -162,7 +172,8 @@ public boolean equals(Object o) {
&& Objects.equals(escalate, that.escalate)
&& Objects.equals(requestedAuthConfigs, that.requestedAuthConfigs)
&& Objects.equals(requestedToolConfirmations, that.requestedToolConfirmations)
&& Objects.equals(endInvocation, that.endInvocation);
&& Objects.equals(endInvocation, that.endInvocation)
&& Objects.equals(compaction, that.compaction);
}

@Override
Expand All @@ -175,7 +186,8 @@ public int hashCode() {
escalate,
requestedAuthConfigs,
requestedToolConfirmations,
endInvocation);
endInvocation,
compaction);
}

/** Builder for {@link EventActions}. */
Expand All @@ -190,6 +202,7 @@ public static class Builder {
private ConcurrentMap<String, ToolConfirmation> requestedToolConfirmations =
new ConcurrentHashMap<>();
private Optional<Boolean> endInvocation = Optional.empty();
private Optional<EventCompaction> compaction = Optional.empty();

public Builder() {}

Expand All @@ -203,6 +216,7 @@ private Builder(EventActions eventActions) {
this.requestedToolConfirmations =
new ConcurrentHashMap<>(eventActions.requestedToolConfirmations());
this.endInvocation = eventActions.endInvocation();
this.compaction = eventActions.compaction();
}

@CanIgnoreReturnValue
Expand Down Expand Up @@ -262,6 +276,13 @@ public Builder endInvocation(boolean endInvocation) {
return this;
}

@CanIgnoreReturnValue
@JsonProperty("compaction")
public Builder compaction(EventCompaction value) {
this.compaction = Optional.ofNullable(value);
return this;
}

@CanIgnoreReturnValue
public Builder merge(EventActions other) {
if (other.skipSummarization().isPresent()) {
Expand All @@ -288,6 +309,9 @@ public Builder merge(EventActions other) {
if (other.endInvocation().isPresent()) {
this.endInvocation = other.endInvocation();
}
if (other.compaction().isPresent()) {
this.compaction = other.compaction();
}
return this;
}

Expand All @@ -301,6 +325,7 @@ public EventActions build() {
eventActions.setRequestedAuthConfigs(this.requestedAuthConfigs);
eventActions.setRequestedToolConfirmations(this.requestedToolConfirmations);
eventActions.setEndInvocation(this.endInvocation);
eventActions.setCompaction(this.compaction);
return eventActions;
}
}
Expand Down
47 changes: 47 additions & 0 deletions core/src/main/java/com/google/adk/events/EventCompaction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.google.adk.events;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.google.auto.value.AutoValue;
import com.google.genai.types.Content;

/** The compaction of the events. */
@AutoValue
@JsonDeserialize(builder = EventCompaction.Builder.class)
public abstract class EventCompaction {

@JsonProperty("startTimestamp")
public abstract long startTimestamp();

@JsonProperty("endTimestamp")
public abstract long endTimestamp();

@JsonProperty("compactedContent")
public abstract Content compactedContent();

public static Builder builder() {
return new AutoValue_EventCompaction.Builder();
}

/** Builder for {@link EventCompaction}. */
@AutoValue.Builder
public abstract static class Builder {

@JsonCreator
static Builder create() {
return builder();
}

@JsonProperty("startTimestamp")
public abstract Builder startTimestamp(long startTimestamp);

@JsonProperty("endTimestamp")
public abstract Builder endTimestamp(long endTimestamp);

@JsonProperty("compactedContent")
public abstract Builder compactedContent(Content compactedContent);

public abstract EventCompaction build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.google.adk.summarizer;

import com.google.adk.events.Event;
import io.reactivex.rxjava3.core.Maybe;
import java.util.List;

/** Base interface for producing events summary. */
public interface BaseEventSummarizer {

/**
* Compact a list of events into a single event.
*
* <p>If compaction failed, return {@link Maybe#empty()}. Otherwise, compact into a content and
* return it.
*
* <p>This method will summarize the events and return a new summary event indicating the range of
* events it summarized.
*
* @param events Events to compact.
* @return The new compacted event, or {@link Maybe#empty()} if no compaction happened.
*/
Maybe<Event> summarizeEvents(List<Event> events);
}
20 changes: 20 additions & 0 deletions core/src/main/java/com/google/adk/summarizer/EventCompactor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.google.adk.summarizer;

import com.google.adk.events.Event;
import com.google.adk.sessions.BaseSessionService;
import com.google.adk.sessions.Session;
import io.reactivex.rxjava3.core.Completable;

/** Base interface for compacting events. */
public interface EventCompactor {

/**
* Compacts events in the given session. If there is compaction happened, the new compaction event
* will be appended to the given {@link BaseSessionService}.
*
* @param session the session containing the events to be compacted.
* @param sessionService the session service for appending the new compaction event.
* @return the {@link Event} containing the events summary.
*/
Completable compact(Session session, BaseSessionService sessionService);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.google.adk.summarizer;

import java.util.Optional;

/**
* Configuration for event compaction.
*
* @param compactionInterval The number of <b>new</b> user-initiated invocations that, once fully
* represented in the session's events, will trigger a compaction.
* @param overlapSize The number of preceding invocations to include from the end of the last
* compacted range. This creates an overlap between consecutive compacted summaries, maintaining
* context.
* @param summarizer An optional event summarizer to use for compaction.
*/
public record EventsCompactionConfig(
int compactionInterval, int overlapSize, Optional<BaseEventSummarizer> summarizer) {

public EventsCompactionConfig(int compactionInterval, int overlapSize) {
this(compactionInterval, overlapSize, Optional.empty());
}
}
101 changes: 101 additions & 0 deletions core/src/main/java/com/google/adk/summarizer/LlmEventSummarizer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package com.google.adk.summarizer;

import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.joining;

import com.google.adk.events.Event;
import com.google.adk.events.EventActions;
import com.google.adk.events.EventCompaction;
import com.google.adk.models.BaseLlm;
import com.google.adk.models.LlmRequest;
import com.google.common.collect.ImmutableList;
import com.google.genai.types.Content;
import com.google.genai.types.Part;
import io.reactivex.rxjava3.core.Maybe;
import java.util.List;
import java.util.Optional;

/** An LLM-based event summarizer for sliding window compaction. */
public final class LlmEventSummarizer implements BaseEventSummarizer {

private static final String DEFAULT_PROMPT_TEMPLATE =
"""
The following is a conversation history between a user and an AI \
agent. Please summarize the conversation, focusing on key \
information and decisions made, as well as any unresolved \
questions or tasks. The summary should be concise and capture the \
essence of the interaction.

{conversation_history}
""";

private final BaseLlm baseLlm;
private final String promptTemplate;

public LlmEventSummarizer(BaseLlm baseLlm) {
this(baseLlm, DEFAULT_PROMPT_TEMPLATE);
}

public LlmEventSummarizer(BaseLlm baseLlm, String promptTemplate) {
this.baseLlm = baseLlm;
this.promptTemplate = promptTemplate;
}

@Override
public Maybe<Event> summarizeEvents(List<Event> events) {
if (events.isEmpty()) {
return Maybe.empty();
}

String conversationHistory = formatEventsForPrompt(events);
String prompt = promptTemplate.replace("{conversation_history}", conversationHistory);

LlmRequest llmRequest =
LlmRequest.builder()
.model(baseLlm.model())
.contents(
ImmutableList.of(
Content.builder()
.role("user")
.parts(ImmutableList.of(Part.fromText(prompt)))
.build()))
.build();

return baseLlm
.generateContent(llmRequest, false)
.firstElement()
.flatMap(
llmResponse ->
Maybe.fromOptional(
llmResponse
.content()
.map(content -> content.toBuilder().role("model").build())
.map(
summaryContent ->
EventCompaction.builder()
.startTimestamp(events.get(0).timestamp())
.endTimestamp(events.get(events.size() - 1).timestamp())
.compactedContent(summaryContent)
.build())
.map(
compaction ->
Event.builder()
.author("user")
.actions(EventActions.builder().compaction(compaction).build())
.invocationId(Event.generateEventId())
.build())));
}

private String formatEventsForPrompt(List<Event> events) {
return events.stream()
.flatMap(
event ->
event.content().flatMap(Content::parts).stream()
.flatMap(List::stream)
.map(Part::text)
.flatMap(Optional::stream)
.filter(not(String::isEmpty))
.map(text -> event.author() + ": " + text))
.collect(joining("\\n"));
}
}
Loading