Skip to content

Commit 2ca62ab

Browse files
authored
Remove openai latest dep restriction (#14423)
1 parent c50eb3c commit 2ca62ab

File tree

14 files changed

+503
-115
lines changed

14 files changed

+503
-115
lines changed

docs/supported-libraries.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ These are the supported libraries and frameworks:
106106
| [MongoDB Driver](https://mongodb.github.io/mongo-java-driver/) | 3.1+ | [opentelemetry-mongo-3.1](../instrumentation/mongo/mongo-3.1/library) | [Database Client Spans], [Database Client Metrics] [6] |
107107
| [MyBatis](https://mybatis.org/mybatis-3/) | 3.2+ | N/A | none |
108108
| [Netty HTTP codec [5]](https://github.com/netty/netty) | 3.8+ | [opentelemetry-netty-4.1](../instrumentation/netty/netty-4.1/library) | [HTTP Client Spans], [HTTP Client Metrics], [HTTP Server Spans], [HTTP Server Metrics] |
109-
| [OpenAI Java SDK](https://github.com/openai/openai-java) | 1.1+ (not including 3.0+ yet) | [openai-java-1.1](../instrumentation/openai/openai-java-1.1/library) | [GenAI Client Spans], [GenAI Client Metrics] |
109+
| [OpenAI Java SDK](https://github.com/openai/openai-java) | 1.1+ | [openai-java-1.1](../instrumentation/openai/openai-java-1.1/library) | [GenAI Client Spans], [GenAI Client Metrics] |
110110
| [OpenSearch Rest Client](https://github.com/opensearch-project/opensearch-java) | 1.0+ | | [Database Client Spans], [Database Client Metrics] [6] |
111111
| [OkHttp](https://github.com/square/okhttp/) | 2.2+ | [opentelemetry-okhttp-3.0](../instrumentation/okhttp/okhttp-3.0/library) | [HTTP Client Spans], [HTTP Client Metrics] |
112112
| [Oracle UCP](https://docs.oracle.com/database/121/JJUCP/) | 11.2+ | [opentelemetry-oracle-ucp-11.2](../instrumentation/oracle-ucp-11.2/library) | [Database Pool Metrics] |

instrumentation/openai/openai-java-1.1/javaagent/build.gradle.kts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ dependencies {
1919
library("com.openai:openai-java:1.1.0")
2020

2121
testImplementation(project(":instrumentation:openai:openai-java-1.1:testing"))
22-
23-
latestDepTestLibrary("com.openai:openai-java:2.+") // documented limitation
2422
}
2523

2624
tasks {

instrumentation/openai/openai-java-1.1/library/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# Library Instrumentation for OpenAI Java SDK version 1.1.0 and higher
22

33
Provides OpenTelemetry instrumentation for [openai-java](https://github.com/openai/openai-java/).
4-
Versions 1.1 through 2.x are supported.
54

65
## Quickstart
76

instrumentation/openai/openai-java-1.1/library/build.gradle.kts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ dependencies {
77
library("com.openai:openai-java:1.1.0")
88

99
testImplementation(project(":instrumentation:openai:openai-java-1.1:testing"))
10-
11-
latestDepTestLibrary("com.openai:openai-java:2.+") // documented limitation
1210
}
1311

1412
tasks {

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

Lines changed: 222 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,16 @@
2424
import io.opentelemetry.api.logs.LogRecordBuilder;
2525
import io.opentelemetry.api.logs.Logger;
2626
import io.opentelemetry.context.Context;
27+
import java.lang.invoke.MethodHandle;
28+
import java.lang.invoke.MethodHandles;
29+
import java.lang.invoke.MethodType;
2730
import java.util.HashMap;
2831
import java.util.List;
2932
import java.util.Map;
3033
import java.util.Objects;
34+
import java.util.Optional;
3135
import java.util.stream.Collectors;
36+
import javax.annotation.Nullable;
3237

3338
final class ChatCompletionEventsHelper {
3439

@@ -215,21 +220,232 @@ private static LogRecordBuilder newEvent(Logger eventLogger, String name) {
215220
private static Value<?> buildToolCallEventObject(
216221
ChatCompletionMessageToolCall call, boolean captureMessageContent) {
217222
Map<String, Value<?>> result = new HashMap<>();
218-
result.put("id", Value.of(call.id()));
219-
result.put("type", Value.of("function")); // "function" is the only currently supported type
220-
result.put("function", buildFunctionEventObject(call.function(), captureMessageContent));
223+
FunctionAccess functionAccess = getFunctionAccess(call);
224+
if (functionAccess != null) {
225+
result.put("id", Value.of(functionAccess.id()));
226+
result.put("type", Value.of("function")); // "function" is the only currently supported type
227+
result.put("function", buildFunctionEventObject(functionAccess, captureMessageContent));
228+
}
221229
return Value.of(result);
222230
}
223231

224232
private static Value<?> buildFunctionEventObject(
225-
ChatCompletionMessageToolCall.Function function, boolean captureMessageContent) {
233+
FunctionAccess functionAccess, boolean captureMessageContent) {
226234
Map<String, Value<?>> result = new HashMap<>();
227-
result.put("name", Value.of(function.name()));
235+
result.put("name", Value.of(functionAccess.name()));
228236
if (captureMessageContent) {
229-
result.put("arguments", Value.of(function.arguments()));
237+
result.put("arguments", Value.of(functionAccess.arguments()));
230238
}
231239
return Value.of(result);
232240
}
233241

242+
@Nullable
243+
private static FunctionAccess getFunctionAccess(ChatCompletionMessageToolCall call) {
244+
if (V1FunctionAccess.isAvailable()) {
245+
return V1FunctionAccess.create(call);
246+
}
247+
if (V3FunctionAccess.isAvailable()) {
248+
return V3FunctionAccess.create(call);
249+
}
250+
251+
return null;
252+
}
253+
254+
private interface FunctionAccess {
255+
String id();
256+
257+
String name();
258+
259+
String arguments();
260+
}
261+
262+
private static String invokeStringHandle(@Nullable MethodHandle methodHandle, Object object) {
263+
if (methodHandle == null) {
264+
return "";
265+
}
266+
267+
try {
268+
return (String) methodHandle.invoke(object);
269+
} catch (Throwable ignore) {
270+
return "";
271+
}
272+
}
273+
274+
private static class V1FunctionAccess implements FunctionAccess {
275+
@Nullable private static final MethodHandle idHandle;
276+
@Nullable private static final MethodHandle functionHandle;
277+
@Nullable private static final MethodHandle nameHandle;
278+
@Nullable private static final MethodHandle argumentsHandle;
279+
280+
static {
281+
MethodHandle id;
282+
MethodHandle function;
283+
MethodHandle name;
284+
MethodHandle arguments;
285+
286+
try {
287+
MethodHandles.Lookup lookup = MethodHandles.lookup();
288+
id =
289+
lookup.findVirtual(
290+
ChatCompletionMessageToolCall.class, "id", MethodType.methodType(String.class));
291+
Class<?> functionClass =
292+
Class.forName(
293+
"com.openai.models.chat.completions.ChatCompletionMessageToolCall$Function");
294+
function =
295+
lookup.findVirtual(
296+
ChatCompletionMessageToolCall.class,
297+
"function",
298+
MethodType.methodType(functionClass));
299+
name = lookup.findVirtual(functionClass, "name", MethodType.methodType(String.class));
300+
arguments =
301+
lookup.findVirtual(functionClass, "arguments", MethodType.methodType(String.class));
302+
} catch (Exception exception) {
303+
id = null;
304+
function = null;
305+
name = null;
306+
arguments = null;
307+
}
308+
idHandle = id;
309+
functionHandle = function;
310+
nameHandle = name;
311+
argumentsHandle = arguments;
312+
}
313+
314+
private final ChatCompletionMessageToolCall toolCall;
315+
private final Object function;
316+
317+
V1FunctionAccess(ChatCompletionMessageToolCall toolCall, Object function) {
318+
this.toolCall = toolCall;
319+
this.function = function;
320+
}
321+
322+
@Nullable
323+
static FunctionAccess create(ChatCompletionMessageToolCall toolCall) {
324+
if (functionHandle == null) {
325+
return null;
326+
}
327+
328+
try {
329+
return new V1FunctionAccess(toolCall, functionHandle.invoke(toolCall));
330+
} catch (Throwable ignore) {
331+
return null;
332+
}
333+
}
334+
335+
static boolean isAvailable() {
336+
return idHandle != null;
337+
}
338+
339+
@Override
340+
public String id() {
341+
return invokeStringHandle(idHandle, toolCall);
342+
}
343+
344+
@Override
345+
public String name() {
346+
return invokeStringHandle(nameHandle, function);
347+
}
348+
349+
@Override
350+
public String arguments() {
351+
return invokeStringHandle(argumentsHandle, function);
352+
}
353+
}
354+
355+
static class V3FunctionAccess implements FunctionAccess {
356+
@Nullable private static final MethodHandle functionToolCallHandle;
357+
@Nullable private static final MethodHandle idHandle;
358+
@Nullable private static final MethodHandle functionHandle;
359+
@Nullable private static final MethodHandle nameHandle;
360+
@Nullable private static final MethodHandle argumentsHandle;
361+
362+
static {
363+
MethodHandle functionToolCall;
364+
MethodHandle id;
365+
MethodHandle function;
366+
MethodHandle name;
367+
MethodHandle arguments;
368+
369+
try {
370+
MethodHandles.Lookup lookup = MethodHandles.lookup();
371+
functionToolCall =
372+
lookup.findVirtual(
373+
ChatCompletionMessageToolCall.class,
374+
"function",
375+
MethodType.methodType(Optional.class));
376+
Class<?> functionToolCallClass =
377+
Class.forName(
378+
"com.openai.models.chat.completions.ChatCompletionMessageFunctionToolCall");
379+
id = lookup.findVirtual(functionToolCallClass, "id", MethodType.methodType(String.class));
380+
Class<?> functionClass =
381+
Class.forName(
382+
"com.openai.models.chat.completions.ChatCompletionMessageFunctionToolCall$Function");
383+
function =
384+
lookup.findVirtual(
385+
functionToolCallClass, "function", MethodType.methodType(functionClass));
386+
name = lookup.findVirtual(functionClass, "name", MethodType.methodType(String.class));
387+
arguments =
388+
lookup.findVirtual(functionClass, "arguments", MethodType.methodType(String.class));
389+
} catch (Exception exception) {
390+
functionToolCall = null;
391+
id = null;
392+
function = null;
393+
name = null;
394+
arguments = null;
395+
}
396+
functionToolCallHandle = functionToolCall;
397+
idHandle = id;
398+
functionHandle = function;
399+
nameHandle = name;
400+
argumentsHandle = arguments;
401+
}
402+
403+
private final Object functionToolCall;
404+
private final Object function;
405+
406+
V3FunctionAccess(Object functionToolCall, Object function) {
407+
this.functionToolCall = functionToolCall;
408+
this.function = function;
409+
}
410+
411+
@Nullable
412+
@SuppressWarnings("unchecked")
413+
static FunctionAccess create(ChatCompletionMessageToolCall toolCall) {
414+
if (functionToolCallHandle == null || functionHandle == null) {
415+
return null;
416+
}
417+
418+
try {
419+
Optional<Object> optional = (Optional<Object>) functionToolCallHandle.invoke(toolCall);
420+
if (!optional.isPresent()) {
421+
return null;
422+
}
423+
Object functionToolCall = optional.get();
424+
return new V3FunctionAccess(functionToolCall, functionHandle.invoke(functionToolCall));
425+
} catch (Throwable ignore) {
426+
return null;
427+
}
428+
}
429+
430+
static boolean isAvailable() {
431+
return idHandle != null;
432+
}
433+
434+
@Override
435+
public String id() {
436+
return invokeStringHandle(idHandle, functionToolCall);
437+
}
438+
439+
@Override
440+
public String name() {
441+
return invokeStringHandle(nameHandle, function);
442+
}
443+
444+
@Override
445+
public String arguments() {
446+
return invokeStringHandle(argumentsHandle, function);
447+
}
448+
}
449+
234450
private ChatCompletionEventsHelper() {}
235451
}

instrumentation/openai/openai-java-1.1/library/src/test/java/io/opentelemetry/instrumentation/openai/v1_1/ChatTest.java

Lines changed: 9 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiSystemIncubatingValues.OPENAI;
2121
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiTokenTypeIncubatingValues.COMPLETION;
2222
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GenAiTokenTypeIncubatingValues.INPUT;
23-
import static java.util.Collections.emptyList;
2423
import static java.util.Collections.emptyMap;
2524
import static java.util.Collections.singletonList;
2625
import static org.assertj.core.api.Assertions.assertThat;
@@ -318,14 +317,14 @@ void toolCallsNoCaptureContent() {
318317
assertThat(toolCalls).hasSize(2);
319318
String newYorkCallId =
320319
toolCalls.stream()
321-
.filter(call -> call.function().arguments().contains("New York"))
322-
.map(ChatCompletionMessageToolCall::id)
320+
.filter(call -> testHelper.arguments(call).contains("New York"))
321+
.map(call -> testHelper.id(call))
323322
.findFirst()
324323
.get();
325324
String londonCallId =
326325
toolCalls.stream()
327-
.filter(call -> call.function().arguments().contains("London"))
328-
.map(ChatCompletionMessageToolCall::id)
326+
.filter(call -> testHelper.arguments(call).contains("London"))
327+
.map(call -> testHelper.id(call))
329328
.findFirst()
330329
.get();
331330

@@ -774,63 +773,18 @@ void streamToolCallsNoCaptureContent() {
774773
List<ChatCompletionChunk> chunks =
775774
doCompletionsStreaming(params, clientNoCaptureContent(), clientAsyncNoCaptureContent());
776775

777-
List<ChatCompletionMessageToolCall> toolCalls = new ArrayList<>();
778-
779-
ChatCompletionMessageToolCall.Builder currentToolCall = null;
780-
ChatCompletionMessageToolCall.Function.Builder currentFunction = null;
781-
StringBuilder currentArgs = null;
782-
783-
for (ChatCompletionChunk chunk : chunks) {
784-
List<ChatCompletionChunk.Choice.Delta.ToolCall> calls =
785-
chunk.choices().get(0).delta().toolCalls().orElse(emptyList());
786-
if (calls.isEmpty()) {
787-
continue;
788-
}
789-
for (ChatCompletionChunk.Choice.Delta.ToolCall call : calls) {
790-
if (call.id().isPresent()) {
791-
if (currentToolCall != null) {
792-
if (currentFunction != null && currentArgs != null) {
793-
currentFunction.arguments(currentArgs.toString());
794-
currentToolCall.function(currentFunction.build());
795-
}
796-
toolCalls.add(currentToolCall.build());
797-
}
798-
currentToolCall = ChatCompletionMessageToolCall.builder().id(call.id().get());
799-
currentFunction = ChatCompletionMessageToolCall.Function.builder();
800-
currentArgs = new StringBuilder();
801-
}
802-
if (call.function().isPresent()) {
803-
if (call.function().get().name().isPresent()) {
804-
if (currentFunction != null) {
805-
currentFunction.name(call.function().get().name().get());
806-
}
807-
}
808-
if (call.function().get().arguments().isPresent()) {
809-
if (currentArgs != null) {
810-
currentArgs.append(call.function().get().arguments().get());
811-
}
812-
}
813-
}
814-
}
815-
}
816-
if (currentToolCall != null) {
817-
if (currentFunction != null && currentArgs != null) {
818-
currentFunction.arguments(currentArgs.toString());
819-
currentToolCall.function(currentFunction.build());
820-
}
821-
toolCalls.add(currentToolCall.build());
822-
}
776+
List<ChatCompletionMessageToolCall> toolCalls = getToolCalls(chunks);
823777

824778
String newYorkCallId =
825779
toolCalls.stream()
826-
.filter(call -> call.function().arguments().contains("New York"))
827-
.map(ChatCompletionMessageToolCall::id)
780+
.filter(call -> testHelper.arguments(call).contains("New York"))
781+
.map(call -> testHelper.id(call))
828782
.findFirst()
829783
.get();
830784
String londonCallId =
831785
toolCalls.stream()
832-
.filter(call -> call.function().arguments().contains("London"))
833-
.map(ChatCompletionMessageToolCall::id)
786+
.filter(call -> testHelper.arguments(call).contains("London"))
787+
.map(call -> testHelper.id(call))
834788
.findFirst()
835789
.get();
836790

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
plugins {
2+
id("otel.java-conventions")
3+
}
4+
5+
dependencies {
6+
api(project(":testing-common"))
7+
compileOnly("com.openai:openai-java:3.0.0")
8+
}

0 commit comments

Comments
 (0)