uipath 2.1.75__py3-none-any.whl → 2.1.77__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.
@@ -75,15 +75,10 @@ class ConsoleProgressReporter:
75
75
  result.append(" - No evaluators", style="dim")
76
76
  self.console.print(result)
77
77
 
78
- def _extract_error_message(self, eval_item_payload) -> str:
79
- """Extract clean error message from evaluation item."""
80
- if hasattr(eval_item_payload, "_error_message"):
81
- error_message = getattr(eval_item_payload, "_error_message", None)
82
- if error_message:
83
- return str(error_message) or "Execution failed"
84
- return "Execution failed"
85
-
86
- def _display_failed_evaluation(self, eval_name: str, error_msg: str) -> None:
78
+ def _extract_error_message(self, payload: EvalRunUpdatedEvent) -> str:
79
+ return str(payload.exception_details.exception) or "Execution failed" # type: ignore
80
+
81
+ def _display_failed_evaluation(self, eval_name: str) -> None:
87
82
  """Display results for a failed evaluation."""
88
83
  from rich.text import Text
89
84
 
@@ -92,11 +87,6 @@ class ConsoleProgressReporter:
92
87
  result.append(eval_name, style="bold white")
93
88
  self.console.print(result)
94
89
 
95
- error_text = Text()
96
- error_text.append(" ", style="")
97
- error_text.append(error_msg, style="red")
98
- self.console.print(error_text)
99
-
100
90
  def start_display(self):
101
91
  """Start the display."""
102
92
  if not self.display_started:
@@ -122,37 +112,47 @@ class ConsoleProgressReporter:
122
112
  except Exception as e:
123
113
  logger.error(f"Failed to handle create eval run event: {e}")
124
114
 
115
+ def _display_logs_panel(self, eval_name: str, logs, error_msg: str = "") -> None:
116
+ """Display execution logs panel with optional exception at the end."""
117
+ self.console.print(
118
+ Rule(
119
+ f"[dim italic]Execution Logs: {eval_name}[/dim italic]",
120
+ style="dim",
121
+ align="center",
122
+ )
123
+ )
124
+
125
+ if logs:
126
+ for record in logs:
127
+ self.console.print(f" [dim]{record.getMessage()}[/dim]")
128
+ elif not error_msg:
129
+ self.console.print(" [dim italic]No execution logs[/dim italic]")
130
+
131
+ if error_msg:
132
+ self.console.print(f" [red]{error_msg}[/red]")
133
+
134
+ self.console.print(Rule(style="dim"))
135
+
125
136
  async def handle_update_eval_run(self, payload: EvalRunUpdatedEvent) -> None:
126
137
  """Handle evaluation run updates."""
127
138
  try:
128
139
  if payload.success:
129
- # Store results for final display
130
140
  self.eval_results_by_name[payload.eval_item.name] = payload.eval_results
131
141
  self._display_successful_evaluation(
132
142
  payload.eval_item.name, payload.eval_results
133
143
  )
144
+ self._display_logs_panel(payload.eval_item.name, payload.logs)
134
145
  else:
135
- error_msg = self._extract_error_message(payload.eval_item)
136
- self._display_failed_evaluation(payload.eval_item.name, error_msg)
137
-
138
- logs = payload.logs
139
-
140
- self.console.print(
141
- Rule(
142
- f"[dim italic]Execution Logs: {payload.eval_item.name}[/dim italic]",
143
- style="dim",
144
- align="center",
145
- )
146
- )
147
-
148
- if len(logs) > 0:
149
- for record in logs:
150
- log_line = f" [dim]{record.getMessage()}[/dim]"
151
- self.console.print(log_line)
152
- else:
153
- self.console.print(" [dim italic]No execution logs[/dim italic]")
154
-
155
- self.console.print(Rule(style="dim"))
146
+ error_msg = self._extract_error_message(payload)
147
+ self._display_failed_evaluation(payload.eval_item.name)
148
+
149
+ if payload.exception_details.runtime_exception: # type: ignore
150
+ self._display_logs_panel(
151
+ payload.eval_item.name, payload.logs, error_msg
152
+ )
153
+ else:
154
+ self.console.print(f" [red]{error_msg}[/red]")
155
+ self.console.print()
156
156
  except Exception as e:
157
157
  logger.error(f"Console reporter error: {e}")
158
158
 
@@ -0,0 +1,5 @@
1
+ class EvaluationRuntimeException(Exception):
2
+ def __init__(self, spans, logs, root_exception):
3
+ self.spans = spans
4
+ self.logs = logs
5
+ self.root_exception = root_exception
@@ -11,6 +11,7 @@ from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
11
11
 
12
12
  from ..._events._event_bus import EventBus
13
13
  from ..._events._events import (
14
+ EvalItemExceptionDetails,
14
15
  EvalRunCreatedEvent,
15
16
  EvalRunUpdatedEvent,
16
17
  EvalSetRunCreatedEvent,
@@ -31,6 +32,7 @@ from .._runtime._logging import ExecutionLogHandler
31
32
  from .._utils._eval_set import EvalHelpers
32
33
  from ._evaluator_factory import EvaluatorFactory
33
34
  from ._models._evaluation_set import EvaluationItem, EvaluationSet
35
+ from ._models._exceptions import EvaluationRuntimeException
34
36
  from ._models._output import (
35
37
  EvaluationResultDto,
36
38
  EvaluationRunResult,
@@ -232,8 +234,7 @@ class UiPathEvalRuntime(UiPathBaseRuntime, Generic[T, C]):
232
234
  wait_for_completion=False,
233
235
  )
234
236
  except Exception as e:
235
- error_msg = str(e)
236
- eval_item._error_message = error_msg # type: ignore[attr-defined]
237
+ exception_details = EvalItemExceptionDetails(exception=e)
237
238
 
238
239
  for evaluator in evaluators:
239
240
  evaluator_counts[evaluator.id] += 1
@@ -242,18 +243,28 @@ class UiPathEvalRuntime(UiPathBaseRuntime, Generic[T, C]):
242
243
  0.0 - evaluator_averages[evaluator.id]
243
244
  ) / count
244
245
 
