2525import com .openai .models .responses .ResponseOutputMessage ;
2626import com .openai .models .responses .ResponseOutputText ;
2727import com .openai .models .responses .ResponseStreamEvent ;
28- import com .openai .models .responses .ResponseUsage ;
2928import datadog .trace .api .DDSpanId ;
3029import datadog .trace .api .llmobs .LLMObs ;
3130import datadog .trace .api .llmobs .LLMObsContext ;
@@ -353,8 +352,7 @@ public void withCreateEmbeddingResponse(AgentSpan span, CreateEmbeddingResponse
353352 }
354353 }
355354
356- public void withResponseCreateParams (
357- AgentSpan span , ResponseCreateParams params , boolean stream ) {
355+ public void withResponseCreateParams (AgentSpan span , ResponseCreateParams params ) {
358356 span .setTag ("_ml_obs_tag.span.kind" , Tags .LLMOBS_LLM_SPAN_KIND );
359357 span .setResourceName (RESPONSES_CREATE );
360358 span .setTag ("openai.request.endpoint" , "v1/responses" );
@@ -365,24 +363,54 @@ public void withResponseCreateParams(
365363 // Use ResponseCreateParams._model() b/o ResponseCreateParams.model() changed type from
366364 // ResponsesModel to Optional<ResponsesModel> in
367365 // https://github.com/openai/openai-java/commit/87dd64658da6cec7564f3b571e15ec0e2db0660b
368- span .setTag (REQUEST_MODEL , extractResponseModel (params ._model ()));
366+ String modelName = extractResponseModel (params ._model ());
367+ span .setTag (REQUEST_MODEL , modelName );
369368
369+ // Set model_name and model_provider as fallback (will be overridden by withResponse if called)
370+ // span.setTag("_ml_obs_tag.model_name", modelName);
371+ // span.setTag("_ml_obs_tag.model_provider", "openai");
372+
373+ List <LLMObs .LLMMessage > inputMessages = new ArrayList <>();
374+
375+ // Add instructions as system message first (if present)
376+ params
377+ .instructions ()
378+ .ifPresent (
379+ instructions -> {
380+ inputMessages .add (LLMObs .LLMMessage .from ("system" , instructions ));
381+ });
382+
383+ // Add user input message
370384 Optional <String > textOpt = params ._input ().asString ();
371385 if (textOpt .isPresent ()) {
372- LLMObs .LLMMessage msg = LLMObs .LLMMessage .from ("user" , textOpt .get ());
373- span .setTag ("_ml_obs_tag.input" , Collections .singletonList (msg ));
386+ inputMessages .add (LLMObs .LLMMessage .from ("user" , textOpt .get ()));
374387 }
375388
376- Map <String , Object > metadata = new HashMap <>();
377- params .maxOutputTokens ().ifPresent (v -> metadata .put ("max_tokens" , v ));
378- params .temperature ().ifPresent (v -> metadata .put ("temperature" , v ));
379- if (stream ) {
380- metadata .put ("stream" , true );
389+ if (!inputMessages .isEmpty ()) {
390+ span .setTag ("_ml_obs_tag.input" , inputMessages );
381391 }
382- span .setTag ("_ml_obs_tag.metadata" , metadata );
383392 }
384393
385394 public void withResponse (AgentSpan span , Response response ) {
395+ withResponse (span , response , false );
396+ }
397+
398+ public void withResponseStreamEvents (AgentSpan span , List <ResponseStreamEvent > events ) {
399+ for (ResponseStreamEvent event : events ) {
400+ if (event .isCompleted ()) {
401+ Response response = event .asCompleted ().response ();
402+ withResponse (span , response , true );
403+ return ;
404+ }
405+ if (event .isIncomplete ()) {
406+ Response response = event .asIncomplete ().response ();
407+ withResponse (span , response , true );
408+ return ;
409+ }
410+ }
411+ }
412+
413+ private void withResponse (AgentSpan span , Response response , boolean stream ) {
386414 String modelName = extractResponseModel (response ._model ());
387415 span .setTag (RESPONSE_MODEL , modelName );
388416 span .setTag ("_ml_obs_tag.model_name" , modelName );
@@ -393,7 +421,72 @@ public void withResponse(AgentSpan span, Response response) {
393421 span .setTag ("_ml_obs_tag.output" , outputMessages );
394422 }
395423
396- response .usage ().ifPresent (usage -> withResponseUsage (span , usage ));
424+ Map <String , Object > metadata = new HashMap <>();
425+
426+ response .maxOutputTokens ().ifPresent (v -> metadata .put ("max_output_tokens" , v ));
427+ response .temperature ().ifPresent (v -> metadata .put ("temperature" , v ));
428+ response .topP ().ifPresent (v -> metadata .put ("top_p" , v ));
429+
430+ // Extract tool_choice as string
431+ Response .ToolChoice toolChoice = response .toolChoice ();
432+ if (toolChoice .isOptions ()) {
433+ metadata .put ("tool_choice" , toolChoice .asOptions ()._value ().asString ().orElse (null ));
434+ } else if (toolChoice .isTypes ()) {
435+ metadata .put ("tool_choice" , toolChoice .asTypes ().type ().toString ().toLowerCase ());
436+ } else if (toolChoice .isFunction ()) {
437+ metadata .put ("tool_choice" , "function" );
438+ }
439+
440+ // Extract truncation as string
441+ response
442+ .truncation ()
443+ .ifPresent (
444+ (Response .Truncation t ) ->
445+ metadata .put ("truncation" , t ._value ().asString ().orElse (null )));
446+
447+ // Extract text format
448+ response
449+ .text ()
450+ .ifPresent (
451+ textConfig -> {
452+ textConfig
453+ .format ()
454+ .ifPresent (
455+ format -> {
456+ Map <String , Object > textMap = new HashMap <>();
457+ Map <String , String > formatMap = new HashMap <>();
458+ if (format .isText ()) {
459+ formatMap .put ("type" , "text" );
460+ // metadata.put("text.format.type", "text");
461+ // } else if (format.isJsonSchema()) {
462+ // formatMap.put("type", "json_schema");
463+ // } else if (format.isJsonObject()) {
464+ // formatMap.put("type", "json_object");
465+ }
466+ textMap .put ("format" , formatMap );
467+ metadata .put ("text" , textMap );
468+ });
469+ });
470+
471+ if (stream ) {
472+ metadata .put ("stream" , true );
473+ }
474+
475+ response
476+ .usage ()
477+ .ifPresent (
478+ usage -> {
479+ span .setTag ("_ml_obs_metric.input_tokens" , usage .inputTokens ());
480+ span .setTag ("_ml_obs_metric.output_tokens" , usage .outputTokens ());
481+ span .setTag ("_ml_obs_metric.total_tokens" , usage .totalTokens ());
482+ span .setTag (
483+ "_ml_obs_metric.cache_read_input_tokens" ,
484+ usage .inputTokensDetails ().cachedTokens ());
485+ long reasoningTokens = usage .outputTokensDetails ().reasoningTokens ();
486+ metadata .put ("reasoning_tokens" , reasoningTokens );
487+ });
488+
489+ span .setTag ("_ml_obs_tag.metadata" , metadata );
397490 }
398491
399492 private List <LLMObs .LLMMessage > extractResponseOutputMessages (List <ResponseOutputItem > output ) {
@@ -431,14 +524,6 @@ private String extractMessageContent(ResponseOutputMessage message) {
431524 return result .isEmpty () ? null : result ;
432525 }
433526
434- private static void withResponseUsage (AgentSpan span , ResponseUsage usage ) {
435- span .setTag ("_ml_obs_metric.input_tokens" , usage .inputTokens ());
436- span .setTag ("_ml_obs_metric.output_tokens" , usage .outputTokens ());
437- span .setTag ("_ml_obs_metric.total_tokens" , usage .totalTokens ());
438- span .setTag (
439- "_ml_obs_metric.cache_read_input_tokens" , usage .inputTokensDetails ().cachedTokens ());
440- }
441-
442527 private String extractResponseModel (JsonField <ResponsesModel > model ) {
443528 Optional <String > str = model .asString ();
444529 if (str .isPresent ()) {
@@ -465,15 +550,4 @@ private String extractResponseModel(JsonField<ResponsesModel> model) {
465550 }
466551 return null ;
467552 }
468-
469- public void withResponseStreamEvent (AgentSpan span , List <ResponseStreamEvent > events ) {
470- // Find the completed event which contains the full response
471- for (ResponseStreamEvent event : events ) {
472- if (event .isCompleted ()) {
473- Response response = event .asCompleted ().response ();
474- withResponse (span , response );
475- return ;
476- }
477- }
478- }
479553}
0 commit comments