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,116 @@
1
+ from typing import Optional
2
+
3
+ from requests.adapters import HTTPAdapter
4
+ from urllib3.util import Retry
5
+
6
+ # from agentops.client.auth_manager import AuthManager
7
+
8
+
9
+ class BaseHTTPAdapter(HTTPAdapter):
10
+ """Base HTTP adapter with enhanced connection pooling and retry logic"""
11
+
12
+ def __init__(
13
+ self,
14
+ pool_connections: int = 15,
15
+ pool_maxsize: int = 256,
16
+ max_retries: Optional[Retry] = None,
17
+ ):
18
+ """
19
+ Initialize the base HTTP adapter.
20
+
21
+ Args:
22
+ pool_connections: Number of connection pools to cache
23
+ pool_maxsize: Maximum number of connections to save in the pool
24
+ max_retries: Retry configuration for failed requests
25
+ """
26
+ if max_retries is None:
27
+ max_retries = Retry(
28
+ total=3,
29
+ backoff_factor=0.1,
30
+ status_forcelist=[500, 502, 503, 504],
31
+ )
32
+
33
+ super().__init__(pool_connections=pool_connections, pool_maxsize=pool_maxsize, max_retries=max_retries)
34
+
35
+
36
+ # class AuthenticatedHttpAdapter(BaseHTTPAdapter):
37
+ # """HTTP adapter with automatic JWT authentication and refresh"""
38
+ #
39
+ # def __init__(
40
+ # self,
41
+ # auth_manager: AuthManager,
42
+ # api_key: str,
43
+ # token_fetcher: Callable[[str], Union[str, AuthTokenResponse]],
44
+ # pool_connections: int = 15,
45
+ # pool_maxsize: int = 256,
46
+ # max_retries: Optional[Retry] = None,
47
+ # ):
48
+ # """
49
+ # Initialize the authenticated HTTP adapter.
50
+ #
51
+ # Args:
52
+ # auth_manager: The authentication manager to use
53
+ # api_key: The API key to authenticate with
54
+ # token_fetcher: Function to fetch a new token if needed
55
+ # pool_connections: Number of connection pools to cache
56
+ # pool_maxsize: Maximum number of connections to save in the pool
57
+ # max_retries: Retry configuration for failed requests
58
+ # """
59
+ # self.auth_manager = auth_manager
60
+ # self.api_key = api_key
61
+ # self.token_fetcher = token_fetcher
62
+ #
63
+ # super().__init__(
64
+ # pool_connections=pool_connections,
65
+ # pool_maxsize=pool_maxsize,
66
+ # max_retries=max_retries
67
+ # )
68
+ #
69
+ # def add_headers(self, request, **kwargs):
70
+ # """Add authentication headers to the request"""
71
+ # # Get fresh auth headers from the auth manager
72
+ # self.auth_manager.maybe_fetch(self.api_key, self.token_fetcher)
73
+ # auth_headers = self.auth_manager.prepare_auth_headers(self.api_key)
74
+ #
75
+ # # Update request headers
76
+ # for key, value in auth_headers.items():
77
+ # request.headers[key] = value
78
+ #
79
+ # return request
80
+ #
81
+ # def send(self, request, **kwargs):
82
+ # """Send the request with authentication retry logic"""
83
+ # # Ensure allow_redirects is set to False
84
+ # kwargs["allow_redirects"] = False
85
+ #
86
+ # # Add auth headers to initial request
87
+ # request = self.add_headers(request, **kwargs)
88
+ #
89
+ # # Make the initial request
90
+ # response = super().send(request, **kwargs)
91
+ #
92
+ # # If we get a 401/403, check if it's due to token expiration
93
+ # if self.auth_manager.is_token_expired_response(response):
94
+ # logger.debug("Token expired, attempting to refresh")
95
+ # try:
96
+ # # Force token refresh
97
+ # self.auth_manager.clear_token()
98
+ # self.auth_manager.maybe_fetch(self.api_key, self.token_fetcher)
99
+ #
100
+ # # Update request with new token
101
+ # request = self.add_headers(request, **kwargs)
102
+ #
103
+ # # Retry the request
104
+ # logger.debug("Retrying request with new token")
105
+ # response = super().send(request, **kwargs)
106
+ # except AgentOpsApiJwtExpiredException as e:
107
+ # # Authentication failed
108
+ # logger.warning(f"Failed to refresh authentication token: {e}")
109
+ # except ApiServerException as e:
110
+ # # Server error during token refresh
111
+ # logger.error(f"Server error during token refresh: {e}")
112
+ # except Exception as e:
113
+ # # Unexpected error during token refresh
114
+ # logger.error(f"Unexpected error during token refresh: {e}")
115
+ #
116
+ # return response
@@ -0,0 +1,215 @@
1
+ from typing import Dict, Optional
2
+
3
+ import requests
4
+
5
+ from agentops.client.http.http_adapter import BaseHTTPAdapter
6
+ from agentops.logging import logger
7
+ from agentops.helpers.version import get_agentops_version
8
+
9
+
10
+ class HttpClient:
11
+ """Base HTTP client with connection pooling and session management"""
12
+
13
+ _session: Optional[requests.Session] = None
14
+ _project_id: Optional[str] = None
15
+
16
+ @classmethod
17
+ def get_project_id(cls) -> Optional[str]:
18
+ """Get the stored project ID"""
19
+ return cls._project_id
20
+
21
+ @classmethod
22
+ def get_session(cls) -> requests.Session:
23
+ """Get or create the global session with optimized connection pooling"""
24
+ if cls._session is None:
25
+ cls._session = requests.Session()
26
+
27
+ # Configure connection pooling
28
+ adapter = BaseHTTPAdapter()
29
+
30
+ # Mount adapter for both HTTP and HTTPS
31
+ cls._session.mount("http://", adapter)
32
+ cls._session.mount("https://", adapter)
33
+
34
+ # Set default headers
35
+ cls._session.headers.update(
36
+ {
37
+ "Connection": "keep-alive",
38
+ "Keep-Alive": "timeout=10, max=1000",
39
+ "Content-Type": "application/json",
40
+ "User-Agent": f"agentops-python/{get_agentops_version() or 'unknown'}",
41
+ }
42
+ )
43
+ logger.debug(f"Agentops version: agentops-python/{get_agentops_version() or 'unknown'}")
44
+ return cls._session
45
+
46
+ # @classmethod
47
+ # def get_authenticated_session(
48
+ # cls,
49
+ # endpoint: str,
50
+ # api_key: str,
51
+ # token_fetcher: Optional[Callable[[str], str]] = None,
52
+ # ) -> requests.Session:
53
+ # """
54
+ # Create a new session with authentication handling.
55
+ #
56
+ # Args:
57
+ # endpoint: Base API endpoint (used to derive auth endpoint if needed)
58
+ # api_key: The API key to use for authentication
59
+ # token_fetcher: Optional custom token fetcher function
60
+ #
61
+ # Returns:
62
+ # A requests.Session with authentication handling
63
+ # """
64
+ # # Create auth manager with default token endpoint
65
+ # auth_endpoint = f"{endpoint}/auth/token"
66
+ # auth_manager = AuthManager(auth_endpoint)
67
+ #
68
+ # # Use provided token fetcher or create a default one
69
+ # if token_fetcher is None:
70
+ # def default_token_fetcher(key: str) -> str:
71
+ # # Simple token fetching implementation
72
+ # try:
73
+ # response = requests.post(
74
+ # auth_manager.token_endpoint,
75
+ # json={"api_key": key},
76
+ # headers={"Content-Type": "application/json"},
77
+ # timeout=30
78
+ # )
79
+ #
80
+ # if response.status_code == 401 or response.status_code == 403:
81
+ # error_msg = "Invalid API key or unauthorized access"
82
+ # try:
83
+ # error_data = response.json()
84
+ # if "error" in error_data:
85
+ # error_msg = error_data["error"]
86
+ # except Exception:
87
+ # if response.text:
88
+ # error_msg = response.text
89
+ #
90
+ # logger.error(f"Authentication failed: {error_msg}")
91
+ # raise AgentOpsApiJwtExpiredException(f"Authentication failed: {error_msg}")
92
+ #
93
+ # if response.status_code >= 500:
94
+ # logger.error(f"Server error during authentication: {response.status_code}")
95
+ # raise ApiServerException(f"Server error during authentication: {response.status_code}")
96
+ #
97
+ # if response.status_code != 200:
98
+ # logger.error(f"Unexpected status code during authentication: {response.status_code}")
99
+ # raise AgentOpsApiJwtExpiredException(f"Failed to fetch token: {response.status_code}")
100
+ #
101
+ # token_data = response.json()
102
+ # if "token" not in token_data:
103
+ # logger.error("Token not found in response")
104
+ # raise AgentOpsApiJwtExpiredException("Token not found in response")
105
+ #
106
+ # # Store project_id if present in the response
107
+ # if "project_id" in token_data:
108
+ # HttpClient._project_id = token_data["project_id"]
109
+ # logger.debug(f"Project ID stored: {HttpClient._project_id} (will be set as {ResourceAttributes.PROJECT_ID})")
110
+ #
111
+ # return token_data["token"]
112
+ # except requests.RequestException as e:
113
+ # logger.error(f"Network error during authentication: {e}")
114
+ # raise AgentOpsApiJwtExpiredException(f"Network error during authentication: {e}")
115
+ #
116
+ # token_fetcher = default_token_fetcher
117
+ #
118
+ # # Create a new session
119
+ # session = requests.Session()
120
+ #
121
+ # # Create an authenticated adapter
122
+ # adapter = AuthenticatedHttpAdapter(
123
+ # auth_manager=auth_manager,
124
+ # api_key=api_key,
125
+ # token_fetcher=token_fetcher
126
+ # )
127
+ #
128
+ # # Mount the adapter for both HTTP and HTTPS
129
+ # session.mount("http://", adapter)
130
+ # session.mount("https://", adapter)
131
+ #
132
+ # # Set default headers
133
+ # session.headers.update({
134
+ # "Connection": "keep-alive",
135
+ # "Keep-Alive": "timeout=10, max=1000",
136
+ # "Content-Type": "application/json",
137
+ # })
138
+ #
139
+ # return session
140
+
141
+ @classmethod
142
+ def request(
143
+ cls,
144
+ method: str,
145
+ url: str,
146
+ data: Optional[Dict] = None,
147
+ headers: Optional[Dict] = None,
148
+ timeout: int = 30,
149
+ max_redirects: int = 5,
150
+ ) -> requests.Response:
151
+ """
152
+ Make a generic HTTP request
153
+
154
+ Args:
155
+ method: HTTP method (e.g., 'get', 'post', 'put', 'delete')
156
+ url: Full URL for the request
157
+ data: Request payload (for POST, PUT methods)
158
+ headers: Request headers
159
+ timeout: Request timeout in seconds
160
+ max_redirects: Maximum number of redirects to follow (default: 5)
161
+
162
+ Returns:
163
+ Response from the API
164
+
165
+ Raises:
166
+ requests.RequestException: If the request fails
167
+ ValueError: If the redirect limit is exceeded or an unsupported HTTP method is used
168
+ """
169
+ session = cls.get_session()
170
+ method = method.lower()
171
+ redirect_count = 0
172
+
173
+ while redirect_count <= max_redirects:
174
+ # Make the request with allow_redirects=False
175
+ if method == "get":
176
+ response = session.get(url, headers=headers, timeout=timeout, allow_redirects=False)
177
+ elif method == "post":
178
+ response = session.post(url, json=data, headers=headers, timeout=timeout, allow_redirects=False)
179
+ elif method == "put":
180
+ response = session.put(url, json=data, headers=headers, timeout=timeout, allow_redirects=False)
181
+ elif method == "delete":
182
+ response = session.delete(url, headers=headers, timeout=timeout, allow_redirects=False)
183
+ else:
184
+ raise ValueError(f"Unsupported HTTP method: {method}")
185
+
186
+ # Check if we got a redirect response
187
+ if response.status_code in (301, 302, 303, 307, 308):
188
+ redirect_count += 1
189
+
190
+ if redirect_count > max_redirects:
191
+ raise ValueError(f"Exceeded maximum number of redirects ({max_redirects})")
192
+
193
+ # Get the new location
194
+ if "location" not in response.headers:
195
+ # No location header, can't redirect
196
+ return response
197
+
198
+ # Update URL to the redirect location
199
+ url = response.headers["location"]
200
+
201
+ # For 303 redirects, always use GET for the next request
202
+ if response.status_code == 303:
203
+ method = "get"
204
+ data = None
205
+
206
+ logger.debug(f"Following redirect ({redirect_count}/{max_redirects}) to: {url}")
207
+
208
+ # Continue the loop to make the next request
209
+ continue
210
+
211
+ # Not a redirect, return the response
212
+ return response
213
+
214
+ # This should never be reached due to the max_redirects check above
215
+ return response
agentops/config.py ADDED
@@ -0,0 +1,268 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ import sys
5
+ from dataclasses import dataclass, field
6
+ from typing import List, Optional, Set, TypedDict, Union
7
+ from uuid import UUID
8
+
9
+ from opentelemetry.sdk.trace import SpanProcessor
10
+ from opentelemetry.sdk.trace.export import SpanExporter
11
+
12
+ from agentops.exceptions import InvalidApiKeyException
13
+ from agentops.helpers.env import get_env_bool, get_env_int, get_env_list
14
+ from agentops.helpers.serialization import AgentOpsJSONEncoder
15
+
16
+
17
+ class ConfigDict(TypedDict):
18
+ api_key: Optional[str]
19
+ endpoint: Optional[str]
20
+ app_url: Optional[str]
21
+ max_wait_time: Optional[int]
22
+ export_flush_interval: Optional[int]
23
+ max_queue_size: Optional[int]
24
+ default_tags: Optional[List[str]]
25
+ trace_name: Optional[str]
26
+ instrument_llm_calls: Optional[bool]
27
+ auto_start_session: Optional[bool]
28
+ auto_init: Optional[bool]
29
+ skip_auto_end_session: Optional[bool]
30
+ env_data_opt_out: Optional[bool]
31
+ log_level: Optional[Union[str, int]]
32
+ fail_safe: Optional[bool]
33
+ prefetch_jwt_token: Optional[bool]
34
+ log_session_replay_url: Optional[bool]
35
+
36
+
37
+ @dataclass
38
+ class Config:
39
+ api_key: Optional[str] = field(
40
+ default_factory=lambda: os.getenv("AGENTOPS_API_KEY"),
41
+ metadata={"description": "API key for authentication with AgentOps services"},
42
+ )
43
+
44
+ endpoint: str = field(
45
+ default_factory=lambda: os.getenv("AGENTOPS_API_ENDPOINT", "https://api.agentops.ai"),
46
+ metadata={"description": "Base URL for the AgentOps API"},
47
+ )
48
+
49
+ app_url: str = field(
50
+ default_factory=lambda: os.getenv("AGENTOPS_APP_URL", "https://app.agentops.ai"),
51
+ metadata={"description": "Dashboard URL for the AgentOps application"},
52
+ )
53
+
54
+ max_wait_time: int = field(
55
+ default_factory=lambda: get_env_int("AGENTOPS_MAX_WAIT_TIME", 5000),
56
+ metadata={"description": "Maximum time in milliseconds to wait for API responses"},
57
+ )
58
+
59
+ export_flush_interval: int = field(
60
+ default_factory=lambda: get_env_int("AGENTOPS_EXPORT_FLUSH_INTERVAL", 1000),
61
+ metadata={"description": "Time interval in milliseconds between automatic exports of telemetry data"},
62
+ )
63
+
64
+ max_queue_size: int = field(
65
+ default_factory=lambda: get_env_int("AGENTOPS_MAX_QUEUE_SIZE", 512),
66
+ metadata={"description": "Maximum number of events to queue before forcing a flush"},
67
+ )
68
+
69
+ default_tags: Set[str] = field(
70
+ default_factory=lambda: get_env_list("AGENTOPS_DEFAULT_TAGS"),
71
+ metadata={"description": "Default tags to apply to all sessions"},
72
+ )
73
+
74
+ trace_name: Optional[str] = field(
75
+ default_factory=lambda: os.getenv("AGENTOPS_TRACE_NAME"),
76
+ metadata={"description": "Default name for the trace/session"},
77
+ )
78
+
79
+ instrument_llm_calls: bool = field(
80
+ default_factory=lambda: get_env_bool("AGENTOPS_INSTRUMENT_LLM_CALLS", True),
81
+ metadata={"description": "Whether to automatically instrument and track LLM API calls"},
82
+ )
83
+
84
+ auto_start_session: bool = field(
85
+ default_factory=lambda: get_env_bool("AGENTOPS_AUTO_START_SESSION", True),
86
+ metadata={"description": "Whether to automatically start a session when initializing"},
87
+ )
88
+
89
+ auto_init: bool = field(
90
+ default_factory=lambda: get_env_bool("AGENTOPS_AUTO_INIT", True),
91
+ metadata={"description": "Whether to automatically initialize the client on import"},
92
+ )
93
+
94
+ skip_auto_end_session: bool = field(
95
+ default_factory=lambda: get_env_bool("AGENTOPS_SKIP_AUTO_END_SESSION", False),
96
+ metadata={"description": "Whether to skip automatically ending sessions on program exit"},
97
+ )
98
+
99
+ env_data_opt_out: bool = field(
100
+ default_factory=lambda: get_env_bool("AGENTOPS_ENV_DATA_OPT_OUT", False),
101
+ metadata={"description": "Whether to opt out of collecting environment data"},
102
+ )
103
+
104
+ log_level: Union[str, int] = field(
105
+ default_factory=lambda: os.getenv("AGENTOPS_LOG_LEVEL", "INFO"),
106
+ metadata={"description": "Logging level for AgentOps logs"},
107
+ )
108
+
109
+ fail_safe: bool = field(
110
+ default_factory=lambda: get_env_bool("AGENTOPS_FAIL_SAFE", False),
111
+ metadata={"description": "Whether to suppress errors and continue execution when possible"},
112
+ )
113
+
114
+ prefetch_jwt_token: bool = field(
115
+ default_factory=lambda: get_env_bool("AGENTOPS_PREFETCH_JWT_TOKEN", True),
116
+ metadata={"description": "Whether to prefetch JWT token during initialization"},
117
+ )
118
+
119
+ log_session_replay_url: bool = field(
120
+ default_factory=lambda: get_env_bool("AGENTOPS_LOG_SESSION_REPLAY_URL", True),
121
+ metadata={"description": "Whether to log session replay URLs to the console"},
122
+ )
123
+
124
+ exporter_endpoint: Optional[str] = field(
125
+ default_factory=lambda: os.getenv("AGENTOPS_EXPORTER_ENDPOINT", "https://otlp.agentops.ai/v1/traces"),
126
+ metadata={
127
+ "description": "Endpoint for the span exporter. When not provided, the default AgentOps endpoint will be used."
128
+ },
129
+ )
130
+
131
+ exporter: Optional[SpanExporter] = field(
132
+ default_factory=lambda: None, metadata={"description": "Custom span exporter for OpenTelemetry trace data"}
133
+ )
134
+
135
+ processor: Optional[SpanProcessor] = field(
136
+ default_factory=lambda: None, metadata={"description": "Custom span processor for OpenTelemetry trace data"}
137
+ )
138
+
139
+ def configure(
140
+ self,
141
+ api_key: Optional[str] = None,
142
+ endpoint: Optional[str] = None,
143
+ app_url: Optional[str] = None,
144
+ max_wait_time: Optional[int] = None,
145
+ export_flush_interval: Optional[int] = None,
146
+ max_queue_size: Optional[int] = None,
147
+ default_tags: Optional[List[str]] = None,
148
+ trace_name: Optional[str] = None,
149
+ instrument_llm_calls: Optional[bool] = None,
150
+ auto_start_session: Optional[bool] = None,
151
+ auto_init: Optional[bool] = None,
152
+ skip_auto_end_session: Optional[bool] = None,
153
+ env_data_opt_out: Optional[bool] = None,
154
+ log_level: Optional[Union[str, int]] = None,
155
+ fail_safe: Optional[bool] = None,
156
+ prefetch_jwt_token: Optional[bool] = None,
157
+ log_session_replay_url: Optional[bool] = None,
158
+ exporter: Optional[SpanExporter] = None,
159
+ processor: Optional[SpanProcessor] = None,
160
+ exporter_endpoint: Optional[str] = None,
161
+ ):
162
+ """Configure settings from kwargs, validating where necessary"""
163
+ if api_key is not None:
164
+ self.api_key = api_key
165
+ if not TESTING: # Allow setting dummy keys in tests
166
+ try:
167
+ UUID(api_key)
168
+ except ValueError:
169
+ raise InvalidApiKeyException(api_key, self.endpoint)
170
+
171
+ if endpoint is not None:
172
+ self.endpoint = endpoint
173
+
174
+ if app_url is not None:
175
+ self.app_url = app_url
176
+
177
+ if max_wait_time is not None:
178
+ self.max_wait_time = max_wait_time
179
+
180
+ if export_flush_interval is not None:
181
+ self.export_flush_interval = export_flush_interval
182
+
183
+ if max_queue_size is not None:
184
+ self.max_queue_size = max_queue_size
185
+
186
+ if default_tags is not None:
187
+ self.default_tags = set(default_tags)
188
+
189
+ if trace_name is not None:
190
+ self.trace_name = trace_name
191
+
192
+ if instrument_llm_calls is not None:
193
+ self.instrument_llm_calls = instrument_llm_calls
194
+
195
+ if auto_start_session is not None:
196
+ self.auto_start_session = auto_start_session
197
+
198
+ if auto_init is not None:
199
+ self.auto_init = auto_init
200
+
201
+ if skip_auto_end_session is not None:
202
+ self.skip_auto_end_session = skip_auto_end_session
203
+
204
+ if env_data_opt_out is not None:
205
+ self.env_data_opt_out = env_data_opt_out
206
+
207
+ if log_level is not None:
208
+ if isinstance(log_level, str):
209
+ log_level_str = log_level.upper()
210
+ if hasattr(logging, log_level_str):
211
+ self.log_level = getattr(logging, log_level_str)
212
+ else:
213
+ self.log_level = logging.INFO
214
+ else:
215
+ self.log_level = log_level
216
+
217
+ if fail_safe is not None:
218
+ self.fail_safe = fail_safe
219
+
220
+ if prefetch_jwt_token is not None:
221
+ self.prefetch_jwt_token = prefetch_jwt_token
222
+
223
+ if log_session_replay_url is not None:
224
+ self.log_session_replay_url = log_session_replay_url
225
+
226
+ if exporter is not None:
227
+ self.exporter = exporter
228
+
229
+ if processor is not None:
230
+ self.processor = processor
231
+
232
+ if exporter_endpoint is not None:
233
+ self.exporter_endpoint = exporter_endpoint
234
+ # else:
235
+ # self.exporter_endpoint = self.endpoint
236
+
237
+ def dict(self):
238
+ """Return a dictionary representation of the config"""
239
+ return {
240
+ "api_key": self.api_key,
241
+ "endpoint": self.endpoint,
242
+ "app_url": self.app_url,
243
+ "max_wait_time": self.max_wait_time,
244
+ "export_flush_interval": self.export_flush_interval,
245
+ "max_queue_size": self.max_queue_size,
246
+ "default_tags": self.default_tags,
247
+ "trace_name": self.trace_name,
248
+ "instrument_llm_calls": self.instrument_llm_calls,
249
+ "auto_start_session": self.auto_start_session,
250
+ "auto_init": self.auto_init,
251
+ "skip_auto_end_session": self.skip_auto_end_session,
252
+ "env_data_opt_out": self.env_data_opt_out,
253
+ "log_level": self.log_level,
254
+ "fail_safe": self.fail_safe,
255
+ "prefetch_jwt_token": self.prefetch_jwt_token,
256
+ "log_session_replay_url": self.log_session_replay_url,
257
+ "exporter": self.exporter,
258
+ "processor": self.processor,
259
+ "exporter_endpoint": self.exporter_endpoint,
260
+ }
261
+
262
+ def json(self):
263
+ """Return a JSON representation of the config"""
264
+ return json.dumps(self.dict(), cls=AgentOpsJSONEncoder)
265
+
266
+
267
+ # checks if pytest is imported
268
+ TESTING = "pytest" in sys.modules
agentops/enums.py ADDED
@@ -0,0 +1,36 @@
1
+ """
2
+ AgentOps enums for user-friendly API.
3
+
4
+ This module provides simple enums that users can import from agentops
5
+ without needing to know about OpenTelemetry internals.
6
+ """
7
+
8
+ from enum import Enum
9
+ from opentelemetry.trace.status import StatusCode
10
+
11
+
12
+ class TraceState(Enum):
13
+ """
14
+ Enum for trace end states.
15
+
16
+ This provides a user-friendly interface that maps to OpenTelemetry StatusCode internally.
17
+ Users can simply use agentops.TraceState.SUCCESS instead of importing OpenTelemetry.
18
+ """
19
+
20
+ SUCCESS = StatusCode.OK
21
+ ERROR = StatusCode.ERROR
22
+ UNSET = StatusCode.UNSET
23
+
24
+ def __str__(self) -> str:
25
+ """Return the name for string representation."""
26
+ return self.name
27
+
28
+ def to_status_code(self) -> StatusCode:
29
+ """Convert to OpenTelemetry StatusCode."""
30
+ return self.value
31
+
32
+
33
+ # For backward compatibility, also provide these as module-level constants
34
+ SUCCESS = TraceState.SUCCESS
35
+ ERROR = TraceState.ERROR
36
+ UNSET = TraceState.UNSET
agentops/exceptions.py ADDED
@@ -0,0 +1,38 @@
1
+ class MultiSessionException(Exception):
2
+ def __init__(self, message):
3
+ super().__init__(message)
4
+
5
+
6
+ class NoSessionException(Exception):
7
+ def __init__(self, message="No session found"):
8
+ super().__init__(message)
9
+
10
+
11
+ class NoApiKeyException(Exception):
12
+ def __init__(
13
+ self,
14
+ message="Could not initialize AgentOps client - API Key is missing."
15
+ + "\n\t Find your API key at https://app.agentops.ai/settings/projects",
16
+ ):
17
+ super().__init__(message)
18
+
19
+
20
+ class InvalidApiKeyException(Exception):
21
+ def __init__(self, api_key, endpoint):
22
+ message = f"API Key is invalid: {{{api_key}}}.\n\t Find your API key at {endpoint}/settings/projects"
23
+ super().__init__(message)
24
+
25
+
26
+ class ApiServerException(Exception):
27
+ def __init__(self, message):
28
+ super().__init__(message)
29
+
30
+
31
+ class AgentOpsClientNotInitializedException(RuntimeError):
32
+ def __init__(self, message="AgentOps client must be initialized before using this feature"):
33
+ super().__init__(message)
34
+
35
+
36
+ class AgentOpsApiJwtExpiredException(Exception):
37
+ def __init__(self, message="JWT token has expired"):
38
+ super().__init__(message)