Skip to content

Commit 3e3afe5

Browse files
authored
Merge pull request #2 from braintrustdata/ark/BRA-3203-otel-attachments
Ark/bra 3203 otel attachments
2 parents 831b4c1 + 4a38281 commit 3e3afe5

7 files changed

Lines changed: 1110 additions & 253 deletions

File tree

src/main/java/dev/braintrust/instrumentation/openai/otel/ChatCompletionEventsHelper.java

Lines changed: 12 additions & 243 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import static io.opentelemetry.api.common.AttributeKey.stringKey;
99

10-
import com.fasterxml.jackson.databind.ObjectMapper;
1110
import com.openai.models.chat.completions.ChatCompletion;
1211
import com.openai.models.chat.completions.ChatCompletionAssistantMessageParam;
1312
import com.openai.models.chat.completions.ChatCompletionContentPartText;
@@ -24,36 +23,28 @@
2423
import io.opentelemetry.api.logs.Logger;
2524
import io.opentelemetry.api.trace.Span;
2625
import io.opentelemetry.context.Context;
27-
import java.lang.invoke.MethodHandle;
28-
import java.lang.invoke.MethodHandles;
29-
import java.lang.invoke.MethodType;
3026
import java.util.HashMap;
3127
import java.util.List;
3228
import java.util.Map;
3329
import java.util.Objects;
34-
import java.util.Optional;
3530
import java.util.stream.Collectors;
36-
import javax.annotation.Nullable;
3731
import lombok.SneakyThrows;
3832
import lombok.extern.slf4j.Slf4j;
3933

4034
@Slf4j
4135
final class ChatCompletionEventsHelper {
4236

4337
private static final AttributeKey<String> EVENT_NAME = stringKey("event.name");
44-
private static final ObjectMapper JSON_MAPPER =
45-
new com.fasterxml.jackson.databind.ObjectMapper();
4638

4739
@SneakyThrows
4840
public static void emitPromptLogEvents(
4941
Context context,
5042
Logger eventLogger,
5143
ChatCompletionCreateParams request,
5244
boolean captureMessageContent) {
53-
Span.current()
54-
.setAttribute(
55-
"braintrust.input_json",
56-
JSON_MAPPER.writeValueAsString(request.messages()));
45+
String semconvJson = GenAiSemconvSerializer.serializeInputMessages(request.messages());
46+
Span span = Span.current();
47+
span.setAttribute("gen_ai.input.messages", semconvJson);
5748
}
5849

5950
private static String contentToString(ChatCompletionToolMessageParam.Content content) {
@@ -138,13 +129,12 @@ public static void emitCompletionLogEvents(
138129
} else if (completion.choices().size() > 1) {
139130
log.debug("multiple choices in OAI response: {}", completion.choices().size());
140131
} else {
141-
Span.current()
142-
.setAttribute(
143-
"braintrust.output_json",
144-
JSON_MAPPER.writeValueAsString(
145-
new ChatCompletionMessage[] {
146-
completion.choices().get(0).message()
147-
}));
132+
// Set gen_ai.output.messages attribute for single choice (most common case)
133+
ChatCompletion.Choice choice = completion.choices().get(0);
134+
String outputJson =
135+
GenAiSemconvSerializer.serializeOutputMessage(
136+
choice.message(), choice.finishReason().toString());
137+
Span.current().setAttribute("gen_ai.output.messages", outputJson);
148138
}
149139
for (ChatCompletion.Choice choice : completion.choices()) {
150140
ChatCompletionMessage choiceMsg = choice.message();
@@ -211,7 +201,8 @@ private static LogRecordBuilder newEvent(Logger eventLogger, String name) {
211201
private static Value<?> buildToolCallEventObject(
212202
ChatCompletionMessageToolCall call, boolean captureMessageContent) {
213203
Map<String, Value<?>> result = new HashMap<>();
214-
FunctionAccess functionAccess = getFunctionAccess(call);
204+
GenAiSemconvSerializer.FunctionAccess functionAccess =
205+
GenAiSemconvSerializer.getFunctionAccess(call);
215206
if (functionAccess != null) {
216207
result.put("id", Value.of(functionAccess.id()));
217208
result.put(
@@ -223,7 +214,7 @@ private static Value<?> buildToolCallEventObject(
223214
}
224215

225216
private static Value<?> buildFunctionEventObject(
226-
FunctionAccess functionAccess, boolean captureMessageContent) {
217+
GenAiSemconvSerializer.FunctionAccess functionAccess, boolean captureMessageContent) {
227218
Map<String, Value<?>> result = new HashMap<>();
228219
result.put("name", Value.of(functionAccess.name()));
229220
if (captureMessageContent) {
@@ -232,227 +223,5 @@ private static Value<?> buildFunctionEventObject(
232223
return Value.of(result);
233224
}
234225

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

0 commit comments

Comments
 (0)