Skip to content

Commit fea6d4e

Browse files
authored
Merge branch 'main' into feat/memory-service-scaffold
2 parents 38291b4 + b8dba25 commit fea6d4e

64 files changed

Lines changed: 7572 additions & 1080 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/publish-docs.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ on:
99
- "packages/uipath/docs/**"
1010
- "packages/uipath/mkdocs.yml"
1111
- "packages/uipath/pyproject.toml"
12+
- "packages/uipath-platform/src/**"
13+
- "packages/uipath-platform/pyproject.toml"
14+
- "packages/uipath-core/src/**"
15+
- "packages/uipath-core/pyproject.toml"
1216
repository_dispatch:
1317
types: [publish-docs]
1418

packages/uipath-core/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-core"
3-
version = "0.5.10"
3+
version = "0.5.11"
44
description = "UiPath Core abstractions"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

packages/uipath-core/src/uipath/core/guardrails/guardrails.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ class BaseGuardrail(BaseModel):
227227
name: str
228228
description: str | None = None
229229
enabled_for_evals: bool = Field(True, alias="enabledForEvals")
230-
selector: GuardrailSelector
230+
selector: GuardrailSelector | None = None
231231

232232
model_config = ConfigDict(populate_by_name=True, extra="allow")
233233

packages/uipath-core/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/uipath-platform/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-platform"
3-
version = "0.1.16"
3+
version = "0.1.31"
44
description = "HTTP client library for programmatic access to UiPath Platform"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

packages/uipath-platform/src/uipath/platform/_uipath.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,9 @@ def llm(self) -> UiPathLlmChatService:
144144

145145
@property
146146
def entities(self) -> EntitiesService:
147-
return EntitiesService(self._config, self._execution_context)
147+
return EntitiesService(
148+
self._config, self._execution_context, folders_service=self.folders
149+
)
148150

149151
@cached_property
150152
def resource_catalog(self) -> ResourceCatalogService:

