Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5134311
add APIs for llm obs
gary-huang Sep 19, 2024
b5c7a0a
add llm message class to support llm spans
gary-huang Mar 6, 2025
7f8f586
add llm message class to support llm spans
gary-huang Mar 6, 2025
9206dbe
impl llmobs agent and llmobs apis
gary-huang Jan 27, 2025
f5efd26
support llm messages with tool calls
gary-huang Mar 6, 2025
76740e9
handle default model name and provider
gary-huang Mar 26, 2025
5e66a2f
rm unneeded file
gary-huang Apr 11, 2025
75290af
impl llmobs agent and llmobs apis
gary-huang Jan 27, 2025
7b2db7c
impl llmobs agent
gary-huang Jan 27, 2025
d76c94d
working writer
gary-huang Feb 7, 2025
e0b4510
add support for llm message and tool calls
gary-huang Mar 6, 2025
87cdaf1
impl llmobs agent and llmobs apis
gary-huang Jan 27, 2025
f78b2c5
use new ctx api to track parent span
gary-huang Apr 1, 2025
52d3502
cleaned up whitespace
Jul 2, 2025
f54c468
resolve merge conflicts
Jul 2, 2025
688d984
remaining merge conflicts
Jul 2, 2025
e62d569
merge with feature branch
Jul 2, 2025
157e53e
fix bad method call
Jul 2, 2025
ade08ca
fixed llmobs intake creation if llmobs not enabled
Jul 3, 2025
973c09f
removed print statements
Jul 3, 2025
384141d
Merge branch 'gary/add-llm-obs-writer' into gary/use-ctx-api
nayeem-kamal Jul 3, 2025
c74a642
ran spotless
Jul 3, 2025
1eb8db3
added tests for llmobsspanmapper
Jul 7, 2025
75d85a2
fixed coverage for tags
Jul 7, 2025
ec3cf25
Merge branch 'gary/add-llm-obs-writer' into gary/use-ctx-api
nayeem-kamal Jul 8, 2025
7c16dd2
Merge branch 'gary/llmobs-sdk-merge' into gary/use-ctx-api
gary-huang Jul 8, 2025
8c08304
Merge branch 'gary/llmobs-sdk-merge' into gary/use-ctx-api
gary-huang Jul 8, 2025
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
2 changes: 1 addition & 1 deletion dd-java-agent/agent-jmxfetch/integrations-core
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package datadog.trace.llmobs.domain;

