Skip to content

Commit 0c92e85

Browse files
committed
KG-217. Implement span adapters for Langfuse and Weave clients
1 parent 0aa98bd commit 0c92e85

File tree

10 files changed

+148
-10
lines changed

10 files changed

+148
-10
lines changed

agents/agents-features/agents-features-opentelemetry/src/jvmMain/kotlin/ai/koog/agents/features/opentelemetry/extension/SpanExt.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package ai.koog.agents.features.opentelemetry.extension
22

33
import ai.koog.agents.features.opentelemetry.attribute.Attribute
44
import ai.koog.agents.features.opentelemetry.attribute.toSdkAttributes
5+
import ai.koog.agents.features.opentelemetry.event.EventBodyField
56
import ai.koog.agents.features.opentelemetry.event.GenAIAgentEvent
7+
import ai.koog.agents.features.opentelemetry.span.GenAIAgentSpan
68
import ai.koog.agents.features.opentelemetry.span.SpanEndStatus
79
import io.opentelemetry.api.trace.Span
810
import io.opentelemetry.api.trace.SpanBuilder
@@ -36,3 +38,15 @@ internal fun Span.setEvents(events: List<GenAIAgentEvent>) {
3638
addEvent(event.name, attributes.toSdkAttributes())
3739
}
3840
}
41+
42+
internal inline fun <reified TBodyField> GenAIAgentSpan.eventBodyFieldToAttribute(
43+
event: GenAIAgentEvent,
44+
attributeCreate: (TBodyField) -> Attribute
45+
) where TBodyField : EventBodyField {
46+
event.bodyFields.filterIsInstance<TBodyField>().forEach { bodyField ->
47+
val attributeFromEvent = attributeCreate(bodyField)
48+
this.addAttribute(attributeFromEvent)
49+
}
50+
51+
this.removeEvent(event)
52+
}

agents/agents-features/agents-features-opentelemetry/src/jvmMain/kotlin/ai/koog/agents/features/opentelemetry/feature/OpenTelemetryConfig.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package ai.koog.agents.features.opentelemetry.feature
22

33
import ai.koog.agents.core.feature.config.FeatureConfig
44
import ai.koog.agents.features.opentelemetry.attribute.addAttributes
5-
import ai.koog.agents.features.opentelemetry.integrations.SpanAdapter
5+
import ai.koog.agents.features.opentelemetry.integration.SpanAdapter
66
import io.github.oshai.kotlinlogging.KotlinLogging
77
import io.opentelemetry.api.common.AttributeKey
88
import io.opentelemetry.api.common.Attributes
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package ai.koog.agents.features.opentelemetry.integrations
1+
package ai.koog.agents.features.opentelemetry.integration
22

33
import ai.koog.agents.features.opentelemetry.span.GenAIAgentSpan
44

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package ai.koog.agents.features.opentelemetry.integrations
1+
package ai.koog.agents.features.opentelemetry.integration.langfuse
22

