77
88import static io .opentelemetry .api .common .AttributeKey .stringKey ;
99
10- import com .fasterxml .jackson .databind .ObjectMapper ;
1110import com .openai .models .chat .completions .ChatCompletion ;
1211import com .openai .models .chat .completions .ChatCompletionAssistantMessageParam ;
1312import com .openai .models .chat .completions .ChatCompletionContentPartText ;
2423import io .opentelemetry .api .logs .Logger ;
2524import io .opentelemetry .api .trace .Span ;
2625import io .opentelemetry .context .Context ;
27- import java .lang .invoke .MethodHandle ;
28- import java .lang .invoke .MethodHandles ;
29- import java .lang .invoke .MethodType ;
3026import java .util .HashMap ;
3127import java .util .List ;
3228import java .util .Map ;
3329import java .util .Objects ;
34- import java .util .Optional ;
3530import java .util .stream .Collectors ;
36- import javax .annotation .Nullable ;
3731import lombok .SneakyThrows ;
3832import lombok .extern .slf4j .Slf4j ;
3933
4034@ Slf4j
4135final 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