import datadog.context.ContextScope;
import datadog.trace.api.DDSpanTypes;
import datadog.trace.api.llmobs.LLMObs;
import datadog.trace.api.llmobs.LLMObsSpan;
import datadog.trace.api.llmobs.LLMObsTags;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext;
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
import datadog.trace.bootstrap.instrumentation.api.Tags;
import java.util.Collections;
Expand All @@ -29,13 +31,15 @@ public class DDLLMObsSpan implements LLMObsSpan {
private static final String OUTPUT = LLMOBS_TAG_PREFIX + "output";
private static final String SPAN_KIND = LLMOBS_TAG_PREFIX + Tags.SPAN_KIND;
private static final String METADATA = LLMOBS_TAG_PREFIX + LLMObsTags.METADATA;
private static final String PARENT_ID_TAG_INTERNAL = "parent_id";

private static final String LLM_OBS_INSTRUMENTATION_NAME = "llmobs";

private static final Logger LOGGER = LoggerFactory.getLogger(DDLLMObsSpan.class);

private final AgentSpan span;
private final String spanKind;
private final ContextScope scope;

private boolean finished = false;

Expand Down Expand Up @@ -63,6 +67,24 @@ public DDLLMObsSpan(
if (sessionId != null && !sessionId.isEmpty()) {
this.span.setTag(LLMOBS_TAG_PREFIX + LLMObsTags.SESSION_ID, sessionId);
}

AgentSpanContext parent = LLMObsState.getLLMObsParentContext();
String parentSpanID = LLMObsState.ROOT_SPAN_ID;
if (null != parent) {
if (parent.getTraceId() != this.span.getTraceId()) {
LOGGER.error(
"trace ID mismatch, retrieved parent from context trace_id={}, span_id={}, started span trace_id={}, span_id={}",
parent.getTraceId(),
parent.getSpanId(),
this.span.getTraceId(),
this.span.getSpanId());
} else {
parentSpanID = String.valueOf(parent.getSpanId());
}
}
this.span.setTag(LLMOBS_TAG_PREFIX + PARENT_ID_TAG_INTERNAL, parentSpanID);
this.scope = LLMObsState.attach();
LLMObsState.setLLMObsParentContext(this.span.context());
}

@Override
Expand Down Expand Up @@ -271,6 +293,7 @@ public void finish() {
return;
}
this.span.finish();
this.scope.close();
this.finished = true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package datadog.trace.llmobs.domain;

import datadog.context.Context;
import datadog.context.ContextKey;
import datadog.context.ContextScope;
import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext;

public class LLMObsState {
public static final String ROOT_SPAN_ID = "undefined";

private static final ContextKey<LLMObsState> CONTEXT_KEY = ContextKey.named("llmobs_span");

private AgentSpanContext parentSpanID;

public static ContextScope attach() {
return Context.current().with(CONTEXT_KEY, new LLMObsState()).attach();
}

private static LLMObsState fromContext() {
return Context.current().get(CONTEXT_KEY);
}

public static AgentSpanContext getLLMObsParentContext() {
LLMObsState state = fromContext();
if (state != null) {
return state.parentSpanID;
}
return null;
}

public static void setLLMObsParentContext(AgentSpanContext llmObsParentContext) {
LLMObsState state = fromContext();
if (state != null) {
state.parentSpanID = llmObsParentContext;
}
}
}
1 change: 1 addition & 0 deletions dd-trace-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ excludedClassesCoverage += [
'datadog.trace.context.NoopTraceScope',
'datadog.trace.payloadtags.PayloadTagsData',
'datadog.trace.payloadtags.PayloadTagsData.PathAndValue',
'datadog.trace.api.llmobs.LLMObsTags',
]

description = 'dd-trace-api'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class DDSpanTypes {
public static final String PROTOBUF = "protobuf";

public static final String MULE = "mule";

public static final String VALKEY = "valkey";
public static final String WEBSOCKET = "websocket";

Expand Down
2 changes: 2 additions & 0 deletions dd-trace-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ excludedClassesCoverage += [
// Interface with an empty defender method
'datadog.trace.core.propagation.HttpCodec.Extractor',
'datadog.trace.core.flare.*',
'datadog.trace.llmobs.writer.ddintake.LLMObsSpanMapper',
'datadog.trace.llmobs.writer.ddintake.LLMObsSpanMapper.PayloadV1',
// FIXME(DSM): test coverage needed
'datadog.trace.core.datastreams.DataStreamContextInjector',
'datadog.trace.common.sampling.TraceSamplingRules.RuleAdapter',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ public static Writer createWriter(
if (!DD_AGENT_WRITER_TYPE.equals(configuredType)
&& !DD_INTAKE_WRITER_TYPE.equals(configuredType)) {
log.warn(
"Writer type not configured correctly: Type {} not recognized. Ignoring", configuredType);
"Writer type not configured correctly: Type {} not recognized. Ignoring ",
configuredType);
configuredType = datadog.trace.api.ConfigDefaults.DEFAULT_AGENT_WRITER_TYPE;
}

Expand All @@ -84,14 +85,22 @@ public static Writer createWriter(

// The AgentWriter doesn't support the CI Visibility protocol. If CI Visibility is
// enabled, check if we can use the IntakeWriter instead.
if (DD_AGENT_WRITER_TYPE.equals(configuredType) && config.isCiVisibilityEnabled()) {
if (DD_AGENT_WRITER_TYPE.equals(configuredType) && (config.isCiVisibilityEnabled())) {
if (featuresDiscovery.supportsEvpProxy() || config.isCiVisibilityAgentlessEnabled()) {
configuredType = DD_INTAKE_WRITER_TYPE;
} else {
log.info(
"CI Visibility functionality is limited. Please upgrade to Agent v6.40+ or v7.40+ or enable Agentless mode.");
}
}
if (DD_AGENT_WRITER_TYPE.equals(configuredType) && (config.isLlmObsEnabled())) {
if (featuresDiscovery.supportsEvpProxy() || config.isLlmObsAgentlessEnabled()) {
configuredType = DD_INTAKE_WRITER_TYPE;
} else {
log.info("LLM Observability functionality is limited.");
// TODO: add supported agent version to this log line for llm obs
}
}

RemoteWriter remoteWriter;
if (DD_INTAKE_WRITER_TYPE.equals(configuredType)) {
Expand All @@ -117,7 +126,11 @@ public static Writer createWriter(
createDDIntakeRemoteApi(config, commObjects, featuresDiscovery, TrackType.CITESTCOV);
builder.addTrack(TrackType.CITESTCOV, coverageApi);
}

if (config.isLlmObsEnabled()) {
final RemoteApi llmobsApi =
createDDIntakeRemoteApi(config, commObjects, featuresDiscovery, TrackType.LLMOBS);
builder.addTrack(TrackType.LLMOBS, llmobsApi);
}
remoteWriter = builder.build();

} else { // configuredType == DDAgentWriter
Expand Down Expand Up @@ -173,20 +186,34 @@ private static RemoteApi createDDIntakeRemoteApi(
SharedCommunicationObjects commObjects,
DDAgentFeaturesDiscovery featuresDiscovery,
TrackType trackType) {
if (featuresDiscovery.supportsEvpProxy() && !config.isCiVisibilityAgentlessEnabled()) {
boolean evpProxySupported = featuresDiscovery.supportsEvpProxy();
boolean useProxyApi =
(evpProxySupported && TrackType.LLMOBS == trackType && !config.isLlmObsAgentlessEnabled())
|| (evpProxySupported
&& (TrackType.CITESTCOV == trackType || TrackType.CITESTCYCLE == trackType)
&& !config.isCiVisibilityAgentlessEnabled());

if (useProxyApi) {
return DDEvpProxyApi.builder()
.httpClient(commObjects.okHttpClient)
.agentUrl(commObjects.agentUrl)
.evpProxyEndpoint(featuresDiscovery.getEvpProxyEndpoint())
.trackType(trackType)
.compressionEnabled(featuresDiscovery.supportsContentEncodingHeadersWithEvpProxy())
.build();

} else {
HttpUrl hostUrl = null;
String llmObsAgentlessUrl = config.getLlMObsAgentlessUrl();

if (config.getCiVisibilityAgentlessUrl() != null) {
hostUrl = HttpUrl.get(config.getCiVisibilityAgentlessUrl());
log.info("Using host URL '{}' to report CI Visibility traces in Agentless mode.", hostUrl);
} else if (config.isLlmObsEnabled()
&& config.isLlmObsAgentlessEnabled()
&& llmObsAgentlessUrl != null
&& !llmObsAgentlessUrl.isEmpty()) {
hostUrl = HttpUrl.get(llmObsAgentlessUrl);
log.info("Using host URL '{}' to report LLM Obs traces in Agentless mode.", hostUrl);
}
return DDIntakeApi.builder()
.hostUrl(hostUrl)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import datadog.trace.civisibility.writer.ddintake.CiTestCycleMapperV1;
import datadog.trace.common.writer.RemoteMapper;
import datadog.trace.common.writer.RemoteMapperDiscovery;
import datadog.trace.llmobs.writer.ddintake.LLMObsSpanMapper;

/**
* Mapper discovery logic when a DDIntake is used. The mapper is discovered based on a backend
Expand Down Expand Up @@ -40,6 +41,8 @@ public void discover() {
mapper = new CiTestCycleMapperV1(wellKnownTags, compressionEnabled);
} else if (TrackType.CITESTCOV.equals(trackType)) {
mapper = new CiTestCovMapperV2(compressionEnabled);
} else if (TrackType.LLMOBS.equals(trackType)) {
mapper = new LLMObsSpanMapper();
} else {
mapper = RemoteMapper.NO_OP;
}
Expand Down
Loading
Loading