lucidicai 1.2.20__py3-none-any.whl → 1.2.22__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 CHANGED
@@ -315,6 +315,7 @@ def _auto_end_session():
315
315
  client = Client()
316
316
  if hasattr(client, 'auto_end') and client.auto_end and client.session and not client.session.is_finished:
317
317
  logger.info("Auto-ending active session on exit")
318
+ client.auto_end = False # To avoid repeating auto-end on exit
318
319
  end_session()
319
320
  except Exception as e:
320
321
  logger.debug(f"Error during auto-end session: {e}")
lucidicai/event.py CHANGED
@@ -14,37 +14,48 @@ 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
+ # TODO: this is really bad, clean this up later
20
+ def init_event(self, **kwargs) -> None:
22
21
  from .client import Client
23
- request_data = {
24
- "step_id": self.step_id,
25
- # TODO: get rid of these in backend API interface?
26
- # "description": description,
27
- # "result": result
28
- }
22
+ request_data = self._build_request_data(**kwargs)
23
+ if self.step_id:
24
+ request_data['step_id'] = self.step_id
25
+ else:
26
+ request_data['session_id'] = self.session_id
29
27
  data = Client().make_request('initevent', 'POST', request_data)
30
28
  self.event_id = data["event_id"]
29
+ self.step_id = data["step_id"]
30
+ self._upload_screenshots(**kwargs)
31
+ return self.event_id
31
32
 
32
33
  def update_event(self, **kwargs) -> None:
33
34
  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
35
  if 'is_finished' in kwargs:
40
36
  self.is_finished = kwargs['is_finished']
41
- request_data = {
42
- "event_id": self.event_id,
37
+ request_data = self._build_request_data(**kwargs)
38
+ request_data['event_id'] = self.event_id
39
+ Client().make_request('updateevent', 'PUT', request_data)
40
+ self._upload_screenshots(**kwargs)
41
+
42
+ def _build_request_data(self, **kwargs) -> dict:
43
+ from .client import Client
44
+ num_new_screenshots = len(kwargs.get("screenshots", []))
45
+ return {
43
46
  "description": Client().mask(kwargs.get("description", None)),
44
47
  "result": Client().mask(kwargs.get("result", None)),
45
48
  "is_finished": self.is_finished,
46
49
  "cost_added": kwargs.get("cost_added", None),
47
50
  "model": kwargs.get("model", None),
48
- "nscreenshots": len(self.screenshots)
51
+ "nscreenshots": len(self.screenshots) + num_new_screenshots,
52
+ "duration": kwargs.get("duration", None)
49
53
  }
50
- Client().make_request('updateevent', 'PUT', request_data)
54
+
55
+ def _upload_screenshots(self, **kwargs) -> None:
56
+ from .client import Client
57
+ if 'screenshots' in kwargs and kwargs['screenshots'] is not None:
58
+ for i in range(len(kwargs['screenshots'])):
59
+ 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))
60
+ upload_image_to_s3(presigned_url, kwargs['screenshots'][i], "JPEG")
61
+ self.screenshots.append(kwargs['screenshots'][i])
lucidicai/session.py CHANGED
@@ -110,14 +110,12 @@ class Session:
110
110
 
111
111
  def create_event(self, **kwargs):
112
112
  # Get step_id from kwargs or active step
113
- temp_step_created = False
114
113
  if 'step_id' in kwargs and kwargs['step_id'] is not None:
115
114
  step_id = kwargs['step_id']
116
115
  elif self._active_step:
117
116
  step_id = self._active_step
118
117
  else:
119
- step_id = self.create_step()
120
- temp_step_created = True
118
+ step_id = None
121
119
  kwargs.pop('step_id', None)
122
120
  event = Event(
123
121
  session_id=self.session_id,
@@ -126,9 +124,6 @@ class Session:
126
124
  )
127
125
  self.event_history[event.event_id] = event
128
126
  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
127
  return event.event_id
133
128
 
134
129
  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:
@@ -352,7 +366,21 @@ class LucidicSpanProcessor(SpanProcessor):
352
366
  if tool_name:
353
367
  if DEBUG:
354
368
  logger.info(f"[SpanProcessor] Found openai agents tool call: {tool_name}")