33
import ai.koog.agents.features.opentelemetry.feature.OpenTelemetryConfig
44
import io.github.oshai.kotlinlogging.KotlinLogging
@@ -11,7 +11,7 @@ import kotlin.time.Duration.Companion.seconds
1111
/**
1212
* Configure an OpenTelemetry span exporter that sends data to [Langfuse](https://langfuse.com/).
1313
*
14-
* @param langfuseUrl the base URL of the Langfuse instance. If not set is retrieved from `LANGFUSE_URL` environment variable. Defaults to [https://cloud.langfuse.com](https://cloud.langfuse.com).
14+
* @param langfuseUrl the base URL of the Langfuse instance. If not set is retrieved from `LANGFUSE_HOST` environment variable. Defaults to [https://cloud.langfuse.com](https://cloud.langfuse.com).
1515
* @param langfusePublicKey if not set is retrieved from `LANGFUSE_PUBLIC_KEY` environment variable.
1616
* @param langfuseSecretKey if not set is retrieved from `LANGFUSE_SECRET_KEY` environment variable.
1717
* @param timeout OpenTelemetry SpanExporter timeout. See [io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder.setTimeout].
@@ -46,7 +46,7 @@ public fun OpenTelemetryConfig.addLangfuseExporter(
4646
.build()
4747
)
4848

49-
// TODO: Add span adapter for Langfuse
49+
addSpanAdapter(LangfuseSpanAdapter)
5050
}
5151

5252
private val logger = KotlinLogging.logger {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package ai.koog.agents.features.opentelemetry.integration.langfuse
2+
3+
import ai.koog.agents.core.annotation.InternalAgentsApi
4+
import ai.koog.agents.features.opentelemetry.attribute.CustomAttribute
5+
import ai.koog.agents.features.opentelemetry.event.AssistantMessageEvent
6+
import ai.koog.agents.features.opentelemetry.event.ChoiceEvent
7+
import ai.koog.agents.features.opentelemetry.event.EventBodyFields
8+
import ai.koog.agents.features.opentelemetry.event.UserMessageEvent
9+
import ai.koog.agents.features.opentelemetry.extension.eventBodyFieldToAttribute
10+
import ai.koog.agents.features.opentelemetry.integration.SpanAdapter
11+
import ai.koog.agents.features.opentelemetry.span.GenAIAgentSpan
12+
import ai.koog.agents.features.opentelemetry.span.InferenceSpan
13+
14+
@OptIn(InternalAgentsApi::class)
15+
internal object LangfuseSpanAdapter : SpanAdapter() {
16+
17+
override fun onBeforeSpanFinished(span: GenAIAgentSpan) {
18+
when (span) {
19+
is InferenceSpan -> span.prepareSpanAttributes()
20+
}
21+
}
22+
23+
//region Private Methods
24+
25+
private fun InferenceSpan.prepareSpanAttributes() {
26+
this.events.forEach { event ->
27+
when (event) {
28+
is AssistantMessageEvent -> {
29+
this.eventBodyFieldToAttribute<EventBodyFields.Role>(event) { role ->
30+
CustomAttribute("gen_ai.completion.${role.key}", role.value)
31+
}
32+
33+
this.eventBodyFieldToAttribute<EventBodyFields.Content>(event) { content ->
34+
CustomAttribute("gen_ai.completion.content", content.value)
35+
}
36+
}
37+
38+
is ChoiceEvent -> {
39+
val index = event.index
40+
41+
this.eventBodyFieldToAttribute<EventBodyFields.Role>(event) { role ->
42+
CustomAttribute("gen_ai.$index.${role.key}", role.value)
43+
}
44+
45+
this.eventBodyFieldToAttribute<EventBodyFields.Content>(event) { content ->
46+
CustomAttribute("gen_ai.completion.$index.content", content.value)
47+
}
48+
}
49+
50+
is UserMessageEvent -> {
51+
this.eventBodyFieldToAttribute<EventBodyFields.Content>(event) { content ->
52+
CustomAttribute("gen_ai.completion.content", content.value)
53+
}
54+
}
55+
56+
else -> { }
57+
}
58+
}
59+
}
60+
61+
//endregion Private Methods
62+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package ai.koog.agents.features.opentelemetry.integrations
1+
package ai.koog.agents.features.opentelemetry.integration.weave
22

33
import ai.koog.agents.features.opentelemetry.feature.OpenTelemetryConfig
44
import io.github.oshai.kotlinlogging.KotlinLogging
@@ -46,7 +46,7 @@ public fun OpenTelemetryConfig.addWeaveExporter(
4646
.build()
4747
)
4848

49-
// TODO: Add span adapter for Langfuse
49+
addSpanAdapter(WeaveSpanAdapter)
5050
}
5151

5252
private val logger = KotlinLogging.logger { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package ai.koog.agents.features.opentelemetry.integration.weave
2+
3+
import ai.koog.agents.core.annotation.InternalAgentsApi
4+
import ai.koog.agents.features.opentelemetry.attribute.CustomAttribute
5+
import ai.koog.agents.features.opentelemetry.event.AssistantMessageEvent
6+
import ai.koog.agents.features.opentelemetry.event.ChoiceEvent
7+
import ai.koog.agents.features.opentelemetry.event.EventBodyFields
8+
import ai.koog.agents.features.opentelemetry.event.UserMessageEvent
9+
import ai.koog.agents.features.opentelemetry.extension.eventBodyFieldToAttribute
10+
import ai.koog.agents.features.opentelemetry.integration.SpanAdapter
11+
import ai.koog.agents.features.opentelemetry.span.GenAIAgentSpan
12+
import ai.koog.agents.features.opentelemetry.span.InferenceSpan
13+
14+
@OptIn(InternalAgentsApi::class)
15+
internal object WeaveSpanAdapter : SpanAdapter() {
16+
17+
override fun onBeforeSpanFinished(span: GenAIAgentSpan) {
18+
when (span) {
19+
is InferenceSpan -> span.prepareSpanAttributes()
20+
}
21+
}
22+
23+
//region Private Methods
24+
25+
private fun InferenceSpan.prepareSpanAttributes() {
26+
this.events.forEach { event ->
27+
when (event) {
28+
is AssistantMessageEvent -> {
29+
this.eventBodyFieldToAttribute<EventBodyFields.Role>(event) { role ->
30+
CustomAttribute("gen_ai.completion.${role.key}", role.value)
31+
}
32+
33+
this.eventBodyFieldToAttribute<EventBodyFields.Content>(event) { content ->
34+
CustomAttribute("gen_ai.completion.content", content.value)
35+
}
36+
}
37+
38+
is ChoiceEvent -> {
39+
val index = event.index
40+
41+
this.eventBodyFieldToAttribute<EventBodyFields.Role>(event) { role ->
42+
CustomAttribute("gen_ai.$index.${role.key}", role.value)
43+
}
44+
45+
this.eventBodyFieldToAttribute<EventBodyFields.Content>(event) { content ->
46+
CustomAttribute("gen_ai.completion.$index.content", content.value)
47+
}
48+
}
49+
50+
is UserMessageEvent -> {
51+
this.eventBodyFieldToAttribute<EventBodyFields.Content>(event) { content ->
52+
CustomAttribute("gen_ai.completion.content", content.value)
53+
}
54+
}
55+
56+
else -> { }
57+
}
58+
}
59+
}
60+
61+
//endregion Private Methods
62+
}

agents/agents-features/agents-features-opentelemetry/src/jvmTest/kotlin/ai/koog/agents/features/opentelemetry/feature/OpenTelemetryTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import ai.koog.agents.features.opentelemetry.OpenTelemetryTestAPI.createAgent
1212
import ai.koog.agents.features.opentelemetry.attribute.CustomAttribute
1313
import ai.koog.agents.features.opentelemetry.attribute.SpanAttributes
1414
import ai.koog.agents.features.opentelemetry.attribute.SpanAttributes.Response.FinishReasonType
15-
import ai.koog.agents.features.opentelemetry.integrations.SpanAdapter
15+
import ai.koog.agents.features.opentelemetry.integration.SpanAdapter
1616
import ai.koog.agents.features.opentelemetry.mock.MockSpanExporter
1717
import ai.koog.agents.features.opentelemetry.mock.TestGetWeatherTool
1818
import ai.koog.agents.features.opentelemetry.span.GenAIAgentSpan

examples/src/main/kotlin/ai/koog/agents/example/features/langfuse/Langfuse.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package ai.koog.agents.example.features.langfuse
33
import ai.koog.agents.core.agent.AIAgent
44
import ai.koog.agents.example.ApiKeyService
55
import ai.koog.agents.features.opentelemetry.feature.OpenTelemetry
6-
import ai.koog.agents.features.opentelemetry.integrations.addLangfuseExporter
6+
import ai.koog.agents.features.opentelemetry.integration.langfuse.addLangfuseExporter
77
import ai.koog.prompt.executor.clients.openai.OpenAIModels
88
import ai.koog.prompt.executor.llms.all.simpleOpenAIExecutor
99
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter

examples/src/main/kotlin/ai/koog/agents/example/features/weave/Weave.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package ai.koog.agents.example.features.weave
33
import ai.koog.agents.core.agent.AIAgent
44
import ai.koog.agents.example.ApiKeyService
55
import ai.koog.agents.features.opentelemetry.feature.OpenTelemetry
6-
import ai.koog.agents.features.opentelemetry.integrations.addWeaveExporter
6+
import ai.koog.agents.features.opentelemetry.integration.weave.addWeaveExporter
77
import ai.koog.prompt.executor.clients.openai.OpenAIModels
88
import ai.koog.prompt.executor.llms.all.simpleOpenAIExecutor
99
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter

0 commit comments

Comments
 (0)