diff --git a/agentops/__init__.py b/agentops/__init__.py
index e4d48055a..c0e0f3c95 100755
--- a/agentops/__init__.py
+++ b/agentops/__init__.py
@@ -20,6 +20,7 @@
from opentelemetry.trace.status import StatusCode
from agentops.logging.config import logger
+from agentops.helpers.deprecation import deprecated, warn_deprecated_param
import threading
# Thread-safe client management
@@ -40,6 +41,7 @@ def get_client() -> Client:
return _client
+@deprecated("Automatically tracked in v4.")
def record(event):
"""
Legacy function to record an event. This is kept for backward compatibility.
@@ -107,6 +109,10 @@ def init(
"""
global _client
+ # Check for deprecated parameters and emit warnings
+ if tags is not None:
+ warn_deprecated_param("tags", "default_tags")
+
# Merge tags and default_tags if both are provided
merged_tags = None
if tags and default_tags:
@@ -241,7 +247,7 @@ def end_trace(
Args:
trace_context: The TraceContext object returned by start_trace. If None, ends all active traces.
- end_state: The final state of the trace (e.g., "Success", "Failure", "Error").
+ end_state: The final state of the trace (e.g., "Success", "Indeterminate", "Error").
"""
if not tracer.initialized:
logger.warning("AgentOps SDK not initialized. Cannot end trace.")
diff --git a/agentops/client/__init__.py b/agentops/client/__init__.py
index 935d612d6..fed4f941a 100644
--- a/agentops/client/__init__.py
+++ b/agentops/client/__init__.py
@@ -1,5 +1,5 @@
-from .client import Client
-from .api import ApiClient
+from agentops.client.client import Client
+from agentops.client.api import ApiClient
__all__ = ["Client", "ApiClient"]
diff --git a/agentops/helpers/__init__.py b/agentops/helpers/__init__.py
index ba8c1aad7..d3d534e2c 100644
--- a/agentops/helpers/__init__.py
+++ b/agentops/helpers/__init__.py
@@ -1,12 +1,12 @@
-from .time import get_ISO_time, iso_to_unix_nano, from_unix_nano_to_iso
-from .serialization import (
+from agentops.helpers.time import get_ISO_time
+from agentops.helpers.serialization import (
AgentOpsJSONEncoder,
serialize_uuid,
safe_serialize,
is_jsonable,
filter_unjsonable,
)
-from .system import (
+from agentops.helpers.system import (
get_host_env,
get_sdk_details,
get_os_details,
@@ -17,14 +17,11 @@
get_current_directory,
get_virtual_env,
)
-from .version import get_agentops_version, check_agentops_update
-from .debug import debug_print_function_params
-from .env import get_env_bool, get_env_int, get_env_list
+from agentops.helpers.version import get_agentops_version, check_agentops_update
+from agentops.helpers.env import get_env_bool, get_env_int, get_env_list
__all__ = [
"get_ISO_time",
- "iso_to_unix_nano",
- "from_unix_nano_to_iso",
"AgentOpsJSONEncoder",
"serialize_uuid",
"safe_serialize",
@@ -41,7 +38,6 @@
"get_virtual_env",
"get_agentops_version",
"check_agentops_update",
- "debug_print_function_params",
"get_env_bool",
"get_env_int",
"get_env_list",
diff --git a/agentops/helpers/debug.py b/agentops/helpers/debug.py
deleted file mode 100644
index 46e5b0ab4..000000000
--- a/agentops/helpers/debug.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from functools import wraps
-from pprint import pformat
-
-from agentops.logging import logger
-
-
-def debug_print_function_params(func):
- @wraps(func)
- def wrapper(self, *args, **kwargs):
- logger.debug("\n")
- logger.debug(f"{func.__name__} called with arguments:")
-
- for key, value in kwargs.items():
- logger.debug(f"{key}: {pformat(value)}")
-
- logger.debug("\n")
-
- return func(self, *args, **kwargs)
-
- return wrapper
diff --git a/agentops/helpers/deprecation.py b/agentops/helpers/deprecation.py
new file mode 100644
index 000000000..0ecf89e80
--- /dev/null
+++ b/agentops/helpers/deprecation.py
@@ -0,0 +1,50 @@
+"""
+Deprecation utilities for AgentOps SDK.
+"""
+
+import functools
+from typing import Set, Callable, Any
+from agentops.logging import logger
+
+# Track which deprecation warnings have been shown to avoid spam
+_shown_warnings: Set[str] = set()
+
+
+def deprecated(message: str):
+ """
+ Decorator to mark functions as deprecated.
+
+ Args:
+ message: Deprecation message to show
+ """
+
+ def decorator(func: Callable) -> Callable:
+ @functools.wraps(func)
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
+ warning_key = f"{func.__module__}.{func.__name__}"
+ if warning_key not in _shown_warnings:
+ logger.warning(f"{func.__name__}() is deprecated and will be removed in v4 in the future. {message}")
+ _shown_warnings.add(warning_key)
+ return func(*args, **kwargs)
+
+ return wrapper
+
+ return decorator
+
+
+def warn_deprecated_param(param_name: str, replacement: str = None):
+ """
+ Warn about deprecated parameter usage.
+
+ Args:
+ param_name: Name of the deprecated parameter
+ replacement: Suggested replacement parameter
+ """
+ warning_key = f"param.{param_name}"
+ if warning_key not in _shown_warnings:
+ if replacement:
+ message = f"Parameter '{param_name}' is deprecated and will be removed in v4 in the future. Use '{replacement}' instead."
+ else:
+ message = f"Parameter '{param_name}' is deprecated and will be removed in v4 in the future."
+ logger.warning(message)
+ _shown_warnings.add(warning_key)
diff --git a/agentops/helpers/system.py b/agentops/helpers/system.py
index bfbc78e68..dfb2cdd01 100644
--- a/agentops/helpers/system.py
+++ b/agentops/helpers/system.py
@@ -61,20 +61,6 @@ def get_sdk_details():
return {}
-def get_python_details():
- try:
- return {"Python Version": platform.python_version()}
- except:
- return {}
-
-
-def get_agentops_details():
- try:
- return {"AgentOps SDK Version": get_agentops_version()}
- except:
- return {}
-
-
def get_sys_packages():
sys_packages = {}
for module in sys.modules:
diff --git a/agentops/helpers/time.py b/agentops/helpers/time.py
index 56051344b..a4219976f 100644
--- a/agentops/helpers/time.py
+++ b/agentops/helpers/time.py
@@ -9,12 +9,3 @@ def get_ISO_time():
str: The current UTC time as a string in ISO 8601 format.
"""
return datetime.now(timezone.utc).isoformat()
-
-
-def iso_to_unix_nano(iso_time: str) -> int:
- dt = datetime.fromisoformat(iso_time)
- return int(dt.timestamp() * 1_000_000_000)
-
-
-def from_unix_nano_to_iso(unix_nano: int) -> str:
- return datetime.fromtimestamp(unix_nano / 1_000_000_000, timezone.utc).isoformat()
diff --git a/agentops/helpers/validation.py b/agentops/helpers/validation.py
deleted file mode 100644
index 78c5d2008..000000000
--- a/agentops/helpers/validation.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from typing import Any
-
-
-def is_coroutine_or_generator(fn: Any) -> bool:
- """Check if a function is asynchronous (coroutine or async generator)"""
- import inspect
-
- return inspect.iscoroutinefunction(fn) or inspect.isasyncgenfunction(fn)
diff --git a/agentops/instrumentation/ag2/instrumentor.py b/agentops/instrumentation/ag2/instrumentor.py
index 9818d3a39..6c42ce859 100644
--- a/agentops/instrumentation/ag2/instrumentor.py
+++ b/agentops/instrumentation/ag2/instrumentor.py
@@ -424,7 +424,7 @@ def wrapper(wrapped, instance, args, kwargs):
if tool_type == "function" and isinstance(result, tuple) and len(result) > 0:
success = result[0] if isinstance(result[0], bool) else False
- span.set_attribute(ToolAttributes.TOOL_STATUS, "success" if success else "failure")
+ span.set_attribute(ToolAttributes.TOOL_STATUS, "success" if success else "error")
if len(result) > 1 and isinstance(result[1], dict):
try:
@@ -435,7 +435,7 @@ def wrapper(wrapped, instance, args, kwargs):
if tool_type == "code" and isinstance(result, tuple) and len(result) >= 3:
exit_code = result[0]
span.set_attribute("exit_code", exit_code)
- span.set_attribute(ToolAttributes.TOOL_STATUS, "success" if exit_code == 0 else "failure")
+ span.set_attribute(ToolAttributes.TOOL_STATUS, "success" if exit_code == 0 else "error")
if len(result) > 1 and result[1]:
stdout = result[1]
diff --git a/agentops/instrumentation/common/__init__.py b/agentops/instrumentation/common/__init__.py
index 144fa48e4..45178a2da 100644
--- a/agentops/instrumentation/common/__init__.py
+++ b/agentops/instrumentation/common/__init__.py
@@ -1,4 +1,4 @@
-from .attributes import AttributeMap, _extract_attributes_from_mapping
-from .wrappers import _with_tracer_wrapper
+from agentops.instrumentation.common.attributes import AttributeMap, _extract_attributes_from_mapping
+from agentops.instrumentation.common.wrappers import _with_tracer_wrapper
__all__ = ["AttributeMap", "_extract_attributes_from_mapping", "_with_tracer_wrapper"]
diff --git a/agentops/instrumentation/common/objects.py b/agentops/instrumentation/common/objects.py
index 39e65e91e..3b06f40bb 100644
--- a/agentops/instrumentation/common/objects.py
+++ b/agentops/instrumentation/common/objects.py
@@ -1,5 +1,5 @@
from agentops.client.api.types import UploadedObjectResponse
-from . import AttributeMap, _extract_attributes_from_mapping
+from agentops.instrumentation.common import AttributeMap, _extract_attributes_from_mapping
UPLOADED_OBJECT_ATTRIBUTES: AttributeMap = {
diff --git a/agentops/instrumentation/crewai/instrumentation.py b/agentops/instrumentation/crewai/instrumentation.py
index d655794b5..d26fa2a8e 100644
--- a/agentops/instrumentation/crewai/instrumentation.py
+++ b/agentops/instrumentation/crewai/instrumentation.py
@@ -14,7 +14,7 @@
from agentops.instrumentation.crewai.version import __version__
from agentops.semconv import SpanAttributes, AgentOpsSpanKindValues, Meters, ToolAttributes, MessageAttributes
from agentops.semconv.core import CoreAttributes
-from .crewai_span_attributes import CrewAISpanAttributes, set_span_attribute
+from agentops.instrumentation.crewai.crewai_span_attributes import CrewAISpanAttributes, set_span_attribute
from agentops import get_client
# Initialize logger
diff --git a/agentops/instrumentation/openai_agents/__init__.py b/agentops/instrumentation/openai_agents/__init__.py
index 1b3fb4b1c..74a819267 100644
--- a/agentops/instrumentation/openai_agents/__init__.py
+++ b/agentops/instrumentation/openai_agents/__init__.py
@@ -31,7 +31,7 @@ def get_version() -> str:
LIBRARY_VERSION: str = get_version()
# Import after defining constants to avoid circular imports
-from .instrumentor import OpenAIAgentsInstrumentor # noqa: E402
+from agentops.instrumentation.openai_agents.instrumentor import OpenAIAgentsInstrumentor # noqa: E402
__all__ = [
"LIBRARY_NAME",
diff --git a/agentops/legacy/__init__.py b/agentops/legacy/__init__.py
index d835c0877..5f77800d2 100644
--- a/agentops/legacy/__init__.py
+++ b/agentops/legacy/__init__.py
@@ -13,6 +13,7 @@
from agentops.logging import logger
from agentops.sdk.core import TraceContext, tracer
+from agentops.helpers.deprecation import deprecated
_current_session: Optional["Session"] = None
_current_trace_context: Optional[TraceContext] = None
@@ -60,6 +61,7 @@ def end_session(self, **kwargs: Any):
end_session(session_or_status=self, **kwargs)
+@deprecated("Use agentops.start_trace() instead.")
def start_session(
tags: Union[Dict[str, Any], List[str], None] = None,
) -> Session:
@@ -121,6 +123,7 @@ def _set_span_attributes(span: Any, attributes: Dict[str, Any]) -> None:
span.set_attribute(f"agentops.legacy.{key}", str(value))
+@deprecated("Use agentops.end_trace() instead.")
def end_session(session_or_status: Any = None, **kwargs: Any) -> None:
"""
@deprecated Use agentops.end_trace() instead.
@@ -186,6 +189,7 @@ def end_session(session_or_status: Any = None, **kwargs: Any) -> None:
pass
+@deprecated("Use agentops.end_trace() instead.")
def end_all_sessions() -> None:
"""@deprecated Ends all active sessions/traces."""
if not tracer.initialized:
@@ -201,13 +205,15 @@ def end_all_sessions() -> None:
_current_trace_context = None
+@deprecated("Automatically tracked in v4.")
def ToolEvent(*args: Any, **kwargs: Any) -> None:
- """@deprecated Use tracing instead."""
+ """@deprecated Automatically tracked in v4."""
return None
+@deprecated("Automatically tracked in v4.")
def ErrorEvent(*args: Any, **kwargs: Any) -> Any:
- """@deprecated Use tracing instead. Returns minimal object for test compatibility."""
+ """@deprecated Automatically tracked in v4. Returns minimal object for test compatibility."""
from agentops.helpers.time import get_ISO_time
class LegacyErrorEvent:
@@ -218,8 +224,9 @@ def __init__(self):
return LegacyErrorEvent()
+@deprecated("Automatically tracked in v4.")
def ActionEvent(*args: Any, **kwargs: Any) -> Any:
- """@deprecated Use tracing instead. Returns minimal object for test compatibility."""
+ """@deprecated Automatically tracked in v4. Returns minimal object for test compatibility."""
from agentops.helpers.time import get_ISO_time
class LegacyActionEvent:
@@ -230,11 +237,13 @@ def __init__(self):
return LegacyActionEvent()
+@deprecated("Automatically tracked in v4.")
def LLMEvent(*args: Any, **kwargs: Any) -> None:
- """@deprecated Use tracing instead."""
+ """@deprecated Automatically tracked in v4."""
return None
+@deprecated("Use @agent decorator instead.")
def track_agent(*args: Any, **kwargs: Any) -> Any:
"""@deprecated No-op decorator."""
@@ -244,6 +253,7 @@ def noop(f: Any) -> Any:
return noop
+@deprecated("Use @tool decorator instead.")
def track_tool(*args: Any, **kwargs: Any) -> Any:
"""@deprecated No-op decorator."""
@@ -262,6 +272,6 @@ def noop(f: Any) -> Any:
"track_agent",
"track_tool",
"end_all_sessions",
- "Session", # Exposing the legacy Session class itself
+ "Session",
"LLMEvent",
]
diff --git a/agentops/logging/__init__.py b/agentops/logging/__init__.py
index 167c4a673..6379ed0ae 100644
--- a/agentops/logging/__init__.py
+++ b/agentops/logging/__init__.py
@@ -1,4 +1,4 @@
-from .config import configure_logging, logger
-from .instrument_logging import setup_print_logger, upload_logfile
+from agentops.logging.config import configure_logging, logger
+from agentops.logging.instrument_logging import setup_print_logger, upload_logfile
__all__ = ["logger", "configure_logging", "setup_print_logger", "upload_logfile"]
diff --git a/agentops/logging/config.py b/agentops/logging/config.py
index ead2b6e34..faf2071d0 100644
--- a/agentops/logging/config.py
+++ b/agentops/logging/config.py
@@ -1,7 +1,7 @@
import logging
import os
-from .formatters import AgentOpsLogFileFormatter, AgentOpsLogFormatter
+from agentops.logging.formatters import AgentOpsLogFileFormatter, AgentOpsLogFormatter
# Create the logger at module level
logger = logging.getLogger("agentops")
diff --git a/agentops/sdk/converters.py b/agentops/sdk/converters.py
deleted file mode 100644
index fee21e257..000000000
--- a/agentops/sdk/converters.py
+++ /dev/null
@@ -1,126 +0,0 @@
-"""
-Legacy helpers that were being used throughout the SDK
-"""
-
-import uuid
-from datetime import datetime, timezone
-from typing import Optional
-from uuid import UUID
-
-from opentelemetry.util.types import Attributes, AttributeValue
-
-
-def ns_to_iso(ns_time: Optional[int]) -> Optional[str]:
- """Convert nanosecond timestamp to ISO format."""
- if ns_time is None:
- return None
- seconds = ns_time / 1e9
- dt = datetime.fromtimestamp(seconds, tz=timezone.utc)
- return dt.isoformat().replace("+00:00", "Z")
-
-
-def trace_id_to_uuid(trace_id: int) -> UUID:
- # Convert the trace_id to a 32-character hex string
- trace_id_hex = format(trace_id, "032x")
-
- # Format as UUID string (8-4-4-4-12)
- uuid_str = (
- f"{trace_id_hex[0:8]}-{trace_id_hex[8:12]}-{trace_id_hex[12:16]}-{trace_id_hex[16:20]}-{trace_id_hex[20:32]}"
- )
-
- # Create UUID object
- return UUID(uuid_str)
-
-
-def uuid_to_int16(uuid: UUID) -> int:
- return int(uuid.hex, 16)
-
-
-def dict_to_span_attributes(data: dict, prefix: str = "") -> Attributes:
- """Convert a dictionary to OpenTelemetry span attributes.
-
- Follows OpenTelemetry AttributeValue type constraints:
- - str
- - bool
- - int
- - float
- - Sequence[str]
- - Sequence[bool]
- - Sequence[int]
- - Sequence[float]
-
- Args:
- data: Dictionary to convert
- prefix: Optional prefix for attribute names (e.g. "session.")
-
- Returns:
- Dictionary of span attributes with flattened structure
- """
- attributes: dict[str, AttributeValue] = {}
-
- def _flatten(obj, parent_key=""):
- if isinstance(obj, dict):
- for key, value in obj.items():
- new_key = f"{parent_key}.{key}" if parent_key else key
- if prefix:
- new_key = f"{prefix}{new_key}"
-
- if isinstance(value, dict):
- _flatten(value, new_key)
- elif isinstance(value, (str, bool, int, float)):
- attributes[new_key] = value
- elif isinstance(value, (list, tuple)):
- # Only include sequences if they contain valid types
- if value and all(isinstance(x, str) for x in value):
- attributes[new_key] = list(value)
- elif value and all(isinstance(x, bool) for x in value):
- attributes[new_key] = list(value)
- elif value and all(isinstance(x, int) for x in value):
- attributes[new_key] = list(value)
- elif value and all(isinstance(x, float) for x in value):
- attributes[new_key] = list(value)
- else:
- # Convert mixed/unsupported sequences to string
- attributes[new_key] = ",".join(str(x) for x in value)
- else:
- # Convert unsupported types to string
- attributes[new_key] = str(value)
-
- _flatten(data)
- return attributes
-
-
-def uuid_to_int(uuid_str):
- """Convert a UUID string to a decimal integer."""
- # If input is a UUID object, convert to string
- if isinstance(uuid_str, uuid.UUID):
- uuid_str = str(uuid_str)
-
- # Remove hyphens if they exist
- uuid_str = uuid_str.replace("-", "")
-
- # Convert the hex string to an integer
- return int(uuid_str, 16)
-
-
-def int_to_uuid(integer):
- """Convert a decimal integer back to a UUID object."""
- # Convert the integer to hex and remove '0x' prefix
- hex_str = hex(integer)[2:]
-
- # Pad with zeros to ensure it's 32 characters long (128 bits)
- hex_str = hex_str.zfill(32)
-
- # Insert hyphens in the correct positions
- uuid_str = f"{hex_str[:8]}-{hex_str[8:12]}-{hex_str[12:16]}-{hex_str[16:20]}-{hex_str[20:]}"
-
- # Return as UUID object
- return uuid.UUID(uuid_str)
-
-
-def camel_to_snake(text: str) -> str:
- """Convert CamelCase class names to snake_case format"""
- import re
-
- text = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", text)
- return re.sub("([a-z0-9])([A-Z])", r"\1_\2", text).lower()
diff --git a/agentops/sdk/core.py b/agentops/sdk/core.py
index 8874fd2b6..f4c09f23e 100644
--- a/agentops/sdk/core.py
+++ b/agentops/sdk/core.py
@@ -389,7 +389,7 @@ def end_trace(
Args:
trace_context: The TraceContext object returned by start_trace. If None, ends all active traces.
- end_state: The final state of the trace (e.g., "Success", "Failure", "Error").
+ end_state: The final state of the trace (e.g., "Success", "Indeterminate", "Error").
"""
if not self.initialized:
logger.warning("Global tracer not initialized. Cannot end trace.")
diff --git a/agentops/sdk/decorators/__init__.py b/agentops/sdk/decorators/__init__.py
index 6881bc19a..d996cc80a 100644
--- a/agentops/sdk/decorators/__init__.py
+++ b/agentops/sdk/decorators/__init__.py
@@ -3,10 +3,7 @@
Provides @trace for creating trace-level spans (sessions) and other decorators for nested spans.
"""
-import functools
-from termcolor import colored
-
-from agentops.logging import logger
+from agentops.helpers.deprecation import deprecated
from agentops.sdk.decorators.factory import create_entity_decorator
from agentops.semconv.span_kinds import SpanKind
@@ -22,10 +19,8 @@
# For backward compatibility: @session decorator calls @trace decorator
-@functools.wraps(trace)
def session(*args, **kwargs): # noqa: F811
"""@deprecated Use @agentops.trace instead. Wraps the @trace decorator for backward compatibility."""
- logger.info(colored("@agentops.session decorator is deprecated. Please use @agentops.trace instead.", "yellow"))
# If called as @session or @session(...)
if not args or not callable(args[0]): # called with kwargs like @session(name=...)
return trace(*args, **kwargs)
@@ -33,6 +28,10 @@ def session(*args, **kwargs): # noqa: F811
return trace(args[0], **kwargs) # args[0] is the wrapped function
+# Apply deprecation decorator to session function
+session = deprecated("Use @trace decorator instead.")(session)
+
+
# Note: The original `operation = task` was potentially problematic if `operation` was meant to be distinct.
# Using operation_decorator for clarity if a distinct OPERATION kind decorator is needed.
# For now, keeping the alias as it was, assuming it was intentional for `operation` to be `task`.
diff --git a/agentops/sdk/decorators/factory.py b/agentops/sdk/decorators/factory.py
index 7be5c26bb..2ddca1437 100644
--- a/agentops/sdk/decorators/factory.py
+++ b/agentops/sdk/decorators/factory.py
@@ -11,7 +11,7 @@
from agentops.semconv.span_kinds import SpanKind
from agentops.semconv import SpanAttributes, CoreAttributes
-from .utility import (
+from agentops.sdk.decorators.utility import (
_create_as_current_span,
_process_async_generator,
_process_sync_generator,
@@ -119,7 +119,7 @@ async def _wrapped_session_async() -> Any:
return result
except Exception:
if trace_context:
- tracer.end_trace(trace_context, "Failure")
+ tracer.end_trace(trace_context, "Indeterminate")
raise
finally:
if trace_context and trace_context.span.is_recording():
@@ -151,7 +151,7 @@ async def _wrapped_session_async() -> Any:
return result
except Exception:
if trace_context:
- tracer.end_trace(trace_context, "Failure")
+ tracer.end_trace(trace_context, "Indeterminate")
raise
finally:
if trace_context and trace_context.span.is_recording():
diff --git a/agentops/sdk/formatters.py b/agentops/sdk/formatters.py
deleted file mode 100644
index 740e8b6c5..000000000
--- a/agentops/sdk/formatters.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from datetime import datetime
-from decimal import Decimal, ROUND_HALF_UP
-
-
-def format_duration(start_time, end_time) -> str:
- """Format duration between two timestamps"""
- if not start_time or not end_time:
- return "0.0s"
-
- start = datetime.fromisoformat(start_time.replace("Z", "+00:00"))
- end = datetime.fromisoformat(end_time.replace("Z", "+00:00"))
- duration = end - start
-
- hours, remainder = divmod(duration.total_seconds(), 3600)
- minutes, seconds = divmod(remainder, 60)
-
- parts = []
- if hours > 0:
- parts.append(f"{int(hours)}h")
- if minutes > 0:
- parts.append(f"{int(minutes)}m")
- parts.append(f"{seconds:.1f}s")
-
- return " ".join(parts)
-
-
-def format_token_cost(cost: float | Decimal) -> str:
- """Format token cost to 2 decimal places, or 6 decimal places if non-zero"""
- if isinstance(cost, Decimal):
- return "{:.6f}".format(cost.quantize(Decimal("0.000001"), rounding=ROUND_HALF_UP))
- return "{:.2f}".format(cost)
diff --git a/agentops/sdk/processors.py b/agentops/sdk/processors.py
index 2f65a1637..4a36da3b2 100644
--- a/agentops/sdk/processors.py
+++ b/agentops/sdk/processors.py
@@ -4,76 +4,12 @@
This module contains processors for OpenTelemetry spans.
"""
-import time
-from threading import Event, Lock, Thread
-from typing import Dict, Optional
+from typing import Optional
from opentelemetry.context import Context
from opentelemetry.sdk.trace import ReadableSpan, Span, SpanProcessor
-from opentelemetry.sdk.trace.export import SpanExporter
-
-from agentops.logging import logger
-from agentops.semconv.core import CoreAttributes
-from agentops.logging import upload_logfile
-
-
-class LiveSpanProcessor(SpanProcessor):
- def __init__(self, span_exporter: SpanExporter, **kwargs):
- self.span_exporter = span_exporter
- self._in_flight: Dict[int, Span] = {}
- self._lock = Lock()
- self._stop_event = Event()
- self._export_thread = Thread(target=self._export_periodically, daemon=True)
- self._export_thread.start()
-
- def _export_periodically(self) -> None:
- while not self._stop_event.is_set():
- time.sleep(1)
- with self._lock:
- to_export = [self._readable_span(span) for span in self._in_flight.values()]
- if to_export:
- self.span_exporter.export(to_export)
-
- def _readable_span(self, span: Span) -> ReadableSpan:
- readable = span._readable_span()
- readable._end_time = time.time_ns()
- readable._attributes = {
- **(readable._attributes or {}),
- CoreAttributes.IN_FLIGHT: True,
- }
- return readable
- def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None:
- if not span.context or not span.context.trace_flags.sampled:
- return
- with self._lock:
- self._in_flight[span.context.span_id] = span
-
- def on_end(self, span: ReadableSpan) -> None:
- if not span.context or not span.context.trace_flags.sampled:
- return
- with self._lock:
- del self._in_flight[span.context.span_id]
- self.span_exporter.export((span,))
-
- def shutdown(self) -> None:
- self._stop_event.set()
- self._export_thread.join()
- self.span_exporter.shutdown()
-
- def force_flush(self, timeout_millis: int = 30000) -> bool:
- return True
-
- def export_in_flight_spans(self) -> None:
- """Export all in-flight spans without ending them.
-
- This method is primarily used for testing to ensure all spans
- are exported before assertions are made.
- """
- with self._lock:
- to_export = [self._readable_span(span) for span in self._in_flight.values()]
- if to_export:
- self.span_exporter.export(to_export)
+from agentops.logging import logger, upload_logfile
class InternalSpanProcessor(SpanProcessor):
diff --git a/agentops/semconv/__init__.py b/agentops/semconv/__init__.py
index 297dcd79d..9aa656c5c 100644
--- a/agentops/semconv/__init__.py
+++ b/agentops/semconv/__init__.py
@@ -1,19 +1,19 @@
"""AgentOps semantic conventions for spans."""
-from .span_kinds import SpanKind
-from .core import CoreAttributes
-from .agent import AgentAttributes
-from .tool import ToolAttributes
-from .status import ToolStatus
-from .workflow import WorkflowAttributes
-from .instrumentation import InstrumentationAttributes
-from .enum import LLMRequestTypeValues
-from .span_attributes import SpanAttributes
-from .meters import Meters
-from .span_kinds import AgentOpsSpanKindValues
-from .resource import ResourceAttributes
-from .message import MessageAttributes
-from .langchain import LangChainAttributes, LangChainAttributeValues
+from agentops.semconv.span_kinds import SpanKind
+from agentops.semconv.core import CoreAttributes
+from agentops.semconv.agent import AgentAttributes
+from agentops.semconv.tool import ToolAttributes
+from agentops.semconv.status import ToolStatus
+from agentops.semconv.workflow import WorkflowAttributes
+from agentops.semconv.instrumentation import InstrumentationAttributes
+from agentops.semconv.enum import LLMRequestTypeValues
+from agentops.semconv.span_attributes import SpanAttributes
+from agentops.semconv.meters import Meters
+from agentops.semconv.span_kinds import AgentOpsSpanKindValues
+from agentops.semconv.resource import ResourceAttributes
+from agentops.semconv.message import MessageAttributes
+from agentops.semconv.langchain import LangChainAttributes, LangChainAttributeValues
SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY = "suppress_language_model_instrumentation"
__all__ = [
diff --git a/docs/mint.json b/docs/mint.json
index 1c45eef72..a6fcf4af1 100644
--- a/docs/mint.json
+++ b/docs/mint.json
@@ -158,7 +158,7 @@
"pages": [
"v2/concepts/core-concepts",
"v2/concepts/decorators",
- "v2/concepts/sessions",
+ "v2/concepts/traces",
"v2/concepts/spans",
"v2/concepts/tags"
],
diff --git a/docs/v2/concepts/sessions.mdx b/docs/v2/concepts/sessions.mdx
deleted file mode 100644
index b2eae78f2..000000000
--- a/docs/v2/concepts/sessions.mdx
+++ /dev/null
@@ -1,92 +0,0 @@
----
-title: 'Sessions'
-description: 'Effectively manage sessions in your agent workflow'
----
-## Automatic Session Management
-
-The simplest way to create and manage sessions is to use the `init` function with automatic session management:
-
-```python
-import agentops
-
-# Initialize with automatic session creation (default)
-agentops.init(api_key="YOUR_API_KEY", tags=["production"])
-```
-
-This approach:
-- Creates a session automatically when you initialize the SDK
-- Tracks all events in the context of this session
-- Manages the session throughout the lifecycle of your application
-
-## Manual Session Creation
-
-For more control, you can disable automatic session creation and start sessions manually:
-
-```python
-import agentops
-
-# Initialize without auto-starting a session
-agentops.init(api_key="YOUR_API_KEY", auto_start_session=False)
-
-# Later, manually start a session when needed
-agentops.start_session(tags=["customer-query", "high-priority"])
-```
-
-Manual session management is useful when:
-- You want to control exactly when session tracking begins
-- You need to associate different sessions with different sets of tags
-- Your application has distinct workflows that should be tracked separately
-
-## Using the Session Decorator
-
-As an alternative to the methods above, you can use the `@session` decorator to create a session for a specific function:
-
-```python
-from agentops.sdk.decorators import session
-
-@session
-def process_customer_data(customer_id):
- # This entire function execution will be tracked as a session
- return analyze_data(customer_id)
-```
-
-## Session State
-
-Every session has an associated state that includes:
-
-- **Session ID**: A unique identifier
-- **Start Time**: When the session began
-- **Tags**: Labels associated with the session
-- **Events**: All events recorded during the session
-
-This state is automatically managed by AgentOps and synchronized with the dashboard.
-
-## Session Context
-
-Sessions create a context for all event recording. When an event is recorded:
-
-1. It's associated with the current active session
-2. It's automatically included in the session's timeline
-3. It inherits the session's tags for filtering and analysis
-
-## Viewing Sessions in the Dashboard
-
-The AgentOps dashboard provides several views for analyzing your sessions:
-
-1. **Session List**: Overview of all sessions with filtering options
-2. **Session Details**: In-depth view of a single session
-3. **Timeline View**: Chronological display of all events in a session
-4. **Tree View**: Hierarchical representation of agents, operations, and events
-5. **Analytics**: Aggregated metrics across sessions
-
-## Best Practices
-
-- **Start sessions at logical boundaries** in your application workflow
-- **Use descriptive session names** to easily identify them in the dashboard
-- **Apply consistent tags** to group related sessions
-- **Use fewer, longer sessions** rather than many short ones for better analysis
-- **Use automatic session management** unless you have specific needs for manual control
-
-
-
-
diff --git a/docs/v2/concepts/tags.mdx b/docs/v2/concepts/tags.mdx
index 40ea2bbd1..20ac2a92d 100644
--- a/docs/v2/concepts/tags.mdx
+++ b/docs/v2/concepts/tags.mdx
@@ -4,7 +4,7 @@ description: 'Organize and filter your sessions with customizable tags'
---
## Adding Tags
-You can add tags when initializing AgentOps, which is the most common approach:
+You can add tags when initializing AgentOps, whicreh is the most common approach:
```python
import agentops
@@ -12,20 +12,24 @@ import agentops
# Initialize AgentOps with tags
agentops.init(
api_key="YOUR_API_KEY",
- tags=["production", "customer-service", "gpt-4"]
+ default_tags=["production", "customer-service", "gpt-4"]
)
```
-Alternatively, when using manual session creation:
+Alternatively, when using manual trace creation:
```python
# Initialize without auto-starting a session
agentops.init(api_key="YOUR_API_KEY", auto_start_session=False)
-# Later start a session with specific tags
-agentops.start_session(tags=["development", "testing", "claude-3"])
+# Later start a trace with specific tags (modern approach)
+trace = agentops.start_trace(trace_name="test_workflow", default_tags=["development", "testing", "claude-3"])
```
+
+Legacy approach using `agentops.start_session(default_tags=["development", "testing", "claude-3"])` is deprecated and will be removed in v4.0. Use `agentops.start_trace()` instead.
+
+
## Tag Use Cases
Tags can be used for various purposes:
@@ -35,7 +39,7 @@ Tags can be used for various purposes:
Tag sessions based on their environment:
```python
-tags=["production"] # or ["development", "staging", "testing"]
+default_tags=["production"] # or ["development", "staging", "testing"]
```
### Feature Tracking
@@ -43,7 +47,7 @@ tags=["production"] # or ["development", "staging", "testing"]
Tag sessions related to specific features or components:
```python
-tags=["search-functionality", "user-authentication", "content-generation"]
+default_tags=["search-functionality", "user-authentication", "content-generation"]
```
### User Segmentation
@@ -51,7 +55,7 @@ tags=["search-functionality", "user-authentication", "content-generation"]
Tag sessions based on user characteristics:
```python
-tags=["premium-user", "new-user", "enterprise-customer"]
+default_tags=["premium-user", "new-user", "enterprise-customer"]
```
### Experiment Tracking
@@ -59,7 +63,7 @@ tags=["premium-user", "new-user", "enterprise-customer"]
Tag sessions as part of specific experiments:
```python
-tags=["experiment-123", "control-group", "variant-A"]
+default_tags=["experiment-123", "control-group", "variant-A"]
```
### Model Identification
@@ -67,7 +71,7 @@ tags=["experiment-123", "control-group", "variant-A"]
Tag sessions with the models being used:
```python
-tags=["gpt-4", "claude-3-opus", "mistral-large"]
+default_tags=["gpt-4", "claude-3-opus", "mistral-large"]
```
## Viewing Tagged Sessions
diff --git a/docs/v2/concepts/traces.mdx b/docs/v2/concepts/traces.mdx
new file mode 100644
index 000000000..41a21f8b4
--- /dev/null
+++ b/docs/v2/concepts/traces.mdx
@@ -0,0 +1,275 @@
+---
+title: 'Traces'
+description: 'Effectively manage traces in your agent workflow'
+---
+## Automatic Trace Management
+
+The simplest way to create and manage traces is to use the `init` function with automatic trace creation:
+
+```python
+import agentops
+
+# Initialize with automatic trace creation (default)
+agentops.init(api_key="YOUR_API_KEY", default_tags=["production"])
+```
+
+This approach:
+- Creates a trace automatically when you initialize the SDK
+- Tracks all events in the context of this trace
+- Manages the trace throughout the lifecycle of your application
+
+## Manual Trace Creation
+For more control, you can disable automatic trace creation and start traces manually:
+
+```python
+import agentops
+
+# Initialize without auto-starting a trace
+agentops.init(api_key="YOUR_API_KEY", auto_start_session=False)
+
+# Later, manually start a trace when needed
+trace_context = agentops.start_trace(
+ trace_name="Customer Workflow",
+ tags=["customer-query", "high-priority"]
+)
+
+# End the trace when done
+agentops.end_trace(trace_context, end_state="Success")
+```
+
+Manual trace management is useful when:
+- You want to control exactly when trace tracking begins
+- You need to associate different traces with different sets of tags
+- Your application has distinct workflows that should be tracked separately
+
+## Using the Trace Decorator
+
+You can use the `@trace` decorator to create a trace for a specific function:
+
+```python
+import agentops
+
+@agentops.trace
+def process_customer_data(customer_id):
+ # This entire function execution will be tracked as a trace
+ return analyze_data(customer_id)
+
+# Or with custom parameters
+@agentops.trace(name="data_processing", tags=["analytics"])
+def analyze_user_behavior(user_data):
+ return perform_analysis(user_data)
+```
+
+## Trace Context Manager
+
+TraceContext objects support Python's context manager protocol, making it easy to manage trace lifecycles:
+
+```python
+import agentops
+
+# Using trace context as a context manager
+with agentops.start_trace("user_session", tags=["web"]) as trace:
+ # All operations here are tracked within this trace
+ process_user_request()
+ # Trace automatically ends when exiting the context
+ # Success/Error state is set based on whether exceptions occurred
+```
+
+## Trace States
+
+Every trace has an associated state that indicates its completion status. AgentOps provides multiple ways to specify trace end states for flexibility and backward compatibility.
+
+### AgentOps TraceState Enum (Recommended)
+
+The recommended approach is to use the `TraceState` enum from AgentOps:
+
+```python
+from agentops import TraceState
+
+# Available states
+agentops.end_trace(trace_context, end_state=TraceState.SUCCESS) # Trace completed successfully
+agentops.end_trace(trace_context, end_state=TraceState.ERROR) # Trace encountered an error
+agentops.end_trace(trace_context, end_state=TraceState.UNSET) # Trace state is not determined
+```
+
+### OpenTelemetry StatusCode
+
+For advanced users familiar with OpenTelemetry, you can use StatusCode directly:
+
+```python
+from opentelemetry.trace.status import StatusCode
+
+agentops.end_trace(trace_context, end_state=StatusCode.OK) # Same as TraceState.SUCCESS
+agentops.end_trace(trace_context, end_state=StatusCode.ERROR) # Same as TraceState.ERROR
+agentops.end_trace(trace_context, end_state=StatusCode.UNSET) # Same as TraceState.UNSET
+```
+
+### String Values
+
+String values are also supported for convenience:
+
+```python
+# String representations
+agentops.end_trace(trace_context, end_state="Success") # Maps to SUCCESS
+agentops.end_trace(trace_context, end_state="Error") # Maps to ERROR
+agentops.end_trace(trace_context, end_state="Indeterminate") # Maps to UNSET
+```
+
+### State Mapping
+
+All state representations map to the same underlying OpenTelemetry StatusCode:
+
+| AgentOps TraceState | OpenTelemetry StatusCode | String Values | Description |
+|-------------------|-------------------------|---------------|-------------|
+| `TraceState.SUCCESS` | `StatusCode.OK` | "Success" | Trace completed successfully |
+| `TraceState.ERROR` | `StatusCode.ERROR` | "Error" | Trace encountered an error |
+| `TraceState.UNSET` | `StatusCode.UNSET` | "Indeterminate" | Trace state is not determined |
+
+### Default Behavior
+
+If no end state is provided, the default is `TraceState.SUCCESS`:
+
+```python
+# These are equivalent
+agentops.end_trace(trace_context)
+agentops.end_trace(trace_context, end_state=TraceState.SUCCESS)
+```
+
+## Trace Attributes
+
+Every trace collects comprehensive metadata to provide rich context for analysis. Trace attributes are automatically captured by AgentOps and fall into several categories:
+
+### Core Trace Attributes
+
+**Identity and Timing:**
+- **Trace ID**: A unique identifier for the trace
+- **Span ID**: Identifier for the root span of the trace
+- **Start Time**: When the trace began
+- **End Time**: When the trace completed (set automatically)
+- **Duration**: Total execution time (calculated automatically)
+
+**User-Defined Attributes:**
+- **Trace Name**: Custom name provided when starting the trace
+- **Tags**: Labels for filtering and grouping (list of strings or dictionary)
+- **End State**: Success, error, or unset status
+
+```python
+# Tags can be provided as a list of strings or a dictionary
+agentops.start_trace("my_trace", tags=["production", "experiment-a"])
+agentops.start_trace("my_trace", tags={"environment": "prod", "version": "1.2.3"})
+```
+
+### Resource Attributes
+
+AgentOps automatically captures system and environment information:
+
+**Project and Service:**
+- **Project ID**: AgentOps project identifier
+- **Service Name**: Service name (defaults to "agentops")
+- **Service Version**: Version of your service
+- **Environment**: Deployment environment (dev, staging, prod)
+- **SDK Version**: AgentOps SDK version being used
+
+**Host System Information:**
+- **Host Name**: Machine hostname
+- **Host System**: Operating system (Windows, macOS, Linux)
+- **Host Version**: OS version details
+- **Host Processor**: CPU architecture information
+- **Host Machine**: Machine type identifier
+
+**Performance Metrics:**
+- **CPU Count**: Number of available CPU cores
+- **CPU Percent**: CPU utilization at trace start
+- **Memory Total**: Total system memory
+- **Memory Available**: Available system memory
+- **Memory Used**: Currently used memory
+- **Memory Percent**: Memory utilization percentage
+
+**Dependencies:**
+- **Imported Libraries**: List of Python packages imported in your environment
+
+### Span Hierarchy
+
+**Nested Operations:**
+- **Spans**: All spans (operations, agents, tools, workflows) recorded during the trace
+- **Parent-Child Relationships**: Hierarchical structure of operations
+- **Span Kinds**: Types of operations (agents, tools, workflows, tasks)
+
+### Accessing Trace Attributes
+
+While most attributes are automatically captured, you can access trace information programmatically:
+
+```python
+import agentops
+
+# Start a trace and get the context
+trace_context = agentops.start_trace("my_workflow", tags={"version": "1.0"})
+
+# Access trace information
+trace_id = trace_context.span.get_span_context().trace_id
+span_id = trace_context.span.get_span_context().span_id
+
+print(f"Trace ID: {trace_id}")
+print(f"Span ID: {span_id}")
+
+# End the trace
+agentops.end_trace(trace_context)
+```
+
+### Custom Attributes
+
+You can add custom attributes to spans within your trace:
+
+```python
+import agentops
+
+with agentops.start_trace("custom_workflow") as trace:
+ # Add custom attributes to the current span
+ trace.span.set_attribute("custom.workflow.step", "data_processing")
+ trace.span.set_attribute("custom.batch.size", 100)
+ trace.span.set_attribute("custom.user.id", "user_123")
+
+ # Your workflow logic here
+ process_data()
+```
+
+### Attribute Naming Conventions
+
+AgentOps follows OpenTelemetry semantic conventions for attribute naming:
+
+- **AgentOps Specific**: `agentops.*` (e.g., `agentops.span.kind`)
+- **GenAI Operations**: `gen_ai.*` (e.g., `gen_ai.request.model`)
+- **System Resources**: Standard names (e.g., `host.name`, `service.name`)
+- **Custom Attributes**: Use your own namespace (e.g., `myapp.user.id`)
+
+## Trace Context
+
+Traces create a context for all span recording. When a span is recorded:
+
+1. It's associated with the current active trace
+2. It's automatically included in the trace's timeline
+3. It inherits the trace's tags for filtering and analysis
+
+## Viewing Traces in the Dashboard
+
+The AgentOps dashboard provides several views for analyzing your traces:
+
+1. **Trace List**: Overview of all traces with filtering options
+2. **Trace Details**: In-depth view of a single trace
+3. **Timeline View**: Chronological display of all spans in a trace
+4. **Tree View**: Hierarchical representation of agents, operations, and events
+5. **Analytics**: Aggregated metrics across traces
+
+## Best Practices
+
+- **Start traces at logical boundaries** in your application workflow
+- **Use descriptive trace names** to easily identify them in the dashboard
+- **Apply consistent tags** to group related traces
+- **Use fewer, longer traces** rather than many short ones for better analysis
+- **Use automatic trace management** unless you have specific needs for manual control
+- **Leverage context managers** for automatic trace lifecycle management
+- **Set appropriate end states** to track success/failure rates
+
+
+
+
diff --git a/docs/v2/usage/manual-trace-control.mdx b/docs/v2/usage/manual-trace-control.mdx
index 7086904bb..c3db00a03 100644
--- a/docs/v2/usage/manual-trace-control.mdx
+++ b/docs/v2/usage/manual-trace-control.mdx
@@ -26,7 +26,7 @@ try:
agentops.end_trace(trace, "Success")
except Exception as e:
# End the trace with failure state
- agentops.end_trace(trace, "Failure")
+ agentops.end_trace(trace, "Indeterminate")
```
### Trace Names and Tags
@@ -72,7 +72,7 @@ for i, item in enumerate(batch_items):
if result.get("processed"):
agentops.end_trace(trace, "Success")
else:
- agentops.end_trace(trace, "Failure")
+ agentops.end_trace(trace, "Indeterminate")
except Exception as e:
agentops.end_trace(trace, "Error")
```
diff --git a/docs/v2/usage/sdk-reference.mdx b/docs/v2/usage/sdk-reference.mdx
index 9d4812d2f..c6bc046bb 100644
--- a/docs/v2/usage/sdk-reference.mdx
+++ b/docs/v2/usage/sdk-reference.mdx
@@ -27,7 +27,7 @@ Initializes the AgentOps SDK and automatically starts tracking your application.
- `max_wait_time` (int, optional): The maximum time to wait in milliseconds before flushing the queue. Defaults to 5,000 (5 seconds).
- `max_queue_size` (int, optional): The maximum size of the event queue. Defaults to 512.
- `default_tags` (List[str], optional): Default tags for the sessions that can be used for grouping or sorting later (e.g. ["GPT-4"]).
-- `tags` (List[str], optional): [Deprecated] Use `default_tags` instead.
+- `tags` (List[str], optional): **[Deprecated]** Use `default_tags` instead. Will be removed in v4.0.
- `instrument_llm_calls` (bool, optional): Whether to instrument LLM calls automatically. Defaults to True.
- `auto_start_session` (bool, optional): Whether to start a session automatically when the client is created. Set to False if running in a Jupyter Notebook. Defaults to True.
- `auto_init` (bool, optional): Whether to automatically initialize the client on import. Defaults to True.
@@ -141,7 +141,7 @@ Ends a specific trace or all active traces.
**Parameters**:
- `trace` (TraceContext, optional): The specific trace to end. If not provided, all active traces will be ended.
-- `end_state` (str, optional): The end state for the trace(s). You can use any descriptive string that makes sense for your application (e.g., "Success", "Failure", "Error", "Timeout", etc.).
+- `end_state` (str, optional): The end state for the trace(s). You can use any descriptive string that makes sense for your application (e.g., "Success", "Indeterminate", "Error", "Timeout", etc.).
**Example**:
@@ -206,13 +206,16 @@ See [Decorators](/v2/concepts/decorators) for more detailed documentation on usi
## Legacy Functions
-The following functions are maintained for backward compatibility with older versions of the SDK and integrations. New code should use the functions and decorators described above instead.
+
+The following functions are **deprecated** and will be removed in v4.0. They are maintained for backward compatibility with older versions of the SDK and integrations. New code should use the functions and decorators described above instead. When used, these functions will log deprecation warnings.
+
-- `start_session()`: Legacy function for starting sessions. Use `@trace` decorator or `start_trace()` instead.
-- `record(event)`: Legacy function to record an event. Replaced by decorator-based tracing.
-- `track_agent()`: Legacy decorator for marking agents. Replaced by the `@agent` decorator.
-- `track_tool()`: Legacy decorator for marking tools. Replaced by the `@tool` decorator.
-- `ToolEvent()`, `ErrorEvent()`, `ActionEvent()`, `LLMEvent()`: Legacy event types. Replaced by automatic instrumentation and decorators.
+- `start_session()`: **Deprecated.** Legacy function for starting sessions. Use `@trace` decorator or `start_trace()` instead.
+- `end_session()`: **Deprecated.** Legacy function for ending sessions. Use `end_trace()` instead.
+- `record(event)`: **Deprecated.** Legacy function to record an event. Replaced by decorator-based tracing.
+- `track_agent()`: **Deprecated.** Legacy decorator for marking agents. Replaced by the `@agent` decorator.
+- `track_tool()`: **Deprecated.** Legacy decorator for marking tools. Replaced by the `@tool` decorator.
+- `ToolEvent()`, `ErrorEvent()`, `ActionEvent()`, `LLMEvent()`: **Deprecated.** Legacy event types. Replaced by automatic instrumentation and decorators.
diff --git a/tests/unit/test_session.py b/tests/unit/test_session.py
index 4b40bf770..d334dae5c 100644
--- a/tests/unit/test_session.py
+++ b/tests/unit/test_session.py
@@ -454,3 +454,223 @@ def test_session_management_integration():
# Verify calls were made
assert mock_tracer.start_trace.call_count >= 2
assert mock_tracer.end_trace.call_count >= 2
+
+
+# CrewAI Backwards Compatibility Tests
+# These tests ensure CrewAI integration patterns continue to work
+
+
+def test_session_auto_start(mock_tracing_core, mock_api_client, mock_trace_context, reset_client):
+ """Test auto-start session functionality for CrewAI compatibility"""
+ import agentops
+ from agentops.legacy import Session
+
+ # Configure mocks for session initialization
+ mock_tracing_core.start_trace.return_value = mock_trace_context
+
+ # Pass a dummy API key for the test
+ session = agentops.init(api_key="test-api-key", auto_start_session=True)
+
+ assert isinstance(session, Session)
+
+
+def test_crewai_backwards_compatibility(mock_tracing_core, mock_api_client, mock_trace_context, reset_client):
+ """
+ CrewAI needs to access:
+
+ agentops.track_agent
+ agentops.track_tool
+ agentops.start_session
+ agentops.end_session
+ agentops.ActionEvent
+ agentops.ToolEvent
+ """
+ import agentops
+ from agentops.legacy import Session
+
+ # Configure mocks
+ mock_tracing_core.start_trace.return_value = mock_trace_context
+
+ # Test initialization with API key
+ agentops.init(api_key="test-api-key")
+
+ # Test session management functions
+ session = agentops.start_session(tags=["test", "crewai"])
+ assert isinstance(session, Session)
+
+ # Test that passing a string to end_session doesn't raise an error
+ agentops.end_session("Success") # This pattern is used in CrewAI
+
+ # Test track_agent function exists and doesn't raise errors
+ try:
+ # Mock an agent object similar to what CrewAI would provide
+ class MockAgent:
+ def __init__(self):
+ self.role = "Test Agent"
+ self.goal = "Testing"
+ self.id = "test-agent-id"
+
+ agent = MockAgent()
+ agentops.track_agent(agent)
+ except Exception as e:
+ assert False, f"track_agent raised an exception: {e}"
+
+ # Test track_tool function exists and doesn't raise errors
+ try:
+ # Mock a tool object similar to what CrewAI would provide
+ class MockTool:
+ def __init__(self):
+ self.name = "Test Tool"
+ self.description = "A test tool"
+
+ tool = MockTool()
+ agentops.track_tool(tool, "Test Agent")
+ except Exception as e:
+ assert False, f"track_tool raised an exception: {e}"
+
+ # Test events that CrewAI might use
+ tool_event = agentops.ToolEvent(name="test_tool")
+ action_event = agentops.ActionEvent(action_type="test_action")
+
+ # Verify that record function works with these events
+ agentops.record(tool_event)
+ agentops.record(action_event)
+
+
+def test_crewai_kwargs_pattern(mock_tracing_core, mock_api_client, mock_trace_context, reset_client):
+ """
+ Test the CrewAI < 0.105.0 pattern where end_session is called with only kwargs.
+
+ In versions < 0.105.0, CrewAI directly calls:
+ agentops.end_session(
+ end_state="Success",
+ end_state_reason="Finished Execution",
+ is_auto_end=True
+ )
+ """
+ import agentops
+ from agentops.legacy import Session
+
+ # Configure mocks
+ mock_tracing_core.start_trace.return_value = mock_trace_context
+
+ # Initialize with test API key
+ agentops.init(api_key="test-api-key")
+
+ # Create a session
+ session = agentops.start_session(tags=["test", "crewai-kwargs"])
+ assert isinstance(session, Session)
+
+ # Test the CrewAI < 0.105.0 pattern - calling end_session with only kwargs
+ agentops.end_session(end_state="Success", end_state_reason="Finished Execution", is_auto_end=True)
+
+ # After calling end_session, creating a new session should work correctly
+ # (this implicitly tests that the internal state is reset properly)
+ new_session = agentops.start_session(tags=["test", "post-end"])
+ assert isinstance(new_session, Session)
+
+
+def test_crewai_kwargs_pattern_no_session(mock_tracing_core, mock_api_client, reset_client):
+ """
+ Test the CrewAI < 0.105.0 pattern where end_session is called with only kwargs,
+ but no session has been created.
+
+ This should log a warning but not fail.
+ """
+ import agentops
+
+ # Initialize with test API key
+ agentops.init(api_key="test-api-key")
+
+ # We don't need to explicitly clear the session state
+ # Just make sure we start with a clean state by calling init
+
+ # Test the CrewAI < 0.105.0 pattern - calling end_session with only kwargs
+ # when no session exists. This should not raise an error.
+ agentops.end_session(end_state="Success", end_state_reason="Finished Execution", is_auto_end=True)
+
+
+def test_crewai_kwargs_force_flush(mock_tracing_core, mock_api_client, mock_trace_context, reset_client):
+ """
+ Test that when using the CrewAI < 0.105.0 pattern (end_session with kwargs),
+ the spans are properly exported to the backend with force_flush.
+
+ This is a more comprehensive test that ensures spans are actually sent
+ to the backend when using the CrewAI integration pattern.
+ """
+ import agentops
+ import time
+
+ # Configure mocks
+ mock_tracing_core.start_trace.return_value = mock_trace_context
+
+ # Initialize AgentOps with API key
+ agentops.init(api_key="test-api-key")
+
+ # Create a session
+ agentops.start_session(tags=["test", "crewai-integration"])
+
+ # Simulate some work
+ time.sleep(0.1)
+
+ # End session with kwargs (CrewAI < 0.105.0 pattern)
+ agentops.end_session(end_state="Success", end_state_reason="Test Finished", is_auto_end=True)
+
+ # Explicitly ensure the core isn't already shut down for the test
+ # Note: We're using mocks, so we check the mock's initialized state
+ assert mock_tracing_core.initialized, "Mocked tracer should still be initialized"
+
+
+def test_crewai_task_instrumentation(mock_tracing_core, mock_api_client, mock_trace_context, reset_client):
+ """
+ Test the CrewAI task instrumentation focusing on span attributes and tags.
+ This test verifies that task spans are properly created with correct attributes
+ and tags without requiring a session.
+ """
+ import agentops
+ from opentelemetry.trace import SpanKind
+ from agentops.semconv import SpanAttributes, AgentOpsSpanKindValues
+ from opentelemetry import trace
+ from agentops.semconv.core import CoreAttributes
+
+ # Configure mocks
+ mock_tracing_core.start_trace.return_value = mock_trace_context
+
+ # Initialize AgentOps with API key and default tags
+ agentops.init(
+ api_key="test-api-key",
+ )
+ agentops.start_session(tags=["test", "crewai-integration"])
+ # Get the tracer
+ tracer = trace.get_tracer(__name__)
+
+ # Create a mock task instance
+ class MockTask:
+ def __init__(self):
+ self.description = "Test Task Description"
+ self.agent = "Test Agent"
+ self.tools = ["tool1", "tool2"]
+
+ task = MockTask()
+
+ # Start a span for the task
+ with tracer.start_as_current_span(
+ f"{task.description}.task",
+ kind=SpanKind.CLIENT,
+ attributes={
+ SpanAttributes.AGENTOPS_SPAN_KIND: AgentOpsSpanKindValues.TASK.value,
+ CoreAttributes.TAGS: ["crewai", "task-test"],
+ },
+ ) as span:
+ # Verify span attributes
+ assert span.attributes[SpanAttributes.AGENTOPS_SPAN_KIND] == AgentOpsSpanKindValues.TASK.value
+ assert "crewai" in span.attributes[CoreAttributes.TAGS]
+ assert "task-test" in span.attributes[CoreAttributes.TAGS]
+
+ # Verify span name
+ assert span.name == f"{task.description}.task"
+
+ # Verify span kind
+ assert span.kind == SpanKind.CLIENT
+
+ agentops.end_session(end_state="Success", end_state_reason="Test Finished", is_auto_end=True)
diff --git a/tests/unit/test_session_legacy.py b/tests/unit/test_session_legacy.py
deleted file mode 100644
index e3b587f24..000000000
--- a/tests/unit/test_session_legacy.py
+++ /dev/null
@@ -1,198 +0,0 @@
-def test_session_auto_start(instrumentation):
- import agentops
- from agentops.legacy import Session
-
- # Pass a dummy API key for the test
- session = agentops.init(api_key="test-api-key", auto_start_session=True)
-
- assert isinstance(session, Session)
-
-
-def test_crewai_backwards_compatibility(instrumentation):
- """
- CrewAI needs to access:
-
- agentops.track_agent
- agentops.track_tool
- agentops.start_session
- agentops.end_session
- agentops.ActionEvent
- agentops.ToolEvent
- """
- import agentops
- from agentops.legacy import Session
-
- # Test initialization with API key
- agentops.init(api_key="test-api-key")
-
- # Test session management functions
- session = agentops.start_session(tags=["test", "crewai"])
- assert isinstance(session, Session)
-
- # Test that passing a string to end_session doesn't raise an error
- agentops.end_session("Success") # This pattern is used in CrewAI
-
- # Test track_agent function exists and doesn't raise errors
- try:
- # Mock an agent object similar to what CrewAI would provide
- class MockAgent:
- def __init__(self):
- self.role = "Test Agent"
- self.goal = "Testing"
- self.id = "test-agent-id"
-
- agent = MockAgent()
- agentops.track_agent(agent)
- except Exception as e:
- assert False, f"track_agent raised an exception: {e}"
-
- # Test track_tool function exists and doesn't raise errors
- try:
- # Mock a tool object similar to what CrewAI would provide
- class MockTool:
- def __init__(self):
- self.name = "Test Tool"
- self.description = "A test tool"
-
- tool = MockTool()
- agentops.track_tool(tool, "Test Agent")
- except Exception as e:
- assert False, f"track_tool raised an exception: {e}"
-
- # Test events that CrewAI might use
- tool_event = agentops.ToolEvent(name="test_tool")
- action_event = agentops.ActionEvent(action_type="test_action")
-
- # Verify that record function works with these events
- agentops.record(tool_event)
- agentops.record(action_event)
-
-
-def test_crewai_kwargs_pattern(instrumentation):
- """
- Test the CrewAI < 0.105.0 pattern where end_session is called with only kwargs.
-
- In versions < 0.105.0, CrewAI directly calls:
- agentops.end_session(
- end_state="Success",
- end_state_reason="Finished Execution",
- is_auto_end=True
- )
- """
- import agentops
- from agentops.legacy import Session
-
- # Initialize with test API key
- agentops.init(api_key="test-api-key")
-
- # Create a session
- session = agentops.start_session(tags=["test", "crewai-kwargs"])
- assert isinstance(session, Session)
-
- # Test the CrewAI < 0.105.0 pattern - calling end_session with only kwargs
- agentops.end_session(end_state="Success", end_state_reason="Finished Execution", is_auto_end=True)
-
- # After calling end_session, creating a new session should work correctly
- # (this implicitly tests that the internal state is reset properly)
- new_session = agentops.start_session(tags=["test", "post-end"])
- assert isinstance(new_session, Session)
-
-
-def test_crewai_kwargs_pattern_no_session(instrumentation):
- """
- Test the CrewAI < 0.105.0 pattern where end_session is called with only kwargs,
- but no session has been created.
-
- This should log a warning but not fail.
- """
- import agentops
-
- # Initialize with test API key
- agentops.init(api_key="test-api-key")
-
- # We don't need to explicitly clear the session state
- # Just make sure we start with a clean state by calling init
-
- # Test the CrewAI < 0.105.0 pattern - calling end_session with only kwargs
- # when no session exists. This should not raise an error.
- agentops.end_session(end_state="Success", end_state_reason="Finished Execution", is_auto_end=True)
-
-
-def test_crewai_kwargs_force_flush():
- """
- Test that when using the CrewAI < 0.105.0 pattern (end_session with kwargs),
- the spans are properly exported to the backend with force_flush.
-
- This is a more comprehensive test that ensures spans are actually sent
- to the backend when using the CrewAI integration pattern.
- """
- import agentops
- from agentops.sdk.core import tracer
- import time
-
- # Initialize AgentOps with API key
- agentops.init(api_key="test-api-key")
-
- # Create a session
- agentops.start_session(tags=["test", "crewai-integration"])
-
- # Simulate some work
- time.sleep(0.1)
-
- # End session with kwargs (CrewAI < 0.105.0 pattern)
- agentops.end_session(end_state="Success", end_state_reason="Test Finished", is_auto_end=True)
-
- # Explicitly ensure the core isn't already shut down for the test
- assert tracer._initialized, "Global tracer should still be initialized"
-
-
-def test_crewai_task_instrumentation(instrumentation):
- """
- Test the CrewAI task instrumentation focusing on span attributes and tags.
- This test verifies that task spans are properly created with correct attributes
- and tags without requiring a session.
- """
- import agentops
- from opentelemetry.trace import SpanKind
- from agentops.semconv import SpanAttributes, AgentOpsSpanKindValues
- from opentelemetry import trace
- from agentops.semconv.core import CoreAttributes
-
- # Initialize AgentOps with API key and default tags
- agentops.init(
- api_key="test-api-key",
- )
- agentops.start_session(tags=["test", "crewai-integration"])
- # Get the tracer
- tracer = trace.get_tracer(__name__)
-
- # Create a mock task instance
- class MockTask:
- def __init__(self):
- self.description = "Test Task Description"
- self.agent = "Test Agent"
- self.tools = ["tool1", "tool2"]
-
- task = MockTask()
-
- # Start a span for the task
- with tracer.start_as_current_span(
- f"{task.description}.task",
- kind=SpanKind.CLIENT,
- attributes={
- SpanAttributes.AGENTOPS_SPAN_KIND: AgentOpsSpanKindValues.TASK.value,
- CoreAttributes.TAGS: ["crewai", "task-test"],
- },
- ) as span:
- # Verify span attributes
- assert span.attributes[SpanAttributes.AGENTOPS_SPAN_KIND] == AgentOpsSpanKindValues.TASK.value
- assert "crewai" in span.attributes[CoreAttributes.TAGS]
- assert "task-test" in span.attributes[CoreAttributes.TAGS]
-
- # Verify span name
- assert span.name == f"{task.description}.task"
-
- # Verify span kind
- assert span.kind == SpanKind.CLIENT
-
- agentops.end_session(end_state="Success", end_state_reason="Test Finished", is_auto_end=True)