Skip to content

Commit d616ffb

Browse files
wikaaaaacopybara-github
authored andcommitted
feat: wire sliding-window context compaction into the runner
PiperOrigin-RevId: 932346604
1 parent ac8b9e6 commit d616ffb

6 files changed

Lines changed: 566 additions & 36 deletions

File tree

core/src/commonMain/kotlin/com/google/adk/kt/apps/App.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.google.adk.kt.apps
1818

1919
import com.google.adk.kt.agents.BaseAgent
20+
import com.google.adk.kt.summarizer.EventsCompactionConfig
2021

2122
/**
2223
* Represents an LLM-backed agentic application.
@@ -31,8 +32,14 @@ import com.google.adk.kt.agents.BaseAgent
3132
*
3233
* @property appName The application name.
3334
* @property rootAgent The root agent of the application's agent tree.
35+
* @property eventsCompactionConfig Optional configuration controlling context-compaction strategies
36+
* for sessions of this application. When `null`, no compaction runs.
3437
*/
35-
data class App(val appName: String, val rootAgent: BaseAgent) {
38+
data class App(
39+
val appName: String,
40+
val rootAgent: BaseAgent,
41+
val eventsCompactionConfig: EventsCompactionConfig? = null,
42+
) {
3643
init {
3744
require(IDENTIFIER_REGEX.matches(appName)) {
3845
"Invalid app name '$appName': must be a valid identifier consisting of letters, digits, " +

core/src/commonMain/kotlin/com/google/adk/kt/runners/AbstractRunner.kt

Lines changed: 90 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package com.google.adk.kt.runners
2020

2121
import com.google.adk.kt.agents.BaseAgent
2222
import com.google.adk.kt.agents.InvocationContext
23+
import com.google.adk.kt.agents.LlmAgent
2324
import com.google.adk.kt.agents.ResumabilityConfig
2425
import com.google.adk.kt.agents.RunConfig
2526
import com.google.adk.kt.agents.findAgent
@@ -41,6 +42,9 @@ import com.google.adk.kt.sessions.Session
4142
import com.google.adk.kt.sessions.SessionKey
4243
import com.google.adk.kt.sessions.SessionService
4344
import com.google.adk.kt.sessions.State
45+
import com.google.adk.kt.summarizer.EventsCompactionConfig
46+
import com.google.adk.kt.summarizer.LlmEventSummarizer
47+
import com.google.adk.kt.summarizer.SlidingWindowEventCompactor
4448
import com.google.adk.kt.telemetry.trace
4549
import com.google.adk.kt.types.Blob
4650
import com.google.adk.kt.types.Content
@@ -53,39 +57,64 @@ import kotlinx.coroutines.flow.map
5357
import kotlinx.coroutines.flow.toList
5458
import kotlinx.coroutines.runBlocking
5559

56-
/**
57-
* An abstract base class for [Runner] implementations that provides common orchestration logic.
58-
*
59-
* Subclasses may be constructed either from explicit fields or from an [App] (via the [App]-based
60-
* constructor), which supplies the [App.appName] and [App.rootAgent].
61-
*/
62-
abstract class AbstractRunner(
63-
override val appName: String,
64-
override val agent: BaseAgent,
65-
override val sessionService: SessionService,
66-
override val artifactService: ArtifactService?,
67-
override val memoryService: MemoryService?,
68-
override val pluginManager: PluginManager,
69-
override val resumabilityConfig: ResumabilityConfig = ResumabilityConfig(),
70-
) : Runner {
71-
72-
/** Creates a runner from an [App], taking its [App.appName] and [App.rootAgent]. */
60+
/** An abstract base class for [Runner] implementations that provides common orchestration logic. */
61+
abstract class AbstractRunner : Runner {
62+
63+
val app: App?
64+
final override val appName: String
65+
final override val agent: BaseAgent
66+
final override val sessionService: SessionService
67+
final override val artifactService: ArtifactService?
68+
final override val memoryService: MemoryService?
69+
final override val pluginManager: PluginManager
70+
final override val resumabilityConfig: ResumabilityConfig
71+
72+
/** Creates a runner from explicit fields, not using an [App]. */
73+
constructor(
74+
appName: String,
75+
agent: BaseAgent,
76+
sessionService: SessionService,
77+
artifactService: ArtifactService?,
78+
memoryService: MemoryService?,
79+
pluginManager: PluginManager,
80+
resumabilityConfig: ResumabilityConfig = ResumabilityConfig(),
81+
) {
82+
this.appName = appName
83+
this.agent = agent
84+
this.sessionService = sessionService
85+
this.artifactService = artifactService
86+
this.memoryService = memoryService
87+
this.pluginManager = pluginManager
88+
this.resumabilityConfig = resumabilityConfig
89+
this.app = null
90+
}
91+
92+
/**
93+
* Creates a runner from an [App]. The compaction config is resolved at construction (failing fast
94+
* if a default summarizer is required but the root agent is not an [LlmAgent]) and stored back on
95+
* the [app], so [App.eventsCompactionConfig] returns the effective config.
96+
*/
7397
constructor(
7498
app: App,
7599
sessionService: SessionService,
76100
artifactService: ArtifactService?,
77101
memoryService: MemoryService?,
78102
pluginManager: PluginManager,
79103
resumabilityConfig: ResumabilityConfig = ResumabilityConfig(),
80-
) : this(
81-
app.appName,
82-
app.rootAgent,
83-
sessionService,
84-
artifactService,
85-
memoryService,
86-
pluginManager,
87-
resumabilityConfig,
88-
)
104+
) {
105+
this.appName = app.appName
106+
this.agent = app.rootAgent
107+
this.sessionService = sessionService
108+
this.artifactService = artifactService
109+
this.memoryService = memoryService
110+
this.pluginManager = pluginManager
111+
this.resumabilityConfig = resumabilityConfig
112+
this.app =
113+
app.copy(
114+
eventsCompactionConfig =
115+
resolveEventsCompactionConfig(app.rootAgent, app.eventsCompactionConfig)
116+
)
117+
}
89118

90119
/**
91120
* Main entry method to run the agent in this runner.
@@ -125,6 +154,10 @@ abstract class AbstractRunner(
125154

126155
// 4. Run agent with plugins
127156
emitAll(runAgentWithPlugins(context))
157+
158+
// 5. Post-invocation context compaction. Runs once the agent has finished emitting and all
159+
// its events have been appended to `session`.
160+
runPostInvocationCompaction(session)
128161
}
129162
.trace("invocation")
130163

@@ -680,7 +713,38 @@ abstract class AbstractRunner(
680713
return "e-" + Uuid.random()
681714
}
682715

716+
/**
717+
* Runs post-invocation sliding-window context compaction over [session] when configured. A no-op
718+
* when no compaction config was supplied or sliding-window compaction is not configured. The
719+
* compactor appends a single summary [Event] to [session] (via [sessionService]) when the
720+
* configured invocation interval is reached.
721+
*/
722+
private suspend fun runPostInvocationCompaction(session: Session) {
723+
val config = app?.eventsCompactionConfig ?: return
724+
if (!config.hasSlidingWindowConfig()) return
725+
SlidingWindowEventCompactor(config).compact(session, sessionService)
726+
}
727+
683728
private companion object {
684729
private val logger = LoggerFactory.getLogger(AbstractRunner::class)
730+
731+
/**
732+
* Returns [config] with a default [LlmEventSummarizer] injected when compaction is configured
733+
* but no summarizer was supplied. The default summarizer uses [rootAgent]'s model, so
734+
* [rootAgent] must be an [LlmAgent] in that case; otherwise an [IllegalArgumentException] is
735+
* thrown. Returns [config] unchanged when it is `null` or already carries a summarizer. Any
736+
* configured compaction strategy needs a summarizer, so this does not depend on which strategy
737+
* is set.
738+
*/
739+
private fun resolveEventsCompactionConfig(
740+
rootAgent: BaseAgent,
741+
config: EventsCompactionConfig?,
742+
): EventsCompactionConfig? {
743+
if (config == null || config.summarizer != null) return config
744+
val model =
745+
(rootAgent as? LlmAgent)?.model
746+
?: throw IllegalArgumentException("No BaseLlm model available for event compaction")
747+
return config.copy(summarizer = LlmEventSummarizer(model))
748+
}
685749
}
686750
}

core/src/commonTest/kotlin/com/google/adk/kt/apps/AppTest.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616

1717
package com.google.adk.kt.apps
1818

19+
import com.google.adk.kt.summarizer.EventsCompactionConfig
1920
import com.google.adk.kt.testing.DummyAgent
2021
import kotlin.test.Test
2122
import kotlin.test.assertEquals
2223
import kotlin.test.assertFailsWith
24+
import kotlin.test.assertNull
2325
import kotlin.test.assertSame
2426

2527
class AppTest {
@@ -34,6 +36,22 @@ class AppTest {
3436
assertSame(agent, app.rootAgent)
3537
}
3638

39+
@Test
40+
fun construct_noEventsCompactionConfig_defaultsToNull() {
41+
val app = App(appName = "my_app", rootAgent = DummyAgent())
42+
43+
assertNull(app.eventsCompactionConfig)
44+
}
45+
46+
@Test
47+
fun construct_withEventsCompactionConfig_exposesIt() {
48+
val config = EventsCompactionConfig(compactionInterval = 2, overlapSize = 1)
49+
50+
val app = App(appName = "my_app", rootAgent = DummyAgent(), eventsCompactionConfig = config)
51+
52+
assertSame(config, app.eventsCompactionConfig)
53+
}
54+
3755
@Test
3856
fun construct_emptyName_throwsIllegalArgumentException() {
3957
assertFailsWith<IllegalArgumentException> { App(appName = "", rootAgent = DummyAgent()) }

0 commit comments

Comments
 (0)