Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,19 @@ package com.google.adk.kt.summarizer
* @property summarizer The [EventSummarizer] used to produce the compaction summary. When `null`,
* the runner defaults it to an [LlmEventSummarizer] backed by the root agent's model, requiring
* the root to be an `LlmAgent` (otherwise it throws at construction).
* @property tokenThreshold The most recent prompt token count that triggers
* intra-invocation token-threshold (tail-retention) compaction before an LLM call. Must be
* strictly positive when set, and must be set together with [eventRetentionSize].
* @property eventRetentionSize The number of most recent events kept raw (un-compacted) when
* token-threshold compaction fires; the older events are summarized into a single event. Must be
* non-negative when set, and must be set together with [tokenThreshold].
*/
data class EventsCompactionConfig(
val compactionInterval: Int? = null,
val overlapSize: Int? = null,
val summarizer: EventSummarizer? = null,
val tokenThreshold: Int? = null,
val eventRetentionSize: Int? = null,
) {
init {
if (compactionInterval != null) {
Expand All @@ -46,8 +54,23 @@ data class EventsCompactionConfig(
"compactionInterval and overlapSize must be set together or both null " +
"(got compactionInterval=$compactionInterval, overlapSize=$overlapSize)."
}
if (tokenThreshold != null) {
require(tokenThreshold > 0) { "tokenThreshold must be > 0, but was $tokenThreshold." }
}
if (eventRetentionSize != null) {
require(eventRetentionSize >= 0) {
"eventRetentionSize must be >= 0, but was $eventRetentionSize."
}
}
require((tokenThreshold == null) == (eventRetentionSize == null)) {
"tokenThreshold and eventRetentionSize must be set together or both null " +
"(got tokenThreshold=$tokenThreshold, eventRetentionSize=$eventRetentionSize)."
}
}

/** Returns true when sliding-window compaction is fully configured. */
fun hasSlidingWindowConfig(): Boolean = compactionInterval != null && overlapSize != null

/** Returns true when token-threshold compaction is fully configured. */
fun hasTokenThresholdConfig(): Boolean = tokenThreshold != null && eventRetentionSize != null
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class EventsCompactionConfigTest {
val config = EventsCompactionConfig()

assertFalse(config.hasSlidingWindowConfig())
assertFalse(config.hasTokenThresholdConfig())
}

@Test
Expand Down Expand Up @@ -83,6 +84,77 @@ class EventsCompactionConfigTest {
}
}

@Test
fun construct_validTokenThreshold_succeeds() {
val config =
EventsCompactionConfig(
tokenThreshold = 50000,
eventRetentionSize = 5,
summarizer = NoopSummarizer,
)

assertTrue(config.hasTokenThresholdConfig())
assertFalse(config.hasSlidingWindowConfig())
assertEquals(50000, config.tokenThreshold)
assertEquals(5, config.eventRetentionSize)
}

@Test
fun construct_eventRetentionSizeZero_succeeds() {
val config = EventsCompactionConfig(tokenThreshold = 50000, eventRetentionSize = 0)

assertTrue(config.hasTokenThresholdConfig())
}

@Test
fun construct_bothStrategiesConfigured_succeeds() {
val config =
EventsCompactionConfig(
compactionInterval = 3,
overlapSize = 1,
tokenThreshold = 50000,
eventRetentionSize = 5,
)

assertTrue(config.hasSlidingWindowConfig())
assertTrue(config.hasTokenThresholdConfig())
}

@Test
fun construct_tokenThresholdZero_throwsIllegalArgumentException() {
assertFailsWith<IllegalArgumentException> {
EventsCompactionConfig(tokenThreshold = 0, eventRetentionSize = 5)
}
}

@Test
fun construct_tokenThresholdNegative_throwsIllegalArgumentException() {
assertFailsWith<IllegalArgumentException> {
EventsCompactionConfig(tokenThreshold = -1, eventRetentionSize = 5)
}
}

@Test
fun construct_eventRetentionSizeNegative_throwsIllegalArgumentException() {
assertFailsWith<IllegalArgumentException> {
EventsCompactionConfig(tokenThreshold = 50000, eventRetentionSize = -1)
}
}

@Test
fun construct_tokenThresholdWithoutEventRetentionSize_throwsIllegalArgumentException() {
assertFailsWith<IllegalArgumentException> {
EventsCompactionConfig(tokenThreshold = 50000, eventRetentionSize = null)
}
}

@Test
fun construct_eventRetentionSizeWithoutTokenThreshold_throwsIllegalArgumentException() {
assertFailsWith<IllegalArgumentException> {
EventsCompactionConfig(tokenThreshold = null, eventRetentionSize = 5)
}
}

private object NoopSummarizer : EventSummarizer {
override suspend fun summarizeEvents(events: List<Event>): Event? = null
}
Expand Down
Loading