Skip to content

Commit b945f9e

Browse files
committed
Migrate generative AI instrumentation semantic conventions to OTel 1.37.0.
Change-Id: I5fb76c82be9f53414fefe008b02b295410a72078 Change-Id: I2108810fbbb4ea96408637807e6da6abac0875d7
1 parent c3fd484 commit b945f9e

File tree

12 files changed

+329
-10
lines changed

12 files changed

+329
-10
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.api.incubator.semconv.genai;
7+
8+
import static io.opentelemetry.api.common.AttributeKey.stringKey;
9+
import static io.opentelemetry.instrumentation.api.internal.AttributesExtractorUtil.internalSet;
10+
11+
import io.opentelemetry.api.common.AttributeKey;
12+
import io.opentelemetry.api.common.AttributesBuilder;
13+
import io.opentelemetry.context.Context;
14+
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
15+
import javax.annotation.Nullable;
16+
17+
/**
18+
* Extractor of <a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-agent-spans/">
19+
* GenAI agent attributes</a>.
20+
*
21+
* <p>This class delegates to a type-specific {@link GenAiAgentAttributesGetter} for individual
22+
* attribute extraction from request/response objects.
23+
*/
24+
public final class GenAiAgentAttributesExtractor<REQUEST, RESPONSE>
25+
implements AttributesExtractor<REQUEST, RESPONSE> {
26+
27+
// copied from GenAiIncubatingAttributes
28+
private static final AttributeKey<String> GEN_AI_AGENT_DESCRIPTION =
29+
stringKey("gen_ai.agent.description");
30+
private static final AttributeKey<String> GEN_AI_AGENT_ID = stringKey("gen_ai.agent.id");
31+
private static final AttributeKey<String> GEN_AI_AGENT_NAME = stringKey("gen_ai.agent.name");
32+
private static final AttributeKey<String> GEN_AI_DATA_SOURCE_ID =
33+
stringKey("gen_ai.data_source.id");
34+
35+
/** Creates the GenAI agent attributes extractor. */
36+
public static <REQUEST, RESPONSE> AttributesExtractor<REQUEST, RESPONSE> create(
37+
GenAiAgentAttributesGetter<REQUEST, RESPONSE> attributesGetter) {
38+
return new GenAiAgentAttributesExtractor<>(attributesGetter);
39+
}
40+
41+
private final GenAiAgentAttributesGetter<REQUEST, RESPONSE> getter;
42+
43+
private GenAiAgentAttributesExtractor(GenAiAgentAttributesGetter<REQUEST, RESPONSE> getter) {
44+
this.getter = getter;
45+
}
46+
47+
@Override
48+
public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) {
49+
internalSet(attributes, GEN_AI_AGENT_DESCRIPTION, getter.getDescription(request));
50+
internalSet(attributes, GEN_AI_AGENT_ID, getter.getId(request));
51+
internalSet(attributes, GEN_AI_AGENT_NAME, getter.getName(request));
52+
internalSet(attributes, GEN_AI_DATA_SOURCE_ID, getter.getDataSourceId(request));
53+
}
54+
55+
@Override
56+
public void onEnd(
57+
AttributesBuilder attributes,
58+
Context context,
59+
REQUEST request,
60+
@Nullable RESPONSE response,
61+
@Nullable Throwable error) {}
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.api.incubator.semconv.genai;
7+
8+
import javax.annotation.Nullable;
9+
10+
/**
11+
* An interface for getting GenAI agent attributes.
12+
*
13+
* <p>Instrumentation authors will create implementations of this interface for their specific
14+
* library/framework. It will be used by the {@link GenAiAgentAttributesExtractor} to obtain the
15+
* various GenAI agent attributes in a type-generic way.
16+
*/
17+
public interface GenAiAgentAttributesGetter<REQUEST, RESPONSE>
18+
extends GenAiAttributesGetter<REQUEST, RESPONSE> {
19+
20+
@Nullable
21+
String getName(REQUEST request);
22+
23+
@Nullable
24+
String getDescription(REQUEST request);
25+
26+
@Nullable
27+
String getId(REQUEST request);
28+
29+
@Nullable
30+
String getDataSourceId(REQUEST request);
31+
}

instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiAttributesExtractor.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,13 @@ public final class GenAiAttributesExtractor<REQUEST, RESPONSE>
2929
implements AttributesExtractor<REQUEST, RESPONSE> {
3030

3131
// copied from GenAiIncubatingAttributes
32+
private static final AttributeKey<String> GEN_AI_CONVERSATION_ID =
33+
stringKey("gen_ai.conversation.id");
3234
static final AttributeKey<String> GEN_AI_OPERATION_NAME = stringKey("gen_ai.operation.name");
35+
private static final AttributeKey<String> GEN_AI_OUTPUT_TYPE = stringKey("gen_ai.output.type");
36+
static final AttributeKey<String> GEN_AI_PROVIDER_NAME = stringKey("gen_ai.provider.name");
37+
private static final AttributeKey<Long> GEN_AI_REQUEST_CHOICE_COUNT =
38+
longKey("gen_ai.request.choice.count");
3339
private static final AttributeKey<List<String>> GEN_AI_REQUEST_ENCODING_FORMATS =
3440
stringArrayKey("gen_ai.request.encoding_formats");
3541
private static final AttributeKey<Double> GEN_AI_REQUEST_FREQUENCY_PENALTY =
@@ -52,7 +58,6 @@ public final class GenAiAttributesExtractor<REQUEST, RESPONSE>
5258
stringArrayKey("gen_ai.response.finish_reasons");
5359
private static final AttributeKey<String> GEN_AI_RESPONSE_ID = stringKey("gen_ai.response.id");
5460
static final AttributeKey<String> GEN_AI_RESPONSE_MODEL = stringKey("gen_ai.response.model");
55-
static final AttributeKey<String> GEN_AI_PROVIDER_NAME = stringKey("gen_ai.provider.name");
5661
static final AttributeKey<Long> GEN_AI_USAGE_INPUT_TOKENS = longKey("gen_ai.usage.input_tokens");
5762
static final AttributeKey<Long> GEN_AI_USAGE_OUTPUT_TOKENS =
5863
longKey("gen_ai.usage.output_tokens");
@@ -71,8 +76,11 @@ private GenAiAttributesExtractor(GenAiAttributesGetter<REQUEST, RESPONSE> getter
7176

7277
@Override
7378
public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) {
79+
internalSet(attributes, GEN_AI_CONVERSATION_ID, getter.getConversationId(request));
7480
internalSet(attributes, GEN_AI_OPERATION_NAME, getter.getOperationName(request));
81+
internalSet(attributes, GEN_AI_OUTPUT_TYPE, getter.getOutputType(request));
7582
internalSet(attributes, GEN_AI_PROVIDER_NAME, getter.getSystem(request));
83+
internalSet(attributes, GEN_AI_REQUEST_CHOICE_COUNT, getter.getChoiceCount(request));
7684
internalSet(attributes, GEN_AI_REQUEST_MODEL, getter.getRequestModel(request));
7785
internalSet(attributes, GEN_AI_REQUEST_SEED, getter.getRequestSeed(request));
7886
internalSet(

instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiAttributesGetter.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
* library/framework. It will be used by the {@link GenAiAttributesExtractor} to obtain the various
1616
* GenAI attributes in a type-generic way.
1717
*/
18-
public interface GenAiAttributesGetter<REQUEST, RESPONSE> {
19-
String getOperationName(REQUEST request);
18+
public interface GenAiAttributesGetter<REQUEST, RESPONSE>
19+
extends GenAiOperationAttributesGetter<REQUEST, RESPONSE> {
2020

2121
String getSystem(REQUEST request);
2222

@@ -50,6 +50,21 @@ public interface GenAiAttributesGetter<REQUEST, RESPONSE> {
5050
@Nullable
5151
Double getRequestTopP(REQUEST request);
5252

53+
@Nullable
54+
default Long getChoiceCount(REQUEST request) {
55+
return null;
56+
}
57+
58+
@Nullable
59+
default String getOutputType(REQUEST request) {
60+
return null;
61+
}
62+
63+
@Nullable
64+
default String getConversationId(REQUEST request) {
65+
return null;
66+
}
67+
5368
List<String> getResponseFinishReasons(REQUEST request, @Nullable RESPONSE response);
5469

5570
@Nullable
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.api.incubator.semconv.genai;
7+
8+
import javax.annotation.Nullable;
9+
10+
/**
11+
* An interface for getting GenAI operation attributes.
12+
*
13+
* <p>Instrumentation authors will create implementations of this interface for their specific
14+
* library/framework. It will be used by the {@link GenAiAttributesExtractor} to obtain the various
15+
* GenAI attributes in a type-generic way. It will also be used by the {@link
16+
* GenAiSpanNameExtractor} to generate span name in a type-generic way.
17+
*/
18+
public interface GenAiOperationAttributesGetter<REQUEST, RESPONSE> {
19+
20+
String getOperationName(REQUEST request);
21+
22+
@Nullable
23+
String getOperationTarget(REQUEST request);
24+
}

instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiSpanNameExtractor.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,33 @@ public final class GenAiSpanNameExtractor<REQUEST> implements SpanNameExtractor<
1212

1313
/**
1414
* Returns a {@link SpanNameExtractor} that constructs the span name according to GenAI semantic
15-
* conventions: {@code <gen_ai.operation.name> <gen_ai.request.model>}.
15+
* conventions.
16+
*
17+
* <ul>
18+
* <li>Inference - {@code <gen_ai.operation.name> <gen_ai.request.model>}.
19+
* <li>Embeddings - {@code <gen_ai.operation.name> <gen_ai.request.model>}.
20+
* <li>Execute tool - {@code execute_tool <gen_ai.tool.name>}.
21+
* <li>Create/Invoke agent - {@code <gen_ai.operation.name> <gen_ai.agent.name>}.
22+
* </ul>
1623
*/
1724
public static <REQUEST> SpanNameExtractor<REQUEST> create(
18-
GenAiAttributesGetter<REQUEST, ?> attributesGetter) {
25+
GenAiOperationAttributesGetter<REQUEST, ?> attributesGetter) {
1926
return new GenAiSpanNameExtractor<>(attributesGetter);
2027
}
2128

22-
private final GenAiAttributesGetter<REQUEST, ?> getter;
29+
private final GenAiOperationAttributesGetter<REQUEST, ?> getter;
2330

24-
private GenAiSpanNameExtractor(GenAiAttributesGetter<REQUEST, ?> getter) {
31+
private GenAiSpanNameExtractor(GenAiOperationAttributesGetter<REQUEST, ?> getter) {
2532
this.getter = getter;
2633
}
2734

2835
@Override
2936
public String extract(REQUEST request) {
3037
String operation = getter.getOperationName(request);
31-
String model = getter.getRequestModel(request);
32-
if (model == null) {
38+
String operationTarget = getter.getOperationTarget(request);
39+
if (operationTarget == null) {
3340
return operation;
3441
}
35-
return operation + ' ' + model;
42+
return operation + ' ' + operationTarget;
3643
}
3744
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.api.incubator.semconv.genai;
7+
8+
import static io.opentelemetry.api.common.AttributeKey.stringKey;
9+
import static io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiAttributesExtractor.GEN_AI_OPERATION_NAME;
10+
import static io.opentelemetry.instrumentation.api.internal.AttributesExtractorUtil.internalSet;
11+
12+
import io.opentelemetry.api.common.AttributeKey;
13+
import io.opentelemetry.api.common.AttributesBuilder;
14+
import io.opentelemetry.context.Context;
15+
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
16+
import javax.annotation.Nullable;
17+
18+
/**
19+
* Extractor of <a
20+
* href="https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/#execute-tool-span">GenAI
21+
* tool attributes</a>.
22+
*
23+
* <p>This class delegates to a type-specific {@link GenAiToolAttributesGetter} for individual
24+
* attribute extraction from request/response objects.
25+
*/
26+
public class GenAiToolAttributesExtractor<REQUEST, RESPONSE>
27+
implements AttributesExtractor<REQUEST, RESPONSE> {
28+
29+
// copied from GenAiIncubatingAttributes
30+
private static final AttributeKey<String> GEN_AI_TOOL_CALL_ID = stringKey("gen_ai.tool.call.id");
31+
private static final AttributeKey<String> GEN_AI_TOOL_DESCRIPTION =
32+
stringKey("gen_ai.tool.description");
33+
private static final AttributeKey<String> GEN_AI_TOOL_NAME = stringKey("gen_ai.tool.name");
34+
private static final AttributeKey<String> GEN_AI_TOOL_TYPE = stringKey("gen_ai.tool.type");
35+
36+
/** Creates the GenAI tool attributes extractor. */
37+
public static <REQUEST, RESPONSE> AttributesExtractor<REQUEST, RESPONSE> create(
38+
GenAiToolAttributesGetter<REQUEST, RESPONSE> attributesGetter) {
39+
return new GenAiToolAttributesExtractor<>(attributesGetter);
40+
}
41+
42+
private final GenAiToolAttributesGetter<REQUEST, RESPONSE> getter;
43+
44+
private GenAiToolAttributesExtractor(GenAiToolAttributesGetter<REQUEST, RESPONSE> getter) {
45+
this.getter = getter;
46+
}
47+
48+
@Override
49+
public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) {
50+
internalSet(attributes, GEN_AI_OPERATION_NAME, getter.getOperationName(request));
51+
internalSet(attributes, GEN_AI_TOOL_DESCRIPTION, getter.getToolDescription(request));
52+
internalSet(attributes, GEN_AI_TOOL_NAME, getter.getToolName(request));
53+
internalSet(attributes, GEN_AI_TOOL_TYPE, getter.getToolType(request));
54+
}
55+
56+
@Override
57+
public void onEnd(
58+
AttributesBuilder attributes,
59+
Context context,
60+
REQUEST request,
61+
@Nullable RESPONSE response,
62+
@Nullable Throwable error) {
63+
internalSet(attributes, GEN_AI_TOOL_CALL_ID, getter.getToolCallId(request, response));
64+
}
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.api.incubator.semconv.genai;
7+
8+
import javax.annotation.Nullable;
9+
10+
/**
11+
* An interface for getting GenAI tool attributes.
12+
*
13+
* <p>Instrumentation authors will create implementations of this interface for their specific
14+
* library/framework. It will be used by the {@link GenAiToolAttributesExtractor} to obtain the
15+
* various GenAI tool attributes in a type-generic way.
16+
*/
17+
public interface GenAiToolAttributesGetter<REQUEST, RESPONSE>
18+
extends GenAiOperationAttributesGetter<REQUEST, RESPONSE> {
19+
20+
String getToolDescription(REQUEST request);
21+
22+
String getToolName(REQUEST request);
23+
24+
String getToolType(REQUEST request);
25+
26+
@Nullable
27+
String getToolCallId(REQUEST request, @Nullable RESPONSE response);
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.api.incubator.semconv.genai;
7+
8+
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiOperationNameIncubatingValues.CHAT;
9+
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiOperationNameIncubatingValues.CREATE_AGENT;
10+
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiOperationNameIncubatingValues.EMBEDDINGS;
11+
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiOperationNameIncubatingValues.EXECUTE_TOOL;
12+
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiOperationNameIncubatingValues.GENERATE_CONTENT;
13+
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiOperationNameIncubatingValues.INVOKE_AGENT;
14+
import static org.junit.jupiter.api.Assertions.assertEquals;
15+
import static org.mockito.Mockito.when;
16+
17+
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
18+
import java.util.stream.Stream;
19+
import org.junit.jupiter.api.extension.ExtendWith;
20+
import org.junit.jupiter.params.ParameterizedTest;
21+
import org.junit.jupiter.params.provider.Arguments;
22+
import org.junit.jupiter.params.provider.MethodSource;
23+
import org.mockito.Mock;
24+
import org.mockito.junit.jupiter.MockitoExtension;
25+
26+
@ExtendWith(MockitoExtension.class)
27+
public class GenAiSpanNameExtractorTest {
28+
29+
@Mock GenAiOperationAttributesGetter<Request, Void> getter;
30+
31+
@ParameterizedTest
32+
@MethodSource("spanNameParams")
33+
void shouldExtractSpanName(
34+
String operationName, String operationTarget, String expectedSpanName) {
35+
// given
36+
Request request = new Request();
37+
38+
when(getter.getOperationName(request)).thenReturn(operationName);
39+
when(getter.getOperationTarget(request)).thenReturn(operationTarget);
40+
41+
SpanNameExtractor<Request> underTest = GenAiSpanNameExtractor.create(getter);
42+
43+
// when
44+
String spanName = underTest.extract(request);
45+
46+
// then
47+
assertEquals(expectedSpanName, spanName);
48+
}
49+
50+
static Stream<Arguments> spanNameParams() {
51+
return Stream.of(
52+
Arguments.of(CHAT, "gpt-4o", "chat gpt-4o"),
53+
Arguments.of(GENERATE_CONTENT, "qwen-max", "generate_content qwen-max"),
54+
Arguments.of(EMBEDDINGS, "text-embeddings-v2", "embeddings text-embeddings-v2"),
55+
Arguments.of(EXECUTE_TOOL, "get_weather", "execute_tool get_weather"),
56+
Arguments.of(CREATE_AGENT, "summary_agent", "create_agent summary_agent"),
57+
Arguments.of(INVOKE_AGENT, "order_assistant", "invoke_agent order_assistant"));
58+
}
59+
60+
static class Request {}
61+
}

instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/BedrockRuntimeAttributesGetter.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ public String getOperationName(ExecutionAttributes executionAttributes) {
2828
return BedrockRuntimeAccess.getOperationName(executionAttributes);
2929
}
3030

31+
@Override
32+
public String getOperationTarget(ExecutionAttributes executionAttributes) {
33+
// FIXME: Only work when operation name are chat or text_completion. Fix this if there's more
34+
// kinds of operation names.
35+
return getRequestModel(executionAttributes);
36+
}
37+
3138
@Override
3239
public String getSystem(ExecutionAttributes executionAttributes) {
3340
return GenAiProviderNameIncubatingValues.AWS_BEDROCK;

0 commit comments

Comments
 (0)