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

@@ -210,7 +210,7 @@ class StudioWebProgressReporter:
210
210
  }
211
211
 
212
212
  @gracefully_handle_errors
213
- async def create_eval_set_run(
213
+ async def create_eval_set_run_sw(
214
214
  self,
215
215
  eval_set_id: str,
216
216
  agent_snapshot: StudioWebAgentSnapshot,
@@ -352,13 +352,15 @@ class StudioWebProgressReporter:
352
352
  is_coded = self._is_coded_evaluator(payload.evaluators)
353
353
  self.is_coded_eval[payload.execution_id] = is_coded
354
354
 
355
- eval_set_run_id = await self.create_eval_set_run(
356
- eval_set_id=payload.eval_set_id,
357
- agent_snapshot=self._extract_agent_snapshot(payload.entrypoint),
358
- no_of_evals=payload.no_of_evals,
359
- evaluators=payload.evaluators,
360
- is_coded=is_coded,
361
- )
355
+ eval_set_run_id = payload.eval_set_run_id
356
+ if not eval_set_run_id:
357
+ eval_set_run_id = await self.create_eval_set_run_sw(
358
+ eval_set_id=payload.eval_set_id,
359
+ agent_snapshot=self._extract_agent_snapshot(payload.entrypoint),
360
+ no_of_evals=payload.no_of_evals,
361
+ evaluators=payload.evaluators,
362
+ is_coded=is_coded,
363
+ )
362
364
  self.eval_set_run_ids[payload.execution_id] = eval_set_run_id
363
365
  current_span = trace.get_current_span()
364
366
  if current_span.is_recording():
@@ -151,6 +151,7 @@ class UiPathEvalContext(UiPathRuntimeContext):
151
151
  workers: Optional[int] = 1
152
152
  eval_set: Optional[str] = None
153
153
  eval_ids: Optional[List[str]] = None
154
+ eval_set_run_id: Optional[str] = None
154
155
  verbose: bool = False
155
156
 
156
157
 
@@ -214,6 +215,7 @@ class UiPathEvalRuntime(UiPathBaseRuntime, Generic[T, C]):
214
215
  EvalSetRunCreatedEvent(
215
216
  execution_id=self.execution_id,
216
217
  entrypoint=self.context.entrypoint or "",
218
+ eval_set_run_id=self.context.eval_set_run_id,
217
219
  eval_set_id=evaluation_set.id,
218
220
  no_of_evals=len(evaluation_set.evaluations),
219
221
  evaluators=evaluators,
@@ -45,8 +45,6 @@ PYTHON_BINARY_EXTENSIONS = {".pickle", ".pkl"}
45
45
 
46
46
  SPECIAL_EXTENSIONS = {""} # Extensionless binary files
47
47
 
48
- UIPATH_PROJECT_ID = "UIPATH_PROJECT_ID"
49
-
50
48
  # Pre-compute the union for optimal performance
51
49
  BINARY_EXTENSIONS = (
52
50
  IMAGE_EXTENSIONS
uipath/_cli/cli_eval.py CHANGED
@@ -12,9 +12,9 @@ from uipath._cli._evals._runtime import (
12
12
  UiPathEvalContext,
13
13
  )
14
14
  from uipath._cli._runtime._runtime_factory import generate_runtime_factory
15
- from uipath._cli._utils._constants import UIPATH_PROJECT_ID
16
15
  from uipath._cli._utils._folders import get_personal_workspace_key_async
17
16
  from uipath._cli.middlewares import Middlewares
17
+ from uipath._config import UiPathConfig
18
18
  from uipath._events._event_bus import EventBus
19
19
  from uipath.eval._helpers import auto_discover_entrypoint
20
20
  from uipath.tracing import LlmOpsHttpExporter
@@ -39,12 +39,13 @@ def setup_reporting_prereq(no_report: bool) -> bool:
39
39
  if no_report:
40
40
  return False
41
41
 
42
- if not os.getenv(UIPATH_PROJECT_ID, False):
42
+ if not UiPathConfig.is_studio_project:
43
43
  console.warning(
44
44
  "UIPATH_PROJECT_ID environment variable not set. Results will no be reported to Studio Web."
45
45
  )
46
46
  return False
47
- if not os.getenv("UIPATH_FOLDER_KEY"):
47
+
48
+ if not UiPathConfig.folder_key:
48
49
  folder_key = asyncio.run(get_personal_workspace_key_async())
49
50
  if folder_key:
50
51
  os.environ["UIPATH_FOLDER_KEY"] = folder_key
@@ -55,6 +56,12 @@ def setup_reporting_prereq(no_report: bool) -> bool:
55
56
  @click.argument("entrypoint", required=False)
56
57
  @click.argument("eval_set", required=False)
57
58
  @click.option("--eval-ids", cls=LiteralOption, default="[]")
59
+ @click.option(
60
+ "--eval-set-run-id",
61
+ required=False,
62
+ type=str,
63
+ help="Custom evaluation set run ID (if not provided, a UUID will be generated)",
64
+ )
58
65
  @click.option(
59
66
  "--no-report",
60
67
  is_flag=True,
@@ -78,6 +85,7 @@ def eval(
78
85
  entrypoint: Optional[str],
79
86
  eval_set: Optional[str],
80
87
  eval_ids: List[str],
88
+ eval_set_run_id: Optional[str],
81
89
  no_report: bool,
82
90
  workers: int,
83
91
  output_file: Optional[str],
@@ -88,6 +96,7 @@ def eval(
88
96
  entrypoint: Path to the agent script to evaluate (optional, will auto-discover if not specified)
89
97
  eval_set: Path to the evaluation set JSON file (optional, will auto-discover if not specified)
90
98
  eval_ids: Optional list of evaluation IDs
99
+ eval_set_run_id: Custom evaluation set run ID (optional, will generate UUID if not specified)
91
100
  workers: Number of parallel workers for running evaluations
92
101
  no_report: Do not report the evaluation results
93
102
  """
@@ -95,6 +104,7 @@ def eval(
95
104
  "entrypoint": entrypoint or auto_discover_entrypoint(),
96
105
  "eval_set": eval_set,
97
106
  "eval_ids": eval_ids,
107
+ "eval_set_run_id": eval_set_run_id,
98
108
  "workers": workers,
99
109
  "no_report": no_report,
100
110
  "output_file": output_file,
@@ -130,6 +140,7 @@ def eval(
130
140
 
131
141
  eval_context.no_report = no_report
132
142
  eval_context.workers = workers
143
+ eval_context.eval_set_run_id = eval_set_run_id
133
144
 
134
145
  # Load eval set to resolve the path
135
146
  eval_set_path = eval_set or EvalHelpers.auto_discover_eval_set()
uipath/_cli/cli_init.py CHANGED
@@ -10,6 +10,7 @@ from typing import Any, Dict, Optional
10
10
 
11
11
  import click
12
12
 
13
+ from .._config import UiPathConfig
13
14
  from .._utils.constants import ENV_TELEMETRY_ENABLED
14
15
  from ..telemetry import track
15
16
  from ..telemetry._constants import _PROJECT_KEY, _TELEMETRY_CONFIG_FILE
@@ -43,9 +44,7 @@ def create_telemetry_config_file(target_directory: str) -> None:
43
44
  return
44
45
 
45
46
  os.makedirs(uipath_dir, exist_ok=True)
46
- telemetry_data = {
47
- _PROJECT_KEY: os.getenv("UIPATH_PROJECT_ID", None) or str(uuid.uuid4())
48
- }
47
+ telemetry_data = {_PROJECT_KEY: UiPathConfig.project_id or str(uuid.uuid4())}
49
48
 
50
49
  with open(telemetry_file, "w") as f:
51
50
  json.dump(telemetry_data, f, indent=4)
uipath/_cli/cli_pack.py CHANGED
@@ -8,8 +8,8 @@ from string import Template
8
8
  import click
9
9
  from pydantic import TypeAdapter
10
10
 
11
- from uipath._cli._utils._constants import UIPATH_PROJECT_ID
12
11
  from uipath._cli.models.runtime_schema import Bindings, RuntimeSchema
12
+ from uipath._config import UiPathConfig
13
13
 
14
14
  from ..telemetry import track
15
15
  from ..telemetry._constants import _PROJECT_KEY, _TELEMETRY_CONFIG_FILE
@@ -35,8 +35,8 @@ def get_project_id() -> str:
35
35
  Project ID string (either from telemetry file or newly generated).
36
36
  """
37
37
  # first check if this is a studio project
38
- if os.getenv(UIPATH_PROJECT_ID, None):
39
- return os.getenv(UIPATH_PROJECT_ID)
38
+ if project_id := UiPathConfig.project_id:
39
+ return project_id
40
40
 
41
41
  telemetry_file = os.path.join(".uipath", _TELEMETRY_CONFIG_FILE)
42
42
 
uipath/_cli/cli_pull.py CHANGED
@@ -11,14 +11,13 @@ It handles:
11
11
 
12
12
  # type: ignore
13
13
  import asyncio
14
- import os
15
14
  from pathlib import Path
16
15
 
17
16
  import click
18
17
 
18
+ from .._config import UiPathConfig
19
19
  from ..telemetry import track
20
20
  from ._utils._console import ConsoleLogger
21
- from ._utils._constants import UIPATH_PROJECT_ID
22
21
  from ._utils._project_files import ProjectPullError, pull_project
23
22
 
24
23
  console = ConsoleLogger()
@@ -49,10 +48,9 @@ def pull(root: Path) -> None:
49
48
  $ uipath pull
50
49
  $ uipath pull /path/to/project
51
50
  """
52
- project_id = os.getenv(UIPATH_PROJECT_ID)
51
+ project_id = UiPathConfig.project_id
53
52
  if not project_id:
54
53
  console.error("UIPATH_PROJECT_ID environment variable not found.")
55
- return
56
54
 
57
55
  download_configuration = {
58
56
  "source_code": root,
uipath/_cli/cli_push.py CHANGED
@@ -1,6 +1,5 @@
1
1
  # type: ignore
2
2
  import asyncio
3
- import os
4
3
  from typing import Any, AsyncIterator, Optional
5
4
  from urllib.parse import urlparse
6
5
 
@@ -8,12 +7,10 @@ import click
8
7
 
9
8
  from uipath.models.exceptions import EnrichedException
10
9
 
10
+ from .._config import UiPathConfig
11
11
  from ..telemetry import track
12
12
  from ._push.sw_file_handler import FileOperationUpdate, SwFileHandler
13
13
  from ._utils._console import ConsoleLogger
14
- from ._utils._constants import (
15
- UIPATH_PROJECT_ID,
16
- )
17
14
  from ._utils._project_files import (
18
15
  ensure_config_file,
19
16
  get_project_config,
@@ -99,7 +96,7 @@ def push(root: str, nolock: bool) -> None:
99
96
  config = get_project_config(root)
100
97
  validate_config(config)
101
98
 
102
- project_id = os.getenv(UIPATH_PROJECT_ID)
99
+ project_id = UiPathConfig.project_id
103
100
  if not project_id:
104
101
  console.error("UIPATH_PROJECT_ID environment variable not found.")
105
102
 
uipath/_config.py CHANGED
@@ -1,6 +1,44 @@
1
+ import os
2
+ from pathlib import Path
3
+ from typing import Optional
4
+
1
5
  from pydantic import BaseModel
2
6
 
3
7
 
4
8
  class Config(BaseModel):
5
9
  base_url: str
6
10
  secret: str
11
+
12
+
13
+ class ConfigurationManager:
14
+ _instance = None
15
+
16
+ def __new__(cls):
17
+ if cls._instance is None:
18
+ cls._instance = super().__new__(cls)
19
+ return cls._instance
20
+
21
+ @property
22
+ def bindings_file_path(self) -> Path:
23
+ from uipath._utils.constants import UIPATH_BINDINGS_FILE
24
+
25
+ return Path(UIPATH_BINDINGS_FILE)
26
+
27
+ @property
28
+ def project_id(self) -> Optional[str]:
29
+ from uipath._utils.constants import ENV_UIPATH_PROJECT_ID
30
+
31
+ return os.getenv(ENV_UIPATH_PROJECT_ID, None)
32
+
33
+ @property
34
+ def folder_key(self) -> Optional[str]:
35
+ from uipath._utils.constants import ENV_FOLDER_KEY
36
+
37
+ return os.getenv(ENV_FOLDER_KEY, None)
38
+
39
+ @property
40
+ def is_studio_project(self) -> bool:
41
+ return self.project_id is not None
42
+
43
+
44
+ UiPathConfig = ConfigurationManager()
uipath/_events/_events.py CHANGED
@@ -20,6 +20,7 @@ class EvalSetRunCreatedEvent(BaseModel):
20
20
  execution_id: str
21
21
  entrypoint: str
22
22
  eval_set_id: str
23
+ eval_set_run_id: Optional[str] = None
23
24
  no_of_evals: int
24
25
  # skip validation to avoid abstract class instantiation
25
26
  evaluators: SkipValidation[List[AnyEvaluator]]
@@ -100,6 +100,7 @@ uv run uipath run --resume
100
100
  entrypoint: Path to the agent script to evaluate (optional, will auto-discover if not specified)
101
101
  eval_set: Path to the evaluation set JSON file (optional, will auto-discover if not specified)
102
102
  eval_ids: Optional list of evaluation IDs
103
+ eval_set_run_id: Custom evaluation set run ID (optional, will generate UUID if not specified)
103
104
  workers: Number of parallel workers for running evaluations
104
105
  no_report: Do not report the evaluation results
105
106
 
@@ -115,6 +116,7 @@ uv run uipath run --resume
115
116
 
116
117
  | Option | Type | Default | Description |
117
118
  |--------|------|---------|-------------|
119
+ | `--eval-set-run-id` | value | `Sentinel.UNSET` | Custom evaluation set run ID (if not provided, a UUID will be generated) |
118
120
  | `--no-report` | flag | false | Do not report the evaluation results |
119
121
  | `--workers` | value | `1` | Number of parallel workers for running evaluations (default: 1) |
120
122
  | `--output-file` | value | `Sentinel.UNSET` | File path where the output will be written |
@@ -12,6 +12,7 @@ ENV_ROBOT_KEY = "UIPATH_ROBOT_KEY"
12
12
  ENV_TENANT_ID = "UIPATH_TENANT_ID"
13
13
  ENV_ORGANIZATION_ID = "UIPATH_ORGANIZATION_ID"
14
14
  ENV_TELEMETRY_ENABLED = "UIPATH_TELEMETRY_ENABLED"
15
+ ENV_UIPATH_PROJECT_ID = "UIPATH_PROJECT_ID"
15
16
 
16
17
  # Headers
17
18
  HEADER_FOLDER_KEY = "x-uipath-folderkey"
@@ -47,6 +48,7 @@ COMMUNITY_agents_SUFFIX = "-community-agents"
47
48
 
48
49
  # File names
49
50
  UIPATH_CONFIG_FILE = "uipath.json"
51
+ UIPATH_BINDINGS_FILE = "bindings.json"
50
52
 
51
53
  # Evaluators
52
54
  CUSTOM_EVALUATOR_PREFIX = "file://"
@@ -32,20 +32,25 @@ def _get_llm_messages(attributes: Dict[str, Any], prefix: str) -> List[Dict[str,
32
32
  """Extracts and reconstructs LLM messages from flattened attributes."""
33
33
  messages: dict[int, dict[str, Any]] = {}
34
34
  message_prefix = f"{prefix}."
35
+ prefix_len = len(message_prefix)
35
36
 
36
37
  for key, value in attributes.items():
37
38
  if key.startswith(message_prefix):
38
- parts = key[len(message_prefix) :].split(".")
39
+ # Avoid repeated string slicing and splits
40
+ parts = key[prefix_len:].split(".")
39
41
  if len(parts) >= 2 and parts[0].isdigit():
40
42
  index = int(parts[0])
41
43
  if index not in messages:
42
44
  messages[index] = {}
43
45
  current: Any = messages[index]
44
46
 
45
- for i, part in enumerate(parts[1:-1]):
47
+ # Traverse parts except the last one
48
+ parts_len = len(parts)
49
+ for i in range(1, parts_len - 1):
50
+ part = parts[i]
46
51
  key_part: str | int = part
47
52
  if part.isdigit() and (
48
- i + 2 < len(parts) and parts[i + 2].isdigit()
53
+ i + 2 < parts_len and parts[i + 2].isdigit()
49
54
  ):
50
55
  key_part = int(part)
51
56
 
@@ -60,6 +65,10 @@ def _get_llm_messages(attributes: Dict[str, Any], prefix: str) -> List[Dict[str,
60
65
 
61
66
  current[parts[-1]] = value
62
67
 
68
+ # Convert dict to list, ordered by index, avoid sorted() if we can use range
69
+ if not messages:
70
+ return []
71
+
63
72
  # Convert dict to list, ordered by index
64
73
  return [messages[i] for i in sorted(messages.keys())]
65
74
 
@@ -112,18 +121,30 @@ class LlmOpsHttpExporter(SpanExporter):
112
121
  f"Exporting {len(spans)} spans to {self.base_url}/llmopstenant_/api/Traces/spans"
113
122
  )
114
123
 
124
+ # Use optimized path: keep attributes as dict for processing
125
+ # Only serialize at the very end
115
126
  span_list = [
116
127
  _SpanUtils.otel_span_to_uipath_span(
117
- span, custom_trace_id=self.trace_id
118
- ).to_dict()
128
+ span, custom_trace_id=self.trace_id, serialize_attributes=False
129
+ ).to_dict(serialize_attributes=False)
119
130
  for span in spans
120
131
  ]
132
+
121
133
  url = self._build_url(span_list)
122
134
 
135
+ # Process spans in-place if needed - work directly with dict
123
136
  if self._extra_process_spans:
124
- span_list = [self._process_span_attributes(span) for span in span_list]
137
+ for span_data in span_list:
138
+ self._process_span_attributes(span_data)
125
139
 
126
- logger.debug("Payload: %s", json.dumps(span_list))
140
+ # Serialize attributes once at the very end
141
+ for span_data in span_list:
142
+ if isinstance(span_data.get("Attributes"), dict):
143
+ span_data["Attributes"] = json.dumps(span_data["Attributes"])
144
+
145
+ # Only serialize for logging if debug is enabled to avoid allocation
146
+ if logger.isEnabledFor(logging.DEBUG):
147
+ logger.debug("Payload: %s", json.dumps(span_list))
127
148
 
128
149
  return self._send_with_retries(url, span_list)
129
150
 
@@ -133,7 +154,8 @@ class LlmOpsHttpExporter(SpanExporter):
133
154
 
134
155
  def _map_llm_call_attributes(self, attributes: Dict[str, Any]) -> Dict[str, Any]:
135
156
  """Maps attributes for LLM calls, handling flattened keys."""
136
- result = attributes.copy() # Keep original attributes including basic mappings
157
+ # Modify attributes in place to avoid copy
158
+ result = attributes
137
159
 
138
160
  # Token Usage
139
161
  token_keys = {
@@ -202,7 +224,8 @@ class LlmOpsHttpExporter(SpanExporter):
202
224
 
203
225
  def _map_tool_call_attributes(self, attributes: Dict[str, Any]) -> Dict[str, Any]:
204
226
  """Maps attributes for tool calls."""
205
- result = attributes.copy() # Keep original attributes
227
+ # Modify attributes in place to avoid copy
228
+ result = attributes
206
229
 
207
230
  result["type"] = "toolCall"
208
231
  result["callId"] = attributes.get("call_id") or attributes.get("id")
@@ -225,22 +248,32 @@ class LlmOpsHttpExporter(SpanExporter):
225
248
  return self.Status.ERROR
226
249
  return self.Status.SUCCESS
227
250
 
228
- def _process_span_attributes(self, span_data: Dict[str, Any]) -> Dict[str, Any]:
229
- """Extracts, transforms, and maps attributes for a span."""
251
+ def _process_span_attributes(self, span_data: Dict[str, Any]) -> None:
252
+ """Extracts, transforms, and maps attributes for a span in-place.
253
+
254
+ Args:
255
+ span_data: Span dict with Attributes as dict or JSON string
256
+
257
+ Note:
258
+ Modifies span_data in-place. When optimized path is used (dict),
259
+ modifies dict directly. When legacy path is used (str), parse → modify → serialize.
260
+ """
230
261
  if "Attributes" not in span_data:
231
- return span_data
262
+ return
232
263
 
233
264
  attributes_val = span_data["Attributes"]
234
265
  if isinstance(attributes_val, str):
266
+ # Legacy path: parse JSON string
235
267
  try:
236
268
  attributes: Dict[str, Any] = json.loads(attributes_val)
237
269
  except json.JSONDecodeError as e:
238
270
  logger.warning(f"Failed to parse attributes JSON: {e}")
239
- return span_data
271
+ return
240
272
  elif isinstance(attributes_val, dict):
273
+ # Optimized path: work directly with dict
241
274
  attributes = attributes_val
242
275
  else:
243
- return span_data
276
+ return
244
277
 
245
278
  # Determine SpanType
246
279
  if "openinference.span.kind" in attributes:
@@ -258,23 +291,23 @@ class LlmOpsHttpExporter(SpanExporter):
258
291
  attributes[new_key] = attributes[old_key]
259
292
 
260
293
  # Apply detailed mapping based on SpanType
294
+ # Modify attributes dict in place to avoid allocations
261
295
  span_type = span_data.get("SpanType")
262
296
  if span_type == "completion":
263
- processed_attributes = self._map_llm_call_attributes(attributes)
297
+ self._map_llm_call_attributes(attributes)
264
298
  elif span_type == "toolCall":
265
- processed_attributes = self._map_tool_call_attributes(attributes)
266
- else:
267
- processed_attributes = attributes.copy()
299
+ self._map_tool_call_attributes(attributes)
268
300
 
269
- span_data["Attributes"] = json.dumps(processed_attributes)
301
+ # If attributes were a string (legacy path), serialize back
302
+ # If dict (optimized path), leave as dict - caller will serialize once at the end
303
+ if isinstance(attributes_val, str):
304
+ span_data["Attributes"] = json.dumps(attributes)
270
305
 
271
306
  # Determine status based on error information
272
307
  error = attributes.get("error") or attributes.get("exception.message")
273
308
  status = self._determine_status(error)
274
309
  span_data["Status"] = status
275
310
 
276
- return span_data
277
-
278
311
  def _build_url(self, span_list: list[Dict[str, Any]]) -> str:
279
312
  """Construct the URL for the API request."""
280
313
  trace_id = str(span_list[0]["TraceId"])
@@ -323,7 +356,10 @@ class JsonLinesFileExporter(SpanExporter):
323
356
  def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
324
357
  try:
325
358
  uipath_spans = [
326
- _SpanUtils.otel_span_to_uipath_span(span).to_dict() for span in spans
359
+ _SpanUtils.otel_span_to_uipath_span(
360
+ span, serialize_attributes=True
361
+ ).to_dict(serialize_attributes=True)
362
+ for span in spans
327
363
  ]
328
364
 
329
365
  with open(self.file_path, "a") as f:
uipath/tracing/_utils.py CHANGED
@@ -63,12 +63,15 @@ def _simple_serialize_defaults(obj):
63
63
 
64
64
  @dataclass
65
65
  class UiPathSpan:
66
- """Represents a span in the UiPath tracing system."""
66
+ """Represents a span in the UiPath tracing system.
67
+
68
+ Note: attributes can be either a JSON string (backwards compatible) or a dict (optimized).
69
+ """
67
70
 
68
71
  id: uuid.UUID
69
72
  trace_id: uuid.UUID
70
73
  name: str
71
- attributes: str
74
+ attributes: str | Dict[str, Any] # Support both str (legacy) and dict (optimized)
72
75
  parent_id: Optional[uuid.UUID] = None
73
76
  start_time: str = field(default_factory=lambda: datetime.now().isoformat())
74
77
  end_time: str = field(default_factory=lambda: datetime.now().isoformat())
@@ -96,16 +99,32 @@ class UiPathSpan:
96
99
 
97
100
  job_key: Optional[str] = field(default_factory=lambda: env.get("UIPATH_JOB_KEY"))
98
101
 
99
- def to_dict(self) -> Dict[str, Any]:
100
- """Convert the Span to a dictionary suitable for JSON serialization."""
102
+ def to_dict(self, serialize_attributes: bool = True) -> Dict[str, Any]:
103
+ """Convert the Span to a dictionary suitable for JSON serialization.
104
+
105
+ Args:
106
+ serialize_attributes: If True and attributes is a dict, serialize to JSON string.
107
+ If False, keep attributes as-is (dict or str).
108
+ Default True for backwards compatibility.
109
+ """
110
+ # Cache UUID string conversions to avoid repeated str() calls
111
+ id_str = str(self.id)
112
+ trace_id_str = str(self.trace_id)
113
+ parent_id_str = str(self.parent_id) if self.parent_id else None
114
+
115
+ # Handle attributes serialization
116
+ attributes_out = self.attributes
117
+ if serialize_attributes and isinstance(self.attributes, dict):
118
+ attributes_out = json.dumps(self.attributes)
119
+
101
120
  return {
102
- "Id": str(self.id),
103
- "TraceId": str(self.trace_id),
104
- "ParentId": str(self.parent_id) if self.parent_id else None,
121
+ "Id": id_str,
122
+ "TraceId": trace_id_str,
123
+ "ParentId": parent_id_str,
105
124
  "Name": self.name,
106
125
  "StartTime": self.start_time,
107
126
  "EndTime": self.end_time,
108
- "Attributes": self.attributes,
127
+ "Attributes": attributes_out,
109
128
  "Status": self.status,
110
129
  "CreatedAt": self.created_at,
111
130
  "UpdatedAt": self.updated_at,
@@ -168,9 +187,18 @@ class _SpanUtils:
168
187
 
169
188
  @staticmethod
170
189
  def otel_span_to_uipath_span(
171
- otel_span: ReadableSpan, custom_trace_id: Optional[str] = None
190
+ otel_span: ReadableSpan,
191
+ custom_trace_id: Optional[str] = None,
192
+ serialize_attributes: bool = True,
172
193
  ) -> UiPathSpan:
173
- """Convert an OpenTelemetry span to a UiPathSpan."""
194
+ """Convert an OpenTelemetry span to a UiPathSpan.
195
+
196
+ Args:
197
+ otel_span: The OpenTelemetry span to convert
198
+ custom_trace_id: Optional custom trace ID to use
199
+ serialize_attributes: If True, serialize attributes to JSON string (backwards compatible).
200
+ If False, keep as dict for optimized processing. Default True.
201
+ """
174
202
  # Extract the context information from the OTel span
175
203
  span_context = otel_span.get_span_context()
176
204
 
@@ -192,10 +220,11 @@ class _SpanUtils:
192
220
  if parent_span_id_str:
193
221
  parent_id = uuid.UUID(parent_span_id_str)
194
222
 
195
- # Convert attributes to a format compatible with UiPathSpan
196
- attributes_dict: dict[str, Any] = (
197
- dict(otel_span.attributes) if otel_span.attributes else {}
198
- )
223
+ # Build attributes dict efficiently
224
+ # Use the otel attributes as base - we only add new keys, don't modify existing
225
+ otel_attrs = otel_span.attributes if otel_span.attributes else {}
226
+ # Only copy if we need to modify - we'll build attributes_dict lazily
227
+ attributes_dict: dict[str, Any] = dict(otel_attrs) if otel_attrs else {}
199
228
 
200
229
  # Map status
201
230
  status = 1 # Default to OK
@@ -203,32 +232,31 @@ class _SpanUtils:
203
232
  status = 2 # Error
204
233
  attributes_dict["error"] = otel_span.status.description
205
234
 
206
- original_inputs = attributes_dict.get("input", None)
207
- original_outputs = attributes_dict.get("output", None)
208
-
235
+ # Process inputs - avoid redundant parsing if already parsed
236
+ original_inputs = otel_attrs.get("input", None)
209
237
  if original_inputs:
210
- try:
211
- if isinstance(original_inputs, str):
212
- json_inputs = json.loads(original_inputs)
213
- attributes_dict["input.value"] = json_inputs
238
+ if isinstance(original_inputs, str):
239
+ try:
240
+ attributes_dict["input.value"] = json.loads(original_inputs)
214
241
  attributes_dict["input.mime_type"] = "application/json"
215
- else:
242
+ except Exception as e:
243
+ logger.warning(f"Error parsing inputs: {e}")
216
244
  attributes_dict["input.value"] = original_inputs
217
- except Exception as e:
218
- logger.warning(f"Error parsing inputs: {e}")
219
- attributes_dict["input.value"] = str(original_inputs)
245
+ else:
246
+ attributes_dict["input.value"] = original_inputs
220
247
 
248
+ # Process outputs - avoid redundant parsing if already parsed
249
+ original_outputs = otel_attrs.get("output", None)
221
250
  if original_outputs:
222
- try:
223
- if isinstance(original_outputs, str):
224
- json_outputs = json.loads(original_outputs)
225
- attributes_dict["output.value"] = json_outputs
251
+ if isinstance(original_outputs, str):
252
+ try:
253
+ attributes_dict["output.value"] = json.loads(original_outputs)
226
254
  attributes_dict["output.mime_type"] = "application/json"
227
- else:
255
+ except Exception as e:
256
+ logger.warning(f"Error parsing output: {e}")
228
257
  attributes_dict["output.value"] = original_outputs
229
- except Exception as e:
230
- logger.warning(f"Error parsing output: {e}")
231
- attributes_dict["output.value"] = str(original_outputs)
258
+ else:
259
+ attributes_dict["output.value"] = original_outputs
232
260
 
233
261
  # Add events as additional attributes if they exist
234
262
  if otel_span.events:
@@ -275,7 +303,9 @@ class _SpanUtils:
275
303
  trace_id=trace_id,
276
304
  parent_id=parent_id,
277
305
  name=otel_span.name,
278
- attributes=json.dumps(attributes_dict),
306
+ attributes=json.dumps(attributes_dict)
307
+ if serialize_attributes
308
+ else attributes_dict,
279
309
  start_time=start_time,
280
310
  end_time=end_time_str,
281
311
  status=status,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: uipath
3
- Version: 2.1.119
3
+ Version: 2.1.121
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
@@ -1,5 +1,5 @@
1
1
  uipath/__init__.py,sha256=IaeKItOOQXMa95avueJ3dAq-XcRHyZVNjcCGwlSB000,634
2
- uipath/_config.py,sha256=pi3qxPzDTxMEstj_XkGOgKJqD6RTHHv7vYv8sS_-d5Q,92
2
+ uipath/_config.py,sha256=YEiLmpHeeT2K_CinSVWr9aIWU6txfkgAUPP-jqxa9R0,990
3
3
  uipath/_execution_context.py,sha256=Qo8VMUFgtiL-40KsZrvul5bGv1CRERle_fCw1ORCggY,2374
4
4
  uipath/_folder_context.py,sha256=D-bgxdwpwJP4b_QdVKcPODYh15kMDrOar2xNonmMSm4,1861
5
5
  uipath/_uipath.py,sha256=ycu11bjIUx5priRkB3xSRcilugHuSmfSN4Nq6Yz88gk,3702
@@ -11,14 +11,14 @@ uipath/_cli/cli_auth.py,sha256=CzetSRqSUvMs02PtI4w5Vi_0fv_ETA307bB2vXalWzY,2628
11
11
  uipath/_cli/cli_debug.py,sha256=-s6Nmy0DnDyITjZAf6f71hZ1YDDt0Yl57XklEkuL0FU,4068
12
12
  uipath/_cli/cli_deploy.py,sha256=KPCmQ0c_NYD5JofSDao5r6QYxHshVCRxlWDVnQvlp5w,645
13
13
  uipath/_cli/cli_dev.py,sha256=nEfpjw1PZ72O6jmufYWVrueVwihFxDPOeJakdvNHdOA,2146
14
- uipath/_cli/cli_eval.py,sha256=44DLzz4NDpjgqtYf-YTk4FulAF_BI4i3mCNZV0o3Otk,5037
15
- uipath/_cli/cli_init.py,sha256=AXLZyN_PJju6BmHZNcgTx3tO-la60fUKQNJDIj_jgyo,7537
14
+ uipath/_cli/cli_eval.py,sha256=odAZtE8jfT9Yl3UyJgN6WJJcImt32iSJyi_uw1BTUtg,5404
15
+ uipath/_cli/cli_init.py,sha256=39sQnIHol46D5ce4MBJRrD_8KOqvK1IyYhgn42_XYIM,7545
16
16
  uipath/_cli/cli_invoke.py,sha256=m-te-EjhDpk_fhFDkt-yQFzmjEHGo5lQDGEQWxSXisQ,4395
17
17
  uipath/_cli/cli_new.py,sha256=9378NYUBc9j-qKVXV7oja-jahfJhXBg8zKVyaon7ctY,2102
18
- uipath/_cli/cli_pack.py,sha256=U5rXVbUnHFgdEsXyhkjmWza8dfob1wU9lyl4yrYnUss,11076
18
+ uipath/_cli/cli_pack.py,sha256=7mJgZahtpKM_4ZZEA3WNv61eJJeRwNv87FjBPmXRJ3c,11041
19
19
  uipath/_cli/cli_publish.py,sha256=DgyfcZjvfV05Ldy0Pk5y_Le_nT9JduEE_x-VpIc_Kq0,6471
20
- uipath/_cli/cli_pull.py,sha256=YKUXp5mt_WwLKhqCPv2JcSNVagbICd196MWOzzLHZck,2161
21
- uipath/_cli/cli_push.py,sha256=irOh4Bi5LDWgGN6kHd2KwBVTByG0_fq2RUp5hJSzNz0,3781
20
+ uipath/_cli/cli_pull.py,sha256=nV-OCdue_7C6x4f8jRoTYSnM-BgsWVsZUVibkgnogDU,2117
21
+ uipath/_cli/cli_push.py,sha256=5J-dhDilsYrlYg1aV1eWOU84QC_PlwTi9tI3UZptg1k,3743
22
22
  uipath/_cli/cli_register.py,sha256=5-Asb8DSTR4W6M3TDi4U-AKXYOCZ3l2vcTuMOybDHEo,1465
23
23
  uipath/_cli/cli_run.py,sha256=9vbmSG_FzS0x8KNNzSrST37FKjUOzhXPjl8xTm2ZEqQ,4203
24
24
  uipath/_cli/middlewares.py,sha256=tb0c4sU1SCYi0PNs956Qmk24NDk0C0mBfVQmTcyORE0,5000
@@ -54,8 +54,8 @@ uipath/_cli/_evals/_console_progress_reporter.py,sha256=RlfhtyEHq2QjyXRevyeAhtGT
54
54
  uipath/_cli/_evals/_evaluate.py,sha256=yRVhZ6uV58EV5Fv5X_K6425ZGsseQslnLe6FpIKy-u8,833
55
55
  uipath/_cli/_evals/_evaluator_factory.py,sha256=gPF9fRMZBOUPnJSM1fzQyXGHMGYQw_0VmHv-JOGbZf4,14348
56
56
  uipath/_cli/_evals/_helpers.py,sha256=dYHgkWxy2fOuqqZDtOKWKsZ1Ri4dn8qMnuB6DE-1MUk,6661
57
- uipath/_cli/_evals/_progress_reporter.py,sha256=qWMJZskfEjHOLKLL7FqhpraFaYAWpcqO2YFV7oAyBDY,33191
58
- uipath/_cli/_evals/_runtime.py,sha256=rE2cD7_axKgKuwb2IxLvT0AwvyO-PQUHS-YU1Pg_Hys,25604
57
+ uipath/_cli/_evals/_progress_reporter.py,sha256=MbN6QWCl2o6ydmmG-bjf7pPy9guA3bODar6iBettp6k,33315
58
+ uipath/_cli/_evals/_runtime.py,sha256=Gy8O0UJxtCbHwVWdHzhi8u4gAEAJnx891_HSzgPkTCk,25708
59
59
  uipath/_cli/_evals/_span_collection.py,sha256=RoKoeDFG2XODdlgI27ionCjU7LLD_C0LJJ3gu0wab10,779
60
60
  uipath/_cli/_evals/_models/_evaluation_set.py,sha256=7P6zIkgerGKHXL6rD1YHXFFWpyxCUpNu7AX71bAaNoE,7270
61
61
  uipath/_cli/_evals/_models/_evaluator.py,sha256=UXrN103gHJFw3MtVWlGwViQWAo2cICRR-n357zL6wTA,9369
@@ -88,7 +88,7 @@ uipath/_cli/_templates/main.py.template,sha256=QB62qX5HKDbW4lFskxj7h9uuxBITnTWqu
88
88
  uipath/_cli/_templates/package.nuspec.template,sha256=YZyLc-u_EsmIoKf42JsLQ55OGeFmb8VkIU2VF7DFbtw,359
89
89
  uipath/_cli/_utils/_common.py,sha256=fSZkps1sjOXRyWn8GqmQfGqYAZ_tVua6dgT18Lpscds,3467
90
90
  uipath/_cli/_utils/_console.py,sha256=scvnrrFoFX6CE451K-PXKV7UN0DUkInbOtDZ5jAdPP0,10070
91
- uipath/_cli/_utils/_constants.py,sha256=rS8lQ5Nzull8ytajK6lBsz398qiCp1REoAwlHtyBwF0,1415
91
+ uipath/_cli/_utils/_constants.py,sha256=AXeVidtHUFiODrkB2BCX_bqDL-bUzRg-Ieh1-2cCrGA,1374
92
92
  uipath/_cli/_utils/_debug.py,sha256=zamzIR4VgbdKADAE4gbmjxDsbgF7wvdr7C5Dqp744Oc,1739
93
93
  uipath/_cli/_utils/_eval_set.py,sha256=QsAtF_K0sKTn_5lcOnhmWbTrGpmlFn0HV7wG3mSuASU,3771
94
94
  uipath/_cli/_utils/_folders.py,sha256=RsYrXzF0NA1sPxgBoLkLlUY3jDNLg1V-Y8j71Q8a8HY,1357
@@ -104,10 +104,10 @@ uipath/_cli/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
104
104
  uipath/_cli/models/runtime_schema.py,sha256=Po1SYFwTBlWZdmwIG2GvFy0WYbZnT5U1aGjfWcd8ZAA,2181
105
105
  uipath/_events/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
106
106
  uipath/_events/_event_bus.py,sha256=4-VzstyX69cr7wT1EY7ywp-Ndyz2CyemD3Wk_-QmRpo,5496
107
- uipath/_events/_events.py,sha256=S-3zQiJM6y0pKysXhxQQciNaGcnqJqRw5PrRGgFEKn4,4565
107
+ uipath/_events/_events.py,sha256=ShRoV_ARbJiDFFy0tHUQiC61V_CDzGhA6uCC9KpuUH4,4607
108
108
  uipath/_resources/AGENTS.md,sha256=nRQNAVeEBaBvuMzXw8uXtMnGebLClUgwIMlgb8_qU9o,1039
109
109
  uipath/_resources/CLAUDE.md,sha256=kYsckFWTVe948z_fNWLysCHvi9_YpchBXl3s1Ek03lU,10
110
- uipath/_resources/CLI_REFERENCE.md,sha256=v6pVR2jbBP7hTXwAwi3Ia5C96BfDKs4xGCCUOmsbEBM,6530
110
+ uipath/_resources/CLI_REFERENCE.md,sha256=PNVZINTXDSW4XN8QtxV3kS2WLreR7UyLfSso1_VXWBg,6758
111
111
  uipath/_resources/REQUIRED_STRUCTURE.md,sha256=3laqGiNa3kauJ7jRI1d7w_fWKUDkqYBjcTT_6_8FAGk,1417
112
112
  uipath/_resources/SDK_REFERENCE.md,sha256=lvQxxdsHi-kzur2C1L4fJ6cEvjVdJPUNSX8UMofgqUQ,19485
113
113
  uipath/_services/__init__.py,sha256=_LNy4u--VlhVtTO66bULbCoBjyJBTuyh9jnzjWrv-h4,1140
@@ -139,7 +139,7 @@ uipath/_utils/_request_spec.py,sha256=iCtBLqtbWUpFG5g1wtIZBzSupKsfaRLiQFoFc_4B70
139
139
  uipath/_utils/_ssl_context.py,sha256=xSYitos0eJc9cPHzNtHISX9PBvL6D2vas5G_GiBdLp8,1783
140
140
  uipath/_utils/_url.py,sha256=-4eluSrIZCUlnQ3qU17WPJkgaC2KwF9W5NeqGnTNGGo,2512
141
141
  uipath/_utils/_user_agent.py,sha256=pVJkFYacGwaQBomfwWVAvBQgdBUo62e4n3-fLIajWUU,563
142
- uipath/_utils/constants.py,sha256=eUEER5NQuX1_h5uum2xHEJPrNfP0YGQrSV80fFYXEh0,1820
142
+ uipath/_utils/constants.py,sha256=qsnkTO7cw7VaN0sYM7q9-YJjHi1W6IT6TiJ2YCtcbIE,1903
143
143
  uipath/agent/_utils.py,sha256=OwSwpTxhZSAeyofasWwckE07qfMDCHuk8bX6A_ZXDbo,5287
144
144
  uipath/agent/conversation/__init__.py,sha256=5hK-Iz131mnd9m6ANnpZZffxXZLVFDQ9GTg5z9ik1oQ,5265
145
145
  uipath/agent/conversation/async_stream.py,sha256=BA_8uU1DgE3VpU2KkJj0rkI3bAHLk_ZJKsajR0ipMpo,2055
@@ -219,14 +219,14 @@ uipath/telemetry/__init__.py,sha256=Wna32UFzZR66D-RzTKlPWlvji9i2HJb82NhHjCCXRjY,
219
219
  uipath/telemetry/_constants.py,sha256=uRDuEZayBYtBA0tMx-2AS_D-oiVA7oKgp9zid9jNats,763
220
220
  uipath/telemetry/_track.py,sha256=3RZgJtY8y28Y5rfVmC432OyRu7N3pSxPouwa82KWFso,4787
221
221
  uipath/tracing/__init__.py,sha256=0oUuxJKOHE14iOL4SP93FOiEYRIFLTq-o0NwRcTB8Q4,317
222
- uipath/tracing/_otel_exporters.py,sha256=LAtaQV5QN4PSLgEbIVvlmUETNbMYqTX87R-3mRKy_S8,12438
222
+ uipath/tracing/_otel_exporters.py,sha256=68wuAZyB_PScnSCW230PVs3qSqoJBNoArJJaE4UebNA,13956
223
223
  uipath/tracing/_traced.py,sha256=yBIY05PCCrYyx50EIHZnwJaKNdHPNx-YTR1sHQl0a98,19901
224
- uipath/tracing/_utils.py,sha256=zMjiKjNpSN3YQNEU4-u5AAvPtUsi8QuEqNLya89jfAU,14466
224
+ uipath/tracing/_utils.py,sha256=59XSCaH4Te9Jo3cqY8Wb2cNu9gPfVm8AMMSAg2OTXWo,16018
225
225
  uipath/utils/__init__.py,sha256=VD-KXFpF_oWexFg6zyiWMkxl2HM4hYJMIUDZ1UEtGx0,105
226
226
  uipath/utils/_endpoints_manager.py,sha256=tnF_FiCx8qI2XaJDQgYkMN_gl9V0VqNR1uX7iawuLp8,8230
227
227
  uipath/utils/dynamic_schema.py,sha256=w0u_54MoeIAB-mf3GmwX1A_X8_HDrRy6p998PvX9evY,3839
228
- uipath-2.1.119.dist-info/METADATA,sha256=3bFAJBRf1iXbqhxg8hQUgj3ggLWI6Sptx_R-iQbunbM,6626
229
- uipath-2.1.119.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
230
- uipath-2.1.119.dist-info/entry_points.txt,sha256=9C2_29U6Oq1ExFu7usihR-dnfIVNSKc-0EFbh0rskB4,43
231
- uipath-2.1.119.dist-info/licenses/LICENSE,sha256=-KBavWXepyDjimmzH5fVAsi-6jNVpIKFc2kZs0Ri4ng,1058
232
- uipath-2.1.119.dist-info/RECORD,,
228
+ uipath-2.1.121.dist-info/METADATA,sha256=5MIPaAAsfFBU2ncAQrZJFW0QpfhYRAKV3JLNEHM0X6w,6626
229
+ uipath-2.1.121.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
230
+ uipath-2.1.121.dist-info/entry_points.txt,sha256=9C2_29U6Oq1ExFu7usihR-dnfIVNSKc-0EFbh0rskB4,43
231
+ uipath-2.1.121.dist-info/licenses/LICENSE,sha256=-KBavWXepyDjimmzH5fVAsi-6jNVpIKFc2kZs0Ri4ng,1058
232
+ uipath-2.1.121.dist-info/RECORD,,