Skip to content

Commit aeee620

Browse files
committed
KG-505. Add system features to agents core
- Add a list of system features (Debugger) that can be installed through agent configuration parameters; - Add agent logic to read a system property and set up Debugger feature with a provided config; - Add tests to check updates in an agent pipeline logic.
1 parent ceaf83d commit aeee620

File tree

13 files changed

+607
-99
lines changed

13 files changed

+607
-99
lines changed

agents/agents-core/src/commonMain/kotlin/ai/koog/agents/core/agent/AIAgent.kt

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,8 @@ public interface AIAgent<Input, Output> : Closeable {
158158
* @param Output The type of the output the AI agent will produce.
159159
* @param promptExecutor The executor responsible for processing prompts and interacting with the language model.
160160
* @param agentConfig The configuration for the AI agent, including the prompt, model, and other parameters.
161-
* @param toolRegistry The registry of tools available for use by the agent. Defaults to an empty registry.
162161
* @param strategy The strategy for executing the AI agent's graph logic, including workflows and decision-making.
162+
* @param toolRegistry The registry of tools available for use by the agent. Defaults to an empty registry.
163163
* @param id Unique identifier for the agent. Random UUID will be generated if set to null.
164164
* @param clock The clock to be used for time-related operations. Defaults to the system clock.
165165
* @param installFeatures A lambda expression to install additional features in the agent's feature context. Defaults to an empty implementation.
@@ -174,7 +174,7 @@ public interface AIAgent<Input, Output> : Closeable {
174174
id: String? = null,
175175
clock: Clock = Clock.System,
176176
noinline installFeatures: FeatureContext.() -> Unit = {},
177-
): AIAgent<Input, Output> {
177+
): GraphAIAgent<Input, Output> {
178178
return GraphAIAgent(
179179
inputType = typeOf<Input>(),
180180
outputType = typeOf<Output>(),
@@ -192,7 +192,6 @@ public interface AIAgent<Input, Output> : Closeable {
192192
* Operator function to create and invoke an AI agent with the given parameters.
193193
*
194194
* @param promptExecutor The executor responsible for running the prompt and generating outputs.
195-
* @param prompt The prompt to be processed by the AI agent.
196195
* @param agentConfig Configuration settings for the AI agent.
197196
* @param strategy The strategy to be used for the AI agent's execution graph. Defaults to a single-run strategy.
198197
* @param toolRegistry Registry of tools available for the AI agent to use. Defaults to an empty registry.
@@ -208,7 +207,7 @@ public interface AIAgent<Input, Output> : Closeable {
208207
toolRegistry: ToolRegistry = ToolRegistry.EMPTY,
209208
id: String? = null,
210209
installFeatures: FeatureContext.() -> Unit = {},
211-
): AIAgent<String, String> = GraphAIAgent(
210+
): GraphAIAgent<String, String> = GraphAIAgent(
212211
inputType = typeOf<String>(),
213212
outputType = typeOf<String>(),
214213
promptExecutor = promptExecutor,
@@ -227,9 +226,11 @@ public interface AIAgent<Input, Output> : Closeable {
227226
* @param Output The type of the output the AI agent will produce.
228227
* @param promptExecutor The executor responsible for running prompts against the language model.
229228
* @param agentConfig The configuration for the AI agent, including prompt setup, language model, and iteration limits.
230-
* @param toolRegistry The registry containing available tools for the AI agent. Defaults to an empty registry.
231229
* @param strategy The strategy for executing the agent's logic, including workflows and decision-making.
230+
* @param toolRegistry The registry containing available tools for the AI agent. Defaults to an empty registry.
232231
* @param id Unique identifier for the agent. Random UUID will be generated if set to null.
232+
* @param clock The clock instance used for time-related operations. Defaults to the system clock.
233+
* @param installFeatures A lambda expression to install additional features in the agent's feature context. Defaults to an empty implementation.
233234
* @return A `FunctionalAIAgent` instance configured with the provided parameters and execution strategy.
234235
*/
235236
@OptIn(ExperimentalUuidApi::class)
@@ -332,9 +333,11 @@ public interface AIAgent<Input, Output> : Closeable {
332333
numberOfChoices: Int = 1,
333334
maxIterations: Int = 50,
334335
noinline installFeatures: FeatureContext.() -> Unit = {},
335-
): AIAgent<Input, Output> {
336-
return AIAgent(
336+
): GraphAIAgent<Input, Output> {
337+
return GraphAIAgent(
337338
id = id,
339+
inputType = typeOf<Input>(),
340+
outputType = typeOf<Output>(),
338341
promptExecutor = promptExecutor,
339342
strategy = strategy,
340343
agentConfig = AIAgentConfig(
@@ -351,6 +354,7 @@ public interface AIAgent<Input, Output> : Closeable {
351354
maxAgentIterations = maxIterations,
352355
),
353356
toolRegistry = toolRegistry,
357+
clock = clock,
354358
installFeatures = installFeatures
355359
)
356360
}
@@ -359,10 +363,12 @@ public interface AIAgent<Input, Output> : Closeable {
359363
* Creates an [FunctionalAIAgent] with the specified parameters to execute a strategy with the assistance of a tool registry,
360364
* configured language model, and associated features.
361365
*
366+
* @param Input The type of input accepted by the agent.
367+
* @param Output The type of output produced by the agent.
362368
* @param promptExecutor The executor used to process prompts for the language model.
363369
* @param llmModel The language model configuration defining the underlying LLM instance and its behavior.
364-
* @param func The operational strategy for the AI agent, which determines how to handle the provided input.
365370
* @param toolRegistry Registry containing tools available to the agent for use during execution. Default is an empty registry.
371+
* @param strategy The strategy to be executed by the agent. Default is a single-run strategy.
366372
* @param id Unique identifier for the agent. Random UUID will be generated if set to null.
367373
* @param systemPrompt The system prompt that sets the initial context or instructions for the AI agent.
368374
* @param temperature The temperature setting for the language model, which adjusts the diversity of output. Default is 1.0.
@@ -382,7 +388,7 @@ public interface AIAgent<Input, Output> : Closeable {
382388
numberOfChoices: Int = 1,
383389
maxIterations: Int = 50,
384390
installFeatures: FunctionalAIAgent.FeatureContext.() -> Unit = {},
385-
): AIAgent<Input, Output> = FunctionalAIAgent(
391+
): FunctionalAIAgent<Input, Output> = FunctionalAIAgent(
386392
promptExecutor = promptExecutor,
387393
agentConfig = AIAgentConfig(
388394
prompt = prompt(

agents/agents-core/src/commonMain/kotlin/ai/koog/agents/core/agent/GraphAIAgent.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import kotlin.reflect.KType
3535
* @property inputType [KType] representing [Input] - agent input.
3636
* @property outputType [KType] representing [Output] - agent output.
3737
* @property promptExecutor Executor used to manage and execute prompt strings.
38-
* @property strategy The execution strategy defining how the agent processes input and produces output..
38+
* @property strategy The execution strategy defining how the agent processes input and produces output.
3939
* @property agentConfig Configuration details for the local agent that define its operational parameters.
4040
* @property toolRegistry Registry of tools the agent can interact with, defaulting to an empty registry.
4141
* @property installFeatures Lambda for installing additional features within the agent environment.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package ai.koog.agents.core.feature.config
2+
3+
/**
4+
* A utility object that provides constants representing system variables
5+
* related to the configuration of features in the system.
6+
*
7+
* These system variables enable external configuration of features via
8+
* environment variables or JVM options.
9+
*/
10+
public object FeatureSystemVariables {
11+
12+
/**
13+
* The name of the environment variable used to configure features in the system.
14+
*/
15+
public const val KOOG_FEATURES_ENV_VAR_NAME: String = "KOOG_FEATURES"
16+
17+
/**
18+
* The name of the JVM option used to configure features in the system.
19+
*/
20+
public const val KOOG_FEATURES_VM_OPTION_NAME: String = "koog.features"
21+
}

agents/agents-core/src/commonMain/kotlin/ai/koog/agents/core/feature/debugger/Debugger.kt

Lines changed: 116 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
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
35
import ai.koog.agents.core.agent.entity.AIAgentGraphStrategy
46
import ai.koog.agents.core.agent.entity.AIAgentStorageKey
57
import ai.koog.agents.core.annotation.InternalAgentsApi
68
import ai.koog.agents.core.environment.ReceivedToolResult
9+
import ai.koog.agents.core.feature.AIAgentFunctionalFeature
710
import 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
1112
import ai.koog.agents.core.feature.model.events.AgentClosingEvent
1213
import ai.koog.agents.core.feature.model.events.AgentCompletedEvent
1314
import ai.koog.agents.core.feature.model.events.AgentExecutionFailedEvent
@@ -29,11 +30,14 @@ import ai.koog.agents.core.feature.model.events.ToolCallStartingEvent
2930
import ai.koog.agents.core.feature.model.events.ToolValidationFailedEvent
3031
import ai.koog.agents.core.feature.model.events.startNodeToGraph
3132
import ai.koog.agents.core.feature.model.toAgentError
33+
import ai.koog.agents.core.feature.pipeline.AIAgentFunctionalPipeline
3234
import ai.koog.agents.core.feature.pipeline.AIAgentGraphPipeline
35+
import ai.koog.agents.core.feature.pipeline.AIAgentPipeline
3336
import 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
3439
import ai.koog.agents.core.tools.Tool
3540
import ai.koog.agents.core.utils.SerializationUtils
36-
import ai.koog.agents.core.feature.debugger.writer.DebuggerFeatureMessageRemoteWriter
3741
import ai.koog.prompt.llm.toModelInfo
3842
import io.github.oshai.kotlinlogging.KotlinLogging
3943
import 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

Comments
 (0)