1- package ai.koog.agents.features.debugger. feature
1+ package ai.koog.agents.core. feature.debugger
22
3+ import ai.koog.agents.core.agent.context.AIAgentContext
4+ import ai.koog.agents.core.agent.context.featureOrThrow
35import ai.koog.agents.core.agent.entity.AIAgentGraphStrategy
46import ai.koog.agents.core.agent.entity.AIAgentStorageKey
57import ai.koog.agents.core.annotation.InternalAgentsApi
68import ai.koog.agents.core.environment.ReceivedToolResult
9+ import ai.koog.agents.core.feature.AIAgentFunctionalFeature
710import ai.koog.agents.core.feature.AIAgentGraphFeature
8- import ai.koog.agents.core.feature.debugger.DebuggerConfig
9- import ai.koog.agents.core.system.readEnvironmentVariable
10- import ai.koog.agents.core.system.readVMOption
11+ import ai.koog.agents.core.feature.debugger.writer.DebuggerFeatureMessageRemoteWriter
1112import ai.koog.agents.core.feature.model.events.AgentClosingEvent
1213import ai.koog.agents.core.feature.model.events.AgentCompletedEvent
1314import ai.koog.agents.core.feature.model.events.AgentExecutionFailedEvent
@@ -29,11 +30,14 @@ import ai.koog.agents.core.feature.model.events.ToolCallStartingEvent
2930import ai.koog.agents.core.feature.model.events.ToolValidationFailedEvent
3031import ai.koog.agents.core.feature.model.events.startNodeToGraph
3132import ai.koog.agents.core.feature.model.toAgentError
33+ import ai.koog.agents.core.feature.pipeline.AIAgentFunctionalPipeline
3234import ai.koog.agents.core.feature.pipeline.AIAgentGraphPipeline
35+ import ai.koog.agents.core.feature.pipeline.AIAgentPipeline
3336import ai.koog.agents.core.feature.remote.server.config.DefaultServerConnectionConfig
37+ import ai.koog.agents.core.system.getEnvironmentVariableOrNull
38+ import ai.koog.agents.core.system.getVMOptionOrNull
3439import ai.koog.agents.core.tools.Tool
3540import ai.koog.agents.core.utils.SerializationUtils
36- import ai.koog.agents.core.feature.debugger.writer.DebuggerFeatureMessageRemoteWriter
3741import ai.koog.prompt.llm.toModelInfo
3842import io.github.oshai.kotlinlogging.KotlinLogging
3943import kotlinx.serialization.json.JsonElement
@@ -52,13 +56,18 @@ import kotlin.time.toDuration
5256 * This feature serves as a debugging tool for analyzing the AI agent's behavior and
5357 * interactions with its components, providing insights into the execution flow and
5458 * potential issues.
59+ *
60+ * @property port The port number on which the debugger server is listening for connections.
61+ * @property awaitInitialConnectionTimeout The timeout duration for the debugger server to wait for a connection.
5562 */
56- public class Debugger {
63+ public class Debugger ( public val port : Int , public val awaitInitialConnectionTimeout : Duration ? = null ) {
5764
5865 /* *
5966 * Companion object implementing agent feature, handling [Debugger] creation and installation.
6067 */
61- public companion object Feature : AIAgentGraphFeature<DebuggerConfig, Debugger> {
68+ public companion object Feature :
69+ AIAgentGraphFeature <DebuggerConfig , Debugger >,
70+ AIAgentFunctionalFeature <DebuggerConfig , Debugger > {
6271
6372 private val logger = KotlinLogging .logger { }
6473
@@ -89,20 +98,45 @@ public class Debugger {
8998
9099 override fun createInitialConfig (): DebuggerConfig = DebuggerConfig ()
91100
92- override fun install (
93- config : DebuggerConfig ,
94- pipeline : AIAgentGraphPipeline ,
95- ): Debugger {
101+ override fun install (config : DebuggerConfig , pipeline : AIAgentGraphPipeline ): Debugger {
96102 logger.debug { " Debugger Feature. Start installing feature: ${Debugger ::class .simpleName} " }
97103
104+ val writer = configureRemoteWriter(config)
105+ installGraphPipeline(pipeline, writer)
106+
107+ return Debugger (
108+ port = writer.server.connectionConfig.port,
109+ awaitInitialConnectionTimeout = writer.server.connectionConfig.awaitInitialConnectionTimeout
110+ )
111+ }
112+
113+ override fun install (config : DebuggerConfig , pipeline : AIAgentFunctionalPipeline ): Debugger {
114+ logger.debug { " Debugger Feature. Start installing feature: ${Debugger ::class .simpleName} " }
115+
116+ val writer = configureRemoteWriter(config)
117+ installFunctionalPipeline(pipeline, writer)
118+
119+ return Debugger (
120+ port = writer.server.connectionConfig.port,
121+ awaitInitialConnectionTimeout = writer.server.connectionConfig.awaitInitialConnectionTimeout
122+ )
123+ }
124+
125+ /* *
126+ * Creates a new [DebuggerFeatureMessageRemoteWriter] instance for the given [AIAgentGraphPipeline]
127+ */
128+ private fun configureRemoteWriter (config : DebuggerConfig ): DebuggerFeatureMessageRemoteWriter {
129+ logger.debug { " Debugger Feature. Creating debugger remote writer" }
130+
98131 // Config that will be used to connect to the debugger server where
99132 // port is taken from environment variables if not set explicitly
100133
101134 val port = config.port ? : readPortValue()
102- logger.debug { " Debugger Feature. Use debugger port: $port " }
103-
104135 val awaitInitialConnectionTimeout = config.awaitInitialConnectionTimeout ? : readWaitConnectionTimeout()
105- logger.debug { " Debugger Feature. Use debugger server wait connection timeout: $awaitInitialConnectionTimeout " }
136+ logger.debug {
137+ " Debugger Feature. Use debugger with parameters " +
138+ " (port: $port , server wait connection timeout: $awaitInitialConnectionTimeout )"
139+ }
106140
107141 val debuggerServerConfig = DefaultServerConnectionConfig (
108142 port = port,
@@ -113,8 +147,13 @@ public class Debugger {
113147 val writer = DebuggerFeatureMessageRemoteWriter (connectionConfig = debuggerServerConfig)
114148 config.addMessageProcessor(writer)
115149
116- val debugger = Debugger ()
150+ return writer
151+ }
117152
153+ private fun installCommon (
154+ pipeline : AIAgentPipeline ,
155+ writer : DebuggerFeatureMessageRemoteWriter ,
156+ ) {
118157 // region Intercept Agent Events
119158
120159 pipeline.interceptAgentStarting(this ) intercept@{ eventContext ->
@@ -184,43 +223,6 @@ public class Debugger {
184223
185224 // endregion Intercept Strategy Events
186225
187- // region Intercept Node Events
188-
189- pipeline.interceptNodeExecutionStarting(this ) intercept@{ eventContext ->
190- val event = NodeExecutionStartingEvent (
191- runId = eventContext.context.runId,
192- nodeName = eventContext.node.name,
193- input = getNodeData(eventContext.input, eventContext.inputType),
194- timestamp = pipeline.clock.now().toEpochMilliseconds()
195- )
196- writer.onMessage(event)
197- }
198-
199- pipeline.interceptNodeExecutionCompleted(this ) intercept@{ eventContext ->
200-
201- val event = NodeExecutionCompletedEvent (
202- runId = eventContext.context.runId,
203- nodeName = eventContext.node.name,
204- input = getNodeData(eventContext.input, eventContext.inputType),
205- output = getNodeData(eventContext.output, eventContext.outputType),
206- timestamp = pipeline.clock.now().toEpochMilliseconds()
207- )
208- writer.onMessage(event)
209- }
210-
211- pipeline.interceptNodeExecutionFailed(this ) intercept@{ eventContext ->
212- val event = NodeExecutionFailedEvent (
213- runId = eventContext.context.runId,
214- nodeName = eventContext.node.name,
215- input = getNodeData(eventContext.input, eventContext.inputType),
216- error = eventContext.throwable.toAgentError(),
217- timestamp = pipeline.clock.now().toEpochMilliseconds()
218- )
219- writer.onMessage(event)
220- }
221-
222- // endregion Intercept Node Events
223-
224226 // region Intercept LLM Call Events
225227
226228 pipeline.interceptLLMCallStarting(this ) intercept@{ eventContext ->
@@ -354,25 +356,74 @@ public class Debugger {
354356 }
355357
356358 // endregion Intercept Tool Call Events
359+ }
360+
361+ private fun installGraphPipeline (
362+ pipeline : AIAgentGraphPipeline ,
363+ writer : DebuggerFeatureMessageRemoteWriter ,
364+ ) {
365+ installCommon(pipeline, writer)
366+
367+ // region Intercept Node Events
368+
369+ pipeline.interceptNodeExecutionStarting(this ) intercept@{ eventContext ->
370+ val event = NodeExecutionStartingEvent (
371+ runId = eventContext.context.runId,
372+ nodeName = eventContext.node.name,
373+ input = getNodeData(eventContext.input, eventContext.inputType),
374+ timestamp = pipeline.clock.now().toEpochMilliseconds()
375+ )
376+ writer.onMessage(event)
377+ }
378+
379+ pipeline.interceptNodeExecutionCompleted(this ) intercept@{ eventContext ->
357380
358- return debugger
381+ val event = NodeExecutionCompletedEvent (
382+ runId = eventContext.context.runId,
383+ nodeName = eventContext.node.name,
384+ input = getNodeData(eventContext.input, eventContext.inputType),
385+ output = getNodeData(eventContext.output, eventContext.outputType),
386+ timestamp = pipeline.clock.now().toEpochMilliseconds()
387+ )
388+ writer.onMessage(event)
389+ }
390+
391+ pipeline.interceptNodeExecutionFailed(this ) intercept@{ eventContext ->
392+ val event = NodeExecutionFailedEvent (
393+ runId = eventContext.context.runId,
394+ nodeName = eventContext.node.name,
395+ input = getNodeData(eventContext.input, eventContext.inputType),
396+ error = eventContext.throwable.toAgentError(),
397+ timestamp = pipeline.clock.now().toEpochMilliseconds()
398+ )
399+ writer.onMessage(event)
400+ }
401+
402+ // endregion Intercept Node Events
403+ }
404+
405+ private fun installFunctionalPipeline (
406+ pipeline : AIAgentFunctionalPipeline ,
407+ writer : DebuggerFeatureMessageRemoteWriter ,
408+ ) {
409+ installCommon(pipeline, writer)
359410 }
360411
361412 // region Private Methods
362413
363414 private fun readPortValue (): Int? {
364415 val debuggerPortValue =
365- readEnvironmentVariable (name = KOOG_DEBUGGER_PORT_ENV_VAR )
366- ? : readVMOption (name = KOOG_DEBUGGER_PORT_VM_OPTION )
416+ getEnvironmentVariableOrNull (name = KOOG_DEBUGGER_PORT_ENV_VAR )
417+ ? : getVMOptionOrNull (name = KOOG_DEBUGGER_PORT_VM_OPTION )
367418
368419 logger.debug { " Debugger Feature. Reading Koog debugger port value from system variables: $debuggerPortValue " }
369420 return debuggerPortValue?.toIntOrNull()
370421 }
371422
372423 private fun readWaitConnectionTimeout (): Duration ? {
373424 val debuggerWaitConnectionTimeoutValue =
374- readEnvironmentVariable (name = KOOG_DEBUGGER_WAIT_CONNECTION_MS_ENV_VAR )
375- ? : readVMOption (name = KOOG_DEBUGGER_WAIT_CONNECTION_TIMEOUT_MS_VM_OPTION )
425+ getEnvironmentVariableOrNull (name = KOOG_DEBUGGER_WAIT_CONNECTION_MS_ENV_VAR )
426+ ? : getVMOptionOrNull (name = KOOG_DEBUGGER_WAIT_CONNECTION_TIMEOUT_MS_VM_OPTION )
376427
377428 logger.debug { " Debugger Feature. Reading Koog debugger wait connection timeout value from system variables: $debuggerWaitConnectionTimeoutValue " }
378429 return debuggerWaitConnectionTimeoutValue?.toLongOrNull()?.toDuration(DurationUnit .MILLISECONDS )
@@ -399,3 +450,11 @@ public class Debugger {
399450 // endregion Private Methods
400451 }
401452}
453+
454+ /* *
455+ * Extension function to access the Debugger feature from an agent context.
456+ *
457+ * @return The [Debugger] feature instance for this agent
458+ * @throws IllegalStateException if the Debugger feature is not installed
459+ */
460+ public fun AIAgentContext.debugger (): Debugger = featureOrThrow(Debugger )
0 commit comments