Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 11 additions & 7 deletions agentops/instrumentation/common/token_counting.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,29 @@ class TokenUsage:
reasoning_tokens: Optional[int] = None

def to_attributes(self) -> Dict[str, int]:
"""Convert to span attributes dictionary."""
"""Convert to span attributes dictionary.

Only metrics greater than zero are included so that non‑LLM spans do
not contain empty token usage fields.
"""
attributes = {}

if self.prompt_tokens is not None:
if self.prompt_tokens:
attributes[SpanAttributes.LLM_USAGE_PROMPT_TOKENS] = self.prompt_tokens

if self.completion_tokens is not None:
if self.completion_tokens:
attributes[SpanAttributes.LLM_USAGE_COMPLETION_TOKENS] = self.completion_tokens

if self.total_tokens is not None:
if self.total_tokens:
attributes[SpanAttributes.LLM_USAGE_TOTAL_TOKENS] = self.total_tokens

if self.cached_prompt_tokens is not None:
if self.cached_prompt_tokens:
attributes[SpanAttributes.LLM_USAGE_CACHE_CREATION_INPUT_TOKENS] = self.cached_prompt_tokens

if self.cached_read_tokens is not None:
if self.cached_read_tokens:
attributes[SpanAttributes.LLM_USAGE_CACHE_READ_INPUT_TOKENS] = self.cached_read_tokens

if self.reasoning_tokens is not None:
if self.reasoning_tokens:
attributes[SpanAttributes.LLM_USAGE_REASONING_TOKENS] = self.reasoning_tokens

return attributes
Expand Down
5 changes: 2 additions & 3 deletions agentops/sdk/attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ def get_global_resource_attributes(
"""
Get all global resource attributes for telemetry.

Combines service metadata, system information, and imported libraries
into a complete resource attributes dictionary.
Combines service metadata and imported libraries into a complete
resource attributes dictionary.

Args:
service_name: Name of the service
Expand All @@ -73,7 +73,6 @@ def get_global_resource_attributes(
# Start with service attributes
attributes: dict[str, Any] = {
ResourceAttributes.SERVICE_NAME: service_name,
**get_system_resource_attributes(),
}

if project_id:
Expand Down
4 changes: 4 additions & 0 deletions agentops/sdk/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
get_trace_attributes,
get_span_attributes,
get_session_end_attributes,
get_system_resource_attributes,
)
from agentops.semconv import SpanKind
from agentops.helpers.dashboard import log_trace_url
Expand Down Expand Up @@ -354,6 +355,9 @@ def start_trace(

# Build trace attributes
attributes = get_trace_attributes(tags=tags)
# Include system metadata only for the default session trace
if trace_name == "session":
attributes.update(get_system_resource_attributes())

# make_span creates and starts the span, and activates it in the current context
# It returns: span, context_object, context_token
Expand Down
26 changes: 26 additions & 0 deletions tests/unit/instrumentation/common/test_token_counting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from agentops.instrumentation.common.token_counting import TokenUsage
from agentops.semconv import SpanAttributes


class TestTokenUsageToAttributes:
def test_skips_zero_values(self):
usage = TokenUsage(
prompt_tokens=0,
completion_tokens=0,
total_tokens=0,
cached_prompt_tokens=0,
cached_read_tokens=0,
reasoning_tokens=0,
)

attrs = usage.to_attributes()
assert attrs == {}

def test_includes_positive_values_only(self):
usage = TokenUsage(prompt_tokens=5, completion_tokens=0, total_tokens=5)
attrs = usage.to_attributes()
assert SpanAttributes.LLM_USAGE_PROMPT_TOKENS in attrs
assert attrs[SpanAttributes.LLM_USAGE_PROMPT_TOKENS] == 5
assert SpanAttributes.LLM_USAGE_COMPLETION_TOKENS not in attrs
assert SpanAttributes.LLM_USAGE_TOTAL_TOKENS in attrs
assert attrs[SpanAttributes.LLM_USAGE_TOTAL_TOKENS] == 5
35 changes: 35 additions & 0 deletions tests/unit/sdk/test_resource_attributes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from unittest.mock import patch


from agentops.sdk.core import tracer
from agentops.sdk.attributes import get_global_resource_attributes
from agentops.semconv.resource import ResourceAttributes


@patch("agentops.sdk.attributes.get_imported_libraries", return_value=["agentops"])
def test_global_resource_attributes_no_system(mock_libs):
attrs = get_global_resource_attributes("svc", project_id="proj")
assert attrs[ResourceAttributes.SERVICE_NAME] == "svc"
assert attrs[ResourceAttributes.PROJECT_ID] == "proj"
assert ResourceAttributes.IMPORTED_LIBRARIES in attrs
assert ResourceAttributes.HOST_MACHINE not in attrs
assert ResourceAttributes.CPU_COUNT not in attrs


@patch("agentops.sdk.core.get_system_resource_attributes")
def test_system_metadata_only_for_session(mock_sys_attrs, instrumentation):
mock_sys_attrs.return_value = {ResourceAttributes.HOST_MACHINE: "test"}

ctx = tracer.start_trace("session")
tracer.end_trace(ctx, end_state="Success")
spans = instrumentation.get_finished_spans()
assert len(spans) == 1
assert spans[0].attributes.get(ResourceAttributes.HOST_MACHINE) == "test"

instrumentation.clear_spans()

ctx = tracer.start_trace("custom")
tracer.end_trace(ctx, end_state="Success")
spans = instrumentation.get_finished_spans()
assert len(spans) == 1
assert ResourceAttributes.HOST_MACHINE not in spans[0].attributes
Loading