Skip to content

Commit e40dc81

Browse files
authored
refactor(agents): KG-815. Tool agent event context carries the original exception (#1918)
- Tool agent event context contains a serializable AIAgentError instance. This type hides important details about an exception, e.g., the exact type of the exception. Replaced this serializable type with a Throwable instead and transform to AIAgentError when needed; - Renamed `throwable` to `error` on Agent/Node/Subgraph failed contexts and pipeline parameters for consistency across lifecycle events; - Switched `onToolValidationFailed` and `onToolCallFailed` pipeline signatures to accept Throwable; - Added a custom serializer for ToolResultKind.Failure / ValidationError that encodes a Throwable through AIAgentError, preserving the existing JSON wire format; - Updated OpenTelemetry span and metric helpers to take Throwable directly, fixing a latent `error.type` attribute that always reported AIAgentError as the exception class; closes: [KG-815](https://youtrack.jetbrains.com/issue/KG-815)
1 parent 3270e4c commit e40dc81

41 files changed

Lines changed: 203 additions & 125 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

agents/agents-core/src/commonMain/kotlin/ai/koog/agents/core/environment/ContextualAgentEnvironment.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package ai.koog.agents.core.environment
33
import ai.koog.agents.core.agent.context.AIAgentContext
44
import ai.koog.agents.core.agent.execution.AgentExecutionInfo
55
import ai.koog.agents.core.annotation.InternalAgentsApi
6-
import ai.koog.agents.core.feature.model.toAgentError
76
import ai.koog.prompt.message.Message
87
import ai.koog.serialization.JSONObject
98
import ai.koog.serialization.kotlinx.toKoogJSONObject
@@ -58,7 +57,7 @@ public class ContextualAgentEnvironment(
5857
toolDescription = toolDescription,
5958
toolArgs = toolArgs,
6059
message = message,
61-
error = e.toAgentError(),
60+
error = e,
6261
context = context
6362
)
6463
return ReceivedToolResult(
@@ -67,7 +66,7 @@ public class ContextualAgentEnvironment(
6766
toolArgs = toolArgs,
6867
toolDescription = null,
6968
content = message,
70-
resultKind = ToolResultKind.ValidationError(e.toAgentError()),
69+
resultKind = ToolResultKind.ValidationError(e),
7170
result = null
7271
)
7372
}

agents/agents-core/src/commonMain/kotlin/ai/koog/agents/core/environment/GenericAgentEnvironment.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package ai.koog.agents.core.environment
22

3-
import ai.koog.agents.core.feature.model.toAgentError
43
import ai.koog.agents.core.tools.Tool
54
import ai.koog.agents.core.tools.ToolException
65
import ai.koog.agents.core.tools.ToolRegistry
@@ -61,7 +60,7 @@ public class GenericAgentEnvironment(
6160
toolArgs = JSONObject(emptyMap()),
6261
toolDescription = null,
6362
content = "Tool with name '$toolName' failed to parse arguments due to the error: ${e.message}",
64-
resultKind = ToolResultKind.Failure(e.toAgentError()),
63+
resultKind = ToolResultKind.Failure(e),
6564
result = null,
6665
)
6766
}
@@ -95,7 +94,7 @@ public class GenericAgentEnvironment(
9594
toolArgs = toolArgsJson,
9695
toolDescription = toolDescription,
9796
content = "Tool with name '$toolName' failed to parse arguments due to the error: ${e.message}",
98-
resultKind = ToolResultKind.Failure(e.toAgentError()),
97+
resultKind = ToolResultKind.Failure(e),
9998
result = null,
10099
)
101100
}
@@ -112,7 +111,7 @@ public class GenericAgentEnvironment(
112111
toolArgs = toolArgsJson,
113112
toolDescription = toolDescription,
114113
content = e.message,
115-
resultKind = ToolResultKind.ValidationError(e.toAgentError()),
114+
resultKind = ToolResultKind.ValidationError(e),
116115
result = null,
117116
)
118117
} catch (e: Exception) {
@@ -124,7 +123,7 @@ public class GenericAgentEnvironment(
124123
toolArgs = toolArgsJson,
125124
toolDescription = toolDescription,
126125
content = "Tool with name '$toolName' failed to execute due to the error: ${e.message}!",
127-
resultKind = ToolResultKind.Failure(e.toAgentError()),
126+
resultKind = ToolResultKind.Failure(e),
128127
result = null
129128
)
130129
}
@@ -144,7 +143,7 @@ public class GenericAgentEnvironment(
144143
toolArgs = toolArgsJson,
145144
toolDescription = toolDescription,
146145
content = "Tool with name '$toolName' failed to serialize result due to the error: ${e.message}!",
147-
resultKind = ToolResultKind.Failure(e.toAgentError()),
146+
resultKind = ToolResultKind.Failure(e),
148147
result = null
149148
)
150149
}

