Skip to content

Commit d7a4dd8

Browse files
committed
Fix comments from CR
- Convert event field to a single attribute; - Fix service name and service version; - Update tests; - Update README.md according to xthe latest changes.
1 parent 436762e commit d7a4dd8

File tree

14 files changed

+464
-175
lines changed

14 files changed

+464
-175
lines changed

agents/agents-features/agents-features-opentelemetry/README.md

Lines changed: 85 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -57,32 +57,31 @@ install(OpenTelemetry) {
5757

5858
The example includes the following configuration methods:
5959

60-
| Name | Data type | Required | Description |
61-
|--------------------|-----------|----------|----------------------------------------------------------------------------------------------------------------------|
62-
| `setServiceInfo` | Unit | No | Sets the information about the service being instrumented, including service name, version and namespace (optional). |
63-
| `addSpanExporter` | Unit | No | Adds a span exporter to send telemetry data to external systems or for logging purposes. |
60+
| Name | Data type | Required | Description |
61+
|--------------------|-----------|----------|-------------------------------------------------------------------------------------------------|
62+
| `setServiceInfo` | Unit | No | Sets the information about the service being instrumented, including service name and version. |
63+
| `addSpanExporter` | Unit | No | Adds a span exporter to send telemetry data to external systems or for logging purposes. |
6464

6565
Please see below the full list of available configuration properties:
6666

6767
| Name | Data type | Default value | Description |
6868
|--------------------|--------------------|---------------|------------------------------------------------------------------------------|
6969
| `serviceName` | `String` | `ai.koog` | The name of the service being instrumented. |
70-
| `serviceVersion` | `String` | `` | The version of the service being instrumented. |
71-
| `serviceNamespace` | `String?` | `null` | The namespace of the service being instrumented (optional). |
70+
| `serviceVersion` | `String` | `0.0.0` | The version of the service being instrumented. |
7271
| `sdk` | `OpenTelemetrySdk` | | The OpenTelemetry SDK instance to use for telemetry collection. |
7372
| `isVerbose` | `Boolean` | `false` | Whether to enable verbose logging for debugging OpenTelemetry configuration. |
7473
| `tracer` | `Tracer` | | The OpenTelemetry tracer instance used for creating spans. |
7574

7675
Configuration API:
7776

78-
| Name | Arguments | Description |
79-
|-------------------------|--------------------------------------------------------------------------|--------------------------------------------------------------------------------|
80-
| `setServiceInfo` | `serviceName: String, serviceVersion: String, serviceNamespace: String?` | Sets the service information including name, version and optional namespace. |
81-
| `addSpanExporter` | `exporter: SpanExporter` | Adds a span exporter to send telemetry data to external systems. |
82-
| `addSpanProcessor` | `processor: SpanProcessor` | Adds a span processor to process spans before they are exported. |
83-
| `addResourceAttributes` | `attributes: Map<String, String>` | Adds resource attributes to provide additional context about the service. |
84-
| `setSampler` | `sampler: Sampler` | Sets the sampling strategy to control which spans are collected. |
85-
| `setVerbose` | `verbose: Boolean` | Enables or disables verbose logging for debugging OpenTelemetry configuration. |
77+
| Name | Arguments | Description |
78+
|-------------------------|-----------------------------------------------------|-----------------------------------------------------------------------------------|
79+
| `setServiceInfo` | `serviceName: String, serviceVersion: String` | Sets the service information including name and version. |
80+
| `addSpanExporter` | `exporter: SpanExporter` | Adds a span exporter to send telemetry data to external systems. |
81+
| `addSpanProcessor` | `processor: (SpanExporter) -> SpanProcessor` | Adds a span processor creator function to process spans before they are exported. |
82+
| `addResourceAttributes` | `attributes: Map<AttributeKey<T>, T> where T : Any` | Adds resource attributes to provide additional context about the service. |
83+
| `setSampler` | `sampler: Sampler` | Sets the sampling strategy to control which spans are collected. |
84+
| `setVerbose` | `verbose: Boolean` | Enables or disables verbose logging for debugging OpenTelemetry configuration. |
8685

8786

8887
### Advanced configuration
@@ -99,9 +98,9 @@ install(OpenTelemetry) {
9998

10099
// Add resource attributes
101100
addResourceAttributes(mapOf(
102-
AttributeKey.stringKey(OpenTelemetryConfig.DEPLOYMENT_ENVIRONMENT to "production"),
103-
AttributeKey.stringKey("custom.attribute") to "custom-value")
104-
)
101+
AttributeKey.stringKey("deployment.environment") to "production",
102+
AttributeKey.stringKey("custom.attribute") to "custom-value"
103+
))
105104
}
106105
```
107106

@@ -115,49 +114,78 @@ to use. The available method is:
115114
#### Resource attributes
116115

117116
Resource attributes represent additional information about a process producing telemetry. This information can be
118-
included in an OpenTelemetry configuration in Koog using the `addResourceAttribute()` method that takes a key and
119-
a string value as its arguments. Standard resource attribute keys are provided as constants such as `OpenTelemetryConfig.DEPLOYMENT_ENVIRONMENT` and are as follows:
117+
included in an OpenTelemetry configuration in Koog using the `addResourceAttributes()` method that takes a map of
118+
`AttributeKey<T>` to values of type `T`.
119+
120+
The following default resource attributes are automatically added:
121+
122+
- `service.name`: The name of the service being instrumented. Set to the value of `serviceName`.
123+
- `service.version`: The version of the service being instrumented. Set to the value of `serviceVersion`.
124+
- `service.instance.time`: The timestamp when the service instance was created.
125+
- `os.type`: The operating system type.
126+
- `os.version`: The operating system version.
127+
- `os.arch`: The operating system architecture.
128+
129+
The OpenTelemetry feature automatically adds various attributes to spans following the [OpenTelemetry Semantic Convention for GenAI](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/):
130+
131+
### Common Attributes
132+
133+
- `gen_ai.operation.name`: the type of operation being performed (e.g., "create_agent", "invoke_agent", "chat", "execute_tool")
134+
- `gen_ai.system`: the LLM provider being used
135+
- `gen_ai.agent.id`: the ID of the agent
136+
- `gen_ai.agent.name`: the name of the agent (when available)
137+
- `gen_ai.conversation.id`: the ID of the conversation/run
138+
- `gen_ai.request.model`: the LLM model being used
139+
- `gen_ai.request.temperature`: the temperature parameter for the LLM request
140+
- `error.type`: the type of error that occurred (when applicable)
141+
142+
### Custom Attributes
143+
144+
Some custom attributes specific to Koog are also added:
145+
146+
- `koog.agent.strategy.name`: the name of the agent strategy
147+
- `koog.node.name`: the name of the node being executed
148+
149+
### Span Types
120150

121-
- `SERVICE_NAME`: The name of the service being instrumented. Automatically inferred from the `serviceName` property, if set.
122-
- `SERVICE_VERSION`: The version of the service being instrumented. Automatically inferred from the `serviceVersion` property, if set.
123-
- `DEPLOYMENT_ENVIRONMENT`: The label for the deployment environment where the process is running. For example, `production`.
151+
The OpenTelemetry feature creates different types of spans for various operations:
124152

125-
The OpenTelemetry feature automatically adds various attributes to spans:
153+
1. **Create Agent Span**: Represents the creation of an agent
154+
- Key attributes: `gen_ai.operation.name` (create_agent), `gen_ai.agent.id`, `gen_ai.request.model`
126155

127-
- `koog.event.strategy.name`: the name of the agent strategy
128-
- `koog.event.eventId`: the ID of the event
129-
- `koog.event.result`: the result of the agent execution
130-
- `koog.llm.call.prompt`: the prompt sent to the LLM
131-
- `koog.llm.call.responses`: the responses received from the LLM
132-
- `koog.tool.name`: the name of the tool being called
133-
- `koog.tool.args`: the arguments passed to the tool
134-
- `koog.tool.call.result`: indicates a successful tool call result
156+
2. **Invoke Agent Span**: Represents a specific agent run
157+
- Key attributes: `gen_ai.operation.name` (invoke_agent), `gen_ai.agent.id`, `gen_ai.conversation.id`
158+
159+
3. **Node Execute Span**: Represents the execution of a node in the agent strategy
160+
- Key attributes: `gen_ai.conversation.id`, `koog.node.name`
161+
162+
4. **Inference Span**: Represents an LLM call
163+
- Key attributes: `gen_ai.operation.name` (chat), `gen_ai.conversation.id`, `gen_ai.request.model`, `gen_ai.request.temperature`
164+
165+
5. **Execute Tool Span**: Represents a tool call
166+
- Key attributes: `gen_ai.tool.name`, `gen_ai.tool.description`
135167

136168
## Exporters
137169

138170
Exporters send collected telemetry data to an OpenTelemetry Collector or other types of destinations or backend implementations.
139171

140-
### OTLP Exporter
172+
### OTLP Exporter example
141173

142174
The OTLP (OpenTelemetry Protocol) exporter sends telemetry data to an OpenTelemetry Collector. This is useful for integrating with systems like Jaeger, Zipkin, or Prometheus.
143175

144-
To add an OpenTelemetry Exporter, use the `addOtlpExporter` function. The function takes a single argument:
145-
146-
| Name | Data type | Required | Default | Description |
147-
|------------|-----------|----------|-------------------------|-----------------------------------------------------------------------------------------|
148-
| `endpoint` | String | No | `http://localhost:4317` | The address of the OpenTelemetry backend or Collector for the collected telemetry data. |
149-
176+
To add an OpenTelemetry Exporter, use the `addSpanExporter` function to a custom exporter:
150177
```kotlin
151178
install(OpenTelemetry) {
152179
// The default endpoint is http://localhost:4317
153-
addOtlpExporter()
154-
155-
// Specify a custom endpoint
156-
addOtlpExporter("http://my-otel-collector:4317")
180+
addSpanExporter(
181+
OtlpGrpcSpanExporter.builder()
182+
.setEndpoint("http://localhost:4317")
183+
.build()
184+
)
157185
}
158186
```
159187

