From 8badd70573dffeac01e5174ad0186fc73ac94156 Mon Sep 17 00:00:00 2001 From: sankalp-uipath Date: Wed, 17 Jun 2026 14:51:15 +0530 Subject: [PATCH 01/10] feat(agent): add ontology binding to Data Fabric context config --- .../uipath/src/uipath/agent/models/agent.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/uipath/src/uipath/agent/models/agent.py b/packages/uipath/src/uipath/agent/models/agent.py index 72e7c438f..707aa5ebd 100644 --- a/packages/uipath/src/uipath/agent/models/agent.py +++ b/packages/uipath/src/uipath/agent/models/agent.py @@ -415,6 +415,21 @@ class AgentContextSettings(BaseCfg): ) +class DataFabricOntologyItem(BaseCfg): + """A single ontology attached to a Data Fabric context. + + Mirrors :class:`DataFabricEntityItem`, but a context may attach at most one + ontology. Carries its own ``folderId`` so the ontology is resolved from its + own folder, independent of the entities (which may span several folders). + """ + + id: Optional[str] = Field(None, alias="id") + reference_key: Optional[str] = Field(None, alias="referenceKey") + name: str = Field(..., alias="name") + folder_key: Optional[str] = Field(None, alias="folderId") + description: Optional[str] = Field(None, alias="description") + + class AgentContextResourceConfig(BaseAgentResourceConfig): """Agent context resource configuration model.""" @@ -428,6 +443,11 @@ class AgentContextResourceConfig(BaseAgentResourceConfig): None, description="Context settings" ) entity_set: Optional[List[DataFabricEntityItem]] = Field(None, alias="entitySet") + ontology: Optional[DataFabricOntologyItem] = Field( + None, + alias="ontology", + description="A single ontology attached to this context (at most one).", + ) argument_properties: Dict[str, AgentToolArgumentProperties] = Field( {}, alias="argumentProperties" ) From 906d6181d56e5f283d1e78414fe62d013055dcd7 Mon Sep 17 00:00:00 2001 From: sankalp-uipath Date: Wed, 17 Jun 2026 15:22:23 +0530 Subject: [PATCH 02/10] feat(entities): add ontology binding + get_ontology_file to Data Fabric service --- .../platform/entities/_entities_service.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/packages/uipath-platform/src/uipath/platform/entities/_entities_service.py b/packages/uipath-platform/src/uipath/platform/entities/_entities_service.py index 951a4b07b..f10f3ee3b 100644 --- a/packages/uipath-platform/src/uipath/platform/entities/_entities_service.py +++ b/packages/uipath-platform/src/uipath/platform/entities/_entities_service.py @@ -1,5 +1,6 @@ import json as json_module import logging +import re from typing import Any, Dict, List, Optional, Type import sqlparse @@ -51,6 +52,10 @@ ] _AGGREGATE_FUNCTIONS = ("COUNT", "SUM", "AVG", "MIN", "MAX") +# Ontology name contract (QueryEngine OntologyController): lowercase, starts +# with a letter, max 64 chars. The name becomes a URL path segment. +_ONTOLOGY_NAME_RE = re.compile(r"^[a-z][a-z0-9-]{0,63}$") + class EntitiesService(BaseService): """Service for managing UiPath Data Service entities. @@ -723,6 +728,56 @@ async def _query_entities_for_records_async( response = await self.request_async(spec.method, spec.endpoint, json=spec.json) return response.json().get("results", []) + async def get_ontology_file_async( + self, + ontology_name: str, + file_type: str = "owl", + folder_key: Optional[str] = None, + ) -> Dict[str, Any]: + """Fetch one file of an ontology from Data Fabric. + + PREVIEW: This method is in preview and may change in future releases. + + Ontologies are served by the same QueryEngine service as entity SQL + queries, under ``datafabric_/api/ontologies``. The JSON wrapper is + requested so the result is notation-agnostic — the ``owl`` file content + may be Turtle or OWL Functional Notation. + + Args: + ontology_name: Ontology name. Validated against the QE name contract. + file_type: One of owl, r2rml, shacl, summary, context. + folder_key: Folder the ontology lives in. + + Returns: + Dict[str, Any]: The file record (e.g. ``content``, ``mediaType``). + + Raises: + ValueError: If the ontology name is invalid. + """ + self._validate_ontology_name(ontology_name) + spec = self._ontology_file_spec(ontology_name, file_type) + headers = {"Accept": "application/json", **self._folder_key_headers(folder_key)} + response = await self.request_async(spec.method, spec.endpoint, headers=headers) + return response.json() + + @staticmethod + def _validate_ontology_name(ontology_name: str) -> None: + """Validate the ontology name before it becomes a URL path segment.""" + if not _ONTOLOGY_NAME_RE.match(ontology_name or ""): + raise ValueError( + f"Invalid ontology name {ontology_name!r}. " + "Must match ^[a-z][a-z0-9-]{0,63}$." + ) + + @staticmethod + def _ontology_file_spec(ontology_name: str, file_type: str) -> RequestSpec: + return RequestSpec( + method="GET", + endpoint=Endpoint( + f"datafabric_/api/ontologies/{ontology_name}/files/{file_type}" + ), + ) + @traced(name="entity_record_insert_batch", run_type="uipath") def insert_records( self, From cd4ff8875a08f6049cc0d93a216267ea1763b161 Mon Sep 17 00:00:00 2001 From: sankalp-uipath Date: Wed, 17 Jun 2026 15:41:42 +0530 Subject: [PATCH 03/10] feat(agent): allow multiple ontologies per context (ontologySet) Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/uipath/src/uipath/agent/models/agent.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/uipath/src/uipath/agent/models/agent.py b/packages/uipath/src/uipath/agent/models/agent.py index 707aa5ebd..af4deb1f7 100644 --- a/packages/uipath/src/uipath/agent/models/agent.py +++ b/packages/uipath/src/uipath/agent/models/agent.py @@ -418,9 +418,10 @@ class AgentContextSettings(BaseCfg): class DataFabricOntologyItem(BaseCfg): """A single ontology attached to a Data Fabric context. - Mirrors :class:`DataFabricEntityItem`, but a context may attach at most one - ontology. Carries its own ``folderId`` so the ontology is resolved from its - own folder, independent of the entities (which may span several folders). + Mirrors :class:`DataFabricEntityItem`. A context may attach one or more + ontologies (see ``ontology_set``). Each carries its own ``folderId`` so it + is resolved from its own folder, independent of the entities (which may also + span several folders). """ id: Optional[str] = Field(None, alias="id") @@ -443,10 +444,10 @@ class AgentContextResourceConfig(BaseAgentResourceConfig): None, description="Context settings" ) entity_set: Optional[List[DataFabricEntityItem]] = Field(None, alias="entitySet") - ontology: Optional[DataFabricOntologyItem] = Field( + ontology_set: Optional[List[DataFabricOntologyItem]] = Field( None, - alias="ontology", - description="A single ontology attached to this context (at most one).", + alias="ontologySet", + description="Ontologies attached to this context (mirrors entity_set).", ) argument_properties: Dict[str, AgentToolArgumentProperties] = Field( {}, alias="argumentProperties" From 2048c565697a3fadc57204bb7e7cb1a73151d4a4 Mon Sep 17 00:00:00 2001 From: sankalp-uipath Date: Mon, 22 Jun 2026 13:06:05 +0530 Subject: [PATCH 04/10] fix(entities): scope ontology fetch via header_folder, not missing _folder_key_headers --- .../src/uipath/platform/entities/_entities_service.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/uipath-platform/src/uipath/platform/entities/_entities_service.py b/packages/uipath-platform/src/uipath/platform/entities/_entities_service.py index e69cc2d7a..a2c674d84 100644 --- a/packages/uipath-platform/src/uipath/platform/entities/_entities_service.py +++ b/packages/uipath-platform/src/uipath/platform/entities/_entities_service.py @@ -25,6 +25,7 @@ from ..common._bindings import _resource_overwrites from ..common._config import UiPathApiConfig from ..common._execution_context import UiPathExecutionContext +from ..common._folder_context import header_folder from ..common._models import Endpoint, RequestSpec from ..orchestrator._folder_service import FolderService from ._entity_data_service import EntityDataService, FileContent @@ -1133,7 +1134,7 @@ async def get_ontology_file_async( """ self._validate_ontology_name(ontology_name) spec = self._ontology_file_spec(ontology_name, file_type) - headers = {"Accept": "application/json", **self._folder_key_headers(folder_key)} + headers = {"Accept": "application/json", **header_folder(folder_key, None)} response = await self.request_async(spec.method, spec.endpoint, headers=headers) return response.json() From 28d3c0cf40dd9428ea757b701c13625affb3fdd2 Mon Sep 17 00:00:00 2001 From: sankalp-uipath Date: Mon, 22 Jun 2026 18:05:49 +0530 Subject: [PATCH 05/10] feat(entities): validate ontology file_type; require folderId; add coverage --- .../platform/entities/_entities_service.py | 15 +++- .../tests/services/test_entities_service.py | 75 +++++++++++++++++ .../uipath/src/uipath/agent/models/agent.py | 4 +- .../uipath/tests/agent/models/test_agent.py | 81 +++++++++++++++++++ 4 files changed, 172 insertions(+), 3 deletions(-) diff --git a/packages/uipath-platform/src/uipath/platform/entities/_entities_service.py b/packages/uipath-platform/src/uipath/platform/entities/_entities_service.py index a2c674d84..8137de096 100644 --- a/packages/uipath-platform/src/uipath/platform/entities/_entities_service.py +++ b/packages/uipath-platform/src/uipath/platform/entities/_entities_service.py @@ -64,6 +64,9 @@ # Ontology name contract (QueryEngine OntologyController): lowercase, starts # with a letter, max 64 chars. The name becomes a URL path segment. _ONTOLOGY_NAME_RE = re.compile(r"^[a-z][a-z0-9-]{0,63}$") +# Allowed ontology component file types (also URL path segments). +_ONTOLOGY_FILE_TYPES = frozenset({"owl", "r2rml", "shacl", "summary", "context"}) + class EntitiesService(BaseService): """Service for managing UiPath Data Service entities. @@ -1130,9 +1133,10 @@ async def get_ontology_file_async( Dict[str, Any]: The file record (e.g. ``content``, ``mediaType``). Raises: - ValueError: If the ontology name is invalid. + ValueError: If the ontology name or file type is invalid. """ self._validate_ontology_name(ontology_name) + self._validate_file_type(file_type) spec = self._ontology_file_spec(ontology_name, file_type) headers = {"Accept": "application/json", **header_folder(folder_key, None)} response = await self.request_async(spec.method, spec.endpoint, headers=headers) @@ -1147,6 +1151,15 @@ def _validate_ontology_name(ontology_name: str) -> None: "Must match ^[a-z][a-z0-9-]{0,63}$." ) + @staticmethod + def _validate_file_type(file_type: str) -> None: + """Validate the file type before it becomes a URL path segment.""" + if file_type not in _ONTOLOGY_FILE_TYPES: + allowed = ", ".join(sorted(_ONTOLOGY_FILE_TYPES)) + raise ValueError( + f"Invalid ontology file type {file_type!r}. One of: {allowed}." + ) + @staticmethod def _ontology_file_spec(ontology_name: str, file_type: str) -> RequestSpec: return RequestSpec( diff --git a/packages/uipath-platform/tests/services/test_entities_service.py b/packages/uipath-platform/tests/services/test_entities_service.py index 258c3a9d8..54762cf24 100644 --- a/packages/uipath-platform/tests/services/test_entities_service.py +++ b/packages/uipath-platform/tests/services/test_entities_service.py @@ -2647,3 +2647,78 @@ def test_5xx_with_batch_shape_still_propagates( entity_key=str(entity_key), records=[{"name": "x"}], ) + + +class TestGetOntologyFileAsync: + """Tests for EntitiesService.get_ontology_file_async.""" + + @pytest.mark.anyio + async def test_builds_endpoint_and_folder_header( + self, service: EntitiesService + ) -> None: + response = MagicMock() + response.json.return_value = {"content": "OWL", "mediaType": "text/plain"} + service.request_async = AsyncMock(return_value=response) # type: ignore[method-assign] + + result = await service.get_ontology_file_async( + "library", "owl", folder_key="folder-1" + ) + + assert result == {"content": "OWL", "mediaType": "text/plain"} + service.request_async.assert_called_once() + call = service.request_async.call_args + method, endpoint = call.args[0], call.args[1] + headers = call.kwargs["headers"] + assert method == "GET" + assert str(endpoint) == "/datafabric_/api/ontologies/library/files/owl" + assert headers["Accept"] == "application/json" + assert headers["x-uipath-folderkey"] == "folder-1" + + @pytest.mark.anyio + async def test_no_folder_header_when_folder_key_none( + self, service: EntitiesService + ) -> None: + response = MagicMock() + response.json.return_value = {"content": "OWL", "mediaType": "text/plain"} + service.request_async = AsyncMock(return_value=response) # type: ignore[method-assign] + + await service.get_ontology_file_async("library") + + headers = service.request_async.call_args.kwargs["headers"] + assert "x-uipath-folderkey" not in headers + + @pytest.mark.anyio + @pytest.mark.parametrize( + "file_type", ["owl", "r2rml", "shacl", "summary", "context"] + ) + async def test_accepts_allowed_file_types( + self, service: EntitiesService, file_type: str + ) -> None: + response = MagicMock() + response.json.return_value = {"content": "x"} + service.request_async = AsyncMock(return_value=response) # type: ignore[method-assign] + + await service.get_ontology_file_async("library", file_type) + + endpoint = service.request_async.call_args.args[1] + assert str(endpoint) == f"/datafabric_/api/ontologies/library/files/{file_type}" + + @pytest.mark.anyio + async def test_rejects_invalid_ontology_name( + self, service: EntitiesService + ) -> None: + service.request_async = AsyncMock() # type: ignore[method-assign] + + with pytest.raises(ValueError, match="Invalid ontology name"): + await service.get_ontology_file_async("Bad_Name") # uppercase + underscore + + service.request_async.assert_not_called() + + @pytest.mark.anyio + async def test_rejects_invalid_file_type(self, service: EntitiesService) -> None: + service.request_async = AsyncMock() # type: ignore[method-assign] + + with pytest.raises(ValueError, match="Invalid ontology file type"): + await service.get_ontology_file_async("library", "exe") + + service.request_async.assert_not_called() diff --git a/packages/uipath/src/uipath/agent/models/agent.py b/packages/uipath/src/uipath/agent/models/agent.py index 2c43d21b5..57dde1b79 100644 --- a/packages/uipath/src/uipath/agent/models/agent.py +++ b/packages/uipath/src/uipath/agent/models/agent.py @@ -437,9 +437,9 @@ class DataFabricOntologyItem(BaseCfg): """ id: Optional[str] = Field(None, alias="id") - reference_key: Optional[str] = Field(None, alias="referenceKey") + ontology_key: Optional[str] = Field(None, alias="referenceKey") name: str = Field(..., alias="name") - folder_key: Optional[str] = Field(None, alias="folderId") + folder_key: str = Field(..., alias="folderId") description: Optional[str] = Field(None, alias="description") diff --git a/packages/uipath/tests/agent/models/test_agent.py b/packages/uipath/tests/agent/models/test_agent.py index ab0e6a557..54954ed34 100644 --- a/packages/uipath/tests/agent/models/test_agent.py +++ b/packages/uipath/tests/agent/models/test_agent.py @@ -3867,6 +3867,87 @@ def test_datafabric_context_config_parses(self): assert parsed.entity_set[1].entity_key == "orders-ref" assert parsed.entity_set[1].description is None + def test_datafabric_context_config_parses_ontology_set(self): + """ontologySet parses into typed DataFabricOntologyItem entries.""" + config = { + "$resourceType": "context", + "name": "TestDataFabric", + "description": "", + "contextType": "datafabricentityset", + "entitySet": [{"id": "e1", "name": "Customers", "folderId": "f1"}], + "ontologySet": [ + {"name": "ecommerce", "folderId": "f1"}, + { + "id": "o2", + "referenceKey": "ont-ref", + "name": "finance", + "folderId": "f2", + "description": "Finance domain", + }, + ], + } + + parsed = AgentContextResourceConfig.model_validate(config) + + assert parsed.ontology_set is not None + assert len(parsed.ontology_set) == 2 + # first: minimal, no referenceKey + assert parsed.ontology_set[0].name == "ecommerce" + assert parsed.ontology_set[0].folder_key == "f1" + assert parsed.ontology_set[0].ontology_key is None + # second: its own folder + referenceKey (aliased to ontology_key) + assert parsed.ontology_set[1].name == "finance" + assert parsed.ontology_set[1].folder_key == "f2" + assert parsed.ontology_set[1].ontology_key == "ont-ref" + assert parsed.ontology_set[1].description == "Finance domain" + + def test_datafabric_context_config_dumps_ontology_set_by_alias(self): + """ontologySet round-trips back to aliased JSON keys.""" + config = { + "$resourceType": "context", + "name": "TestDataFabric", + "description": "", + "contextType": "datafabricentityset", + "ontologySet": [ + {"referenceKey": "ont-ref", "name": "finance", "folderId": "f2"} + ], + } + + parsed = AgentContextResourceConfig.model_validate(config) + dumped = parsed.model_dump(by_alias=True, exclude_none=True) + + assert "ontologySet" in dumped + assert dumped["ontologySet"][0]["name"] == "finance" + assert dumped["ontologySet"][0]["folderId"] == "f2" + assert dumped["ontologySet"][0]["referenceKey"] == "ont-ref" + + def test_datafabric_ontology_requires_folder_id(self): + """folderId is required on each ontology (mirrors DataFabricEntityItem).""" + config = { + "$resourceType": "context", + "name": "TestDataFabric", + "description": "", + "contextType": "datafabricentityset", + "ontologySet": [{"name": "ecommerce"}], # missing folderId + } + + with pytest.raises(ValidationError): + AgentContextResourceConfig.model_validate(config) + + def test_datafabric_context_config_without_ontology_set(self): + """ontology_set defaults to None when not provided (backward compatible).""" + config = { + "$resourceType": "context", + "name": "TestDataFabric", + "description": "", + "contextType": "datafabricentityset", + "entitySet": [{"id": "e1", "name": "Customers", "folderId": "f1"}], + } + + parsed = AgentContextResourceConfig.model_validate(config) + + assert parsed.ontology_set is None + def test_is_datafabric(self): """Test is_datafabric property with datafabricentityset contextType.""" config = { From d4ccd793213a2b04ebafc342de0d771e7c5c987d Mon Sep 17 00:00:00 2001 From: sankalp-uipath Date: Tue, 23 Jun 2026 02:45:20 +0530 Subject: [PATCH 06/10] chore: bump uipath to 2.11.8 and uipath-platform to 0.1.72 Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/uipath-platform/pyproject.toml | 2 +- packages/uipath-platform/uv.lock | 2 +- packages/uipath/pyproject.toml | 4 ++-- packages/uipath/uv.lock | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/uipath-platform/pyproject.toml b/packages/uipath-platform/pyproject.toml index 37d6fcdaa..6d9516a9d 100644 --- a/packages/uipath-platform/pyproject.toml +++ b/packages/uipath-platform/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath-platform" -version = "0.1.71" +version = "0.1.72" description = "HTTP client library for programmatic access to UiPath Platform" readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/packages/uipath-platform/uv.lock b/packages/uipath-platform/uv.lock index df0dc786a..159a3e71b 100644 --- a/packages/uipath-platform/uv.lock +++ b/packages/uipath-platform/uv.lock @@ -1095,7 +1095,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.1.71" +version = "0.1.72" source = { editable = "." } dependencies = [ { name = "httpx" }, diff --git a/packages/uipath/pyproject.toml b/packages/uipath/pyproject.toml index 581f9a3d2..1305d59bc 100644 --- a/packages/uipath/pyproject.toml +++ b/packages/uipath/pyproject.toml @@ -1,13 +1,13 @@ [project] name = "uipath" -version = "2.11.7" +version = "2.11.8" description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools." readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" dependencies = [ "uipath-core>=0.5.17, <0.6.0", "uipath-runtime>=0.11.0, <0.12.0", - "uipath-platform>=0.1.68, <0.2.0", + "uipath-platform>=0.1.72, <0.2.0", "click>=8.3.1", "httpx>=0.28.1", "pyjwt>=2.10.1", diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index a805e5cdc..22515b111 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.11" [options] -exclude-newer = "2026-06-20T16:42:14.097008Z" +exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P2D" [options.exclude-newer-package] @@ -2552,7 +2552,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.11.7" +version = "2.11.8" source = { editable = "." } dependencies = [ { name = "applicationinsights" }, @@ -2691,7 +2691,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.1.71" +version = "0.1.72" source = { editable = "../uipath-platform" } dependencies = [ { name = "httpx" }, From f22074c86ef864eed01956c1c4b39692afc7ca3c Mon Sep 17 00:00:00 2001 From: sankalp-uipath Date: Tue, 23 Jun 2026 11:40:54 +0530 Subject: [PATCH 07/10] docs(entities): mark get_ontology_file_async as Preview Feature Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/uipath/platform/entities/_entities_service.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/uipath-platform/src/uipath/platform/entities/_entities_service.py b/packages/uipath-platform/src/uipath/platform/entities/_entities_service.py index 8137de096..6483bb82d 100644 --- a/packages/uipath-platform/src/uipath/platform/entities/_entities_service.py +++ b/packages/uipath-platform/src/uipath/platform/entities/_entities_service.py @@ -1117,7 +1117,9 @@ async def get_ontology_file_async( ) -> Dict[str, Any]: """Fetch one file of an ontology from Data Fabric. - PREVIEW: This method is in preview and may change in future releases. + !!! warning "Preview Feature" + This method is currently experimental. Behavior and parameters are + subject to change in future versions. Ontologies are served by the same QueryEngine service as entity SQL queries, under ``datafabric_/api/ontologies``. The JSON wrapper is From bcbf2dd3f9dbd8b3e833888523aeb141eb7cf428 Mon Sep 17 00:00:00 2001 From: sankalp-uipath Date: Tue, 23 Jun 2026 12:01:03 +0530 Subject: [PATCH 08/10] chore: bump uipath to 2.11.9 (main advanced to 2.11.8) Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/uipath/pyproject.toml | 2 +- packages/uipath/uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/uipath/pyproject.toml b/packages/uipath/pyproject.toml index 1305d59bc..e6817ee9a 100644 --- a/packages/uipath/pyproject.toml +++ b/packages/uipath/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath" -version = "2.11.8" +version = "2.11.9" description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools." readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index 22515b111..9d50dd482 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2552,7 +2552,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.11.8" +version = "2.11.9" source = { editable = "." } dependencies = [ { name = "applicationinsights" }, From 75aaab5909d9e1521d8d59e1acf84a052d9b5ee4 Mon Sep 17 00:00:00 2001 From: sankalp-uipath Date: Tue, 23 Jun 2026 18:18:47 +0530 Subject: [PATCH 09/10] feat(agent): ontology as standalone resource (ontologyRefs) --- packages/uipath-platform/pyproject.toml | 2 +- packages/uipath-platform/uv.lock | 2 +- packages/uipath/pyproject.toml | 4 +- .../uipath/src/uipath/agent/models/agent.py | 36 ++-- .../uipath/tests/agent/models/test_agent.py | 192 +++++++++++++----- packages/uipath/uv.lock | 4 +- 6 files changed, 170 insertions(+), 70 deletions(-) diff --git a/packages/uipath-platform/pyproject.toml b/packages/uipath-platform/pyproject.toml index 6d9516a9d..93223b014 100644 --- a/packages/uipath-platform/pyproject.toml +++ b/packages/uipath-platform/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath-platform" -version = "0.1.72" +version = "0.1.74" description = "HTTP client library for programmatic access to UiPath Platform" readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/packages/uipath-platform/uv.lock b/packages/uipath-platform/uv.lock index 159a3e71b..132ed4edd 100644 --- a/packages/uipath-platform/uv.lock +++ b/packages/uipath-platform/uv.lock @@ -1095,7 +1095,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.1.72" +version = "0.1.74" source = { editable = "." } dependencies = [ { name = "httpx" }, diff --git a/packages/uipath/pyproject.toml b/packages/uipath/pyproject.toml index e6817ee9a..6848d2de7 100644 --- a/packages/uipath/pyproject.toml +++ b/packages/uipath/pyproject.toml @@ -1,13 +1,13 @@ [project] name = "uipath" -version = "2.11.9" +version = "2.11.10" description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools." readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" dependencies = [ "uipath-core>=0.5.17, <0.6.0", "uipath-runtime>=0.11.0, <0.12.0", - "uipath-platform>=0.1.72, <0.2.0", + "uipath-platform>=0.1.74, <0.2.0", "click>=8.3.1", "httpx>=0.28.1", "pyjwt>=2.10.1", diff --git a/packages/uipath/src/uipath/agent/models/agent.py b/packages/uipath/src/uipath/agent/models/agent.py index 57dde1b79..c5d6b9245 100644 --- a/packages/uipath/src/uipath/agent/models/agent.py +++ b/packages/uipath/src/uipath/agent/models/agent.py @@ -102,6 +102,7 @@ class AgentResourceType(str, CaseInsensitiveEnum): ESCALATION = "escalation" MCP = "mcp" A2A = "a2a" + ONTOLOGY = "ontology" UNKNOWN = "unknown" # fallback branch discriminator @@ -341,6 +342,7 @@ class BaseAgentResourceConfig(BaseCfg): AgentResourceType.ESCALATION, AgentResourceType.MCP, AgentResourceType.A2A, + AgentResourceType.ONTOLOGY, AgentResourceType.UNKNOWN, ] = Field(alias="$resourceType") @@ -427,20 +429,25 @@ class AgentContextSettings(BaseCfg): ) -class DataFabricOntologyItem(BaseCfg): - """A single ontology attached to a Data Fabric context. +class AgentOntologyResourceConfig(BaseAgentResourceConfig): + """A Data Fabric ontology as a standalone, first-class agent resource. - Mirrors :class:`DataFabricEntityItem`. A context may attach one or more - ontologies (see ``ontology_set``). Each carries its own ``folderId`` so it - is resolved from its own folder, independent of the entities (which may also - span several folders). + Promoted from a nested context field to its own ``resources[]`` entry so a + single ontology can be defined once and referenced by one or more Data + Fabric contexts (see ``AgentContextResourceConfig.ontology_refs``). Each + ontology carries its own ``folderId`` so it resolves from its own folder, + independent of the entities (which may also span several folders). + + ``name`` (inherited) is the ontology name used both to reference this + resource from a context and to fetch it from the QueryEngine ontology API. """ + resource_type: Literal[AgentResourceType.ONTOLOGY] = Field( + alias="$resourceType", default=AgentResourceType.ONTOLOGY, frozen=True + ) id: Optional[str] = Field(None, alias="id") ontology_key: Optional[str] = Field(None, alias="referenceKey") - name: str = Field(..., alias="name") folder_key: str = Field(..., alias="folderId") - description: Optional[str] = Field(None, alias="description") class AgentContextResourceConfig(BaseAgentResourceConfig): @@ -456,10 +463,14 @@ class AgentContextResourceConfig(BaseAgentResourceConfig): None, description="Context settings" ) entity_set: Optional[List[DataFabricEntityItem]] = Field(None, alias="entitySet") - ontology_set: Optional[List[DataFabricOntologyItem]] = Field( + ontology_refs: Optional[List[str]] = Field( None, - alias="ontologySet", - description="Ontologies attached to this context (mirrors entity_set).", + alias="ontologyRefs", + description=( + "Names of standalone ontology resources " + "(AgentOntologyResourceConfig) this context is grounded by. " + "Resolved against the agent's resources at runtime." + ), ) argument_properties: Dict[str, AgentToolArgumentProperties] = Field( {}, alias="argumentProperties" @@ -1157,6 +1168,7 @@ class AgentUnknownToolResourceConfig(BaseAgentToolResourceConfig): EscalationResourceConfig, # nested discrim on 'escalation_type' AgentMcpResourceConfig, AgentA2aResourceConfig, + AgentOntologyResourceConfig, AgentUnknownResourceConfig, # when parent sets resource_type="Unknown" ], Field(discriminator="resource_type"), @@ -1510,7 +1522,7 @@ def _normalize_guardrails(v: Dict[str, Any]) -> None: @staticmethod def _normalize_resources(v: Dict[str, Any]) -> None: - KNOWN_RES = {"tool", "context", "escalation", "mcp", "a2a"} + KNOWN_RES = {"tool", "context", "escalation", "mcp", "a2a", "ontology"} TOOL_MAP = { "agent": "Agent", "process": "Process", diff --git a/packages/uipath/tests/agent/models/test_agent.py b/packages/uipath/tests/agent/models/test_agent.py index 54954ed34..dd2133972 100644 --- a/packages/uipath/tests/agent/models/test_agent.py +++ b/packages/uipath/tests/agent/models/test_agent.py @@ -35,8 +35,10 @@ AgentMessageRole, AgentNumberOperator, AgentNumberRule, + AgentOntologyResourceConfig, AgentProcessToolResourceConfig, AgentQuickFormChannelProperties, + AgentResourceConfig, AgentResourceType, AgentToolArgumentPropertiesVariant, AgentToolType, @@ -3867,75 +3869,104 @@ def test_datafabric_context_config_parses(self): assert parsed.entity_set[1].entity_key == "orders-ref" assert parsed.entity_set[1].description is None - def test_datafabric_context_config_parses_ontology_set(self): - """ontologySet parses into typed DataFabricOntologyItem entries.""" + def test_ontology_resource_parses_standalone(self): + """A standalone ontology resource parses via the resource union.""" config = { - "$resourceType": "context", - "name": "TestDataFabric", - "description": "", - "contextType": "datafabricentityset", - "entitySet": [{"id": "e1", "name": "Customers", "folderId": "f1"}], - "ontologySet": [ - {"name": "ecommerce", "folderId": "f1"}, - { - "id": "o2", - "referenceKey": "ont-ref", - "name": "finance", - "folderId": "f2", - "description": "Finance domain", - }, - ], + "$resourceType": "ontology", + "name": "ecommerce", + "description": "E-commerce domain ontology", + "folderId": "f1", } - parsed = AgentContextResourceConfig.model_validate(config) + parsed = TypeAdapter(AgentResourceConfig).validate_python(config) - assert parsed.ontology_set is not None - assert len(parsed.ontology_set) == 2 - # first: minimal, no referenceKey - assert parsed.ontology_set[0].name == "ecommerce" - assert parsed.ontology_set[0].folder_key == "f1" - assert parsed.ontology_set[0].ontology_key is None - # second: its own folder + referenceKey (aliased to ontology_key) - assert parsed.ontology_set[1].name == "finance" - assert parsed.ontology_set[1].folder_key == "f2" - assert parsed.ontology_set[1].ontology_key == "ont-ref" - assert parsed.ontology_set[1].description == "Finance domain" - - def test_datafabric_context_config_dumps_ontology_set_by_alias(self): - """ontologySet round-trips back to aliased JSON keys.""" + assert isinstance(parsed, AgentOntologyResourceConfig) + assert parsed.resource_type == AgentResourceType.ONTOLOGY + assert parsed.name == "ecommerce" + assert parsed.folder_key == "f1" + assert parsed.ontology_key is None + + def test_ontology_resource_parses_optional_fields(self): + """id / referenceKey are optional and aliased on the ontology resource.""" config = { - "$resourceType": "context", - "name": "TestDataFabric", - "description": "", - "contextType": "datafabricentityset", - "ontologySet": [ - {"referenceKey": "ont-ref", "name": "finance", "folderId": "f2"} - ], + "$resourceType": "ontology", + "id": "o2", + "referenceKey": "ont-ref", + "name": "finance", + "description": "Finance domain", + "folderId": "f2", } - parsed = AgentContextResourceConfig.model_validate(config) + parsed = TypeAdapter(AgentResourceConfig).validate_python(config) + + assert isinstance(parsed, AgentOntologyResourceConfig) + assert parsed.id == "o2" + assert parsed.ontology_key == "ont-ref" + assert parsed.folder_key == "f2" + assert parsed.description == "Finance domain" + + def test_ontology_resource_dumps_by_alias(self): + """The ontology resource round-trips back to aliased JSON keys.""" + parsed = TypeAdapter(AgentResourceConfig).validate_python( + { + "$resourceType": "ontology", + "referenceKey": "ont-ref", + "name": "finance", + "description": "Finance domain", + "folderId": "f2", + } + ) dumped = parsed.model_dump(by_alias=True, exclude_none=True) - assert "ontologySet" in dumped - assert dumped["ontologySet"][0]["name"] == "finance" - assert dumped["ontologySet"][0]["folderId"] == "f2" - assert dumped["ontologySet"][0]["referenceKey"] == "ont-ref" + assert dumped["$resourceType"] == "ontology" + assert dumped["name"] == "finance" + assert dumped["folderId"] == "f2" + assert dumped["referenceKey"] == "ont-ref" + + def test_ontology_resource_requires_folder_id(self): + """folderId is required on an ontology resource.""" + config = { + "$resourceType": "ontology", + "name": "ecommerce", + "description": "", + # missing folderId + } + + with pytest.raises(ValidationError): + TypeAdapter(AgentResourceConfig).validate_python(config) - def test_datafabric_ontology_requires_folder_id(self): - """folderId is required on each ontology (mirrors DataFabricEntityItem).""" + def test_context_config_parses_ontology_refs(self): + """ontologyRefs parses into a list of ontology resource names.""" config = { "$resourceType": "context", "name": "TestDataFabric", "description": "", "contextType": "datafabricentityset", - "ontologySet": [{"name": "ecommerce"}], # missing folderId + "entitySet": [{"id": "e1", "name": "Customers", "folderId": "f1"}], + "ontologyRefs": ["ecommerce", "finance"], } - with pytest.raises(ValidationError): - AgentContextResourceConfig.model_validate(config) + parsed = AgentContextResourceConfig.model_validate(config) + + assert parsed.ontology_refs == ["ecommerce", "finance"] + + def test_context_config_dumps_ontology_refs_by_alias(self): + """ontology_refs round-trips back to the ontologyRefs alias.""" + parsed = AgentContextResourceConfig.model_validate( + { + "$resourceType": "context", + "name": "TestDataFabric", + "description": "", + "contextType": "datafabricentityset", + "ontologyRefs": ["library"], + } + ) + dumped = parsed.model_dump(by_alias=True, exclude_none=True) - def test_datafabric_context_config_without_ontology_set(self): - """ontology_set defaults to None when not provided (backward compatible).""" + assert dumped["ontologyRefs"] == ["library"] + + def test_context_config_without_ontology_refs(self): + """ontology_refs defaults to None when not provided (backward compatible).""" config = { "$resourceType": "context", "name": "TestDataFabric", @@ -3946,7 +3977,64 @@ def test_datafabric_context_config_without_ontology_set(self): parsed = AgentContextResourceConfig.model_validate(config) - assert parsed.ontology_set is None + assert parsed.ontology_refs is None + + def test_ontology_resource_survives_full_definition_normalization(self): + """Regression: an ontology resource parses through the full + AgentDefinition normalizer (not coerced to Unknown). + + The per-resource union recognises ``$resourceType: "ontology"``, but the + runtime parses the whole AgentDefinition, which runs + ``_normalize_resources``. That normalizer's known-type set must include + "ontology"; otherwise it is rewritten to "unknown", parses as + AgentUnknownResourceConfig, and the context's ``ontologyRefs`` never + resolve at runtime. + """ + json_data = { + "id": "test-ontology-def", + "name": "Agent with ontology resource", + "version": "1.0.0", + "settings": { + "model": "gpt-4o-2024-11-20", + "maxTokens": 16384, + "temperature": 0, + "engine": "basic-v1", + }, + "inputSchema": {"type": "object", "properties": {}}, + "outputSchema": {"type": "object", "properties": {}}, + "resources": [ + { + "$resourceType": "context", + "contextType": "datafabricentityset", + "name": "Entities", + "description": "DF context", + "entitySet": [ + {"id": "e1", "name": "LibraryLoan", "folderId": "f1"} + ], + "ontologyRefs": ["library"], + }, + { + "$resourceType": "ontology", + "name": "library", + "description": "Library ontology", + "folderId": "f1", + }, + ], + "messages": [{"role": "system", "content": "Test system message"}], + } + + config: AgentDefinition = TypeAdapter(AgentDefinition).validate_python( + json_data + ) + + type_names = {type(r).__name__ for r in config.resources} + assert "AgentOntologyResourceConfig" in type_names + assert "AgentUnknownResourceConfig" not in type_names + onto = next( + r for r in config.resources if isinstance(r, AgentOntologyResourceConfig) + ) + assert onto.name == "library" + assert onto.folder_key == "f1" def test_is_datafabric(self): """Test is_datafabric property with datafabricentityset contextType.""" diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index 9d50dd482..ed87adad5 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2552,7 +2552,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.11.9" +version = "2.11.10" source = { editable = "." } dependencies = [ { name = "applicationinsights" }, @@ -2691,7 +2691,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.1.72" +version = "0.1.74" source = { editable = "../uipath-platform" } dependencies = [ { name = "httpx" }, From c231702baf05d5f45ac8d37d58910d482be71023 Mon Sep 17 00:00:00 2001 From: sankalp-uipath Date: Tue, 23 Jun 2026 18:56:36 +0530 Subject: [PATCH 10/10] test: annotate parsed for mypy (var-annotated) Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/uipath/tests/agent/models/test_agent.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/uipath/tests/agent/models/test_agent.py b/packages/uipath/tests/agent/models/test_agent.py index 446f79004..9babeb78e 100644 --- a/packages/uipath/tests/agent/models/test_agent.py +++ b/packages/uipath/tests/agent/models/test_agent.py @@ -3926,7 +3926,9 @@ def test_ontology_resource_parses_standalone(self): "folderId": "f1", } - parsed = TypeAdapter(AgentResourceConfig).validate_python(config) + parsed: AgentResourceConfig = TypeAdapter(AgentResourceConfig).validate_python( + config + ) assert isinstance(parsed, AgentOntologyResourceConfig) assert parsed.resource_type == AgentResourceType.ONTOLOGY @@ -3945,7 +3947,9 @@ def test_ontology_resource_parses_optional_fields(self): "folderId": "f2", } - parsed = TypeAdapter(AgentResourceConfig).validate_python(config) + parsed: AgentResourceConfig = TypeAdapter(AgentResourceConfig).validate_python( + config + ) assert isinstance(parsed, AgentOntologyResourceConfig) assert parsed.id == "o2" @@ -3955,7 +3959,7 @@ def test_ontology_resource_parses_optional_fields(self): def test_ontology_resource_dumps_by_alias(self): """The ontology resource round-trips back to aliased JSON keys.""" - parsed = TypeAdapter(AgentResourceConfig).validate_python( + parsed: AgentResourceConfig = TypeAdapter(AgentResourceConfig).validate_python( { "$resourceType": "ontology", "referenceKey": "ont-ref",