uipath 2.1.110__py3-none-any.whl → 2.1.112__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.

Potentially problematic release.


This version of uipath might be problematic. Click here for more details.

@@ -4,7 +4,7 @@ import logging
4
4
  import os
5
5
  from abc import ABC, abstractmethod
6
6
  from enum import Enum
7
- from typing import Any, Dict, List, Optional, Set
7
+ from typing import Any, Dict, List, Literal, Optional, Set
8
8
 
9
9
  from pydantic import BaseModel
10
10
  from pysignalr.client import SignalRClient
@@ -123,7 +123,7 @@ class UiPathDebugBridge(ABC):
123
123
  pass
124
124
 
125
125
  @abstractmethod
126
- def get_breakpoints(self) -> List[str]:
126
+ def get_breakpoints(self) -> List[str] | Literal["*"]:
127
127
  """Get nodes to suspend execution at.
128
128
 
129
129
  Returns:
@@ -220,7 +220,7 @@ class ConsoleDebugBridge(UiPathDebugBridge):
220
220
  """Print error."""
221
221
  self.console.print()
222
222
  self.console.print("[red]─" * 40)
223
- self.console.print(f"[red]✗ Error[/red] [dim]{execution_id}")
223
+ self.console.print("[red]✗ Error[/red]")
224
224
  self.console.print("[red]─" * 40)
225
225
 
226
226
  # Truncate very long errors
@@ -228,7 +228,7 @@ class ConsoleDebugBridge(UiPathDebugBridge):
228
228
  if len(error) > 500:
229
229
  error_display = error[:500] + "\n[dim]... (truncated)"
230
230
 
231
- self.console.print(f"[white]{error_display}[/white]")
231
+ self.console.print(f"[red]{error_display}[/red]")
232
232
  self.console.print("[red]─" * 40)
233
233
 
234
234
  async def wait_for_resume(self) -> Any:
@@ -252,21 +252,14 @@ class ConsoleDebugBridge(UiPathDebugBridge):
252
252
  # These commands don't resume execution, loop again
253
253
  continue
254
254
 
255
- # Reset step modes if continuing
256
- if command_result["command"] == DebugCommand.CONTINUE:
257
- self.state.step_mode = False
258
-
259
- if command_result["command"] == DebugCommand.QUIT:
260
- raise DebuggerQuitException("User requested exit")
261
-
262
255
  # Commands that resume execution: CONTINUE, STEP
263
256
  self.console.print()
264
257
  return command_result
265
258
 
266
- def get_breakpoints(self) -> List[str]:
259
+ def get_breakpoints(self) -> List[str] | Literal["*"]:
267
260
  """Get nodes to suspend execution at."""
268
261
  if self.state.step_mode:
269
- return ["*"] # Suspend at all nodes
262
+ return "*" # Suspend at all nodes
270
263
  return list(self.state.breakpoints) # Only suspend at breakpoints
271
264
 
272
265
  def _parse_command(self, user_input: str) -> Dict[str, Any]:
@@ -283,6 +276,7 @@ class ConsoleDebugBridge(UiPathDebugBridge):
283
276
  args = parts[1:] if len(parts) > 1 else []
284
277
 
285
278
  if cmd in ["c", "continue"]:
279
+ self.state.step_mode = False
286
280
  return {"command": DebugCommand.CONTINUE, "args": None}
287
281
 
288
282
  elif cmd in ["s", "step"]:
@@ -318,7 +312,7 @@ class ConsoleDebugBridge(UiPathDebugBridge):
318
312
  }
319
313
 
320
314
  elif cmd in ["q", "quit", "exit"]:
321
- raise KeyboardInterrupt("User requested exit")
315
+ raise DebuggerQuitException("User requested exit")
322
316
 
323
317
  elif cmd in ["h", "help", "?"]:
324
318
  self._print_help()
@@ -465,26 +459,75 @@ class SignalRDebugBridge(UiPathDebugBridge):
465
459
  self._client = SignalRClient(self.hub_url, headers=all_headers)
466
460
 
467
461
  # Register event handlers
468
- self._client.on("ResumeExecution", self._handle_resume)
462
+ self._client.on("Start", self._handle_start)
463
+ self._client.on("Resume", self._handle_resume)
464
+ self._client.on("Step", self._handle_step)
465
+ self._client.on("AddBreakpoints", self._handle_add_breakpoints)
466
+ self._client.on("RemoveBreakpoints", self._handle_remove_breakpoints)
467
+ self._client.on("Quit", self._handle_quit)
469
468
  self._client.on_open(self._handle_open)
470
469
  self._client.on_close(self._handle_close)
471
470
  self._client.on_error(self._handle_error)
472
471
 
473
- # Start connection in background
474
- asyncio.create_task(self._client.run())
472
+ self._run_task = asyncio.create_task(self._client.run())
473
+
474
+ async def cleanup_run_task() -> str:
475
+ error_message = (
476
+ "Failed to establish WebSocket connection within 10s timeout"
477
+ )
478
+
479
+ if self._run_task:
480
+ if not self._run_task.done():
481
+ self._run_task.cancel()
482
+ try:
483
+ await self._run_task
484
+ except asyncio.CancelledError:
485
+ pass # Expected on cancel
486
+ except Exception as task_error:
487
+ error_msg = str(task_error).strip()
488
+ error_detail = f": {error_msg}" if error_msg else ""
489
+ return f"{error_message}: {type(task_error).__name__}{error_detail}"
490
+
491
+ return error_message
492
+
493
+ try:
494
+ # Wait for connection with timeout
495
+ await asyncio.wait_for(self._connected_event.wait(), timeout=10.0)
496
+ except asyncio.TimeoutError as e:
497
+ # Clean up on timeout
498
+ raise RuntimeError(await cleanup_run_task()) from e
499
+ except Exception:
500
+ # Clean up on any other error
501
+ await cleanup_run_task()
502
+ raise
475
503
 
476
- # Wait for connection to establish
477
- await asyncio.wait_for(self._connected_event.wait(), timeout=30.0)
504
+ # Check if run_task failed
505
+ if self._run_task.done():
506
+ exception = self._run_task.exception()
507
+ if exception:
508
+ raise exception
478
509
 
479
510
  async def disconnect(self) -> None:
480
511
  """Close SignalR connection."""
481
- if self._client and hasattr(self._client, "_transport"):
482
- transport = self._client._transport
483
- if transport and hasattr(transport, "_ws") and transport._ws:
484
- try:
512
+ if not self._client:
513
+ return
514
+
515
+ # Cancel the run task first
516
+ if self._run_task and not self._run_task.done():
517
+ self._run_task.cancel()
518
+ try:
519
+ await self._run_task
520
+ except (Exception, asyncio.CancelledError):
521
+ pass
522
+
523
+ # Try to close the client cleanly
524
+ try:
525
+ if hasattr(self._client, "_transport"):
526
+ transport = self._client._transport
527
+ if transport and hasattr(transport, "_ws") and transport._ws:
485
528
  await transport._ws.close()
486
- except Exception as e:
487
- logger.warning(f"Error closing SignalR WebSocket: {e}")
529
+ except Exception as e:
530
+ logger.warning(f"Error closing SignalR WebSocket: {e}")
488
531
 
489
532
  async def emit_execution_started(self, execution_id: str, **kwargs) -> None:
490
533
  """Send execution started event."""
@@ -541,6 +584,9 @@ class SignalRDebugBridge(UiPathDebugBridge):
541
584
  error: str,
542
585
  ) -> None:
543
586
  """Send execution error event."""
587
+ if not self._connected_event.is_set():
588
+ return
589
+
544
590
  logger.error(f"Execution error: {execution_id} - {error}")
545
591
  await self._send(
546
592
  "OnExecutionError",
@@ -558,24 +604,139 @@ class SignalRDebugBridge(UiPathDebugBridge):
558
604
  logger.info("Resume command received")
559
605
  return self._resume_data
560
606
 
561
- def get_breakpoints(self) -> List[str]:
607
+ def get_breakpoints(self) -> List[str] | Literal["*"]:
562
608
  """Get nodes to suspend execution at."""
563
609
  if self.state.step_mode:
564
- return ["*"] # Suspend at all nodes
610
+ return "*" # Suspend at all nodes
565
611
  return list(self.state.breakpoints) # Only suspend at breakpoints
566
612
 
567
- async def _send(self, method: str, data: Dict[str, Any]) -> None:
568
- """Send message to SignalR hub."""
613
+ async def _send(self, event_name: str, data: Dict[str, Any]) -> None:
614
+ """Send message to SignalR hub via SendCommand.
615
+
616
+ Args:
617
+ event_name: The event/command name (e.g., "OnExecutionStarted")
618
+ data: The data payload to send
619
+ """
569
620
  if not self._client:
570
621
  raise RuntimeError("SignalR client not connected")
622
+ try:
623
+ # Wrap the event in SendCommand protocol
624
+ # Server expects: SendCommand(event_name, json_string_of_data)
625
+ data_json = json.dumps(data)
626
+ arguments: list[Any] = [event_name, data_json]
627
+ await self._client.send(method="SendCommand", arguments=arguments)
628
+ logger.debug(f"Sent command: {event_name} with data: {data}")
629
+ except Exception as e:
630
+ logger.error(f"Error sending command {event_name} to SignalR hub: {e}")
631
+
632
+ async def _handle_start(self, args: list[Any]) -> None:
633
+ """Handle Start command from SignalR server.
571
634
 
