Emit gen_ai.* attributes on APM spans - #12450
Conversation
Write the scalar gen_ai.* subset of an LLM Observability span onto the APM span, so model, provider, application, conversation and token usage become indexed, searchable APM tags. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
🟢 Java Benchmark SLOs — All performance SLOs passed
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
🛡️ Codex Security Review · Automatically triggeredSecurity review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
|
Codex Review: Didn't find any major issues. Swish! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
The openai-java instrumentation keeps tracing when LLM Observability is disabled, and the operation, model, provider and ml_app are all known on that path. Token usage and conversation id are not computed there, so they stay unreported. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The repository does not accept new .groovy files. The module already has a JUnit forked-test base for LLMObs configurations, so the coverage moves there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
More details
The finish-time mapping reads only scalar LLM Observability tags. It keeps message bodies off APM spans and limits usage metrics to LLM and embedding spans.
🤖 Datadog Autotest · Commit 1527dca · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Codex Review: Didn't find any major issues. Keep it up! Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
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". |
| * Writes the subset available with LLM Observability disabled. Token usage and conversation id | ||
| * are never computed on that path, so they are left out. | ||
| */ | ||
| public static void applyWithoutLlmObs( |
There was a problem hiding this comment.
Is it possible to have one method that is called for all situations regardless of whether llmobs is enabled (although I think this might be a bit more annoying in Java since there are no optional parameters)?
Feel free to disregard if it ends up making things more complex.
| return value == null || value.isEmpty() ? null : value; | ||
| } | ||
|
|
||
| private static String stringTag(AgentSpan span, String key) { |
There was a problem hiding this comment.
This seems to be repeated in the OpenAiDecorator.java file. Is there a way to avoid this duplication?
| "gen_ai.request.model" String | ||
| "gen_ai.provider.name" "openai" | ||
| "gen_ai.application.name" String | ||
| if (!isStreaming) { |
There was a problem hiding this comment.
Do we not collect token usage for streamed Open AI spans?
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
What Does This Do
Emits the scalar
gen_ai.*attributes onto APM spans, making them indexed APM tags:gen_ai.operation.name,gen_ai.request.model,gen_ai.provider.name,gen_ai.application.name,gen_ai.conversation.idgen_ai.usage.{input,output,total,cache_read_input,cache_write_input,reasoning_output}_tokensGenAiApmTagsreads the values back off the span's_ml_obs_tag.*/_ml_obs_metric.*keys at finish time and is called from both emission sites: manual SDK spans (DDLLMObsSpan.finish) and auto-instrumentation (OpenAiDecorator.doBeforeFinish). SinceLLMObsSpanMapperonly serializes_ml_obs_*keys, the unprefixed tags land on the APM span only.Message bodies (input, output, tool definitions, retrieval documents) stay off the APM span and keep coming from the LLMObs track. Port of DataDog/dd-trace-py#20083 and DataDog/dd-trace-js#10261.
Motivation
The APM trace UI merges these keys from the LLMObs track client-side, so the values render but aren't indexed. You can't filter, facet, or monitor on model, provider, or token usage in APM.
Additional Notes
llmandembeddingspans always get model and provider, falling back tocustom(matching what the LLMObs event already writes), and are the only kinds that getgen_ai.usage.*. Other kinds get model fields only when explicitly set, since their metrics would be misleading under agen_ai.usage.*key.Emission runs ahead of
span.finish()while the span is still mutable, in its own try/catch so a failure can't cost the LLMObs event.The openai-java instrumentation keeps tracing with LLM Observability disabled, so
applyWithoutLlmObsemits what is resolvable there: operation (fromopenai.request.endpoint), model (fromopenai.response.model, falling back to the request model), provider, and ml_app. Token usage and conversation id are not computed on that path and stay unreported, which is the one place this diverges from the Python PR. Java has no_dd.llmobs.*shadow-tag equivalent to read them from.Verified against live OpenAI calls with the local agent build. With LLM Observability enabled:
With it disabled:
🤖 Generated with Claude Code