160-
### Logging Exporter
188+
### Logging Exporter example
161189

162190
A logging exporter that outputs trace information to the console is included by default. This type of export is useful
163191
for development and debugging purposes.
@@ -166,7 +194,6 @@ for development and debugging purposes.
166194
install(OpenTelemetry) {
167195
// The logging exporter is added by default
168196
addSpanExporter(LoggingSpanExporter.create())
169-
// You can add additional exporters as needed
170197
}
171198
```
172199

@@ -191,8 +218,11 @@ services:
191218
2. Configure your agent to use the OTLP exporter:
192219
```kotlin
193220
install(OpenTelemetry) {
194-
serviceName = "my-agent-service"
195-
addOtlpExporter() // The default endpoint is http://localhost:4317
221+
addSpanExporter(
222+
OtlpGrpcSpanExporter.builder()
223+
.setEndpoint("http://localhost:4317")
224+
.build()
225+
)
196226
}
197227
```
198228

@@ -215,8 +245,8 @@ suspend fun main() {
215245
systemPrompt = "You are a code assistant. Provide concise code examples.",
216246
installFeatures = {
217247
install(OpenTelemetry) {
218-
serviceName = "my-otlp-agent"
219-
addOtlpExporter()
248+
setServiceInfo("my-otlp-agent", "1.0.0")
249+
addSpanExporter(LoggingSpanExporter.create())
220250
}
221251
}
222252
)
@@ -240,18 +270,19 @@ suspend fun main() {
240270
systemPrompt = "You are a helpful assistant.",
241271
installFeatures = {
242272
install(OpenTelemetry) {
243-
serviceName = "advanced-agent"
244-
serviceVersion = "2.0.0"
273+
// Configure service info
274+
setServiceInfo("advanced-agent", "2.0.0")
245275

246276
// Configure sampling
247-
sampler = Sampler.alwaysOn()
277+
setSampler(Sampler.alwaysOn())
248278

249279
// Add resource attributes
250-
addResourceAttribute(OpenTelemetryConfig.DEPLOYMENT_ENVIRONMENT, "staging")
251-
addResourceAttribute("code.source", "documentation-example")
280+
addResourceAttributes(mapOf(
281+
AttributeKey.stringKey("deployment.environment") to "production",
282+
AttributeKey.stringKey("custom.attribute") to "custom-value"
283+
))
252284

253285
// Add exporters
254-
addOtlpExporter("http://otel-collector:4317")
255286
addSpanExporter(LoggingSpanExporter.create())
256287
}
257288
}

agents/agents-features/agents-features-opentelemetry/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ val generateProductProperties = tasks.register("generateProductProperties") {
7272
propertiesFile.asFile.parentFile.mkdirs()
7373
propertiesFile.asFile.writeText("""
7474
version=$rootProjectVersion
75-
serviceName=$rootProjectGroup
75+
name=$rootProjectGroup
7676
""".trimIndent())
7777
}
7878
}

agents/agents-features/agents-features-opentelemetry/src/jvmMain/kotlin/ai/koog/agents/features/opentelemetry/attribute/attributes.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import io.opentelemetry.api.common.Attributes
55
import io.opentelemetry.api.common.AttributesBuilder
66
import java.util.function.BiConsumer
77

8-
internal fun AttributesBuilder.addAttributes(attributes: Map<AttributeKey<*>, Any>) {
8+
internal fun AttributesBuilder.addAttributes(attributes: Map<AttributeKey<*>, Any>): AttributesBuilder {
99
attributes.forEach { (key, value) ->
1010
@Suppress("UNCHECKED_CAST")
1111
put(key as AttributeKey<Any>, value)
1212
}
13+
return this
1314
}
1415

1516
internal fun List<Attribute>.toSdkAttributes() : Attributes {
@@ -33,7 +34,7 @@ internal fun List<Attribute>.toSdkAttributes() : Attributes {
3334

3435
override fun asMap(): Map<AttributeKey<*>, Any> = sdkAttributesMap
3536

36-
override fun toBuilder(): AttributesBuilder = Attributes.builder().also { builder -> builder.addAttributes(sdkAttributesMap) }
37+
override fun toBuilder(): AttributesBuilder = Attributes.builder().addAttributes(sdkAttributesMap)
3738
}
3839
}
3940

@@ -69,7 +70,7 @@ private fun Attribute.toSdkAttribute(): Pair<AttributeKey<*>, Any> {
6970
else if (value.all { it is Boolean }) {
7071
Pair(AttributeKey.booleanArrayKey(key), value)
7172
}
72-
else if (value.all { it is Int}) {
73+
else if (value.all { it is Int }) {
7374
Pair(AttributeKey.longArrayKey(key), value.map { (it as Int).toLong() })
7475
}
7576
else if (value.all { it is Long }) {

agents/agents-features/agents-features-opentelemetry/src/jvmMain/kotlin/ai/koog/agents/features/opentelemetry/event/AssistantMessageEvent.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ internal data class AssistantMessageEvent(
2828
}
2929

3030
is Message.Tool.Call -> {
31-
add(EventBodyFields.ToolCalls(tools = listOf(message), verbose = verbose))
31+
add(EventBodyFields.ToolCalls(tools = listOf(message)))
3232
}
3333

3434
is Message.Tool.Result -> {
35-
add(EventBodyFields.ToolCalls(tools = listOf(message), verbose = verbose))
35+
add(EventBodyFields.ToolCalls(tools = listOf(message)))
3636
}
3737

3838
else -> error(

agents/agents-features/agents-features-opentelemetry/src/jvmMain/kotlin/ai/koog/agents/features/opentelemetry/event/ChoiceEvent.kt

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,18 @@ internal class ChoiceEvent(
2020
override val bodyFields: List<EventBodyField> = buildList {
2121
when (message) {
2222
is Message.Assistant -> {
23+
add(EventBodyFields.Index(0))
24+
message.finishReason?.let { reason ->
25+
add(EventBodyFields.FinishReason(reason))
26+
}
2327
add(EventBodyFields.Message(
2428
role = message.role.takeIf { role -> role != Message.Role.Assistant },
2529
content = message.content
2630
))
27-
28-
message.finishReason?.let { reason ->
29-
add(EventBodyFields.FinishReason(reason))
30-
}
31-
32-
add(EventBodyFields.Index(0))
33-
3431
}
3532
is Message.Tool.Call -> {
36-
add(EventBodyFields.ToolCalls(tools = listOf(message), verbose = verbose))
3733
add(EventBodyFields.Index(0))
34+
add(EventBodyFields.ToolCalls(tools = listOf(message)))
3835
}
3936
}
4037
}
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package ai.koog.agents.features.opentelemetry.event
22

3-
import ai.koog.agents.features.opentelemetry.attribute.Attribute
4-
import ai.koog.agents.features.opentelemetry.attribute.CustomAttribute
53
import io.github.oshai.kotlinlogging.KotlinLogging
64

75
/**
@@ -18,50 +16,39 @@ internal abstract class EventBodyField {
1816

1917
abstract val value: Any
2018

21-
open val verbose: Boolean
22-
get() = false
19+
val valueString: String
20+
get() = convertValueToString(key, value)
2321

24-
internal fun toAttribute(): Attribute {
25-
return CustomAttribute(key, getAttributeValue(key, value), verbose)
26-
}
22+
//region Private Methods
2723

28-
private fun getAttributeValue(key: Any, value: Any): Any {
24+
private fun convertValueToString(key: Any, value: Any): String {
2925
return when (value) {
30-
// Types supported by Attributes
3126
is CharSequence, is Char -> {
3227
"\"${value}\""
3328
}
3429
is Boolean,
3530
is Int, is Long,
3631
is Double, is Float -> {
37-
value
32+
value.toString()
3833
}
3934
is List<*> -> {
40-
if (value.all { it is CharSequence } || value.all { it is Char } ||
41-
value.all { it is Boolean } ||
42-
value.all { it is Int } || value.all { it is Long } ||
43-
value.all { it is Double } || value.all { it is Float } ) {
44-
value
45-
}
46-
else {
47-
48-
// Not supported by Attributes
49-
logger.debug { "${key}: Unsupported type for event body: ${value::class.simpleName}. use toString()" }
50-
value.filterNotNull().joinToString(prefix = "[", postfix = "]") { getAttributeValue(key, it).toString() }
35+
value.filterNotNull().joinToString(separator = ",", prefix = "[", postfix = "]") { item ->
36+
convertValueToString(key, item)
5137
}
5238
}
5339

54-
// Types not supported by Attributes
5540
is Map<*, *> -> {
5641
value.entries
5742
.filter { it.key != null && it.value != null }
58-
.joinToString(prefix = "{", postfix = "}") { entry -> "\"${entry.key}\": ${getAttributeValue(entry.key!!, entry.value!!)}" }
43+
.joinToString(separator = ",", prefix = "{", postfix = "}") { entry -> "\"${entry.key}\":${convertValueToString(entry.key!!, entry.value!!)}" }
5944
}
6045

6146
else -> {
62-
logger.debug { "${key}: Unsupported type for event body: ${value::class.simpleName}. use toString()" }
47+
logger.debug { "${key}: Custom type for event body: ${value::class.simpleName}. Use toString()" }
6348
value.toString()
6449
}
6550
}
6651
}
52+
53+
//endregion Private Methods
6754
}

0 commit comments

Comments
 (0)