572
- await self._client.send(method=method, arguments=[data])
635
+ Args:
636
+ args: List containing command arguments, typically [dict_with_args]
637
+ """
638
+ logger.info(f"Start command received with args: {args}")
639
+ if not args or len(args) == 0:
640
+ logger.warning("Start command received with empty args.")
641
+ return
642
+
643
+ command_args = args[0] if isinstance(args[0], dict) else {}
644
+ self.state.breakpoints = set(command_args.get("breakpoints", []))
645
+ step_mode = command_args.get("enableStepMode", False)
646
+ self.state.step_mode = step_mode
573
647
 
574
648
  async def _handle_resume(self, args: list[Any]) -> None:
575
- """Handle resume command from SignalR server."""
576
- if self._resume_event and len(args) > 0:
577
- self._resume_data = args[0]
649
+ """Handle Resume command from SignalR server.
650
+
651
+ Args:
652
+ args: List containing command arguments
653
+ """
654
+ logger.info(f"Resume command received with args: {args}")
655
+ command_args = args[0] if args and len(args) > 0 else {}
656
+
657
+ if self._resume_event:
658
+ self._resume_data = command_args
578
659
  self._resume_event.set()
660
+ logger.info("Resume event set")
661
+ else:
662
+ logger.warning("Resume command received but no resume event is waiting")
663
+
664
+ async def _handle_step(self, args: list[Any]) -> None:
665
+ """Handle Step command from SignalR server.
666
+
667
+ Args:
668
+ args: List containing command arguments
669
+ """
670
+ logger.info(f"Step command received with args: {args}")
671
+ self.state.step_mode = True
672
+ logger.info("Step mode enabled")
673
+
674
+ async def _handle_add_breakpoints(self, args: list[Any]) -> None:
675
+ """Handle AddBreakpoints command from SignalR server.
676
+
677
+ Args:
678
+ args: List containing command arguments with breakpoints list
679
+ """
680
+ logger.info(f"AddBreakpoints command received with args: {args}")
681
+ if not args or len(args) == 0:
682
+ logger.warning("AddBreakpoints command received with empty args.")
683
+ return
684
+
685
+ command_args = args[0] if isinstance(args[0], dict) else {}
686
+ break_points = command_args.get("breakpoints", [])
687
+
688
+ for bp in break_points:
689
+ node_name = (
690
+ bp.get("node", {}).get("name")
691
+ if isinstance(bp.get("node"), dict)
692
+ else None
693
+ )
694
+ if node_name:
695
+ self.state.add_breakpoint(node_name)
696
+ logger.info(f"Breakpoint set at: {node_name}")
697
+ else:
698
+ logger.warning(f"Breakpoint command received without node name: {bp}")
699
+
700
+ async def _handle_remove_breakpoints(self, args: list[Any]) -> None:
701
+ """Handle RemoveBreakpoints command from SignalR server.
702
+
703
+ Args:
704
+ args: List containing command arguments with breakpoints list
705
+ """
706
+ logger.info(f"RemoveBreakpoints command received with args: {args}")
707
+ if not args or len(args) == 0:
708
+ self.state.clear_all_breakpoints()
709
+ logger.info("All breakpoints cleared")
710
+ return
711
+
712
+ command_args = args[0] if isinstance(args[0], dict) else {}
713
+ break_points = command_args.get("breakpoints", [])
714
+
715
+ if not break_points:
716
+ self.state.clear_all_breakpoints()
717
+ logger.info("All breakpoints cleared")
718
+ else:
719
+ for bp in break_points:
720
+ node_name = (
721
+ bp.get("node", {}).get("name")
722
+ if isinstance(bp.get("node"), dict)
723
+ else None
724
+ )
725
+ if node_name:
726
+ self.state.remove_breakpoint(node_name)
727
+ logger.info(f"Breakpoint removed: {node_name}")
728
+
729
+ async def _handle_quit(self, args: list[Any]) -> None:
730
+ """Handle Quit command from SignalR server.
731
+
732
+ Args:
733
+ args: List containing command arguments
734
+ """
735
+ if args:
736
+ logger.info(f"Quit command received from server with args: {args}")
737
+ else:
738
+ logger.info("Quit command received from server")
739
+ raise DebuggerQuitException("Quit command received from server")
579
740
 
580
741
  async def _handle_open(self) -> None:
581
742
  """Handle SignalR connection open."""
@@ -35,6 +35,7 @@ class UiPathDebugRuntime(UiPathBaseRuntime, Generic[T, C]):
35
35
  self.context: UiPathRuntimeContext = context
36
36
  self.factory: UiPathRuntimeFactory[T, C] = factory
37
37
  self.debug_bridge: UiPathDebugBridge = debug_bridge
38
+ self.execution_id: str = context.job_id or "default"
38
39
  self._inner_runtime: Optional[T] = None
39
40
 
40
41
  @classmethod
@@ -57,7 +58,7 @@ class UiPathDebugRuntime(UiPathBaseRuntime, Generic[T, C]):
57
58
  raise RuntimeError("Failed to create inner runtime")
58
59
 
59
60
  await self.debug_bridge.emit_execution_started(
60
- execution_id=self.context.job_id or "default"
61
+ execution_id=self.execution_id
61
62
  )
62
63
 
63
64
  # Try to stream events from inner runtime
@@ -78,8 +79,11 @@ class UiPathDebugRuntime(UiPathBaseRuntime, Generic[T, C]):
78
79
 
79
80
  except Exception as e:
80
81
  # Emit execution error
82
+ self.context.result = UiPathRuntimeResult(
83
+ status=UiPathRuntimeStatus.FAULTED,
84
+ )
81
85
  await self.debug_bridge.emit_execution_error(
82
- execution_id=self.context.job_id or "default",
86
+ execution_id=self.execution_id,
83
87
  error=str(e),
84
88
  )
85
89
  raise
@@ -142,6 +142,10 @@ class LegacyEvaluationItem(BaseModel):
142
142
  default=None,
143
143
  alias="mockingStrategy",
144
144
  )
145
+ input_mocking_strategy: Optional[InputMockingStrategy] = Field(
146
+ default=None,
147
+ alias="inputMockingStrategy",
148
+ )
145
149
 
146
150
 
147
151
  class EvaluationSet(BaseModel):
@@ -1,6 +1,7 @@
1
1
  import asyncio
2
2
  import json
3
3
  import logging
4
+ import os
4
5
  import uuid
5
6
  from collections import defaultdict
6
7
  from pathlib import Path
@@ -186,6 +187,13 @@ class UiPathEvalRuntime(UiPathBaseRuntime, Generic[T, C]):
186
187
  if self.context.eval_set is None:
187
188
  raise ValueError("eval_set must be provided for evaluation runs")
188
189
 
190
+ # Get entrypoint from a temporary runtime
191
+ temp_context = self.factory.new_context(
192
+ entrypoint=self.context.entrypoint, runtime_dir=os.getcwd()
193
+ )
194
+ temp_runtime = self.factory.from_context(temp_context)
195
+ self.entrypoint = await temp_runtime.get_entrypoint()
196
+
189
197
  event_bus = self.event_bus
190
198
 
191
199
  # Load eval set (path is already resolved in cli_eval.py)
@@ -330,6 +338,10 @@ class UiPathEvalRuntime(UiPathBaseRuntime, Generic[T, C]):
330
338
  evaluators: List[AnyEvaluator],
331
339
  event_bus: EventBus,
332
340
  ) -> EvaluationRunResult:
341
+ # Generate LLM-based input if input_mocking_strategy is defined
342
+ if eval_item.input_mocking_strategy:
343
+ eval_item = await self._generate_input_for_eval(eval_item)
344
+
333
345
  execution_id = str(uuid.uuid4())
334
346
 
335
347
  set_execution_context(eval_item, self.span_collector, execution_id)
@@ -462,12 +474,10 @@ class UiPathEvalRuntime(UiPathBaseRuntime, Generic[T, C]):
462
474
  return evaluation_run_results
463
475
 
464
476
  async def _generate_input_for_eval(
465
- self, eval_item: EvaluationItem
466
- ) -> EvaluationItem:
477
+ self, eval_item: AnyEvaluationItem
478
+ ) -> AnyEvaluationItem:
467
479
  """Use LLM to generate a mock input for an evaluation item."""
468
- # TODO(bai): get the input schema from agent definition, once it is available there.
469
- input_schema: dict[str, Any] = {}
470
- generated_input = await generate_llm_input(eval_item, input_schema)
480
+ generated_input = await generate_llm_input(eval_item, self.entrypoint.input)
471
481
  updated_eval_item = eval_item.model_copy(update={"inputs": generated_input})
472
482
  return updated_eval_item
473
483
 
@@ -5,7 +5,7 @@ from datetime import datetime
5
5
  from typing import Any, Dict
6
6
 
7
7
  from uipath import UiPath
8
- from uipath._cli._evals._models._evaluation_set import EvaluationItem
8
+ from uipath._cli._evals._models._evaluation_set import AnyEvaluationItem
9
9
  from uipath.tracing._traced import traced
10
10
 
11
11
  from .mocker import UiPathInputMockingError
@@ -54,20 +54,30 @@ OUTPUT: ONLY the simulated agent input in the exact format of the INPUT_SCHEMA i
54
54
 
55
55
  @traced(name="__mocker__")
56
56
  async def generate_llm_input(
57
- evaluation_item: EvaluationItem,
57
+ evaluation_item: AnyEvaluationItem,
58
58
  input_schema: Dict[str, Any],
59
59
  ) -> Dict[str, Any]:
60
60
  """Generate synthetic input using an LLM based on the evaluation context."""
61
61
  try:
62
62
  llm = UiPath().llm
63
63
 
64
+ # Ensure additionalProperties is set for strict mode compatibility
65
+ if "additionalProperties" not in input_schema:
66
+ input_schema["additionalProperties"] = False
67
+
68
+ expected_output = (
69
+ getattr(evaluation_item, "evaluation_criterias", None)
70
+ or getattr(evaluation_item, "expected_output", None)
71
+ or {}
72
+ )
73
+
64
74
  prompt = get_input_mocking_prompt(
65
75
  input_schema=json.dumps(input_schema, indent=2),
66
76
  input_generation_instructions=evaluation_item.input_mocking_strategy.prompt
67
77
  if evaluation_item.input_mocking_strategy
68
78
  else "",
69
79
  expected_behavior=evaluation_item.expected_agent_behavior or "",
70
- expected_output=json.dumps(evaluation_item.evaluation_criterias, indent=2),
80
+ expected_output=json.dumps(expected_output, indent=2),
71
81
  )
72
82
 
73
83
  response_format = {
@@ -382,7 +382,7 @@ class UiPathRuntimeContext(BaseModel):
382
382
  log_handler: Optional[logging.Handler] = None
383
383
  chat_handler: Optional[UiPathConversationHandler] = None
384
384
  is_conversational: Optional[bool] = None
385
- breakpoints: Optional[List[str]] = None
385
+ breakpoints: Optional[List[str] | Literal["*"]] = None
386
386
 
387
387
  model_config = {"arbitrary_types_allowed": True, "extra": "allow"}
388
388
 
@@ -127,19 +127,19 @@ Documents service
127
127
  # Create a validation action for a document based on the extraction response. More details about validation actions can be found in the [official documentation](https://docs.uipath.com/ixp/automation-cloud/latest/user-guide/validating-extractions).
128
128
  sdk.documents.create_validation_action(action_title: str, action_priority: <enum 'ActionPriority, action_catalog: str, action_folder: str, storage_bucket_name: str, storage_bucket_directory_path: str, extraction_response: uipath.models.documents.ExtractionResponse) -> uipath.models.documents.ValidationAction
129
129
 
130
- # Asynchronously create a validation action for a document based on the extraction response.
130
+ # Asynchronous version of the [`create_validation_action`][uipath._services.documents_service.DocumentsService.create_validation_action] method.
131
131
  sdk.documents.create_validation_action_async(action_title: str, action_priority: <enum 'ActionPriority, action_catalog: str, action_folder: str, storage_bucket_name: str, storage_bucket_directory_path: str, extraction_response: uipath.models.documents.ExtractionResponse) -> uipath.models.documents.ValidationAction
132
132
 
133
133
  # Extract predicted data from a document using an IXP project.
134
- sdk.documents.extract(project_name: str, tag: str, file: Union[IO[bytes], bytes, str, NoneType]=None, file_path: Optional[str]=None) -> uipath.models.documents.ExtractionResponse
134
+ sdk.documents.extract(project_name: str, tag: str, file: Union[IO[bytes], bytes, str, NoneType]=None, file_path: Optional[str]=None, project_type: <enum 'ProjectType="IXP", document_type_name: Optional[str]=None) -> typing.Union[uipath.models.documents.ExtractionResponse, uipath.models.documents.ExtractionResponseIXP]
135
135
 
136
- # Asynchronously extract predicted data from a document using an IXP project.
137
- sdk.documents.extract_async(project_name: str, tag: str, file: Union[IO[bytes], bytes, str, NoneType]=None, file_path: Optional[str]=None) -> uipath.models.documents.ExtractionResponse
136
+ # Asynchronously version of the [`extract`][uipath._services.documents_service.DocumentsService.extract] method.
137
+ sdk.documents.extract_async(project_name: str, tag: str, file: Union[IO[bytes], bytes, str, NoneType]=None, file_path: Optional[str]=None, project_type: <enum 'ProjectType="IXP", document_type_name: Optional[str]=None) -> typing.Union[uipath.models.documents.ExtractionResponse, uipath.models.documents.ExtractionResponseIXP]
138
138
 
139
139
  # Get the result of a validation action.
140
140
  sdk.documents.get_validation_result(validation_action: uipath.models.documents.ValidationAction) -> uipath.models.documents.ValidatedResult
141
141
 
142
- # Asynchronously get the result of a validation action.
142
+ # Asynchronous version of the [`get_validation_result`][uipath._services.documents_service.DocumentsService.get_validation_result] method.
143
143
  sdk.documents.get_validation_result_async(validation_action: uipath.models.documents.ValidationAction) -> uipath.models.documents.ValidatedResult
144
144
 
145
145
  ```