lucidicai 1.2.21__tar.gz → 1.3.0__tar.gz
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-1.2.21 → lucidicai-1.3.0}/PKG-INFO +1 -1
- {lucidicai-1.2.21 → lucidicai-1.3.0}/README.md +1 -1
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/__init__.py +40 -35
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/client.py +61 -15
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/event.py +29 -19
- lucidicai-1.3.0/lucidicai/lru.py +19 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/session.py +4 -35
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/telemetry/lucidic_span_processor.py +16 -2
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai.egg-info/PKG-INFO +1 -1
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai.egg-info/SOURCES.txt +1 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/setup.py +1 -1
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/constants.py +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/decorators.py +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/errors.py +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/image_upload.py +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/model_pricing.py +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/singleton.py +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/step.py +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/streaming.py +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/telemetry/__init__.py +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/telemetry/base_provider.py +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/telemetry/litellm_bridge.py +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/telemetry/lucidic_exporter.py +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/telemetry/openai_agents_instrumentor.py +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/telemetry/opentelemetry_converter.py +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/telemetry/otel_handlers.py +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/telemetry/otel_init.py +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/telemetry/otel_provider.py +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/telemetry/pydantic_ai_handler.py +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/telemetry/utils/__init__.py +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/telemetry/utils/image_storage.py +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/telemetry/utils/text_storage.py +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/telemetry/utils/universal_image_interceptor.py +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai.egg-info/dependency_links.txt +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai.egg-info/requires.txt +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai.egg-info/top_level.txt +0 -0
- {lucidicai-1.2.21 → lucidicai-1.3.0}/setup.cfg +0 -0
|
@@ -65,7 +65,7 @@ LUCIDIC_AGENT_ID=your_agent_id # Required: Your agent identifier
|
|
|
65
65
|
```python
|
|
66
66
|
lai.init(
|
|
67
67
|
session_name="My Session", # Required: Name for this session
|
|
68
|
-
|
|
68
|
+
api_key="...", # Optional: Override env var
|
|
69
69
|
agent_id="...", # Optional: Override env var
|
|
70
70
|
providers=["openai", "anthropic"], # Optional: LLM providers to track
|
|
71
71
|
task="Process customer request", # Optional: High-level task description
|
|
@@ -111,7 +111,7 @@ __all__ = [
|
|
|
111
111
|
def init(
|
|
112
112
|
session_name: Optional[str] = None,
|
|
113
113
|
session_id: Optional[str] = None,
|
|
114
|
-
|
|
114
|
+
api_key: Optional[str] = None,
|
|
115
115
|
agent_id: Optional[str] = None,
|
|
116
116
|
task: Optional[str] = None,
|
|
117
117
|
providers: Optional[List[ProviderType]] = [],
|
|
@@ -128,7 +128,7 @@ def init(
|
|
|
128
128
|
Args:
|
|
129
129
|
session_name: The display name of the session.
|
|
130
130
|
session_id: Custom ID of the session. If not provided, a random ID will be generated.
|
|
131
|
-
|
|
131
|
+
api_key: API key for authentication. If not provided, will use the LUCIDIC_API_KEY environment variable.
|
|
132
132
|
agent_id: Agent ID. If not provided, will use the LUCIDIC_AGENT_ID environment variable.
|
|
133
133
|
task: Task description.
|
|
134
134
|
providers: List of provider types ("openai", "anthropic", "langchain", "pydantic_ai").
|
|
@@ -142,27 +142,29 @@ def init(
|
|
|
142
142
|
InvalidOperationError: If the client is already initialized.
|
|
143
143
|
APIKeyVerificationError: If the API key is invalid.
|
|
144
144
|
"""
|
|
145
|
-
|
|
146
|
-
lucidic_api_key = os.getenv("LUCIDIC_API_KEY", None)
|
|
147
|
-
if lucidic_api_key is None:
|
|
148
|
-
raise APIKeyVerificationError("Make sure to either pass your API key into lai.init() or set the LUCIDIC_API_KEY environment variable.")
|
|
149
|
-
if agent_id is None:
|
|
150
|
-
agent_id = os.getenv("LUCIDIC_AGENT_ID", None)
|
|
151
|
-
if agent_id is None:
|
|
152
|
-
raise APIKeyVerificationError("Lucidic agent ID not specified. Make sure to either pass your agent ID into lai.init() or set the LUCIDIC_AGENT_ID environment variable.")
|
|
153
|
-
|
|
145
|
+
|
|
154
146
|
# get current client which will be NullClient if never lai is never initialized
|
|
155
147
|
client = Client()
|
|
156
|
-
#
|
|
148
|
+
# if not yet initialized or still the NullClient -> creaet a real client when init is called
|
|
157
149
|
if not getattr(client, 'initialized', False):
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
150
|
+
if api_key is None:
|
|
151
|
+
api_key = os.getenv("LUCIDIC_API_KEY", None)
|
|
152
|
+
if api_key is None:
|
|
153
|
+
raise APIKeyVerificationError("Make sure to either pass your API key into lai.init() or set the LUCIDIC_API_KEY environment variable.")
|
|
154
|
+
if agent_id is None:
|
|
155
|
+
agent_id = os.getenv("LUCIDIC_AGENT_ID", None)
|
|
156
|
+
if agent_id is None:
|
|
157
|
+
raise APIKeyVerificationError("Lucidic agent ID not specified. Make sure to either pass your agent ID into lai.init() or set the LUCIDIC_AGENT_ID environment variable.")
|
|
158
|
+
client = Client(api_key=api_key, agent_id=agent_id)
|
|
159
|
+
else:
|
|
160
|
+
# Already initialized, this is a re-init
|
|
161
|
+
api_key = api_key or os.getenv("LUCIDIC_API_KEY", None)
|
|
162
|
+
agent_id = agent_id or os.getenv("LUCIDIC_AGENT_ID", None)
|
|
163
|
+
client.agent_id = agent_id
|
|
164
|
+
if api_key is not None and agent_id is not None and (api_key != client.api_key or agent_id != client.agent_id):
|
|
165
|
+
client.set_api_key(api_key)
|
|
166
|
+
client.agent_id = agent_id
|
|
167
|
+
|
|
166
168
|
|
|
167
169
|
# Handle auto_end with environment variable support
|
|
168
170
|
if auto_end is None:
|
|
@@ -170,14 +172,14 @@ def init(
|
|
|
170
172
|
|
|
171
173
|
# Set up providers
|
|
172
174
|
_setup_providers(client, providers)
|
|
173
|
-
|
|
175
|
+
real_session_id = client.init_session(
|
|
174
176
|
session_name=session_name,
|
|
175
177
|
mass_sim_id=mass_sim_id,
|
|
176
178
|
task=task,
|
|
177
179
|
rubrics=rubrics,
|
|
178
180
|
tags=tags,
|
|
179
181
|
production_monitoring=production_monitoring,
|
|
180
|
-
|
|
182
|
+
session_id=session_id,
|
|
181
183
|
)
|
|
182
184
|
if masking_function:
|
|
183
185
|
client.masking_function = masking_function
|
|
@@ -186,20 +188,20 @@ def init(
|
|
|
186
188
|
client.auto_end = auto_end
|
|
187
189
|
|
|
188
190
|
logger.info("Session initialized successfully")
|
|
189
|
-
return
|
|
191
|
+
return real_session_id
|
|
190
192
|
|
|
191
193
|
|
|
192
194
|
def continue_session(
|
|
193
195
|
session_id: str,
|
|
194
|
-
|
|
196
|
+
api_key: Optional[str] = None,
|
|
195
197
|
agent_id: Optional[str] = None,
|
|
196
198
|
providers: Optional[List[ProviderType]] = [],
|
|
197
199
|
masking_function = None,
|
|
198
200
|
auto_end: Optional[bool] = True,
|
|
199
201
|
):
|
|
200
|
-
if
|
|
201
|
-
|
|
202
|
-
if
|
|
202
|
+
if api_key is None:
|
|
203
|
+
api_key = os.getenv("LUCIDIC_API_KEY", None)
|
|
204
|
+
if api_key is None:
|
|
203
205
|
raise APIKeyVerificationError("Make sure to either pass your API key into lai.init() or set the LUCIDIC_API_KEY environment variable.")
|
|
204
206
|
if agent_id is None:
|
|
205
207
|
agent_id = os.getenv("LUCIDIC_AGENT_ID", None)
|
|
@@ -211,7 +213,7 @@ def continue_session(
|
|
|
211
213
|
raise InvalidOperationError("[Lucidic] Session already in progress. Please call lai.end_session() or lai.reset_sdk() first.")
|
|
212
214
|
# if not yet initialized or still the NullClient -> create a real client when init is called
|
|
213
215
|
if not getattr(client, 'initialized', False):
|
|
214
|
-
client = Client(
|
|
216
|
+
client = Client(api_key=api_key, agent_id=agent_id)
|
|
215
217
|
|
|
216
218
|
# Handle auto_end with environment variable support
|
|
217
219
|
if auto_end is None:
|
|
@@ -284,8 +286,10 @@ def end_session(
|
|
|
284
286
|
|
|
285
287
|
def reset_sdk() -> None:
|
|
286
288
|
"""
|
|
287
|
-
Reset the SDK.
|
|
289
|
+
DEPRECATED: Reset the SDK.
|
|
288
290
|
"""
|
|
291
|
+
return
|
|
292
|
+
|
|
289
293
|
client = Client()
|
|
290
294
|
if not client.initialized:
|
|
291
295
|
return
|
|
@@ -315,6 +319,7 @@ def _auto_end_session():
|
|
|
315
319
|
client = Client()
|
|
316
320
|
if hasattr(client, 'auto_end') and client.auto_end and client.session and not client.session.is_finished:
|
|
317
321
|
logger.info("Auto-ending active session on exit")
|
|
322
|
+
client.auto_end = False # To avoid repeating auto-end on exit
|
|
318
323
|
end_session()
|
|
319
324
|
except Exception as e:
|
|
320
325
|
logger.debug(f"Error during auto-end session: {e}")
|
|
@@ -341,7 +346,7 @@ signal.signal(signal.SIGTERM, _signal_handler)
|
|
|
341
346
|
def create_mass_sim(
|
|
342
347
|
mass_sim_name: str,
|
|
343
348
|
total_num_sessions: int,
|
|
344
|
-
|
|
349
|
+
api_key: Optional[str] = None,
|
|
345
350
|
agent_id: Optional[str] = None,
|
|
346
351
|
task: Optional[str] = None,
|
|
347
352
|
tags: Optional[list] = None
|
|
@@ -352,7 +357,7 @@ def create_mass_sim(
|
|
|
352
357
|
Args:
|
|
353
358
|
mass_sim_name: Name of the mass simulation.
|
|
354
359
|
total_num_sessions: Total intended number of sessions. More sessions can be added later.
|
|
355
|
-
|
|
360
|
+
api_key: API key for authentication. If not provided, will use the LUCIDIC_API_KEY environment variable.
|
|
356
361
|
agent_id: Agent ID. If not provided, will use the LUCIDIC_AGENT_ID environment variable.
|
|
357
362
|
task: Task description.
|
|
358
363
|
tags: Tags for the mass simulation.
|
|
@@ -360,9 +365,9 @@ def create_mass_sim(
|
|
|
360
365
|
Returns:
|
|
361
366
|
mass_sim_id: ID of the created mass simulation. Pass this to lai.init() to create a new session in the mass sim.
|
|
362
367
|
"""
|
|
363
|
-
if
|
|
364
|
-
|
|
365
|
-
if
|
|
368
|
+
if api_key is None:
|
|
369
|
+
api_key = os.getenv("LUCIDIC_API_KEY", None)
|
|
370
|
+
if api_key is None:
|
|
366
371
|
raise APIKeyVerificationError("Make sure to either pass your API key into lai.init() or set the LUCIDIC_API_KEY environment variable.")
|
|
367
372
|
if agent_id is None:
|
|
368
373
|
agent_id = os.getenv("LUCIDIC_AGENT_ID", None)
|
|
@@ -372,7 +377,7 @@ def create_mass_sim(
|
|
|
372
377
|
client = Client()
|
|
373
378
|
except LucidicNotInitializedError:
|
|
374
379
|
client = Client( # TODO: fail hard if incorrect API key or agent ID provided and wrong, fail silently if not provided
|
|
375
|
-
|
|
380
|
+
api_key=api_key,
|
|
376
381
|
agent_id=agent_id,
|
|
377
382
|
)
|
|
378
383
|
mass_sim_id = client.init_mass_sim(mass_sim_name=mass_sim_name, total_num_sims=total_num_sessions, task=task, tags=tags) # TODO: change total_num_sims to total_num_sessions everywhere
|
|
@@ -13,7 +13,7 @@ from .errors import APIKeyVerificationError, InvalidOperationError, LucidicNotIn
|
|
|
13
13
|
from .telemetry.base_provider import BaseProvider
|
|
14
14
|
from .session import Session
|
|
15
15
|
from .singleton import singleton, clear_singletons
|
|
16
|
-
|
|
16
|
+
from .lru import LRUCache
|
|
17
17
|
|
|
18
18
|
NETWORK_RETRIES = 3
|
|
19
19
|
|
|
@@ -22,14 +22,16 @@ NETWORK_RETRIES = 3
|
|
|
22
22
|
class Client:
|
|
23
23
|
def __init__(
|
|
24
24
|
self,
|
|
25
|
-
|
|
25
|
+
api_key: str,
|
|
26
26
|
agent_id: str,
|
|
27
27
|
):
|
|
28
28
|
self.base_url = "https://analytics.lucidic.ai/api" if not (os.getenv("LUCIDIC_DEBUG", 'False') == 'True') else "http://localhost:8000/api"
|
|
29
29
|
self.initialized = False
|
|
30
30
|
self.session = None
|
|
31
|
+
self.previous_sessions = LRUCache(500) # For LRU cache of previously initialized sessions
|
|
32
|
+
self.custom_session_id_translations = LRUCache(500) # For translations of custom session IDs to real session IDs
|
|
31
33
|
self.providers = []
|
|
32
|
-
self.api_key =
|
|
34
|
+
self.api_key = api_key
|
|
33
35
|
self.agent_id = agent_id
|
|
34
36
|
self.masking_function = None
|
|
35
37
|
self.auto_end = False # Default to False until explicitly set during init
|
|
@@ -42,13 +44,17 @@ class Client:
|
|
|
42
44
|
)
|
|
43
45
|
adapter = HTTPAdapter(max_retries=retry_cfg, pool_connections=20, pool_maxsize=100)
|
|
44
46
|
self.request_session.mount("https://", adapter)
|
|
45
|
-
self.
|
|
47
|
+
self.set_api_key(api_key)
|
|
46
48
|
self.prompts = dict()
|
|
49
|
+
|
|
50
|
+
def set_api_key(self, api_key: str):
|
|
51
|
+
self.api_key = api_key
|
|
52
|
+
self.request_session.headers.update({"Authorization": f"Api-Key {self.api_key}", "User-Agent": "lucidic-sdk/1.1"})
|
|
47
53
|
try:
|
|
48
|
-
self.verify_api_key(self.base_url,
|
|
54
|
+
self.verify_api_key(self.base_url, api_key)
|
|
49
55
|
except APIKeyVerificationError:
|
|
50
56
|
raise APIKeyVerificationError("Invalid API Key")
|
|
51
|
-
|
|
57
|
+
|
|
52
58
|
def clear(self):
|
|
53
59
|
self.undo_overrides()
|
|
54
60
|
clear_singletons()
|
|
@@ -69,7 +75,7 @@ class Client:
|
|
|
69
75
|
def undo_overrides(self):
|
|
70
76
|
for provider in self.providers:
|
|
71
77
|
provider.undo_override()
|
|
72
|
-
|
|
78
|
+
|
|
73
79
|
def init_session(
|
|
74
80
|
self,
|
|
75
81
|
session_name: str,
|
|
@@ -78,30 +84,69 @@ class Client:
|
|
|
78
84
|
rubrics: Optional[list] = None,
|
|
79
85
|
tags: Optional[list] = None,
|
|
80
86
|
production_monitoring: Optional[bool] = False,
|
|
81
|
-
|
|
87
|
+
session_id: Optional[str] = None,
|
|
82
88
|
) -> None:
|
|
89
|
+
if session_id:
|
|
90
|
+
# Check if it's a known session ID, maybe custom and maybe real
|
|
91
|
+
if session_id in self.custom_session_id_translations:
|
|
92
|
+
session_id = self.custom_session_id_translations[session_id]
|
|
93
|
+
# Check if it's the same as the current session
|
|
94
|
+
if self.session and self.session.session_id == session_id:
|
|
95
|
+
return self.session.session_id
|
|
96
|
+
# Check if it's a previous session that we have saved
|
|
97
|
+
if session_id in self.previous_sessions:
|
|
98
|
+
if self.session:
|
|
99
|
+
self.previous_sessions[self.session.session_id] = self.session
|
|
100
|
+
self.session = self.previous_sessions.pop(session_id) # Remove from previous sessions because it's now the current session
|
|
101
|
+
return self.session.session_id
|
|
102
|
+
|
|
103
|
+
# Either there's no session ID, or we don't know about the old session
|
|
104
|
+
# We need to go to the backend in both cases
|
|
105
|
+
request_data = {
|
|
106
|
+
"agent_id": self.agent_id,
|
|
107
|
+
"session_name": session_name,
|
|
108
|
+
"task": task,
|
|
109
|
+
"mass_sim_id": mass_sim_id,
|
|
110
|
+
"rubrics": rubrics,
|
|
111
|
+
"tags": tags,
|
|
112
|
+
"session_id": session_id
|
|
113
|
+
}
|
|
114
|
+
data = self.make_request('initsession', 'POST', request_data)
|
|
115
|
+
real_session_id = data["session_id"]
|
|
116
|
+
if session_id and session_id != real_session_id:
|
|
117
|
+
self.custom_session_id_translations[session_id] = real_session_id
|
|
118
|
+
|
|
119
|
+
if self.session:
|
|
120
|
+
self.previous_sessions[self.session.session_id] = self.session
|
|
121
|
+
|
|
83
122
|
self.session = Session(
|
|
84
123
|
agent_id=self.agent_id,
|
|
124
|
+
session_id=real_session_id,
|
|
85
125
|
session_name=session_name,
|
|
86
126
|
mass_sim_id=mass_sim_id,
|
|
87
127
|
task=task,
|
|
88
128
|
rubrics=rubrics,
|
|
89
129
|
tags=tags,
|
|
90
|
-
production_monitoring=production_monitoring,
|
|
91
|
-
custom_session_id=custom_session_id
|
|
92
130
|
)
|
|
93
131
|
self.initialized = True
|
|
94
132
|
return self.session.session_id
|
|
95
133
|
|
|
96
134
|
def continue_session(self, session_id: str):
|
|
135
|
+
if session_id in self.custom_session_id_translations:
|
|
136
|
+
session_id = self.custom_session_id_translations[session_id]
|
|
137
|
+
if self.session and self.session.session_id == session_id:
|
|
138
|
+
return self.session.session_id
|
|
139
|
+
if self.session:
|
|
140
|
+
self.previous_sessions[self.session.session_id] = self.session
|
|
141
|
+
data = self.make_request('continuesession', 'POST', {"session_id": session_id})
|
|
142
|
+
real_session_id = data["session_id"]
|
|
143
|
+
if session_id != real_session_id:
|
|
144
|
+
self.custom_session_id_translations[session_id] = real_session_id
|
|
97
145
|
self.session = Session(
|
|
98
146
|
agent_id=self.agent_id,
|
|
99
|
-
session_id=
|
|
147
|
+
session_id=real_session_id
|
|
100
148
|
)
|
|
101
|
-
|
|
102
|
-
# Custom session ID provided
|
|
103
|
-
self.session.custom_session_id = session_id
|
|
104
|
-
self.initialized = True
|
|
149
|
+
logger.info(f"Session {data.get('session_name', '')} continuing...")
|
|
105
150
|
return self.session.session_id
|
|
106
151
|
|
|
107
152
|
def init_mass_sim(self, **kwargs) -> str:
|
|
@@ -131,6 +176,7 @@ class Client:
|
|
|
131
176
|
return prompt
|
|
132
177
|
|
|
133
178
|
def make_request(self, endpoint, method, data):
|
|
179
|
+
print(f"Making request to {self.base_url}/{endpoint} with method {method} and data {data}")
|
|
134
180
|
http_methods = {
|
|
135
181
|
"GET": lambda data: self.request_session.get(f"{self.base_url}/{endpoint}", params=data),
|
|
136
182
|
"POST": lambda data: self.request_session.post(f"{self.base_url}/{endpoint}", json=data),
|
|
@@ -14,37 +14,47 @@ class Event:
|
|
|
14
14
|
self.event_id = None
|
|
15
15
|
self.screenshots = []
|
|
16
16
|
self.is_finished = False
|
|
17
|
-
self.init_event()
|
|
18
|
-
self.update_event(**kwargs)
|
|
17
|
+
self.init_event(**kwargs)
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
def init_event(self) -> None:
|
|
19
|
+
def init_event(self, **kwargs) -> None:
|
|
22
20
|
from .client import Client
|
|
23
|
-
request_data =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
21
|
+
request_data = self._build_request_data(**kwargs)
|
|
22
|
+
if self.step_id:
|
|
23
|
+
request_data['step_id'] = self.step_id
|
|
24
|
+
else:
|
|
25
|
+
request_data['session_id'] = self.session_id
|
|
29
26
|
data = Client().make_request('initevent', 'POST', request_data)
|
|
30
27
|
self.event_id = data["event_id"]
|
|
28
|
+
self.step_id = data["step_id"]
|
|
29
|
+
self._upload_screenshots(**kwargs)
|
|
30
|
+
return self.event_id
|
|
31
31
|
|
|
32
32
|
def update_event(self, **kwargs) -> None:
|
|
33
33
|
from .client import Client
|
|
34
|
-
if 'screenshots' in kwargs and kwargs['screenshots'] is not None:
|
|
35
|
-
for i in range(len(kwargs['screenshots'])):
|
|
36
|
-
presigned_url, bucket_name, object_key = get_presigned_url(Client().agent_id, session_id=self.session_id, event_id=self.event_id, nthscreenshot=len(self.screenshots))
|
|
37
|
-
upload_image_to_s3(presigned_url, kwargs['screenshots'][i], "JPEG")
|
|
38
|
-
self.screenshots.append(kwargs['screenshots'][i])
|
|
39
34
|
if 'is_finished' in kwargs:
|
|
40
35
|
self.is_finished = kwargs['is_finished']
|
|
41
|
-
request_data =
|
|
42
|
-
|
|
36
|
+
request_data = self._build_request_data(**kwargs)
|
|
37
|
+
request_data['event_id'] = self.event_id
|
|
38
|
+
Client().make_request('updateevent', 'PUT', request_data)
|
|
39
|
+
self._upload_screenshots(**kwargs)
|
|
40
|
+
|
|
41
|
+
def _build_request_data(self, **kwargs) -> dict:
|
|
42
|
+
from .client import Client
|
|
43
|
+
num_new_screenshots = len(kwargs.get("screenshots", []) or [])
|
|
44
|
+
return {
|
|
43
45
|
"description": Client().mask(kwargs.get("description", None)),
|
|
44
46
|
"result": Client().mask(kwargs.get("result", None)),
|
|
45
47
|
"is_finished": self.is_finished,
|
|
46
48
|
"cost_added": kwargs.get("cost_added", None),
|
|
47
49
|
"model": kwargs.get("model", None),
|
|
48
|
-
"nscreenshots": len(self.screenshots)
|
|
50
|
+
"nscreenshots": len(self.screenshots) + num_new_screenshots,
|
|
51
|
+
"duration": kwargs.get("duration", None)
|
|
49
52
|
}
|
|
50
|
-
|
|
53
|
+
|
|
54
|
+
def _upload_screenshots(self, **kwargs) -> None:
|
|
55
|
+
from .client import Client
|
|
56
|
+
if 'screenshots' in kwargs and kwargs['screenshots'] is not None:
|
|
57
|
+
for i in range(len(kwargs['screenshots'])):
|
|
58
|
+
presigned_url, bucket_name, object_key = get_presigned_url(Client().agent_id, session_id=self.session_id, event_id=self.event_id, nthscreenshot=len(self.screenshots))
|
|
59
|
+
upload_image_to_s3(presigned_url, kwargs['screenshots'][i], "JPEG")
|
|
60
|
+
self.screenshots.append(kwargs['screenshots'][i])
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from collections import OrderedDict
|
|
2
|
+
|
|
3
|
+
class LRUCache(OrderedDict):
|
|
4
|
+
def __init__(self, capacity: int):
|
|
5
|
+
super().__init__()
|
|
6
|
+
self.capacity = capacity
|
|
7
|
+
|
|
8
|
+
def __getitem__(self, key):
|
|
9
|
+
if key not in self:
|
|
10
|
+
raise KeyError(key)
|
|
11
|
+
self.move_to_end(key) # Mark as recently used
|
|
12
|
+
return super().__getitem__(key)
|
|
13
|
+
|
|
14
|
+
def __setitem__(self, key, value):
|
|
15
|
+
if key in self:
|
|
16
|
+
self.move_to_end(key) # Update position
|
|
17
|
+
super().__setitem__(key, value)
|
|
18
|
+
if len(self) > self.capacity:
|
|
19
|
+
self.popitem(last=False) # Evict least recently used item
|
|
@@ -15,11 +15,12 @@ logger = logging.getLogger("Lucidic")
|
|
|
15
15
|
class Session:
|
|
16
16
|
def __init__(
|
|
17
17
|
self,
|
|
18
|
-
agent_id: str,
|
|
18
|
+
agent_id: str,
|
|
19
|
+
session_id = None,
|
|
19
20
|
**kwargs
|
|
20
21
|
):
|
|
21
22
|
self.agent_id = agent_id
|
|
22
|
-
self.session_id =
|
|
23
|
+
self.session_id = session_id
|
|
23
24
|
self.step_history = dict()
|
|
24
25
|
self._active_step: Optional[str] = None # Step ID, not Step object
|
|
25
26
|
self.event_history = dict()
|
|
@@ -30,33 +31,6 @@ class Session:
|
|
|
30
31
|
self.session_eval = None
|
|
31
32
|
self.session_eval_reason = None
|
|
32
33
|
self.has_gif = None
|
|
33
|
-
if kwargs.get("session_id", None) is None: # The kwarg, not the attribute
|
|
34
|
-
self.init_session(**kwargs)
|
|
35
|
-
else:
|
|
36
|
-
self.continue_session(kwargs["session_id"])
|
|
37
|
-
|
|
38
|
-
def init_session(self, **kwargs) -> None:
|
|
39
|
-
from .client import Client
|
|
40
|
-
request_data = {
|
|
41
|
-
"agent_id": self.agent_id,
|
|
42
|
-
"session_name": kwargs.get("session_name", None),
|
|
43
|
-
"task": kwargs.get("task", None),
|
|
44
|
-
"mass_sim_id": kwargs.get("mass_sim_id", None),
|
|
45
|
-
"rubrics": kwargs.get("rubrics", None),
|
|
46
|
-
"tags": kwargs.get("tags", None),
|
|
47
|
-
"production_monitoring": kwargs.get("production_monitoring", False),
|
|
48
|
-
"custom_session_id": kwargs.get("custom_session_id", None)
|
|
49
|
-
}
|
|
50
|
-
data = Client().make_request('initsession', 'POST', request_data)
|
|
51
|
-
self.session_id = data["session_id"]
|
|
52
|
-
|
|
53
|
-
def continue_session(self, session_id: str) -> None:
|
|
54
|
-
from .client import Client
|
|
55
|
-
self.session_id = session_id
|
|
56
|
-
data = Client().make_request('continuesession', 'POST', {"session_id": session_id})
|
|
57
|
-
self.session_id = data["session_id"]
|
|
58
|
-
logger.info(f"Session {data['session_name']} continuing...")
|
|
59
|
-
return self.session_id
|
|
60
34
|
|
|
61
35
|
@property
|
|
62
36
|
def active_step(self) -> Optional[Step]:
|
|
@@ -110,14 +84,12 @@ class Session:
|
|
|
110
84
|
|
|
111
85
|
def create_event(self, **kwargs):
|
|
112
86
|
# Get step_id from kwargs or active step
|
|
113
|
-
temp_step_created = False
|
|
114
87
|
if 'step_id' in kwargs and kwargs['step_id'] is not None:
|
|
115
88
|
step_id = kwargs['step_id']
|
|
116
89
|
elif self._active_step:
|
|
117
90
|
step_id = self._active_step
|
|
118
91
|
else:
|
|
119
|
-
step_id =
|
|
120
|
-
temp_step_created = True
|
|
92
|
+
step_id = None
|
|
121
93
|
kwargs.pop('step_id', None)
|
|
122
94
|
event = Event(
|
|
123
95
|
session_id=self.session_id,
|
|
@@ -126,9 +98,6 @@ class Session:
|
|
|
126
98
|
)
|
|
127
99
|
self.event_history[event.event_id] = event
|
|
128
100
|
self._active_event = event
|
|
129
|
-
if temp_step_created:
|
|
130
|
-
self.update_step(step_id=step_id, is_finished=True)
|
|
131
|
-
self._active_step = None
|
|
132
101
|
return event.event_id
|
|
133
102
|
|
|
134
103
|
def update_event(self, **kwargs):
|
|
@@ -239,6 +239,12 @@ class LucidicSpanProcessor(SpanProcessor):
|
|
|
239
239
|
# Calculate cost
|
|
240
240
|
cost = self._calculate_cost(attributes)
|
|
241
241
|
|
|
242
|
+
# Calculate duration in seconds
|
|
243
|
+
duration_seconds = None
|
|
244
|
+
if span.start_time and span.end_time:
|
|
245
|
+
duration_ns = span.end_time - span.start_time
|
|
246
|
+
duration_seconds = duration_ns / 1_000_000_000
|
|
247
|
+
|
|
242
248
|
# Check success
|
|
243
249
|
is_successful = span.status.status_code != StatusCode.ERROR
|
|
244
250
|
|
|
@@ -247,7 +253,8 @@ class LucidicSpanProcessor(SpanProcessor):
|
|
|
247
253
|
'description': description,
|
|
248
254
|
'result': formatted_result,
|
|
249
255
|
'model': model,
|
|
250
|
-
'is_finished': True
|
|
256
|
+
'is_finished': True,
|
|
257
|
+
'duration': duration_seconds
|
|
251
258
|
}
|
|
252
259
|
|
|
253
260
|
if images:
|
|
@@ -288,6 +295,12 @@ class LucidicSpanProcessor(SpanProcessor):
|
|
|
288
295
|
# Calculate cost
|
|
289
296
|
cost = self._calculate_cost(attributes)
|
|
290
297
|
|
|
298
|
+
# Calculate duration in seconds
|
|
299
|
+
duration_seconds = None
|
|
300
|
+
if span.start_time and span.end_time:
|
|
301
|
+
duration_ns = span.end_time - span.start_time
|
|
302
|
+
duration_seconds = duration_ns / 1_000_000_000
|
|
303
|
+
|
|
291
304
|
# Check success
|
|
292
305
|
is_successful = span.status.status_code != StatusCode.ERROR
|
|
293
306
|
|
|
@@ -295,7 +308,8 @@ class LucidicSpanProcessor(SpanProcessor):
|
|
|
295
308
|
update_kwargs = {
|
|
296
309
|
'event_id': event_id,
|
|
297
310
|
'result': result,
|
|
298
|
-
'is_finished': True
|
|
311
|
+
'is_finished': True,
|
|
312
|
+
'duration': duration_seconds
|
|
299
313
|
}
|
|
300
314
|
|
|
301
315
|
if cost is not None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lucidicai-1.2.21 → lucidicai-1.3.0}/lucidicai/telemetry/utils/universal_image_interceptor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|