355
- return f"Agent Tool Call: {tool_name}"
369
+
370
+ # Extract and format tool parameters
371
+ tool_params_str = attributes.get('gen_ai.tool.parameters')
372
+ if tool_params_str:
373
+ try:
374
+ # Parse the JSON string
375
+ tool_params = json.loads(tool_params_str)
376
+ # Format the parameters nicely
377
+ formatted_params = json.dumps(tool_params, indent=2)
378
+ return f"Agent Tool Call: {tool_name}\nParameters:{formatted_params}"
379
+ except json.JSONDecodeError:
380
+ # If parsing fails, just include the raw string
381
+ return f"Agent Tool Call: {tool_name}\nParameters: {tool_params_str}"
382
+ else:
383
+ return f"Agent Tool Call: {tool_name}"
356
384
 
357
385
  # Fallback
358
386
  if DEBUG:
@@ -491,6 +519,30 @@ class LucidicSpanProcessor(SpanProcessor):
491
519
  if DEBUG:
492
520
  logger.info(f"[SpanProcessor -- Agent Tool Call Response Received]") # span attributes: {attributes}")
493
521
 
522
+ # Check the operation type to determine what kind of response this is
523
+ operation_name = attributes.get('gen_ai.operation.name')
524
+ tool_result = attributes.get('gen_ai.tool.result')
525
+ agent_name = attributes.get('gen_ai.agent.name')
526
+
527
+ # For function/tool spans, just show the tool result
528
+ if operation_name == 'function' and tool_result:
529
+ return f"Tool Result: {tool_result}"
530
+
531
+ # For agent spans or response spans without tool results, this might be a handoff
532
+ # We can check if there's actual completion content
533
+ completion_content = None
534
+ for i in range(10): # Check up to 10 completions
535
+ content = attributes.get(f'gen_ai.completion.{i}.content')
536
+ if content:
537
+ completion_content = content
538
+ break
539
+
540
+ # If we have completion content, return it (this is the actual agent response)
541
+ if completion_content:
542
+ return completion_content
543
+
544
+ # Otherwise, this is likely a handoff scenario
545
+ # Since we can't determine the next agent, just indicate a handoff occurred
494
546
  return "Agent Handoff"
495
547
 
496
548
  return "Response received"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lucidicai
3
- Version: 1.2.20
3
+ Version: 1.2.22
4
4
  Summary: Lucidic AI Python SDK
5
5
  Author: Andy Liang
6
6
  Author-email: andy@lucidic.ai
@@ -1,13 +1,13 @@
1
- lucidicai/__init__.py,sha256=sUfrleiPQPuXq5e0UnkXPjuTShiVoHnPtl_O0nzPcd8,20589
1
+ lucidicai/__init__.py,sha256=8_qfdx62xyvrdyONXCHZ4iKDWCvmyncp1T5Epb1DIv8,20664
2
2
  lucidicai/action.py,sha256=sPRd1hTIVXDqnvG9ZXWEipUFh0bsXcE0Fm7RVqmVccM,237
3
3
  lucidicai/client.py,sha256=3yBVppvwBwesca_pZSKgTUDihzZe5JhVgh1AALCpJ_Q,6620
4
4
  lucidicai/constants.py,sha256=_u0z3M4geZgS1g-CrOZUVjtcew8l70dKQnpVQvlXh9w,2172
5
5
  lucidicai/decorators.py,sha256=oqXyfHk9f9UmeaIquuU8mtzed1qZtO_-svwadpoat6g,13950
6
6
  lucidicai/errors.py,sha256=gTg0bdzjuTcUnakRbZnxjngO4gZnRLVwRHRglpZZJsM,970
7
- lucidicai/event.py,sha256=iPbNBRb2ZFohBsrRQZHzfa9GbxCsgFIw3M8w4gFrSi4,2000
7
+ lucidicai/event.py,sha256=SjPSGNBuW6YLDJUhY16IvQgE9lrg3bypgWOK9IKVJWI,2567
8
8
  lucidicai/image_upload.py,sha256=6SRudg-BpInM2gzMx1Yf1Rz_Zyh8inwoJ7U4pBw7ruY,3807
