Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "uipath"
version = "2.5.12"
version = "2.5.13"
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"
Expand Down
5 changes: 5 additions & 0 deletions src/uipath/platform/_uipath.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from .._utils._auth import resolve_config_from_env
from .action_center import TasksService
from .agenthub._agenthub_service import AgentHubService
from .chat import ConversationsService, UiPathLlmChatService, UiPathOpenAIService
from .common import (
ApiClient,
Expand Down Expand Up @@ -153,3 +154,7 @@ def mcp(self) -> McpService:
@property
def guardrails(self) -> GuardrailsService:
return GuardrailsService(self._config, self._execution_context)

@property
def agenthub(self) -> AgentHubService:
return AgentHubService(self._config, self._execution_context, self.folders)
8 changes: 8 additions & 0 deletions src/uipath/platform/agenthub/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""UiPath AgentHub Models.

This module contains models related to UiPath AgentHub service.
"""

from uipath.platform.agenthub.agenthub import LlmModel

__all__ = ["LlmModel"]
202 changes: 202 additions & 0 deletions src/uipath/platform/agenthub/_agenthub_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
from typing import Any

from ..._utils import Endpoint, RequestSpec, header_folder
from ..common import BaseService, FolderContext, UiPathApiConfig, UiPathExecutionContext
from ..orchestrator import FolderService
from .agenthub import LlmModel


class AgentHubService(FolderContext, BaseService):
"""Service class for interacting with AgentHub platform service."""

def __init__(
self,
config: UiPathApiConfig,
execution_context: UiPathExecutionContext,
folder_service: FolderService,
) -> None:
self._folder_service = folder_service
super().__init__(config=config, execution_context=execution_context)

def get_available_llm_models(
self, headers: dict[str, Any] | None = None
) -> list[LlmModel]:
"""Fetch available models from LLM Gateway discovery endpoint.

Returns:
List of available models and their configurations.
"""
spec = self._available_models_spec(headers=headers)

response = self.request(
spec.method,
spec.endpoint,
params=spec.params,
headers=spec.headers,
)
return [
LlmModel.model_validate(available_model)
for available_model in response.json()
]

async def get_available_llm_models_async(
self, headers: dict[str, Any] | None = None
) -> list[LlmModel]:
"""Asynchronously fetch available models from LLM Gateway discovery endpoint.

Returns:
List of available models and their configurations.
"""
spec = self._available_models_spec(headers=headers)

response = await self.request_async(
spec.method,
spec.endpoint,
params=spec.params,
headers=spec.headers,
)
return [
LlmModel.model_validate(available_model)
for available_model in response.json()
]

def invoke_system_agent(
self,
*,
agent_name: str,
entrypoint: str,
input_arguments: dict[str, Any] | None = None,
folder_key: str | None = None,
folder_path: str | None = None,
headers: dict[str, Any] | None = None,
) -> str:
"""Start a system agent job.

Args:
agent_name: The name of the system agent to invoke.
entrypoint: The entry point to execute.
input_arguments: Optional input arguments to pass to the agent.
folder_key: Optional folder key to override the default folder context.
folder_path: Optional folder path to override the default folder context.

Returns:
str: The started job's key.
"""
folder_key = self._resolve_folder_key(folder_key, folder_path)

spec = self._start_spec(
agent_name=agent_name,
entrypoint=entrypoint,
input_arguments=input_arguments,
folder_key=folder_key,
headers=headers,
)

response = self.request(
spec.method,
url=spec.endpoint,
json=spec.json,
headers=spec.headers,
)

response_data = response.json()

return response_data["key"]

async def invoke_system_agent_async(
self,
*,
agent_name: str,
entrypoint: str,
input_arguments: dict[str, Any] | None = None,
folder_key: str | None = None,
folder_path: str | None = None,
headers: dict[str, Any] | None = None,
) -> str:
"""Asynchronously start a system agent and return the job.

Args:
agent_name: The name of the system agent to invoke.
entrypoint: The entry point to execute.
input_arguments: Optional input arguments to pass to the agent.
folder_key: Optional folder key to override the default folder context.
folder_path: Optional folder path to override the default folder context.

Returns:
str: The started job's key.

"""
folder_key = self._resolve_folder_key(folder_key, folder_path)

spec = self._start_spec(
agent_name=agent_name,
entrypoint=entrypoint,
input_arguments=input_arguments,
folder_key=folder_key,
headers=headers,
)

response = await self.request_async(
spec.method,
url=spec.endpoint,
json=spec.json,
headers=spec.headers,
)

response_data = response.json()

return response_data["key"]

def _start_spec(
self,
agent_name: str,
entrypoint: str,
input_arguments: dict[str, Any] | None,
folder_key: str,
headers: dict[str, Any] | None,
) -> RequestSpec:
"""Build the request specification for starting a system agent.

Args:
agent_name: The name of the system agent.
entrypoint: The entry point to execute.
input_arguments: Input arguments for the agent.
folder_key: Folder key for scoping.

Returns:
RequestSpec: The request specification with endpoint, method, headers, and body.
"""
return RequestSpec(
method="POST",
endpoint=Endpoint(f"agenthub_/api/systemagents/{agent_name}/start"),
headers=header_folder(folder_key, None) | (headers or {}),
json={
"EntryPoint": entrypoint,
"InputArguments": input_arguments or {},
},
)

def _resolve_folder_key(
self, folder_key: str | None, folder_path: str | None
) -> str:
if folder_key is None and folder_path is not None:
folder_key = self._folder_service.retrieve_key(folder_path=folder_path)

if folder_key is None and folder_path is None:
folder_key = self._folder_key or (
self._folder_service.retrieve_key(folder_path=self._folder_path)
if self._folder_path
else None
)

if folder_key is None:
raise ValueError("AgentHubClient: Failed to resolve folder key")

return folder_key

def _available_models_spec(self, headers: dict[str, Any] | None) -> RequestSpec:
return RequestSpec(
method="GET",
endpoint=Endpoint("/agenthub_/llm/api/discovery"),
headers=headers or {},
)
18 changes: 18 additions & 0 deletions src/uipath/platform/agenthub/agenthub.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""AgentHub response payload models."""

from pydantic import BaseModel, ConfigDict, Field


class LlmModel(BaseModel):
"""Model representing an available LLM model."""

model_name: str = Field(..., alias="modelName")
vendor: str | None = Field(default=None)

model_config = ConfigDict(
validate_by_name=True,
validate_by_alias=True,
use_enum_values=True,
arbitrary_types_allowed=True,
extra="allow",
)
4 changes: 4 additions & 0 deletions src/uipath/platform/common/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
CreateTask,
DocumentExtraction,
InvokeProcess,
InvokeSystemAgent,
WaitBatchTransform,
WaitDeepRag,
WaitDocumentExtraction,
WaitEscalation,
WaitJob,
WaitSystemAgent,
WaitTask,
)
from .paging import PagedResult
Expand All @@ -48,4 +50,6 @@
"WaitBatchTransform",
"DocumentExtraction",
"WaitDocumentExtraction",
"InvokeSystemAgent",
"WaitSystemAgent",
]
18 changes: 18 additions & 0 deletions src/uipath/platform/common/interrupt_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,24 @@ class WaitBatchTransform(BaseModel):
index_folder_key: str | None = None


