Skip to content

Commit 6406945

Browse files
committed
WIP
1 parent caaf1b5 commit 6406945

File tree

5 files changed

+261
-1
lines changed

5 files changed

+261
-1
lines changed

instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/InstrumentedEmbeddingServiceAsync.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,6 @@ private CompletableFuture<CreateEmbeddingResponse> create(
6262
}
6363

6464
future = future.whenComplete((res, t) -> instrumenter.end(context, request, res, t));
65-
return CompletableFutureWrapper.wrap(future, context);
65+
return CompletableFutureWrapper.wrap(future, parentContext);
6666
}
6767
}

instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/OpenAITelemetry.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package io.opentelemetry.instrumentation.openai.v1_1;
77

88
import com.openai.client.OpenAIClient;
9+
import com.openai.client.OpenAIClientAsync;
910
import com.openai.models.chat.completions.ChatCompletion;
1011
import com.openai.models.chat.completions.ChatCompletionCreateParams;
1112
import com.openai.models.embeddings.CreateEmbeddingResponse;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package io.opentelemetry.instrumentation.openai.v1_1;
2+
3+
import static java.util.Collections.singletonList;
4+
5+
import java.util.List;
6+
import java.util.function.Consumer;
7+
import org.junit.jupiter.api.BeforeAll;
8+
import org.junit.jupiter.api.extension.RegisterExtension;
9+
import com.openai.client.OpenAIClient;
10+
import com.openai.client.OpenAIClientAsync;
11+
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
12+
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
13+
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
14+
15+
class EmbeddingsTest extends AbstractEmbeddingsTest {
16+
17+
@RegisterExtension
18+
private static final LibraryInstrumentationExtension testing =
19+
LibraryInstrumentationExtension.create();
20+
21+
private static OpenAITelemetry telemetry;
22+
23+
@BeforeAll
24+
static void setup() {
25+
telemetry =
26+
OpenAITelemetry.builder(testing.getOpenTelemetry()).setCaptureMessageContent(true).build();
27+
}
28+
29+
@Override
30+
protected InstrumentationExtension getTesting() {
31+
return testing;
32+
}
33+
34+
@Override
35+
protected OpenAIClient wrap(OpenAIClient client) {
36+
return telemetry.wrap(client);
37+
}
38+
39+
@Override
40+
protected OpenAIClientAsync wrap(OpenAIClientAsync client) {
41+
return telemetry.wrap(client);
42+
}
43+
44+
@Override
45+
protected List<Consumer<SpanDataAssert>> maybeWithTransportSpan(Consumer<SpanDataAssert> span) {
46+
return singletonList(span);
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.openai.v1_1;
7+
8+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
9+
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_OPERATION_NAME;
10+
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_REQUEST_MODEL;
11+
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_RESPONSE_MODEL;
12+
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_SYSTEM;
13+
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_TOKEN_TYPE;
14+
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_USAGE_INPUT_TOKENS;
15+
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiOperationNameIncubatingValues.EMBEDDINGS;
16+
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiSystemIncubatingValues.OPENAI;
17+
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiTokenTypeIncubatingValues.INPUT;
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
import com.openai.client.OpenAIClient;
21+
import com.openai.client.OpenAIClientAsync;
22+
import com.openai.models.embeddings.CreateEmbeddingResponse;
23+
import com.openai.models.embeddings.EmbeddingCreateParams;
24+
import io.opentelemetry.api.trace.Span;
25+
import io.opentelemetry.api.trace.SpanKind;
26+
import io.opentelemetry.context.Context;
27+
import io.opentelemetry.instrumentation.testing.recording.RecordingExtension;
28+
import java.util.Collections;
29+
import java.util.concurrent.CompletionException;
30+
import org.junit.jupiter.api.Test;
31+
import org.junit.jupiter.api.extension.RegisterExtension;
32+
33+
public abstract class AbstractEmbeddingsTest extends AbstractOpenAiTest {
34+
protected static final String INSTRUMENTATION_NAME = "io.opentelemetry.openai-java-1.1";
35+
36+
private static final String API_URL = "https://api.openai.com/v1";
37+
38+
private static final String MODEL = "text-embedding-3-small";
39+
40+
@RegisterExtension static final RecordingExtension recording = new RecordingExtension(API_URL);
41+
42+
protected final CreateEmbeddingResponse doEmbeddings(EmbeddingCreateParams request) {
43+
return doEmbeddings(request, getClient(), getClientAsync());
44+
}
45+
46+
protected final CreateEmbeddingResponse doEmbeddings(
47+
EmbeddingCreateParams request, OpenAIClient client, OpenAIClientAsync clientAsync) {
48+
switch (testType) {
49+
case SYNC:
50+
return client.embeddings().create(request);
51+
case SYNC_FROM_ASYNC:
52+
return clientAsync.sync().embeddings().create(request);
53+
case ASYNC:
54+
case ASYNC_FROM_SYNC:
55+
OpenAIClientAsync cl = testType == TestType.ASYNC ? clientAsync : client.async();
56+
try {
57+
return cl.embeddings()
58+
.create(request)
59+
.thenApply(
60+
res -> {
61+
assertThat(Span.fromContextOrNull(Context.current())).isNull();
62+
return res;
63+
})
64+
.join();
65+
} catch (CompletionException e) {
66+
if (e.getCause() instanceof RuntimeException) {
67+
throw (RuntimeException) e.getCause();
68+
}
69+
throw e;
70+
}
71+
}
72+
throw new AssertionError();
73+
}
74+
75+
@Test
76+
void basic() {
77+
String text = "South Atlantic Ocean.";
78+
79+
EmbeddingCreateParams request =
80+
EmbeddingCreateParams.builder()
81+
.model(MODEL)
82+
.inputOfArrayOfStrings(Collections.singletonList(text))
83+
.build();
84+
CreateEmbeddingResponse response = doEmbeddings(request);
85+
86+
assertThat(response.data()).hasSize(1);
87+
88+
getTesting()
89+
.waitAndAssertTraces(
90+
trace ->
91+
maybeWithTransportSpan(
92+
span ->
93+
span.hasName("embeddings text-embedding-3-small")
94+
.hasKind(SpanKind.CLIENT)
95+
.hasAttributesSatisfyingExactly(
96+
equalTo(GEN_AI_SYSTEM, OPENAI),
97+
equalTo(GEN_AI_OPERATION_NAME, EMBEDDINGS),
98+
equalTo(GEN_AI_REQUEST_MODEL, MODEL),
99+
equalTo(GEN_AI_RESPONSE_MODEL, MODEL),
100+
equalTo(GEN_AI_USAGE_INPUT_TOKENS, 4))));
101+
102+
getTesting()
103+
.waitAndAssertMetrics(
104+
INSTRUMENTATION_NAME,
105+
metric ->
106+
metric
107+
.hasName("gen_ai.client.operation.duration")
108+
.hasHistogramSatisfying(
109+
histogram ->
110+
histogram.hasPointsSatisfying(
111+
point ->
112+
point
113+
.hasSumGreaterThan(0.0)
114+
.hasAttributesSatisfyingExactly(
115+
equalTo(GEN_AI_SYSTEM, "openai"),
116+
equalTo(GEN_AI_OPERATION_NAME, "chat"),
117+
equalTo(GEN_AI_REQUEST_MODEL, MODEL),
118+
equalTo(GEN_AI_RESPONSE_MODEL, MODEL)))),
119+
metric ->
120+
metric
121+
.hasName("gen_ai.client.token.usage")
122+
.hasHistogramSatisfying(
123+
histogram ->
124+
histogram.hasPointsSatisfying(
125+
point ->
126+
point
127+
.hasSum(4.0)
128+
.hasAttributesSatisfyingExactly(
129+
equalTo(GEN_AI_SYSTEM, "openai"),
130+
equalTo(GEN_AI_OPERATION_NAME, "chat"),
131+
equalTo(GEN_AI_REQUEST_MODEL, MODEL),
132+
equalTo(GEN_AI_RESPONSE_MODEL, MODEL),
133+
equalTo(GEN_AI_TOKEN_TYPE, INPUT)))));
134+
}
135+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.openai.v1_1;
7+
8+
import com.openai.client.OpenAIClient;
9+
import com.openai.client.OpenAIClientAsync;
10+
import com.openai.client.okhttp.OpenAIOkHttpClient;
11+
import com.openai.client.okhttp.OpenAIOkHttpClientAsync;
12+
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
13+
import io.opentelemetry.instrumentation.testing.recording.RecordingExtension;
14+
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
15+
import java.util.List;
16+
import java.util.function.Consumer;
17+
import org.junit.jupiter.api.extension.RegisterExtension;
18+
import org.junit.jupiter.params.Parameter;
19+
import org.junit.jupiter.params.ParameterizedClass;
20+
21+
@ParameterizedClass
22+
abstract class AbstractOpenAiTest {
23+
enum TestType {
24+
SYNC,
25+
SYNC_FROM_ASYNC,
26+
ASYNC,
27+
ASYNC_FROM_SYNC,
28+
}
29+
30+
protected static final String INSTRUMENTATION_NAME = "io.opentelemetry.openai-java-1.1";
31+
32+
private static final String API_URL = "https://api.openai.com/v1";
33+
34+
@RegisterExtension static final RecordingExtension recording = new RecordingExtension(API_URL);
35+
36+
protected abstract InstrumentationExtension getTesting();
37+
38+
protected abstract OpenAIClient wrap(OpenAIClient client);
39+
40+
protected abstract OpenAIClientAsync wrap(OpenAIClientAsync client);
41+
42+
protected final OpenAIClient getRawClient() {
43+
OpenAIOkHttpClient.Builder builder =
44+
OpenAIOkHttpClient.builder().baseUrl("http://localhost:" + recording.getPort());
45+
if (recording.isRecording()) {
46+
builder.apiKey(System.getenv("OPENAI_API_KEY"));
47+
} else {
48+
builder.apiKey("unused");
49+
}
50+
return builder.build();
51+
}
52+
53+
protected final OpenAIClientAsync getRawClientAsync() {
54+
OpenAIOkHttpClientAsync.Builder builder =
55+
OpenAIOkHttpClientAsync.builder().baseUrl("http://localhost:" + recording.getPort());
56+
if (recording.isRecording()) {
57+
builder.apiKey(System.getenv("OPENAI_API_KEY"));
58+
} else {
59+
builder.apiKey("unused");
60+
}
61+
return builder.build();
62+
}
63+
64+
protected final OpenAIClient getClient() {
65+
return wrap(getRawClient());
66+
}
67+
68+
protected final OpenAIClientAsync getClientAsync() {
69+
return wrap(getRawClientAsync());
70+
}
71+
72+
protected abstract List<Consumer<SpanDataAssert>> maybeWithTransportSpan(
73+
Consumer<SpanDataAssert> span);
74+
75+
@Parameter protected TestType testType;
76+
}

0 commit comments

Comments
 (0)