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.
- agentops/__init__.py +488 -0
- agentops/client/__init__.py +5 -0
- agentops/client/api/__init__.py +71 -0
- agentops/client/api/base.py +162 -0
- agentops/client/api/types.py +21 -0
- agentops/client/api/versions/__init__.py +10 -0
- agentops/client/api/versions/v3.py +65 -0
- agentops/client/api/versions/v4.py +104 -0
- agentops/client/client.py +211 -0
- agentops/client/http/__init__.py +0 -0
- agentops/client/http/http_adapter.py +116 -0
- agentops/client/http/http_client.py +215 -0
- agentops/config.py +268 -0
- agentops/enums.py +36 -0
- agentops/exceptions.py +38 -0
- agentops/helpers/__init__.py +44 -0
- agentops/helpers/dashboard.py +54 -0
- agentops/helpers/deprecation.py +50 -0
- agentops/helpers/env.py +52 -0
- agentops/helpers/serialization.py +137 -0
- agentops/helpers/system.py +178 -0
- agentops/helpers/time.py +11 -0
- agentops/helpers/version.py +36 -0
- agentops/instrumentation/__init__.py +598 -0
- agentops/instrumentation/common/__init__.py +82 -0
- agentops/instrumentation/common/attributes.py +278 -0
- agentops/instrumentation/common/instrumentor.py +147 -0
- agentops/instrumentation/common/metrics.py +100 -0
- agentops/instrumentation/common/objects.py +26 -0
- agentops/instrumentation/common/span_management.py +176 -0
- agentops/instrumentation/common/streaming.py +218 -0
- agentops/instrumentation/common/token_counting.py +177 -0
- agentops/instrumentation/common/version.py +71 -0
- agentops/instrumentation/common/wrappers.py +235 -0
- agentops/legacy/__init__.py +277 -0
- agentops/legacy/event.py +156 -0
- agentops/logging/__init__.py +4 -0
- agentops/logging/config.py +86 -0
- agentops/logging/formatters.py +34 -0
- agentops/logging/instrument_logging.py +91 -0
- agentops/sdk/__init__.py +27 -0
- agentops/sdk/attributes.py +151 -0
- agentops/sdk/core.py +607 -0
- agentops/sdk/decorators/__init__.py +51 -0
- agentops/sdk/decorators/factory.py +486 -0
- agentops/sdk/decorators/utility.py +216 -0
- agentops/sdk/exporters.py +87 -0
- agentops/sdk/processors.py +71 -0
- agentops/sdk/types.py +21 -0
- agentops/semconv/__init__.py +36 -0
- agentops/semconv/agent.py +29 -0
- agentops/semconv/core.py +19 -0
- agentops/semconv/enum.py +11 -0
- agentops/semconv/instrumentation.py +13 -0
- agentops/semconv/langchain.py +63 -0
- agentops/semconv/message.py +61 -0
- agentops/semconv/meters.py +24 -0
- agentops/semconv/resource.py +52 -0
- agentops/semconv/span_attributes.py +118 -0
- agentops/semconv/span_kinds.py +50 -0
- agentops/semconv/status.py +11 -0
- agentops/semconv/tool.py +15 -0
- agentops/semconv/workflow.py +69 -0
- agentops/validation.py +357 -0
- mseep_agentops-0.4.18.dist-info/METADATA +49 -0
- mseep_agentops-0.4.18.dist-info/RECORD +94 -0
- mseep_agentops-0.4.18.dist-info/WHEEL +5 -0
- mseep_agentops-0.4.18.dist-info/licenses/LICENSE +21 -0
- mseep_agentops-0.4.18.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/conftest.py +10 -0
- tests/unit/__init__.py +0 -0
- tests/unit/client/__init__.py +1 -0
- tests/unit/client/test_http_adapter.py +221 -0
- tests/unit/client/test_http_client.py +206 -0
- tests/unit/conftest.py +54 -0
- tests/unit/sdk/__init__.py +1 -0
- tests/unit/sdk/instrumentation_tester.py +207 -0
- tests/unit/sdk/test_attributes.py +392 -0
- tests/unit/sdk/test_concurrent_instrumentation.py +468 -0
- tests/unit/sdk/test_decorators.py +763 -0
- tests/unit/sdk/test_exporters.py +241 -0
- tests/unit/sdk/test_factory.py +1188 -0
- tests/unit/sdk/test_internal_span_processor.py +397 -0
- tests/unit/sdk/test_resource_attributes.py +35 -0
- tests/unit/test_config.py +82 -0
- tests/unit/test_context_manager.py +777 -0
- tests/unit/test_events.py +27 -0
- tests/unit/test_host_env.py +54 -0
- tests/unit/test_init_py.py +501 -0
- tests/unit/test_serialization.py +433 -0
- tests/unit/test_session.py +676 -0
- tests/unit/test_user_agent.py +34 -0
- 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()
|
agentops/sdk/__init__.py
ADDED
@@ -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
|
+
}
|