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,162 @@
|
|
1
|
+
"""
|
2
|
+
Base API client classes for making HTTP requests.
|
3
|
+
|
4
|
+
This module provides the foundation for all API clients in the AgentOps SDK.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Any, Dict, Optional, Protocol
|
8
|
+
|
9
|
+
import requests
|
10
|
+
|
11
|
+
from agentops.client.http.http_client import HttpClient
|
12
|
+
from agentops.helpers.version import get_agentops_version
|
13
|
+
|
14
|
+
|
15
|
+
class TokenFetcher(Protocol):
|
16
|
+
"""Protocol for token fetching functions"""
|
17
|
+
|
18
|
+
def __call__(self, api_key: str) -> str:
|
19
|
+
...
|
20
|
+
|
21
|
+
|
22
|
+
class BaseApiClient:
|
23
|
+
"""
|
24
|
+
Base class for API communication with connection pooling.
|
25
|
+
|
26
|
+
This class provides the core HTTP functionality without authentication.
|
27
|
+
It should be used for APIs that don't require authentication.
|
28
|
+
"""
|
29
|
+
|
30
|
+
def __init__(self, endpoint: str):
|
31
|
+
"""
|
32
|
+
Initialize the base API client.
|
33
|
+
|
34
|
+
Args:
|
35
|
+
endpoint: The base URL for the API
|
36
|
+
"""
|
37
|
+
self.endpoint = endpoint
|
38
|
+
self.http_client = HttpClient()
|
39
|
+
self.last_response: Optional[requests.Response] = None
|
40
|
+
|
41
|
+
def prepare_headers(self, custom_headers: Optional[Dict[str, str]] = None) -> Dict[str, str]:
|
42
|
+
"""
|
43
|
+
Prepare headers for API requests.
|
44
|
+
|
45
|
+
Args:
|
46
|
+
custom_headers: Additional headers to include
|
47
|
+
|
48
|
+
Returns:
|
49
|
+
Headers dictionary with standard headers and any custom headers
|
50
|
+
"""
|
51
|
+
headers = {
|
52
|
+
"Content-Type": "application/json",
|
53
|
+
"Connection": "keep-alive",
|
54
|
+
"Keep-Alive": "timeout=10, max=1000",
|
55
|
+
"User-Agent": f"agentops-python/{get_agentops_version() or 'unknown'}",
|
56
|
+
}
|
57
|
+
|
58
|
+
if custom_headers:
|
59
|
+
headers.update(custom_headers)
|
60
|
+
|
61
|
+
return headers
|
62
|
+
|
63
|
+
def _get_full_url(self, path: str) -> str:
|
64
|
+
"""
|
65
|
+
Get the full URL for a path.
|
66
|
+
|
67
|
+
Args:
|
68
|
+
path: The API endpoint path
|
69
|
+
|
70
|
+
Returns:
|
71
|
+
The full URL
|
72
|
+
"""
|
73
|
+
return f"{self.endpoint}{path}"
|
74
|
+
|
75
|
+
def request(
|
76
|
+
self,
|
77
|
+
method: str,
|
78
|
+
path: str,
|
79
|
+
data: Optional[Dict[str, Any]] = None,
|
80
|
+
headers: Optional[Dict[str, str]] = None,
|
81
|
+
timeout: int = 30,
|
82
|
+
) -> requests.Response:
|
83
|
+
"""
|
84
|
+
Make a generic HTTP request
|
85
|
+
|
86
|
+
Args:
|
87
|
+
method: HTTP method (e.g., 'get', 'post', 'put', 'delete')
|
88
|
+
path: API endpoint path
|
89
|
+
data: Request payload (for POST, PUT methods)
|
90
|
+
headers: Request headers
|
91
|
+
timeout: Request timeout in seconds
|
92
|
+
|
93
|
+
Returns:
|
94
|
+
Response from the API
|
95
|
+
|
96
|
+
Raises:
|
97
|
+
Exception: If the request fails
|
98
|
+
"""
|
99
|
+
url = self._get_full_url(path)
|
100
|
+
|
101
|
+
try:
|
102
|
+
response = self.http_client.request(method=method, url=url, data=data, headers=headers, timeout=timeout)
|
103
|
+
|
104
|
+
self.last_response = response
|
105
|
+
return response
|
106
|
+
except requests.RequestException as e:
|
107
|
+
self.last_response = None
|
108
|
+
raise Exception(f"{method.upper()} request failed: {str(e)}") from e
|
109
|
+
|
110
|
+
def post(self, path: str, data: Dict[str, Any], headers: Dict[str, str]) -> requests.Response:
|
111
|
+
"""
|
112
|
+
Make POST request
|
113
|
+
|
114
|
+
Args:
|
115
|
+
path: API endpoint path
|
116
|
+
data: Request payload
|
117
|
+
headers: Request headers
|
118
|
+
|
119
|
+
Returns:
|
120
|
+
Response from the API
|
121
|
+
"""
|
122
|
+
return self.request("post", path, data=data, headers=headers)
|
123
|
+
|
124
|
+
def get(self, path: str, headers: Dict[str, str]) -> requests.Response:
|
125
|
+
"""
|
126
|
+
Make GET request
|
127
|
+
|
128
|
+
Args:
|
129
|
+
path: API endpoint path
|
130
|
+
headers: Request headers
|
131
|
+
|
132
|
+
Returns:
|
133
|
+
Response from the API
|
134
|
+
"""
|
135
|
+
return self.request("get", path, headers=headers)
|
136
|
+
|
137
|
+
def put(self, path: str, data: Dict[str, Any], headers: Dict[str, str]) -> requests.Response:
|
138
|
+
"""
|
139
|
+
Make PUT request
|
140
|
+
|
141
|
+
Args:
|
142
|
+
path: API endpoint path
|
143
|
+
data: Request payload
|
144
|
+
headers: Request headers
|
145
|
+
|
146
|
+
Returns:
|
147
|
+
Response from the API
|
148
|
+
"""
|
149
|
+
return self.request("put", path, data=data, headers=headers)
|
150
|
+
|
151
|
+
def delete(self, path: str, headers: Dict[str, str]) -> requests.Response:
|
152
|
+
"""
|
153
|
+
Make DELETE request
|
154
|
+
|
155
|
+
Args:
|
156
|
+
path: API endpoint path
|
157
|
+
headers: Request headers
|
158
|
+
|
159
|
+
Returns:
|
160
|
+
Response from the API
|
161
|
+
"""
|
162
|
+
return self.request("delete", path, headers=headers)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
"""
|
2
|
+
Common types used across API client modules.
|
3
|
+
|
4
|
+
This module contains type definitions used by multiple API client modules.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import TypedDict
|
8
|
+
|
9
|
+
|
10
|
+
class AuthTokenResponse(TypedDict):
|
11
|
+
"""Response from the auth/token endpoint"""
|
12
|
+
|
13
|
+
token: str
|
14
|
+
project_id: str
|
15
|
+
|
16
|
+
|
17
|
+
class UploadedObjectResponse(TypedDict):
|
18
|
+
"""Response from the v4/objects/upload endpoint"""
|
19
|
+
|
20
|
+
url: str
|
21
|
+
size: int
|
@@ -0,0 +1,10 @@
|
|
1
|
+
"""
|
2
|
+
API client versions package.
|
3
|
+
|
4
|
+
This package contains client implementations for different API versions.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from agentops.client.api.versions.v3 import V3Client
|
8
|
+
from agentops.client.api.versions.v4 import V4Client
|
9
|
+
|
10
|
+
__all__ = ["V3Client", "V4Client"]
|
@@ -0,0 +1,65 @@
|
|
1
|
+
"""
|
2
|
+
V3 API client for the AgentOps API.
|
3
|
+
|
4
|
+
This module provides the client for the V3 version of the AgentOps API.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from agentops.client.api.base import BaseApiClient
|
8
|
+
from agentops.client.api.types import AuthTokenResponse
|
9
|
+
from agentops.exceptions import ApiServerException
|
10
|
+
from agentops.logging import logger
|
11
|
+
from termcolor import colored
|
12
|
+
|
13
|
+
|
14
|
+
class V3Client(BaseApiClient):
|
15
|
+
"""Client for the AgentOps V3 API"""
|
16
|
+
|
17
|
+
def __init__(self, endpoint: str):
|
18
|
+
"""
|
19
|
+
Initialize the V3 API client.
|
20
|
+
|
21
|
+
Args:
|
22
|
+
endpoint: The base URL for the API
|
23
|
+
"""
|
24
|
+
# Set up with V3-specific auth endpoint
|
25
|
+
super().__init__(endpoint)
|
26
|
+
|
27
|
+
def fetch_auth_token(self, api_key: str) -> AuthTokenResponse:
|
28
|
+
path = "/v3/auth/token"
|
29
|
+
data = {"api_key": api_key}
|
30
|
+
headers = self.prepare_headers()
|
31
|
+
|
32
|
+
r = self.post(path, data, headers)
|
33
|
+
|
34
|
+
if r.status_code != 200:
|
35
|
+
error_msg = f"Authentication failed: {r.status_code}"
|
36
|
+
try:
|
37
|
+
error_data = r.json()
|
38
|
+
if "error" in error_data:
|
39
|
+
error_msg = f"{error_data['error']}"
|
40
|
+
except Exception:
|
41
|
+
pass
|
42
|
+
logger.error(f"{error_msg} - Perhaps an invalid API key?")
|
43
|
+
raise ApiServerException(error_msg)
|
44
|
+
|
45
|
+
try:
|
46
|
+
jr = r.json()
|
47
|
+
token = jr.get("token")
|
48
|
+
if not token:
|
49
|
+
raise ApiServerException("No token in authentication response")
|
50
|
+
|
51
|
+
# Check project premium status
|
52
|
+
if jr.get("project_prem_status") != "pro":
|
53
|
+
logger.info(
|
54
|
+
colored(
|
55
|
+
"\x1b[34mYou're on the agentops free plan 🤔\x1b[0m",
|
56
|
+
"blue",
|
57
|
+
)
|
58
|
+
)
|
59
|
+
|
60
|
+
return jr
|
61
|
+
except Exception as e:
|
62
|
+
logger.error(f"Failed to process authentication response: {str(e)}")
|
63
|
+
raise ApiServerException(f"Failed to process authentication response: {str(e)}")
|
64
|
+
|
65
|
+
# Add V3-specific API methods here
|
@@ -0,0 +1,104 @@
|
|
1
|
+
"""
|
2
|
+
V4 API client for the AgentOps API.
|
3
|
+
|
4
|
+
This module provides the client for the V4 version of the AgentOps API.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Optional, Union, Dict
|
8
|
+
|
9
|
+
from agentops.client.api.base import BaseApiClient
|
10
|
+
from agentops.exceptions import ApiServerException
|
11
|
+
from agentops.client.api.types import UploadedObjectResponse
|
12
|
+
from agentops.helpers.version import get_agentops_version
|
13
|
+
|
14
|
+
|
15
|
+
class V4Client(BaseApiClient):
|
16
|
+
"""Client for the AgentOps V4 API"""
|
17
|
+
|
18
|
+
auth_token: str
|
19
|
+
|
20
|
+
def set_auth_token(self, token: str):
|
21
|
+
"""
|
22
|
+
Set the authentication token for API requests.
|
23
|
+
|
24
|
+
Args:
|
25
|
+
token: The authentication token to set
|
26
|
+
"""
|
27
|
+
self.auth_token = token
|
28
|
+
|
29
|
+
def prepare_headers(self, custom_headers: Optional[Dict[str, str]] = None) -> Dict[str, str]:
|
30
|
+
"""
|
31
|
+
Prepare headers for API requests.
|
32
|
+
|
33
|
+
Args:
|
34
|
+
custom_headers: Additional headers to include
|
35
|
+
Returns:
|
36
|
+
Headers dictionary with standard headers and any custom headers
|
37
|
+
"""
|
38
|
+
headers = {
|
39
|
+
"Authorization": f"Bearer {self.auth_token}",
|
40
|
+
"User-Agent": f"agentops-python/{get_agentops_version() or 'unknown'}",
|
41
|
+
}
|
42
|
+
if custom_headers:
|
43
|
+
headers.update(custom_headers)
|
44
|
+
return headers
|
45
|
+
|
46
|
+
def upload_object(self, body: Union[str, bytes]) -> UploadedObjectResponse:
|
47
|
+
"""
|
48
|
+
Upload an object to the API and return the response.
|
49
|
+
|
50
|
+
Args:
|
51
|
+
body: The object to upload, either as a string or bytes.
|
52
|
+
Returns:
|
53
|
+
UploadedObjectResponse: The response from the API after upload.
|
54
|
+
"""
|
55
|
+
if isinstance(body, bytes):
|
56
|
+
body = body.decode("utf-8")
|
57
|
+
|
58
|
+
response = self.post("/v4/objects/upload/", body, self.prepare_headers())
|
59
|
+
|
60
|
+
if response.status_code != 200:
|
61
|
+
error_msg = f"Upload failed: {response.status_code}"
|
62
|
+
try:
|
63
|
+
error_data = response.json()
|
64
|
+
if "error" in error_data:
|
65
|
+
error_msg = error_data["error"]
|
66
|
+
except Exception:
|
67
|
+
pass
|
68
|
+
raise ApiServerException(error_msg)
|
69
|
+
|
70
|
+
try:
|
71
|
+
response_data = response.json()
|
72
|
+
return UploadedObjectResponse(**response_data)
|
73
|
+
except Exception as e:
|
74
|
+
raise ApiServerException(f"Failed to process upload response: {str(e)}")
|
75
|
+
|
76
|
+
def upload_logfile(self, body: Union[str, bytes], trace_id: int) -> UploadedObjectResponse:
|
77
|
+
"""
|
78
|
+
Upload an log file to the API and return the response.
|
79
|
+
|
80
|
+
Args:
|
81
|
+
body: The log file to upload, either as a string or bytes.
|
82
|
+
Returns:
|
83
|
+
UploadedObjectResponse: The response from the API after upload.
|
84
|
+
"""
|
85
|
+
if isinstance(body, bytes):
|
86
|
+
body = body.decode("utf-8")
|
87
|
+
|
88
|
+
response = self.post("/v4/logs/upload/", body, {**self.prepare_headers(), "Trace-Id": str(trace_id)})
|
89
|
+
|
90
|
+
if response.status_code != 200:
|
91
|
+
error_msg = f"Upload failed: {response.status_code}"
|
92
|
+
try:
|
93
|
+
error_data = response.json()
|
94
|
+
if "error" in error_data:
|
95
|
+
error_msg = error_data["error"]
|
96
|
+
except Exception:
|
97
|
+
pass
|
98
|
+
raise ApiServerException(error_msg)
|
99
|
+
|
100
|
+
try:
|
101
|
+
response_data = response.json()
|
102
|
+
return UploadedObjectResponse(**response_data)
|
103
|
+
except Exception as e:
|
104
|
+
raise ApiServerException(f"Failed to process upload response: {str(e)}")
|
@@ -0,0 +1,211 @@
|
|
1
|
+
import atexit
|
2
|
+
from typing import Optional, Any
|
3
|
+
|
4
|
+
from agentops.client.api import ApiClient
|
5
|
+
from agentops.config import Config
|
6
|
+
from agentops.exceptions import NoApiKeyException
|
7
|
+
from agentops.instrumentation import instrument_all
|
8
|
+
from agentops.logging import logger
|
9
|
+
from agentops.logging.config import configure_logging, intercept_opentelemetry_logging
|
10
|
+
from agentops.sdk.core import TraceContext, tracer
|
11
|
+
from agentops.legacy import Session
|
12
|
+
|
13
|
+
# Global variables to hold the client's auto-started trace and its legacy session wrapper
|
14
|
+
_client_init_trace_context: Optional[TraceContext] = None
|
15
|
+
_client_legacy_session_for_init_trace: Optional[Session] = None
|
16
|
+
|
17
|
+
# Single atexit handler registered flag
|
18
|
+
_atexit_registered = False
|
19
|
+
|
20
|
+
|
21
|
+
def _end_init_trace_atexit():
|
22
|
+
"""Global atexit handler to end the client's auto-initialized trace during shutdown."""
|
23
|
+
global _client_init_trace_context, _client_legacy_session_for_init_trace
|
24
|
+
if _client_init_trace_context is not None:
|
25
|
+
logger.debug("Auto-ending client's init trace during shutdown.")
|
26
|
+
try:
|
27
|
+
# Use global tracer to end the trace directly
|
28
|
+
if tracer.initialized and _client_init_trace_context.span.is_recording():
|
29
|
+
tracer.end_trace(_client_init_trace_context, end_state="Shutdown")
|
30
|
+
except Exception as e:
|
31
|
+
logger.warning(f"Error ending client's init trace during shutdown: {e}")
|
32
|
+
finally:
|
33
|
+
_client_init_trace_context = None
|
34
|
+
_client_legacy_session_for_init_trace = None # Clear its legacy wrapper too
|
35
|
+
|
36
|
+
|
37
|
+
class Client:
|
38
|
+
"""Singleton client for AgentOps service"""
|
39
|
+
|
40
|
+
config: Config
|
41
|
+
_initialized: bool
|
42
|
+
_init_trace_context: Optional[TraceContext] = None # Stores the context of the auto-started trace
|
43
|
+
_legacy_session_for_init_trace: Optional[
|
44
|
+
Session
|
45
|
+
] = None # Stores the legacy Session wrapper for the auto-started trace
|
46
|
+
|
47
|
+
__instance = None # Class variable for singleton pattern
|
48
|
+
|
49
|
+
api: ApiClient
|
50
|
+
|
51
|
+
def __new__(cls, *args: Any, **kwargs: Any) -> "Client":
|
52
|
+
if cls.__instance is None:
|
53
|
+
cls.__instance = super(Client, cls).__new__(cls)
|
54
|
+
# Initialize instance variables that should only be set once per instance
|
55
|
+
cls.__instance._init_trace_context = None
|
56
|
+
cls.__instance._legacy_session_for_init_trace = None
|
57
|
+
return cls.__instance
|
58
|
+
|
59
|
+
def __init__(self):
|
60
|
+
# Initialization of attributes like config, _initialized should happen here if they are instance-specific
|
61
|
+
# and not shared via __new__ for a true singleton that can be re-configured.
|
62
|
+
# However, the current pattern re-initializes config in init().
|
63
|
+
if (
|
64
|
+
not hasattr(self, "_initialized") or not self._initialized
|
65
|
+
): # Ensure init logic runs only once per actual initialization intent
|
66
|
+
self.config = Config() # Initialize config here for the instance
|
67
|
+
self._initialized = False
|
68
|
+
# self._init_trace_context = None # Already done in __new__
|
69
|
+
# self._legacy_session_for_init_trace = None # Already done in __new__
|
70
|
+
|
71
|
+
def init(self, **kwargs: Any) -> None: # Return type updated to None
|
72
|
+
# Recreate the Config object to parse environment variables at the time of initialization
|
73
|
+
# This allows re-init with new env vars if needed, though true singletons usually init once.
|
74
|
+
self.config = Config()
|
75
|
+
self.configure(**kwargs)
|
76
|
+
|
77
|
+
# Only treat as re-initialization if a different non-None API key is explicitly provided
|
78
|
+
provided_api_key = kwargs.get("api_key")
|
79
|
+
if self.initialized and provided_api_key is not None and provided_api_key != self.config.api_key:
|
80
|
+
logger.warning("AgentOps Client being re-initialized with a different API key. This is unusual.")
|
81
|
+
# Reset initialization status to allow re-init with new key/config
|
82
|
+
self._initialized = False
|
83
|
+
if self._init_trace_context and self._init_trace_context.span.is_recording():
|
84
|
+
logger.warning("Ending previously auto-started trace due to re-initialization.")
|
85
|
+
tracer.end_trace(self._init_trace_context, "Reinitialized")
|
86
|
+
self._init_trace_context = None
|
87
|
+
self._legacy_session_for_init_trace = None
|
88
|
+
|
89
|
+
if self.initialized:
|
90
|
+
logger.debug("AgentOps Client already initialized.")
|
91
|
+
# If auto_start_session was true, return the existing legacy session wrapper
|
92
|
+
if self.config.auto_start_session:
|
93
|
+
return self._legacy_session_for_init_trace
|
94
|
+
return None # If not auto-starting, and already initialized, return None
|
95
|
+
|
96
|
+
if not self.config.api_key:
|
97
|
+
raise NoApiKeyException
|
98
|
+
|
99
|
+
configure_logging(self.config)
|
100
|
+
intercept_opentelemetry_logging()
|
101
|
+
|
102
|
+
self.api = ApiClient(self.config.endpoint)
|
103
|
+
|
104
|
+
try:
|
105
|
+
response = self.api.v3.fetch_auth_token(self.config.api_key)
|
106
|
+
if response is None:
|
107
|
+
# If auth fails, we cannot proceed with tracer initialization that depends on project_id
|
108
|
+
logger.error("Failed to fetch auth token. AgentOps SDK will not be initialized.")
|
109
|
+
return None # Explicitly return None if auth fails
|
110
|
+
except Exception as e:
|
111
|
+
# Re-raise authentication exceptions so they can be caught by tests and calling code
|
112
|
+
logger.error(f"Authentication failed: {e}")
|
113
|
+
raise
|
114
|
+
|
115
|
+
self.api.v4.set_auth_token(response["token"])
|
116
|
+
|
117
|
+
tracing_config = self.config.dict()
|
118
|
+
tracing_config["project_id"] = response["project_id"]
|
119
|
+
|
120
|
+
tracer.initialize_from_config(tracing_config, jwt=response["token"])
|
121
|
+
|
122
|
+
if self.config.instrument_llm_calls:
|
123
|
+
instrument_all()
|
124
|
+
|
125
|
+
# self._initialized = True # Set initialized to True here - MOVED to after trace start attempt
|
126
|
+
|
127
|
+
global _atexit_registered
|
128
|
+
if not _atexit_registered:
|
129
|
+
atexit.register(_end_init_trace_atexit) # Register new atexit handler
|
130
|
+
_atexit_registered = True
|
131
|
+
|
132
|
+
# Auto-start trace if configured
|
133
|
+
if self.config.auto_start_session:
|
134
|
+
if self._init_trace_context is None or not self._init_trace_context.span.is_recording():
|
135
|
+
logger.debug("Auto-starting init trace.")
|
136
|
+
trace_name = self.config.trace_name or "default"
|
137
|
+
self._init_trace_context = tracer.start_trace(
|
138
|
+
trace_name=trace_name,
|
139
|
+
tags=list(self.config.default_tags) if self.config.default_tags else None,
|
140
|
+
is_init_trace=True,
|
141
|
+
)
|
142
|
+
if self._init_trace_context:
|
143
|
+
self._legacy_session_for_init_trace = Session(self._init_trace_context)
|
144
|
+
|
145
|
+
# For backward compatibility, also update the global references in legacy and client modules
|
146
|
+
# These globals are what old code might have been using via agentops.legacy.get_session() or similar indirect access.
|
147
|
+
global _client_init_trace_context, _client_legacy_session_for_init_trace
|
148
|
+
_client_init_trace_context = self._init_trace_context
|
149
|
+
_client_legacy_session_for_init_trace = self._legacy_session_for_init_trace
|
150
|
+
|
151
|
+
# Update legacy module's _current_session and _current_trace_context
|
152
|
+
# This is tricky; direct access to another module's globals is not ideal.
|
153
|
+
# Prefer explicit calls if possible, but for maximum BC:
|
154
|
+
try:
|
155
|
+
import agentops.legacy
|
156
|
+
|
157
|
+
agentops.legacy._current_session = self._legacy_session_for_init_trace
|
158
|
+
agentops.legacy._current_trace_context = self._init_trace_context
|
159
|
+
except ImportError:
|
160
|
+
pass # Should not happen
|
161
|
+
|
162
|
+
else:
|
163
|
+
logger.error("Failed to start the auto-init trace.")
|
164
|
+
# Even if auto-start fails, core services up to the tracer might be initialized.
|
165
|
+
# Set self.initialized to True if tracer is up, but return None.
|
166
|
+
self._initialized = tracer.initialized
|
167
|
+
return None # Failed to start trace
|
168
|
+
|
169
|
+
self._initialized = True # Successfully initialized and auto-trace started (if configured)
|
170
|
+
# For backward compatibility, return the legacy session wrapper when auto_start_session=True
|
171
|
+
return self._legacy_session_for_init_trace
|
172
|
+
else:
|
173
|
+
logger.debug("Auto-start session is disabled. No init trace started by client.")
|
174
|
+
self._initialized = True # Successfully initialized, just no auto-trace
|
175
|
+
return None # No auto-session, so return None
|
176
|
+
|
177
|
+
def configure(self, **kwargs: Any) -> None:
|
178
|
+
"""Update client configuration"""
|
179
|
+
self.config.configure(**kwargs)
|
180
|
+
|
181
|
+
@property
|
182
|
+
def initialized(self) -> bool:
|
183
|
+
return self._initialized
|
184
|
+
|
185
|
+
@initialized.setter
|
186
|
+
def initialized(self, value: bool) -> None:
|
187
|
+
if self._initialized and self._initialized != value:
|
188
|
+
# Allow re-setting to False if we are intentionally re-initializing
|
189
|
+
# This logic is now partly in init() to handle re-init cases
|
190
|
+
pass
|
191
|
+
self._initialized = value
|
192
|
+
|
193
|
+
# ------------------------------------------------------------
|
194
|
+
# Remove the old __instance = None at the end of the class definition if it's a repeat
|
195
|
+
# __instance = None # This was a class variable, should be defined once
|
196
|
+
|
197
|
+
# Make _init_trace_context and _legacy_session_for_init_trace accessible
|
198
|
+
# to the atexit handler if it becomes a static/class method or needs access
|
199
|
+
# For now, the atexit handler is global and uses global vars copied from these.
|
200
|
+
|
201
|
+
# Deprecate and remove the old global _active_session from this module.
|
202
|
+
# Consumers should use agentops.start_trace() or rely on the auto-init trace.
|
203
|
+
# For a transition, the auto-init trace's legacy wrapper is set to legacy module's globals.
|
204
|
+
|
205
|
+
|
206
|
+
# Ensure the global _active_session (if needed for some very old compatibility) points to the client's legacy session for init trace.
|
207
|
+
# This specific global _active_session in client.py is problematic and should be phased out.
|
208
|
+
# For now, _client_legacy_session_for_init_trace is the primary global for the auto-init trace's legacy Session.
|
209
|
+
|
210
|
+
# Remove the old global _active_session defined at the top of this file if it's no longer the primary mechanism.
|
211
|
+
# The new globals _client_init_trace_context and _client_legacy_session_for_init_trace handle the auto-init trace.
|
File without changes
|