lucidicai 2.0.2__py3-none-any.whl → 2.1.1__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.
- lucidicai/__init__.py +367 -899
- lucidicai/api/__init__.py +1 -0
- lucidicai/api/client.py +218 -0
- lucidicai/api/resources/__init__.py +1 -0
- lucidicai/api/resources/dataset.py +192 -0
- lucidicai/api/resources/event.py +88 -0
- lucidicai/api/resources/session.py +126 -0
- lucidicai/core/__init__.py +1 -0
- lucidicai/core/config.py +223 -0
- lucidicai/core/errors.py +60 -0
- lucidicai/core/types.py +35 -0
- lucidicai/sdk/__init__.py +1 -0
- lucidicai/sdk/context.py +231 -0
- lucidicai/sdk/decorators.py +187 -0
- lucidicai/sdk/error_boundary.py +299 -0
- lucidicai/sdk/event.py +126 -0
- lucidicai/sdk/event_builder.py +304 -0
- lucidicai/sdk/features/__init__.py +1 -0
- lucidicai/sdk/features/dataset.py +605 -0
- lucidicai/sdk/features/feature_flag.py +383 -0
- lucidicai/sdk/init.py +361 -0
- lucidicai/sdk/shutdown_manager.py +302 -0
- lucidicai/telemetry/context_bridge.py +82 -0
- lucidicai/telemetry/context_capture_processor.py +25 -9
- lucidicai/telemetry/litellm_bridge.py +20 -24
- lucidicai/telemetry/lucidic_exporter.py +99 -60
- lucidicai/telemetry/openai_patch.py +295 -0
- lucidicai/telemetry/openai_uninstrument.py +87 -0
- lucidicai/telemetry/telemetry_init.py +16 -1
- lucidicai/telemetry/utils/model_pricing.py +278 -0
- lucidicai/utils/__init__.py +1 -0
- lucidicai/utils/images.py +337 -0
- lucidicai/utils/logger.py +168 -0
- lucidicai/utils/queue.py +393 -0
- {lucidicai-2.0.2.dist-info → lucidicai-2.1.1.dist-info}/METADATA +1 -1
- {lucidicai-2.0.2.dist-info → lucidicai-2.1.1.dist-info}/RECORD +38 -9
- {lucidicai-2.0.2.dist-info → lucidicai-2.1.1.dist-info}/WHEEL +0 -0
- {lucidicai-2.0.2.dist-info → lucidicai-2.1.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""API client and resources."""
|
lucidicai/api/client.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""Pure HTTP client for Lucidic API communication.
|
|
2
|
+
|
|
3
|
+
This module contains only the HTTP client logic, separated from
|
|
4
|
+
session management and other concerns.
|
|
5
|
+
"""
|
|
6
|
+
import json
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
from urllib.parse import urlencode
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
from requests.adapters import HTTPAdapter
|
|
12
|
+
from urllib3.util import Retry
|
|
13
|
+
|
|
14
|
+
from ..core.config import SDKConfig, get_config
|
|
15
|
+
from ..core.errors import APIKeyVerificationError
|
|
16
|
+
from ..utils.logger import debug, info, warning, error, mask_sensitive, truncate_data
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class HttpClient:
|
|
20
|
+
"""HTTP client for API communication."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, config: Optional[SDKConfig] = None):
|
|
23
|
+
"""Initialize the HTTP client.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
config: SDK configuration (uses global if not provided)
|
|
27
|
+
"""
|
|
28
|
+
self.config = config or get_config()
|
|
29
|
+
self.base_url = self.config.network.base_url
|
|
30
|
+
|
|
31
|
+
# Create session with connection pooling
|
|
32
|
+
self.session = requests.Session()
|
|
33
|
+
|
|
34
|
+
# Configure retries
|
|
35
|
+
retry_cfg = Retry(
|
|
36
|
+
total=self.config.network.max_retries,
|
|
37
|
+
backoff_factor=self.config.network.backoff_factor,
|
|
38
|
+
status_forcelist=[502, 503, 504],
|
|
39
|
+
allowed_methods=["GET", "POST", "PUT", "DELETE"],
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Configure adapter with connection pooling
|
|
43
|
+
adapter = HTTPAdapter(
|
|
44
|
+
max_retries=retry_cfg,
|
|
45
|
+
pool_connections=self.config.network.connection_pool_size,
|
|
46
|
+
pool_maxsize=self.config.network.connection_pool_maxsize
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
self.session.mount("https://", adapter)
|
|
50
|
+
self.session.mount("http://", adapter)
|
|
51
|
+
|
|
52
|
+
# Set headers
|
|
53
|
+
self._update_headers()
|
|
54
|
+
|
|
55
|
+
# Verify API key if configured
|
|
56
|
+
if self.config.api_key:
|
|
57
|
+
self._verify_api_key()
|
|
58
|
+
|
|
59
|
+
def _update_headers(self) -> None:
|
|
60
|
+
"""Update session headers with authentication."""
|
|
61
|
+
headers = {
|
|
62
|
+
"User-Agent": "lucidic-sdk/2.0",
|
|
63
|
+
"Content-Type": "application/json"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if self.config.api_key:
|
|
67
|
+
headers["Authorization"] = f"Api-Key {self.config.api_key}"
|
|
68
|
+
|
|
69
|
+
if self.config.agent_id:
|
|
70
|
+
headers["x-agent-id"] = self.config.agent_id
|
|
71
|
+
|
|
72
|
+
self.session.headers.update(headers)
|
|
73
|
+
|
|
74
|
+
def _verify_api_key(self) -> None:
|
|
75
|
+
"""Verify the API key with the backend."""
|
|
76
|
+
debug("[HTTP] Verifying API key")
|
|
77
|
+
try:
|
|
78
|
+
response = self.get("verifyapikey")
|
|
79
|
+
# Backend returns 200 OK for valid key, check if we got a response
|
|
80
|
+
if response is None:
|
|
81
|
+
raise APIKeyVerificationError("No response from API")
|
|
82
|
+
info("[HTTP] API key verified successfully")
|
|
83
|
+
except APIKeyVerificationError:
|
|
84
|
+
raise
|
|
85
|
+
except requests.RequestException as e:
|
|
86
|
+
raise APIKeyVerificationError(f"Could not verify API key: {e}")
|
|
87
|
+
|
|
88
|
+
def get(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
89
|
+
"""Make a GET request.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
endpoint: API endpoint (without base URL)
|
|
93
|
+
params: Query parameters
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Response data as dictionary
|
|
97
|
+
"""
|
|
98
|
+
return self.request("GET", endpoint, params=params)
|
|
99
|
+
|
|
100
|
+
def post(self, endpoint: str, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
101
|
+
"""Make a POST request.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
endpoint: API endpoint (without base URL)
|
|
105
|
+
data: Request body data
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Response data as dictionary
|
|
109
|
+
"""
|
|
110
|
+
# Add current_time to all POST requests like TypeScript SDK does
|
|
111
|
+
from datetime import datetime, timezone
|
|
112
|
+
if data is None:
|
|
113
|
+
data = {}
|
|
114
|
+
data["current_time"] = datetime.now(timezone.utc).isoformat()
|
|
115
|
+
return self.request("POST", endpoint, json=data)
|
|
116
|
+
|
|
117
|
+
def put(self, endpoint: str, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
118
|
+
"""Make a PUT request.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
endpoint: API endpoint (without base URL)
|
|
122
|
+
data: Request body data
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Response data as dictionary
|
|
126
|
+
"""
|
|
127
|
+
# Add current_time to all PUT requests like TypeScript SDK does
|
|
128
|
+
from datetime import datetime, timezone
|
|
129
|
+
if data is None:
|
|
130
|
+
data = {}
|
|
131
|
+
data["current_time"] = datetime.now(timezone.utc).isoformat()
|
|
132
|
+
return self.request("PUT", endpoint, json=data)
|
|
133
|
+
|
|
134
|
+
def delete(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
135
|
+
"""Make a DELETE request.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
endpoint: API endpoint (without base URL)
|
|
139
|
+
params: Query parameters
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Response data as dictionary
|
|
143
|
+
"""
|
|
144
|
+
return self.request("DELETE", endpoint, params=params)
|
|
145
|
+
|
|
146
|
+
def request(
|
|
147
|
+
self,
|
|
148
|
+
method: str,
|
|
149
|
+
endpoint: str,
|
|
150
|
+
params: Optional[Dict[str, Any]] = None,
|
|
151
|
+
json: Optional[Dict[str, Any]] = None,
|
|
152
|
+
**kwargs
|
|
153
|
+
) -> Dict[str, Any]:
|
|
154
|
+
"""Make an HTTP request.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
method: HTTP method
|
|
158
|
+
endpoint: API endpoint (without base URL)
|
|
159
|
+
params: Query parameters
|
|
160
|
+
json: Request body (for POST/PUT)
|
|
161
|
+
**kwargs: Additional arguments for requests
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Response data as dictionary
|
|
165
|
+
|
|
166
|
+
Raises:
|
|
167
|
+
requests.RequestException: On HTTP errors
|
|
168
|
+
"""
|
|
169
|
+
url = f"{self.base_url}/{endpoint}"
|
|
170
|
+
|
|
171
|
+
# Log request details
|
|
172
|
+
debug(f"[HTTP] {method} {url}")
|
|
173
|
+
if params:
|
|
174
|
+
debug(f"[HTTP] Query params: {mask_sensitive(params)}")
|
|
175
|
+
if json:
|
|
176
|
+
debug(f"[HTTP] Request body: {truncate_data(mask_sensitive(json))}")
|
|
177
|
+
|
|
178
|
+
response = self.session.request(
|
|
179
|
+
method=method,
|
|
180
|
+
url=url,
|
|
181
|
+
params=params,
|
|
182
|
+
json=json,
|
|
183
|
+
timeout=self.config.network.timeout,
|
|
184
|
+
**kwargs
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# Raise for HTTP errors with more detail
|
|
188
|
+
if not response.ok:
|
|
189
|
+
# Try to get error details from response
|
|
190
|
+
try:
|
|
191
|
+
error_data = response.json()
|
|
192
|
+
error_msg = error_data.get('detail', response.text)
|
|
193
|
+
except:
|
|
194
|
+
error_msg = response.text
|
|
195
|
+
|
|
196
|
+
error(f"[HTTP] Error {response.status_code}: {error_msg}")
|
|
197
|
+
|
|
198
|
+
response.raise_for_status()
|
|
199
|
+
|
|
200
|
+
# Parse JSON response
|
|
201
|
+
try:
|
|
202
|
+
data = response.json()
|
|
203
|
+
except ValueError:
|
|
204
|
+
# For empty responses (like verifyapikey), return success
|
|
205
|
+
if response.status_code == 200 and not response.text:
|
|
206
|
+
data = {"success": True}
|
|
207
|
+
else:
|
|
208
|
+
# Return text if not JSON
|
|
209
|
+
data = {"response": response.text}
|
|
210
|
+
|
|
211
|
+
debug(f"[HTTP] Response ({response.status_code}): {truncate_data(data)}")
|
|
212
|
+
|
|
213
|
+
return data
|
|
214
|
+
|
|
215
|
+
def close(self) -> None:
|
|
216
|
+
"""Close the HTTP session."""
|
|
217
|
+
if self.session:
|
|
218
|
+
self.session.close()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""API resource modules."""
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Dataset resource API operations."""
|
|
2
|
+
from typing import Any, Dict, List, Optional
|
|
3
|
+
|
|
4
|
+
from ..client import HttpClient
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DatasetResource:
|
|
8
|
+
"""Handle dataset-related API operations."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, http: HttpClient):
|
|
11
|
+
"""Initialize dataset resource.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
http: HTTP client instance
|
|
15
|
+
"""
|
|
16
|
+
self.http = http
|
|
17
|
+
|
|
18
|
+
def list_datasets(self, agent_id=None):
|
|
19
|
+
"""List all datasets for agent.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
agent_id: Optional agent ID to filter by
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Dictionary with num_datasets and datasets list
|
|
26
|
+
"""
|
|
27
|
+
params = {}
|
|
28
|
+
if agent_id:
|
|
29
|
+
params["agent_id"] = agent_id
|
|
30
|
+
return self.http.get("sdk/datasets", params)
|
|
31
|
+
|
|
32
|
+
def create_dataset(self, name, description=None, tags=None,
|
|
33
|
+
suggested_flag_config=None, agent_id=None):
|
|
34
|
+
"""Create new dataset.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
name: Dataset name (must be unique per agent)
|
|
38
|
+
description: Optional description
|
|
39
|
+
tags: Optional list of tags
|
|
40
|
+
suggested_flag_config: Optional flag configuration
|
|
41
|
+
agent_id: Optional agent ID
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Dictionary with dataset_id
|
|
45
|
+
"""
|
|
46
|
+
data = {"name": name}
|
|
47
|
+
if description is not None:
|
|
48
|
+
data["description"] = description
|
|
49
|
+
if tags is not None:
|
|
50
|
+
data["tags"] = tags
|
|
51
|
+
if suggested_flag_config is not None:
|
|
52
|
+
data["suggested_flag_config"] = suggested_flag_config
|
|
53
|
+
if agent_id is not None:
|
|
54
|
+
data["agent_id"] = agent_id
|
|
55
|
+
return self.http.post("sdk/datasets/create", data)
|
|
56
|
+
|
|
57
|
+
def get_dataset(self, dataset_id):
|
|
58
|
+
"""Get dataset with all items - uses existing endpoint.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
dataset_id: Dataset UUID
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Full dataset data including all items
|
|
65
|
+
"""
|
|
66
|
+
return self.http.get("getdataset", {"dataset_id": dataset_id})
|
|
67
|
+
|
|
68
|
+
def update_dataset(self, dataset_id, **kwargs):
|
|
69
|
+
"""Update dataset metadata.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
dataset_id: Dataset UUID
|
|
73
|
+
**kwargs: Fields to update (name, description, tags, suggested_flag_config)
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Updated dataset data
|
|
77
|
+
"""
|
|
78
|
+
data = {"dataset_id": dataset_id}
|
|
79
|
+
data.update(kwargs)
|
|
80
|
+
return self.http.put("sdk/datasets/update", data)
|
|
81
|
+
|
|
82
|
+
def delete_dataset(self, dataset_id):
|
|
83
|
+
"""Delete dataset and all items.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
dataset_id: Dataset UUID
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Success message
|
|
90
|
+
"""
|
|
91
|
+
return self.http.delete("sdk/datasets/delete", {"dataset_id": dataset_id})
|
|
92
|
+
|
|
93
|
+
def create_item(self, dataset_id, name, input_data,
|
|
94
|
+
expected_output=None, description=None,
|
|
95
|
+
tags=None, metadata=None, flag_overrides=None):
|
|
96
|
+
"""Create dataset item.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
dataset_id: Dataset UUID
|
|
100
|
+
name: Item name
|
|
101
|
+
input_data: Input data dictionary
|
|
102
|
+
expected_output: Optional expected output
|
|
103
|
+
description: Optional description
|
|
104
|
+
tags: Optional list of tags
|
|
105
|
+
metadata: Optional metadata dictionary
|
|
106
|
+
flag_overrides: Optional flag overrides
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Dictionary with datasetitem_id
|
|
110
|
+
"""
|
|
111
|
+
data = {
|
|
112
|
+
"dataset_id": dataset_id,
|
|
113
|
+
"name": name,
|
|
114
|
+
"input": input_data
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# Add optional fields if provided
|
|
118
|
+
if expected_output is not None:
|
|
119
|
+
data["expected_output"] = expected_output
|
|
120
|
+
if description is not None:
|
|
121
|
+
data["description"] = description
|
|
122
|
+
if tags is not None:
|
|
123
|
+
data["tags"] = tags
|
|
124
|
+
if metadata is not None:
|
|
125
|
+
data["metadata"] = metadata
|
|
126
|
+
if flag_overrides is not None:
|
|
127
|
+
data["flag_overrides"] = flag_overrides
|
|
128
|
+
|
|
129
|
+
return self.http.post("sdk/datasets/items/create", data)
|
|
130
|
+
|
|
131
|
+
def get_item(self, dataset_id, item_id):
|
|
132
|
+
"""Get specific dataset item.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
dataset_id: Dataset UUID
|
|
136
|
+
item_id: Item UUID
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Dataset item data
|
|
140
|
+
"""
|
|
141
|
+
return self.http.get("sdk/datasets/items/get", {
|
|
142
|
+
"dataset_id": dataset_id,
|
|
143
|
+
"datasetitem_id": item_id
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
def update_item(self, dataset_id, item_id, **kwargs):
|
|
147
|
+
"""Update dataset item.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
dataset_id: Dataset UUID
|
|
151
|
+
item_id: Item UUID
|
|
152
|
+
**kwargs: Fields to update
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Updated item data
|
|
156
|
+
"""
|
|
157
|
+
data = {
|
|
158
|
+
"dataset_id": dataset_id,
|
|
159
|
+
"datasetitem_id": item_id
|
|
160
|
+
}
|
|
161
|
+
data.update(kwargs)
|
|
162
|
+
return self.http.put("sdk/datasets/items/update", data)
|
|
163
|
+
|
|
164
|
+
def delete_item(self, dataset_id, item_id):
|
|
165
|
+
"""Delete dataset item.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
dataset_id: Dataset UUID
|
|
169
|
+
item_id: Item UUID
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Success message
|
|
173
|
+
"""
|
|
174
|
+
return self.http.delete("sdk/datasets/items/delete", {
|
|
175
|
+
"dataset_id": dataset_id,
|
|
176
|
+
"datasetitem_id": item_id
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
def list_item_sessions(self, dataset_id, item_id):
|
|
180
|
+
"""List all sessions for a dataset item.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
dataset_id: Dataset UUID
|
|
184
|
+
item_id: Item UUID
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Dictionary with num_sessions and sessions list
|
|
188
|
+
"""
|
|
189
|
+
return self.http.get("sdk/datasets/items/sessions", {
|
|
190
|
+
"dataset_id": dataset_id,
|
|
191
|
+
"datasetitem_id": item_id
|
|
192
|
+
})
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Event resource API operations."""
|
|
2
|
+
from typing import Any, Dict, Optional
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from ..client import HttpClient
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class EventResource:
|
|
9
|
+
"""Handle event-related API operations."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, http: HttpClient):
|
|
12
|
+
"""Initialize event resource.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
http: HTTP client instance
|
|
16
|
+
"""
|
|
17
|
+
self.http = http
|
|
18
|
+
|
|
19
|
+
def create_event(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
20
|
+
"""Create a new event.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
params: Event parameters including:
|
|
24
|
+
- client_event_id: Client-generated event ID
|
|
25
|
+
- session_id: Session ID
|
|
26
|
+
- type: Event type
|
|
27
|
+
- occurred_at: When the event occurred
|
|
28
|
+
- payload: Event payload
|
|
29
|
+
- etc.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
API response with optional blob_url for large payloads
|
|
33
|
+
"""
|
|
34
|
+
return self.http.post("events", params)
|
|
35
|
+
|
|
36
|
+
def get_event(self, event_id: str) -> Dict[str, Any]:
|
|
37
|
+
"""Get an event by ID.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
event_id: Event ID
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Event data
|
|
44
|
+
"""
|
|
45
|
+
return self.http.get(f"events/{event_id}")
|
|
46
|
+
|
|
47
|
+
def update_event(self, event_id: str, updates: Dict[str, Any]) -> Dict[str, Any]:
|
|
48
|
+
"""Update an existing event.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
event_id: Event ID
|
|
52
|
+
updates: Fields to update
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Updated event data
|
|
56
|
+
"""
|
|
57
|
+
return self.http.put(f"events/{event_id}", updates)
|
|
58
|
+
|
|
59
|
+
def list_events(
|
|
60
|
+
self,
|
|
61
|
+
session_id: Optional[str] = None,
|
|
62
|
+
event_type: Optional[str] = None,
|
|
63
|
+
limit: int = 100,
|
|
64
|
+
offset: int = 0
|
|
65
|
+
) -> Dict[str, Any]:
|
|
66
|
+
"""List events with optional filters.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
session_id: Filter by session ID
|
|
70
|
+
event_type: Filter by event type
|
|
71
|
+
limit: Maximum number of events to return
|
|
72
|
+
offset: Pagination offset
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
List of events and pagination info
|
|
76
|
+
"""
|
|
77
|
+
params = {
|
|
78
|
+
"limit": limit,
|
|
79
|
+
"offset": offset
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if session_id:
|
|
83
|
+
params["session_id"] = session_id
|
|
84
|
+
|
|
85
|
+
if event_type:
|
|
86
|
+
params["type"] = event_type
|
|
87
|
+
|
|
88
|
+
return self.http.get("events", params)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Session resource API operations."""
|
|
2
|
+
from typing import Any, Dict, List, Optional
|
|
3
|
+
|
|
4
|
+
from ..client import HttpClient
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SessionResource:
|
|
8
|
+
"""Handle session-related API operations."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, http: HttpClient):
|
|
11
|
+
"""Initialize session resource.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
http: HTTP client instance
|
|
15
|
+
"""
|
|
16
|
+
self.http = http
|
|
17
|
+
|
|
18
|
+
def create_session(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
19
|
+
"""Create a new session.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
params: Session parameters including:
|
|
23
|
+
- session_name: Name of the session
|
|
24
|
+
- agent_id: Agent ID
|
|
25
|
+
- task: Optional task description
|
|
26
|
+
- tags: Optional tags
|
|
27
|
+
- etc.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Created session data with session_id
|
|
31
|
+
"""
|
|
32
|
+
return self.http.post("initsession", params)
|
|
33
|
+
|
|
34
|
+
def get_session(self, session_id: str) -> Dict[str, Any]:
|
|
35
|
+
"""Get a session by ID.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
session_id: Session ID
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Session data
|
|
42
|
+
"""
|
|
43
|
+
return self.http.get(f"sessions/{session_id}")
|
|
44
|
+
|
|
45
|
+
def update_session(self, session_id: str, updates: Dict[str, Any]) -> Dict[str, Any]:
|
|
46
|
+
"""Update an existing session.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
session_id: Session ID
|
|
50
|
+
updates: Fields to update (task, is_finished, etc.)
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Updated session data
|
|
54
|
+
"""
|
|
55
|
+
# Add session_id to the updates payload
|
|
56
|
+
updates["session_id"] = session_id
|
|
57
|
+
return self.http.put("updatesession", updates)
|
|
58
|
+
|
|
59
|
+
def end_session(
|
|
60
|
+
self,
|
|
61
|
+
session_id: str,
|
|
62
|
+
is_successful: Optional[bool] = None,
|
|
63
|
+
is_successful_reason: Optional[str] = None,
|
|
64
|
+
session_eval: Optional[float] = None,
|
|
65
|
+
session_eval_reason: Optional[str] = None
|
|
66
|
+
) -> Dict[str, Any]:
|
|
67
|
+
"""End a session.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
session_id: Session ID
|
|
71
|
+
is_successful: Whether session was successful
|
|
72
|
+
is_successful_reason: Reason for success or failure
|
|
73
|
+
session_eval: Session evaluation score
|
|
74
|
+
session_eval_reason: Reason for evaluation
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Final session data
|
|
78
|
+
"""
|
|
79
|
+
updates = {
|
|
80
|
+
"is_finished": True
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if is_successful is not None:
|
|
84
|
+
updates["is_successful"] = is_successful
|
|
85
|
+
|
|
86
|
+
if session_eval is not None:
|
|
87
|
+
updates["session_eval"] = session_eval
|
|
88
|
+
|
|
89
|
+
if session_eval_reason is not None:
|
|
90
|
+
updates["session_eval_reason"] = session_eval_reason
|
|
91
|
+
|
|
92
|
+
if is_successful_reason is not None:
|
|
93
|
+
updates["is_successful_reason"] = is_successful_reason
|
|
94
|
+
|
|
95
|
+
return self.update_session(session_id, updates)
|
|
96
|
+
|
|
97
|
+
def list_sessions(
|
|
98
|
+
self,
|
|
99
|
+
agent_id: Optional[str] = None,
|
|
100
|
+
experiment_id: Optional[str] = None,
|
|
101
|
+
limit: int = 100,
|
|
102
|
+
offset: int = 0
|
|
103
|
+
) -> Dict[str, Any]:
|
|
104
|
+
"""List sessions with optional filters.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
agent_id: Filter by agent ID
|
|
108
|
+
experiment_id: Filter by experiment ID
|
|
109
|
+
limit: Maximum number of sessions
|
|
110
|
+
offset: Pagination offset
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
List of sessions and pagination info
|
|
114
|
+
"""
|
|
115
|
+
params = {
|
|
116
|
+
"limit": limit,
|
|
117
|
+
"offset": offset
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if agent_id:
|
|
121
|
+
params["agent_id"] = agent_id
|
|
122
|
+
|
|
123
|
+
if experiment_id:
|
|
124
|
+
params["experiment_id"] = experiment_id
|
|
125
|
+
|
|
126
|
+
return self.http.get("sessions", params)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core utilities and configuration."""
|