Skip to content
1 change: 1 addition & 0 deletions ddtrace/llmobs/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ class LLMObsSamplingDecision(str, Enum):
LLMOBS_APM_SHADOW_MODEL_NAME_TAG_KEY = "_dd.llmobs.model_name"
LLMOBS_APM_SHADOW_MODEL_PROVIDER_TAG_KEY = "_dd.llmobs.model_provider"
LLMOBS_APM_SHADOW_ENABLED_METRIC_KEY = "_dd.llmobs.enabled"
LLMOBS_ARTIFICIAL_GEN_AI_TAGS_KEY = "_dd.llmobs.artificial_gen_ai_tags"

GEN_AI_OPERATION_NAME_TAG_KEY = "gen_ai.operation.name"
GEN_AI_REQUEST_MODEL_TAG_KEY = "gen_ai.request.model"
Expand Down
4 changes: 4 additions & 0 deletions ddtrace/llmobs/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from ddtrace.llmobs._constants import INPUT_TOKENS_METRIC_KEY
from ddtrace.llmobs._constants import INTERNAL_CONTEXT_VARIABLE_KEYS
from ddtrace.llmobs._constants import INTERNAL_QUERY_VARIABLE_KEYS
from ddtrace.llmobs._constants import LLMOBS_ARTIFICIAL_GEN_AI_TAGS_KEY
from ddtrace.llmobs._constants import LLMOBS_STRUCT
from ddtrace.llmobs._constants import ML_APP
from ddtrace.llmobs._constants import ML_APP_DEFAULT
Expand Down Expand Up @@ -1127,3 +1128,6 @@ def set_gen_ai_apm_tags(span: Span, llmobs_data: Mapping[str, Any], span_kind: O
value = metrics.get(llmobs_key)
if value is not None:
span._set_attribute(gen_ai_key, value)
# Without this tag, the backend processor identifies gen_ai tags on the APM span and creates
# a duplicate LLMObs span.
span.set_tag(LLMOBS_ARTIFICIAL_GEN_AI_TAGS_KEY, "true")
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
fixes:
- |
LLM Observability: Fixes an issue where APM spans annotated with gen_ai attributes could
cause a duplicate LLM Observability span to be created.
2 changes: 2 additions & 0 deletions tests/llmobs/test_llmobs_gen_ai_apm_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from ddtrace.llmobs._constants import GEN_AI_USAGE_OUTPUT_TOKENS_METRIC_KEY
from ddtrace.llmobs._constants import GEN_AI_USAGE_REASONING_OUTPUT_TOKENS_METRIC_KEY
from ddtrace.llmobs._constants import GEN_AI_USAGE_TOTAL_TOKENS_METRIC_KEY
from ddtrace.llmobs._constants import LLMOBS_ARTIFICIAL_GEN_AI_TAGS_KEY
from ddtrace.llmobs._constants import LLMOBS_STRUCT
from ddtrace.llmobs._constants import UNKNOWN_MODEL_NAME
from ddtrace.llmobs._constants import UNKNOWN_MODEL_PROVIDER
Expand Down Expand Up @@ -58,6 +59,7 @@ def test_llm_span_emits_all_scalars(llmobs, test_spans):
assert span.get_tag(GEN_AI_PROVIDER_NAME_TAG_KEY) == "openai"
assert span.get_tag(GEN_AI_CONVERSATION_ID_TAG_KEY) == "sess-1"
assert span.get_tag(GEN_AI_APPLICATION_NAME_TAG_KEY) is not None
assert span.get_tag(LLMOBS_ARTIFICIAL_GEN_AI_TAGS_KEY) == "true"
assert _usage(span) == {
GEN_AI_USAGE_INPUT_TOKENS_METRIC_KEY: 10,
GEN_AI_USAGE_OUTPUT_TOKENS_METRIC_KEY: 20,
Expand Down
1 change: 1 addition & 0 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"meta._dd.llmobs.span_kind",
"meta._dd.llmobs.model_name",
"meta._dd.llmobs.model_provider",
"meta._dd.llmobs.artificial_gen_ai_tags",
"meta._dd.p.llmobs_trace_id",
"meta._dd.p.llmobs_parent_id",
"meta._dd.p.llmobs_ml_app",
Expand Down
Loading