agents/agents-core/src/commonMain/kotlin/ai/koog/agents/core/environment/ToolResultKind.kt

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package ai.koog.agents.core.environment
22

33
import ai.koog.agents.core.feature.model.AIAgentError
4+
import ai.koog.agents.core.feature.model.toAgentError
5+
import kotlinx.serialization.KSerializer
46
import kotlinx.serialization.Serializable
7+
import kotlinx.serialization.descriptors.SerialDescriptor
8+
import kotlinx.serialization.encoding.Decoder
9+
import kotlinx.serialization.encoding.Encoder
510

611
/**
712
* Represents the possible result types for a tool operation.
@@ -18,16 +23,46 @@ public sealed class ToolResultKind {
1823
/**
1924
* Represents a failure result in the context of a tool operation.
2025
*
21-
* @property error The [Throwable] that caused the failure. It can be null if no specific throwable information is available.
26+
* @property error The exception that caused the failure, or `null` if no exception is available.
27+
* Encoded as an [AIAgentError] over the wire.
2228
*/
2329
@Serializable
24-
public data class Failure(public val error: AIAgentError?) : ToolResultKind()
30+
public data class Failure(
31+
@Serializable(with = ThrowableAsAgentErrorSerializer::class)
32+
public val error: Throwable?,
33+
) : ToolResultKind()
2534

2635
/**
2736
* Represents a validation error result in the context of a tool operation.
2837
*
29-
* @property error The specific tool exception that describes the details of the validation failure.
38+
* @property error The exception describing the validation failure.
39+
* Encoded as an [AIAgentError] over the wire.
3040
*/
3141
@Serializable
32-
public data class ValidationError(public val error: AIAgentError) : ToolResultKind()
42+
public data class ValidationError(
43+
@Serializable(with = ThrowableAsAgentErrorSerializer::class)
44+
public val error: Throwable,
45+
) : ToolResultKind()
46+
}
47+
48+
/**
49+
* Serializes a [Throwable] field by encoding it through [AIAgentError]
50+
*
51+
* The original [AIAgentError] is used to preserve the original [Throwable] to use it in agent event context.
52+
*
53+
* Note: Decoding is intentionally lossy.
54+
* [ReceivedToolResult] is not deserialized anywhere in the codebase today,
55+
* and full round-trip fidelity can be added (via a wrapper) if a real decoded use case appears.
56+
* It returns a plain [Throwable] with the original message but no stack/cause/type.
57+
*/
58+
internal object ThrowableAsAgentErrorSerializer : KSerializer<Throwable> {
59+
private val delegate = AIAgentError.serializer()
60+
override val descriptor: SerialDescriptor = delegate.descriptor
61+
62+
override fun serialize(encoder: Encoder, value: Throwable) {
63+
delegate.serialize(encoder, value.toAgentError())
64+
}
65+
66+
override fun deserialize(decoder: Decoder): Throwable =
67+
Throwable(delegate.deserialize(decoder).message)
3368
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public class ContextualPromptExecutor(
117117
}
118118
.catch { error ->
119119
logger.debug(error) { "Error in LLM streaming call (event id: $eventId): $error" }
120-
context.pipeline.onLLMStreamingFailed(eventId, context.executionInfo, context.runId, prompt = effectivePrompt, model, throwable = error, context)
120+
context.pipeline.onLLMStreamingFailed(eventId, context.executionInfo, context.runId, prompt = effectivePrompt, model, error = error, context)
121121

122122
throw error
123123
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ public class Debugger(public val port: Int, public val awaitInitialConnectionTim
209209
executionInfo = eventContext.executionInfo,
210210
agentId = eventContext.agentId,
211211
runId = eventContext.runId,
212-
error = eventContext.throwable.toAgentError(),
212+
error = eventContext.error.toAgentError(),
213213
timestamp = pipeline.clock.now().toEpochMilliseconds()
214214
)
215215
writer.onMessage(event)
@@ -378,7 +378,7 @@ public class Debugger(public val port: Int, public val awaitInitialConnectionTim
378378
toolArgs = eventContext.toolArgs,
379379
toolDescription = eventContext.toolDescription,
380380
message = eventContext.message,
381-
error = eventContext.error,
381+
error = eventContext.error.toAgentError(),
382382
timestamp = pipeline.clock.now().toEpochMilliseconds()
383383
)
384384
writer.onMessage(event)
@@ -393,7 +393,7 @@ public class Debugger(public val port: Int, public val awaitInitialConnectionTim
393393
toolName = eventContext.toolName,
394394
toolArgs = eventContext.toolArgs,
395395
toolDescription = eventContext.toolDescription,
396-
error = eventContext.error,
396+
error = eventContext.error?.toAgentError(),
397397
timestamp = pipeline.clock.now().toEpochMilliseconds()
398398
)
399399
writer.onMessage(event)
@@ -474,7 +474,7 @@ public class Debugger(public val port: Int, public val awaitInitialConnectionTim
474474
eventContext.inputType,
475475
pipeline.config.serializer
476476
),
477-
error = eventContext.throwable.toAgentError(),
477+
error = eventContext.error.toAgentError(),
478478
timestamp = pipeline.clock.now().toEpochMilliseconds()
479479
)
480480
writer.onMessage(event)
@@ -532,7 +532,7 @@ public class Debugger(public val port: Int, public val awaitInitialConnectionTim
532532
eventContext.inputType,
533533
pipeline.config.serializer
534534
),
535-
error = eventContext.throwable.toAgentError(),
535+
error = eventContext.error.toAgentError(),
536536
timestamp = pipeline.clock.now().toEpochMilliseconds()
537537
)
538538
writer.onMessage(event)

