mseep-agentops 0.4.18__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. agentops/__init__.py +488 -0
  2. agentops/client/__init__.py +5 -0
  3. agentops/client/api/__init__.py +71 -0
  4. agentops/client/api/base.py +162 -0
  5. agentops/client/api/types.py +21 -0
  6. agentops/client/api/versions/__init__.py +10 -0
  7. agentops/client/api/versions/v3.py +65 -0
  8. agentops/client/api/versions/v4.py +104 -0
  9. agentops/client/client.py +211 -0
  10. agentops/client/http/__init__.py +0 -0
  11. agentops/client/http/http_adapter.py +116 -0
  12. agentops/client/http/http_client.py +215 -0
  13. agentops/config.py +268 -0
  14. agentops/enums.py +36 -0
  15. agentops/exceptions.py +38 -0
  16. agentops/helpers/__init__.py +44 -0
  17. agentops/helpers/dashboard.py +54 -0
  18. agentops/helpers/deprecation.py +50 -0
  19. agentops/helpers/env.py +52 -0
  20. agentops/helpers/serialization.py +137 -0
  21. agentops/helpers/system.py +178 -0
  22. agentops/helpers/time.py +11 -0
  23. agentops/helpers/version.py +36 -0
  24. agentops/instrumentation/__init__.py +598 -0
  25. agentops/instrumentation/common/__init__.py +82 -0
  26. agentops/instrumentation/common/attributes.py +278 -0
  27. agentops/instrumentation/common/instrumentor.py +147 -0
  28. agentops/instrumentation/common/metrics.py +100 -0
  29. agentops/instrumentation/common/objects.py +26 -0
  30. agentops/instrumentation/common/span_management.py +176 -0
  31. agentops/instrumentation/common/streaming.py +218 -0
  32. agentops/instrumentation/common/token_counting.py +177 -0
  33. agentops/instrumentation/common/version.py +71 -0
  34. agentops/instrumentation/common/wrappers.py +235 -0
  35. agentops/legacy/__init__.py +277 -0
  36. agentops/legacy/event.py +156 -0
  37. agentops/logging/__init__.py +4 -0
  38. agentops/logging/config.py +86 -0
  39. agentops/logging/formatters.py +34 -0
  40. agentops/logging/instrument_logging.py +91 -0
  41. agentops/sdk/__init__.py +27 -0
  42. agentops/sdk/attributes.py +151 -0
  43. agentops/sdk/core.py +607 -0
  44. agentops/sdk/decorators/__init__.py +51 -0
  45. agentops/sdk/decorators/factory.py +486 -0
  46. agentops/sdk/decorators/utility.py +216 -0
  47. agentops/sdk/exporters.py +87 -0
  48. agentops/sdk/processors.py +71 -0
  49. agentops/sdk/types.py +21 -0
  50. agentops/semconv/__init__.py +36 -0
  51. agentops/semconv/agent.py +29 -0
  52. agentops/semconv/core.py +19 -0
  53. agentops/semconv/enum.py +11 -0
  54. agentops/semconv/instrumentation.py +13 -0
  55. agentops/semconv/langchain.py +63 -0
  56. agentops/semconv/message.py +61 -0
  57. agentops/semconv/meters.py +24 -0
  58. agentops/semconv/resource.py +52 -0
  59. agentops/semconv/span_attributes.py +118 -0
  60. agentops/semconv/span_kinds.py +50 -0
  61. agentops/semconv/status.py +11 -0
  62. agentops/semconv/tool.py +15 -0
  63. agentops/semconv/workflow.py +69 -0
  64. agentops/validation.py +357 -0
  65. mseep_agentops-0.4.18.dist-info/METADATA +49 -0
  66. mseep_agentops-0.4.18.dist-info/RECORD +94 -0
  67. mseep_agentops-0.4.18.dist-info/WHEEL +5 -0
  68. mseep_agentops-0.4.18.dist-info/licenses/LICENSE +21 -0
  69. mseep_agentops-0.4.18.dist-info/top_level.txt +2 -0
  70. tests/__init__.py +0 -0
  71. tests/conftest.py +10 -0
  72. tests/unit/__init__.py +0 -0
  73. tests/unit/client/__init__.py +1 -0
  74. tests/unit/client/test_http_adapter.py +221 -0
  75. tests/unit/client/test_http_client.py +206 -0
  76. tests/unit/conftest.py +54 -0
  77. tests/unit/sdk/__init__.py +1 -0
  78. tests/unit/sdk/instrumentation_tester.py +207 -0
  79. tests/unit/sdk/test_attributes.py +392 -0
  80. tests/unit/sdk/test_concurrent_instrumentation.py +468 -0
  81. tests/unit/sdk/test_decorators.py +763 -0
  82. tests/unit/sdk/test_exporters.py +241 -0
  83. tests/unit/sdk/test_factory.py +1188 -0
  84. tests/unit/sdk/test_internal_span_processor.py +397 -0
  85. tests/unit/sdk/test_resource_attributes.py +35 -0
  86. tests/unit/test_config.py +82 -0
  87. tests/unit/test_context_manager.py +777 -0
  88. tests/unit/test_events.py +27 -0
  89. tests/unit/test_host_env.py +54 -0
  90. tests/unit/test_init_py.py +501 -0
  91. tests/unit/test_serialization.py +433 -0
  92. tests/unit/test_session.py +676 -0
  93. tests/unit/test_user_agent.py +34 -0
  94. tests/unit/test_validation.py +405 -0
