-
Notifications
You must be signed in to change notification settings - Fork 24
Integrate with AutomationOps and SemanticProxy #1578
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 9 additions & 0 deletions
9
packages/uipath-platform/src/uipath/platform/automation_ops/__init__.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| """AutomationOps service package. | ||
|
|
||
| Provides the ``AutomationOpsService`` client for retrieving deployed AI Trust | ||
| Layer policies from AgentHub. | ||
| """ | ||
|
|
||
| from ._automation_ops_service import AutomationOpsService | ||
|
|
||
| __all__ = ["AutomationOpsService"] |
64 changes: 64 additions & 0 deletions
64
packages/uipath-platform/src/uipath/platform/automation_ops/_automation_ops_service.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| """AutomationOps service for UiPath Platform. | ||
|
|
||
| Provides methods for retrieving deployed policies from the AgentHub service. | ||
| """ | ||
|
|
||
| from typing import Any | ||
|
|
||
| from uipath.core.tracing import traced | ||
|
|
||
| from ..common._base_service import BaseService | ||
| from ..common._config import UiPathApiConfig | ||
| from ..common._execution_context import UiPathExecutionContext | ||
| from ..common._models import Endpoint, RequestSpec | ||
|
|
||
| _DEPLOYED_POLICY_ENDPOINT = Endpoint("agenthub_/api/policies/deployed-policy") | ||
|
|
||
|
|
||
| class AutomationOpsService(BaseService): | ||
| """Service for interacting with UiPath AutomationOps policies via AgentHub.""" | ||
|
|
||
| def __init__( | ||
| self, | ||
| config: UiPathApiConfig, | ||
| execution_context: UiPathExecutionContext, | ||
| ) -> None: | ||
| super().__init__(config=config, execution_context=execution_context) | ||
|
|
||
| @traced(name="automation_ops_get_deployed_policy", run_type="uipath") | ||
| def get_deployed_policy(self) -> dict[str, Any]: | ||
| """Retrieve the deployed policy. | ||
|
|
||
| Returns: | ||
| The deployed policy response as a dictionary. | ||
| """ | ||
| spec = self._deployed_policy_spec() | ||
| response = self.request( | ||
| spec.method, | ||
| url=spec.endpoint, | ||
| headers=spec.headers, | ||
| scoped="tenant", | ||
| ) | ||
| return response.json() | ||
|
|
||
| @traced(name="automation_ops_get_deployed_policy", run_type="uipath") | ||
| async def get_deployed_policy_async(self) -> dict[str, Any]: | ||
| """Retrieve the deployed policy (async). | ||
|
|
||
| Returns: | ||
| The deployed policy response as a dictionary. | ||
| """ | ||
| spec = self._deployed_policy_spec() | ||
| response = await self.request_async( | ||
| spec.method, | ||
| url=spec.endpoint, | ||
| headers=spec.headers, | ||
| scoped="tenant", | ||
| ) | ||
| return response.json() | ||
|
|
||
| def _deployed_policy_spec(self) -> RequestSpec: | ||
| return RequestSpec( | ||
| method="POST", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it POST ? for "Get" Policy? |
||
| endpoint=_DEPLOYED_POLICY_ENDPOINT, | ||
| ) | ||
36 changes: 36 additions & 0 deletions
36
packages/uipath-platform/src/uipath/platform/semantic_proxy/__init__.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| """SemanticProxy service package. | ||
|
|
||
| Provides the ``SemanticProxyService`` client, Pydantic request/response models for | ||
| the PII detection endpoint, and utilities for rehydrating masked text with | ||
| original PII values after LLM processing. | ||
| """ | ||
|
|
||
| from ._semantic_proxy_service import SemanticProxyService | ||
| from .pii_utilities import ( | ||
| rehydrate_from_pii_entities, | ||
| rehydrate_from_pii_response, | ||
| ) | ||
| from .semantic_proxy import ( | ||
| PiiDetectionRequest, | ||
| PiiDetectionResponse, | ||
| PiiDocument, | ||
| PiiDocumentResult, | ||
| PiiEntity, | ||
| PiiEntityThreshold, | ||
| PiiFile, | ||
| PiiFileResult, | ||
| ) | ||
|
|
||
| __all__ = [ | ||
| "PiiDetectionRequest", | ||
| "PiiDetectionResponse", | ||
| "PiiDocument", | ||
| "PiiDocumentResult", | ||
| "PiiEntity", | ||
| "PiiEntityThreshold", | ||
| "PiiFile", | ||
| "PiiFileResult", | ||
| "SemanticProxyService", | ||
| "rehydrate_from_pii_entities", | ||
| "rehydrate_from_pii_response", | ||
| ] |
74 changes: 74 additions & 0 deletions
74
packages/uipath-platform/src/uipath/platform/semantic_proxy/_semantic_proxy_service.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| """SemanticProxy service for UiPath Platform. | ||
|
|
||
| Provides methods for interacting with the SemanticProxy service (e.g. PII detection). | ||
| """ | ||
|
|
||
| from uipath.core.tracing import traced | ||
|
|
||
| from ..common._base_service import BaseService | ||
| from ..common._config import UiPathApiConfig | ||
| from ..common._execution_context import UiPathExecutionContext | ||
| from ..common._models import Endpoint, RequestSpec | ||
| from .semantic_proxy import PiiDetectionRequest, PiiDetectionResponse | ||
|
|
||
| _PII_DETECTION_ENDPOINT = Endpoint("semanticproxy_/api/pii-detection") | ||
|
|
||
|
|
||
| class SemanticProxyService(BaseService): | ||
| """Service for interacting with UiPath SemanticProxy.""" | ||
|
|
||
| def __init__( | ||
| self, | ||
| config: UiPathApiConfig, | ||
| execution_context: UiPathExecutionContext, | ||
| ) -> None: | ||
| super().__init__(config=config, execution_context=execution_context) | ||
|
|
||
| @traced(name="semantic_proxy_detect_pii", run_type="uipath") | ||
| def detect_pii(self, request: PiiDetectionRequest) -> PiiDetectionResponse: | ||
| """Detect PII in the provided documents and/or files. | ||
|
|
||
| Args: | ||
| request: The PII detection request payload. | ||
|
|
||
| Returns: | ||
| The PII detection response. | ||
| """ | ||
| spec = self._pii_detection_spec(request) | ||
| response = self.request( | ||
| spec.method, | ||
| url=spec.endpoint, | ||
| json=spec.json, | ||
| headers=spec.headers, | ||
| scoped="tenant", | ||
| ) | ||
| return PiiDetectionResponse.model_validate(response.json()) | ||
|
|
||
| @traced(name="semantic_proxy_detect_pii", run_type="uipath") | ||
| async def detect_pii_async( | ||
| self, request: PiiDetectionRequest | ||
| ) -> PiiDetectionResponse: | ||
| """Detect PII in the provided documents and/or files (async). | ||
|
|
||
| Args: | ||
| request: The PII detection request payload. | ||
|
|
||
| Returns: | ||
| The PII detection response. | ||
| """ | ||
| spec = self._pii_detection_spec(request) | ||
| response = await self.request_async( | ||
| spec.method, | ||
| url=spec.endpoint, | ||
| json=spec.json, | ||
| headers=spec.headers, | ||
| scoped="tenant", | ||
| ) | ||
| return PiiDetectionResponse.model_validate(response.json()) | ||
|
|
||
| def _pii_detection_spec(self, request: PiiDetectionRequest) -> RequestSpec: | ||
| return RequestSpec( | ||
| method="POST", | ||
| endpoint=_PII_DETECTION_ENDPOINT, | ||
| json=request.model_dump(by_alias=True, exclude_none=True), | ||
| ) |
115 changes: 115 additions & 0 deletions
115
packages/uipath-platform/src/uipath/platform/semantic_proxy/pii_utilities.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| """Utility methods for working with PII data. | ||
|
|
||
| Python port of UiPath.SemanticProxy.Client.PiiUtilities (C#). | ||
| """ | ||
|
|
||
| import json | ||
| import re | ||
| from typing import Callable, Iterable | ||
|
|
||
| from .semantic_proxy import PiiDetectionResponse, PiiEntity | ||
|
|
||
|
|
||
| def rehydrate_from_pii_entities( | ||
| masked_text: str, pii_entities: Iterable[PiiEntity] | ||
| ) -> str: | ||
| """Rehydrate masked text by replacing PII placeholders with original values. | ||
|
|
||
| Placeholders (e.g. ``[Person-1]``) are matched case-insensitively and replaced | ||
| with the corresponding original PII text. The function also replaces variants | ||
| without the surrounding brackets (e.g. ``Person-1``) in case the LLM stripped | ||
| them in its output. | ||
|
|
||
| Args: | ||
| masked_text: The masked text with PII placeholders. | ||
| pii_entities: The PII entities containing the original values. | ||
|
|
||
| Returns: | ||
| The rehydrated text with original PII values. | ||
| """ | ||
| if not masked_text: | ||
| return masked_text | ||
|
|
||
| entities = [e for e in pii_entities if e.replacement_text] | ||
| if not entities: | ||
| return masked_text | ||
|
|
||
| # Sort by replacement text length descending to avoid substring collisions | ||
| # (e.g. "[Person-10]" must be replaced before "[Person-1]"). | ||
| entities.sort(key=lambda e: len(e.replacement_text), reverse=True) | ||
|
|
||
| rehydrated = masked_text | ||
| for entity in entities: | ||
| if not entity.replacement_text or not entity.pii_text: | ||
| continue | ||
| escaped_pii = _add_escape_characters(entity.pii_text) | ||
| # Replace the full placeholder (with brackets) case-insensitively. | ||
| # ``_literal_replacer`` bypasses regex backreference interpretation in the | ||
| # replacement string. | ||
| rehydrated = re.sub( | ||
| re.escape(entity.replacement_text), | ||
| _literal_replacer(escaped_pii), | ||
| rehydrated, | ||
| flags=re.IGNORECASE, | ||
| ) | ||
| # Also replace the content without brackets (in case the LLM dropped them). | ||
| if entity.replacement_text.startswith("[") and entity.replacement_text.endswith( | ||
| "]" | ||
| ): | ||
| no_brackets = entity.replacement_text[1:-1] | ||
| rehydrated = re.sub( | ||
| re.escape(no_brackets), | ||
| _literal_replacer(escaped_pii), | ||
| rehydrated, | ||
| flags=re.IGNORECASE, | ||
| ) | ||
|
|
||
| return rehydrated | ||
|
|
||
|
|
||
| def _literal_replacer(replacement: str) -> Callable[[re.Match[str]], str]: | ||
| """Return a replacement function that ignores regex backreference syntax.""" | ||
|
|
||
| def replace(_match: re.Match[str]) -> str: | ||
| return replacement | ||
|
|
||
| return replace | ||
|
|
||
|
|
||
| def rehydrate_from_pii_response( | ||
| masked_text: str, response: PiiDetectionResponse | ||
| ) -> str: | ||
| """Rehydrate masked text using all PII entities from a detection response. | ||
|
|
||
| Merges entities from both ``response.response`` (detected in documents/prompts) | ||
| and ``response.files`` (detected in files), so placeholders originating from | ||
| either source are rehydrated. | ||
|
|
||
| Args: | ||
| masked_text: The masked text with PII placeholders. | ||
| response: The PII detection response containing entities to rehydrate. | ||
|
|
||
| Returns: | ||
| The rehydrated text with original PII values. | ||
| """ | ||
| entities: list[PiiEntity] = [] | ||
| for doc in response.response: | ||
| entities.extend(doc.pii_entities) | ||
| for file in response.files: | ||
| entities.extend(file.pii_entities) | ||
| return rehydrate_from_pii_entities(masked_text, entities) | ||
|
|
||
|
|
||
| def _add_escape_characters(text: str) -> str: | ||
| """Escape special characters in text using JSON serialization. | ||
|
|
||
| Mirrors C# ``AddEscapeCharacters`` — serializes as JSON then strips the | ||
| surrounding quotes to get the escaped content. | ||
| """ | ||
| if not text: | ||
| return "" | ||
| try: | ||
| serialized = json.dumps(text, ensure_ascii=False) | ||
| return serialized[1:-1] | ||
| except (TypeError, ValueError): | ||
| return text |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure what the return type is here... but it will be better to have Response object here so that its not free form any objects in response... and unexpected responses can be caught early... similar to PII