diff --git a/dd-java-agent/agent-llmobs/src/main/java/datadog/trace/llmobs/domain/LLMObsEval.java b/dd-java-agent/agent-llmobs/src/main/java/datadog/trace/llmobs/domain/LLMObsEval.java index f38f00f4c4c..d641212b539 100644 --- a/dd-java-agent/agent-llmobs/src/main/java/datadog/trace/llmobs/domain/LLMObsEval.java +++ b/dd-java-agent/agent-llmobs/src/main/java/datadog/trace/llmobs/domain/LLMObsEval.java @@ -5,6 +5,7 @@ import com.squareup.moshi.JsonReader; import com.squareup.moshi.JsonWriter; import com.squareup.moshi.Moshi; +import datadog.trace.api.DDTraceApiInfo; import datadog.trace.llmobs.LLMObsIntakeWorker; import java.io.IOException; import java.util.List; @@ -45,7 +46,10 @@ public LLMObsEval( this.ml_app = mlApp; this.metric_type = metricType; this.label = label; - this.tags = tags == null ? null : IntakeTags.flatten(tags); + // Every submission carries the tracer version and the ML app, as dd-trace-py and dd-trace-js + // do, so that evals can be filtered by tag and not only by the top level ml_app field. + this.tags = + IntakeTags.flatten(tags, "ddtrace.version:" + DDTraceApiInfo.VERSION, "ml_app:" + mlApp); } /** diff --git a/dd-java-agent/agent-llmobs/src/test/java/datadog/trace/llmobs/domain/LLMObsEvalTest.java b/dd-java-agent/agent-llmobs/src/test/java/datadog/trace/llmobs/domain/LLMObsEvalTest.java index c293537a3f9..381ef849824 100644 --- a/dd-java-agent/agent-llmobs/src/test/java/datadog/trace/llmobs/domain/LLMObsEvalTest.java +++ b/dd-java-agent/agent-llmobs/src/test/java/datadog/trace/llmobs/domain/LLMObsEvalTest.java @@ -6,6 +6,7 @@ import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.Moshi; import com.squareup.moshi.Types; +import datadog.trace.api.DDTraceApiInfo; import java.io.IOException; import java.util.Arrays; import java.util.Collections; @@ -21,8 +22,9 @@ * an adapter held by the worker. These tests exist to prove that indirection changed nothing on the * wire: the same key set as before, no {@code submitter}, no feedback-only target. * - *
The single intentional addition is {@code event_kind:"evaluation"}, which discriminates - * evaluations from feedback the way dd-trace-py and dd-trace-js do. + *
The intentional additions are {@code event_kind:"evaluation"}, which discriminates evaluations + * from feedback, and the {@code ddtrace.version} and {@code ml_app} tags every submission carries. + * Both align the payload with dd-trace-py and dd-trace-js. */ class LLMObsEvalTest { @@ -67,9 +69,12 @@ void testScoreEvalCarriesTheV1KeySetAndNothingElse() throws IOException { assertEquals("score", metric.get("metric_type")); assertEquals("sentiment", metric.get("label")); assertEquals(0.75, metric.get("score_value")); - assertEquals(Collections.singletonList("source:web-ui"), metric.get("tags")); + assertEquals( + Arrays.asList( + "ddtrace.version:" + DDTraceApiInfo.VERSION, "ml_app:my-app", "source:web-ui"), + metric.get("tags")); - // The only addition to the v1 payload. + // The additions to the v1 payload. assertEquals("evaluation", metric.get("event_kind")); // Feedback-only keys must never leak into the v1 payload. @@ -92,8 +97,10 @@ void testCategoricalEvalCarriesTheV1KeySet() throws IOException { assertEquals("categorical", metric.get("metric_type")); assertEquals("positive", metric.get("categorical_value")); assertFalse(metric.containsKey("score_value"), metric.toString()); - // A null tag map is omitted rather than serialized as an empty list. - assertFalse(metric.containsKey("tags"), metric.toString()); + // A null user tag map still yields the tags every submission carries. + assertEquals( + Arrays.asList("ddtrace.version:" + DDTraceApiInfo.VERSION, "ml_app:my-app"), + metric.get("tags")); assertEquals("evaluation", metric.get("event_kind")); }