Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -199,11 +199,14 @@ private static void tagOpenAIRequest(
@SneakyThrows
private static void tagOpenAIResponse(
Span span, JsonNode responseJson, @Nullable Long timeToFirstTokenNanoseconds) {
// Output — chat completions API uses "choices"; Responses API uses "output"
// Output — chat completions API uses "choices"; Responses API uses "output"; audio
// transcriptions and translations return a text-keyed object.
if (responseJson.has("choices")) {
span.setAttribute("braintrust.output_json", toJson(responseJson.get("choices")));
} else if (responseJson.has("output")) {
span.setAttribute("braintrust.output_json", toJson(responseJson.get("output")));
} else if (responseJson.has("text")) {
span.setAttribute("braintrust.output_json", toJson(responseJson));
}

Map<String, Object> metrics = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

/**
* Covers {@link InstrumentationSemConv#addServerSideChildSpans}: only vendor-executed (server-side)
* tool calls in an OpenAI Responses body become child spans nested under the LLM span. Client-side
* calls ({@code function_call}, {@code computer_call}) are excluded.
*/
/** Covers provider response tagging and {@link InstrumentationSemConv#addServerSideChildSpans}. */
class InstrumentationSemConvTest {

private static final AttributeKey<String> SPAN_ATTRIBUTES =
Expand Down Expand Up @@ -81,6 +77,44 @@ private static JsonNode json(String s) {
return BraintrustJsonMapper.fromJson(s, JsonNode.class);
}

private SpanData tagOpenAIResponse(String responseBody) {
Span span = tracer.spanBuilder("llm").startSpan();
try {
InstrumentationSemConv.tagLLMSpanResponse(
tracer, span, InstrumentationSemConv.PROVIDER_NAME_OPENAI, responseBody);
} finally {
span.end();
}
return byName(exporter.getFinishedSpanItems(), "llm");
}

@Test
void tagsOpenAIAudioTranscriptionOutput() {
SpanData span = tagOpenAIResponse("{\"text\":\"Hello from the recording.\"}");

assertEquals(
json("{\"text\":\"Hello from the recording.\"}"),
json(span.getAttributes().get(OUTPUT_JSON)));
}

@Test
void tagsOpenAIVerboseAudioTranscriptionOutput() {
String body =
"""
{
"text": "Hello from the recording.",
"language": "english",
"duration": 1.25,
"segments": [{"id": 0, "text": "Hello from the recording."}],
"words": [{"word": "Hello", "start": 0.0, "end": 0.4}]
}
""";

SpanData span = tagOpenAIResponse(body);

assertEquals(json(body), json(span.getAttributes().get(OUTPUT_JSON)));
}

@Test
void emitsWebSearchCallToolSpanParentedToLlm() {
String body =
Expand Down
Loading