246
+ eval_run_updated_event = EvalRunUpdatedEvent(
247
+ execution_id=self.execution_id,
248
+ eval_item=eval_item,
249
+ eval_results=[],
250
+ success=False,
251
+ agent_output={},
252
+ agent_execution_time=0.0,
253
+ exception_details=exception_details,
254
+ spans=[],
255
+ logs=[],
256
+ )
257
+ if isinstance(e, EvaluationRuntimeException):
258
+ eval_run_updated_event.spans = e.spans
259
+ eval_run_updated_event.logs = e.logs
260
+ eval_run_updated_event.exception_details.exception = ( # type: ignore
261
+ e.root_exception
262
+ )
263
+ eval_run_updated_event.exception_details.runtime_exception = True # type: ignore
264
+
245
265
  await event_bus.publish(
246
266
  EvaluationEvents.UPDATE_EVAL_RUN,
247
- EvalRunUpdatedEvent(
248
- execution_id=self.execution_id,
249
- eval_item=eval_item,
250
- eval_results=[],
251
- success=False,
252
- agent_output={},
253
- agent_execution_time=0.0,
254
- spans=[],
255
- logs=[],
256
- ),
267
+ eval_run_updated_event,
257
268
  wait_for_completion=False,
258
269
  )
259
270
 
@@ -274,6 +285,17 @@ class UiPathEvalRuntime(UiPathBaseRuntime, Generic[T, C]):
274
285
  )
275
286
  return self.context.result
276
287
 
288
+ def _get_and_clear_execution_data(
289
+ self, execution_id: str
290
+ ) -> tuple[List[ReadableSpan], list[logging.LogRecord]]:
291
+ spans = self.span_exporter.get_spans(execution_id)
292
+ self.span_exporter.clear(execution_id)
293
+
294
+ logs = self.logs_exporter.get_logs(execution_id)
295
+ self.logs_exporter.clear(execution_id)
296
+
297
+ return spans, logs
298
+
277
299
  async def execute_runtime(
278
300
  self, eval_item: EvaluationItem
279
301
  ) -> UiPathEvalRunExecutionOutput:
@@ -284,6 +306,9 @@ class UiPathEvalRuntime(UiPathBaseRuntime, Generic[T, C]):
284
306
  is_eval_run=True,
285
307
  log_handler=self._setup_execution_logging(eval_item_id),
286
308
  )
