diff --git a/agentops/instrumentation/common/token_counting.py b/agentops/instrumentation/common/token_counting.py index 7d2f19f07..dd5b676dd 100644 --- a/agentops/instrumentation/common/token_counting.py +++ b/agentops/instrumentation/common/token_counting.py @@ -32,9 +32,12 @@ def to_attributes(self) -> Dict[str, int]: if self.prompt_tokens: attributes[SpanAttributes.LLM_USAGE_PROMPT_TOKENS] = self.prompt_tokens + # Dual-emit current OTel GenAI names so mixed-SDK collectors can sum either family. + attributes[SpanAttributes.LLM_USAGE_INPUT_TOKENS] = self.prompt_tokens if self.completion_tokens: attributes[SpanAttributes.LLM_USAGE_COMPLETION_TOKENS] = self.completion_tokens + attributes[SpanAttributes.LLM_USAGE_OUTPUT_TOKENS] = self.completion_tokens if self.total_tokens: attributes[SpanAttributes.LLM_USAGE_TOTAL_TOKENS] = self.total_tokens diff --git a/agentops/semconv/span_attributes.py b/agentops/semconv/span_attributes.py index 2c5f8ce1c..255980008 100644 --- a/agentops/semconv/span_attributes.py +++ b/agentops/semconv/span_attributes.py @@ -58,8 +58,12 @@ class SpanAttributes: LLM_RESPONSE_ID = "gen_ai.response.id" # Usage metrics + # Legacy GenAI names still emitted by AgentOps; current OTel GenAI names are + # dual-emitted during the transition (#1447). LLM_USAGE_COMPLETION_TOKENS = "gen_ai.usage.completion_tokens" LLM_USAGE_PROMPT_TOKENS = "gen_ai.usage.prompt_tokens" + LLM_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens" + LLM_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens" LLM_USAGE_TOTAL_TOKENS = "gen_ai.usage.total_tokens" LLM_USAGE_CACHE_CREATION_INPUT_TOKENS = "gen_ai.usage.cache_creation_input_tokens" LLM_USAGE_CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read_input_tokens" diff --git a/tests/unit/instrumentation/common/test_token_counting.py b/tests/unit/instrumentation/common/test_token_counting.py index 2f00561b9..2e5e28253 100644 --- a/tests/unit/instrumentation/common/test_token_counting.py +++ b/tests/unit/instrumentation/common/test_token_counting.py @@ -21,6 +21,18 @@ def test_includes_positive_values_only(self): attrs = usage.to_attributes() assert SpanAttributes.LLM_USAGE_PROMPT_TOKENS in attrs assert attrs[SpanAttributes.LLM_USAGE_PROMPT_TOKENS] == 5 + assert SpanAttributes.LLM_USAGE_INPUT_TOKENS in attrs + assert attrs[SpanAttributes.LLM_USAGE_INPUT_TOKENS] == 5 assert SpanAttributes.LLM_USAGE_COMPLETION_TOKENS not in attrs + assert SpanAttributes.LLM_USAGE_OUTPUT_TOKENS not in attrs assert SpanAttributes.LLM_USAGE_TOTAL_TOKENS in attrs assert attrs[SpanAttributes.LLM_USAGE_TOTAL_TOKENS] == 5 + + def test_dual_emits_current_otel_genai_names(self): + usage = TokenUsage(prompt_tokens=10, completion_tokens=4, total_tokens=14) + attrs = usage.to_attributes() + assert attrs[SpanAttributes.LLM_USAGE_PROMPT_TOKENS] == 10 + assert attrs[SpanAttributes.LLM_USAGE_INPUT_TOKENS] == 10 + assert attrs[SpanAttributes.LLM_USAGE_COMPLETION_TOKENS] == 4 + assert attrs[SpanAttributes.LLM_USAGE_OUTPUT_TOKENS] == 4 + assert attrs[SpanAttributes.LLM_USAGE_TOTAL_TOKENS] == 14