packages/uipath-platform/src/uipath/platform/common/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from ._base_service import BaseService
88
from ._bindings import (
99
ConnectionResourceOverwrite,
10+
EntityResourceOverwrite,
1011
GenericResourceOverwrite,
1112
ResourceOverwrite,
1213
ResourceOverwriteParser,
@@ -100,6 +101,7 @@
100101
"EndpointManager",
101102
"jsonschema_to_pydantic",
102103
"ConnectionResourceOverwrite",
104+
"EntityResourceOverwrite",
103105
"GenericResourceOverwrite",
104106
"ResourceOverwrite",
105107
"ResourceOverwriteParser",

packages/uipath-platform/src/uipath/platform/common/_base_service.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
HTTPStatusError,
1212
Response,
1313
)
14+
from opentelemetry import trace
15+
from opentelemetry.trace import format_span_id, format_trace_id
1416
from tenacity import (
1517
retry,
1618
retry_if_exception,
@@ -61,6 +63,38 @@ def _get_caller_component() -> str:
6163
return ""
6264

6365

66+
_TRACE_PARENT_HEADER = "x-uipath-traceparent-id"
67+
68+
69+
def _inject_trace_context(headers: dict[str, str]) -> None:
70+
"""Inject UiPath trace context header.
71+
72+
Trace ID: uses the agent trace ID from UIPATH_TRACE_ID env var (same
73+
remapping the LLMOps exporter applies), falling back to the OTEL trace ID.
74+
Span ID: uses the LLMOps tool span (via external span provider) so the
75+
span ID matches what's visible in the LLMOps trace UI.
76+
"""
77+
from uipath.core.tracing.span_utils import UiPathSpanUtils
78+
79+
from ._config import UiPathConfig
80+
from ._span_utils import _SpanUtils
81+
82+
llmops_span = UiPathSpanUtils.get_external_current_span()
83+
span = llmops_span or trace.get_current_span()
84+
ctx = span.get_span_context()
85+
if not (ctx.trace_id and ctx.span_id):
86+
return
87+
88+
config_trace_id = UiPathConfig.trace_id
89+
trace_id = (
90+
_SpanUtils.normalize_trace_id(config_trace_id)
91+
if config_trace_id
92+
else format_trace_id(ctx.trace_id)
93+
)
94+
span_id = format_span_id(ctx.span_id)
95+
headers[_TRACE_PARENT_HEADER] = f"00-{trace_id}-{span_id}-01"
96+
97+
6498
class BaseService:
6599
def __init__(
66100
self, config: UiPathApiConfig, execution_context: UiPathExecutionContext
@@ -106,6 +140,7 @@ def request(
106140

107141
kwargs.setdefault("headers", {})
108142
kwargs["headers"][HEADER_USER_AGENT] = user_agent_value(specific_component)
143+
_inject_trace_context(kwargs["headers"])
109144

110145
override = resolve_service_url(str(url))
111146
if override:
@@ -150,6 +185,7 @@ async def request_async(
150185
kwargs["headers"][HEADER_USER_AGENT] = user_agent_value(
151186
self._specific_component
152187
)
188+
_inject_trace_context(kwargs["headers"])
153189

154190
override = resolve_service_url(str(url))
155191
if override:

packages/uipath-platform/src/uipath/platform/common/_bindings.py

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,14 @@
1414
Union,
1515
)
1616

17-
from pydantic import AliasChoices, BaseModel, ConfigDict, Field, TypeAdapter
17+
from pydantic import (
18+
AliasChoices,
19+
BaseModel,
20+
ConfigDict,
21+
Field,
22+
TypeAdapter,
23+
model_validator,
24+
)
1825

1926
logger = logging.getLogger(__name__)
2027

@@ -59,6 +66,29 @@ def folder_identifier(self) -> str:
5966
return self.folder_path
6067

6168

69+
class EntityResourceOverwrite(ResourceOverwrite):
70+
resource_type: Literal["entity"]
71+
name: str = Field(alias="name")
72+
folder_id: Optional[str] = Field(default=None, alias="folderId")
73+
folder_path: Optional[str] = Field(default=None, alias="folderPath")
74+
75+
@model_validator(mode="after")
76+
def validate_folder_identifier(self) -> "EntityResourceOverwrite":
77+
if self.folder_id and self.folder_path:
78+
raise ValueError("Only one of folderId or folderPath may be provided.")
79+
if not self.folder_id and not self.folder_path:
80+
raise ValueError("Either folderId or folderPath must be provided.")
81+
return self
82+
83+
@property
84+
def resource_identifier(self) -> str:
85+
return self.name
86+
87+
@property
88+
def folder_identifier(self) -> str:
89+
return self.folder_id or self.folder_path or ""
90+
91+
6292
class ConnectionResourceOverwrite(ResourceOverwrite):
6393
resource_type: Literal["connection"]
6494
# In eval context, studio web provides "ConnectionId".
@@ -83,7 +113,9 @@ def folder_identifier(self) -> str:
83113

84114

85115
ResourceOverwriteUnion = Annotated[
86-
Union[GenericResourceOverwrite, ConnectionResourceOverwrite],
116+
Union[
117+
GenericResourceOverwrite, EntityResourceOverwrite, ConnectionResourceOverwrite
118+
],
87119
Field(discriminator="resource_type"),
88120
]
89121

@@ -112,9 +144,23 @@ def parse(cls, key: str, value: dict[str, Any]) -> ResourceOverwrite:
112144
The appropriate ResourceOverwrite subclass instance
113145
"""
114146
resource_type = key.split(".")[0]
115-
value_with_type = {"resource_type": resource_type, **value}
147+
normalized_value = cls._normalize_value(resource_type, value)
148+
value_with_type = {"resource_type": resource_type, **normalized_value}
116149
return cls._adapter.validate_python(value_with_type)
117150

151+
@staticmethod
152+
def _normalize_value(resource_type: str, value: dict[str, Any]) -> dict[str, Any]:
153+
if resource_type != "entity":
154+
return value
155+
156+
normalized = dict(value)
157+
if "folderId" in normalized:
158+
normalized["folder_id"] = normalized.pop("folderId")
159+
if "folderPath" in normalized:
160+
normalized["folder_path"] = normalized.pop("folderPath")
161+
162+
return normalized
163+
118164

119165
_resource_overwrites: ContextVar[Optional[dict[str, ResourceOverwrite]]] = ContextVar(
120166
"resource_overwrites", default=None

0 commit comments

Comments
 (0)