Skip to content

Commit 9fc8b20

Browse files
Add initial instrumentation of OpenAI client (#14221)
Co-authored-by: otelbot <[email protected]>
1 parent cea2728 commit 9fc8b20

File tree

35 files changed

+3113
-17
lines changed

35 files changed

+3113
-17
lines changed

.fossa.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,12 @@ targets:
721721
- type: gradle
722722
path: ./
723723
target: ':instrumentation:okhttp:okhttp-3.0:library'
724+
- type: gradle
725+
path: ./
726+
target: ':instrumentation:openai:openai-java-1.1:javaagent'
727+
- type: gradle
728+
path: ./
729+
target: ':instrumentation:openai:openai-java-1.1:library'
724730
- type: gradle
725731
path: ./
726732
target: ':instrumentation:opensearch:opensearch-rest-1.0:javaagent'

buildscripts/checkstyle.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@
254254
<property name="arrayInitIndent" value="2"/>
255255
</module>
256256
-->
257+
<!-- handled by error prone
257258
<module name="AbbreviationAsWordInName">
258259
<property name="ignoreFinal" value="false"/>
259260
<property name="allowedAbbreviationLength" value="0"/>
@@ -262,6 +263,7 @@
262263
PARAMETER_DEF, VARIABLE_DEF, METHOD_DEF, PATTERN_VARIABLE_DEF, RECORD_DEF,
263264
RECORD_COMPONENT_DEF"/>
264265
</module>
266+
-->
265267
<module name="OverloadMethodsDeclarationOrder"/>
266268
<!-- there are only a few violations of this, and they all appear to be for good reasons
267269
<module name="VariableDeclarationUsageDistance"/>

conventions/src/main/kotlin/otel.scala-conventions.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ plugins {
55
}
66

77
dependencies {
8+
compileOnly("org.scala-lang:scala-library:2.11.12")
89
testCompileOnly("org.scala-lang:scala-library:2.11.12")
910
}
1011

instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2BedrockRuntimeTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
import io.opentelemetry.api.common.Value;
3030
import io.opentelemetry.api.trace.SpanContext;
3131
import io.opentelemetry.api.trace.SpanKind;
32-
import io.opentelemetry.instrumentation.awssdk.v2_2.recording.RecordingExtension;
3332
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
33+
import io.opentelemetry.instrumentation.testing.recording.RecordingExtension;
3434
import io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes;
3535
import java.net.URI;
3636
import java.util.ArrayList;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Settings for the OpenAI instrumentation
2+
3+
| System property | Type | Default | Description |
4+
|------------------------------------------------------|---------|---------|------------------------------------------|
5+
| `otel.instrumentation.genai.capture-message-content` | Boolean | `false` | record content of user and LLM messages. |
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
plugins {
2+
id("otel.javaagent-instrumentation")
3+
}
4+
5+
muzzle {
6+
pass {
7+
group.set("com.openai")
8+
module.set("openai-java")
9+
versions.set("[1.1.0,)")
10+
// TODO: assertInverse after completing instrumentation
11+
}
12+
}
13+
14+
dependencies {
15+
implementation(project(":instrumentation:openai:openai-java-1.1:library"))
16+
17+
testInstrumentation(project(":instrumentation:okhttp:okhttp-3.0:javaagent"))
18+
19+
library("com.openai:openai-java:1.1.0")
20+
21+
testImplementation(project(":instrumentation:openai:openai-java-1.1:testing"))
22+
}
23+
24+
tasks {
25+
withType<Test>().configureEach {
26+
systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
27+
// TODO run tests both with and without genai message capture
28+
systemProperty("otel.instrumentation.genai.capture-message-content", "true")
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.openai.v1_1;
7+
8+
import static io.opentelemetry.javaagent.instrumentation.openai.v1_1.OpenAiSingletons.TELEMETRY;
9+
import static net.bytebuddy.matcher.ElementMatchers.named;
10+
import static net.bytebuddy.matcher.ElementMatchers.returns;
11+
12+
import com.openai.client.OpenAIClient;
13+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
14+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
15+
import net.bytebuddy.asm.Advice;
16+
import net.bytebuddy.description.type.TypeDescription;
17+
import net.bytebuddy.matcher.ElementMatcher;
18+
19+
public class OpenAiClientInstrumentation implements TypeInstrumentation {
20+
@Override
21+
public ElementMatcher<TypeDescription> typeMatcher() {
22+
return named("com.openai.client.okhttp.OpenAIOkHttpClient$Builder");
23+
}
24+
25+
@Override
26+
public void transform(TypeTransformer transformer) {
27+
transformer.applyAdviceToMethod(
28+
named("build").and(returns(named("com.openai.client.OpenAIClient"))),
29+
OpenAiClientInstrumentation.class.getName() + "$BuildAdvice");
30+
}
31+
32+
@SuppressWarnings("unused")
33+
public static class BuildAdvice {
34+
@Advice.OnMethodExit(suppress = Throwable.class)
35+
@Advice.AssignReturned.ToReturned
36+
public static OpenAIClient onExit(@Advice.Return OpenAIClient client) {
37+
return TELEMETRY.wrap(client);
38+
}
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.openai.v1_1;
7+
8+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
9+
import static java.util.Collections.singletonList;
10+
11+
import com.google.auto.service.AutoService;
12+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
13+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
14+
import java.util.List;
15+
import net.bytebuddy.matcher.ElementMatcher;
16+
17+
@AutoService(InstrumentationModule.class)
18+
public class OpenAiInstrumentationModule extends InstrumentationModule {
19+
public OpenAiInstrumentationModule() {
20+
super("openai-java", "openai-java-1.1", "openai");
21+
}
22+
23+
@Override
24+
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
25+
return hasClassesNamed("com.openai.client.OpenAIClient");
26+
}
27+
28+
@Override
29+
public List<TypeInstrumentation> typeInstrumentations() {
30+
return singletonList(new OpenAiClientInstrumentation());
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.openai.v1_1;
7+
8+
import io.opentelemetry.api.GlobalOpenTelemetry;
9+
import io.opentelemetry.instrumentation.openai.v1_1.OpenAITelemetry;
10+
import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig;
11+
12+
public final class OpenAiSingletons {
13+
public static final OpenAITelemetry TELEMETRY =
14+
OpenAITelemetry.builder(GlobalOpenTelemetry.get())
15+
.setCaptureMessageContent(
16+
AgentInstrumentationConfig.get()
17+
.getBoolean("otel.instrumentation.genai.capture-message-content", false))
18+
.build();
19+
20+
private OpenAiSingletons() {}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.openai.v1_1;
7+
8+
import com.openai.client.OpenAIClient;
9+
import io.opentelemetry.instrumentation.openai.v1_1.AbstractChatTest;
10+
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
11+
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
12+
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
13+
import java.util.ArrayList;
14+
import java.util.List;
15+
import java.util.function.Consumer;
16+
import org.junit.jupiter.api.extension.RegisterExtension;
17+
18+
class ChatTest extends AbstractChatTest {
19+
20+
@RegisterExtension
21+
private static final AgentInstrumentationExtension testing =
22+
AgentInstrumentationExtension.create();
23+
24+
@Override
25+
protected InstrumentationExtension getTesting() {
26+
return testing;
27+
}
28+
29+
@Override
30+
protected OpenAIClient wrap(OpenAIClient client) {
31+
return client;
32+
}
33+
34+
@Override
35+
protected final List<Consumer<SpanDataAssert>> maybeWithTransportSpan(
36+
Consumer<SpanDataAssert> span) {
37+
List<Consumer<SpanDataAssert>> result = new ArrayList<>();
38+
result.add(span);
39+
// Do a very simple assertion since the telemetry is not part of this library.
40+
result.add(s -> s.hasName("POST"));
41+
return result;
42+
}
43+
}

0 commit comments

Comments
 (0)