class InvokeSystemAgent(BaseModel):
"""Model representing a system agent job invocation."""

agent_name: str
entrypoint: str
input_arguments: dict[str, Any] | None = None
folder_path: str | None = None
folder_key: str | None = None


class WaitSystemAgent(BaseModel):
"""Model representing a wait system agent job invocation."""

job_key: str
process_folder_path: str | None = None
process_folder_key: str | None = None


class DocumentExtraction(BaseModel):
"""Model representing a document extraction task creation."""

Expand Down
32 changes: 27 additions & 5 deletions src/uipath/platform/resume_triggers/_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
WaitJob,
WaitTask,
)
from uipath.platform.common.interrupt_models import InvokeSystemAgent, WaitSystemAgent
from uipath.platform.context_grounding import DeepRagStatus
from uipath.platform.errors import (
BatchTransformNotCompleteException,
Expand Down Expand Up @@ -387,7 +388,9 @@ def _determine_trigger_type(self, value: Any) -> UiPathResumeTriggerType:
"""
if isinstance(value, (CreateTask, WaitTask, CreateEscalation, WaitEscalation)):
return UiPathResumeTriggerType.TASK
if isinstance(value, (InvokeProcess, WaitJob)):
if isinstance(
value, (InvokeProcess, WaitJob, InvokeSystemAgent, WaitSystemAgent)
):
return UiPathResumeTriggerType.JOB
if isinstance(value, (CreateDeepRag, WaitDeepRag)):
return UiPathResumeTriggerType.DEEP_RAG
Expand All @@ -411,7 +414,9 @@ def _determine_trigger_name(self, value: Any) -> UiPathResumeTriggerName:
return UiPathResumeTriggerName.ESCALATION
if isinstance(value, (CreateTask, WaitTask)):
return UiPathResumeTriggerName.TASK
if isinstance(value, (InvokeProcess, WaitJob)):
if isinstance(
value, (InvokeProcess, WaitJob, InvokeSystemAgent, WaitSystemAgent)
):
return UiPathResumeTriggerName.JOB
if isinstance(value, (CreateDeepRag, WaitDeepRag)):
return UiPathResumeTriggerName.DEEP_RAG
Expand Down Expand Up @@ -546,15 +551,21 @@ async def _handle_job_trigger(
"""Handle job-type resume triggers.

Args:
value: The suspend value (InvokeProcess or WaitJob)
value: The suspend value (InvokeProcess, WaitJob, InvokeSystemAgent, WaitSystemAgent)
resume_trigger: The resume trigger to populate
uipath: The UiPath client instance
"""
resume_trigger.folder_path = value.process_folder_path
resume_trigger.folder_key = value.process_folder_key
if isinstance(value, InvokeSystemAgent):
resume_trigger.folder_path = value.folder_path
resume_trigger.folder_key = value.folder_key
else:
resume_trigger.folder_path = value.process_folder_path
resume_trigger.folder_key = value.process_folder_key

if isinstance(value, WaitJob):
resume_trigger.item_key = value.job.key
elif isinstance(value, WaitSystemAgent):
resume_trigger.item_key = value.job_key
elif isinstance(value, InvokeProcess):
job = await uipath.processes.invoke_async(
name=value.name,
Expand All @@ -565,6 +576,17 @@ async def _handle_job_trigger(
if not job:
raise Exception("Failed to invoke process")
resume_trigger.item_key = job.key
elif isinstance(value, InvokeSystemAgent):
job_key = await uipath.agenthub.invoke_system_agent_async(
agent_name=value.agent_name,
entrypoint=value.entrypoint,
input_arguments=value.input_arguments,
folder_path=value.folder_path,
folder_key=value.folder_key,
)
if not job_key:
raise Exception("Failed to invoke system agent")
resume_trigger.item_key = job_key

def _handle_api_trigger(
self, value: Any, resume_trigger: UiPathResumeTrigger
Expand Down
Loading
Loading