Skip to content

Commit 4bdfc39

Browse files
committed
fix(llmobs/openai-java): inherit session_id on auto-instrumented openai.request span
OpenAiDecorator.afterStart() already consulted LLMObsContext.current() to set the LLMObs parent_id tag but never read session_id from the context. Auto-instrumented openai.request spans therefore carried no _ml_obs_tag.session_id even when a manual LLMObs workflow parent had one. dd-trace-py and dd-trace-js both auto-propagate session_id to auto-instrumented LLM spans via parent context. Add a SESSION_ID constant to CommonTags. In OpenAiDecorator.afterStart(), after the existing parent_id block, read LLMObsContext.currentSessionId() and stamp it on the span as CommonTags.SESSION_ID when present. With this change, auto-instrumented openai.request spans now appear under their session in the LLM Trace Explorer's Sessions view, matching Python and Node behavior. Depends on the LLMObsContext.currentSessionId() API added in the preceding fix(llmobs): propagate session_id from parent context commit. Originating issue: MLOS-646.
1 parent cd29846 commit 4bdfc39

3 files changed

Lines changed: 74 additions & 0 deletions

File tree

dd-java-agent/instrumentation/openai-java/openai-java-3.0/src/main/java/datadog/trace/instrumentation/openai_java/CommonTags.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ interface CommonTags {
3030
String ENV = TAG_PREFIX + "env";
3131
String SERVICE = TAG_PREFIX + "service";
3232
String PARENT_ID = TAG_PREFIX + "parent_id";
33+
String SESSION_ID = TAG_PREFIX + LLMObsTags.SESSION_ID;
3334

3435
String TOOL_DEFINITIONS = TAG_PREFIX + "tool_definitions";
3536

dd-java-agent/instrumentation/openai-java/openai-java-3.0/src/main/java/datadog/trace/instrumentation/openai_java/OpenAiDecorator.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,16 @@ public AgentSpan afterStart(AgentSpan span) {
115115
parentSpanId = String.valueOf(parent.getSpanId());
116116
}
117117
span.setTag(CommonTags.PARENT_ID, parentSpanId);
118+
119+
// Inherit session_id from the active LLMObs parent (e.g. a manual workflow span).
120+
// Matches dd-trace-py / dd-trace-js, where auto-instrumented LLM spans inherit
121+
// session_id from the workflow root via context propagation. Without this, the
122+
// auto-instrumented openai.request span would not appear under its session in
123+
// the LLM Trace Explorer's Sessions view.
124+
String sessionId = LLMObsContext.currentSessionId();
125+
if (sessionId != null && !sessionId.isEmpty()) {
126+
span.setTag(CommonTags.SESSION_ID, sessionId);
127+
}
118128
}
119129
return super.afterStart(span);
120130
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
2+
3+
import datadog.context.ContextScope
4+
import datadog.trace.api.llmobs.LLMObsContext
5+
import datadog.trace.bootstrap.instrumentation.api.AgentTracer
6+
7+
/**
8+
* Verifies that auto-instrumented openai.request spans inherit session_id from an active
9+
* LLMObs parent context, matching the behavior of dd-trace-py and dd-trace-js.
10+
*
11+
* Background: customer apps typically set session_id on a manual LLMObs workflow root
12+
* via LLMObs.startWorkflowSpan(..., sessionId) and then call OpenAI without manually
13+
* wrapping in LLMObs.startLLMSpan. The auto-instrumented LLM span needs to inherit the
14+
* session_id from the workflow parent so the trace appears under its session in the
15+
* LLM Trace Explorer's Sessions view.
16+
*
17+
* Note: this test attaches the LLMObsContext directly rather than going through
18+
* LLMObs.startWorkflowSpan() — the latter resolves to a no-op factory in this
19+
* test module since agent-llmobs isn't loaded here. We're specifically validating
20+
* OpenAiDecorator's session_id inheritance behavior, which only depends on
21+
* LLMObsContext.currentSessionId() being set.
22+
*/
23+
class SessionIdPropagationTest extends OpenAiTest {
24+
25+
def "openai.request span inherits session_id from active LLMObs context"() {
26+
setup:
27+
def expectedSessionId = "session-propagation-test-abc"
28+
29+
when:
30+
runUnderTrace("parent") {
31+
def parentCtx = AgentTracer.activeSpan().context()
32+
ContextScope scope = LLMObsContext.attach(parentCtx, expectedSessionId)
33+
try {
34+
openAiClient.chat().completions().create(chatCompletionCreateParams(false))
35+
} finally {
36+
scope.close()
37+
}
38+
}
39+
TEST_WRITER.waitForTraces(1)
40+
def openAiSpan = TEST_WRITER.flatten().find {
41+
it.operationName.toString() == "openai.request"
42+
}
43+
44+
then:
45+
openAiSpan != null
46+
expectedSessionId == openAiSpan.getTag("_ml_obs_tag.session_id")
47+
}
48+
49+
def "openai.request span has no session_id when no LLMObs parent context is active"() {
50+
when:
51+
runUnderTrace("non-llmobs-parent") {
52+
openAiClient.chat().completions().create(chatCompletionCreateParams(false))
53+
}
54+
TEST_WRITER.waitForTraces(1)
55+
def openAiSpan = TEST_WRITER.flatten().find {
56+
it.operationName.toString() == "openai.request"
57+
}
58+
59+
then:
60+
openAiSpan != null
61+
null == openAiSpan.getTag("_ml_obs_tag.session_id")
62+
}
63+
}

0 commit comments

Comments
 (0)