From 085fa4d86fe7bf146a351b5c714477371311c0b9 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Thu, 24 Jul 2025 17:01:52 +0900 Subject: [PATCH 01/11] Instrument openai async client --- .../OpenAiClientAsyncInstrumentation.java | 40 +++++ .../v1_1/OpenAiInstrumentationModule.java | 4 +- .../instrumentation/openai/v1_1/ChatTest.java | 6 + .../v1_1/ChatCompletionEventsHelper.java | 22 ++- .../openai/v1_1/CompletableFutureWrapper.java | 30 ++++ .../InstrumentedChatCompletionService.java | 34 ++-- ...nstrumentedChatCompletionServiceAsync.java | 147 ++++++++++++++++ .../v1_1/InstrumentedChatServiceAsync.java | 49 ++++++ .../openai/v1_1/InstrumentedOpenAiClient.java | 5 + .../v1_1/InstrumentedOpenAiClientAsync.java | 54 ++++++ .../openai/v1_1/OpenAITelemetry.java | 8 + .../openai/v1_1/StreamListener.java | 112 ++++++++++++ .../v1_1/TracingAsyncStreamedResponse.java | 70 ++++++++ .../openai/v1_1/TracingStreamedResponse.java | 107 +----------- .../instrumentation/openai/v1_1/ChatTest.java | 80 ++++----- .../openai/v1_1/AbstractChatTest.java | 162 ++++++++++++++---- 16 files changed, 719 insertions(+), 211 deletions(-) create mode 100644 instrumentation/openai/openai-java-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/openai/v1_1/OpenAiClientAsyncInstrumentation.java create mode 100644 instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/CompletableFutureWrapper.java create mode 100644 instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedChatCompletionServiceAsync.java create mode 100644 instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedChatServiceAsync.java create mode 100644 instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedOpenAiClientAsync.java create mode 100644 instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/StreamListener.java create mode 100644 instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/TracingAsyncStreamedResponse.java diff --git a/instrumentation/openai/openai-java-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/openai/v1_1/OpenAiClientAsyncInstrumentation.java b/instrumentation/openai/openai-java-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/openai/v1_1/OpenAiClientAsyncInstrumentation.java new file mode 100644 index 000000000000..b37eef3c54e9 --- /dev/null +++ b/instrumentation/openai/openai-java-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/openai/v1_1/OpenAiClientAsyncInstrumentation.java @@ -0,0 +1,40 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.openai.v1_1; + +import static io.opentelemetry.javaagent.instrumentation.openai.v1_1.OpenAiSingletons.TELEMETRY; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; + +import com.openai.client.OpenAIClientAsync; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class OpenAiClientAsyncInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("com.openai.client.okhttp.OpenAIOkHttpClientAsync$Builder"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + named("build").and(returns(named("com.openai.client.OpenAIClientAsync"))), + OpenAiClientAsyncInstrumentation.class.getName() + "$BuildAdvice"); + } + + @SuppressWarnings("unused") + public static class BuildAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + @Advice.AssignReturned.ToReturned + public static OpenAIClientAsync onExit(@Advice.Return OpenAIClientAsync client) { + return TELEMETRY.wrap(client); + } + } +} diff --git a/instrumentation/openai/openai-java-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/openai/v1_1/OpenAiInstrumentationModule.java b/instrumentation/openai/openai-java-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/openai/v1_1/OpenAiInstrumentationModule.java index dc1d61d9c4f7..c2af04e5e730 100644 --- a/instrumentation/openai/openai-java-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/openai/v1_1/OpenAiInstrumentationModule.java +++ b/instrumentation/openai/openai-java-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/openai/v1_1/OpenAiInstrumentationModule.java @@ -6,7 +6,7 @@ package io.opentelemetry.javaagent.instrumentation.openai.v1_1; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; -import static java.util.Collections.singletonList; +import static java.util.Arrays.asList; import com.google.auto.service.AutoService; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; @@ -27,6 +27,6 @@ public ElementMatcher.Junction classLoaderMatcher() { @Override public List typeInstrumentations() { - return singletonList(new OpenAiClientInstrumentation()); + return asList(new OpenAiClientInstrumentation(), new OpenAiClientAsyncInstrumentation()); } } diff --git a/instrumentation/openai/openai-java-1.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/openai/v1_1/ChatTest.java b/instrumentation/openai/openai-java-1.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/openai/v1_1/ChatTest.java index e66921251783..adf82527a999 100644 --- a/instrumentation/openai/openai-java-1.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/openai/v1_1/ChatTest.java +++ b/instrumentation/openai/openai-java-1.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/openai/v1_1/ChatTest.java @@ -6,6 +6,7 @@ package io.opentelemetry.javaagent.instrumentation.openai.v1_1; import com.openai.client.OpenAIClient; +import com.openai.client.OpenAIClientAsync; import io.opentelemetry.instrumentation.openai.v1_1.AbstractChatTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; @@ -31,6 +32,11 @@ protected OpenAIClient wrap(OpenAIClient client) { return client; } + @Override + protected OpenAIClientAsync wrap(OpenAIClientAsync client) { + return client; + } + @Override protected final List> maybeWithTransportSpan( Consumer span) { 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 454fb546a3fe..0703c2cc4e0c 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 @@ -29,14 +29,16 @@ import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; -import javax.annotation.Nullable; final class ChatCompletionEventsHelper { private static final AttributeKey EVENT_NAME = stringKey("event.name"); public static void emitPromptLogEvents( - Logger eventLogger, ChatCompletionCreateParams request, boolean captureMessageContent) { + Context ctx, + Logger eventLogger, + ChatCompletionCreateParams request, + boolean captureMessageContent) { for (ChatCompletionMessageParam msg : request.messages()) { String eventType; Map> body = new HashMap<>(); @@ -84,7 +86,7 @@ public static void emitPromptLogEvents( } else { continue; } - newEvent(eventLogger, eventType).setBody(Value.of(body)).emit(); + newEvent(eventLogger, eventType).setContext(ctx).setBody(Value.of(body)).emit(); } } @@ -160,7 +162,7 @@ private static String joinContentParts(List conte } public static void emitCompletionLogEvents( - Logger eventLogger, ChatCompletion completion, boolean captureMessageContent) { + Context ctx, Logger eventLogger, ChatCompletion completion, boolean captureMessageContent) { for (ChatCompletion.Choice choice : completion.choices()) { ChatCompletionMessage choiceMsg = choice.message(); Map> message = new HashMap<>(); @@ -179,25 +181,21 @@ public static void emitCompletionLogEvents( .collect(Collectors.toList()))); }); emitCompletionLogEvent( - eventLogger, choice.index(), choice.finishReason().toString(), Value.of(message), null); + ctx, eventLogger, choice.index(), choice.finishReason().toString(), Value.of(message)); } } public static void emitCompletionLogEvent( + Context ctx, Logger eventLogger, long index, String finishReason, - Value eventMessageObject, - @Nullable Context contextOverride) { + Value eventMessageObject) { Map> body = new HashMap<>(); body.put("finish_reason", Value.of(finishReason)); body.put("index", Value.of(index)); body.put("message", eventMessageObject); - LogRecordBuilder builder = newEvent(eventLogger, "gen_ai.choice").setBody(Value.of(body)); - if (contextOverride != null) { - builder.setContext(contextOverride); - } - builder.emit(); + newEvent(eventLogger, "gen_ai.choice").setContext(ctx).setBody(Value.of(body)).emit(); } private static LogRecordBuilder newEvent(Logger eventLogger, String name) { diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/CompletableFutureWrapper.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/CompletableFutureWrapper.java new file mode 100644 index 000000000000..96b1b565daa7 --- /dev/null +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/CompletableFutureWrapper.java @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.openai.v1_1; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import java.util.concurrent.CompletableFuture; + +final class CompletableFutureWrapper { + private CompletableFutureWrapper() {} + + public static CompletableFuture wrap(CompletableFuture future, Context context) { + CompletableFuture result = new CompletableFuture<>(); + future.whenComplete( + (T value, Throwable throwable) -> { + try (Scope ignored = context.makeCurrent()) { + if (throwable != null) { + result.completeExceptionally(throwable); + } else { + result.complete(value); + } + } + }); + + return result; + } +} 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 a0d7b88af421..ec171203631b 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 @@ -75,13 +75,13 @@ private ChatCompletion create( ChatCompletionCreateParams chatCompletionCreateParams, RequestOptions requestOptions) { Context parentCtx = Context.current(); if (!instrumenter.shouldStart(parentCtx, chatCompletionCreateParams)) { - return createWithLogs(chatCompletionCreateParams, requestOptions); + return createWithLogs(parentCtx, chatCompletionCreateParams, requestOptions); } Context ctx = instrumenter.start(parentCtx, chatCompletionCreateParams); ChatCompletion completion; try (Scope ignored = ctx.makeCurrent()) { - completion = createWithLogs(chatCompletionCreateParams, requestOptions); + completion = createWithLogs(ctx, chatCompletionCreateParams, requestOptions); } catch (Throwable t) { instrumenter.end(ctx, chatCompletionCreateParams, null, t); throw t; @@ -92,11 +92,14 @@ private ChatCompletion create( } private ChatCompletion createWithLogs( - ChatCompletionCreateParams chatCompletionCreateParams, RequestOptions requestOptions) { + Context ctx, + ChatCompletionCreateParams chatCompletionCreateParams, + RequestOptions requestOptions) { ChatCompletionEventsHelper.emitPromptLogEvents( - eventLogger, chatCompletionCreateParams, captureMessageContent); + ctx, eventLogger, chatCompletionCreateParams, captureMessageContent); ChatCompletion result = delegate.create(chatCompletionCreateParams, requestOptions); - ChatCompletionEventsHelper.emitCompletionLogEvents(eventLogger, result, captureMessageContent); + ChatCompletionEventsHelper.emitCompletionLogEvents( + ctx, eventLogger, result, captureMessageContent); return result; } @@ -104,12 +107,12 @@ private StreamResponse createStreaming( ChatCompletionCreateParams chatCompletionCreateParams, RequestOptions requestOptions) { Context parentCtx = Context.current(); if (!instrumenter.shouldStart(parentCtx, chatCompletionCreateParams)) { - return createStreamingWithLogs(chatCompletionCreateParams, requestOptions, parentCtx, false); + return createStreamingWithLogs(parentCtx, chatCompletionCreateParams, requestOptions, false); } Context ctx = instrumenter.start(parentCtx, chatCompletionCreateParams); try (Scope ignored = ctx.makeCurrent()) { - return createStreamingWithLogs(chatCompletionCreateParams, requestOptions, ctx, true); + return createStreamingWithLogs(ctx, chatCompletionCreateParams, requestOptions, true); } catch (Throwable t) { instrumenter.end(ctx, chatCompletionCreateParams, null, t); throw t; @@ -117,21 +120,22 @@ private StreamResponse createStreaming( } private StreamResponse createStreamingWithLogs( + Context ctx, ChatCompletionCreateParams chatCompletionCreateParams, RequestOptions requestOptions, - Context parentCtx, boolean newSpan) { ChatCompletionEventsHelper.emitPromptLogEvents( - eventLogger, chatCompletionCreateParams, captureMessageContent); + ctx, eventLogger, chatCompletionCreateParams, captureMessageContent); StreamResponse result = delegate.createStreaming(chatCompletionCreateParams, requestOptions); return new TracingStreamedResponse( result, - parentCtx, - chatCompletionCreateParams, - instrumenter, - eventLogger, - captureMessageContent, - newSpan); + new StreamListener( + ctx, + chatCompletionCreateParams, + instrumenter, + eventLogger, + 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 new file mode 100644 index 000000000000..f8e8d88903a7 --- /dev/null +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedChatCompletionServiceAsync.java @@ -0,0 +1,147 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.openai.v1_1; + +import com.openai.core.RequestOptions; +import com.openai.core.http.AsyncStreamResponse; +import com.openai.models.chat.completions.ChatCompletion; +import com.openai.models.chat.completions.ChatCompletionChunk; +import com.openai.models.chat.completions.ChatCompletionCreateParams; +import com.openai.services.async.chat.ChatCompletionServiceAsync; +import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import java.lang.reflect.Method; +import java.util.concurrent.CompletableFuture; + +final class InstrumentedChatCompletionServiceAsync + extends DelegatingInvocationHandler< + ChatCompletionServiceAsync, InstrumentedChatCompletionServiceAsync> { + + private final Instrumenter instrumenter; + private final Logger eventLogger; + private final boolean captureMessageContent; + + InstrumentedChatCompletionServiceAsync( + ChatCompletionServiceAsync delegate, + Instrumenter instrumenter, + Logger eventLogger, + boolean captureMessageContent) { + super(delegate); + this.instrumenter = instrumenter; + this.eventLogger = eventLogger; + this.captureMessageContent = captureMessageContent; + } + + @Override + protected Class getProxyType() { + return ChatCompletionServiceAsync.class; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + String methodName = method.getName(); + Class[] parameterTypes = method.getParameterTypes(); + + switch (methodName) { + case "create": + if (parameterTypes.length >= 1 && parameterTypes[0] == ChatCompletionCreateParams.class) { + if (parameterTypes.length == 1) { + return create((ChatCompletionCreateParams) args[0], RequestOptions.none()); + } else if (parameterTypes.length == 2 && parameterTypes[1] == RequestOptions.class) { + return create((ChatCompletionCreateParams) args[0], (RequestOptions) args[1]); + } + } + break; + case "createStreaming": + if (parameterTypes.length >= 1 && parameterTypes[0] == ChatCompletionCreateParams.class) { + if (parameterTypes.length == 1) { + return createStreaming((ChatCompletionCreateParams) args[0], RequestOptions.none()); + } else if (parameterTypes.length == 2 && parameterTypes[1] == RequestOptions.class) { + return createStreaming((ChatCompletionCreateParams) args[0], (RequestOptions) args[1]); + } + } + break; + default: + // fallthrough + } + + return super.invoke(proxy, method, args); + } + + private CompletableFuture create( + ChatCompletionCreateParams chatCompletionCreateParams, RequestOptions requestOptions) { + Context parentCtx = Context.current(); + if (!instrumenter.shouldStart(parentCtx, chatCompletionCreateParams)) { + return createWithLogs(parentCtx, chatCompletionCreateParams, requestOptions); + } + + Context ctx = instrumenter.start(parentCtx, chatCompletionCreateParams); + CompletableFuture future; + try (Scope ignored = ctx.makeCurrent()) { + future = createWithLogs(ctx, chatCompletionCreateParams, requestOptions); + } catch (Throwable t) { + instrumenter.end(ctx, chatCompletionCreateParams, null, t); + throw t; + } + + future = + future.whenComplete((res, t) -> instrumenter.end(ctx, chatCompletionCreateParams, res, t)); + return CompletableFutureWrapper.wrap(future, ctx); + } + + private CompletableFuture createWithLogs( + Context ctx, + ChatCompletionCreateParams chatCompletionCreateParams, + RequestOptions requestOptions) { + ChatCompletionEventsHelper.emitPromptLogEvents( + ctx, eventLogger, chatCompletionCreateParams, captureMessageContent); + CompletableFuture future = + delegate.create(chatCompletionCreateParams, requestOptions); + future.thenAccept( + r -> + ChatCompletionEventsHelper.emitCompletionLogEvents( + ctx, eventLogger, r, captureMessageContent)); + return future; + } + + private AsyncStreamResponse createStreaming( + ChatCompletionCreateParams chatCompletionCreateParams, RequestOptions requestOptions) { + Context parentCtx = Context.current(); + if (!instrumenter.shouldStart(parentCtx, chatCompletionCreateParams)) { + return createStreamingWithLogs(parentCtx, chatCompletionCreateParams, requestOptions, false); + } + + Context ctx = instrumenter.start(parentCtx, chatCompletionCreateParams); + try (Scope ignored = ctx.makeCurrent()) { + return createStreamingWithLogs(ctx, chatCompletionCreateParams, requestOptions, true); + } catch (Throwable t) { + instrumenter.end(ctx, chatCompletionCreateParams, null, t); + throw t; + } + } + + private AsyncStreamResponse createStreamingWithLogs( + Context ctx, + ChatCompletionCreateParams chatCompletionCreateParams, + RequestOptions requestOptions, + boolean newSpan) { + ChatCompletionEventsHelper.emitPromptLogEvents( + ctx, eventLogger, chatCompletionCreateParams, captureMessageContent); + AsyncStreamResponse result = + delegate.createStreaming(chatCompletionCreateParams, requestOptions); + return new TracingAsyncStreamedResponse( + result, + new StreamListener( + ctx, + chatCompletionCreateParams, + instrumenter, + eventLogger, + captureMessageContent, + newSpan)); + } +} 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 new file mode 100644 index 000000000000..8dc8bbc09ed4 --- /dev/null +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedChatServiceAsync.java @@ -0,0 +1,49 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.openai.v1_1; + +import com.openai.models.chat.completions.ChatCompletion; +import com.openai.models.chat.completions.ChatCompletionCreateParams; +import com.openai.services.async.ChatServiceAsync; +import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import java.lang.reflect.Method; + +final class InstrumentedChatServiceAsync + extends DelegatingInvocationHandler { + + private final Instrumenter instrumenter; + private final Logger eventLogger; + private final boolean captureMessageContent; + + public InstrumentedChatServiceAsync( + ChatServiceAsync delegate, + Instrumenter instrumenter, + Logger eventLogger, + boolean captureMessageContent) { + super(delegate); + this.instrumenter = instrumenter; + this.eventLogger = eventLogger; + this.captureMessageContent = captureMessageContent; + } + + @Override + protected Class getProxyType() { + return ChatServiceAsync.class; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + String methodName = method.getName(); + Class[] parameterTypes = method.getParameterTypes(); + if (methodName.equals("completions") && parameterTypes.length == 0) { + return new InstrumentedChatCompletionServiceAsync( + delegate.completions(), instrumenter, eventLogger, captureMessageContent) + .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 cc118a85cf33..754c5e8ba7f2 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 @@ -44,6 +44,11 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl delegate.chat(), chatInstrumenter, eventLogger, captureMessageContent) .createProxy(); } + if (methodName.equals("async") && parameterTypes.length == 0) { + return new InstrumentedOpenAiClientAsync( + delegate.async(), chatInstrumenter, eventLogger, captureMessageContent) + .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 new file mode 100644 index 000000000000..bc7635590c79 --- /dev/null +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedOpenAiClientAsync.java @@ -0,0 +1,54 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.openai.v1_1; + +import com.openai.client.OpenAIClientAsync; +import com.openai.models.chat.completions.ChatCompletion; +import com.openai.models.chat.completions.ChatCompletionCreateParams; +import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import java.lang.reflect.Method; + +final class InstrumentedOpenAiClientAsync + extends DelegatingInvocationHandler { + + private final Instrumenter chatInstrumenter; + private final Logger eventLogger; + private final boolean captureMessageContent; + + InstrumentedOpenAiClientAsync( + OpenAIClientAsync delegate, + Instrumenter chatInstrumenter, + Logger eventLogger, + boolean captureMessageContent) { + super(delegate); + this.chatInstrumenter = chatInstrumenter; + this.eventLogger = eventLogger; + this.captureMessageContent = captureMessageContent; + } + + @Override + protected Class getProxyType() { + return OpenAIClientAsync.class; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + String methodName = method.getName(); + Class[] parameterTypes = method.getParameterTypes(); + if (methodName.equals("chat") && parameterTypes.length == 0) { + return new InstrumentedChatServiceAsync( + delegate.chat(), chatInstrumenter, eventLogger, captureMessageContent) + .createProxy(); + } + if (methodName.equals("sync") && parameterTypes.length == 0) { + return new InstrumentedOpenAiClient( + delegate.sync(), chatInstrumenter, eventLogger, captureMessageContent) + .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 ca9f26d1f2ff..7a44eec4a581 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 @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.openai.v1_1; import com.openai.client.OpenAIClient; +import com.openai.client.OpenAIClientAsync; import com.openai.models.chat.completions.ChatCompletion; import com.openai.models.chat.completions.ChatCompletionCreateParams; import io.opentelemetry.api.OpenTelemetry; @@ -48,4 +49,11 @@ public OpenAIClient wrap(OpenAIClient client) { client, chatInstrumenter, eventLogger, captureMessageContent) .createProxy(); } + + /** Wraps the provided OpenAIClientAsync, enabling telemetry for it. */ + public OpenAIClientAsync wrap(OpenAIClientAsync client) { + return new InstrumentedOpenAiClientAsync( + client, chatInstrumenter, eventLogger, captureMessageContent) + .createProxy(); + } } diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/StreamListener.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/StreamListener.java new file mode 100644 index 000000000000..533ca3214baf --- /dev/null +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/StreamListener.java @@ -0,0 +1,112 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.openai.v1_1; + +import com.openai.models.chat.completions.ChatCompletion; +import com.openai.models.chat.completions.ChatCompletionChunk; +import com.openai.models.chat.completions.ChatCompletionCreateParams; +import com.openai.models.completions.CompletionUsage; +import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; +import javax.annotation.Nullable; + +final class StreamListener { + + private final Context parentCtx; + private final ChatCompletionCreateParams request; + private final List choiceBuffers; + + private final Instrumenter instrumenter; + private final Logger eventLogger; + private final boolean captureMessageContent; + private final boolean newSpan; + private final AtomicBoolean hasEnded; + + @Nullable private CompletionUsage usage; + @Nullable private String model; + @Nullable private String responseId; + + StreamListener( + Context parentCtx, + ChatCompletionCreateParams request, + Instrumenter instrumenter, + Logger eventLogger, + boolean captureMessageContent, + boolean newSpan) { + this.parentCtx = parentCtx; + this.request = request; + this.instrumenter = instrumenter; + this.eventLogger = eventLogger; + this.captureMessageContent = captureMessageContent; + this.newSpan = newSpan; + choiceBuffers = new ArrayList<>(); + hasEnded = new AtomicBoolean(); + } + + void onChunk(ChatCompletionChunk chunk) { + model = chunk.model(); + responseId = chunk.id(); + chunk.usage().ifPresent(u -> usage = u); + + for (ChatCompletionChunk.Choice choice : chunk.choices()) { + while (choiceBuffers.size() <= choice.index()) { + choiceBuffers.add(null); + } + StreamedMessageBuffer buffer = choiceBuffers.get((int) choice.index()); + if (buffer == null) { + buffer = new StreamedMessageBuffer(choice.index(), captureMessageContent); + choiceBuffers.set((int) choice.index(), buffer); + } + buffer.append(choice.delta()); + if (choice.finishReason().isPresent()) { + buffer.finishReason = choice.finishReason().get().toString(); + + // message has ended, let's emit + ChatCompletionEventsHelper.emitCompletionLogEvent( + parentCtx, eventLogger, choice.index(), buffer.finishReason, buffer.toEventBody()); + } + } + } + + void endSpan(@Nullable Throwable error) { + // Use an atomic operation since close() type of methods are exposed to the user + // and can come from any thread. + if (!hasEnded.compareAndSet(false, true)) { + return; + } + + if (model == null || responseId == null) { + // Only happens if we got no chunks, so we have no response. + if (newSpan) { + instrumenter.end(parentCtx, request, null, error); + } + return; + } + + ChatCompletion.Builder result = + ChatCompletion.builder() + .created(0) + .model(model) + .id(responseId) + .choices( + choiceBuffers.stream() + .map(StreamedMessageBuffer::toChoice) + .collect(Collectors.toList())); + + if (usage != null) { + result.usage(usage); + } + + if (newSpan) { + instrumenter.end(parentCtx, request, result.build(), error); + } + } +} diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/TracingAsyncStreamedResponse.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/TracingAsyncStreamedResponse.java new file mode 100644 index 000000000000..2c3ca8ee3231 --- /dev/null +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/TracingAsyncStreamedResponse.java @@ -0,0 +1,70 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.openai.v1_1; + +import com.openai.core.http.AsyncStreamResponse; +import com.openai.models.chat.completions.ChatCompletionChunk; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +final class TracingAsyncStreamedResponse implements AsyncStreamResponse { + + private final AsyncStreamResponse delegate; + private final StreamListener listener; + + TracingAsyncStreamedResponse( + AsyncStreamResponse delegate, StreamListener listener) { + this.delegate = delegate; + this.listener = listener; + } + + @Override + public void close() { + listener.endSpan(null); + delegate.close(); + } + + @Override + public AsyncStreamResponse subscribe( + Handler handler) { + delegate.subscribe(new TracingHandler(handler)); + return this; + } + + @Override + public AsyncStreamResponse subscribe( + Handler handler, Executor executor) { + delegate.subscribe(new TracingHandler(handler), executor); + return this; + } + + @Override + public CompletableFuture onCompleteFuture() { + return delegate.onCompleteFuture(); + } + + private class TracingHandler implements Handler { + + private final Handler delegate; + + private TracingHandler(Handler delegate) { + this.delegate = delegate; + } + + @Override + public void onNext(ChatCompletionChunk chunk) { + listener.onChunk(chunk); + delegate.onNext(chunk); + } + + @Override + public void onComplete(Optional error) { + listener.endSpan(error.orElse(null)); + delegate.onComplete(error); + } + } +} diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/TracingStreamedResponse.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/TracingStreamedResponse.java index e5793c4ed840..5795bb2d1b3f 100644 --- a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/TracingStreamedResponse.java +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/TracingStreamedResponse.java @@ -5,21 +5,11 @@ package io.opentelemetry.instrumentation.openai.v1_1; -import com.openai.core.JsonField; import com.openai.core.http.StreamResponse; -import com.openai.models.chat.completions.ChatCompletion; import com.openai.models.chat.completions.ChatCompletionChunk; -import com.openai.models.chat.completions.ChatCompletionCreateParams; -import com.openai.models.completions.CompletionUsage; -import io.opentelemetry.api.logs.Logger; -import io.opentelemetry.context.Context; -import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import java.util.ArrayList; import java.util.Comparator; -import java.util.List; import java.util.Spliterator; import java.util.function.Consumer; -import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; import javax.annotation.Nullable; @@ -27,36 +17,11 @@ final class TracingStreamedResponse implements StreamResponse { private final StreamResponse delegate; - private final Context parentCtx; - private final ChatCompletionCreateParams request; - private final List choiceBuffers; - - private final Instrumenter instrumenter; - private final Logger eventLogger; - private final boolean captureMessageContent; - private final boolean newSpan; - - @Nullable private CompletionUsage usage; - @Nullable private String model; - @Nullable private String responseId; - private boolean hasEnded = false; - - TracingStreamedResponse( - StreamResponse delegate, - Context parentCtx, - ChatCompletionCreateParams request, - Instrumenter instrumenter, - Logger eventLogger, - boolean captureMessageContent, - boolean newSpan) { + private final StreamListener listener; + + TracingStreamedResponse(StreamResponse delegate, StreamListener listener) { this.delegate = delegate; - this.parentCtx = parentCtx; - this.request = request; - this.instrumenter = instrumenter; - this.eventLogger = eventLogger; - this.captureMessageContent = captureMessageContent; - this.newSpan = newSpan; - choiceBuffers = new ArrayList<>(); + this.listener = listener; } @Override @@ -66,42 +31,10 @@ public Stream stream() { @Override public void close() { - endSpan(); + listener.endSpan(null); delegate.close(); } - private synchronized void endSpan() { - if (hasEnded) { - return; - } - hasEnded = true; - - ChatCompletion.Builder result = - ChatCompletion.builder() - .created(0) - .choices( - choiceBuffers.stream() - .map(StreamedMessageBuffer::toChoice) - .collect(Collectors.toList())); - if (model != null) { - result.model(model); - } else { - result.model(JsonField.ofNullable(null)); - } - if (responseId != null) { - result.id(responseId); - } else { - result.id(JsonField.ofNullable(null)); - } - if (usage != null) { - result.usage(usage); - } - - if (newSpan) { - instrumenter.end(parentCtx, request, result.build(), null); - } - } - private class TracingSpliterator implements Spliterator { private final Spliterator delegateSpliterator; @@ -115,37 +48,11 @@ public boolean tryAdvance(Consumer action) { boolean chunkReceived = delegateSpliterator.tryAdvance( chunk -> { - model = chunk.model(); - responseId = chunk.id(); - chunk.usage().ifPresent(u -> usage = u); - - for (ChatCompletionChunk.Choice choice : chunk.choices()) { - while (choiceBuffers.size() <= choice.index()) { - choiceBuffers.add(null); - } - StreamedMessageBuffer buffer = choiceBuffers.get((int) choice.index()); - if (buffer == null) { - buffer = new StreamedMessageBuffer(choice.index(), captureMessageContent); - choiceBuffers.set((int) choice.index(), buffer); - } - buffer.append(choice.delta()); - if (choice.finishReason().isPresent()) { - buffer.finishReason = choice.finishReason().get().toString(); - - // message has ended, let's emit - ChatCompletionEventsHelper.emitCompletionLogEvent( - eventLogger, - choice.index(), - buffer.finishReason, - buffer.toEventBody(), - parentCtx); - } - } - + listener.onChunk(chunk); action.accept(chunk); }); if (!chunkReceived) { - endSpan(); + listener.endSpan(null); } return chunkReceived; } 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 5b8e78edb066..1937c11f75d7 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 @@ -26,7 +26,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.openai.client.OpenAIClient; -import com.openai.core.http.StreamResponse; +import com.openai.client.OpenAIClientAsync; import com.openai.models.chat.completions.ChatCompletion; import com.openai.models.chat.completions.ChatCompletionChunk; import com.openai.models.chat.completions.ChatCompletionCreateParams; @@ -72,6 +72,11 @@ protected OpenAIClient wrap(OpenAIClient client) { return telemetry.wrap(client); } + @Override + protected OpenAIClientAsync wrap(OpenAIClientAsync client) { + return telemetry.wrap(client); + } + @Override // OpenAI SDK does not expose OkHttp client in a way we can wrap. protected final List> maybeWithTransportSpan( @@ -79,18 +84,24 @@ protected final List> maybeWithTransportSpan( return singletonList(span); } + private OpenAIClient clientNoCaptureContent() { + return OpenAITelemetry.builder(testing.getOpenTelemetry()).build().wrap(getRawClient()); + } + + private OpenAIClientAsync clientAsyncNoCaptureContent() { + return OpenAITelemetry.builder(testing.getOpenTelemetry()).build().wrap(getRawClientAsync()); + } + @Test void basicNoCaptureContent() { - OpenAIClient client = - OpenAITelemetry.builder(testing.getOpenTelemetry()).build().wrap(getRawClient()); - ChatCompletionCreateParams params = ChatCompletionCreateParams.builder() .messages(singletonList(createUserMessage(TEST_CHAT_INPUT))) .model(TEST_CHAT_MODEL) .build(); - ChatCompletion response = client.chat().completions().create(params); + ChatCompletion response = + doCompletions(params, clientNoCaptureContent(), clientAsyncNoCaptureContent()); String content = "Atlantic Ocean"; assertThat(response.choices().get(0).message().content()).hasValue(content); @@ -179,9 +190,6 @@ void basicNoCaptureContent() { @Test void multipleChoicesNoCaptureContent() { - OpenAIClient client = - OpenAITelemetry.builder(testing.getOpenTelemetry()).build().wrap(getRawClient()); - ChatCompletionCreateParams params = ChatCompletionCreateParams.builder() .messages(Collections.singletonList(createUserMessage(TEST_CHAT_INPUT))) @@ -189,7 +197,8 @@ void multipleChoicesNoCaptureContent() { .n(2) .build(); - ChatCompletion response = client.chat().completions().create(params); + ChatCompletion response = + doCompletions(params, clientNoCaptureContent(), clientAsyncNoCaptureContent()); String content1 = "South Atlantic Ocean."; assertThat(response.choices().get(0).message().content()).hasValue(content1); String content2 = "Atlantic Ocean."; @@ -288,9 +297,6 @@ void multipleChoicesNoCaptureContent() { @Test void toolCallsNoCaptureContent() { - OpenAIClient client = - OpenAITelemetry.builder(testing.getOpenTelemetry()).build().wrap(getRawClient()); - List chatMessages = new ArrayList<>(); chatMessages.add(createSystemMessage("You are a helpful assistant providing weather updates.")); chatMessages.add(createUserMessage("What is the weather in New York City and London?")); @@ -302,7 +308,8 @@ void toolCallsNoCaptureContent() { .addTool(buildGetWeatherToolDefinition()) .build(); - ChatCompletion response = client.chat().completions().create(params); + ChatCompletion response = + doCompletions(params, clientNoCaptureContent(), clientAsyncNoCaptureContent()); assertThat(response.choices().get(0).message().content()).isEmpty(); @@ -441,14 +448,10 @@ void toolCallsNoCaptureContent() { chatMessages.add(createToolMessage("25 degrees and sunny", newYorkCallId)); chatMessages.add(createToolMessage("15 degrees and raining", londonCallId)); - client - .chat() - .completions() - .create( - ChatCompletionCreateParams.builder() - .messages(chatMessages) - .model(TEST_CHAT_MODEL) - .build()); + doCompletions( + ChatCompletionCreateParams.builder().messages(chatMessages).model(TEST_CHAT_MODEL).build(), + clientNoCaptureContent(), + clientAsyncNoCaptureContent()); getTesting() .waitAndAssertTraces( @@ -572,20 +575,14 @@ void toolCallsNoCaptureContent() { @Test void streamNoCaptureContent() { - OpenAIClient client = - OpenAITelemetry.builder(testing.getOpenTelemetry()).build().wrap(getRawClient()); - ChatCompletionCreateParams params = ChatCompletionCreateParams.builder() .messages(Collections.singletonList(createUserMessage(TEST_CHAT_INPUT))) .model(TEST_CHAT_MODEL) .build(); - List chunks; - try (StreamResponse result = - client.chat().completions().createStreaming(params)) { - chunks = result.stream().collect(Collectors.toList()); - } + List chunks = + doCompletionsStreaming(params, clientNoCaptureContent(), clientAsyncNoCaptureContent()); String fullMessage = chunks.stream() @@ -662,9 +659,6 @@ void streamNoCaptureContent() { @Test void streamMultipleChoicesNoCaptureContent() { - OpenAIClient client = - OpenAITelemetry.builder(testing.getOpenTelemetry()).build().wrap(getRawClient()); - ChatCompletionCreateParams params = ChatCompletionCreateParams.builder() .messages(Collections.singletonList(createUserMessage(TEST_CHAT_INPUT))) @@ -672,11 +666,8 @@ void streamMultipleChoicesNoCaptureContent() { .n(2) .build(); - List chunks; - try (StreamResponse result = - client.chat().completions().createStreaming(params)) { - chunks = result.stream().collect(Collectors.toList()); - } + List chunks = + doCompletionsStreaming(params, clientNoCaptureContent(), clientAsyncNoCaptureContent()); StringBuilder content1Builder = new StringBuilder(); StringBuilder content2Builder = new StringBuilder(); @@ -769,9 +760,6 @@ void streamMultipleChoicesNoCaptureContent() { @Test void streamToolCallsNoCaptureContent() { - OpenAIClient client = - OpenAITelemetry.builder(testing.getOpenTelemetry()).build().wrap(getRawClient()); - List chatMessages = new ArrayList<>(); chatMessages.add(createSystemMessage("You are a helpful assistant providing weather updates.")); chatMessages.add(createUserMessage("What is the weather in New York City and London?")); @@ -783,11 +771,8 @@ void streamToolCallsNoCaptureContent() { .addTool(buildGetWeatherToolDefinition()) .build(); - List chunks; - try (StreamResponse result = - client.chat().completions().createStreaming(params)) { - chunks = result.stream().collect(Collectors.toList()); - } + List chunks = + doCompletionsStreaming(params, clientNoCaptureContent(), clientAsyncNoCaptureContent()); List toolCalls = new ArrayList<>(); @@ -949,10 +934,7 @@ void streamToolCallsNoCaptureContent() { .addTool(buildGetWeatherToolDefinition()) .build(); - try (StreamResponse result = - client.chat().completions().createStreaming(params)) { - chunks = result.stream().collect(Collectors.toList()); - } + doCompletionsStreaming(params, clientNoCaptureContent(), clientAsyncNoCaptureContent()); getTesting() .waitAndAssertTraces( 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 8e4a27f26ca2..e2a6ad65dae9 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 @@ -33,9 +33,12 @@ import static org.assertj.core.api.Assertions.catchThrowable; import com.openai.client.OpenAIClient; +import com.openai.client.OpenAIClientAsync; import com.openai.client.okhttp.OpenAIOkHttpClient; +import com.openai.client.okhttp.OpenAIOkHttpClientAsync; import com.openai.core.JsonObject; import com.openai.core.JsonValue; +import com.openai.core.http.AsyncStreamResponse; import com.openai.core.http.StreamResponse; import com.openai.errors.OpenAIIoException; import com.openai.models.FunctionDefinition; @@ -67,12 +70,25 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.CompletionException; import java.util.function.Consumer; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.EnumSource; +@ParameterizedClass +@EnumSource(AbstractChatTest.TestType.class) public abstract class AbstractChatTest { + enum TestType { + SYNC, + SYNC_FROM_ASYNC, + ASYNC, + ASYNC_FROM_SYNC, + } + protected static final String INSTRUMENTATION_NAME = "io.opentelemetry.openai-java-1.1"; private static final String API_URL = "https://api.openai.com/v1"; @@ -90,7 +106,9 @@ public abstract class AbstractChatTest { protected abstract OpenAIClient wrap(OpenAIClient client); - protected OpenAIClient getRawClient() { + protected abstract OpenAIClientAsync wrap(OpenAIClientAsync client); + + protected final OpenAIClient getRawClient() { OpenAIOkHttpClient.Builder builder = OpenAIOkHttpClient.builder().baseUrl("http://localhost:" + recording.getPort()); if (recording.isRecording()) { @@ -101,13 +119,96 @@ protected OpenAIClient getRawClient() { return builder.build(); } - protected OpenAIClient getClient() { + protected final OpenAIClientAsync getRawClientAsync() { + OpenAIOkHttpClientAsync.Builder builder = + OpenAIOkHttpClientAsync.builder().baseUrl("http://localhost:" + recording.getPort()); + if (recording.isRecording()) { + builder.apiKey(System.getenv("OPENAI_API_KEY")); + } else { + builder.apiKey("unused"); + } + return builder.build(); + } + + protected final OpenAIClient getClient() { return wrap(getRawClient()); } + protected final OpenAIClientAsync getClientAsync() { + return wrap(getRawClientAsync()); + } + protected abstract List> maybeWithTransportSpan( Consumer span); + @Parameter TestType testType; + + protected final ChatCompletion doCompletions(ChatCompletionCreateParams params) { + return doCompletions(params, getClient(), getClientAsync()); + } + + protected final ChatCompletion doCompletions( + ChatCompletionCreateParams params, OpenAIClient client, OpenAIClientAsync clientAsync) { + switch (testType) { + case SYNC: + return client.chat().completions().create(params); + case SYNC_FROM_ASYNC: + return clientAsync.sync().chat().completions().create(params); + case ASYNC: + case ASYNC_FROM_SYNC: + OpenAIClientAsync cl = testType == TestType.ASYNC ? clientAsync : client.async(); + try { + return cl.chat().completions().create(params).join(); + } catch (CompletionException e) { + if (e.getCause() instanceof OpenAIIoException) { + throw ((OpenAIIoException) e.getCause()); + } + throw e; + } + } + throw new AssertionError(); + } + + protected final List doCompletionsStreaming( + ChatCompletionCreateParams params) { + return doCompletionsStreaming(params, getClient(), getClientAsync()); + } + + protected final List doCompletionsStreaming( + ChatCompletionCreateParams params, OpenAIClient client, OpenAIClientAsync clientAsync) { + switch (testType) { + case SYNC: + try (StreamResponse result = + client.chat().completions().createStreaming(params)) { + return result.stream().collect(Collectors.toList()); + } + case SYNC_FROM_ASYNC: + try (StreamResponse result = + clientAsync.sync().chat().completions().createStreaming(params)) { + return result.stream().collect(Collectors.toList()); + } + case ASYNC: + case ASYNC_FROM_SYNC: + { + OpenAIClientAsync cl = testType == TestType.ASYNC ? clientAsync : client.async(); + AsyncStreamResponse stream = + cl.chat().completions().createStreaming(params); + List result = new ArrayList<>(); + stream.subscribe(result::add); + try { + stream.onCompleteFuture().join(); + } catch (CompletionException e) { + if (e.getCause() instanceof OpenAIIoException) { + throw ((OpenAIIoException) e.getCause()); + } + throw e; + } + return result; + } + } + throw new AssertionError(); + } + @Test void basic() { ChatCompletionCreateParams params = @@ -116,7 +217,7 @@ void basic() { .model(TEST_CHAT_MODEL) .build(); - ChatCompletion response = getClient().chat().completions().create(params); + ChatCompletion response = doCompletions(params); String content = "Atlantic Ocean"; assertThat(response.choices().get(0).message().content()).hasValue(content); @@ -217,7 +318,7 @@ void testDeveloperMessage() { .model(TEST_CHAT_MODEL) .build(); - ChatCompletion response = getClient().chat().completions().create(params); + ChatCompletion response = doCompletions(params); String content = "Tomato."; assertThat(response.choices().get(0).message().content()).hasValue(content); @@ -271,7 +372,7 @@ void allTheClientOptions() { .responseFormat(ResponseFormatText.builder().build()) .build(); - ChatCompletion response = getClient().chat().completions().create(params); + ChatCompletion response = doCompletions(params); String content = "Southern Ocean."; assertThat(response.choices().get(0).message().content()).hasValue(content); @@ -376,7 +477,7 @@ void multipleChoices() { .n(2) .build(); - ChatCompletion response = getClient().chat().completions().create(params); + ChatCompletion response = doCompletions(params); String content1 = "South Atlantic Ocean."; assertThat(response.choices().get(0).message().content()).hasValue(content1); String content2 = "Atlantic Ocean."; @@ -489,7 +590,7 @@ void toolCalls() { .addTool(buildGetWeatherToolDefinition()) .build(); - ChatCompletion response = getClient().chat().completions().create(params); + ChatCompletion response = doCompletions(params); assertThat(response.choices().get(0).message().content()).isEmpty(); @@ -815,6 +916,13 @@ void connectionError() { .apiKey("testing") .maxRetries(0) .build()); + OpenAIClientAsync clientAsync = + wrap( + OpenAIOkHttpClientAsync.builder() + .baseUrl("http://localhost:9999/v5") + .apiKey("testing") + .maxRetries(0) + .build()); ChatCompletionCreateParams params = ChatCompletionCreateParams.builder() @@ -822,7 +930,7 @@ void connectionError() { .model(TEST_CHAT_MODEL) .build(); - Throwable thrown = catchThrowable(() -> client.chat().completions().create(params)); + Throwable thrown = catchThrowable(() -> doCompletions(params, client, clientAsync)); assertThat(thrown).isInstanceOf(OpenAIIoException.class); getTesting() @@ -873,11 +981,7 @@ void stream() { .model(TEST_CHAT_MODEL) .build(); - List chunks; - try (StreamResponse result = - getClient().chat().completions().createStreaming(params)) { - chunks = result.stream().collect(Collectors.toList()); - } + List chunks = doCompletionsStreaming(params); String fullMessage = chunks.stream() @@ -962,11 +1066,7 @@ void streamIncludeUsage() { .streamOptions(ChatCompletionStreamOptions.builder().includeUsage(true).build()) .build(); - List chunks; - try (StreamResponse result = - getClient().chat().completions().createStreaming(params)) { - chunks = result.stream().collect(Collectors.toList()); - } + List chunks = doCompletionsStreaming(params); String fullMessage = chunks.stream() @@ -1078,11 +1178,7 @@ void streamMultipleChoices() { .n(2) .build(); - List chunks; - try (StreamResponse result = - getClient().chat().completions().createStreaming(params)) { - chunks = result.stream().collect(Collectors.toList()); - } + List chunks = doCompletionsStreaming(params); StringBuilder content1Builder = new StringBuilder(); StringBuilder content2Builder = new StringBuilder(); @@ -1188,11 +1284,7 @@ void streamToolCalls() { .addTool(buildGetWeatherToolDefinition()) .build(); - List chunks; - try (StreamResponse result = - getClient().chat().completions().createStreaming(params)) { - chunks = result.stream().collect(Collectors.toList()); - } + List chunks = doCompletionsStreaming(params); List toolCalls = new ArrayList<>(); @@ -1363,10 +1455,7 @@ void streamToolCalls() { .addTool(buildGetWeatherToolDefinition()) .build(); - try (StreamResponse result = - getClient().chat().completions().createStreaming(params)) { - chunks = result.stream().collect(Collectors.toList()); - } + chunks = doCompletionsStreaming(params); String finalAnswer = chunks.stream() @@ -1510,6 +1599,13 @@ void streamConnectionError() { .apiKey("testing") .maxRetries(0) .build()); + OpenAIClientAsync clientAsync = + wrap( + OpenAIOkHttpClientAsync.builder() + .baseUrl("http://localhost:9999/v5") + .apiKey("testing") + .maxRetries(0) + .build()); ChatCompletionCreateParams params = ChatCompletionCreateParams.builder() @@ -1517,7 +1613,7 @@ void streamConnectionError() { .model(TEST_CHAT_MODEL) .build(); - Throwable thrown = catchThrowable(() -> client.chat().completions().createStreaming(params)); + Throwable thrown = catchThrowable(() -> doCompletionsStreaming(params, client, clientAsync)); assertThat(thrown).isInstanceOf(OpenAIIoException.class); getTesting() From 4b78428fed68bb9282f1ff70e2ca864b1ea8f4df Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Thu, 24 Jul 2025 17:05:38 +0900 Subject: [PATCH 02/11] Cleanup --- .../instrumentation/openai/v1_1/CompletableFutureWrapper.java | 2 +- .../instrumentation/openai/v1_1/InstrumentedChatService.java | 2 +- .../openai/v1_1/InstrumentedChatServiceAsync.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/CompletableFutureWrapper.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/CompletableFutureWrapper.java index 96b1b565daa7..3f634b856b49 100644 --- a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/CompletableFutureWrapper.java +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/CompletableFutureWrapper.java @@ -12,7 +12,7 @@ final class CompletableFutureWrapper { private CompletableFutureWrapper() {} - public static CompletableFuture wrap(CompletableFuture future, Context context) { + static CompletableFuture wrap(CompletableFuture future, Context context) { CompletableFuture result = new CompletableFuture<>(); future.whenComplete( (T value, Throwable throwable) -> { 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 11c9dba134da..0397279f119a 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 @@ -19,7 +19,7 @@ final class InstrumentedChatService private final Logger eventLogger; private final boolean captureMessageContent; - public InstrumentedChatService( + InstrumentedChatService( ChatService delegate, Instrumenter instrumenter, Logger eventLogger, 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 8dc8bbc09ed4..4bbf888b50c3 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 @@ -19,7 +19,7 @@ final class InstrumentedChatServiceAsync private final Logger eventLogger; private final boolean captureMessageContent; - public InstrumentedChatServiceAsync( + InstrumentedChatServiceAsync( ChatServiceAsync delegate, Instrumenter instrumenter, Logger eventLogger, From df19b40919da9d50345aae62b33d542920ce4373 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Fri, 25 Jul 2025 09:14:35 +0900 Subject: [PATCH 03/11] Rename context --- .../v1_1/ChatCompletionEventsHelper.java | 19 +++++--- .../InstrumentedChatCompletionService.java | 43 ++++++++--------- ...nstrumentedChatCompletionServiceAsync.java | 46 ++++++++++--------- .../openai/v1_1/StreamListener.java | 12 ++--- 4 files changed, 65 insertions(+), 55 deletions(-) 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 0703c2cc4e0c..219bd86b4e6d 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 @@ -35,7 +35,7 @@ final class ChatCompletionEventsHelper { private static final AttributeKey EVENT_NAME = stringKey("event.name"); public static void emitPromptLogEvents( - Context ctx, + Context context, Logger eventLogger, ChatCompletionCreateParams request, boolean captureMessageContent) { @@ -86,7 +86,7 @@ public static void emitPromptLogEvents( } else { continue; } - newEvent(eventLogger, eventType).setContext(ctx).setBody(Value.of(body)).emit(); + newEvent(eventLogger, eventType).setContext(context).setBody(Value.of(body)).emit(); } } @@ -162,7 +162,10 @@ private static String joinContentParts(List conte } public static void emitCompletionLogEvents( - Context ctx, Logger eventLogger, ChatCompletion completion, boolean captureMessageContent) { + Context context, + Logger eventLogger, + ChatCompletion completion, + boolean captureMessageContent) { for (ChatCompletion.Choice choice : completion.choices()) { ChatCompletionMessage choiceMsg = choice.message(); Map> message = new HashMap<>(); @@ -181,12 +184,16 @@ public static void emitCompletionLogEvents( .collect(Collectors.toList()))); }); emitCompletionLogEvent( - ctx, eventLogger, choice.index(), choice.finishReason().toString(), Value.of(message)); + context, + eventLogger, + choice.index(), + choice.finishReason().toString(), + Value.of(message)); } } public static void emitCompletionLogEvent( - Context ctx, + Context context, Logger eventLogger, long index, String finishReason, @@ -195,7 +202,7 @@ public static void emitCompletionLogEvent( body.put("finish_reason", Value.of(finishReason)); body.put("index", Value.of(index)); body.put("message", eventMessageObject); - newEvent(eventLogger, "gen_ai.choice").setContext(ctx).setBody(Value.of(body)).emit(); + newEvent(eventLogger, "gen_ai.choice").setContext(context).setBody(Value.of(body)).emit(); } private static LogRecordBuilder newEvent(Logger eventLogger, String name) { 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 ec171203631b..cef6b2ba2f68 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 @@ -73,65 +73,66 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl private ChatCompletion create( ChatCompletionCreateParams chatCompletionCreateParams, RequestOptions requestOptions) { - Context parentCtx = Context.current(); - if (!instrumenter.shouldStart(parentCtx, chatCompletionCreateParams)) { - return createWithLogs(parentCtx, chatCompletionCreateParams, requestOptions); + Context parentContext = Context.current(); + if (!instrumenter.shouldStart(parentContext, chatCompletionCreateParams)) { + return createWithLogs(parentContext, chatCompletionCreateParams, requestOptions); } - Context ctx = instrumenter.start(parentCtx, chatCompletionCreateParams); + Context context = instrumenter.start(parentContext, chatCompletionCreateParams); ChatCompletion completion; - try (Scope ignored = ctx.makeCurrent()) { - completion = createWithLogs(ctx, chatCompletionCreateParams, requestOptions); + try (Scope ignored = context.makeCurrent()) { + completion = createWithLogs(context, chatCompletionCreateParams, requestOptions); } catch (Throwable t) { - instrumenter.end(ctx, chatCompletionCreateParams, null, t); + instrumenter.end(context, chatCompletionCreateParams, null, t); throw t; } - instrumenter.end(ctx, chatCompletionCreateParams, completion, null); + instrumenter.end(context, chatCompletionCreateParams, completion, null); return completion; } private ChatCompletion createWithLogs( - Context ctx, + Context context, ChatCompletionCreateParams chatCompletionCreateParams, RequestOptions requestOptions) { ChatCompletionEventsHelper.emitPromptLogEvents( - ctx, eventLogger, chatCompletionCreateParams, captureMessageContent); + context, eventLogger, chatCompletionCreateParams, captureMessageContent); ChatCompletion result = delegate.create(chatCompletionCreateParams, requestOptions); ChatCompletionEventsHelper.emitCompletionLogEvents( - ctx, eventLogger, result, captureMessageContent); + context, eventLogger, result, captureMessageContent); return result; } private StreamResponse createStreaming( ChatCompletionCreateParams chatCompletionCreateParams, RequestOptions requestOptions) { - Context parentCtx = Context.current(); - if (!instrumenter.shouldStart(parentCtx, chatCompletionCreateParams)) { - return createStreamingWithLogs(parentCtx, chatCompletionCreateParams, requestOptions, false); + Context parentContext = Context.current(); + if (!instrumenter.shouldStart(parentContext, chatCompletionCreateParams)) { + return createStreamingWithLogs( + parentContext, chatCompletionCreateParams, requestOptions, false); } - Context ctx = instrumenter.start(parentCtx, chatCompletionCreateParams); - try (Scope ignored = ctx.makeCurrent()) { - return createStreamingWithLogs(ctx, chatCompletionCreateParams, requestOptions, true); + Context context = instrumenter.start(parentContext, chatCompletionCreateParams); + try (Scope ignored = context.makeCurrent()) { + return createStreamingWithLogs(context, chatCompletionCreateParams, requestOptions, true); } catch (Throwable t) { - instrumenter.end(ctx, chatCompletionCreateParams, null, t); + instrumenter.end(context, chatCompletionCreateParams, null, t); throw t; } } private StreamResponse createStreamingWithLogs( - Context ctx, + Context context, ChatCompletionCreateParams chatCompletionCreateParams, RequestOptions requestOptions, boolean newSpan) { ChatCompletionEventsHelper.emitPromptLogEvents( - ctx, eventLogger, chatCompletionCreateParams, captureMessageContent); + context, eventLogger, chatCompletionCreateParams, captureMessageContent); StreamResponse result = delegate.createStreaming(chatCompletionCreateParams, requestOptions); return new TracingStreamedResponse( result, new StreamListener( - ctx, + context, chatCompletionCreateParams, instrumenter, eventLogger, 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 f8e8d88903a7..d8908d6229ce 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 @@ -75,69 +75,71 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl private CompletableFuture create( ChatCompletionCreateParams chatCompletionCreateParams, RequestOptions requestOptions) { - Context parentCtx = Context.current(); - if (!instrumenter.shouldStart(parentCtx, chatCompletionCreateParams)) { - return createWithLogs(parentCtx, chatCompletionCreateParams, requestOptions); + Context parentContext = Context.current(); + if (!instrumenter.shouldStart(parentContext, chatCompletionCreateParams)) { + return createWithLogs(parentContext, chatCompletionCreateParams, requestOptions); } - Context ctx = instrumenter.start(parentCtx, chatCompletionCreateParams); + Context context = instrumenter.start(parentContext, chatCompletionCreateParams); CompletableFuture future; - try (Scope ignored = ctx.makeCurrent()) { - future = createWithLogs(ctx, chatCompletionCreateParams, requestOptions); + try (Scope ignored = context.makeCurrent()) { + future = createWithLogs(context, chatCompletionCreateParams, requestOptions); } catch (Throwable t) { - instrumenter.end(ctx, chatCompletionCreateParams, null, t); + instrumenter.end(context, chatCompletionCreateParams, null, t); throw t; } future = - future.whenComplete((res, t) -> instrumenter.end(ctx, chatCompletionCreateParams, res, t)); - return CompletableFutureWrapper.wrap(future, ctx); + future.whenComplete( + (res, t) -> instrumenter.end(context, chatCompletionCreateParams, res, t)); + return CompletableFutureWrapper.wrap(future, context); } private CompletableFuture createWithLogs( - Context ctx, + Context context, ChatCompletionCreateParams chatCompletionCreateParams, RequestOptions requestOptions) { ChatCompletionEventsHelper.emitPromptLogEvents( - ctx, eventLogger, chatCompletionCreateParams, captureMessageContent); + context, eventLogger, chatCompletionCreateParams, captureMessageContent); CompletableFuture future = delegate.create(chatCompletionCreateParams, requestOptions); future.thenAccept( r -> ChatCompletionEventsHelper.emitCompletionLogEvents( - ctx, eventLogger, r, captureMessageContent)); + context, eventLogger, r, captureMessageContent)); return future; } private AsyncStreamResponse createStreaming( ChatCompletionCreateParams chatCompletionCreateParams, RequestOptions requestOptions) { - Context parentCtx = Context.current(); - if (!instrumenter.shouldStart(parentCtx, chatCompletionCreateParams)) { - return createStreamingWithLogs(parentCtx, chatCompletionCreateParams, requestOptions, false); + Context parentContext = Context.current(); + if (!instrumenter.shouldStart(parentContext, chatCompletionCreateParams)) { + return createStreamingWithLogs( + parentContext, chatCompletionCreateParams, requestOptions, false); } - Context ctx = instrumenter.start(parentCtx, chatCompletionCreateParams); - try (Scope ignored = ctx.makeCurrent()) { - return createStreamingWithLogs(ctx, chatCompletionCreateParams, requestOptions, true); + Context context = instrumenter.start(parentContext, chatCompletionCreateParams); + try (Scope ignored = context.makeCurrent()) { + return createStreamingWithLogs(context, chatCompletionCreateParams, requestOptions, true); } catch (Throwable t) { - instrumenter.end(ctx, chatCompletionCreateParams, null, t); + instrumenter.end(context, chatCompletionCreateParams, null, t); throw t; } } private AsyncStreamResponse createStreamingWithLogs( - Context ctx, + Context context, ChatCompletionCreateParams chatCompletionCreateParams, RequestOptions requestOptions, boolean newSpan) { ChatCompletionEventsHelper.emitPromptLogEvents( - ctx, eventLogger, chatCompletionCreateParams, captureMessageContent); + context, eventLogger, chatCompletionCreateParams, captureMessageContent); AsyncStreamResponse result = delegate.createStreaming(chatCompletionCreateParams, requestOptions); return new TracingAsyncStreamedResponse( result, new StreamListener( - ctx, + context, chatCompletionCreateParams, instrumenter, eventLogger, diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/StreamListener.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/StreamListener.java index 533ca3214baf..9cda4c1f01f7 100644 --- a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/StreamListener.java +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/StreamListener.java @@ -20,7 +20,7 @@ final class StreamListener { - private final Context parentCtx; + private final Context context; private final ChatCompletionCreateParams request; private final List choiceBuffers; @@ -35,13 +35,13 @@ final class StreamListener { @Nullable private String responseId; StreamListener( - Context parentCtx, + Context context, ChatCompletionCreateParams request, Instrumenter instrumenter, Logger eventLogger, boolean captureMessageContent, boolean newSpan) { - this.parentCtx = parentCtx; + this.context = context; this.request = request; this.instrumenter = instrumenter; this.eventLogger = eventLogger; @@ -71,7 +71,7 @@ void onChunk(ChatCompletionChunk chunk) { // message has ended, let's emit ChatCompletionEventsHelper.emitCompletionLogEvent( - parentCtx, eventLogger, choice.index(), buffer.finishReason, buffer.toEventBody()); + context, eventLogger, choice.index(), buffer.finishReason, buffer.toEventBody()); } } } @@ -86,7 +86,7 @@ void endSpan(@Nullable Throwable error) { if (model == null || responseId == null) { // Only happens if we got no chunks, so we have no response. if (newSpan) { - instrumenter.end(parentCtx, request, null, error); + instrumenter.end(context, request, null, error); } return; } @@ -106,7 +106,7 @@ void endSpan(@Nullable Throwable error) { } if (newSpan) { - instrumenter.end(parentCtx, request, result.build(), error); + instrumenter.end(context, request, result.build(), error); } } } From caaf1b575bbfe79865b6e133ce73541599302689 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Fri, 25 Jul 2025 14:59:05 +0900 Subject: [PATCH 04/11] Instrument openai embeddings --- .../openai/v1_1/CompletableFutureWrapper.java | 30 +++++ .../v1_1/EmbeddingAttributesGetter.java | 122 ++++++++++++++++++ .../openai/v1_1/GenAiAttributes.java | 1 + .../v1_1/InstrumentedEmbeddingService.java | 65 ++++++++++ .../InstrumentedEmbeddingServiceAsync.java | 67 ++++++++++ .../openai/v1_1/InstrumentedOpenAiClient.java | 5 + .../openai/v1_1/OpenAITelemetry.java | 7 +- .../openai/v1_1/OpenAITelemetryBuilder.java | 13 +- 8 files changed, 308 insertions(+), 2 deletions(-) create mode 100644 instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/CompletableFutureWrapper.java create mode 100644 instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/EmbeddingAttributesGetter.java create mode 100644 instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedEmbeddingService.java create mode 100644 instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedEmbeddingServiceAsync.java diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/CompletableFutureWrapper.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/CompletableFutureWrapper.java new file mode 100644 index 000000000000..3f634b856b49 --- /dev/null +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/CompletableFutureWrapper.java @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.openai.v1_1; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import java.util.concurrent.CompletableFuture; + +final class CompletableFutureWrapper { + private CompletableFutureWrapper() {} + + static CompletableFuture wrap(CompletableFuture future, Context context) { + CompletableFuture result = new CompletableFuture<>(); + future.whenComplete( + (T value, Throwable throwable) -> { + try (Scope ignored = context.makeCurrent()) { + if (throwable != null) { + result.completeExceptionally(throwable); + } else { + result.complete(value); + } + } + }); + + return result; + } +} 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 new file mode 100644 index 000000000000..1be24b13e9b3 --- /dev/null +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/EmbeddingAttributesGetter.java @@ -0,0 +1,122 @@ +package io.opentelemetry.instrumentation.openai.v1_1; + +import static java.util.Collections.singletonList; + +import java.util.List; +import javax.annotation.Nullable; +import com.openai.models.embeddings.CreateEmbeddingResponse; +import com.openai.models.embeddings.EmbeddingCreateParams; +import io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiAttributesGetter; + +enum EmbeddingAttributesGetter implements GenAiAttributesGetter { + INSTANCE; + + @Override + public String getOperationName(EmbeddingCreateParams request) { + return GenAiAttributes.GenAiOperationNameIncubatingValues.EMBEDDING; + } + + @Override + public String getSystem(EmbeddingCreateParams request) { + return GenAiAttributes.GenAiSystemIncubatingValues.OPENAI; + } + + @Override + public String getRequestModel(EmbeddingCreateParams request) { + return request.model().asString(); + } + + @Nullable + @Override + public Long getRequestSeed(EmbeddingCreateParams request) { + return null; + } + + @Nullable + @Override + public List getRequestEncodingFormats(EmbeddingCreateParams request) { + return request.encodingFormat().map(f -> singletonList(f.asString())).orElse(null); + } + + @Nullable + @Override + public Double getRequestFrequencyPenalty(EmbeddingCreateParams request) { + return null; + } + + @Nullable + @Override + public Long getRequestMaxTokens(EmbeddingCreateParams request) { + return null; + } + + @Nullable + @Override + public Double getRequestPresencePenalty(EmbeddingCreateParams request) { + return null; + } + + @Nullable + @Override + public List getRequestStopSequences(EmbeddingCreateParams request) { + return null; + } + + @Nullable + @Override + public Double getRequestTemperature(EmbeddingCreateParams request) { + return null; + } + + @Nullable + @Override + public Double getRequestTopK(EmbeddingCreateParams request) { + return null; + } + + @Nullable + @Override + public Double getRequestTopP(EmbeddingCreateParams request) { + return null; + } + + @Override + public List getResponseFinishReasons(EmbeddingCreateParams request, + @Nullable CreateEmbeddingResponse response) { + return null; + } + + @Nullable + @Override + public String getResponseId(EmbeddingCreateParams request, + @Nullable CreateEmbeddingResponse response) { + return null; + } + + @Nullable + @Override + public String getResponseModel(EmbeddingCreateParams request, + @Nullable CreateEmbeddingResponse response) { + if (response == null) { + return null; + } + return response.model(); + } + + @Nullable + @Override + public Long getUsageInputTokens(EmbeddingCreateParams request, + @Nullable CreateEmbeddingResponse response) { + if (response == null) { + return null; + } + return response.usage().promptTokens(); + } + + @Nullable + @Override + public Long getUsageOutputTokens(EmbeddingCreateParams request, + @Nullable CreateEmbeddingResponse response) { + return null; + } +} 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 b34e4d3c7cab..20ccc80928f2 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 @@ -15,6 +15,7 @@ final class GenAiAttributes { static final class GenAiOperationNameIncubatingValues { static final String CHAT = "chat"; + static final String EMBEDDING = "embeddings"; private GenAiOperationNameIncubatingValues() {} } diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedEmbeddingService.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedEmbeddingService.java new file mode 100644 index 000000000000..b5b7d581018d --- /dev/null +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedEmbeddingService.java @@ -0,0 +1,65 @@ +package io.opentelemetry.instrumentation.openai.v1_1; + +import java.lang.reflect.Method; +import com.openai.core.RequestOptions; +import com.openai.models.embeddings.CreateEmbeddingResponse; +import com.openai.models.embeddings.EmbeddingCreateParams; +import com.openai.services.blocking.EmbeddingService; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; + +final class InstrumentedEmbeddingService + extends DelegatingInvocationHandler { + + private final Instrumenter instrumenter; + + public InstrumentedEmbeddingService(EmbeddingService delegate, + Instrumenter instrumenter) { + super(delegate); + this.instrumenter = instrumenter; + } + + @Override + protected Class getProxyType() { + return EmbeddingService.class; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + String methodName = method.getName(); + Class[] parameterTypes = method.getParameterTypes(); + + if (methodName.equals("create") + && parameterTypes.length >= 1 + && parameterTypes[0] == EmbeddingCreateParams.class) { + if (parameterTypes.length == 1) { + return create((EmbeddingCreateParams) args[0], RequestOptions.none()); + } else if (parameterTypes.length == 2 && parameterTypes[1] == RequestOptions.class) { + return create((EmbeddingCreateParams) args[0], (RequestOptions) args[1]); + } + } + + return super.invoke(proxy, method, args); + } + + private CreateEmbeddingResponse create( + EmbeddingCreateParams request, RequestOptions requestOptions) { + Context parentContext = Context.current(); + if (!instrumenter.shouldStart(parentContext, request)) { + return delegate.create(request, requestOptions); + } + + Context context = instrumenter.start(parentContext, request); + CreateEmbeddingResponse response; + try (Scope ignored = context.makeCurrent()) { + response = delegate.create(request, requestOptions); + } catch (Throwable t) { + instrumenter.end(context, request, null, t); + throw t; + } + + instrumenter.end(context, request, response, null); + return response; + } +} diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedEmbeddingServiceAsync.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedEmbeddingServiceAsync.java new file mode 100644 index 000000000000..265818d6077f --- /dev/null +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedEmbeddingServiceAsync.java @@ -0,0 +1,67 @@ +package io.opentelemetry.instrumentation.openai.v1_1; + +import com.openai.core.RequestOptions; +import com.openai.models.embeddings.CreateEmbeddingResponse; +import com.openai.models.embeddings.EmbeddingCreateParams; +import com.openai.services.async.EmbeddingServiceAsync; +import com.openai.services.blocking.EmbeddingService; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import java.lang.reflect.Method; +import java.util.concurrent.CompletableFuture; + +final class InstrumentedEmbeddingServiceAsync + extends DelegatingInvocationHandler { + + private final Instrumenter instrumenter; + + public InstrumentedEmbeddingServiceAsync(EmbeddingServiceAsync delegate, + Instrumenter instrumenter) { + super(delegate); + this.instrumenter = instrumenter; + } + + @Override + protected Class getProxyType() { + return EmbeddingServiceAsync.class; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + String methodName = method.getName(); + Class[] parameterTypes = method.getParameterTypes(); + + if (methodName.equals("create") + && parameterTypes.length >= 1 + && parameterTypes[0] == EmbeddingCreateParams.class) { + if (parameterTypes.length == 1) { + return create((EmbeddingCreateParams) args[0], RequestOptions.none()); + } else if (parameterTypes.length == 2 && parameterTypes[1] == RequestOptions.class) { + return create((EmbeddingCreateParams) args[0], (RequestOptions) args[1]); + } + } + + return super.invoke(proxy, method, args); + } + + private CompletableFuture create( + EmbeddingCreateParams request, RequestOptions requestOptions) { + Context parentContext = Context.current(); + if (!instrumenter.shouldStart(parentContext, request)) { + return delegate.create(request, requestOptions); + } + + Context context = instrumenter.start(parentContext, request); + CompletableFuture future; + try (Scope ignored = context.makeCurrent()) { + future = delegate.create(request, requestOptions); + } catch (Throwable t) { + instrumenter.end(context, request, null, t); + throw t; + } + + future = future.whenComplete((res, t) -> instrumenter.end(context, request, res, t)); + return CompletableFutureWrapper.wrap(future, context); + } +} 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 cc118a85cf33..bf46e3ebdac1 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 @@ -8,6 +8,8 @@ import com.openai.client.OpenAIClient; import com.openai.models.chat.completions.ChatCompletion; import com.openai.models.chat.completions.ChatCompletionCreateParams; +import com.openai.models.embeddings.CreateEmbeddingResponse; +import com.openai.models.embeddings.EmbeddingCreateParams; import io.opentelemetry.api.logs.Logger; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import java.lang.reflect.Method; @@ -16,16 +18,19 @@ final class InstrumentedOpenAiClient extends DelegatingInvocationHandler { private final Instrumenter chatInstrumenter; + private final Instrumenter embeddingInstrumenter; private final Logger eventLogger; private final boolean captureMessageContent; InstrumentedOpenAiClient( OpenAIClient delegate, Instrumenter chatInstrumenter, + Instrumenter embeddingInstrumenter, Logger eventLogger, boolean captureMessageContent) { super(delegate); this.chatInstrumenter = chatInstrumenter; + this.embeddingInstrumenter = embeddingInstrumenter; this.eventLogger = eventLogger; this.captureMessageContent = captureMessageContent; } 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 ca9f26d1f2ff..f6d93d873786 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 @@ -8,6 +8,8 @@ import com.openai.client.OpenAIClient; import com.openai.models.chat.completions.ChatCompletion; import com.openai.models.chat.completions.ChatCompletionCreateParams; +import com.openai.models.embeddings.CreateEmbeddingResponse; +import com.openai.models.embeddings.EmbeddingCreateParams; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.logs.Logger; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; @@ -28,6 +30,7 @@ public static OpenAITelemetryBuilder builder(OpenTelemetry openTelemetry) { } private final Instrumenter chatInstrumenter; + private final Instrumenter embeddingsInstrumenter; private final Logger eventLogger; @@ -35,9 +38,11 @@ public static OpenAITelemetryBuilder builder(OpenTelemetry openTelemetry) { OpenAITelemetry( Instrumenter chatInstrumenter, + Instrumenter embeddingsInstrumenter, Logger eventLogger, boolean captureMessageContent) { this.chatInstrumenter = chatInstrumenter; + this.embeddingsInstrumenter = embeddingsInstrumenter; this.eventLogger = eventLogger; this.captureMessageContent = captureMessageContent; } @@ -45,7 +50,7 @@ public static OpenAITelemetryBuilder builder(OpenTelemetry openTelemetry) { /** Wraps the provided OpenAIClient, enabling telemetry for it. */ public OpenAIClient wrap(OpenAIClient client) { return new InstrumentedOpenAiClient( - client, chatInstrumenter, eventLogger, captureMessageContent) + client, chatInstrumenter, embeddingsInstrumenter, eventLogger, captureMessageContent) .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 8d0574b541a7..a9b2c8b4596c 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 @@ -8,6 +8,8 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.openai.models.chat.completions.ChatCompletion; import com.openai.models.chat.completions.ChatCompletionCreateParams; +import com.openai.models.embeddings.CreateEmbeddingResponse; +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.GenAiAttributesExtractor; @@ -53,7 +55,16 @@ public OpenAITelemetry build() { .addOperationMetrics(GenAiClientMetrics.get()) .buildInstrumenter(); + Instrumenter embeddingsInstrumenter = + Instrumenter.builder( + openTelemetry, + INSTRUMENTATION_NAME, + GenAiSpanNameExtractor.create(EmbeddingAttributesGetter.INSTANCE)) + .addAttributesExtractor(GenAiAttributesExtractor.create(EmbeddingAttributesGetter.INSTANCE)) + .addOperationMetrics(GenAiClientMetrics.get()) + .buildInstrumenter(); + Logger eventLogger = openTelemetry.getLogsBridge().get(INSTRUMENTATION_NAME); - return new OpenAITelemetry(chatInstrumenter, eventLogger, captureMessageContent); + return new OpenAITelemetry(chatInstrumenter, embeddingsInstrumenter, eventLogger, captureMessageContent); } } From 87820df3b064ea2b80503268127a47f4653dea7b Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Fri, 25 Jul 2025 15:05:05 +0900 Subject: [PATCH 05/11] Fix context --- .../v1_1/InstrumentedChatCompletionServiceAsync.java | 2 +- .../openai/v1_1/AbstractChatTest.java | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) 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 d8908d6229ce..5b2887b304eb 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 @@ -92,7 +92,7 @@ private CompletableFuture create( future = future.whenComplete( (res, t) -> instrumenter.end(context, chatCompletionCreateParams, res, t)); - return CompletableFutureWrapper.wrap(future, context); + return CompletableFutureWrapper.wrap(future, parentContext); } private CompletableFuture createWithLogs( 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 e2a6ad65dae9..e1f11a464405 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 @@ -59,7 +59,9 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.KeyValue; import io.opentelemetry.api.common.Value; +import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.recording.RecordingExtension; import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; @@ -158,7 +160,15 @@ protected final ChatCompletion doCompletions( case ASYNC_FROM_SYNC: OpenAIClientAsync cl = testType == TestType.ASYNC ? clientAsync : client.async(); try { - return cl.chat().completions().create(params).join(); + return cl.chat() + .completions() + .create(params) + .thenApply( + res -> { + assertThat(Span.fromContextOrNull(Context.current())).isNull(); + return res; + }) + .join(); } catch (CompletionException e) { if (e.getCause() instanceof OpenAIIoException) { throw ((OpenAIIoException) e.getCause()); From 64069459a1c059eb16042aa7e2aa71858803e109 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Fri, 25 Jul 2025 15:23:31 +0900 Subject: [PATCH 06/11] WIP --- .../InstrumentedEmbeddingServiceAsync.java | 2 +- .../openai/v1_1/OpenAITelemetry.java | 1 + .../openai/v1_1/EmbeddingsTest.java | 48 +++++++ .../openai/v1_1/AbstractEmbeddingsTest.java | 135 ++++++++++++++++++ .../openai/v1_1/AbstractOpenAiTest.java | 76 ++++++++++ 5 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 instrumentation/openai/openai-java-1.1/library/src/test/java/io/opentelemetry/instrumentation/openai/v1_1/EmbeddingsTest.java create mode 100644 instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractEmbeddingsTest.java create mode 100644 instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractOpenAiTest.java diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedEmbeddingServiceAsync.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedEmbeddingServiceAsync.java index 265818d6077f..1456e650422e 100644 --- a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedEmbeddingServiceAsync.java +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedEmbeddingServiceAsync.java @@ -62,6 +62,6 @@ private CompletableFuture create( } future = future.whenComplete((res, t) -> instrumenter.end(context, request, res, t)); - return CompletableFutureWrapper.wrap(future, context); + return CompletableFutureWrapper.wrap(future, parentContext); } } 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 f6d93d873786..2eb199ceefc8 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 @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.openai.v1_1; import com.openai.client.OpenAIClient; +import com.openai.client.OpenAIClientAsync; import com.openai.models.chat.completions.ChatCompletion; import com.openai.models.chat.completions.ChatCompletionCreateParams; import com.openai.models.embeddings.CreateEmbeddingResponse; diff --git a/instrumentation/openai/openai-java-1.1/library/src/test/java/io/opentelemetry/instrumentation/openai/v1_1/EmbeddingsTest.java b/instrumentation/openai/openai-java-1.1/library/src/test/java/io/opentelemetry/instrumentation/openai/v1_1/EmbeddingsTest.java new file mode 100644 index 000000000000..b7887bb15323 --- /dev/null +++ b/instrumentation/openai/openai-java-1.1/library/src/test/java/io/opentelemetry/instrumentation/openai/v1_1/EmbeddingsTest.java @@ -0,0 +1,48 @@ +package io.opentelemetry.instrumentation.openai.v1_1; + +import static java.util.Collections.singletonList; + +import java.util.List; +import java.util.function.Consumer; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.RegisterExtension; +import com.openai.client.OpenAIClient; +import com.openai.client.OpenAIClientAsync; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; + +class EmbeddingsTest extends AbstractEmbeddingsTest { + + @RegisterExtension + private static final LibraryInstrumentationExtension testing = + LibraryInstrumentationExtension.create(); + + private static OpenAITelemetry telemetry; + + @BeforeAll + static void setup() { + telemetry = + OpenAITelemetry.builder(testing.getOpenTelemetry()).setCaptureMessageContent(true).build(); + } + + @Override + protected InstrumentationExtension getTesting() { + return testing; + } + + @Override + protected OpenAIClient wrap(OpenAIClient client) { + return telemetry.wrap(client); + } + + @Override + protected OpenAIClientAsync wrap(OpenAIClientAsync client) { + return telemetry.wrap(client); + } + + @Override + protected List> maybeWithTransportSpan(Consumer span) { + return singletonList(span); + } +} diff --git a/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractEmbeddingsTest.java b/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractEmbeddingsTest.java new file mode 100644 index 000000000000..2e1044a9e63e --- /dev/null +++ b/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractEmbeddingsTest.java @@ -0,0 +1,135 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.openai.v1_1; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_OPERATION_NAME; +import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_REQUEST_MODEL; +import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_RESPONSE_MODEL; +import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_SYSTEM; +import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_TOKEN_TYPE; +import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_USAGE_INPUT_TOKENS; +import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiOperationNameIncubatingValues.EMBEDDINGS; +import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiSystemIncubatingValues.OPENAI; +import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiTokenTypeIncubatingValues.INPUT; +import static org.assertj.core.api.Assertions.assertThat; + +import com.openai.client.OpenAIClient; +import com.openai.client.OpenAIClientAsync; +import com.openai.models.embeddings.CreateEmbeddingResponse; +import com.openai.models.embeddings.EmbeddingCreateParams; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.testing.recording.RecordingExtension; +import java.util.Collections; +import java.util.concurrent.CompletionException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public abstract class AbstractEmbeddingsTest extends AbstractOpenAiTest { + protected static final String INSTRUMENTATION_NAME = "io.opentelemetry.openai-java-1.1"; + + private static final String API_URL = "https://api.openai.com/v1"; + + private static final String MODEL = "text-embedding-3-small"; + + @RegisterExtension static final RecordingExtension recording = new RecordingExtension(API_URL); + + protected final CreateEmbeddingResponse doEmbeddings(EmbeddingCreateParams request) { + return doEmbeddings(request, getClient(), getClientAsync()); + } + + protected final CreateEmbeddingResponse doEmbeddings( + EmbeddingCreateParams request, OpenAIClient client, OpenAIClientAsync clientAsync) { + switch (testType) { + case SYNC: + return client.embeddings().create(request); + case SYNC_FROM_ASYNC: + return clientAsync.sync().embeddings().create(request); + case ASYNC: + case ASYNC_FROM_SYNC: + OpenAIClientAsync cl = testType == TestType.ASYNC ? clientAsync : client.async(); + try { + return cl.embeddings() + .create(request) + .thenApply( + res -> { + assertThat(Span.fromContextOrNull(Context.current())).isNull(); + return res; + }) + .join(); + } catch (CompletionException e) { + if (e.getCause() instanceof RuntimeException) { + throw (RuntimeException) e.getCause(); + } + throw e; + } + } + throw new AssertionError(); + } + + @Test + void basic() { + String text = "South Atlantic Ocean."; + + EmbeddingCreateParams request = + EmbeddingCreateParams.builder() + .model(MODEL) + .inputOfArrayOfStrings(Collections.singletonList(text)) + .build(); + CreateEmbeddingResponse response = doEmbeddings(request); + + assertThat(response.data()).hasSize(1); + + getTesting() + .waitAndAssertTraces( + trace -> + maybeWithTransportSpan( + span -> + span.hasName("embeddings text-embedding-3-small") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(GEN_AI_SYSTEM, OPENAI), + equalTo(GEN_AI_OPERATION_NAME, EMBEDDINGS), + equalTo(GEN_AI_REQUEST_MODEL, MODEL), + equalTo(GEN_AI_RESPONSE_MODEL, MODEL), + equalTo(GEN_AI_USAGE_INPUT_TOKENS, 4)))); + + getTesting() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + metric -> + metric + .hasName("gen_ai.client.operation.duration") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSumGreaterThan(0.0) + .hasAttributesSatisfyingExactly( + equalTo(GEN_AI_SYSTEM, "openai"), + equalTo(GEN_AI_OPERATION_NAME, "chat"), + equalTo(GEN_AI_REQUEST_MODEL, MODEL), + equalTo(GEN_AI_RESPONSE_MODEL, MODEL)))), + metric -> + metric + .hasName("gen_ai.client.token.usage") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(4.0) + .hasAttributesSatisfyingExactly( + equalTo(GEN_AI_SYSTEM, "openai"), + equalTo(GEN_AI_OPERATION_NAME, "chat"), + equalTo(GEN_AI_REQUEST_MODEL, MODEL), + equalTo(GEN_AI_RESPONSE_MODEL, MODEL), + equalTo(GEN_AI_TOKEN_TYPE, INPUT))))); + } +} diff --git a/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractOpenAiTest.java b/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractOpenAiTest.java new file mode 100644 index 000000000000..2a630d289f15 --- /dev/null +++ b/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractOpenAiTest.java @@ -0,0 +1,76 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.openai.v1_1; + +import com.openai.client.OpenAIClient; +import com.openai.client.OpenAIClientAsync; +import com.openai.client.okhttp.OpenAIOkHttpClient; +import com.openai.client.okhttp.OpenAIOkHttpClientAsync; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.recording.RecordingExtension; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import java.util.List; +import java.util.function.Consumer; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; + +@ParameterizedClass +abstract class AbstractOpenAiTest { + enum TestType { + SYNC, + SYNC_FROM_ASYNC, + ASYNC, + ASYNC_FROM_SYNC, + } + + protected static final String INSTRUMENTATION_NAME = "io.opentelemetry.openai-java-1.1"; + + private static final String API_URL = "https://api.openai.com/v1"; + + @RegisterExtension static final RecordingExtension recording = new RecordingExtension(API_URL); + + protected abstract InstrumentationExtension getTesting(); + + protected abstract OpenAIClient wrap(OpenAIClient client); + + protected abstract OpenAIClientAsync wrap(OpenAIClientAsync client); + + protected final OpenAIClient getRawClient() { + OpenAIOkHttpClient.Builder builder = + OpenAIOkHttpClient.builder().baseUrl("http://localhost:" + recording.getPort()); + if (recording.isRecording()) { + builder.apiKey(System.getenv("OPENAI_API_KEY")); + } else { + builder.apiKey("unused"); + } + return builder.build(); + } + + protected final OpenAIClientAsync getRawClientAsync() { + OpenAIOkHttpClientAsync.Builder builder = + OpenAIOkHttpClientAsync.builder().baseUrl("http://localhost:" + recording.getPort()); + if (recording.isRecording()) { + builder.apiKey(System.getenv("OPENAI_API_KEY")); + } else { + builder.apiKey("unused"); + } + return builder.build(); + } + + protected final OpenAIClient getClient() { + return wrap(getRawClient()); + } + + protected final OpenAIClientAsync getClientAsync() { + return wrap(getRawClientAsync()); + } + + protected abstract List> maybeWithTransportSpan( + Consumer span); + + @Parameter protected TestType testType; +} From 78bee52249b9a528cae25447c7a9bdee032cd19c Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Fri, 25 Jul 2025 16:16:50 +0900 Subject: [PATCH 07/11] Finish --- .../semconv/genai/GenAiAttributesGetter.java | 1 + .../openai/v1_1/EmbeddingsTest.java | 49 + .../v1_1/EmbeddingAttributesGetter.java | 33 +- .../v1_1/InstrumentedEmbeddingService.java | 10 +- .../InstrumentedEmbeddingServiceAsync.java | 11 +- .../openai/v1_1/InstrumentedOpenAiClient.java | 10 +- .../v1_1/InstrumentedOpenAiClientAsync.java | 15 +- .../openai/v1_1/OpenAITelemetry.java | 2 +- .../openai/v1_1/OpenAITelemetryBuilder.java | 6 +- .../openai/v1_1/EmbeddingsTest.java | 13 +- .../openai/v1_1/AbstractChatTest.java | 66 +- .../openai/v1_1/AbstractEmbeddingsTest.java | 137 +- .../openai/v1_1/AbstractOpenAiTest.java | 2 + ...actembeddingstest.alltheclientoptions.yaml | 62 + ...nai.v1_1.abstractembeddingstest.basic.yaml | 1598 +++++++++++++++++ 15 files changed, 1917 insertions(+), 98 deletions(-) create mode 100644 instrumentation/openai/openai-java-1.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/openai/v1_1/EmbeddingsTest.java create mode 100644 instrumentation/openai/openai-java-1.1/testing/src/main/resources/mappings/io.opentelemetry.instrumentation.openai.v1_1.abstractembeddingstest.alltheclientoptions.yaml create mode 100644 instrumentation/openai/openai-java-1.1/testing/src/main/resources/mappings/io.opentelemetry.instrumentation.openai.v1_1.abstractembeddingstest.basic.yaml 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 91b5a9f5bb02..e133518fb40e 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 @@ -50,6 +50,7 @@ public interface GenAiAttributesGetter { @Nullable Double getRequestTopP(REQUEST request); + @Nullable List getResponseFinishReasons(REQUEST request, RESPONSE response); @Nullable diff --git a/instrumentation/openai/openai-java-1.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/openai/v1_1/EmbeddingsTest.java b/instrumentation/openai/openai-java-1.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/openai/v1_1/EmbeddingsTest.java new file mode 100644 index 000000000000..c31bffa92357 --- /dev/null +++ b/instrumentation/openai/openai-java-1.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/openai/v1_1/EmbeddingsTest.java @@ -0,0 +1,49 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.openai.v1_1; + +import com.openai.client.OpenAIClient; +import com.openai.client.OpenAIClientAsync; +import io.opentelemetry.instrumentation.openai.v1_1.AbstractEmbeddingsTest; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import org.junit.jupiter.api.extension.RegisterExtension; + +class EmbeddingsTest extends AbstractEmbeddingsTest { + + @RegisterExtension + private static final AgentInstrumentationExtension testing = + AgentInstrumentationExtension.create(); + + @Override + protected InstrumentationExtension getTesting() { + return testing; + } + + @Override + protected OpenAIClient wrap(OpenAIClient client) { + return client; + } + + @Override + protected OpenAIClientAsync wrap(OpenAIClientAsync client) { + return client; + } + + @Override + protected final List> maybeWithTransportSpan( + Consumer span) { + List> result = new ArrayList<>(); + result.add(span); + // Do a very simple assertion since the telemetry is not part of this library. + result.add(s -> s.hasName("POST")); + return result; + } +} 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 1be24b13e9b3..4f315c734af5 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 @@ -1,14 +1,20 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.instrumentation.openai.v1_1; import static java.util.Collections.singletonList; -import java.util.List; -import javax.annotation.Nullable; import com.openai.models.embeddings.CreateEmbeddingResponse; import com.openai.models.embeddings.EmbeddingCreateParams; import io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiAttributesGetter; +import java.util.List; +import javax.annotation.Nullable; -enum EmbeddingAttributesGetter implements GenAiAttributesGetter { +enum EmbeddingAttributesGetter + implements GenAiAttributesGetter { INSTANCE; @Override @@ -80,23 +86,24 @@ public Double getRequestTopP(EmbeddingCreateParams request) { return null; } + @Nullable @Override - public List getResponseFinishReasons(EmbeddingCreateParams request, - @Nullable CreateEmbeddingResponse response) { + public List getResponseFinishReasons( + EmbeddingCreateParams request, @Nullable CreateEmbeddingResponse response) { return null; } @Nullable @Override - public String getResponseId(EmbeddingCreateParams request, - @Nullable CreateEmbeddingResponse response) { + public String getResponseId( + EmbeddingCreateParams request, @Nullable CreateEmbeddingResponse response) { return null; } @Nullable @Override - public String getResponseModel(EmbeddingCreateParams request, - @Nullable CreateEmbeddingResponse response) { + public String getResponseModel( + EmbeddingCreateParams request, @Nullable CreateEmbeddingResponse response) { if (response == null) { return null; } @@ -105,8 +112,8 @@ public String getResponseModel(EmbeddingCreateParams request, @Nullable @Override - public Long getUsageInputTokens(EmbeddingCreateParams request, - @Nullable CreateEmbeddingResponse response) { + public Long getUsageInputTokens( + EmbeddingCreateParams request, @Nullable CreateEmbeddingResponse response) { if (response == null) { return null; } @@ -115,8 +122,8 @@ public Long getUsageInputTokens(EmbeddingCreateParams request, @Nullable @Override - public Long getUsageOutputTokens(EmbeddingCreateParams request, - @Nullable CreateEmbeddingResponse response) { + public Long getUsageOutputTokens( + EmbeddingCreateParams request, @Nullable CreateEmbeddingResponse response) { return null; } } diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedEmbeddingService.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedEmbeddingService.java index b5b7d581018d..a40c512267cb 100644 --- a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedEmbeddingService.java +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedEmbeddingService.java @@ -1,6 +1,10 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.instrumentation.openai.v1_1; -import java.lang.reflect.Method; import com.openai.core.RequestOptions; import com.openai.models.embeddings.CreateEmbeddingResponse; import com.openai.models.embeddings.EmbeddingCreateParams; @@ -8,13 +12,15 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import java.lang.reflect.Method; final class InstrumentedEmbeddingService extends DelegatingInvocationHandler { private final Instrumenter instrumenter; - public InstrumentedEmbeddingService(EmbeddingService delegate, + public InstrumentedEmbeddingService( + EmbeddingService delegate, Instrumenter instrumenter) { super(delegate); this.instrumenter = instrumenter; diff --git a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedEmbeddingServiceAsync.java b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedEmbeddingServiceAsync.java index 1456e650422e..a0195d257186 100644 --- a/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedEmbeddingServiceAsync.java +++ b/instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedEmbeddingServiceAsync.java @@ -1,10 +1,14 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.instrumentation.openai.v1_1; import com.openai.core.RequestOptions; import com.openai.models.embeddings.CreateEmbeddingResponse; import com.openai.models.embeddings.EmbeddingCreateParams; import com.openai.services.async.EmbeddingServiceAsync; -import com.openai.services.blocking.EmbeddingService; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; @@ -16,8 +20,9 @@ final class InstrumentedEmbeddingServiceAsync private final Instrumenter instrumenter; - public InstrumentedEmbeddingServiceAsync(EmbeddingServiceAsync delegate, - Instrumenter instrumenter) { + public InstrumentedEmbeddingServiceAsync( + EmbeddingServiceAsync delegate, + Instrumenter instrumenter) { super(delegate); this.instrumenter = instrumenter; } 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 295c2dd75b85..4fd816b5b7cb 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 @@ -49,9 +49,17 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl delegate.chat(), chatInstrumenter, eventLogger, captureMessageContent) .createProxy(); } + if (methodName.equals("embeddings") && parameterTypes.length == 0) { + return new InstrumentedEmbeddingService(delegate.embeddings(), embeddingInstrumenter) + .createProxy(); + } if (methodName.equals("async") && parameterTypes.length == 0) { return new InstrumentedOpenAiClientAsync( - delegate.async(), chatInstrumenter, eventLogger, captureMessageContent) + delegate.async(), + chatInstrumenter, + embeddingInstrumenter, + eventLogger, + captureMessageContent) .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 bc7635590c79..bace0b179a1d 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 @@ -8,6 +8,8 @@ import com.openai.client.OpenAIClientAsync; import com.openai.models.chat.completions.ChatCompletion; import com.openai.models.chat.completions.ChatCompletionCreateParams; +import com.openai.models.embeddings.CreateEmbeddingResponse; +import com.openai.models.embeddings.EmbeddingCreateParams; import io.opentelemetry.api.logs.Logger; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import java.lang.reflect.Method; @@ -16,16 +18,19 @@ final class InstrumentedOpenAiClientAsync extends DelegatingInvocationHandler { private final Instrumenter chatInstrumenter; + private final Instrumenter embeddingInstrumenter; private final Logger eventLogger; private final boolean captureMessageContent; InstrumentedOpenAiClientAsync( OpenAIClientAsync delegate, Instrumenter chatInstrumenter, + Instrumenter embeddingInstrumenter, Logger eventLogger, boolean captureMessageContent) { super(delegate); this.chatInstrumenter = chatInstrumenter; + this.embeddingInstrumenter = embeddingInstrumenter; this.eventLogger = eventLogger; this.captureMessageContent = captureMessageContent; } @@ -44,9 +49,17 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl delegate.chat(), chatInstrumenter, eventLogger, captureMessageContent) .createProxy(); } + if (methodName.equals("embeddings") && parameterTypes.length == 0) { + return new InstrumentedEmbeddingServiceAsync(delegate.embeddings(), embeddingInstrumenter) + .createProxy(); + } if (methodName.equals("sync") && parameterTypes.length == 0) { return new InstrumentedOpenAiClient( - delegate.sync(), chatInstrumenter, eventLogger, captureMessageContent) + delegate.sync(), + chatInstrumenter, + embeddingInstrumenter, + eventLogger, + captureMessageContent) .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 5c739334bd57..f6528eec741f 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 @@ -58,7 +58,7 @@ public OpenAIClient wrap(OpenAIClient client) { /** Wraps the provided OpenAIClientAsync, enabling telemetry for it. */ public OpenAIClientAsync wrap(OpenAIClientAsync client) { return new InstrumentedOpenAiClientAsync( - client, chatInstrumenter, eventLogger, captureMessageContent) + client, chatInstrumenter, embeddingsInstrumenter, eventLogger, captureMessageContent) .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 a9b2c8b4596c..6f4f117e961a 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 @@ -60,11 +60,13 @@ public OpenAITelemetry build() { openTelemetry, INSTRUMENTATION_NAME, GenAiSpanNameExtractor.create(EmbeddingAttributesGetter.INSTANCE)) - .addAttributesExtractor(GenAiAttributesExtractor.create(EmbeddingAttributesGetter.INSTANCE)) + .addAttributesExtractor( + GenAiAttributesExtractor.create(EmbeddingAttributesGetter.INSTANCE)) .addOperationMetrics(GenAiClientMetrics.get()) .buildInstrumenter(); Logger eventLogger = openTelemetry.getLogsBridge().get(INSTRUMENTATION_NAME); - return new OpenAITelemetry(chatInstrumenter, embeddingsInstrumenter, eventLogger, captureMessageContent); + return new OpenAITelemetry( + chatInstrumenter, embeddingsInstrumenter, eventLogger, captureMessageContent); } } diff --git a/instrumentation/openai/openai-java-1.1/library/src/test/java/io/opentelemetry/instrumentation/openai/v1_1/EmbeddingsTest.java b/instrumentation/openai/openai-java-1.1/library/src/test/java/io/opentelemetry/instrumentation/openai/v1_1/EmbeddingsTest.java index b7887bb15323..0e7870933fbf 100644 --- a/instrumentation/openai/openai-java-1.1/library/src/test/java/io/opentelemetry/instrumentation/openai/v1_1/EmbeddingsTest.java +++ b/instrumentation/openai/openai-java-1.1/library/src/test/java/io/opentelemetry/instrumentation/openai/v1_1/EmbeddingsTest.java @@ -1,16 +1,21 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.instrumentation.openai.v1_1; import static java.util.Collections.singletonList; -import java.util.List; -import java.util.function.Consumer; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.extension.RegisterExtension; import com.openai.client.OpenAIClient; import com.openai.client.OpenAIClientAsync; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import java.util.List; +import java.util.function.Consumer; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.RegisterExtension; class EmbeddingsTest extends AbstractEmbeddingsTest { 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 e1f11a464405..a3e8dc56ff9b 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 @@ -62,9 +62,6 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.context.Context; -import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import io.opentelemetry.instrumentation.testing.recording.RecordingExtension; -import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -73,28 +70,10 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletionException; -import java.util.function.Consumer; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.params.Parameter; -import org.junit.jupiter.params.ParameterizedClass; -import org.junit.jupiter.params.provider.EnumSource; - -@ParameterizedClass -@EnumSource(AbstractChatTest.TestType.class) -public abstract class AbstractChatTest { - enum TestType { - SYNC, - SYNC_FROM_ASYNC, - ASYNC, - ASYNC_FROM_SYNC, - } - - protected static final String INSTRUMENTATION_NAME = "io.opentelemetry.openai-java-1.1"; - - private static final String API_URL = "https://api.openai.com/v1"; +public abstract class AbstractChatTest extends AbstractOpenAiTest { protected static final AttributeKey EVENT_NAME = AttributeKey.stringKey("event.name"); protected static final String TEST_CHAT_MODEL = "gpt-4o-mini"; @@ -102,49 +81,6 @@ enum TestType { protected static final String TEST_CHAT_INPUT = "Answer in up to 3 words: Which ocean contains Bouvet Island?"; - @RegisterExtension static final RecordingExtension recording = new RecordingExtension(API_URL); - - protected abstract InstrumentationExtension getTesting(); - - protected abstract OpenAIClient wrap(OpenAIClient client); - - protected abstract OpenAIClientAsync wrap(OpenAIClientAsync client); - - protected final OpenAIClient getRawClient() { - OpenAIOkHttpClient.Builder builder = - OpenAIOkHttpClient.builder().baseUrl("http://localhost:" + recording.getPort()); - if (recording.isRecording()) { - builder.apiKey(System.getenv("OPENAI_API_KEY")); - } else { - builder.apiKey("unused"); - } - return builder.build(); - } - - protected final OpenAIClientAsync getRawClientAsync() { - OpenAIOkHttpClientAsync.Builder builder = - OpenAIOkHttpClientAsync.builder().baseUrl("http://localhost:" + recording.getPort()); - if (recording.isRecording()) { - builder.apiKey(System.getenv("OPENAI_API_KEY")); - } else { - builder.apiKey("unused"); - } - return builder.build(); - } - - protected final OpenAIClient getClient() { - return wrap(getRawClient()); - } - - protected final OpenAIClientAsync getClientAsync() { - return wrap(getRawClientAsync()); - } - - protected abstract List> maybeWithTransportSpan( - Consumer span); - - @Parameter TestType testType; - protected final ChatCompletion doCompletions(ChatCompletionCreateParams params) { return doCompletions(params, getClient(), getClientAsync()); } diff --git a/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractEmbeddingsTest.java b/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractEmbeddingsTest.java index 2e1044a9e63e..7df8f0f9e7c3 100644 --- a/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractEmbeddingsTest.java +++ b/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractEmbeddingsTest.java @@ -7,6 +7,7 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_OPERATION_NAME; +import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_REQUEST_ENCODING_FORMATS; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_REQUEST_MODEL; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_RESPONSE_MODEL; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_SYSTEM; @@ -15,17 +16,21 @@ import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiOperationNameIncubatingValues.EMBEDDINGS; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiSystemIncubatingValues.OPENAI; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiTokenTypeIncubatingValues.INPUT; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; import com.openai.client.OpenAIClient; import com.openai.client.OpenAIClientAsync; +import com.openai.client.okhttp.OpenAIOkHttpClient; +import com.openai.client.okhttp.OpenAIOkHttpClientAsync; +import com.openai.errors.OpenAIIoException; import com.openai.models.embeddings.CreateEmbeddingResponse; import com.openai.models.embeddings.EmbeddingCreateParams; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.testing.recording.RecordingExtension; -import java.util.Collections; import java.util.concurrent.CompletionException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -79,7 +84,7 @@ void basic() { EmbeddingCreateParams request = EmbeddingCreateParams.builder() .model(MODEL) - .inputOfArrayOfStrings(Collections.singletonList(text)) + .inputOfArrayOfStrings(singletonList(text)) .build(); CreateEmbeddingResponse response = doEmbeddings(request); @@ -112,8 +117,8 @@ void basic() { point .hasSumGreaterThan(0.0) .hasAttributesSatisfyingExactly( - equalTo(GEN_AI_SYSTEM, "openai"), - equalTo(GEN_AI_OPERATION_NAME, "chat"), + equalTo(GEN_AI_SYSTEM, OPENAI), + equalTo(GEN_AI_OPERATION_NAME, EMBEDDINGS), equalTo(GEN_AI_REQUEST_MODEL, MODEL), equalTo(GEN_AI_RESPONSE_MODEL, MODEL)))), metric -> @@ -126,10 +131,130 @@ void basic() { point .hasSum(4.0) .hasAttributesSatisfyingExactly( - equalTo(GEN_AI_SYSTEM, "openai"), - equalTo(GEN_AI_OPERATION_NAME, "chat"), + equalTo(GEN_AI_SYSTEM, OPENAI), + equalTo(GEN_AI_OPERATION_NAME, EMBEDDINGS), equalTo(GEN_AI_REQUEST_MODEL, MODEL), equalTo(GEN_AI_RESPONSE_MODEL, MODEL), equalTo(GEN_AI_TOKEN_TYPE, INPUT))))); } + + @Test + void allTheClientOptions() { + String text = "South Atlantic Ocean."; + + EmbeddingCreateParams request = + EmbeddingCreateParams.builder() + .model(MODEL) + .encodingFormat(EmbeddingCreateParams.EncodingFormat.BASE64) + .inputOfArrayOfStrings(singletonList(text)) + .build(); + CreateEmbeddingResponse response = doEmbeddings(request); + + assertThat(response.data()).hasSize(1); + + getTesting() + .waitAndAssertTraces( + trace -> + maybeWithTransportSpan( + span -> + span.hasName("embeddings text-embedding-3-small") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(GEN_AI_SYSTEM, OPENAI), + equalTo(GEN_AI_OPERATION_NAME, EMBEDDINGS), + equalTo(GEN_AI_REQUEST_MODEL, MODEL), + equalTo(GEN_AI_REQUEST_ENCODING_FORMATS, singletonList("base64")), + equalTo(GEN_AI_RESPONSE_MODEL, MODEL), + equalTo(GEN_AI_USAGE_INPUT_TOKENS, 4)))); + + getTesting() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + metric -> + metric + .hasName("gen_ai.client.operation.duration") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSumGreaterThan(0.0) + .hasAttributesSatisfyingExactly( + equalTo(GEN_AI_SYSTEM, OPENAI), + equalTo(GEN_AI_OPERATION_NAME, EMBEDDINGS), + equalTo(GEN_AI_REQUEST_MODEL, MODEL), + equalTo(GEN_AI_RESPONSE_MODEL, MODEL)))), + metric -> + metric + .hasName("gen_ai.client.token.usage") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(4.0) + .hasAttributesSatisfyingExactly( + equalTo(GEN_AI_SYSTEM, OPENAI), + equalTo(GEN_AI_OPERATION_NAME, EMBEDDINGS), + equalTo(GEN_AI_REQUEST_MODEL, MODEL), + equalTo(GEN_AI_RESPONSE_MODEL, MODEL), + equalTo(GEN_AI_TOKEN_TYPE, INPUT))))); + } + + @Test + void connectionError() { + OpenAIClient client = + wrap( + OpenAIOkHttpClient.builder() + .baseUrl("http://localhost:9999/v5") + .apiKey("testing") + .maxRetries(0) + .build()); + OpenAIClientAsync clientAsync = + wrap( + OpenAIOkHttpClientAsync.builder() + .baseUrl("http://localhost:9999/v5") + .apiKey("testing") + .maxRetries(0) + .build()); + + EmbeddingCreateParams request = + EmbeddingCreateParams.builder() + .model(MODEL) + .inputOfArrayOfStrings(singletonList(INPUT)) + .build(); + + Throwable thrown = catchThrowable(() -> doEmbeddings(request, client, clientAsync)); + assertThat(thrown).isInstanceOf(OpenAIIoException.class); + + getTesting() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + maybeWithTransportSpan( + span -> + span.hasName("embeddings text-embedding-3-small") + .hasException(thrown) + .hasAttributesSatisfyingExactly( + equalTo(GEN_AI_SYSTEM, OPENAI), + equalTo(GEN_AI_OPERATION_NAME, EMBEDDINGS), + equalTo(GEN_AI_REQUEST_MODEL, MODEL))))); + + getTesting() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + metric -> + metric + .hasName("gen_ai.client.operation.duration") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSumGreaterThan(0.0) + .hasAttributesSatisfyingExactly( + equalTo(GEN_AI_SYSTEM, OPENAI), + equalTo(GEN_AI_OPERATION_NAME, EMBEDDINGS), + equalTo(GEN_AI_REQUEST_MODEL, MODEL))))); + } } diff --git a/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractOpenAiTest.java b/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractOpenAiTest.java index 2a630d289f15..bd2614ef7543 100644 --- a/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractOpenAiTest.java +++ b/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractOpenAiTest.java @@ -17,8 +17,10 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.Parameter; import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.EnumSource; @ParameterizedClass +@EnumSource(AbstractOpenAiTest.TestType.class) abstract class AbstractOpenAiTest { enum TestType { SYNC, diff --git a/instrumentation/openai/openai-java-1.1/testing/src/main/resources/mappings/io.opentelemetry.instrumentation.openai.v1_1.abstractembeddingstest.alltheclientoptions.yaml b/instrumentation/openai/openai-java-1.1/testing/src/main/resources/mappings/io.opentelemetry.instrumentation.openai.v1_1.abstractembeddingstest.alltheclientoptions.yaml new file mode 100644 index 000000000000..f5017d89df4d --- /dev/null +++ b/instrumentation/openai/openai-java-1.1/testing/src/main/resources/mappings/io.opentelemetry.instrumentation.openai.v1_1.abstractembeddingstest.alltheclientoptions.yaml @@ -0,0 +1,62 @@ +--- +id: 100ca9fa-bca3-4922-8287-4da475c9ce69 +name: embeddings +request: + url: /embeddings + method: POST + bodyPatterns: + - equalToJson: |- + { + "input" : [ "South Atlantic Ocean." ], + "model" : "text-embedding-3-small", + "encoding_format" : "base64" + } + ignoreArrayOrder: false + ignoreExtraElements: false +response: + status: 200 + body: | + { + "object": "list", + "data": [ + { + "object": "embedding", + "index": 0, + "embedding": "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" + } + ], + "model": "text-embedding-3-small", + "usage": { + "prompt_tokens": 4, + "total_tokens": 4 + } + } + headers: + Date: "Fri, 25 Jul 2025 06:40:50 GMT" + Content-Type: application/json + access-control-allow-origin: '*' + access-control-expose-headers: X-Request-ID + openai-model: text-embedding-3-small + openai-organization: test_organization + openai-processing-ms: "94" + openai-project: proj_Pf1eM5R55Z35wBy4rt8PxAGq + openai-version: 2020-10-01 + strict-transport-security: max-age=31536000; includeSubDomains; preload + via: envoy-router-7f8f4c5dfc-gfmqg + x-envoy-upstream-service-time: "103" + x-ratelimit-limit-requests: "5000" + x-ratelimit-limit-tokens: "5000000" + x-ratelimit-remaining-requests: "4999" + x-ratelimit-remaining-tokens: "4999994" + x-ratelimit-reset-requests: 12ms + x-ratelimit-reset-tokens: 0s + x-request-id: req_4960c59d7e3e1254fd4fa8f21808c6ae + cf-cache-status: DYNAMIC + Set-Cookie: test_set_cookie + X-Content-Type-Options: nosniff + Server: cloudflare + CF-RAY: 9649ab0aac86263c-NRT + alt-svc: h3=":443"; ma=86400 +uuid: 100ca9fa-bca3-4922-8287-4da475c9ce69 +persistent: true +insertionIndex: 38 diff --git a/instrumentation/openai/openai-java-1.1/testing/src/main/resources/mappings/io.opentelemetry.instrumentation.openai.v1_1.abstractembeddingstest.basic.yaml b/instrumentation/openai/openai-java-1.1/testing/src/main/resources/mappings/io.opentelemetry.instrumentation.openai.v1_1.abstractembeddingstest.basic.yaml new file mode 100644 index 000000000000..5e36d3dff18c --- /dev/null +++ b/instrumentation/openai/openai-java-1.1/testing/src/main/resources/mappings/io.opentelemetry.instrumentation.openai.v1_1.abstractembeddingstest.basic.yaml @@ -0,0 +1,1598 @@ +--- +id: 227a6023-72ed-4209-ab5d-a90c31f47de0 +name: embeddings +request: + url: /embeddings + method: POST + bodyPatterns: + - equalToJson: |- + { + "input" : [ "South Atlantic Ocean." ], + "model" : "text-embedding-3-small" + } + ignoreArrayOrder: false + ignoreExtraElements: false +response: + status: 200 + body: | + { + "object": "list", + "data": [ + { + "object": "embedding", + "index": 0, + "embedding": [ + -0.031545512, + -0.030183835, + 0.010292007, + 0.009111887, + -0.030864673, + 0.06481581, + 0.036016352, + -0.0019474814, + -0.0046864375, + -0.02102656, + 0.0026651986, + -0.010916109, + -0.018723056, + -0.012096229, + 0.014989791, + 0.024464794, + -0.024260541, + 0.018893266, + 0.01660111, + 0.023193894, + -0.028232098, + -0.035585154, + -0.06281869, + 0.061093897, + 0.020345721, + -0.014978444, + 0.008243818, + 0.018246468, + -0.06368108, + 0.025939943, + 0.10512145, + -0.0307512, + 0.00093686196, + 0.0657236, + 0.03930707, + 0.06862851, + -0.020425152, + -0.011733115, + -0.055874135, + 0.008209776, + 0.029003715, + -0.055556413, + 0.0033446185, + 0.0019744313, + 0.008476438, + 0.005350255, + -0.06871929, + -0.031704374, + 0.035040483, + 0.014308954, + -0.034132697, + 0.028459044, + -0.032407906, + -0.04309707, + 0.043165155, + -0.025758386, + -0.013560032, + 0.0026949854, + 0.06921857, + -0.0004801329, + -0.047023237, + 0.048838805, + -0.0015942965, + -0.005137493, + -0.046160843, + -0.002419813, + 0.014059313, + 0.061366234, + -0.056055695, + -0.019767009, + -0.048430305, + 0.022093205, + 0.024124373, + -0.03640216, + -0.053831622, + -0.014853625, + -0.014728804, + -0.03626599, + -0.001547489, + -0.034450423, + -0.029502997, + 0.06281869, + 0.021503145, + -0.012391259, + -0.01554581, + -0.04259779, + 0.016237995, + 0.008425375, + -0.018393984, + 0.026530003, + -0.032748327, + -0.020776918, + -0.020992517, + -0.010462217, + -0.010093429, + 0.039125513, + -0.009418264, + 0.012743025, + 0.024124373, + -0.0049814675, + -0.024169764, + -0.026121499, + 0.020799613, + -0.014025271, + -0.028322877, + -0.024805212, + 0.0018623767, + 0.01011045, + 0.012527427, + -0.011381349, + -0.009066498, + 0.029434914, + -0.0016184095, + 0.018927308, + -0.00461268, + -0.02110599, + 0.0029162578, + -0.06449809, + 0.055964917, + 0.0053162132, + 0.008124672, + -0.036583718, + 0.02132159, + -0.014047965, + -0.037082996, + -0.011613968, + -0.025168326, + -0.010252291, + 0.0067970366, + 0.0030580992, + 0.009531737, + -0.012527427, + -0.016067786, + -0.030978147, + 0.020062039, + 0.07107953, + -0.006428249, + -0.021673355, + 0.0012602602, + 0.011994103, + 0.022944253, + 0.027914373, + 0.039238986, + 0.0034751126, + 0.020118775, + -0.004414102, + -0.05564719, + 0.027120063, + 0.020130122, + -0.03846737, + 0.013253654, + -0.0049275677, + 0.003892126, + 0.025803775, + -0.0047375006, + -0.012924582, + -0.0036595063, + 0.0055176276, + 0.011744462, + -0.0017446483, + 0.0027687429, + -0.023023685, + 0.043074377, + 0.023783954, + -0.016816707, + -0.008493459, + 0.006689237, + 0.0155911995, + -0.052016053, + -0.0115004955, + -0.008187082, + -0.02068614, + -0.025599523, + -0.004955936, + -0.010501932, + -0.035744015, + 0.009429611, + -0.031772457, + -0.01815569, + 0.016782666, + -0.0212762, + 0.0022680429, + 0.035880182, + -0.007114761, + 0.0068480996, + 0.035902876, + 0.010331723, + -0.010445195, + -0.004204177, + -0.009480675, + 0.019188296, + -0.024918685, + 0.07067102, + 0.015750062, + -0.0069332044, + -0.04645587, + 0.014013924, + 0.044980723, + -0.008226797, + 0.033497248, + 0.028232098, + -0.023012338, + -0.033633415, + -0.060186114, + -0.021786828, + -0.033179525, + -0.053740844, + 0.021208115, + 0.055193298, + 0.0005897053, + 0.016317427, + -0.018654972, + 0.02607611, + 0.019880481, + -0.026575392, + -0.003236819, + -0.016657846, + 0.0096905995, + 0.023488924, + -0.012391259, + -0.036039047, + 0.013889103, + 0.01579545, + 0.026303057, + -0.0133103905, + 0.051380605, + 0.056418806, + -0.022467667, + 0.009571453, + -0.021798175, + 0.027029283, + 0.017474853, + -0.006496333, + -0.034813534, + -0.051698327, + -0.04039641, + -0.0058608837, + 0.011545884, + 0.021242158, + 0.009866483, + -0.028844854, + -0.014342995, + 0.05101749, + 0.029843416, + -0.013423864, + -0.020266289, + 0.046365093, + 0.039080124, + -0.016226647, + 0.050155096, + -0.008226797, + -0.01495575, + 0.025849164, + 0.010848025, + 0.010581363, + -0.009480675, + -0.050790545, + -0.05296923, + -0.03989713, + -0.01584084, + -0.045593478, + -0.022932906, + -0.048203357, + 0.010082082, + 0.024714433, + -0.0062069767, + 0.033111442, + 0.006167261, + -0.034178086, + -0.021559883, + 0.0032651874, + 0.037627667, + 0.006314776, + 0.0039999257, + 0.028073236, + 0.035063177, + 0.024691738, + 0.01933581, + 0.011545884, + -0.004453818, + 0.0278009, + -0.056101084, + -0.06685833, + 0.012674942, + -0.012561468, + -0.015647935, + 0.008589911, + -0.027437788, + 0.038989346, + 0.032997966, + 0.081428275, + 0.024805212, + -0.020652099, + -0.015999703, + -0.026030721, + 0.006314776, + 0.021332936, + -0.0018212427, + -0.027937068, + -0.0036708536, + 0.054194737, + -0.019959912, + 0.0136054205, + -0.016498983, + 0.008391333, + 0.03533551, + -0.0463424, + 0.016079133, + 0.023897428, + 0.011006887, + 0.020459194, + 0.0009141674, + 0.034836233, + -0.0174862, + 0.03699222, + -0.0119714085, + -0.054875575, + -0.0031517143, + 0.0035744016, + -0.027142758, + 0.012028145, + 0.024328625, + -0.024964074, + 0.012028145, + -0.0003819078, + -0.046183538, + -0.025304493, + -0.023670482, + 0.024396708, + -0.015182696, + 0.0018510293, + 0.0066041322, + 0.04368713, + -0.009934567, + 0.02216129, + -0.039624795, + -0.032748327, + 0.028345572, + -0.03533551, + 0.06059462, + -0.0092196865, + -0.018348595, + 0.02245632, + 0.02519102, + -0.010013998, + -0.009633863, + -0.018541498, + 0.014411079, + 0.07543689, + -0.015500421, + 0.008680689, + 0.0056509585, + -0.005843863, + -0.010189882, + -0.023647787, + 0.030546948, + 0.033088747, + -0.034768146, + -0.06876468, + 0.021242158, + -0.032589465, + 0.0036027697, + -0.03168168, + 0.00081700605, + 0.04511689, + -0.024124373, + -0.00514884, + 0.0021957038, + 0.00821545, + -0.011568579, + -0.03572132, + 0.047976412, + -0.052061442, + 0.0191656, + -0.0035942593, + 0.043414794, + -0.051743716, + 0.0015148654, + -0.032589465, + -0.0012815364, + -0.044549525, + 0.052469946, + -0.027460482, + 0.0049672835, + 0.029366829, + -0.052560724, + 0.0033247608, + -0.029979583, + -0.00431765, + -0.026098805, + -0.038490064, + -0.026802339, + 0.034700062, + 0.002731864, + -0.0032963925, + 0.008714732, + 0.042348146, + -0.018087607, + -0.038217727, + 0.048838805, + 0.034246173, + -0.027347008, + -0.0063091023, + -0.02283078, + 0.00916295, + 0.015125959, + -0.057235815, + 0.025667608, + 0.005594222, + -0.014569942, + -0.00093969883, + -0.05414935, + -0.0037673058, + 0.03383767, + 0.00007686027, + 0.040351022, + -0.015477726, + 0.05156216, + 0.0044282866, + 0.025667608, + -0.005012673, + 0.055374857, + -0.0075346115, + -0.0042495662, + 0.011937367, + 0.072895095, + 0.034677368, + -0.030342698, + 0.014184133, + 0.015772756, + 0.056010306, + -0.0103090275, + 0.007557306, + 0.030138446, + -0.016986918, + 0.024759823, + -0.0069956146, + 0.012856498, + -0.031068925, + 0.011245181, + 0.0037843266, + 0.020311678, + 0.0007595603, + 0.008709058, + -0.008039567, + -0.0019616657, + 0.03277102, + 0.021378325, + -0.054694016, + -0.017917397, + 0.053740844, + -0.0049956515, + -0.0059289676, + 0.012743025, + 0.028663296, + -0.019449282, + -0.008657995, + 0.027460482, + 0.012799761, + 0.045979287, + 0.008397006, + -0.016691888, + -0.054648627, + 0.011074971, + 0.029843416, + 0.02809593, + 0.000699987, + 0.0661321, + -0.0052112504, + -0.008703384, + -0.022115901, + -0.009214013, + 0.028844854, + 0.014762846, + -0.019676229, + 0.0036566695, + -0.007727516, + -0.020413805, + -0.043324016, + -0.0315909, + -0.002432579, + -0.006473638, + -0.028663296, + -0.0140025765, + -0.018393984, + -0.029071799, + -0.004746011, + 0.013718894, + -0.042824734, + -0.026121499, + 0.022740003, + 0.031704374, + -0.008113324, + -0.0083516175, + 0.034858927, + -0.04107725, + -0.04516228, + -0.021922996, + 0.05296923, + -0.0492927, + 0.042075813, + 0.0046240273, + -0.011778505, + 0.0057190424, + 0.07566384, + -0.04473108, + -0.016816707, + -0.053196173, + -0.025803775, + 0.015239432, + -0.0011248018, + 0.04028294, + -0.017542936, + -0.03556246, + -0.007194192, + -0.0044424706, + -0.0121189235, + 0.025576828, + -0.0015630914, + -0.0014680577, + -0.0076651056, + -0.021253506, + -0.019245032, + -0.0025815123, + -0.045888506, + -0.0011730278, + -0.028663296, + -0.0064566173, + -0.021151379, + 0.037173778, + 0.009429611, + 0.027029283, + -0.015352906, + -0.007960135, + -0.026734253, + -0.021491798, + -0.04039641, + 0.014581289, + -0.01152319, + 0.034064613, + 0.009355854, + 0.015602547, + -0.014751499, + 0.027324313, + -0.008941677, + 0.006808384, + -0.018393984, + -0.008266512, + 0.043868687, + 0.023239283, + -0.019222338, + 0.00060424407, + 0.0140025765, + -0.0074551804, + -0.0028155504, + -0.03551707, + 0.008028219, + -0.017463505, + -0.013911798, + 0.013446558, + 0.029366829, + -0.016533025, + -0.022354193, + -0.00945798, + 0.008714732, + 0.057780486, + 0.016703235, + -0.01879114, + -0.00087516103, + 0.030342698, + 0.02355701, + 0.032839105, + -0.012618205, + -0.006592785, + 0.0066551953, + 0.023783954, + 0.033292998, + -0.017622367, + -0.014354343, + 0.0143997315, + -0.0229556, + -0.022615181, + -0.015307517, + 0.044163715, + -0.0062523657, + 0.011982756, + -0.023284674, + -0.004073683, + 0.032158267, + 0.0064509436, + 0.054694016, + -0.02809593, + -0.0137075465, + -0.023307368, + -0.021196768, + -0.00857289, + 0.04275665, + -0.008385659, + 0.012164312, + 0.042620484, + 0.018212426, + -0.024805212, + -0.017077696, + -0.004972957, + -0.034518506, + 0.00059112377, + 0.014558595, + -0.02371587, + -0.015557157, + 0.020527277, + 0.011267875, + 0.043664437, + 0.017724494, + -0.051244438, + 0.010802636, + 0.0132082645, + -0.0568727, + 0.033678807, + 0.006955899, + -0.017474853, + -0.0070750457, + 0.024464794, + 0.029798027, + 0.007977157, + -0.020447847, + -0.0039204946, + -0.023398146, + 0.052333776, + 0.0062069767, + 0.002241093, + -0.018405331, + 0.028504433, + 0.019074822, + 0.007381423, + -0.0072906446, + 0.018348595, + -0.01866632, + 0.024873296, + 0.053150784, + 0.00857289, + -0.032907188, + 0.02253575, + 0.014592636, + 0.0026538514, + 0.014252217, + -0.012232397, + 0.012436648, + -0.014672067, + -0.029003715, + 0.011846588, + 0.034495812, + 0.018393984, + 0.011188444, + -0.019540062, + -0.02911719, + -0.03374689, + 0.01778123, + -0.039080124, + -0.029207967, + -0.0043630395, + -0.0087431, + 0.0379227, + 0.018950002, + -0.04629701, + -0.015477726, + -0.0057530846, + -0.008385659, + -0.0016524515, + 0.027868984, + -0.043119766, + 0.03969288, + -0.009968609, + 0.042280063, + 0.0072736233, + 0.039738268, + -0.004794237, + 0.025849164, + -0.010944477, + 0.012958624, + 0.0005116926, + -0.03383767, + 0.022490362, + -0.0068480996, + 0.029661858, + -0.011245181, + -0.0013616767, + -0.012084882, + -0.011239507, + -0.009537411, + 0.047613297, + -0.027074674, + 0.04554809, + 0.022649223, + 0.017803924, + 0.014252217, + 0.018654972, + -0.020742876, + -0.0019488999, + 0.020368416, + -0.017168475, + 0.04516228, + -0.005174372, + 0.012096229, + 0.0074268123, + -0.006269387, + -0.012504731, + -0.007892052, + 0.0008233889, + 0.02039111, + 0.043460183, + 0.016657846, + 0.014864972, + -0.047613297, + 0.0050495514, + 0.0015148654, + -0.009140255, + -0.010779941, + 0.020663446, + -0.042189285, + -0.007131782, + 0.03606174, + 0.01004804, + -0.022989644, + 0.012981319, + 0.03247599, + 0.008595585, + 0.02405629, + 0.001938971, + 0.0011439504, + -0.005103451, + -0.016294733, + 0.017974133, + 0.00022730073, + 0.009775705, + 0.012652246, + 0.036606412, + -0.006967246, + -0.021514494, + 0.02076557, + 0.0037928373, + 0.013106139, + 0.0015262127, + 0.020266289, + 0.011948714, + 0.025145631, + -0.0030666096, + -0.01407066, + -0.015954314, + 0.047613297, + -0.003418376, + -0.029639164, + 0.005367276, + 0.016033744, + 0.023670482, + -0.0320221, + -0.014683414, + 0.01276572, + 0.012277786, + -0.034450423, + -0.0029843417, + -0.0055204644, + 0.018847875, + 0.040577967, + 0.017066348, + 0.020470541, + -0.00031293742, + -0.017633714, + 0.0086012585, + 0.052061442, + 0.019596798, + 0.016952876, + -0.025712997, + -0.0024836417, + 0.033179525, + -0.0039034735, + 0.0049701203, + -0.0073700757, + 0.022898864, + -0.017111737, + -0.0067062583, + -0.0015162838, + -0.030546948, + 0.008033893, + -0.002625483, + 0.0012779904, + 0.0015942965, + 0.0240109, + -0.008198429, + 0.03079659, + 0.004615517, + 0.0059573357, + -0.0014950077, + -0.029457608, + -0.01992587, + 0.058506712, + 0.033588026, + 0.033519942, + -0.044050243, + -0.03168168, + 0.015874881, + 0.031568207, + -0.008845225, + -0.0025304493, + -0.04039641, + 0.0028439187, + 0.051153656, + -0.0048623206, + 0.047204796, + -0.0108536985, + -0.0069332044, + -0.009735989, + 0.0007084974, + -0.0324306, + 0.020708835, + 0.033134136, + -0.007869357, + 0.024578266, + 0.008255165, + -0.011699073, + 0.014388384, + 0.024737129, + 0.011857935, + 0.024464794, + 0.0019205316, + 0.052878447, + 0.007767231, + 0.045003418, + -0.020720182, + 0.014978444, + -0.023114463, + 0.061230067, + 0.0070807193, + -0.025281798, + -0.0143997315, + 0.014240869, + -0.03803617, + -0.029480303, + -0.026507309, + -0.024986768, + 0.029230662, + 0.059913777, + 0.0019829418, + -0.013276349, + 0.00052800437, + 0.018859223, + 0.019483326, + 0.012845151, + -0.002519102, + 0.010689163, + -0.0094693275, + 0.013560032, + -0.013299043, + -0.030705811, + -0.032453295, + 0.025667608, + -0.022808086, + -0.0074665276, + -0.005307703, + -0.029888805, + -0.013026708, + -0.0037531217, + 0.010172861, + 0.008527501, + -0.03336108, + -0.018416679, + 0.015364253, + -0.008771468, + 0.02355701, + 0.020924434, + -0.009293444, + -0.00941259, + 0.036175214, + -0.044708386, + -0.023284674, + -0.013684852, + 0.007301992, + 0.021627966, + 0.020708835, + -0.0058325157, + 0.025100242, + 0.03669719, + 0.015057876, + -0.050291263, + 0.03733264, + -0.044504136, + 0.010677815, + 0.02194569, + 0.010439522, + -0.016567068, + 0.027868984, + 0.02283078, + 0.020720182, + 0.00030815028, + -0.0061048507, + -0.009974282, + -0.050018925, + -0.028549824, + -0.021503145, + 0.017792577, + -0.0018141506, + -0.016317427, + 0.020606708, + 0.0019162764, + -0.04023755, + 0.005693511, + 0.028617907, + 0.011080645, + 0.0013269257, + 0.043868687, + 0.021673355, + -0.040078685, + 0.031727068, + 0.024215152, + 0.012663594, + 0.031908628, + 0.0034751126, + -0.0023971186, + -0.016794013, + 0.0046892744, + -0.00278009, + 0.024827907, + -0.009004088, + -0.021854913, + -0.025803775, + 0.013809672, + 0.0017489037, + -0.011960061, + 0.0050608986, + -0.029502997, + -0.01112036, + 0.010831004, + -0.0013552939, + 0.0164309, + 0.003997089, + 0.00021400311, + -0.025145631, + 0.0320221, + 0.002151733, + 0.011188444, + -0.008941677, + 0.049655814, + -0.005611243, + 0.028050542, + -0.030524254, + -0.011489148, + -0.023148505, + 0.013469253, + 0.032929882, + 0.007018309, + -0.037900005, + 0.026643476, + 0.028481739, + 0.03635677, + -0.0034637654, + -0.011914671, + 0.005741737, + 0.03184054, + 0.0054495437, + 0.002772998, + -0.058733657, + 0.0021886118, + 0.003449581, + 0.01638551, + -0.0057928, + 0.0015233759, + -0.0060651354, + -0.029775333, + 0.002021239, + 0.053786233, + -0.046750903, + -0.012130271, + -0.01828051, + 0.051380605, + 0.047658686, + -0.0036708536, + 0.02843635, + 0.007835316, + 0.0041360934, + -0.0366518, + -0.01778123, + -0.0051119616, + 0.027188146, + -0.024124373, + -0.03635677, + -0.003858084, + 0.005906273, + -0.009917546, + 0.025009463, + 0.0231712, + -0.027392399, + 0.0008198429, + -0.03415539, + 0.03383767, + 0.029185273, + 0.018178385, + -0.008828204, + 0.016181258, + -0.0036680168, + -0.02132159, + 0.0030382413, + -0.015523115, + -0.012379912, + -0.012334522, + -0.014059313, + -0.0073870965, + 0.022853475, + -0.017588325, + 0.006133219, + 0.014445121, + -0.019279074, + -0.035744015, + 0.03690144, + 0.024419403, + 0.025145631, + 0.024374014, + -0.054240126, + 0.02342084, + -0.003682201, + -0.059686832, + 0.022864822, + 0.053650066, + 0.006484986, + 0.044458747, + -0.009401243, + -0.031182399, + 0.00443396, + 0.0025985332, + 0.00544387, + 0.053604677, + -0.020323025, + -0.010831004, + -0.015886229, + 0.0009014017, + 0.017894702, + 0.010178534, + 0.027959764, + -0.010257965, + -0.0053559286, + 0.002872287, + 0.063544914, + 0.013423864, + -0.003469439, + -0.0056878375, + -0.0063601653, + -0.014354343, + 0.017872008, + -0.008465091, + 0.000423751, + -0.017645061, + -0.011665031, + 0.00068012916, + 0.011438085, + 0.030115752, + -0.011846588, + -0.05859749, + 0.013355779, + 0.017372726, + -0.025054853, + 0.0076026954, + 0.022127248, + -0.0023162689, + 0.012334522, + -0.00016976634, + -0.024510182, + -0.009435285, + 0.015897576, + 0.028776769, + 0.023375452, + -0.047794856, + 0.0064395964, + 0.025826469, + 0.04393677, + 0.008181408, + 0.015818145, + -0.040623356, + -0.021378325, + -0.019687576, + -0.006774342, + 0.016124522, + 0.06440731, + -0.028799463, + 0.04300629, + 0.0039034735, + -0.011755809, + -0.010121797, + 0.024759823, + -0.033928446, + -0.00703533, + -0.030615034, + -0.017179823, + -0.0104849115, + 0.024555571, + -0.0037559585, + 0.04355096, + 0.0017971296, + -0.01882518, + -0.005398481, + 0.016056439, + -0.00039928334, + -0.009276423, + 0.017837966, + -0.0041360934, + -0.006819731, + 0.031727068, + -0.019233685, + 0.008595585, + -0.011846588, + 0.04019216, + 0.003449581, + -0.019755661, + -0.02186626, + -0.040759526, + 0.0009155858, + -0.037786532, + -0.0022113062, + -0.02784629, + -0.029457608, + -0.003997089, + -0.027029283, + 0.020198205, + 0.01803087, + -0.025690302, + -0.003957373, + 0.002828316, + -0.010910436, + -0.0040169465, + 0.0043119765, + 0.017667755, + -0.027891679, + -0.018008176, + 0.0034070287, + -0.014762846, + 0.027573954, + -0.0045559434, + 0.01601105, + 0.007148803, + -0.007931767, + -0.016101828, + -0.031137008, + 0.043278627, + 0.03980635, + 0.03572132, + 0.018110301, + 0.0057956367, + 0.018167038, + 0.029139884, + 0.028890243, + 0.09368336, + 0.013741588, + -0.031137008, + -0.020947129, + 0.0307512, + -0.008953025, + -0.021968385, + 0.025168326, + 0.014274912, + 0.015216738, + 0.013843714, + 0.0053162132, + 0.019596798, + 0.0011389859, + -0.00067303714, + 0.033519942, + 0.042915512, + 0.0018467741, + 0.050745156, + -0.012277786, + 0.0032453297, + -0.025735691, + -0.02941222, + 0.03501779, + 0.0121189235, + 0.020879043, + 0.0005205577, + -0.019687576, + 0.017849313, + -0.011069298, + 0.00857289, + -0.02911719, + 0.004896363, + 0.02014147, + -0.04865725, + 0.03079659, + -0.015398295, + 0.0006397044, + -0.02578108, + 0.03556246, + 0.0010772849, + -0.01247069, + -0.02553144, + -0.011630989, + -0.009429611, + -0.0044424706, + -0.00031080982, + -0.003242493, + -0.015307517, + 0.002941789, + -0.012822457, + 0.045184974, + -0.0015120286, + 0.0143997315, + -0.040078685, + -0.020561319, + 0.0017829455, + -0.023375452, + 0.0149444025, + -0.023579704, + 0.012436648, + 0.007182845, + 0.026552698, + 0.06799306, + 0.002178683, + -0.023534313, + -0.023398146, + -0.008493459, + -0.011744462, + -0.015954314, + -0.004346018, + -0.012822457, + 0.020447847, + -0.014252217, + 0.006331797, + 0.005216924, + 0.037900005, + -0.032044794, + -0.041485753, + 0.042075813, + -0.018393984, + -0.014581289, + 0.038308505, + 0.0010808309, + 0.031159703, + -0.04448144, + -0.0042580767, + -0.00319143, + 0.010286333, + -0.033270303, + -0.012538774, + 0.007460854, + 0.006031093, + 0.0079544615, + 0.018098954, + 0.014842276, + 0.0007269368, + 0.05691809, + 0.0014581289, + 0.03883048, + 0.03129587, + 0.0061559137, + -0.028050542, + -0.014161439, + 0.015511768, + -0.044935334, + 0.025032159, + -0.020130122, + -0.011086319, + 0.016964223, + 0.0011709002, + 0.027074674, + -0.0043914076, + -0.016555721, + 0.003616954, + 0.007835316, + 0.011364328, + 0.016113175, + -0.018598236, + 0.015534462, + -0.00230634, + -0.027642038, + 0.04237084, + -0.02941222, + 0.0011957224, + 0.047023237, + 0.008062261, + 0.0421212, + -0.03576671, + 0.040668745, + 0.00257442, + -0.010660795, + -0.0032510033, + 0.00095459213, + 0.008374312, + 0.036379464, + 0.0067629945, + -0.0061275456, + -0.011994103, + -0.0021418042, + -0.003060936, + 0.005055225, + -0.021854913, + -0.03336108, + 0.018598236, + -0.04915653, + 0.022059163, + -0.005367276, + -0.0033020661, + -0.020357069, + -0.016612457, + 0.005236782, + -0.027392399, + 0.023216588, + 0.016464941, + -0.015352906, + -0.014933055, + -0.0078807045, + 0.008408354, + 0.020459194, + 0.027596649, + 0.011364328, + -0.032793716, + -0.024737129, + 0.034064613, + -0.0011340214, + 0.031341262, + 0.016294733, + -0.03508587, + 0.019869134, + -0.03256677, + -0.025054853, + -0.015273474, + 0.027596649, + 0.014013924, + 0.020334372, + 0.012674942, + 0.014308954, + -0.0002664844, + -0.004036804, + 0.0024382526, + 0.006042441, + -0.017089043, + -0.0030921411, + -0.012493384, + 0.0021772645, + 0.026847728, + -0.0044992073, + -0.016964223, + 0.012538774, + 0.014479163, + 0.03533551, + 0.035244733, + -0.0134579055, + -0.016101828, + 0.0132082645, + 0.039760962, + -0.016828056, + -0.007489222, + 0.030138446, + 0.010263639, + -0.011216813, + 0.006365839, + 0.067720726, + -0.018813834, + 0.025440661, + -0.010740225, + -0.027347008, + 0.007818294, + 0.04073683, + 0.01276572, + -0.010070735, + -0.0029786678, + -0.0007138165, + -0.029934194, + -0.0064055547, + -0.007296318, + 0.0048226053, + 0.023920123, + -0.0018935818, + -0.00009299472, + -0.024532877, + 0.019199643, + -0.006513354, + 0.0058608837, + 0.0012517497, + -0.03592557, + -0.03522204, + -0.0016184095, + -0.02873138, + -0.023443535, + 0.019869134, + 0.0005638193, + -0.019528715, + 0.04743174, + 0.0015829493, + 0.008816857, + -0.016533025, + -0.038444676, + 0.031999405, + 0.0089984145, + 0.027528565, + -0.009696273, + -0.012618205, + 0.01289054, + 0.029344134, + -0.015636588, + 0.0051828823, + 0.048248746, + 0.019233685, + 0.0051006144, + 0.017599672, + 0.008317576, + -0.03340647, + 0.018314553, + 0.02414707, + -0.018462067, + 0.016930182, + -0.014467816, + 0.018927308, + 0.027437788, + 0.007046677, + 0.0004783599, + 0.012777067, + -0.020674793, + 0.032589465, + -0.03753689, + 0.0033304344, + -0.018087607, + -0.03479084, + 0.0076821265, + -0.005923294, + -0.017452158, + -0.037604973, + -0.044390664, + 0.037809227, + -0.009770031, + 0.015398295, + 0.0020481888, + 0.0023091768, + 0.0060083987, + 0.013582726, + 0.0013021034, + -0.03560785, + -0.019528715, + 0.0031119988, + 0.0036623431, + -0.004692111, + 0.011579926, + 0.017894702, + 0.051652938, + -0.0042637503, + 0.02523641, + 0.02312581, + 0.015761409, + -0.021469103, + 0.010280659, + -0.026507309, + -0.012811109, + -0.026507309, + -0.011506169, + -0.016033744, + 0.00020176929, + -0.0030212204, + 0.0052906815, + -0.01786066, + 0.029888805, + -0.015114612, + 0.0060140723, + -0.023443535, + 0.031658985, + -0.005384297, + -0.009480675, + 0.03942054, + 0.028277488, + -0.010967172, + 0.0038665948, + 0.0027545586, + 0.0026822195, + -0.0060083987, + -0.044413358, + -0.014876319, + 0.029820722, + -0.012822457, + -0.020368416, + -0.006859447, + -0.008692036, + 0.034563895, + 0.010274986, + -0.020742876, + 0.0072679496, + -0.017213864, + 0.018098954, + 0.018121649, + 0.024419403, + 0.011551558, + -0.017361378, + 0.0005308412, + -0.059868388, + 0.018246468, + 0.007250929, + -0.012731678, + 0.016759971, + -0.039738268, + 0.001914858, + 0.003517665, + -0.0005804856, + 0.0038694316, + 0.03345186, + -0.012073535, + 0.01845072, + -0.043573655, + 0.004641048, + 0.0013482019, + -0.010621079, + -0.017338684, + 0.0025247757, + 0.019891828, + -0.018212426, + -0.035880182, + 0.012845151, + -0.012958624, + 0.006246692, + -0.010269312, + -0.009577126, + -0.01828051, + -0.017202517, + 0.029548386, + -0.036129825, + 0.00933316, + -0.0046070064, + -0.030251918, + -0.011937367, + 0.02553144, + 0.0049956515, + -0.030864673, + 0.0013673505, + 0.030546948, + -0.010462217, + 0.038535453, + -0.045207668, + 0.008652321, + 0.0027588138, + -0.008975719, + 0.039760962, + 0.025304493, + 0.0299115, + 0.020572666, + 0.045139585 + ] + } + ], + "model": "text-embedding-3-small", + "usage": { + "prompt_tokens": 4, + "total_tokens": 4 + } + } + headers: + Date: "Fri, 25 Jul 2025 06:33:50 GMT" + Content-Type: application/json + access-control-allow-origin: '*' + access-control-expose-headers: X-Request-ID + openai-model: text-embedding-3-small + openai-organization: test_organization + openai-processing-ms: "115" + openai-project: proj_Pf1eM5R55Z35wBy4rt8PxAGq + openai-version: 2020-10-01 + strict-transport-security: max-age=31536000; includeSubDomains; preload + via: envoy-router-84549dd555-dssv8 + x-envoy-upstream-service-time: "118" + x-ratelimit-limit-requests: "5000" + x-ratelimit-limit-tokens: "5000000" + x-ratelimit-remaining-requests: "4999" + x-ratelimit-remaining-tokens: "4999994" + x-ratelimit-reset-requests: 12ms + x-ratelimit-reset-tokens: 0s + x-request-id: req_23145c67d571cd4266c17f1985b90434 + cf-cache-status: DYNAMIC + Set-Cookie: test_set_cookie + X-Content-Type-Options: nosniff + Server: cloudflare + CF-RAY: 9649a0c7591f986e-NRT + alt-svc: h3=":443"; ma=86400 +uuid: 227a6023-72ed-4209-ab5d-a90c31f47de0 +persistent: true +insertionIndex: 36 From cd8911fc6248b64867e0cc4ab38b75e31c6d76d8 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Thu, 31 Jul 2025 10:44:13 +0900 Subject: [PATCH 08/11] Fix merge --- .../openai/v1_1/OpenAITelemetry.java | 7 -- .../openai/v1_1/AbstractChatTest.java | 76 ------------------- 2 files changed, 83 deletions(-) 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 61ade2bcb1ff..f6528eec741f 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 @@ -61,11 +61,4 @@ public OpenAIClientAsync wrap(OpenAIClientAsync client) { client, chatInstrumenter, embeddingsInstrumenter, eventLogger, captureMessageContent) .createProxy(); } - - /** Wraps the provided OpenAIClientAsync, enabling telemetry for it. */ - public OpenAIClientAsync wrap(OpenAIClientAsync client) { - return new InstrumentedOpenAiClientAsync( - client, chatInstrumenter, eventLogger, captureMessageContent) - .createProxy(); - } } 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 f95d3169a7c6..a3e8dc56ff9b 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 @@ -155,82 +155,6 @@ protected final List doCompletionsStreaming( throw new AssertionError(); } - @Parameter TestType testType; - - protected final ChatCompletion doCompletions(ChatCompletionCreateParams params) { - return doCompletions(params, getClient(), getClientAsync()); - } - - protected final ChatCompletion doCompletions( - ChatCompletionCreateParams params, OpenAIClient client, OpenAIClientAsync clientAsync) { - switch (testType) { - case SYNC: - return client.chat().completions().create(params); - case SYNC_FROM_ASYNC: - return clientAsync.sync().chat().completions().create(params); - case ASYNC: - case ASYNC_FROM_SYNC: - OpenAIClientAsync cl = testType == TestType.ASYNC ? clientAsync : client.async(); - try { - return cl.chat() - .completions() - .create(params) - .thenApply( - res -> { - assertThat(Span.fromContextOrNull(Context.current())).isNull(); - return res; - }) - .join(); - } catch (CompletionException e) { - if (e.getCause() instanceof OpenAIIoException) { - throw ((OpenAIIoException) e.getCause()); - } - throw e; - } - } - throw new AssertionError(); - } - - protected final List doCompletionsStreaming( - ChatCompletionCreateParams params) { - return doCompletionsStreaming(params, getClient(), getClientAsync()); - } - - protected final List doCompletionsStreaming( - ChatCompletionCreateParams params, OpenAIClient client, OpenAIClientAsync clientAsync) { - switch (testType) { - case SYNC: - try (StreamResponse result = - client.chat().completions().createStreaming(params)) { - return result.stream().collect(Collectors.toList()); - } - case SYNC_FROM_ASYNC: - try (StreamResponse result = - clientAsync.sync().chat().completions().createStreaming(params)) { - return result.stream().collect(Collectors.toList()); - } - case ASYNC: - case ASYNC_FROM_SYNC: - { - OpenAIClientAsync cl = testType == TestType.ASYNC ? clientAsync : client.async(); - AsyncStreamResponse stream = - cl.chat().completions().createStreaming(params); - List result = new ArrayList<>(); - stream.subscribe(result::add); - try { - stream.onCompleteFuture().join(); - } catch (CompletionException e) { - if (e.getCause() instanceof OpenAIIoException) { - throw ((OpenAIIoException) e.getCause()); - } - throw e; - } - return result; - } - } - throw new AssertionError(); - } - @Test void basic() { ChatCompletionCreateParams params = From 2a482b2831536de37b3b2941e3f9f8b4d4c4a3f4 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Thu, 31 Jul 2025 10:50:21 +0900 Subject: [PATCH 09/11] Cleanup --- .../openai/v1_1/AbstractEmbeddingsTest.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractEmbeddingsTest.java b/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractEmbeddingsTest.java index 7df8f0f9e7c3..1d11bf695d0e 100644 --- a/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractEmbeddingsTest.java +++ b/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractEmbeddingsTest.java @@ -30,20 +30,12 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; -import io.opentelemetry.instrumentation.testing.recording.RecordingExtension; import java.util.concurrent.CompletionException; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; public abstract class AbstractEmbeddingsTest extends AbstractOpenAiTest { - protected static final String INSTRUMENTATION_NAME = "io.opentelemetry.openai-java-1.1"; - - private static final String API_URL = "https://api.openai.com/v1"; - private static final String MODEL = "text-embedding-3-small"; - @RegisterExtension static final RecordingExtension recording = new RecordingExtension(API_URL); - protected final CreateEmbeddingResponse doEmbeddings(EmbeddingCreateParams request) { return doEmbeddings(request, getClient(), getClientAsync()); } From 2f5ad2976885f5604db6497ca1f0851131fbbaf2 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Wed, 6 Aug 2025 10:29:26 +0900 Subject: [PATCH 10/11] Fix --- .../v1_1/EmbeddingAttributesGetter.java | 2 +- .../openai/v1_1/GenAiAttributes.java | 2 +- .../openai/v1_1/OpenAITelemetryBuilder.java | 3 +- .../openai/v1_1/AbstractEmbeddingsTest.java | 69 +++++++++++++------ .../openai/v1_1/AbstractOpenAiTest.java | 37 ++++++---- 5 files changed, 74 insertions(+), 39 deletions(-) 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 4f315c734af5..f1f36cd504e9 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 @@ -19,7 +19,7 @@ enum EmbeddingAttributesGetter @Override public String getOperationName(EmbeddingCreateParams request) { - return GenAiAttributes.GenAiOperationNameIncubatingValues.EMBEDDING; + return GenAiAttributes.GenAiOperationNameIncubatingValues.EMBEDDINGS; } @Override 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 20ccc80928f2..c6209d44acdb 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 @@ -15,7 +15,7 @@ final class GenAiAttributes { static final class GenAiOperationNameIncubatingValues { static final String CHAT = "chat"; - static final String EMBEDDING = "embeddings"; + static final String EMBEDDINGS = "embeddings"; private GenAiOperationNameIncubatingValues() {} } 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 6f4f117e961a..5f36c76a5798 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 @@ -16,6 +16,7 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiClientMetrics; import io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; /** A builder of {@link OpenAITelemetry}. */ @SuppressWarnings("IdentifierName") // Want to match library's convention @@ -63,7 +64,7 @@ public OpenAITelemetry build() { .addAttributesExtractor( GenAiAttributesExtractor.create(EmbeddingAttributesGetter.INSTANCE)) .addOperationMetrics(GenAiClientMetrics.get()) - .buildInstrumenter(); + .buildInstrumenter(SpanKindExtractor.alwaysClient()); Logger eventLogger = openTelemetry.getLogsBridge().get(INSTRUMENTATION_NAME); return new OpenAITelemetry( diff --git a/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractEmbeddingsTest.java b/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractEmbeddingsTest.java index 1d11bf695d0e..a58a362501fe 100644 --- a/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractEmbeddingsTest.java +++ b/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractEmbeddingsTest.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.openai.v1_1; 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_REQUEST_ENCODING_FORMATS; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_REQUEST_MODEL; @@ -85,16 +86,27 @@ void basic() { getTesting() .waitAndAssertTraces( trace -> - maybeWithTransportSpan( - span -> - span.hasName("embeddings text-embedding-3-small") - .hasKind(SpanKind.CLIENT) - .hasAttributesSatisfyingExactly( - equalTo(GEN_AI_SYSTEM, OPENAI), - equalTo(GEN_AI_OPERATION_NAME, EMBEDDINGS), - equalTo(GEN_AI_REQUEST_MODEL, MODEL), - equalTo(GEN_AI_RESPONSE_MODEL, MODEL), - equalTo(GEN_AI_USAGE_INPUT_TOKENS, 4)))); + trace.hasSpansSatisfyingExactly( + maybeWithTransportSpan( + span -> + span.hasName("embeddings text-embedding-3-small") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(GEN_AI_SYSTEM, OPENAI), + equalTo(GEN_AI_OPERATION_NAME, EMBEDDINGS), + equalTo(GEN_AI_REQUEST_MODEL, MODEL), + equalTo(GEN_AI_RESPONSE_MODEL, MODEL), + equalTo(GEN_AI_USAGE_INPUT_TOKENS, 4), + // Newer versions of the library populate base64 when unset by + // the user. + satisfies( + GEN_AI_REQUEST_ENCODING_FORMATS, + val -> + val.satisfiesAnyOf( + v -> assertThat(v).isNull(), + v -> + assertThat(v) + .isEqualTo(singletonList("base64")))))))); getTesting() .waitAndAssertMetrics( @@ -147,17 +159,19 @@ void allTheClientOptions() { getTesting() .waitAndAssertTraces( trace -> - maybeWithTransportSpan( - span -> - span.hasName("embeddings text-embedding-3-small") - .hasKind(SpanKind.CLIENT) - .hasAttributesSatisfyingExactly( - equalTo(GEN_AI_SYSTEM, OPENAI), - equalTo(GEN_AI_OPERATION_NAME, EMBEDDINGS), - equalTo(GEN_AI_REQUEST_MODEL, MODEL), - equalTo(GEN_AI_REQUEST_ENCODING_FORMATS, singletonList("base64")), - equalTo(GEN_AI_RESPONSE_MODEL, MODEL), - equalTo(GEN_AI_USAGE_INPUT_TOKENS, 4)))); + trace.hasSpansSatisfyingExactly( + maybeWithTransportSpan( + span -> + span.hasName("embeddings text-embedding-3-small") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(GEN_AI_SYSTEM, OPENAI), + equalTo(GEN_AI_OPERATION_NAME, EMBEDDINGS), + equalTo(GEN_AI_REQUEST_MODEL, MODEL), + equalTo( + GEN_AI_REQUEST_ENCODING_FORMATS, singletonList("base64")), + equalTo(GEN_AI_RESPONSE_MODEL, MODEL), + equalTo(GEN_AI_USAGE_INPUT_TOKENS, 4))))); getTesting() .waitAndAssertMetrics( @@ -226,11 +240,22 @@ void connectionError() { maybeWithTransportSpan( span -> span.hasName("embeddings text-embedding-3-small") + .hasKind(SpanKind.CLIENT) .hasException(thrown) .hasAttributesSatisfyingExactly( equalTo(GEN_AI_SYSTEM, OPENAI), equalTo(GEN_AI_OPERATION_NAME, EMBEDDINGS), - equalTo(GEN_AI_REQUEST_MODEL, MODEL))))); + equalTo(GEN_AI_REQUEST_MODEL, MODEL), + // Newer versions of the library populate base64 when unset by + // the user. + satisfies( + GEN_AI_REQUEST_ENCODING_FORMATS, + val -> + val.satisfiesAnyOf( + v -> assertThat(v).isNull(), + v -> + assertThat(v) + .isEqualTo(singletonList("base64")))))))); getTesting() .waitAndAssertMetrics( diff --git a/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractOpenAiTest.java b/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractOpenAiTest.java index bd2614ef7543..9fd4123971a2 100644 --- a/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractOpenAiTest.java +++ b/instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractOpenAiTest.java @@ -41,26 +41,35 @@ enum TestType { protected abstract OpenAIClientAsync wrap(OpenAIClientAsync client); + private OpenAIClient rawClient; + private OpenAIClientAsync rawClientAsync; + protected final OpenAIClient getRawClient() { - OpenAIOkHttpClient.Builder builder = - OpenAIOkHttpClient.builder().baseUrl("http://localhost:" + recording.getPort()); - if (recording.isRecording()) { - builder.apiKey(System.getenv("OPENAI_API_KEY")); - } else { - builder.apiKey("unused"); + if (rawClient == null) { + OpenAIOkHttpClient.Builder builder = + OpenAIOkHttpClient.builder().baseUrl("http://localhost:" + recording.getPort()); + if (recording.isRecording()) { + builder.apiKey(System.getenv("OPENAI_API_KEY")); + } else { + builder.apiKey("unused"); + } + rawClient = builder.build(); } - return builder.build(); + return rawClient; } protected final OpenAIClientAsync getRawClientAsync() { - OpenAIOkHttpClientAsync.Builder builder = - OpenAIOkHttpClientAsync.builder().baseUrl("http://localhost:" + recording.getPort()); - if (recording.isRecording()) { - builder.apiKey(System.getenv("OPENAI_API_KEY")); - } else { - builder.apiKey("unused"); + if (rawClientAsync == null) { + OpenAIOkHttpClientAsync.Builder builder = + OpenAIOkHttpClientAsync.builder().baseUrl("http://localhost:" + recording.getPort()); + if (recording.isRecording()) { + builder.apiKey(System.getenv("OPENAI_API_KEY")); + } else { + builder.apiKey("unused"); + } + rawClientAsync = builder.build(); } - return builder.build(); + return rawClientAsync; } protected final OpenAIClient getClient() { From 9db733ba2cb5ead8dedf0258017edfc7ad43e7e0 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Tue, 12 Aug 2025 12:26:40 +0900 Subject: [PATCH 11/11] Cleanup --- .../api/incubator/semconv/genai/GenAiAttributesGetter.java | 1 - instrumentation/openai/openai-java-1.1/library/README.md | 1 + .../openai/v1_1/EmbeddingAttributesGetter.java | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) 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 e133518fb40e..91b5a9f5bb02 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 @@ -50,7 +50,6 @@ public interface GenAiAttributesGetter { @Nullable Double getRequestTopP(REQUEST request); - @Nullable List getResponseFinishReasons(REQUEST request, RESPONSE response); @Nullable diff --git a/instrumentation/openai/openai-java-1.1/library/README.md b/instrumentation/openai/openai-java-1.1/library/README.md index 4451ec510262..1a90fba0fd3e 100644 --- a/instrumentation/openai/openai-java-1.1/library/README.md +++ b/instrumentation/openai/openai-java-1.1/library/README.md @@ -1,6 +1,7 @@ # Library Instrumentation for OpenAI Java SDK version 1.1.0 and higher Provides OpenTelemetry instrumentation for [openai-java](https://github.com/openai/openai-java/). +Versions 1.1 through 2.x are supported. ## Quickstart 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 f1f36cd504e9..3503ff4da4c1 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 @@ -10,6 +10,7 @@ import com.openai.models.embeddings.CreateEmbeddingResponse; import com.openai.models.embeddings.EmbeddingCreateParams; import io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiAttributesGetter; +import java.util.Collections; import java.util.List; import javax.annotation.Nullable; @@ -86,11 +87,10 @@ public Double getRequestTopP(EmbeddingCreateParams request) { return null; } - @Nullable @Override public List getResponseFinishReasons( EmbeddingCreateParams request, @Nullable CreateEmbeddingResponse response) { - return null; + return Collections.emptyList(); } @Nullable