@@ -0,0 +1,86 @@
1
+ import logging
2
+ import os
3
+
4
+ from agentops.logging.formatters import AgentOpsLogFileFormatter, AgentOpsLogFormatter
5
+
6
+ # Create the logger at module level
7
+ logger = logging.getLogger("agentops")
8
+ logger.propagate = False
9
+ logger.setLevel(logging.CRITICAL)
10
+
11
+
12
+ def configure_logging(config=None): # Remove type hint temporarily to avoid circular import
13
+ """Configure the AgentOps logger with console and optional file handlers.
14
+
15
+ Args:
16
+ config: Optional Config instance. If not provided, a new Config instance will be created.
17
+ """
18
+ # Defer the Config import to avoid circular dependency
19
+ if config is None:
20
+ from agentops.config import Config
21
+
22
+ config = Config()
23
+
24
+ # Use env var as override if present, otherwise use config
25
+ log_level_env = os.environ.get("AGENTOPS_LOG_LEVEL", "").upper()
26
+ if log_level_env and hasattr(logging, log_level_env):
27
+ log_level = getattr(logging, log_level_env)
28
+ else:
29
+ # Handle string log levels from config
30
+ if isinstance(config.log_level, str):
31
+ log_level_str = config.log_level.upper()
32
+ if hasattr(logging, log_level_str):
33
+ log_level = getattr(logging, log_level_str)
34
+ else:
35
+ log_level = logging.INFO
36
+ else:
37
+ log_level = config.log_level if isinstance(config.log_level, int) else logging.INFO
38
+
39
+ logger.setLevel(log_level)
40
+
41
+ # Remove existing handlers
42
+ for handler in logger.handlers[:]:
43
+ logger.removeHandler(handler)
44
+
45
+ # Configure console logging
46
+ stream_handler = logging.StreamHandler()
47
+ stream_handler.setLevel(log_level)
48
+ stream_handler.setFormatter(AgentOpsLogFormatter())
49
+ logger.addHandler(stream_handler)
50
+
51
+ # Configure file logging if enabled
52
+ log_to_file = os.environ.get("AGENTOPS_LOGGING_TO_FILE", "True").lower() == "true"
53
+ if log_to_file:
54
+ file_handler = logging.FileHandler("agentops.log", mode="w")
55
+ file_handler.setLevel(log_level)
56
+ formatter = AgentOpsLogFileFormatter("%(asctime)s - %(levelname)s - %(message)s")
57
+ file_handler.setFormatter(formatter)
58
+ logger.addHandler(file_handler)
59
+
60
+ return logger
61
+
62
+
63
+ def intercept_opentelemetry_logging():
64
+ """
65
+ Configure OpenTelemetry logging to redirect all messages to the AgentOps logger.
66
+ All OpenTelemetry logs will be prefixed with [opentelemetry.X] and set to DEBUG level.
67
+ """
68
+ prefix = "opentelemetry"
69
+ otel_root_logger = logging.getLogger(prefix)
70
+ otel_root_logger.propagate = False
71
+ otel_root_logger.setLevel(logging.DEBUG) # capture all
72
+
73
+ for handler in otel_root_logger.handlers[:]:
74
+ otel_root_logger.removeHandler(handler)
75
+
76
+ # Create a handler that forwards all messages to the AgentOps logger
77
+ class OtelLogHandler(logging.Handler):
78
+ def emit(self, record):
79
+ if record.name.startswith(f"{prefix}."):
80
+ module_name = record.name.replace(f"{prefix}.", "", 1)
81
+ else:
82
+ module_name = record.name
83
+ message = f"[{prefix}.{module_name}] {record.getMessage()}"
84
+ logger.debug(message)
85
+
86
+ otel_root_logger.addHandler(OtelLogHandler())
@@ -0,0 +1,34 @@
1
+ import logging
2
+ import re
3
+
4
+
5
+ class AgentOpsLogFormatter(logging.Formatter):
6
+ """Formatter for console logging with colors and prefix."""
7
+
8
+ blue = "\x1b[34m"
9
+ bold_red = "\x1b[31;1m"
10
+ reset = "\x1b[0m"
11
+ prefix = "🖇 AgentOps: "
12
+
13
+ FORMATS = {
14
+ logging.DEBUG: f"(DEBUG) {prefix}%(message)s",
15
+ logging.INFO: f"{prefix}%(message)s",
16
+ logging.WARNING: f"{prefix}%(message)s",
17
+ logging.ERROR: f"{bold_red}{prefix}%(message)s{reset}",
18
+ logging.CRITICAL: f"{bold_red}{prefix}%(message)s{reset}",
19
+ }
20
+
21
+ def format(self, record):
22
+ log_fmt = self.FORMATS.get(record.levelno, self.FORMATS[logging.INFO])
23
+ formatter = logging.Formatter(log_fmt)
24
+ return formatter.format(record)
25
+
26
+
27
+ class AgentOpsLogFileFormatter(logging.Formatter):
28
+ """Formatter for file logging that removes ANSI escape codes."""
29
+
30
+ ANSI_ESCAPE_PATTERN = re.compile(r"\x1b\[[0-9;]*m")
31
+
32
+ def format(self, record):
33
+ record.msg = self.ANSI_ESCAPE_PATTERN.sub("", str(record.msg))
34
+ return super().format(record)
@@ -0,0 +1,91 @@
1
+ import builtins
2
+ import logging
3
+ import atexit
4
+ from typing import Any
5
+ from io import StringIO
6
+
7
+ _original_print = builtins.print
8
+
9
+ # Global buffer to store logs
10
+ _log_buffer = StringIO()
11
+
12
+
13
+ def setup_print_logger() -> None:
14
+ """
15
+ Instruments the built-in print function and configures logging to use a memory buffer.
16
+ Preserves existing logging configuration and console output behavior.
17
+ """
18
+ buffer_logger = logging.getLogger("agentops_buffer_logger")
19
+ buffer_logger.setLevel(logging.DEBUG)
20
+
21
+ # Check if the logger already has handlers to prevent duplicates
22
+ if not buffer_logger.handlers:
23
+ # Create a StreamHandler that writes to our StringIO buffer
24
+ buffer_handler = logging.StreamHandler(_log_buffer)
25
+ buffer_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
26
+ buffer_handler.setLevel(logging.DEBUG)
27
+ buffer_logger.addHandler(buffer_handler)
28
+
29
+ # Ensure the new logger doesn't propagate to root
30
+ buffer_logger.propagate = False
31
+
32
+ def print_logger(*args: Any, **kwargs: Any) -> None:
33
+ """
34
+ Custom print function that logs to buffer and console.
35
+
36
+ Args:
37
+ *args: Arguments to print
38
+ **kwargs: Keyword arguments to print
39
+ """
40
+ message = " ".join(str(arg) for arg in args)
41
+ buffer_logger.info(message)
42
+
43
+ # print to console using original print
44
+ _original_print(*args, **kwargs)
45
+
46
+ # Only replace print if it hasn't been replaced already
47
+ if builtins.print is _original_print:
48
+ builtins.print = print_logger
49
+
50
+ def cleanup():
51
+ """
52
+ Cleanup function to be called when the process exits.
53
+ Restores the original print function and clears the buffer.
54
+ """
55
+ try:
56
+ # Remove our buffer handler
57
+ for handler in buffer_logger.handlers[:]:
58
+ handler.close()
59
+ buffer_logger.removeHandler(handler)
60
+
61
+ # Clear the buffer
62
+ _log_buffer.seek(0)
63
+ _log_buffer.truncate()
64
+
65
+ # Restore the original print function
66
+ builtins.print = _original_print
67
+ except Exception as e:
68
+ # If something goes wrong during cleanup, just print the error
69
+ _original_print(f"Error during cleanup: {e}")
70
+
71
+ # Register the cleanup function to run when the process exits
72
+ atexit.register(cleanup)
73
+
74
+
75
+ def upload_logfile(trace_id: int) -> None:
76
+ """
77
+ Upload the log content from the memory buffer to the API.
78
+ """
79
+ from agentops import get_client
80
+
81
+ # Get the content from the buffer
82
+ log_content = _log_buffer.getvalue()
83
+ if not log_content:
84
+ return
85
+
86
+ client = get_client()
87
+ client.api.v4.upload_logfile(log_content, trace_id)
88
+
89
+ # Clear the buffer after upload
90
+ _log_buffer.seek(0)
91
+ _log_buffer.truncate()
@@ -0,0 +1,27 @@
1
+ """
2
+ AgentOps SDK for tracing and monitoring AI agents.
3
+
4
+ This module provides a high-level API for creating and managing spans
5
+ for different types of operations in AI agent workflows.
6
+ """
7
+
8
+ # Import decorators
9
+ from agentops.sdk.decorators import agent, operation, session, task, workflow
10
+
11
+ # from agentops.sdk.traced import TracedObject # Merged into TracedObject
12
+ from agentops.sdk.types import TracingConfig
13
+
14
+ from opentelemetry.trace.status import StatusCode
15
+
16
+ __all__ = [
17
+ # Core components
18
+ "TracingConfig",
19
+ # Decorators
20
+ "session",
21
+ "operation",
22
+ "agent",
23
+ "task",
24
+ "workflow",
25
+ # OpenTelemetry status codes
26
+ "StatusCode",
27
+ ]
@@ -0,0 +1,151 @@
1
+ """
2
+ Attribute management for AgentOps SDK.
3
+
4
+ This module contains functions that create attributes for various telemetry contexts,
5
+ isolating the knowledge of semantic conventions from the core tracing logic.
6
+ """
7
+
8
+ import platform
9
+ import os
10
+ from typing import Any, Optional, Union
11
+
12
+ import psutil # type: ignore[import-untyped]
13
+
14
+ from agentops.logging import logger
15
+ from agentops.semconv import ResourceAttributes, SpanAttributes, CoreAttributes
16
+ from agentops.helpers.system import get_imported_libraries
17
+
18
+
19
+ def get_system_resource_attributes() -> dict[str, Any]:
20
+ """
21
+ Get system resource attributes for telemetry.
22
+
23
+ Returns:
24
+ dictionary containing system information attributes
25
+ """
26
+ attributes: dict[str, Any] = {
27
+ ResourceAttributes.HOST_MACHINE: platform.machine(),
28
+ ResourceAttributes.HOST_NAME: platform.node(),
29
+ ResourceAttributes.HOST_NODE: platform.node(),
30
+ ResourceAttributes.HOST_PROCESSOR: platform.processor(),
31
+ ResourceAttributes.HOST_SYSTEM: platform.system(),
32
+ ResourceAttributes.HOST_VERSION: platform.version(),
33
+ ResourceAttributes.HOST_OS_RELEASE: platform.release(),
34
+ }
35
+
36
+ # Add CPU stats
37
+ try:
38
+ attributes[ResourceAttributes.CPU_COUNT] = os.cpu_count() or 0
39
+ attributes[ResourceAttributes.CPU_PERCENT] = psutil.cpu_percent(interval=0.1)
40
+ except Exception as e:
41
+ logger.debug(f"Error getting CPU stats: {e}")
42
+
43
+ # Add memory stats
44
+ try:
45
+ memory = psutil.virtual_memory()
46
+ attributes[ResourceAttributes.MEMORY_TOTAL] = memory.total
47
+ attributes[ResourceAttributes.MEMORY_AVAILABLE] = memory.available
48
+ attributes[ResourceAttributes.MEMORY_USED] = memory.used
49
+ attributes[ResourceAttributes.MEMORY_PERCENT] = memory.percent
50
+ except Exception as e:
51
+ logger.debug(f"Error getting memory stats: {e}")
52
+
53
+ return attributes
54
+
55
+
56
+ def get_global_resource_attributes(
57
+ service_name: str,
58
+ project_id: Optional[str] = None,
59
+ ) -> dict[str, Any]:
60
+ """
61
+ Get all global resource attributes for telemetry.
62
+
63
+ Combines service metadata and imported libraries into a complete
64
+ resource attributes dictionary.
65
+
66
+ Args:
67
+ service_name: Name of the service
68
+ project_id: Optional project ID
69
+
70
+ Returns:
71
+ dictionary containing all resource attributes
72
+ """
73
+ # Start with service attributes
74
+ attributes: dict[str, Any] = {
75
+ ResourceAttributes.SERVICE_NAME: service_name,
76
+ }
77
+
78
+ if project_id:
79
+ attributes[ResourceAttributes.PROJECT_ID] = project_id
80
+
81
+ if imported_libraries := get_imported_libraries():
82
+ attributes[ResourceAttributes.IMPORTED_LIBRARIES] = imported_libraries
83
+
84
+ return attributes
85
+
86
+
87
+ def get_trace_attributes(tags: Optional[Union[dict[str, Any], list[str]]] = None) -> dict[str, Any]:
88
+ """
89
+ Get attributes for trace spans.
90
+
91
+ Args:
92
+ tags: Optional tags to include (dict or list)
93
+
94
+ Returns:
95
+ dictionary containing trace attributes
96
+ """
97
+ attributes: dict[str, Any] = {}
98
+
99
+ if tags:
100
+ if isinstance(tags, list):
101
+ attributes[CoreAttributes.TAGS] = tags
102
+ elif isinstance(tags, dict):
103
+ attributes.update(tags) # Add dict tags directly
104
+ else:
105
+ logger.warning(f"Invalid tags format: {tags}. Must be list or dict.")
106
+
107
+ return attributes
108
+
109
+
110
+ def get_span_attributes(
111
+ operation_name: str, span_kind: str, version: Optional[int] = None, **kwargs: Any
112
+ ) -> dict[str, Any]:
113
+ """
114
+ Get attributes for operation spans.
115
+
116
+ Args:
117
+ operation_name: Name of the operation being traced
118
+ span_kind: Type of operation (from SpanKind)
119
+ version: Optional version identifier for the operation
120
+ **kwargs: Additional attributes to include
121
+
122
+ Returns:
123
+ dictionary containing span attributes
124
+ """
125
+ attributes: dict[str, Any] = {
126
+ SpanAttributes.AGENTOPS_SPAN_KIND: span_kind,
127
+ SpanAttributes.OPERATION_NAME: operation_name,
128
+ }
129
+
130
+ if version is not None:
131
+ attributes[SpanAttributes.OPERATION_VERSION] = version
132
+
133
+ # Add any additional attributes passed as kwargs
134
+ attributes.update(kwargs)
135
+
136
+ return attributes
137
+
138
+
139
+ def get_session_end_attributes(end_state: str) -> dict[str, Any]:
140
+ """
141
+ Get attributes for session ending.
142
+
143
+ Args:
144
+ end_state: The final state of the session
145
+
146
+ Returns:
147
+ dictionary containing session end attributes
148
+ """
149
+ return {
150
+ SpanAttributes.AGENTOPS_SESSION_END_STATE: end_state,
151
+ }