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,44 @@
1
+ from agentops.helpers.time import get_ISO_time
2
+ from agentops.helpers.serialization import (
3
+ AgentOpsJSONEncoder,
4
+ serialize_uuid,
5
+ safe_serialize,
6
+ is_jsonable,
7
+ filter_unjsonable,
8
+ )
9
+ from agentops.helpers.system import (
10
+ get_host_env,
11
+ get_sdk_details,
12
+ get_os_details,
13
+ get_cpu_details,
14
+ get_ram_details,
15
+ get_disk_details,
16
+ get_installed_packages,
17
+ get_current_directory,
18
+ get_virtual_env,
19
+ )
20
+ from agentops.helpers.version import get_agentops_version, check_agentops_update
21
+ from agentops.helpers.env import get_env_bool, get_env_int, get_env_list
22
+
23
+ __all__ = [
24
+ "get_ISO_time",
25
+ "AgentOpsJSONEncoder",
26
+ "serialize_uuid",
27
+ "safe_serialize",
28
+ "is_jsonable",
29
+ "filter_unjsonable",
30
+ "get_host_env",
31
+ "get_sdk_details",
32
+ "get_os_details",
33
+ "get_cpu_details",
34
+ "get_ram_details",
35
+ "get_disk_details",
36
+ "get_installed_packages",
37
+ "get_current_directory",
38
+ "get_virtual_env",
39
+ "get_agentops_version",
40
+ "check_agentops_update",
41
+ "get_env_bool",
42
+ "get_env_int",
43
+ "get_env_list",
44
+ ]
@@ -0,0 +1,54 @@
1
+ """
2
+ Helpers for interacting with the AgentOps dashboard.
3
+ """
4
+
5
+ from typing import Union, Optional
6
+ from termcolor import colored
7
+ from opentelemetry.sdk.trace import Span, ReadableSpan
8
+ from agentops.logging import logger
9
+
10
+
11
+ def get_trace_url(span: Union[Span, ReadableSpan]) -> str:
12
+ """
13
+ Generate a trace URL for a direct link to the session on the AgentOps dashboard.
14
+
15
+ Args:
16
+ span: The span to generate the URL for.
17
+
18
+ Returns:
19
+ The session URL.
20
+ """
21
+ trace_id: Union[int, str] = span.context.trace_id
22
+
23
+ # Convert trace_id to hex string if it's not already
24
+ # We don't add dashes to this to format it as a UUID since the dashboard doesn't either
25
+ if isinstance(trace_id, int):
26
+ trace_id = format(trace_id, "032x")
27
+
28
+ # Get the app_url from the config - import here to avoid circular imports
29
+ from agentops import get_client
30
+
31
+ app_url = get_client().config.app_url
32
+
33
+ return f"{app_url}/sessions?trace_id={trace_id}"
34
+
35
+
36
+ def log_trace_url(span: Union[Span, ReadableSpan], title: Optional[str] = None) -> None:
37
+ """
38
+ Log the trace URL for the AgentOps dashboard.
39
+
40
+ Args:
41
+ span: The span to log the URL for.
42
+ title: Optional title for the trace.
43
+ """
44
+ from agentops import get_client
45
+
46
+ try:
47
+ client = get_client()
48
+ if not client.config.log_session_replay_url:
49
+ return
50
+ except Exception:
51
+ return
52
+
53
+ session_url = get_trace_url(span)
54
+ logger.info(colored(f"\x1b[34mSession Replay for {title} trace: {session_url}\x1b[0m", "blue"))
@@ -0,0 +1,50 @@
1
+ """
2
+ Deprecation utilities for AgentOps SDK.
3
+ """
4
+
5
+ import functools
6
+ from typing import Set, Callable, Any
7
+ from agentops.logging import logger
8
+
9
+ # Track which deprecation warnings have been shown to avoid spam
10
+ _shown_warnings: Set[str] = set()
11
+
12
+
13
+ def deprecated(message: str):
14
+ """
15
+ Decorator to mark functions as deprecated.
16
+
17
+ Args:
18
+ message: Deprecation message to show
19
+ """
20
+
21
+ def decorator(func: Callable) -> Callable:
22
+ @functools.wraps(func)
23
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
24
+ warning_key = f"{func.__module__}.{func.__name__}"
25
+ if warning_key not in _shown_warnings:
26
+ logger.warning(f"{func.__name__}() is deprecated and will be removed in v4 in the future. {message}")
27
+ _shown_warnings.add(warning_key)
28
+ return func(*args, **kwargs)
29
+
30
+ return wrapper
31
+
32
+ return decorator
33
+
34
+
35
+ def warn_deprecated_param(param_name: str, replacement: str = None):
36
+ """
37
+ Warn about deprecated parameter usage.
38
+
39
+ Args:
40
+ param_name: Name of the deprecated parameter
41
+ replacement: Suggested replacement parameter
42
+ """
43
+ warning_key = f"param.{param_name}"
44
+ if warning_key not in _shown_warnings:
45
+ if replacement:
46
+ message = f"Parameter '{param_name}' is deprecated and will be removed in v4 in the future. Use '{replacement}' instead."
47
+ else:
48
+ message = f"Parameter '{param_name}' is deprecated and will be removed in v4 in the future."
49
+ logger.warning(message)
50
+ _shown_warnings.add(warning_key)
@@ -0,0 +1,52 @@
1
+ """Environment variable helper functions"""
2
+
3
+ import os
4
+ from typing import List, Optional, Set
5
+
6
+
7
+ def get_env_bool(key: str, default: bool) -> bool:
8
+ """Get boolean from environment variable
9
+
10
+ Args:
11
+ key: Environment variable name
12
+ default: Default value if not set
13
+
14
+ Returns:
15
+ bool: Parsed boolean value
16
+ """
17
+ val = os.getenv(key)
18
+ if val is None:
19
+ return default
20
+ return val.lower() in ("true", "1", "t", "yes")
21
+
22
+
23
+ def get_env_int(key: str, default: int) -> int:
24
+ """Get integer from environment variable
25
+
26
+ Args:
27
+ key: Environment variable name
28
+ default: Default value if not set
29
+
30
+ Returns:
31
+ int: Parsed integer value
32
+ """
33
+ try:
34
+ return int(os.getenv(key, default))
35
+ except (TypeError, ValueError):
36
+ return default
37
+
38
+
39
+ def get_env_list(key: str, default: Optional[List[str]] = None) -> Set[str]:
40
+ """Get comma-separated list from environment variable
41
+
42
+ Args:
43
+ key: Environment variable name
44
+ default: Default list if not set
45
+
46
+ Returns:
47
+ Set[str]: Set of parsed values
48
+ """
49
+ val = os.getenv(key)
50
+ if val is None:
51
+ return set(default or [])
52
+ return set(val.split(","))
@@ -0,0 +1,137 @@
1
+ """Serialization helpers for AgentOps"""
2
+
3
+ import json
4
+ from datetime import datetime
5
+ from decimal import Decimal
6
+ from enum import Enum
7
+ from typing import Any
8
+ from uuid import UUID
9
+
10
+ from agentops.logging import logger
11
+
12
+
13
+ def is_jsonable(x):
14
+ try:
15
+ json.dumps(x)
16
+ return True
17
+ except (TypeError, OverflowError):
18
+ return False
19
+
20
+
21
+ def filter_unjsonable(d: dict) -> dict:
22
+ def filter_dict(obj):
23
+ if isinstance(obj, dict):
24
+ return {
25
+ k: (
26
+ filter_dict(v)
27
+ if isinstance(v, (dict, list)) or is_jsonable(v)
28
+ else str(v)
29
+ if isinstance(v, UUID)
30
+ else ""
31
+ )
32
+ for k, v in obj.items()
33
+ }
34
+ elif isinstance(obj, list):
35
+ return [
36
+ (
37
+ filter_dict(x)
38
+ if isinstance(x, (dict, list)) or is_jsonable(x)
39
+ else str(x)
40
+ if isinstance(x, UUID)
41
+ else ""
42
+ )
43
+ for x in obj
44
+ ]
45
+ else:
46
+ return obj if is_jsonable(obj) or isinstance(obj, UUID) else ""
47
+
48
+ return filter_dict(d)
49
+
50
+
51
+ class AgentOpsJSONEncoder(json.JSONEncoder):
52
+ """Custom JSON encoder for AgentOps types"""
53
+
54
+ def default(self, obj: Any) -> Any:
55
+ if isinstance(obj, UUID):
56
+ return str(obj)
57
+ if isinstance(obj, datetime):
58
+ return obj.isoformat()
59
+ if isinstance(obj, Decimal):
60
+ return str(obj)
61
+ if isinstance(obj, set):
62
+ return list(obj)
63
+ if hasattr(obj, "to_json"):
64
+ return obj.to_json()
65
+ if isinstance(obj, Enum):
66
+ return obj.value
67
+ return str(obj)
68
+
69
+
70
+ def serialize_uuid(obj: UUID) -> str:
71
+ """Serialize UUID to string"""
72
+ return str(obj)
73
+
74
+
75
+ def model_to_dict(obj: Any) -> dict:
76
+ """Convert a model object to a dictionary safely.
77
+
78
+ Handles various model types including:
79
+ - Pydantic models (model_dump/dict methods)
80
+ - Dictionary-like objects
81
+ - API response objects with parse method
82
+ - Objects with __dict__ attribute
83
+
84
+ Args:
85
+ obj: The model object to convert to dictionary
86
+
87
+ Returns:
88
+ Dictionary representation of the object, or empty dict if conversion fails
89
+ """
90
+ if obj is None:
91
+ return {}
92
+ if isinstance(obj, dict):
93
+ return obj
94
+ if hasattr(obj, "model_dump"): # Pydantic v2
95
+ return obj.model_dump()
96
+ elif hasattr(obj, "dict"): # Pydantic v1
97
+ return obj.dict()
98
+ # TODO this is causing recursion on nested objects.
99
+ # elif hasattr(obj, "parse"): # Raw API response
100
+ # return model_to_dict(obj.parse())
101
+ else:
102
+ # Try to use __dict__ as fallback
103
+ try:
104
+ return obj.__dict__
105
+ except:
106
+ return {}
107
+
108
+
109
+ def safe_serialize(obj: Any) -> Any:
110
+ """Safely serialize an object to JSON-compatible format
111
+
112
+ This function handles complex objects by:
113
+ 1. Returning strings untouched (even if they contain JSON)
114
+ 2. Converting models to dictionaries
115
+ 3. Using custom JSON encoder to handle special types
116
+ 4. Falling back to string representation only when necessary
117
+
118
+ Args:
119
+ obj: The object to serialize
120
+
121
+ Returns:
122
+ If obj is a string, returns the original string untouched.
123
+ Otherwise, returns a JSON string representation of the object.
124
+ """
125
+ # Return strings untouched
126
+ if isinstance(obj, str):
127
+ return obj
128
+
129
+ # Convert any model objects to dictionaries
130
+ if hasattr(obj, "model_dump") or hasattr(obj, "dict") or hasattr(obj, "parse"):
131
+ obj = model_to_dict(obj)
132
+
133
+ try:
134
+ return json.dumps(obj, cls=AgentOpsJSONEncoder)
135
+ except (TypeError, ValueError) as e:
136
+ logger.warning(f"Failed to serialize object: {e}")
137
+ return str(obj)
@@ -0,0 +1,178 @@
1
+ import importlib.metadata
2
+ import os
3
+ import platform
4
+ import socket
5
+ import sys
6
+
7
+ import psutil # type: ignore
8
+
9
+ from agentops.logging import logger
10
+ from agentops.helpers.version import get_agentops_version
11
+
12
+
13
+ def get_imported_libraries():
14
+ """
15
+ Get the top-level imported libraries in the current script.
16
+
17
+ Returns:
18
+ list: List of imported libraries
19
+ """
20
+ user_libs = []
21
+
22
+ builtin_modules = {
23
+ "builtins",
24
+ "sys",
25
+ "os",
26
+ "_thread",
27
+ "abc",
28
+ "io",
29
+ "re",
30
+ "types",
31
+ "collections",
32
+ "enum",
33
+ "math",
34
+ "datetime",
35
+ "time",
36
+ "warnings",
37
+ }
38
+
39
+ try:
40
+ main_module = sys.modules.get("__main__")
41
+ if main_module and hasattr(main_module, "__dict__"):
42
+ for name, obj in main_module.__dict__.items():
43
+ if isinstance(obj, type(sys)) and hasattr(obj, "__name__"):
44
+ mod_name = obj.__name__.split(".")[0]
45
+ if mod_name and not mod_name.startswith("_") and mod_name not in builtin_modules:
46
+ user_libs.append(mod_name)
47
+ except Exception as e:
48
+ logger.debug(f"Error getting imports: {e}")
49
+
50
+ return user_libs
51
+
52
+
53
+ def get_sdk_details():
54
+ try:
55
+ return {
56
+ "AgentOps SDK Version": get_agentops_version(),
57
+ "Python Version": platform.python_version(),
58
+ "System Packages": get_sys_packages(),
59
+ }
60
+ except:
61
+ return {}
62
+
63
+
64
+ def get_sys_packages():
65
+ sys_packages = {}
66
+ for module in sys.modules:
67
+ try:
68
+ version = importlib.metadata.version(module)
69
+ sys_packages[module] = version
70
+ except importlib.metadata.PackageNotFoundError:
71
+ # Skip built-in modules and those without package metadata
72
+ continue
73
+
74
+ return sys_packages
75
+
76
+
77
+ def get_installed_packages():
78
+ try:
79
+ return {
80
+ # TODO: add to opt out
81
+ "Installed Packages": {
82
+ dist.metadata.get("Name"): dist.metadata.get("Version") for dist in importlib.metadata.distributions()
83
+ }
84
+ }
85
+ except:
86
+ return {}
87
+
88
+
89
+ def get_current_directory():
90
+ try:
91
+ return {"Project Working Directory": os.getcwd()}
92
+ except:
93
+ return {}
94
+
95
+
96
+ def get_virtual_env():
97
+ try:
98
+ return {"Virtual Environment": os.environ.get("VIRTUAL_ENV", None)}
99
+ except:
100
+ return {}
101
+
102
+
103
+ def get_os_details():
104
+ try:
105
+ return {
106
+ "Hostname": socket.gethostname(),
107
+ "OS": platform.system(),
108
+ "OS Version": platform.version(),
109
+ "OS Release": platform.release(),
110
+ }
111
+ except:
112
+ return {}
113
+
114
+
115
+ def get_cpu_details():
116
+ try:
117
+ return {
118
+ "Physical cores": psutil.cpu_count(logical=False),
119
+ "Total cores": psutil.cpu_count(logical=True),
120
+ # "Max Frequency": f"{psutil.cpu_freq().max:.2f}Mhz", # Fails right now
121
+ "CPU Usage": f"{psutil.cpu_percent()}%",
122
+ }
123
+ except:
124
+ return {}
125
+
126
+
127
+ def get_ram_details():
128
+ try:
129
+ ram_info = psutil.virtual_memory()
130
+ return {
131
+ "Total": f"{ram_info.total / (1024**3):.2f} GB",
132
+ "Available": f"{ram_info.available / (1024**3):.2f} GB",
133
+ "Used": f"{ram_info.used / (1024**3):.2f} GB",
134
+ "Percentage": f"{ram_info.percent}%",
135
+ }
136
+ except:
137
+ return {}
138
+
139
+
140
+ def get_disk_details():
141
+ partitions = psutil.disk_partitions()
142
+ disk_info = {}
143
+ for partition in partitions:
144
+ try:
145
+ usage = psutil.disk_usage(partition.mountpoint)
146
+ disk_info[partition.device] = {
147
+ "Mountpoint": partition.mountpoint,
148
+ "Total": f"{usage.total / (1024**3):.2f} GB",
149
+ "Used": f"{usage.used / (1024**3):.2f} GB",
150
+ "Free": f"{usage.free / (1024**3):.2f} GB",
151
+ "Percentage": f"{usage.percent}%",
152
+ }
153
+ except OSError as inaccessible:
154
+ # Skip inaccessible partitions, such as removable drives with no media
155
+ logger.debug("Mountpoint %s inaccessible: %s", partition.mountpoint, inaccessible)
156
+
157
+ return disk_info
158
+
159
+
160
+ def get_host_env(opt_out: bool = False):
161
+ if opt_out:
162
+ return {
163
+ "SDK": get_sdk_details(),
164
+ "OS": get_os_details(),
165
+ "Project Working Directory": get_current_directory(),
166
+ "Virtual Environment": get_virtual_env(),
167
+ }
168
+ else:
169
+ return {
170
+ "SDK": get_sdk_details(),
171
+ "OS": get_os_details(),
172
+ "CPU": get_cpu_details(),
173
+ "RAM": get_ram_details(),
174
+ "Disk": get_disk_details(),
175
+ "Installed Packages": get_installed_packages(),
176
+ "Project Working Directory": get_current_directory(),
177
+ "Virtual Environment": get_virtual_env(),
178
+ }
@@ -0,0 +1,11 @@
1
+ from datetime import datetime, timezone
2
+
3
+
4
+ def get_ISO_time():
5
+ """
6
+ Get the current UTC time in ISO 8601 format with milliseconds precision in UTC timezone.
7
+
8
+ Returns:
9
+ str: The current UTC time as a string in ISO 8601 format.
10
+ """
11
+ return datetime.now(timezone.utc).isoformat()
@@ -0,0 +1,36 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ import requests
4
+
5
+ from agentops.logging import logger
6
+
7
+
8
+ def get_agentops_version():
9
+ try:
10
+ pkg_version = version("agentops")
11
+ return pkg_version
12
+ except Exception as e:
13
+ logger.warning("Error reading package version: %s", e)
14
+ return None
15
+
16
+
17
+ def check_agentops_update():
18
+ try:
19
+ response = requests.get("https://pypi.org/pypi/agentops/json")
20
+
21
+ if response.status_code == 200:
22
+ json_data = response.json()
23
+ latest_version = json_data["info"]["version"]
24
+
25
+ try:
26
+ current_version = version("agentops")
27
+ except PackageNotFoundError:
28
+ return None
29
+
30
+ if not latest_version == current_version:
31
+ logger.warning(
32
+ " WARNING: agentops is out of date. Please update with the command: 'pip install --upgrade agentops'"
33
+ )
34
+ except Exception as e:
35
+ logger.debug(f"Failed to check for updates: {e}")
36
+ return None