Skip to content

Add head-based sampling for LLM Observability traces [MLOB-7815] - #12277

Draft
ncybul wants to merge 2 commits into
masterfrom
nicole.cybul/llmobs-java-head-based-sampling
Draft

Add head-based sampling for LLM Observability traces [MLOB-7815]#12277
ncybul wants to merge 2 commits into
masterfrom
nicole.cybul/llmobs-java-head-based-sampling

Conversation

@ncybul

@ncybul ncybul commented Aug 24, 2026

Copy link
Copy Markdown

What Does This Do

Adds head-based retention sampling for LLM Observability traces, controlled by a new DD_LLMOBS_SAMPLE_RATE (dd.llmobs.sample.rate) config, defaulting to 1.0.

At the root of an LLMObs trace, the tracer makes a deterministic keep/drop decision keyed on the APM trace ID, then stamps sampling_decision and sample_rate into the _dd block of every span in that trace. Descendant spans inherit the decision unchanged, so a trace is retained or dropped as a whole.

The SDK never drops a span. Every LLMObs span is still sent to the intake at 100%; the intake performs the drop. This is deliberate: token and cost metrics are computed over full volume regardless of the configured sample rate.

Since Java does not currently propagate LLMObs contexts, communicating the sampling decision across process boundaries is follow-up work. For now, services configured at the same rate independently reach the same decision for the same trace, because the algorithm is deterministic in the APM trace ID and sample rate.

Motivation

Python and Node already support LLMObs head-based sampling; Java did not. Customers running high-volume LLM workloads have no way to control retention volume from the Java tracer.

Claude session: f94e7cd8-1c7d-41b1-835f-553f557f2d04
Resume: claude --resume f94e7cd8-1c7d-41b1-835f-553f557f2d04

@ncybul ncybul added tag: ai generated Largely based on code generated by an AI or LLM comp: mlobs ML Observability (LLMObs) type: feature Enhancements and improvements labels Aug 24, 2026
@ncybul ncybul changed the title Add head-based sampling for LLM Observability traces Add head-based sampling for LLM Observability traces [MLOB-7815] Aug 24, 2026
@datadog-datadog-prod-us1-2

This comment has been minimized.

@dd-octo-sts

dd-octo-sts Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

🟢 Java Benchmark SLOs — All performance SLOs passed

Suite Status
Startup 🟢 pass

SLO thresholds are defined here based on automatically generated metrics. A warning is raised when results are within 5% of the threshold.

PR vs. master results
Scenario Candidate master Δ (95% CI of mean)
startup:insecure-bank:iast:Agent 14.79 s 14.64 s [+0.2%; +1.8%] (maybe worse)
startup:insecure-bank:tracing:Agent 13.65 s 13.62 s [-0.7%; +1.1%] (no difference)
startup:petclinic:appsec:Agent 17.45 s 17.41 s [-0.6%; +1.0%] (no difference)
startup:petclinic:iast:Agent 17.48 s 17.52 s [-1.0%; +0.6%] (no difference)
startup:petclinic:profiling:Agent 16.67 s 17.51 s [-9.2%; -0.5%] (maybe better)
startup:petclinic:sca:Agent 17.46 s 17.30 s [-0.1%; +2.0%] (no difference)
startup:petclinic:tracing:Agent 16.64 s 16.75 s [-1.5%; +0.2%] (no difference)

Commit: f9f5c5c4 · CI Pipeline · Benchmarking Platform UI


Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion.

Comment thread internal-api/src/main/java/datadog/trace/api/llmobs/LLMObsContext.java Outdated
Comment thread internal-api/src/main/java/datadog/trace/api/llmobs/LLMObsContext.java Outdated
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ncybul

ncybul commented Aug 25, 2026

Copy link
Copy Markdown
Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f9f5c5c40a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +186 to +188
boolean stamped = rawSamplingDecision instanceof String && rawSampleRate instanceof String;
String samplingDecision = stamped ? (String) rawSamplingDecision : SAMPLING_DECISION_SAMPLED;
String sampleRate = stamped ? (String) rawSampleRate : SAMPLE_RATE_ALL;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Sample auto-instrumented LLMObs spans

When an OpenAI Java request creates an LLMObs span without a surrounding manual DDLLMObsSpan, OpenAiDecorator.doAfterStart is the other production span-creation path and never stamps either sampling tag. This fallback consequently emits sampling_decision=1 and sample_rate=1, so even DD_LLMOBS_SAMPLE_RATE=0 retains every standalone auto-instrumented OpenAI trace. Apply the configured decision in that path or in a shared LLMObs span initializer.

Useful? React with 👍 / 👎.

Comment on lines +233 to +236
// Remove unconditionally: the generic _ml_obs_tag. sweep below would otherwise surface these
// internal fields in the user-visible tags[] array.
span.removeTag(SAMPLING_DECISION_TAG_INTERNAL_FULL);
span.removeTag(SAMPLE_RATE_TAG_INTERNAL_FULL);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve sampling tags across serialization retries

When the MsgPack buffer fills after these removals, MsgPackWriter.format resets the buffer and invokes map(..., true) on the same spans. The retry then reads missing sampling tags and falls back to the retain-all 1/1 pair, so a trace originally marked dropped can be retained merely because serialization crossed a payload boundary or overflowed while writing later metadata. Preserve the original values for retries or defer mutation until serialization succeeds.

Useful? React with 👍 / 👎.

private final boolean llmObsAgentlessEnabled;
private final String llmObsAgentlessUrl;
private final String llmObsMlApp;
private final double llmObsSampleRate;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include the sample rate in configuration diagnostics

The new llmObsSampleRate field is initialized and exposed but never appended to Config.toString(), so startup configuration diagnostics cannot show whether DD_LLMOBS_SAMPLE_RATE was loaded or clamped. The repository's required new-configuration procedure explicitly includes adding the value to Config.toString(); include it with the other diagnostic fields.

AGENTS.md reference: AGENTS.md:L37-L44

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: mlobs ML Observability (LLMObs) tag: ai generated Largely based on code generated by an AI or LLM type: feature Enhancements and improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant