diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/CaptureMessageOptions.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/CaptureMessageOptions.java new file mode 100644 index 000000000000..f29444493ca9 --- /dev/null +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/CaptureMessageOptions.java @@ -0,0 +1,21 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.incubator.semconv.genai; + +import com.google.auto.value.AutoValue; + +@AutoValue +public abstract class CaptureMessageOptions { + + public abstract boolean captureMessageContent(); + + public abstract boolean emitExperimentalConventions(); + + public static CaptureMessageOptions create( + boolean captureMessageContent, boolean emitExperimentalConventions) { + return new AutoValue_CaptureMessageOptions(captureMessageContent, emitExperimentalConventions); + } +} diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiAgentAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiAgentAttributesExtractor.java new file mode 100644 index 000000000000..466e3429d970 --- /dev/null +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiAgentAttributesExtractor.java @@ -0,0 +1,62 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.incubator.semconv.genai; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.instrumentation.api.internal.AttributesExtractorUtil.internalSet; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import javax.annotation.Nullable; + +/** + * Extractor of + * GenAI agent attributes. + * + *

This class delegates to a type-specific {@link GenAiAgentAttributesGetter} for individual + * attribute extraction from request/response objects. + */ +public final class GenAiAgentAttributesExtractor + implements AttributesExtractor { + + // copied from GenAiIncubatingAttributes + private static final AttributeKey GEN_AI_AGENT_DESCRIPTION = + stringKey("gen_ai.agent.description"); + private static final AttributeKey GEN_AI_AGENT_ID = stringKey("gen_ai.agent.id"); + private static final AttributeKey GEN_AI_AGENT_NAME = stringKey("gen_ai.agent.name"); + private static final AttributeKey GEN_AI_DATA_SOURCE_ID = + stringKey("gen_ai.data_source.id"); + + /** Creates the GenAI agent attributes extractor. */ + public static AttributesExtractor create( + GenAiAgentAttributesGetter attributesGetter) { + return new GenAiAgentAttributesExtractor<>(attributesGetter); + } + + private final GenAiAgentAttributesGetter getter; + + private GenAiAgentAttributesExtractor(GenAiAgentAttributesGetter getter) { + this.getter = getter; + } + + @Override + public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) { + internalSet(attributes, GEN_AI_AGENT_DESCRIPTION, getter.getDescription(request)); + internalSet(attributes, GEN_AI_AGENT_ID, getter.getId(request)); + internalSet(attributes, GEN_AI_AGENT_NAME, getter.getName(request)); + internalSet(attributes, GEN_AI_DATA_SOURCE_ID, getter.getDataSourceId(request)); + } + + @Override + public void onEnd( + AttributesBuilder attributes, + Context context, + REQUEST request, + @Nullable RESPONSE response, + @Nullable Throwable error) {} +} diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiAgentAttributesGetter.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiAgentAttributesGetter.java new file mode 100644 index 000000000000..22a7795271ec --- /dev/null +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiAgentAttributesGetter.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.incubator.semconv.genai; + +import javax.annotation.Nullable; + +/** + * An interface for getting GenAI agent attributes. + * + *

Instrumentation authors will create implementations of this interface for their specific + * library/framework. It will be used by the {@link GenAiAgentAttributesExtractor} to obtain the + * various GenAI agent attributes in a type-generic way. + */ +public interface GenAiAgentAttributesGetter + extends GenAiAttributesGetter { + + @Nullable + String getName(REQUEST request); + + @Nullable + String getDescription(REQUEST request); + + @Nullable + String getId(REQUEST request); + + @Nullable + String getDataSourceId(REQUEST request); +} diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiAttributesExtractor.java index a282e8ec5dcf..9c0ec45ae104 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiAttributesExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiAttributesExtractor.java @@ -29,7 +29,13 @@ public final class GenAiAttributesExtractor implements AttributesExtractor { // copied from GenAiIncubatingAttributes + private static final AttributeKey GEN_AI_CONVERSATION_ID = + stringKey("gen_ai.conversation.id"); static final AttributeKey GEN_AI_OPERATION_NAME = stringKey("gen_ai.operation.name"); + private static final AttributeKey GEN_AI_OUTPUT_TYPE = stringKey("gen_ai.output.type"); + static final AttributeKey GEN_AI_PROVIDER_NAME = stringKey("gen_ai.provider.name"); + private static final AttributeKey GEN_AI_REQUEST_CHOICE_COUNT = + longKey("gen_ai.request.choice.count"); private static final AttributeKey> GEN_AI_REQUEST_ENCODING_FORMATS = stringArrayKey("gen_ai.request.encoding_formats"); private static final AttributeKey GEN_AI_REQUEST_FREQUENCY_PENALTY = @@ -52,7 +58,6 @@ public final class GenAiAttributesExtractor stringArrayKey("gen_ai.response.finish_reasons"); private static final AttributeKey GEN_AI_RESPONSE_ID = stringKey("gen_ai.response.id"); static final AttributeKey GEN_AI_RESPONSE_MODEL = stringKey("gen_ai.response.model"); - static final AttributeKey GEN_AI_PROVIDER_NAME = stringKey("gen_ai.provider.name"); static final AttributeKey GEN_AI_USAGE_INPUT_TOKENS = longKey("gen_ai.usage.input_tokens"); static final AttributeKey GEN_AI_USAGE_OUTPUT_TOKENS = longKey("gen_ai.usage.output_tokens"); @@ -71,8 +76,11 @@ private GenAiAttributesExtractor(GenAiAttributesGetter getter @Override public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) { + internalSet(attributes, GEN_AI_CONVERSATION_ID, getter.getConversationId(request)); internalSet(attributes, GEN_AI_OPERATION_NAME, getter.getOperationName(request)); + internalSet(attributes, GEN_AI_OUTPUT_TYPE, getter.getOutputType(request)); internalSet(attributes, GEN_AI_PROVIDER_NAME, getter.getSystem(request)); + internalSet(attributes, GEN_AI_REQUEST_CHOICE_COUNT, getter.getChoiceCount(request)); internalSet(attributes, GEN_AI_REQUEST_MODEL, getter.getRequestModel(request)); internalSet(attributes, GEN_AI_REQUEST_SEED, getter.getRequestSeed(request)); internalSet( diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiAttributesGetter.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiAttributesGetter.java index ed2e48cd8024..7b731843dd7c 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiAttributesGetter.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiAttributesGetter.java @@ -15,8 +15,8 @@ * library/framework. It will be used by the {@link GenAiAttributesExtractor} to obtain the various * GenAI attributes in a type-generic way. */ -public interface GenAiAttributesGetter { - String getOperationName(REQUEST request); +public interface GenAiAttributesGetter + extends GenAiOperationAttributesGetter { String getSystem(REQUEST request); @@ -50,6 +50,21 @@ public interface GenAiAttributesGetter { @Nullable Double getRequestTopP(REQUEST request); + @Nullable + default Long getChoiceCount(REQUEST request) { + return null; + } + + @Nullable + default String getOutputType(REQUEST request) { + return null; + } + + @Nullable + default String getConversationId(REQUEST request) { + return null; + } + List getResponseFinishReasons(REQUEST request, @Nullable RESPONSE response); @Nullable diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiOperationAttributesGetter.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiOperationAttributesGetter.java new file mode 100644 index 000000000000..b01a1b88559e --- /dev/null +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiOperationAttributesGetter.java @@ -0,0 +1,24 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.incubator.semconv.genai; + +import javax.annotation.Nullable; + +/** + * An interface for getting GenAI operation attributes. + * + *

Instrumentation authors will create implementations of this interface for their specific + * library/framework. It will be used by the {@link GenAiAttributesExtractor} to obtain the various + * GenAI attributes in a type-generic way. It will also be used by the {@link + * GenAiSpanNameExtractor} to generate span name in a type-generic way. + */ +public interface GenAiOperationAttributesGetter { + + String getOperationName(REQUEST request); + + @Nullable + String getOperationTarget(REQUEST request); +} diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiSpanNameExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiSpanNameExtractor.java index d8a7f517da3c..63747b4fa7ff 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiSpanNameExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiSpanNameExtractor.java @@ -12,26 +12,33 @@ public final class GenAiSpanNameExtractor implements SpanNameExtractor< /** * Returns a {@link SpanNameExtractor} that constructs the span name according to GenAI semantic - * conventions: {@code }. + * conventions. + * + *

*/ public static SpanNameExtractor create( - GenAiAttributesGetter attributesGetter) { + GenAiOperationAttributesGetter attributesGetter) { return new GenAiSpanNameExtractor<>(attributesGetter); } - private final GenAiAttributesGetter getter; + private final GenAiOperationAttributesGetter getter; - private GenAiSpanNameExtractor(GenAiAttributesGetter getter) { + private GenAiSpanNameExtractor(GenAiOperationAttributesGetter getter) { this.getter = getter; } @Override public String extract(REQUEST request) { String operation = getter.getOperationName(request); - String model = getter.getRequestModel(request); - if (model == null) { + String operationTarget = getter.getOperationTarget(request); + if (operationTarget == null) { return operation; } - return operation + ' ' + model; + return operation + ' ' + operationTarget; } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiToolAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiToolAttributesExtractor.java new file mode 100644 index 000000000000..7cc8a861dc0d --- /dev/null +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiToolAttributesExtractor.java @@ -0,0 +1,65 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.incubator.semconv.genai; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiAttributesExtractor.GEN_AI_OPERATION_NAME; +import static io.opentelemetry.instrumentation.api.internal.AttributesExtractorUtil.internalSet; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import javax.annotation.Nullable; + +/** + * Extractor of GenAI + * tool attributes. + * + *

This class delegates to a type-specific {@link GenAiToolAttributesGetter} for individual + * attribute extraction from request/response objects. + */ +public class GenAiToolAttributesExtractor + implements AttributesExtractor { + + // copied from GenAiIncubatingAttributes + private static final AttributeKey GEN_AI_TOOL_CALL_ID = stringKey("gen_ai.tool.call.id"); + private static final AttributeKey GEN_AI_TOOL_DESCRIPTION = + stringKey("gen_ai.tool.description"); + private static final AttributeKey GEN_AI_TOOL_NAME = stringKey("gen_ai.tool.name"); + private static final AttributeKey GEN_AI_TOOL_TYPE = stringKey("gen_ai.tool.type"); + + /** Creates the GenAI tool attributes extractor. */ + public static AttributesExtractor create( + GenAiToolAttributesGetter attributesGetter) { + return new GenAiToolAttributesExtractor<>(attributesGetter); + } + + private final GenAiToolAttributesGetter getter; + + private GenAiToolAttributesExtractor(GenAiToolAttributesGetter getter) { + this.getter = getter; + } + + @Override + public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) { + internalSet(attributes, GEN_AI_OPERATION_NAME, getter.getOperationName(request)); + internalSet(attributes, GEN_AI_TOOL_DESCRIPTION, getter.getToolDescription(request)); + internalSet(attributes, GEN_AI_TOOL_NAME, getter.getToolName(request)); + internalSet(attributes, GEN_AI_TOOL_TYPE, getter.getToolType(request)); + } + + @Override + public void onEnd( + AttributesBuilder attributes, + Context context, + REQUEST request, + @Nullable RESPONSE response, + @Nullable Throwable error) { + internalSet(attributes, GEN_AI_TOOL_CALL_ID, getter.getToolCallId(request, response)); + } +} diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiToolAttributesGetter.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiToolAttributesGetter.java new file mode 100644 index 000000000000..cc2ea1283882 --- /dev/null +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiToolAttributesGetter.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.incubator.semconv.genai; + +import javax.annotation.Nullable; + +/** + * An interface for getting GenAI tool attributes. + * + *

Instrumentation authors will create implementations of this interface for their specific + * library/framework. It will be used by the {@link GenAiToolAttributesExtractor} to obtain the + * various GenAI tool attributes in a type-generic way. + */ +public interface GenAiToolAttributesGetter + extends GenAiOperationAttributesGetter { + + String getToolDescription(REQUEST request); + + String getToolName(REQUEST request); + + String getToolType(REQUEST request); + + @Nullable + String getToolCallId(REQUEST request, @Nullable RESPONSE response); +} diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiSpanNameExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiSpanNameExtractorTest.java new file mode 100644 index 000000000000..6fe960a6d3b9 --- /dev/null +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiSpanNameExtractorTest.java @@ -0,0 +1,61 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.incubator.semconv.genai; + +import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiOperationNameIncubatingValues.CHAT; +import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiOperationNameIncubatingValues.CREATE_AGENT; +import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiOperationNameIncubatingValues.EMBEDDINGS; +import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiOperationNameIncubatingValues.EXECUTE_TOOL; +import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiOperationNameIncubatingValues.GENERATE_CONTENT; +import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiOperationNameIncubatingValues.INVOKE_AGENT; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; +import java.util.stream.Stream; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class GenAiSpanNameExtractorTest { + + @Mock GenAiOperationAttributesGetter getter; + + @ParameterizedTest + @MethodSource("spanNameParams") + void shouldExtractSpanName( + String operationName, String operationTarget, String expectedSpanName) { + // given + Request request = new Request(); + + when(getter.getOperationName(request)).thenReturn(operationName); + when(getter.getOperationTarget(request)).thenReturn(operationTarget); + + SpanNameExtractor underTest = GenAiSpanNameExtractor.create(getter); + + // when + String spanName = underTest.extract(request); + + // then + assertEquals(expectedSpanName, spanName); + } + + static Stream spanNameParams() { + return Stream.of( + Arguments.of(CHAT, "gpt-4o", "chat gpt-4o"), + Arguments.of(GENERATE_CONTENT, "qwen-max", "generate_content qwen-max"), + Arguments.of(EMBEDDINGS, "text-embeddings-v2", "embeddings text-embeddings-v2"), + Arguments.of(EXECUTE_TOOL, "get_weather", "execute_tool get_weather"), + Arguments.of(CREATE_AGENT, "summary_agent", "create_agent summary_agent"), + Arguments.of(INVOKE_AGENT, "order_assistant", "invoke_agent order_assistant")); + } + + static class Request {} +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/BedrockRuntimeAttributesGetter.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/BedrockRuntimeAttributesGetter.java index e3c0d2be75f3..75af1b5f664a 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/BedrockRuntimeAttributesGetter.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/BedrockRuntimeAttributesGetter.java @@ -28,6 +28,13 @@ public String getOperationName(ExecutionAttributes executionAttributes) { return BedrockRuntimeAccess.getOperationName(executionAttributes); } + @Override + public String getOperationTarget(ExecutionAttributes executionAttributes) { + // FIXME: Only work when operation name are chat or text_completion. Fix this if there's more + // kinds of operation names. + return getRequestModel(executionAttributes); + } + @Override public String getSystem(ExecutionAttributes executionAttributes) { return GenAiProviderNameIncubatingValues.AWS_BEDROCK; diff --git a/instrumentation/openai/openai-java-1.1/javaagent/README.md b/instrumentation/openai/openai-java-1.1/javaagent/README.md index 4d8f5ac5e7d2..e3fd7c91313a 100644 --- a/instrumentation/openai/openai-java-1.1/javaagent/README.md +++ b/instrumentation/openai/openai-java-1.1/javaagent/README.md @@ -1,5 +1,6 @@ # Settings for the OpenAI instrumentation -| System property | Type | Default | Description | -|------------------------------------------------------|---------|---------|------------------------------------------| -| `otel.instrumentation.genai.capture-message-content` | Boolean | `false` | Record content of user and LLM messages. | +| System property | Type | Default | Description | +|------------------------------------------------------|---------|---------|------------------------------------------------------------------------| +| `otel.instrumentation.genai.capture-message-content` | Boolean | `false` | Record content of user and LLM messages. | +| `otel.semconv-stability.opt-in` | Boolean | `` | Enable experimental features when set as `gen_ai_latest_experimental`. | diff --git a/instrumentation/openai/openai-java-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/openai/v1_1/OpenAiSingletons.java b/instrumentation/openai/openai-java-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/openai/v1_1/OpenAiSingletons.java index 2e956eddc0e3..4c17497efec7 100644 --- a/instrumentation/openai/openai-java-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/openai/v1_1/OpenAiSingletons.java +++ b/instrumentation/openai/openai-java-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/openai/v1_1/OpenAiSingletons.java @@ -15,7 +15,13 @@ public final class OpenAiSingletons { .setCaptureMessageContent( AgentInstrumentationConfig.get() .getBoolean("otel.instrumentation.genai.capture-message-content", false)) + .setEmitExperimentalConventions(emitExperimentalConventions()) .build(); + private static boolean emitExperimentalConventions() { + String config = AgentInstrumentationConfig.get().getString("otel.semconv-stability.opt-in", ""); + return config.contains("gen_ai_latest_experimental"); + } + private OpenAiSingletons() {} } diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/ChatAttributesGetter.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/ChatAttributesGetter.java index 84752ad0c2fd..0ecbae91c56a 100644 --- a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/ChatAttributesGetter.java +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/ChatAttributesGetter.java @@ -10,6 +10,7 @@ import com.openai.models.chat.completions.ChatCompletion; import com.openai.models.chat.completions.ChatCompletionCreateParams; +import com.openai.models.chat.completions.ChatCompletionCreateParams.ResponseFormat; import com.openai.models.completions.CompletionUsage; import io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiAttributesGetter; import java.util.List; @@ -25,6 +26,11 @@ public String getOperationName(ChatCompletionCreateParams request) { return GenAiAttributes.GenAiOperationNameIncubatingValues.CHAT; } + @Override + public String getOperationTarget(ChatCompletionCreateParams request) { + return getRequestModel(request); + } + @Override public String getSystem(ChatCompletionCreateParams request) { return GenAiAttributes.GenAiProviderNameIncubatingValues.OPENAI; @@ -101,6 +107,26 @@ public Double getRequestTopP(ChatCompletionCreateParams request) { return request.topP().orElse(null); } + @Nullable + @Override + public Long getChoiceCount(ChatCompletionCreateParams request) { + return request.n().orElse(null); + } + + @Nullable + @Override + public String getOutputType(ChatCompletionCreateParams request) { + if (request.responseFormat().isPresent()) { + ResponseFormat responseFormat = request.responseFormat().get(); + if (responseFormat.isText()) { + return GenAiAttributes.GenAiOutputTypeIncubatingValues.TEXT; + } else if (responseFormat.isJsonSchema() || responseFormat.isJsonObject()) { + return GenAiAttributes.GenAiOutputTypeIncubatingValues.JSON; + } + } + return null; + } + @Override public List getResponseFinishReasons( ChatCompletionCreateParams request, @Nullable ChatCompletion response) { diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/ChatCompletionEventsHelper.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/ChatCompletionEventsHelper.java index 5aa2551c2d20..06b576b88e78 100644 --- a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/ChatCompletionEventsHelper.java +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/ChatCompletionEventsHelper.java @@ -24,6 +24,7 @@ import io.opentelemetry.api.logs.LogRecordBuilder; import io.opentelemetry.api.logs.Logger; import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.incubator.semconv.genai.CaptureMessageOptions; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; @@ -43,7 +44,13 @@ public static void emitPromptLogEvents( Context context, Logger eventLogger, ChatCompletionCreateParams request, - boolean captureMessageContent) { + CaptureMessageOptions captureMessageOptions) { + if (captureMessageOptions.emitExperimentalConventions()) { + // According to https://opentelemetry.io/docs/specs/semconv/gen-ai/openai/ + // skip if experimental conventions are not enabled + return; + } + boolean captureMessageContent = captureMessageOptions.captureMessageContent(); for (ChatCompletionMessageParam msg : request.messages()) { String eventType; Map> body = new HashMap<>(); @@ -170,7 +177,13 @@ public static void emitCompletionLogEvents( Context context, Logger eventLogger, ChatCompletion completion, - boolean captureMessageContent) { + CaptureMessageOptions captureMessageOptions) { + if (captureMessageOptions.emitExperimentalConventions()) { + // According to https://opentelemetry.io/docs/specs/semconv/gen-ai/openai/ + // skip if experimental conventions are not enabled + return; + } + boolean captureMessageContent = captureMessageOptions.captureMessageContent(); for (ChatCompletion.Choice choice : completion.choices()) { ChatCompletionMessage choiceMsg = choice.message(); Map> message = new HashMap<>(); diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/EmbeddingAttributesGetter.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/EmbeddingAttributesGetter.java index 18ad487a452c..13f940b52a3e 100644 --- a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/EmbeddingAttributesGetter.java +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/EmbeddingAttributesGetter.java @@ -23,6 +23,11 @@ public String getOperationName(EmbeddingCreateParams request) { return GenAiAttributes.GenAiOperationNameIncubatingValues.EMBEDDINGS; } + @Override + public String getOperationTarget(EmbeddingCreateParams request) { + return getRequestModel(request); + } + @Override public String getSystem(EmbeddingCreateParams request) { return GenAiAttributes.GenAiProviderNameIncubatingValues.OPENAI; diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/GenAiAttributes.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/GenAiAttributes.java index 4cccf2182aaa..2397da536fb3 100644 --- a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/GenAiAttributes.java +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/GenAiAttributes.java @@ -26,5 +26,13 @@ static final class GenAiProviderNameIncubatingValues { private GenAiProviderNameIncubatingValues() {} } + static final class GenAiOutputTypeIncubatingValues { + static final String TEXT = "text"; + + static final String JSON = "json"; + + private GenAiOutputTypeIncubatingValues() {} + } + private GenAiAttributes() {} } diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedChatCompletionService.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedChatCompletionService.java index cef6b2ba2f68..7fd35ddc055d 100644 --- a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedChatCompletionService.java +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedChatCompletionService.java @@ -14,6 +14,7 @@ import io.opentelemetry.api.logs.Logger; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.incubator.semconv.genai.CaptureMessageOptions; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import java.lang.reflect.Method; @@ -22,17 +23,17 @@ final class InstrumentedChatCompletionService private final Instrumenter instrumenter; private final Logger eventLogger; - private final boolean captureMessageContent; + private final CaptureMessageOptions captureMessageOptions; InstrumentedChatCompletionService( ChatCompletionService delegate, Instrumenter instrumenter, Logger eventLogger, - boolean captureMessageContent) { + CaptureMessageOptions captureMessageOptions) { super(delegate); this.instrumenter = instrumenter; this.eventLogger = eventLogger; - this.captureMessageContent = captureMessageContent; + this.captureMessageOptions = captureMessageOptions; } @Override @@ -96,10 +97,10 @@ private ChatCompletion createWithLogs( ChatCompletionCreateParams chatCompletionCreateParams, RequestOptions requestOptions) { ChatCompletionEventsHelper.emitPromptLogEvents( - context, eventLogger, chatCompletionCreateParams, captureMessageContent); + context, eventLogger, chatCompletionCreateParams, captureMessageOptions); ChatCompletion result = delegate.create(chatCompletionCreateParams, requestOptions); ChatCompletionEventsHelper.emitCompletionLogEvents( - context, eventLogger, result, captureMessageContent); + context, eventLogger, result, captureMessageOptions); return result; } @@ -126,7 +127,7 @@ private StreamResponse createStreamingWithLogs( RequestOptions requestOptions, boolean newSpan) { ChatCompletionEventsHelper.emitPromptLogEvents( - context, eventLogger, chatCompletionCreateParams, captureMessageContent); + context, eventLogger, chatCompletionCreateParams, captureMessageOptions); StreamResponse result = delegate.createStreaming(chatCompletionCreateParams, requestOptions); return new TracingStreamedResponse( @@ -136,7 +137,7 @@ private StreamResponse createStreamingWithLogs( chatCompletionCreateParams, instrumenter, eventLogger, - captureMessageContent, + captureMessageOptions.captureMessageContent(), newSpan)); } } diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedChatCompletionServiceAsync.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedChatCompletionServiceAsync.java index 5b2887b304eb..f717ae68c491 100644 --- a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedChatCompletionServiceAsync.java +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedChatCompletionServiceAsync.java @@ -14,6 +14,7 @@ import io.opentelemetry.api.logs.Logger; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.incubator.semconv.genai.CaptureMessageOptions; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import java.lang.reflect.Method; import java.util.concurrent.CompletableFuture; @@ -24,17 +25,17 @@ final class InstrumentedChatCompletionServiceAsync private final Instrumenter instrumenter; private final Logger eventLogger; - private final boolean captureMessageContent; + private final CaptureMessageOptions captureMessageOptions; InstrumentedChatCompletionServiceAsync( ChatCompletionServiceAsync delegate, Instrumenter instrumenter, Logger eventLogger, - boolean captureMessageContent) { + CaptureMessageOptions captureMessageOptions) { super(delegate); this.instrumenter = instrumenter; this.eventLogger = eventLogger; - this.captureMessageContent = captureMessageContent; + this.captureMessageOptions = captureMessageOptions; } @Override @@ -100,13 +101,13 @@ private CompletableFuture createWithLogs( ChatCompletionCreateParams chatCompletionCreateParams, RequestOptions requestOptions) { ChatCompletionEventsHelper.emitPromptLogEvents( - context, eventLogger, chatCompletionCreateParams, captureMessageContent); + context, eventLogger, chatCompletionCreateParams, captureMessageOptions); CompletableFuture future = delegate.create(chatCompletionCreateParams, requestOptions); future.thenAccept( r -> ChatCompletionEventsHelper.emitCompletionLogEvents( - context, eventLogger, r, captureMessageContent)); + context, eventLogger, r, captureMessageOptions)); return future; } @@ -133,7 +134,7 @@ private AsyncStreamResponse createStreamingWithLogs( RequestOptions requestOptions, boolean newSpan) { ChatCompletionEventsHelper.emitPromptLogEvents( - context, eventLogger, chatCompletionCreateParams, captureMessageContent); + context, eventLogger, chatCompletionCreateParams, captureMessageOptions); AsyncStreamResponse result = delegate.createStreaming(chatCompletionCreateParams, requestOptions); return new TracingAsyncStreamedResponse( @@ -143,7 +144,7 @@ private AsyncStreamResponse createStreamingWithLogs( chatCompletionCreateParams, instrumenter, eventLogger, - captureMessageContent, + captureMessageOptions.captureMessageContent(), newSpan)); } } diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedChatService.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedChatService.java index 0397279f119a..4ad664c061e6 100644 --- a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedChatService.java +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedChatService.java @@ -9,6 +9,7 @@ import com.openai.models.chat.completions.ChatCompletionCreateParams; import com.openai.services.blocking.ChatService; import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.instrumentation.api.incubator.semconv.genai.CaptureMessageOptions; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import java.lang.reflect.Method; @@ -17,17 +18,17 @@ final class InstrumentedChatService private final Instrumenter instrumenter; private final Logger eventLogger; - private final boolean captureMessageContent; + private final CaptureMessageOptions captureMessageOptions; InstrumentedChatService( ChatService delegate, Instrumenter instrumenter, Logger eventLogger, - boolean captureMessageContent) { + CaptureMessageOptions captureMessageOptions) { super(delegate); this.instrumenter = instrumenter; this.eventLogger = eventLogger; - this.captureMessageContent = captureMessageContent; + this.captureMessageOptions = captureMessageOptions; } @Override @@ -41,7 +42,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl Class[] parameterTypes = method.getParameterTypes(); if (methodName.equals("completions") && parameterTypes.length == 0) { return new InstrumentedChatCompletionService( - delegate.completions(), instrumenter, eventLogger, captureMessageContent) + delegate.completions(), instrumenter, eventLogger, captureMessageOptions) .createProxy(); } return super.invoke(proxy, method, args); diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedChatServiceAsync.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedChatServiceAsync.java index 4bbf888b50c3..ff6fff2024ad 100644 --- a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedChatServiceAsync.java +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedChatServiceAsync.java @@ -9,6 +9,7 @@ import com.openai.models.chat.completions.ChatCompletionCreateParams; import com.openai.services.async.ChatServiceAsync; import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.instrumentation.api.incubator.semconv.genai.CaptureMessageOptions; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import java.lang.reflect.Method; @@ -17,17 +18,17 @@ final class InstrumentedChatServiceAsync private final Instrumenter instrumenter; private final Logger eventLogger; - private final boolean captureMessageContent; + private final CaptureMessageOptions captureMessageOptions; InstrumentedChatServiceAsync( ChatServiceAsync delegate, Instrumenter instrumenter, Logger eventLogger, - boolean captureMessageContent) { + CaptureMessageOptions captureMessageOptions) { super(delegate); this.instrumenter = instrumenter; this.eventLogger = eventLogger; - this.captureMessageContent = captureMessageContent; + this.captureMessageOptions = captureMessageOptions; } @Override @@ -41,7 +42,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl Class[] parameterTypes = method.getParameterTypes(); if (methodName.equals("completions") && parameterTypes.length == 0) { return new InstrumentedChatCompletionServiceAsync( - delegate.completions(), instrumenter, eventLogger, captureMessageContent) + delegate.completions(), instrumenter, eventLogger, captureMessageOptions) .createProxy(); } return super.invoke(proxy, method, args); diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedOpenAiClient.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedOpenAiClient.java index 4fd816b5b7cb..bdc449ec7f9c 100644 --- a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedOpenAiClient.java +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedOpenAiClient.java @@ -11,6 +11,7 @@ import com.openai.models.embeddings.CreateEmbeddingResponse; import com.openai.models.embeddings.EmbeddingCreateParams; import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.instrumentation.api.incubator.semconv.genai.CaptureMessageOptions; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import java.lang.reflect.Method; @@ -20,19 +21,19 @@ final class InstrumentedOpenAiClient private final Instrumenter chatInstrumenter; private final Instrumenter embeddingInstrumenter; private final Logger eventLogger; - private final boolean captureMessageContent; + private final CaptureMessageOptions captureMessageOptions; InstrumentedOpenAiClient( OpenAIClient delegate, Instrumenter chatInstrumenter, Instrumenter embeddingInstrumenter, Logger eventLogger, - boolean captureMessageContent) { + CaptureMessageOptions captureMessageOptions) { super(delegate); this.chatInstrumenter = chatInstrumenter; this.embeddingInstrumenter = embeddingInstrumenter; this.eventLogger = eventLogger; - this.captureMessageContent = captureMessageContent; + this.captureMessageOptions = captureMessageOptions; } @Override @@ -46,7 +47,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl Class[] parameterTypes = method.getParameterTypes(); if (methodName.equals("chat") && parameterTypes.length == 0) { return new InstrumentedChatService( - delegate.chat(), chatInstrumenter, eventLogger, captureMessageContent) + delegate.chat(), chatInstrumenter, eventLogger, captureMessageOptions) .createProxy(); } if (methodName.equals("embeddings") && parameterTypes.length == 0) { @@ -59,7 +60,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl chatInstrumenter, embeddingInstrumenter, eventLogger, - captureMessageContent) + captureMessageOptions) .createProxy(); } return super.invoke(proxy, method, args); diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedOpenAiClientAsync.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedOpenAiClientAsync.java index bace0b179a1d..eb7f568fcd0a 100644 --- a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedOpenAiClientAsync.java +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedOpenAiClientAsync.java @@ -11,6 +11,7 @@ import com.openai.models.embeddings.CreateEmbeddingResponse; import com.openai.models.embeddings.EmbeddingCreateParams; import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.instrumentation.api.incubator.semconv.genai.CaptureMessageOptions; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import java.lang.reflect.Method; @@ -20,19 +21,19 @@ final class InstrumentedOpenAiClientAsync private final Instrumenter chatInstrumenter; private final Instrumenter embeddingInstrumenter; private final Logger eventLogger; - private final boolean captureMessageContent; + private final CaptureMessageOptions captureMessageOptions; InstrumentedOpenAiClientAsync( OpenAIClientAsync delegate, Instrumenter chatInstrumenter, Instrumenter embeddingInstrumenter, Logger eventLogger, - boolean captureMessageContent) { + CaptureMessageOptions captureMessageOptions) { super(delegate); this.chatInstrumenter = chatInstrumenter; this.embeddingInstrumenter = embeddingInstrumenter; this.eventLogger = eventLogger; - this.captureMessageContent = captureMessageContent; + this.captureMessageOptions = captureMessageOptions; } @Override @@ -46,7 +47,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl Class[] parameterTypes = method.getParameterTypes(); if (methodName.equals("chat") && parameterTypes.length == 0) { return new InstrumentedChatServiceAsync( - delegate.chat(), chatInstrumenter, eventLogger, captureMessageContent) + delegate.chat(), chatInstrumenter, eventLogger, captureMessageOptions) .createProxy(); } if (methodName.equals("embeddings") && parameterTypes.length == 0) { @@ -59,7 +60,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl chatInstrumenter, embeddingInstrumenter, eventLogger, - captureMessageContent) + captureMessageOptions) .createProxy(); } return super.invoke(proxy, method, args); diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/OpenAITelemetry.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/OpenAITelemetry.java index f6528eec741f..e2caeba0125e 100644 --- a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/OpenAITelemetry.java +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/OpenAITelemetry.java @@ -13,6 +13,7 @@ import com.openai.models.embeddings.EmbeddingCreateParams; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.instrumentation.api.incubator.semconv.genai.CaptureMessageOptions; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; /** Entrypoint for instrumenting OpenAI clients. */ @@ -35,30 +36,30 @@ public static OpenAITelemetryBuilder builder(OpenTelemetry openTelemetry) { private final Logger eventLogger; - private final boolean captureMessageContent; + private final CaptureMessageOptions captureMessageOptions; OpenAITelemetry( Instrumenter chatInstrumenter, Instrumenter embeddingsInstrumenter, Logger eventLogger, - boolean captureMessageContent) { + CaptureMessageOptions captureMessageOptions) { this.chatInstrumenter = chatInstrumenter; this.embeddingsInstrumenter = embeddingsInstrumenter; this.eventLogger = eventLogger; - this.captureMessageContent = captureMessageContent; + this.captureMessageOptions = captureMessageOptions; } /** Wraps the provided OpenAIClient, enabling telemetry for it. */ public OpenAIClient wrap(OpenAIClient client) { return new InstrumentedOpenAiClient( - client, chatInstrumenter, embeddingsInstrumenter, eventLogger, captureMessageContent) + client, chatInstrumenter, embeddingsInstrumenter, eventLogger, captureMessageOptions) .createProxy(); } /** Wraps the provided OpenAIClientAsync, enabling telemetry for it. */ public OpenAIClientAsync wrap(OpenAIClientAsync client) { return new InstrumentedOpenAiClientAsync( - client, chatInstrumenter, embeddingsInstrumenter, eventLogger, captureMessageContent) + client, chatInstrumenter, embeddingsInstrumenter, eventLogger, captureMessageOptions) .createProxy(); } } diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/OpenAITelemetryBuilder.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/OpenAITelemetryBuilder.java index 5f36c76a5798..10b65a57754b 100644 --- a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/OpenAITelemetryBuilder.java +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/OpenAITelemetryBuilder.java @@ -12,6 +12,7 @@ import com.openai.models.embeddings.EmbeddingCreateParams; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.instrumentation.api.incubator.semconv.genai.CaptureMessageOptions; import io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiClientMetrics; import io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiSpanNameExtractor; @@ -27,6 +28,10 @@ public final class OpenAITelemetryBuilder { private boolean captureMessageContent; + // TODO(cirilla-zmh): Java Instrumentation is still not support structural attributes for + // 'gen_ai.input.messages'. Implement this once it's supported. + private boolean emitExperimentalConventions; + OpenAITelemetryBuilder(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; } @@ -43,6 +48,14 @@ public OpenAITelemetryBuilder setCaptureMessageContent(boolean captureMessageCon return this; } + /** Sets whether emitted the latest experimental version of GenAI conventions. */ + @CanIgnoreReturnValue + public OpenAITelemetryBuilder setEmitExperimentalConventions( + boolean emitExperimentalConventions) { + this.emitExperimentalConventions = emitExperimentalConventions; + return this; + } + /** * Returns a new {@link OpenAITelemetry} with the settings of this {@link OpenAITelemetryBuilder}. */ @@ -68,6 +81,9 @@ public OpenAITelemetry build() { Logger eventLogger = openTelemetry.getLogsBridge().get(INSTRUMENTATION_NAME); return new OpenAITelemetry( - chatInstrumenter, embeddingsInstrumenter, eventLogger, captureMessageContent); + chatInstrumenter, + embeddingsInstrumenter, + eventLogger, + CaptureMessageOptions.create(captureMessageContent, emitExperimentalConventions)); } } diff --git a/instrumentation/openai/openai-java-1.1/library/src/test/java/io/opentelemetry/instrumentation/openai/v1_1/ChatTest.java b/instrumentation/openai/openai-java-1.1/library/src/test/java/io/opentelemetry/instrumentation/openai/v1_1/ChatTest.java index d312be5fa996..2886f1bb2024 100644 --- a/instrumentation/openai/openai-java-1.1/library/src/test/java/io/opentelemetry/instrumentation/openai/v1_1/ChatTest.java +++ b/instrumentation/openai/openai-java-1.1/library/src/test/java/io/opentelemetry/instrumentation/openai/v1_1/ChatTest.java @@ -9,6 +9,7 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_OPERATION_NAME; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_PROVIDER_NAME; +import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_REQUEST_CHOICE_COUNT; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_REQUEST_MODEL; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_RESPONSE_FINISH_REASONS; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_RESPONSE_ID; @@ -215,6 +216,7 @@ void multipleChoicesNoCaptureContent() { equalTo(GEN_AI_REQUEST_MODEL, TEST_CHAT_MODEL), satisfies(GEN_AI_RESPONSE_ID, id -> id.startsWith("chatcmpl-")), equalTo(GEN_AI_RESPONSE_MODEL, TEST_CHAT_RESPONSE_MODEL), + equalTo(GEN_AI_REQUEST_CHOICE_COUNT, 2L), satisfies( GEN_AI_RESPONSE_FINISH_REASONS, reasons -> reasons.containsExactly("stop", "stop")), @@ -711,6 +713,7 @@ void streamMultipleChoicesNoCaptureContent() { equalTo(GEN_AI_REQUEST_MODEL, TEST_CHAT_MODEL), satisfies(GEN_AI_RESPONSE_ID, id -> id.startsWith("chatcmpl-")), equalTo(GEN_AI_RESPONSE_MODEL, TEST_CHAT_RESPONSE_MODEL), + equalTo(GEN_AI_REQUEST_CHOICE_COUNT, 2L), satisfies( GEN_AI_RESPONSE_FINISH_REASONS, reasons -> reasons.containsExactly("stop", "stop")))))); diff --git a/instrumentation/openai/openai-java-1.1/metadata.yaml b/instrumentation/openai/openai-java-1.1/metadata.yaml index 7aaf7d895a2b..8bce4d69a433 100644 --- a/instrumentation/openai/openai-java-1.1/metadata.yaml +++ b/instrumentation/openai/openai-java-1.1/metadata.yaml @@ -8,3 +8,10 @@ configurations: Enables including the full content of user and assistant messages in emitted log events. Note that full content can have data privacy and size concerns, and care should be taken when enabling this. + - name: otel.semconv-stability.opt-in + type: string + default: "" + description: > + A comma-separated list of category-specific values. Once the list include + `gen_ai_latest_experimental`, the instrumentation will emit the latest experimental version + of GenAI conventions and do not emit the old one (v1.36.0 or prior). diff --git a/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractChatTest.java b/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractChatTest.java index b8ee95c14b85..ea084ee4e6b4 100644 --- a/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractChatTest.java +++ b/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractChatTest.java @@ -8,7 +8,9 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_OPERATION_NAME; +import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_OUTPUT_TYPE; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_PROVIDER_NAME; +import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_REQUEST_CHOICE_COUNT; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_REQUEST_FREQUENCY_PENALTY; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_REQUEST_MAX_TOKENS; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_REQUEST_MODEL; @@ -24,6 +26,7 @@ import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_USAGE_INPUT_TOKENS; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_USAGE_OUTPUT_TOKENS; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiOperationNameIncubatingValues.CHAT; +import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiOutputTypeIncubatingValues.TEXT; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiProviderNameIncubatingValues.OPENAI; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiTokenTypeIncubatingValues.INPUT; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiTokenTypeIncubatingValues.OUTPUT; @@ -349,6 +352,7 @@ void allTheClientOptions() { equalTo(GEN_AI_REQUEST_TOP_P, 1.0), satisfies(GEN_AI_RESPONSE_ID, id -> id.startsWith("chatcmpl-")), equalTo(GEN_AI_RESPONSE_MODEL, TEST_CHAT_RESPONSE_MODEL), + equalTo(GEN_AI_OUTPUT_TYPE, TEXT), satisfies( GEN_AI_RESPONSE_FINISH_REASONS, reasons -> reasons.containsExactly("stop")), @@ -450,6 +454,7 @@ void multipleChoices() { equalTo(GEN_AI_REQUEST_MODEL, TEST_CHAT_MODEL), satisfies(GEN_AI_RESPONSE_ID, id -> id.startsWith("chatcmpl-")), equalTo(GEN_AI_RESPONSE_MODEL, TEST_CHAT_RESPONSE_MODEL), + equalTo(GEN_AI_REQUEST_CHOICE_COUNT, 2L), satisfies( GEN_AI_RESPONSE_FINISH_REASONS, reasons -> reasons.containsExactly("stop", "stop")), @@ -1181,6 +1186,7 @@ void streamMultipleChoices() { equalTo(GEN_AI_REQUEST_MODEL, TEST_CHAT_MODEL), satisfies(GEN_AI_RESPONSE_ID, id -> id.startsWith("chatcmpl-")), equalTo(GEN_AI_RESPONSE_MODEL, TEST_CHAT_RESPONSE_MODEL), + equalTo(GEN_AI_REQUEST_CHOICE_COUNT, 2L), satisfies( GEN_AI_RESPONSE_FINISH_REASONS, reasons -> reasons.containsExactly("stop", "stop"))))));