9
9
  lucidicai/model_pricing.py,sha256=o1yWCaF5Qxj4tloXxVG3SZXcTMKtk56J_Nfdo8M4uR0,11947
10
- lucidicai/session.py,sha256=T_4z3CLKMp5DOKSVpdWwMZkZG9ndXU6oiI5Al6DHSTY,5501
10
+ lucidicai/session.py,sha256=mUPwXcGW5tFsK6Udl4i2jndV0STBYHbPyWqA8GxBdHM,5285
11
11
  lucidicai/singleton.py,sha256=gfT3XdWLXSIWMqDXbY6-pnesMZ8RGRitaEPhIsgrRPw,1272
12
12
  lucidicai/state.py,sha256=4Tb1X6l2or6w_e62FYSuEeghAv3xXm5gquKwzCpvdok,235
13
13
  lucidicai/step.py,sha256=_oBIyTBZBvNkUkYHIrwWd75KMSlMtR9Ws2Lo71Lyff8,2522
@@ -33,7 +33,7 @@ lucidicai/telemetry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
33
33
  lucidicai/telemetry/base_provider.py,sha256=nrZVr4Y9xcAiMn4uAN3t3k6DlHNTvlXrA4qQg7lANOQ,544
34
34
  lucidicai/telemetry/litellm_bridge.py,sha256=mOdjEfvP--ToDv8snoOMU4pRQx_Yg4s2o3BMTMzeRK8,14979
35
35
  lucidicai/telemetry/lucidic_exporter.py,sha256=h2GOnEk22Fpeke4Zc7SSk391yr0joUApVwolV5Q4hz4,10818
36
- lucidicai/telemetry/lucidic_span_processor.py,sha256=LhkyJGBnTKpTWdBi2wyW9th4CWZ9EtZ8ulaE0rwYj0Y,27432
36
+ lucidicai/telemetry/lucidic_span_processor.py,sha256=9EKD4fvnEQIoPBLhGCAHRxsLAWmJNGFnb1NpwRUlMOI,30048
37
37
  lucidicai/telemetry/openai_agents_instrumentor.py,sha256=__wIbeglMnEEf4AGTQ--FXeWCKmz2yy8SBupwprEdZA,12694
38
38
  lucidicai/telemetry/opentelemetry_converter.py,sha256=xOHCqoTyO4hUkL6k7fxy84PbljPpYep6ET9ZqbkJehc,17665
39
39
  lucidicai/telemetry/otel_handlers.py,sha256=HqGYIWJI_Vp8So2-HMpPjnrgTBSgBHHLDu01z_sq-Qk,14646
@@ -44,7 +44,7 @@ lucidicai/telemetry/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
44
44
  lucidicai/telemetry/utils/image_storage.py,sha256=4Z59ZpVexr7-lcExfr8GsqXe0y2VZmr8Yjwa-3DeOxU,1457
45
45
  lucidicai/telemetry/utils/text_storage.py,sha256=L62MMJ8E23TDqDTUv2aRntdKMCItsXV7XjY6cFwx2DE,1503
46
46
  lucidicai/telemetry/utils/universal_image_interceptor.py,sha256=zPfVsMjtKxJP2n2OOjKbtPiQJTZ0sf5_28GWprOnJP4,12185
47
- lucidicai-1.2.20.dist-info/METADATA,sha256=vn20bB7YfIoEnDP-o_rATqNh2vDhvBORz5snd_0Sjxw,903
48
- lucidicai-1.2.20.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
49
- lucidicai-1.2.20.dist-info/top_level.txt,sha256=vSSdM3lclF4I5tyVC0xxUk8eIRnnYXMe1hW-eO91HUo,10
50
- lucidicai-1.2.20.dist-info/RECORD,,
47
+ lucidicai-1.2.22.dist-info/METADATA,sha256=jFsbWQNg8hHEu2sHwDzC8xOlRdgUFo1KsL4QjvJDN50,903
48
+ lucidicai-1.2.22.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
49
+ lucidicai-1.2.22.dist-info/top_level.txt,sha256=vSSdM3lclF4I5tyVC0xxUk8eIRnnYXMe1hW-eO91HUo,10
50
+ lucidicai-1.2.22.dist-info/RECORD,,