agents/agents-core/src/commonMain/kotlin/ai/koog/agents/core/feature/handler/agent/AgentEventContext.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,14 @@ public data class AgentCompletedContext(
6262
* @property executionInfo The execution information containing parentId and current execution path;
6363
* @property agentId The unique identifier of the agent associated with the error.
6464
* @property runId The identifier for the session during which the error occurred.
65-
* @property throwable The exception or error thrown during the execution.
65+
* @property error The exception or error thrown during the execution.
6666
*/
6767
public data class AgentExecutionFailedContext(
6868
override val eventId: String,
6969
override val executionInfo: AgentExecutionInfo,
7070
val agentId: String,
7171
val runId: String,
72-
val throwable: Throwable,
72+
val error: Throwable,
7373
public val context: AIAgentContext,
7474
) : AgentEventContext {
7575
override val eventType: AgentLifecycleEventType = AgentLifecycleEventType.AgentExecutionFailed

agents/agents-core/src/commonMain/kotlin/ai/koog/agents/core/feature/handler/node/NodeExecutionEventContext.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public data class NodeExecutionCompletedContext(
6464
* @property context The stage context in which the node experienced the error.
6565
* @property input The input data for the node execution.
6666
* @property inputType [TypeToken] representing the type of the [input].
67-
* @property throwable The exception or error that occurred during node execution.
67+
* @property error The exception or error that occurred during node execution.
6868
*/
6969
public data class NodeExecutionFailedContext(
7070
override val eventId: String,
@@ -73,7 +73,7 @@ public data class NodeExecutionFailedContext(
7373
val context: AIAgentGraphContextBase,
7474
val input: Any?,
7575
val inputType: TypeToken,
76-
val throwable: Throwable
76+
val error: Throwable
7777
) : NodeExecutionEventContext {
7878
override val eventType: AgentLifecycleEventType = AgentLifecycleEventType.NodeExecutionFailed
7979
}

agents/agents-core/src/commonMain/kotlin/ai/koog/agents/core/feature/handler/subgraph/SubgraphExecutionEventContext.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public data class SubgraphExecutionCompletedContext(
6464
* @property context The context in which the subgraph failed to execute.
6565
* @property input The input data for the subgraph execution.
6666
* @property inputType The type of the input data for the subgraph execution.
67-
* @property throwable The exception that caused the subgraph execution to fail.
67+
* @property error The exception that caused the subgraph execution to fail.
6868
*/
6969
public data class SubgraphExecutionFailedContext(
7070
override val eventId: String,
@@ -73,7 +73,7 @@ public data class SubgraphExecutionFailedContext(
7373
val context: AIAgentGraphContextBase,
7474
val input: Any?,
7575
val inputType: TypeToken,
76-
val throwable: Throwable
76+
val error: Throwable
7777
) : SubgraphExecutionEventContext {
7878
override val eventType: AgentLifecycleEventType = AgentLifecycleEventType.SubgraphExecutionFailed
7979
}

agents/agents-core/src/commonMain/kotlin/ai/koog/agents/core/feature/handler/tool/ToolCallEventContext.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import ai.koog.agents.core.agent.context.AIAgentContext
44
import ai.koog.agents.core.agent.execution.AgentExecutionInfo
55
import ai.koog.agents.core.feature.handler.AgentLifecycleEventContext
66
import ai.koog.agents.core.feature.handler.AgentLifecycleEventType
7-
import ai.koog.agents.core.feature.model.AIAgentError
87
import ai.koog.serialization.JSONElement
98
import ai.koog.serialization.JSONObject
109

@@ -66,7 +65,7 @@ public data class ToolCallStartingContext(
6665
*
6766
* @property executionInfo The execution information containing parentId and current execution path;
6867
* @property message A message describing the validation error.
69-
* @property error The [AIAgentError] error describing the validation issue.
68+
* @property error The exception describing the validation issue.
7069
*/
7170
public data class ToolValidationFailedContext(
7271
override val eventId: String,
@@ -77,7 +76,7 @@ public data class ToolValidationFailedContext(
7776
override val toolDescription: String?,
7877
override val toolArgs: JSONObject,
7978
val message: String,
80-
val error: AIAgentError,
79+
val error: Throwable,
8180
override val context: AIAgentContext
8281
) : ToolCallEventContext {
8382
override val eventType: AgentLifecycleEventType = AgentLifecycleEventType.ToolValidationFailed
@@ -88,7 +87,7 @@ public data class ToolValidationFailedContext(
8887
*
8988
* @property executionInfo The execution information containing parentId and current execution path;
9089
* @property message A message describing the failure that occurred.
91-
* @property error The [AIAgentError] instance describing the tool call failure.
90+
* @property error The exception describing the tool call failure, or `null` if no exception is available.
9291
*/
9392
public data class ToolCallFailedContext(
9493
override val eventId: String,
@@ -99,7 +98,7 @@ public data class ToolCallFailedContext(
9998
override val toolDescription: String?,
10099
override val toolArgs: JSONObject,
101100
val message: String,
102-
val error: AIAgentError?,
101+
val error: Throwable?,
103102
override val context: AIAgentContext
104103
) : ToolCallEventContext {
105104
override val eventType: AgentLifecycleEventType = AgentLifecycleEventType.ToolCallFailed

agents/agents-core/src/commonMain/kotlin/ai/koog/agents/core/feature/pipeline/AIAgentGraphPipeline.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public expect open class AIAgentGraphPipeline(
103103
* @param context The context associated with the AI agent executing the node.
104104
* @param input The input data provided to the node.
105105
* @param inputType The type of the input data provided to the node.
106-
* @param throwable The exception or error that occurred during node execution.
106+
* @param error The exception or error that occurred during node execution.
107107
*/
108108
@InternalAgentsApi
109109
public open override suspend fun onNodeExecutionFailed(
@@ -113,7 +113,7 @@ public expect open class AIAgentGraphPipeline(
113113
context: AIAgentGraphContextBase,
114114
input: Any?,
115115
inputType: TypeToken,
116-
throwable: Throwable
116+
error: Throwable
117117
)
118118

119119
//endregion Trigger Node Handlers
@@ -173,7 +173,7 @@ public expect open class AIAgentGraphPipeline(
173173
* @param context The agent context in which the subgraph execution occurred.
174174
* @param input The input data that was provided to the subgraph when it failed.
175175
* @param inputType The type of the input data provided to the subgraph.
176-
* @param throwable The exception or error that caused the subgraph execution to fail.
176+
* @param error The exception or error that caused the subgraph execution to fail.
177177
*/
178178
@InternalAgentsApi
179179
public open override suspend fun onSubgraphExecutionFailed(
@@ -183,7 +183,7 @@ public expect open class AIAgentGraphPipeline(
183183
context: AIAgentGraphContextBase,
184184
input: Any?,
185185
inputType: TypeToken,
186-
throwable: Throwable
186+
error: Throwable
187187
)
188188

189189
//endregion Trigger Subgraph Handlers
@@ -235,7 +235,7 @@ public expect open class AIAgentGraphPipeline(
235235
* Example:
236236
* ```
237237
* pipeline.interceptNodeExecutionFailed(feature) { eventContext ->
238-
* logger.error("Node ${eventContext.node.name} execution failed with error: ${eventContext.throwable}")
238+
* logger.error("Node ${eventContext.node.name} execution failed with error: ${eventContext.error}")
239239
* }
240240
* ```
241241
*/
@@ -290,7 +290,7 @@ public expect open class AIAgentGraphPipeline(
290290
* Example:
291291
* ```
292292
* pipeline.interceptSubgraphExecutionFailed(feature) { eventContext ->
293-
* logger.error("Subgraph ${eventContext.subgraph.name} execution failed with error: ${eventContext.throwable}")
293+
* logger.error("Subgraph ${eventContext.subgraph.name} execution failed with error: ${eventContext.error}")
294294
* }
295295
* ```
296296
*/

0 commit comments

Comments
 (0)