309
+ if runtime_context.execution_id is None:
310
+ raise ValueError("execution_id must be set for eval runs")
311
+
287
312
  attributes = {
288
313
  "evalId": eval_item.id,
289
314
  "span_type": "eval",
@@ -292,21 +317,22 @@ class UiPathEvalRuntime(UiPathBaseRuntime, Generic[T, C]):
292
317
  attributes["execution.id"] = runtime_context.execution_id
293
318
 
294
319
  start_time = time()
295
-
296
- result = await self.factory.execute_in_root_span(
297
- runtime_context, root_span=eval_item.name, attributes=attributes
298
- )
320
+ try:
321
+ result = await self.factory.execute_in_root_span(
322
+ runtime_context, root_span=eval_item.name, attributes=attributes
323
+ )
324
+ except Exception as e:
325
+ spans, logs = self._get_and_clear_execution_data(
326
+ runtime_context.execution_id
327
+ )
328
+ raise EvaluationRuntimeException(
329
+ spans=spans,
330
+ logs=logs,
331
+ root_exception=e,
332
+ ) from e
299
333
 
300
334
  end_time = time()
301
-
302
- if runtime_context.execution_id is None:
303
- raise ValueError("execution_id must be set for eval runs")
304
-
305
- spans = self.span_exporter.get_spans(runtime_context.execution_id)
306
- self.span_exporter.clear(runtime_context.execution_id)
307
-
308
- logs = self.logs_exporter.get_logs(runtime_context.execution_id)
309
- self.logs_exporter.clear(runtime_context.execution_id)
335
+ spans, logs = self._get_and_clear_execution_data(runtime_context.execution_id)
310
336
 
311
337
  if result is None:
312
338
  raise ValueError("Execution result cannot be None for eval runs")
uipath/_cli/cli_eval.py CHANGED
@@ -17,6 +17,7 @@ from uipath._cli._runtime._contracts import (
17
17
  UiPathRuntimeFactory,
18
18
  )
19
19
  from uipath._cli._runtime._runtime import UiPathScriptRuntime
20
+ from uipath._cli._utils._constants import UIPATH_PROJECT_ID
20
21
  from uipath._cli._utils._folders import get_personal_workspace_key_async
21
22
  from uipath._cli.middlewares import Middlewares
22
23
  from uipath._events._event_bus import EventBus
@@ -39,6 +40,22 @@ class LiteralOption(click.Option):
39
40
  raise click.BadParameter(value) from e
40
41
 
41
42
 
43
+ def setup_reporting_prereq(no_report: bool) -> bool:
44
+ if no_report:
45
+ return False
46
+
47
+ if not os.getenv(UIPATH_PROJECT_ID, False):
48
+ console.warning(
49
+ "UIPATH_PROJECT_ID environment variable not set. Results will no be reported to Studio Web."
50
+ )
51
+ return False
52
+ if not os.getenv("UIPATH_FOLDER_KEY"):
53
+ os.environ["UIPATH_FOLDER_KEY"] = asyncio.run(
54
+ get_personal_workspace_key_async()
55
+ )
56
+ return True
57
+
58
+
42
59
  @click.command()
43
60
  @click.argument("entrypoint", required=False)
44
61
  @click.argument("eval_set", required=False)
@@ -79,10 +96,7 @@ def eval(
79
96
  workers: Number of parallel workers for running evaluations
80
97
  no_report: Do not report the evaluation results
81
98
  """
82
- if not no_report and not os.getenv("UIPATH_FOLDER_KEY"):
83
- os.environ["UIPATH_FOLDER_KEY"] = asyncio.run(
84
- get_personal_workspace_key_async()
85
- )
99
+ should_register_progress_reporter = setup_reporting_prereq(no_report)
86
100
 
87
101
  result = Middlewares.next(
88
102
  "eval",
@@ -92,6 +106,7 @@ def eval(
92
106
  no_report=no_report,
93
107
  workers=workers,
94
108
  execution_output_file=output_file,
109
+ register_progress_reporter=should_register_progress_reporter,
95
110
  )
96
111
 
97
112
  if result.error_message:
@@ -100,7 +115,7 @@ def eval(
100
115
  if result.should_continue:
101
116
  event_bus = EventBus()
102
117
 
103
- if not no_report:
118
+ if should_register_progress_reporter:
104
119
  progress_reporter = StudioWebProgressReporter(LlmOpsHttpExporter())
105
120
  asyncio.run(progress_reporter.subscribe_to_eval_runtime_events(event_bus))
106
121
 
uipath/_events/_events.py CHANGED
@@ -1,9 +1,9 @@
1
1
  import enum
2
2
  import logging
3
- from typing import Any, List, Union
3
+ from typing import Any, List, Optional, Union
4
4
 
5
5
  from opentelemetry.sdk.trace import ReadableSpan
6
- from pydantic import BaseModel, ConfigDict
6
+ from pydantic import BaseModel, ConfigDict, model_validator
7
7
 
8
8
  from uipath._cli._evals._models._evaluation_set import EvaluationItem
9
9
  from uipath.eval.models import EvalItemResult
@@ -29,6 +29,13 @@ class EvalRunCreatedEvent(BaseModel):
29
29
  eval_item: EvaluationItem
30
30
 
31
31
 
32
+ class EvalItemExceptionDetails(BaseModel):
33
+ model_config = ConfigDict(arbitrary_types_allowed=True)
34
+
35
+ runtime_exception: bool = False
36
+ exception: Exception
37
+
38
+
32
39
  class EvalRunUpdatedEvent(BaseModel):
33
40
  model_config = ConfigDict(arbitrary_types_allowed=True)
34
41
 
@@ -40,6 +47,13 @@ class EvalRunUpdatedEvent(BaseModel):
40
47
  agent_execution_time: float
41
48
  spans: List[ReadableSpan]
42
49
  logs: List[logging.LogRecord]
50
+ exception_details: Optional[EvalItemExceptionDetails] = None
51
+
52
+ @model_validator(mode="after")
53
+ def validate_exception_details(self):
54
+ if not self.success and self.exception_details is None:
55
+ raise ValueError("exception_details must be provided when success is False")
56
+ return self
43
57
 
44
58
 
45
59
  class EvalSetRunUpdatedEvent(BaseModel):
@@ -1,14 +1,17 @@
1
1
  import json
2
2
  import logging
3
- from typing import Any, Dict
3
+ from typing import Any, Dict, List, Optional
4
+
5
+ from httpx import Response
4
6
 
5
7
  from .._config import Config
6
8
  from .._execution_context import ExecutionContext
7
- from .._utils import Endpoint, RequestSpec, infer_bindings
9
+ from .._utils import Endpoint, RequestSpec, header_folder, infer_bindings
8
10
  from ..models import Connection, ConnectionToken, EventArguments
9
11
  from ..models.connections import ConnectionTokenType
10
12
  from ..tracing._traced import traced
11
13
  from ._base_service import BaseService
14
+ from .folder_service import FolderService
12
15
 
13
16
  logger: logging.Logger = logging.getLogger("uipath")
14
17
 
@@ -20,9 +23,16 @@ class ConnectionsService(BaseService):
20
23
  and secure token management.
21
24
  """
22
25
 
23
- def __init__(self, config: Config, execution_context: ExecutionContext) -> None:
26
+ def __init__(
27
+ self,
28
+ config: Config,
29
+ execution_context: ExecutionContext,
30
+ folders_service: FolderService,
31
+ ) -> None:
24
32
  super().__init__(config=config, execution_context=execution_context)
33
+ self._folders_service = folders_service
25
34
 
35
+ @infer_bindings(resource_type="connection", name="key")
26
36
  @traced(
27
37
  name="connections_retrieve",
28
38
  run_type="uipath",
@@ -45,6 +55,117 @@ class ConnectionsService(BaseService):
45
55
  response = self.request(spec.method, url=spec.endpoint)
46
56
  return Connection.model_validate(response.json())
47
57
 
58
+ @traced(name="connections_list", run_type="uipath")
59
+ def list(
60
+ self,
61
+ *,
62
+ name: Optional[str] = None,
63
+ folder_path: Optional[str] = None,
64
+ folder_key: Optional[str] = None,
65
+ connector_key: Optional[str] = None,
66
+ skip: Optional[int] = None,
67
+ top: Optional[int] = None,
68
+ ) -> List[Connection]:
69
+ """Lists all connections with optional filtering.
70
+
71
+ Args:
72
+ name: Optional connection name to filter (supports partial matching)
73
+ folder_path: Optional folder path for filtering connections
74
+ folder_key: Optional folder key (mutually exclusive with folder_path)
75
+ connector_key: Optional connector key to filter by specific connector type
76
+ skip: Number of records to skip (for pagination)
77
+ top: Maximum number of records to return
78
+
79
+ Returns:
80
+ List[Connection]: List of connection instances
81
+
82
+ Raises:
83
+ ValueError: If both folder_path and folder_key are provided together, or if
84
+ folder_path is provided but cannot be resolved to a folder_key
85
+
86
+ Examples:
87
+ >>> # List all connections
88
+ >>> connections = sdk.connections.list()
89
+
90
+ >>> # Find connections by name
91
+ >>> salesforce_conns = sdk.connections.list(name="Salesforce")
92
+
93
+ >>> # List all Slack connections in Finance folder
94
+ >>> connections = sdk.connections.list(
95
+ ... folder_path="Finance",
96
+ ... connector_key="uipath-slack"
97
+ ... )
98
+ """
99
+ spec = self._list_spec(
100
+ name=name,
101
+ folder_path=folder_path,
102
+ folder_key=folder_key,
103
+ connector_key=connector_key,
104
+ skip=skip,
105
+ top=top,
106
+ )
107
+ response = self.request(
108
+ spec.method, url=spec.endpoint, params=spec.params, headers=spec.headers
109
+ )
110
+
111
+ return self._parse_and_validate_list_response(response)
112
+
113
+ @traced(name="connections_list", run_type="uipath")
114
+ async def list_async(
115
+ self,
116
+ *,
117
+ name: Optional[str] = None,
118
+ folder_path: Optional[str] = None,
119
+ folder_key: Optional[str] = None,
120
+ connector_key: Optional[str] = None,
121
+ skip: Optional[int] = None,
122
+ top: Optional[int] = None,
123
+ ) -> List[Connection]:
124
+ """Asynchronously lists all connections with optional filtering.
125
+
126
+ Args:
127
+ name: Optional connection name to filter (supports partial matching)
128
+ folder_path: Optional folder path for filtering connections
129
+ folder_key: Optional folder key (mutually exclusive with folder_path)
130
+ connector_key: Optional connector key to filter by specific connector type
131
+ skip: Number of records to skip (for pagination)
132
+ top: Maximum number of records to return
133
+
134
+ Returns:
135
+ List[Connection]: List of connection instances
136
+
137
+ Raises:
138
+ ValueError: If both folder_path and folder_key are provided together, or if
139
+ folder_path is provided but cannot be resolved to a folder_key
140
+
141
+ Examples:
142
+ >>> # List all connections
143
+ >>> connections = await sdk.connections.list_async()
144
+
145
+ >>> # Find connections by name
146
+ >>> salesforce_conns = await sdk.connections.list_async(name="Salesforce")
147
+
148
+ >>> # List all Slack connections in Finance folder
149
+ >>> connections = await sdk.connections.list_async(
150
+ ... folder_path="Finance",
151
+ ... connector_key="uipath-slack"
152
+ ... )
153
+ """
154
+ spec = self._list_spec(
155
+ name=name,
156
+ folder_path=folder_path,
157
+ folder_key=folder_key,
158
+ connector_key=connector_key,
159
+ skip=skip,
160
+ top=top,
161
+ )
162
+ response = await self.request_async(
163
+ spec.method, url=spec.endpoint, params=spec.params, headers=spec.headers
164
+ )
165
+
166
+ return self._parse_and_validate_list_response(response)
167
+
168
+ @infer_bindings(resource_type="connection", name="key")
48
169
  @traced(
49
170
  name="connections_retrieve",
50
171
  run_type="uipath",
@@ -213,3 +334,97 @@ class ConnectionsService(BaseService):
213
334
  endpoint=Endpoint(f"/connections_/api/v1/Connections/{key}/token"),
214
335
  params={"tokenType": token_type.value},
215
336
  )
337
+
338
+ def _parse_and_validate_list_response(self, response: Response) -> List[Connection]:
339
+ """Parse and validate the list response from the API.
340
+
341
+ Handles both OData response format (with 'value' field) and raw list responses.
342
+
343
+ Args:
344
+ response: The HTTP response from the API
345
+
346
+ Returns:
347
+ List of validated Connection instances
348
+ """
349
+ data = response.json()
350
+
351
+ # Handle both OData responses (dict with 'value') and raw list responses
352
+ if isinstance(data, dict):
353
+ connections_data = data.get("value", [])
354
+ elif isinstance(data, list):
355
+ connections_data = data
356
+ else:
357
+ connections_data = []
358
+
359
+ return [Connection.model_validate(conn) for conn in connections_data]
360
+
361
+ def _list_spec(
362
+ self,
363
+ name: Optional[str] = None,
364
+ folder_path: Optional[str] = None,
365
+ folder_key: Optional[str] = None,
366
+ connector_key: Optional[str] = None,
367
+ skip: Optional[int] = None,
368
+ top: Optional[int] = None,
369
+ ) -> RequestSpec:
370
+ """Build the request specification for listing connections.
371
+
372
+ Args:
373
+ name: Optional connection name to filter (supports partial matching)
374
+ folder_path: Optional folder path for filtering connections
375
+ folder_key: Optional folder key (mutually exclusive with folder_path)
376
+ connector_key: Optional connector key to filter by specific connector type
377
+ skip: Number of records to skip (for pagination)
378
+ top: Maximum number of records to return
379
+
380
+ Returns:
381
+ RequestSpec with endpoint, params, and headers configured
382
+
383
+ Raises:
384
+ ValueError: If both folder_path and folder_key are provided together, or if
385
+ folder_path is provided but cannot be resolved to a folder_key
386
+ """
387
+ # Validate mutual exclusivity of folder_path and folder_key
388
+ if folder_path is not None and folder_key is not None:
389
+ raise ValueError(
390
+ "folder_path and folder_key are mutually exclusive and cannot be provided together"
391
+ )
392
+
393
+ # Resolve folder_path to folder_key if needed
394
+ resolved_folder_key = folder_key
395
+ if not resolved_folder_key and folder_path:
396
+ resolved_folder_key = self._folders_service.retrieve_key(
397
+ folder_path=folder_path
398
+ )
399
+ if not resolved_folder_key:
400
+ raise ValueError(f"Folder with path '{folder_path}' not found")
401
+
402
+ # Build OData filters
403
+ filters = []
404
+ if name:
405
+ # Escape single quotes in name for OData
406
+ escaped_name = name.replace("'", "''")
407
+ filters.append(f"contains(Name, '{escaped_name}')")
408
+ if connector_key:
409
+ filters.append(f"connector/key eq '{connector_key}'")
410
+
411
+ params = {}
412
+ if filters:
413
+ params["$filter"] = " and ".join(filters)
414
+ if skip is not None:
415
+ params["$skip"] = str(skip)
416
+ if top is not None:
417
+ params["$top"] = str(top)
418
+
419
+ # Always expand connector and folder for complete information
420
+ params["$expand"] = "connector,folder"
421
+
422
+ # Use header_folder which handles validation
423
+ headers = header_folder(resolved_folder_key, None)
424
+
425
+ return RequestSpec(
426
+ method="GET",
427
+ endpoint=Endpoint("/connections_/api/v1/Connections"),
428
+ params=params,
429
+ headers=headers,
430
+ )
uipath/_uipath.py CHANGED
@@ -60,6 +60,7 @@ class UiPath:
60
60
  self._folders_service: Optional[FolderService] = None
61
61
  self._buckets_service: Optional[BucketsService] = None
62
62
  self._attachments_service: Optional[AttachmentsService] = None
63
+ self._connections_service: Optional[ConnectionsService] = None
63
64
 
64
65
  setup_logging(debug)
65
66
  self._execution_context = ExecutionContext()
@@ -98,7 +99,15 @@ class UiPath:
98
99
 
99
100
  @property
100
101
  def connections(self) -> ConnectionsService:
101
- return ConnectionsService(self._config, self._execution_context)
102
+ if not self._connections_service:
103
+ if not self._folders_service:
104
+ self._folders_service = FolderService(
105
+ self._config, self._execution_context
106
+ )
107
+ self._connections_service = ConnectionsService(
108
+ self._config, self._execution_context, self._folders_service
109
+ )
110
+ return self._connections_service
102
111
 
103
112
  @property
104
113
  def context_grounding(self) -> ContextGroundingService:
@@ -28,8 +28,10 @@ def infer_bindings(
28
28
  all_args.get(name), # type: ignore
29
29
  all_args.get(folder_path, None),
30
30
  ) as (name_overwrite_or_default, folder_path_overwrite_or_default):
31
- all_args[name] = name_overwrite_or_default
32
- all_args[folder_path] = folder_path_overwrite_or_default
31
+ if name in sig.parameters:
32
+ all_args[name] = name_overwrite_or_default
33
+ if folder_path in sig.parameters:
34
+ all_args[folder_path] = folder_path_overwrite_or_default
33
35
 
34
36
  return func(**all_args)
35
37
 
@@ -6,12 +6,16 @@ from typing import Any, Optional
6
6
  from opentelemetry.sdk.trace import ReadableSpan
7
7
  from pydantic import field_validator
8
8
 
9
- from uipath._cli._evals._models._trajectory_span import TrajectoryEvaluationTrace
10
9
  from uipath.eval.models import EvaluationResult
11
10
 
12
11
  from ..._services import UiPathLlmChatService
13
12
  from ..._utils.constants import COMMUNITY_agents_SUFFIX
14
- from ..models.models import AgentExecution, LLMResponse, NumericEvaluationResult
13
+ from ..models.models import (
14
+ AgentExecution,
15
+ LLMResponse,
16
+ NumericEvaluationResult,
17
+ TrajectoryEvaluationTrace,
18
+ )
15
19
  from .base_evaluator import BaseEvaluator
16
20
 
17
21
 
@@ -1,7 +1,8 @@
1
1
  """Models for evaluation framework including execution data and evaluation results."""
2
2
 
3
+ from dataclasses import dataclass
3
4
  from enum import IntEnum
4
- from typing import Annotated, Any, Dict, Literal, Optional, Union
5
+ from typing import Annotated, Any, Dict, List, Literal, Optional, Union
5
6
 
6
7
  from opentelemetry.sdk.trace import ReadableSpan
7
8
  from pydantic import BaseModel, ConfigDict, Field
@@ -113,3 +114,111 @@ class EvaluatorType(IntEnum):
113
114
  return cls(value)
114
115
  else:
115
116
  raise ValueError(f"{value} is not a valid EvaluatorType value")
117
+
118
+
119
+ @dataclass
120
+ class TrajectoryEvaluationSpan:
121
+ """Simplified span representation for trajectory evaluation.
122
+
123
+ Contains span information needed for evaluating agent execution paths,
124
+ excluding timestamps which are not useful for trajectory analysis.
125
+ """
126
+
127
+ name: str
128
+ status: str
129
+ attributes: Dict[str, Any]
130
+ parent_name: Optional[str] = None
131
+ events: Optional[List[Dict[str, Any]]] = None
132
+
133
+ def __post_init__(self):
134
+ """Initialize default values."""
135
+ if self.events is None:
136
+ self.events = []
137
+
138
+ @classmethod
139
+ def from_readable_span(
140
+ cls, span: ReadableSpan, parent_spans: Optional[Dict[int, str]] = None
141
+ ) -> "TrajectoryEvaluationSpan":
142
+ """Convert a ReadableSpan to a TrajectoryEvaluationSpan.
143
+
144
+ Args:
145
+ span: The OpenTelemetry ReadableSpan to convert
146
+ parent_spans: Optional mapping of span IDs to names for parent lookup
147
+
148
+ Returns:
149
+ TrajectoryEvaluationSpan with relevant data extracted
150
+ """
151
+ # Extract status
152
+ status_map = {0: "unset", 1: "ok", 2: "error"}
153
+ status = status_map.get(span.status.status_code.value, "unknown")
154
+
155
+ # Extract attributes - keep all attributes for now
156
+ attributes = {}
157
+ if span.attributes:
158
+ attributes = dict(span.attributes)
159
+
160
+ # Get parent name if available
161
+ parent_name = None
162
+ if span.parent and parent_spans and span.parent.span_id in parent_spans:
163
+ parent_name = parent_spans[span.parent.span_id]
164
+
165
+ # Extract events (without timestamps)
166
+ events = []
167
+ if hasattr(span, "events") and span.events:
168
+ for event in span.events:
169
+ event_data = {
170
+ "name": event.name,
171
+ "attributes": dict(event.attributes) if event.attributes else {},
172
+ }
173
+ events.append(event_data)
174
+
175
+ return cls(
176
+ name=span.name,
177
+ status=status,
178
+ attributes=attributes,
179
+ parent_name=parent_name,
180
+ events=events,
181
+ )
182
+
183
+ def to_dict(self) -> Dict[str, Any]:
184
+ """Convert to dictionary for JSON serialization."""
185
+ return {
186
+ "name": self.name,
187
+ "status": self.status,
188
+ "parent_name": self.parent_name,
189
+ "attributes": self.attributes,
190
+ "events": self.events,
191
+ }
192
+
193
+
194
+ class TrajectoryEvaluationTrace(BaseModel):
195
+ """Container for a collection of trajectory evaluation spans."""
196
+
197
+ spans: List[TrajectoryEvaluationSpan]
198
+
199
+ @classmethod
200
+ def from_readable_spans(
201
+ cls, spans: List[ReadableSpan]
202
+ ) -> "TrajectoryEvaluationTrace":
203
+ """Convert a list of ReadableSpans to TrajectoryEvaluationTrace.
204
+
205
+ Args:
206
+ spans: List of OpenTelemetry ReadableSpans to convert
207
+
208
+ Returns:
209
+ TrajectoryEvaluationTrace with converted spans
210
+ """
211
+ # Create a mapping of span IDs to names for parent lookup
212
+ span_id_to_name = {span.get_span_context().span_id: span.name for span in spans}
213
+
214
+ evaluation_spans = [
215
+ TrajectoryEvaluationSpan.from_readable_span(span, span_id_to_name)
216
+ for span in spans
217
+ ]
218
+
219
+ return cls(spans=evaluation_spans)
220
+
221
+ class Config:
222
+ """Pydantic configuration."""
223
+
224
+ arbitrary_types_allowed = True
@@ -2,7 +2,7 @@ import json
2
2
  import os
3
3
  from functools import wraps
4
4
  from importlib.metadata import version
5
- from logging import INFO, LogRecord, getLogger
5
+ from logging import WARNING, LogRecord, getLogger
6
6
  from typing import Any, Callable, Dict, Optional, Union
7
7
 
8
8
  from azure.monitor.opentelemetry import configure_azure_monitor
@@ -102,7 +102,7 @@ class _TelemetryClient:
102
102
  )
103
103
 
104
104
  _logger.addHandler(_AzureMonitorOpenTelemetryEventHandler())
105
- _logger.setLevel(INFO)
105
+ _logger.setLevel(WARNING)
106
106
 
107
107
  _TelemetryClient._initialized = True
108
108
  except Exception:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: uipath
3
- Version: 2.1.75
3
+ Version: 2.1.77
4
4
  Summary: Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools.
5
5
  Project-URL: Homepage, https://uipath.com
6
6
  Project-URL: Repository, https://github.com/UiPath/uipath-python
@@ -2,14 +2,14 @@ uipath/__init__.py,sha256=IaeKItOOQXMa95avueJ3dAq-XcRHyZVNjcCGwlSB000,634
2
2
  uipath/_config.py,sha256=pi3qxPzDTxMEstj_XkGOgKJqD6RTHHv7vYv8sS_-d5Q,92
3
3
  uipath/_execution_context.py,sha256=Qo8VMUFgtiL-40KsZrvul5bGv1CRERle_fCw1ORCggY,2374
4
4
  uipath/_folder_context.py,sha256=D-bgxdwpwJP4b_QdVKcPODYh15kMDrOar2xNonmMSm4,1861
5
- uipath/_uipath.py,sha256=u01pAYrVYfwgAWGeSyD-3Eg33uNHFxhQaH1KAg9NSTA,4616
5
+ uipath/_uipath.py,sha256=xvBwczfjrvrB96ZK7wo0IgDcPDQWuEqu4dioH9CKd4k,5021
6
6
  uipath/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  uipath/_cli/README.md,sha256=GLtCfbeIKZKNnGTCsfSVqRQ27V1btT1i2bSAyW_xZl4,474
8
8
  uipath/_cli/__init__.py,sha256=2RUgXYd8uJaYjA67xWb0w4IZuBmZoY8G1ccNmEQk9oM,2343
9
9
  uipath/_cli/cli_auth.py,sha256=ZEA0Fwoo77Ez9ctpRAIq7sbAwj8F4OouAbMp1g1OvjM,2601
10
10
  uipath/_cli/cli_deploy.py,sha256=KPCmQ0c_NYD5JofSDao5r6QYxHshVCRxlWDVnQvlp5w,645
11
11
  uipath/_cli/cli_dev.py,sha256=nEfpjw1PZ72O6jmufYWVrueVwihFxDPOeJakdvNHdOA,2146
12
- uipath/_cli/cli_eval.py,sha256=yMXx56sk7IqVcpR3MIjjGGYcFaciKaZ4iIi8SY-Oixo,4918
12
+ uipath/_cli/cli_eval.py,sha256=Tg9mEf0T4qkp_YpcqJVxmPp69JkjHF68teX4CM061F0,5444
13
13
  uipath/_cli/cli_init.py,sha256=Ac3-9tIH3rpikIX1ehWTo7InW5tjVNoz_w6fjvgLK4w,7052
14
14
  uipath/_cli/cli_invoke.py,sha256=m-te-EjhDpk_fhFDkt-yQFzmjEHGo5lQDGEQWxSXisQ,4395
15
15
  uipath/_cli/cli_new.py,sha256=9378NYUBc9j-qKVXV7oja-jahfJhXBg8zKVyaon7ctY,2102
@@ -44,16 +44,16 @@ uipath/_cli/_dev/_terminal/_styles/terminal.tcss,sha256=ktVpKwXIXw2VZp8KIZD6fO9i
44
44
  uipath/_cli/_dev/_terminal/_utils/_chat.py,sha256=YUZxYVdmEManwHDuZsczJT1dWIYE1dVBgABlurwMFcE,8493
45
45
  uipath/_cli/_dev/_terminal/_utils/_exporter.py,sha256=oI6D_eMwrh_2aqDYUh4GrJg8VLGrLYhDahR-_o0uJns,4144
46
46
  uipath/_cli/_dev/_terminal/_utils/_logger.py,sha256=_ipTl_oAiMF9I7keGt2AAFAMz40DNLVMVkoiq-07UAU,2943
47
- uipath/_cli/_evals/_console_progress_reporter.py,sha256=lpQvppoejRKD4Xli15Q9dJIJJz9AckhVzuOv7rcPkcM,9230
47
+ uipath/_cli/_evals/_console_progress_reporter.py,sha256=HgB6pdMyoS6YVwuI3EpM2LBcH3U69nrdaTyNgPG8ssg,9304
48
48
  uipath/_cli/_evals/_evaluator_factory.py,sha256=Gycv94VtGOpMir_Gba-UoiAyrSRfbSfe8_pTfjzcA9Q,3875
49
49
  uipath/_cli/_evals/_progress_reporter.py,sha256=kX7rNSa-QCLXIzK-vb9Jjf-XLEtucdeiQPgPlSkpp2U,16778
50
- uipath/_cli/_evals/_runtime.py,sha256=z4wJceJcGT5psBOB87tLDCLkhHafSmtE1q0EU65wnaw,14407
50
+ uipath/_cli/_evals/_runtime.py,sha256=dz3mpZCLxwnLEdkwLo6W7qzBuVAklx6LMWtd4OMRk9w,15489
51
51
  uipath/_cli/_evals/_models/_evaluation_set.py,sha256=XgPNLWciE4FgCYzZXV2kRYHzdtbc33FWSQmZQqVSdMk,4747
52
52
  uipath/_cli/_evals/_models/_evaluator.py,sha256=fuC3UOYwPD4d_wdynHeLSCzbu82golNAnnPnxC8Y4rk,3315
53
53
  uipath/_cli/_evals/_models/_evaluator_base_params.py,sha256=lTYKOV66tcjW85KHTyOdtF1p1VDaBNemrMAvH8bFIFc,382
54
+ uipath/_cli/_evals/_models/_exceptions.py,sha256=-oXLTDa4ab9Boa34ZxuUrCezf8ajIGrIEUVwZnmBASE,195
54
55
  uipath/_cli/_evals/_models/_output.py,sha256=DmwFXh1YdLiMXyXmyoZr_4hgrrv3oiHbrrtIWMqGfsg,3145
55
56
  uipath/_cli/_evals/_models/_sw_reporting.py,sha256=tSBLQFAdOIun8eP0vsqt56K6bmCZz_uMaWI3hskg_24,536
56
- uipath/_cli/_evals/_models/_trajectory_span.py,sha256=8ukM8sB9rvzBMHfC_gnexAC3xlp4uMDevKZrRzcgrm4,3637
57
57
  uipath/_cli/_evals/mocks/__init__.py,sha256=2WXwAy_oZw5bKp6L0HB13QygCJeftOB_Bget0AI6Gik,32
58
58
  uipath/_cli/_evals/mocks/llm_mocker.py,sha256=EtfPUhKcBRQ7vQPQdF3QanKIx2vzzYWA9PfdjKeJnTw,6108
59
59
  uipath/_cli/_evals/mocks/mocker.py,sha256=FjSIqXF5HzQRi1eWrFfXBz-cYZEu5TiMVm2RsKnSEWI,626
@@ -87,7 +87,7 @@ uipath/_cli/_utils/_tracing.py,sha256=2igb03j3EHjF_A406UhtCKkPfudVfFPjUq5tXUEG4o
87
87
  uipath/_cli/_utils/_uv_helpers.py,sha256=6SvoLnZPoKIxW0sjMvD1-ENV_HOXDYzH34GjBqwT138,3450
88
88
  uipath/_events/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
89
89
  uipath/_events/_event_bus.py,sha256=4-VzstyX69cr7wT1EY7ywp-Ndyz2CyemD3Wk_-QmRpo,5496
90
- uipath/_events/_events.py,sha256=rZAXze6F4M9MkoFv-Zcwmatfzq5JapyJoxKeeWuI_DY,1301
90
+ uipath/_events/_events.py,sha256=EzDfzpVm-EIH27Onad5mo8Go6-WB3S6_-6zZQ7qV58w,1811
91
91
  uipath/_resources/AGENTS.md,sha256=YWhWuX9XIbyVhVT3PnPc4Of3_q6bsNJcuzYu3N8f_Ug,25850
92
92
  uipath/_services/__init__.py,sha256=eYZElMfYDQTQU6MMjIke5J-GGT9pzLD5QfbwLiTQkEE,1037
93
93
  uipath/_services/_base_service.py,sha256=x9-9jhPzn9Z16KRdFHhJNvV-FZHvTniMsDfxlS4Cutk,5782
@@ -96,7 +96,7 @@ uipath/_services/api_client.py,sha256=kGm04ijk9AOEQd2BMxvQg-2QoB8dmyoDwFFDPyutAG
96
96
  uipath/_services/assets_service.py,sha256=pG0Io--SeiRRQmfUWPQPl1vq3csZlQgx30LBNKRmmF8,12145
97
97
  uipath/_services/attachments_service.py,sha256=NPQYK7CGjfBaNT_1S5vEAfODmOChTbQZforllFM2ofU,26678
98
98
  uipath/_services/buckets_service.py,sha256=5s8tuivd7GUZYj774DDUYTa0axxlUuesc4EBY1V5sdk,18496
99
- uipath/_services/connections_service.py,sha256=Gt8zPY4oA7cMYAU2LI3lBieoBpV81BOGelnzDWJl_V4,7931
99
+ uipath/_services/connections_service.py,sha256=EbM4ywaJsvafPEhL5bLZDWLMOUK1UyDVLRc4vZgu3Fo,15905
100
100
  uipath/_services/context_grounding_service.py,sha256=Pjx-QQQEiSKD-hY6ityj3QUSALN3fIcKLLHr_NZ0d_g,37117
101
101
  uipath/_services/documents_service.py,sha256=UnFS8EpOZ_Ng2TZk3OiJJ3iNANvFs7QxuoG_v-lQj6c,24815
102
102
  uipath/_services/entities_service.py,sha256=QKCLE6wRgq3HZraF-M2mljy-8il4vsNHrQhUgkewVVk,14028
@@ -107,7 +107,7 @@ uipath/_services/processes_service.py,sha256=O_uHgQ1rnwiV5quG0OQqabAnE6Rf6cWrMEN
107
107
  uipath/_services/queues_service.py,sha256=VaG3dWL2QK6AJBOLoW2NQTpkPfZjsqsYPl9-kfXPFzA,13534
108
108
  uipath/_utils/__init__.py,sha256=VdcpnENJIa0R6Y26NoxY64-wUVyvb4pKfTh1wXDQeMk,526
109
109
  uipath/_utils/_endpoint.py,sha256=yYHwqbQuJIevpaTkdfYJS9CrtlFeEyfb5JQK5osTCog,2489
110
- uipath/_utils/_infer_bindings.py,sha256=yTl3JBgoLwtQV-RCUkDY4qZne9FtE3HHIiYgCAKAOKw,1727
110
+ uipath/_utils/_infer_bindings.py,sha256=eCxfUjd37fOFZ6vOfKl2BhWVUt7iSSJ-VQ0-nDTBcaA,1836
111
111
  uipath/_utils/_logs.py,sha256=adfX_0UAn3YBeKJ8DQDeZs94rJyHGQO00uDfkaTpNWQ,510
112
112
  uipath/_utils/_read_overwrites.py,sha256=OQgG9ycPpFnLub5ELQdX9V2Fyh6F9_zDR3xoYagJaMI,5287
113
113
  uipath/_utils/_request_override.py,sha256=fIVHzgHVXITUlWcp8osNBwIafM1qm4_ejx0ng5UzfJ4,573
@@ -136,9 +136,9 @@ uipath/eval/evaluators/deterministic_evaluator_base.py,sha256=yDWTMU1mG-93D6DscA
136
136
  uipath/eval/evaluators/exact_match_evaluator.py,sha256=Qfz-kIUf80PKjAuge1Tc1GvN6kDB6hHveBZ86w_2How,1512
137
137
  uipath/eval/evaluators/json_similarity_evaluator.py,sha256=cP4kpN-UIf690V5dq4LaCjJc2zFx-nEffUclCwDdlhM,6607
138
138
  uipath/eval/evaluators/llm_as_judge_evaluator.py,sha256=l0bbn8ZLi9ZTXcgr7tJ2tsCvHFqIIeGa7sobaAHgI2Y,4927
139
- uipath/eval/evaluators/trajectory_evaluator.py,sha256=IylFm4yeNcVYgtmBzvzFn4Y2GXdSNnvAF8F4bCvPYdw,5774
139
+ uipath/eval/evaluators/trajectory_evaluator.py,sha256=w9E8yUXp3KCXTfiUD-ut1OVyiOH3RpFFIIe7w3v3pBQ,5740
140
140
  uipath/eval/models/__init__.py,sha256=x360CDZaRjUL3q3kh2CcXYYrQ47jwn6p6JnmhEIvMlA,419
141
- uipath/eval/models/models.py,sha256=is2wo-i0ld8Y_oZpbw5nG4cTXBz4bDLNxN6IjrfRcyM,2886
141
+ uipath/eval/models/models.py,sha256=YgPnkQunjEcEiueVQnYRsbQ3Nj1yQttDQZiMCq_DDkY,6321
142
142
  uipath/models/__init__.py,sha256=d_DkK1AtRUetM1t2NrH5UKgvJOBiynzaKnK5pMY7aIc,1289
143
143
  uipath/models/action_schema.py,sha256=tBn1qQ3NQLU5nwWlBIzIKIx3XK5pO_D1S51IjFlZ1FA,610
144
144
  uipath/models/actions.py,sha256=1vRsJ3JSmMdPkbiYAiHzY8K44vmW3VlMsmQUBAkSgrQ,3141
@@ -159,15 +159,15 @@ uipath/models/processes.py,sha256=bV31xTyF0hRWZmwy3bWj5L8dBD9wttWxfJjwzhjETmk,19
159
159
  uipath/models/queues.py,sha256=gnbeEyYlHtdqdxBalio0lw8mq-78YBG9MPMSkv1BWOg,6934
160
160
  uipath/telemetry/__init__.py,sha256=Wna32UFzZR66D-RzTKlPWlvji9i2HJb82NhHjCCXRjY,61
161
161
  uipath/telemetry/_constants.py,sha256=uRDuEZayBYtBA0tMx-2AS_D-oiVA7oKgp9zid9jNats,763
162
- uipath/telemetry/_track.py,sha256=G_Pyq8n8iMvoCWhUpWedlptXUSuUSbQBBzGxsh4DW9c,4654
162
+ uipath/telemetry/_track.py,sha256=N7EMUJi8cnpZqlVL-B746MGvH1hk3urqWbV0T67UDB8,4660
163
163
  uipath/tracing/__init__.py,sha256=GKRINyWdHVrDsI-8mrZDLdf0oey6GHGlNZTOADK-kgc,224
164
164
  uipath/tracing/_otel_exporters.py,sha256=c0GKU_oUrAwrOrqbyu64c55z1TR6xk01d3y5fLUN1lU,3215
165
165
  uipath/tracing/_traced.py,sha256=yBIY05PCCrYyx50EIHZnwJaKNdHPNx-YTR1sHQl0a98,19901
166
166
  uipath/tracing/_utils.py,sha256=qd7N56tg6VXQ9pREh61esBgUWLNA0ssKsE0QlwrRWFM,11974
167
167
  uipath/utils/__init__.py,sha256=VD-KXFpF_oWexFg6zyiWMkxl2HM4hYJMIUDZ1UEtGx0,105
168
168
  uipath/utils/_endpoints_manager.py,sha256=iRTl5Q0XAm_YgcnMcJOXtj-8052sr6jpWuPNz6CgT0Q,8408
169
- uipath-2.1.75.dist-info/METADATA,sha256=lxr5-VGeCGyTy7OCmrvbR5NqTL0nsu3T7U5W1BxLEXI,6593
170
- uipath-2.1.75.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
171
- uipath-2.1.75.dist-info/entry_points.txt,sha256=9C2_29U6Oq1ExFu7usihR-dnfIVNSKc-0EFbh0rskB4,43
172
- uipath-2.1.75.dist-info/licenses/LICENSE,sha256=-KBavWXepyDjimmzH5fVAsi-6jNVpIKFc2kZs0Ri4ng,1058
173
- uipath-2.1.75.dist-info/RECORD,,
169
+ uipath-2.1.77.dist-info/METADATA,sha256=oiBQqPLJtyrZHTtr_KWQLgnE2nQ3ZDT7aotqSCmssgU,6593
170
+ uipath-2.1.77.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
171
+ uipath-2.1.77.dist-info/entry_points.txt,sha256=9C2_29U6Oq1ExFu7usihR-dnfIVNSKc-0EFbh0rskB4,43
172
+ uipath-2.1.77.dist-info/licenses/LICENSE,sha256=-KBavWXepyDjimmzH5fVAsi-6jNVpIKFc2kZs0Ri4ng,1058
173
+ uipath-2.1.77.dist-info/RECORD,,
@@ -1,115 +0,0 @@
1
- """Trajectory evaluation span model for serializing span data in evaluations."""
2
-
3
- from dataclasses import dataclass
4
- from typing import Any, Dict, List, Optional
5
-
6
- from opentelemetry.sdk.trace import ReadableSpan
7
- from pydantic import BaseModel
8
-
9
-
10
- @dataclass
11
- class TrajectoryEvaluationSpan:
12
- """Simplified span representation for trajectory evaluation.
13
-
14
- Contains span information needed for evaluating agent execution paths,
15
- excluding timestamps which are not useful for trajectory analysis.
16
- """
17
-
18
- name: str
19
- status: str
20
- attributes: Dict[str, Any]
21
- parent_name: Optional[str] = None
22
- events: Optional[List[Dict[str, Any]]] = None
23
-
24
- def __post_init__(self):
25
- """Initialize default values."""
26
- if self.events is None:
27
- self.events = []
28
-
29
- @classmethod
30
- def from_readable_span(
31
- cls, span: ReadableSpan, parent_spans: Optional[Dict[int, str]] = None
32
- ) -> "TrajectoryEvaluationSpan":
33
- """Convert a ReadableSpan to a TrajectoryEvaluationSpan.
34
-
35
- Args:
36
- span: The OpenTelemetry ReadableSpan to convert
37
- parent_spans: Optional mapping of span IDs to names for parent lookup
38
-
39
- Returns:
40
- TrajectoryEvaluationSpan with relevant data extracted
41
- """
42
- # Extract status
43
- status_map = {0: "unset", 1: "ok", 2: "error"}
44
- status = status_map.get(span.status.status_code.value, "unknown")
45
-
46
- # Extract attributes - keep all attributes for now
47
- attributes = {}
48
- if span.attributes:
49
- attributes = dict(span.attributes)
50
-
51
- # Get parent name if available
52
- parent_name = None
53
- if span.parent and parent_spans and span.parent.span_id in parent_spans:
54
- parent_name = parent_spans[span.parent.span_id]
55
-
56
- # Extract events (without timestamps)
57
- events = []
58
- if hasattr(span, "events") and span.events:
59
- for event in span.events:
60
- event_data = {
61
- "name": event.name,
62
- "attributes": dict(event.attributes) if event.attributes else {},
63
- }
64
- events.append(event_data)
65
-
66
- return cls(
67
- name=span.name,
68
- status=status,
69
- attributes=attributes,
70
- parent_name=parent_name,
71
- events=events,
72
- )
73
-
74
- def to_dict(self) -> Dict[str, Any]:
75
- """Convert to dictionary for JSON serialization."""
76
- return {
77
- "name": self.name,
78
- "status": self.status,
79
- "parent_name": self.parent_name,
80
- "attributes": self.attributes,
81
- "events": self.events,
82
- }
83
-
84
-
85
- class TrajectoryEvaluationTrace(BaseModel):
86
- """Container for a collection of trajectory evaluation spans."""
87
-
88
- spans: List[TrajectoryEvaluationSpan]
89
-
90
- @classmethod
91
- def from_readable_spans(
92
- cls, spans: List[ReadableSpan]
93
- ) -> "TrajectoryEvaluationTrace":
94
- """Convert a list of ReadableSpans to TrajectoryEvaluationTrace.
95
-
96
- Args:
97
- spans: List of OpenTelemetry ReadableSpans to convert
98
-
99
- Returns:
100
- TrajectoryEvaluationTrace with converted spans
101
- """
102
- # Create a mapping of span IDs to names for parent lookup
103
- span_id_to_name = {span.get_span_context().span_id: span.name for span in spans}
104
-
105
- evaluation_spans = [
106
- TrajectoryEvaluationSpan.from_readable_span(span, span_id_to_name)
107
- for span in spans
108
- ]
109
-
110
- return cls(spans=evaluation_spans)
111
-
112
- class Config:
113
- """Pydantic configuration."""
114
-
115
- arbitrary_types_allowed = True