2020import com .openai .models .embeddings .EmbeddingCreateParams ;
2121import com .openai .models .responses .Response ;
2222import com .openai .models .responses .ResponseCreateParams ;
23+ import com .openai .models .responses .ResponseFunctionToolCall ;
24+ import com .openai .models .responses .ResponseOutputItem ;
25+ import com .openai .models .responses .ResponseOutputMessage ;
26+ import com .openai .models .responses .ResponseOutputText ;
2327import com .openai .models .responses .ResponseStreamEvent ;
28+ import com .openai .models .responses .ResponseUsage ;
2429import datadog .trace .api .DDSpanId ;
2530import datadog .trace .api .llmobs .LLMObs ;
2631import datadog .trace .api .llmobs .LLMObsContext ;
@@ -348,7 +353,8 @@ public void withCreateEmbeddingResponse(AgentSpan span, CreateEmbeddingResponse
348353 }
349354 }
350355
351- public void withResponseCreateParams (AgentSpan span , ResponseCreateParams params ) {
356+ public void withResponseCreateParams (
357+ AgentSpan span , ResponseCreateParams params , boolean stream ) {
352358 span .setTag ("_ml_obs_tag.span.kind" , Tags .LLMOBS_LLM_SPAN_KIND );
353359 span .setResourceName (RESPONSES_CREATE );
354360 span .setTag ("openai.request.endpoint" , "v1/responses" );
@@ -360,12 +366,77 @@ public void withResponseCreateParams(AgentSpan span, ResponseCreateParams params
360366 // ResponsesModel to Optional<ResponsesModel> in
361367 // https://github.com/openai/openai-java/commit/87dd64658da6cec7564f3b571e15ec0e2db0660b
362368 span .setTag (REQUEST_MODEL , extractResponseModel (params ._model ()));
369+
370+ Optional <String > textOpt = params ._input ().asString ();
371+ if (textOpt .isPresent ()) {
372+ LLMObs .LLMMessage msg = LLMObs .LLMMessage .from ("user" , textOpt .get ());
373+ span .setTag ("_ml_obs_tag.input" , Collections .singletonList (msg ));
374+ }
375+
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 );
381+ }
382+ span .setTag ("_ml_obs_tag.metadata" , metadata );
363383 }
364384
365385 public void withResponse (AgentSpan span , Response response ) {
366- span .setTag (RESPONSE_MODEL , extractResponseModel (response ._model ()));
386+ String modelName = extractResponseModel (response ._model ());
387+ span .setTag (RESPONSE_MODEL , modelName );
388+ span .setTag ("_ml_obs_tag.model_name" , modelName );
389+ span .setTag ("_ml_obs_tag.model_provider" , "openai" );
367390
368- // TODO set LLMObs tags
391+ List <LLMObs .LLMMessage > outputMessages = extractResponseOutputMessages (response .output ());
392+ if (!outputMessages .isEmpty ()) {
393+ span .setTag ("_ml_obs_tag.output" , outputMessages );
394+ }
395+
396+ response .usage ().ifPresent (usage -> withResponseUsage (span , usage ));
397+ }
398+
399+ private List <LLMObs .LLMMessage > extractResponseOutputMessages (List <ResponseOutputItem > output ) {
400+ List <LLMObs .LLMMessage > messages = new ArrayList <>();
401+ List <LLMObs .ToolCall > toolCalls = new ArrayList <>();
402+ String textContent = null ;
403+
404+ for (ResponseOutputItem item : output ) {
405+ if (item .isFunctionCall ()) {
406+ ResponseFunctionToolCall functionCall = item .asFunctionCall ();
407+ LLMObs .ToolCall toolCall = ToolCallExtractor .getToolCall (functionCall );
408+ if (toolCall != null ) {
409+ toolCalls .add (toolCall );
410+ }
411+ } else if (item .isMessage ()) {
412+ ResponseOutputMessage message = item .asMessage ();
413+ textContent = extractMessageContent (message );
414+ }
415+ }
416+
417+ messages .add (LLMObs .LLMMessage .from ("assistant" , textContent , toolCalls ));
418+
419+ return messages ;
420+ }
421+
422+ private String extractMessageContent (ResponseOutputMessage message ) {
423+ StringBuilder contentBuilder = new StringBuilder ();
424+ for (ResponseOutputMessage .Content content : message .content ()) {
425+ if (content .isOutputText ()) {
426+ ResponseOutputText outputText = content .asOutputText ();
427+ contentBuilder .append (outputText .text ());
428+ }
429+ }
430+ String result = contentBuilder .toString ();
431+ return result .isEmpty () ? null : result ;
432+ }
433+
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 ());
369440 }
370441
371442 private String extractResponseModel (JsonField <ResponsesModel > model ) {
@@ -396,11 +467,13 @@ private String extractResponseModel(JsonField<ResponsesModel> model) {
396467 }
397468
398469 public void withResponseStreamEvent (AgentSpan span , List <ResponseStreamEvent > events ) {
399- if (!events .isEmpty ()) {
400- // ResponseStreamEvent responseStreamEvent = events.get(0);
401- // span.setTag(RESPONSE_MODEL, responseStreamEvent.res()); // TODO there is no model
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+ }
402477 }
403-
404- // TODO set LLMObs tags
405478 }
406479}
0 commit comments