Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
"""Shared factory for PIMs read tools.

All read-only PIMs tools (case state, case plan, case entity, execution trace)
share the same shape: ``GET`` a PIMs endpoint scoped by ``instanceId``, accept
an optional ``folderKey`` override, return the parsed JSON body, and map known
HTTP errors to friendly :class:`AgentRuntimeError` instances. Only the path
template and the human-readable subject (e.g. "case plan") differ per tool.

This module provides that shared builder. Each per-tool file collapses to a
path constant plus a thin call to :func:`build_pims_read_tool`.
"""

from typing import Any

from langchain_core.tools import StructuredTool
from uipath.agent.models.agent import AgentInternalToolResourceConfig
from uipath.eval.mocks import mockable
from uipath.platform import UiPath
from uipath.platform.errors import EnrichedException
from uipath.runtime.errors import UiPathErrorCategory

from uipath_langchain.agent.exceptions import raise_for_enriched
from uipath_langchain.agent.react.jsonschema_pydantic_converter import create_model
from uipath_langchain.agent.tools.structured_tool_with_argument_properties import (
StructuredToolWithArgumentProperties,
)
from uipath_langchain.agent.tools.utils import sanitize_tool_name


def _build_error_map(
subject: str,
) -> dict[tuple[int, str | None], tuple[str, UiPathErrorCategory]]:
"""Build the standard PIMs-read error map for the given subject.

``subject`` is the noun used in 401/403 messages (e.g. "case plan",
"execution trace"). The 404 message stays generic since the missing thing
is always the case instance itself.
"""
return {
(404, None): (
"Case instance not found for tool '{tool}': {message}",
UiPathErrorCategory.USER,
),
(401, None): (
f"Unauthorized when fetching {subject} for tool '{{tool}}': {{message}}",
UiPathErrorCategory.SYSTEM,
),
(403, None): (
f"Forbidden when fetching {subject} for tool '{{tool}}': {{message}}",
UiPathErrorCategory.USER,
),
}


def build_pims_read_tool(
resource: AgentInternalToolResourceConfig,
*,
path_template: str,
subject: str,
) -> StructuredTool:
"""Build a read-only PIMs GET tool.

The tool takes an LLM-supplied ``instanceId`` and an optional ``folderKey``
override, calls the PIMs endpoint at ``path_template`` (with the
``{instance_id}`` placeholder filled in), and returns the parsed JSON body.
Known 4xx errors map to friendly :class:`AgentRuntimeError` instances.

Args:
resource: The tool resource configuration (name, description, input
and output schemas, argument properties).
path_template: The PIMs path with an ``{instance_id}`` placeholder
(e.g. ``"pims_/api/v1/cases/{instance_id}/case-rules"``).
subject: The noun used in 401/403 error messages
(e.g. ``"case plan"``, ``"execution trace"``).

Returns:
A :class:`StructuredTool` ready to register in the internal-tool
factory map.

Notes:
The folder key is taken from the optional ``folderKey`` argument when
provided, otherwise from the runtime's ``UIPATH_FOLDER_KEY`` env var
via :class:`uipath.platform.common.ApiClient`.
"""
tool_name = sanitize_tool_name(resource.name)
input_model = create_model(resource.input_schema)
output_model = create_model(resource.output_schema)
error_map = _build_error_map(subject)

@mockable(
name=resource.name,
description=resource.description,
input_schema=input_model.model_json_schema(),
output_schema=output_model.model_json_schema(),
)
async def tool_fn(**kwargs: Any) -> Any:
instance_id = kwargs.get("instanceId")
if not instance_id:
raise ValueError("Argument 'instanceId' is required")

folder_key_override = kwargs.get("folderKey")

client = UiPath()
url = path_template.format(instance_id=instance_id)
request_kwargs: dict[str, Any] = {}
if folder_key_override:
request_kwargs["headers"] = {"x-uipath-folderkey": folder_key_override}
else:
request_kwargs["include_folder_headers"] = True

try:
response = await client.api_client.request_async(
"GET", url, **request_kwargs
)
except EnrichedException as e:
raise_for_enriched(
e, error_map, title=tool_name, tool=tool_name
)
raise

return response.json()

return StructuredToolWithArgumentProperties(
name=tool_name,
description=resource.description,
args_schema=input_model,
coroutine=tool_fn,
output_type=output_model,
argument_properties=resource.argument_properties,
metadata={
"tool_type": resource.type.lower(),
"display_name": tool_name,
"args_schema": input_model,
"output_schema": output_model,
},
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Internal tool that fetches the case entity (business object, documents, comments) for a PIMs case."""

from langchain_core.language_models import BaseChatModel
from langchain_core.tools import StructuredTool
from uipath.agent.models.agent import AgentInternalToolResourceConfig

from ._pims_read_tool_factory import build_pims_read_tool

PIMS_CASE_ENTITY_PATH = "pims_/api/v1/cases/{instance_id}/case-json"


def create_get_case_entity_tool(
resource: AgentInternalToolResourceConfig, llm: BaseChatModel

Check warning on line 13 in src/uipath_langchain/agent/tools/internal_tools/get_case_entity_tool.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the unused function parameter "llm".

See more on https://sonarcloud.io/project/issues?id=UiPath_uipath-langchain-python&issues=AZ6Htj4HVWINsCiRgaCk&open=AZ6Htj4HVWINsCiRgaCk&pullRequest=879
) -> StructuredTool:
"""Create the GetCaseEntity internal tool.

Returns the case-json payload from the PIMs ``case-json`` endpoint. Shared
HTTP/auth/folder/error logic lives in :func:`build_pims_read_tool`.

Note: as of testing on alpha, ``case-json`` returns the case **definition**
(id, version, metadata, stages) rather than the business object / linked
documents / comments. The "entity" naming is provisional pending the CM
team confirming the right surface.
"""
return build_pims_read_tool(
resource,
path_template=PIMS_CASE_ENTITY_PATH,
subject="case entity",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Internal tool that fetches the case plan (stages, tasks, rules) for a PIMs case."""

from langchain_core.language_models import BaseChatModel
from langchain_core.tools import StructuredTool
from uipath.agent.models.agent import AgentInternalToolResourceConfig

from ._pims_read_tool_factory import build_pims_read_tool

PIMS_CASE_PLAN_PATH = "pims_/api/v1/cases/{instance_id}/case-rules"


def create_get_case_plan_tool(
resource: AgentInternalToolResourceConfig, llm: BaseChatModel

Check warning on line 13 in src/uipath_langchain/agent/tools/internal_tools/get_case_plan_tool.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the unused function parameter "llm".

See more on https://sonarcloud.io/project/issues?id=UiPath_uipath-langchain-python&issues=AZ6Htj32VWINsCiRgaCj&open=AZ6Htj32VWINsCiRgaCj&pullRequest=879
) -> StructuredTool:
"""Create the GetCasePlan internal tool.

Returns the full case plan from the PIMs ``case-rules`` endpoint: every
stage and task with entry/exit rules, ``isRequired``, and
``shouldRunOnlyOnce``. Shared HTTP/auth/folder/error logic lives in
:func:`build_pims_read_tool`.
"""
return build_pims_read_tool(
resource,
path_template=PIMS_CASE_PLAN_PATH,
subject="case plan",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Internal tool that fetches the current state of a PIMs case instance."""

from langchain_core.language_models import BaseChatModel
from langchain_core.tools import StructuredTool
from uipath.agent.models.agent import AgentInternalToolResourceConfig

from ._pims_read_tool_factory import build_pims_read_tool

PIMS_INSTANCE_PATH = "pims_/api/v1/instances/{instance_id}"


def create_get_case_state_tool(
resource: AgentInternalToolResourceConfig, llm: BaseChatModel

Check warning on line 13 in src/uipath_langchain/agent/tools/internal_tools/get_case_state_tool.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the unused function parameter "llm".

See more on https://sonarcloud.io/project/issues?id=UiPath_uipath-langchain-python&issues=AZ5rb8UiamSfYn3kyAf4&open=AZ5rb8UiamSfYn3kyAf4&pullRequest=879
) -> StructuredTool:
"""Create the GetCaseState internal tool.

Returns the case instance state from the PIMs ``/v1/instances/{id}``
endpoint. Shared HTTP/auth/folder/error logic lives in
:func:`build_pims_read_tool`.
"""
return build_pims_read_tool(
resource,
path_template=PIMS_INSTANCE_PATH,
subject="case state",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Internal tool that fetches the execution trace (audit trail) for a PIMs case."""

from langchain_core.language_models import BaseChatModel
from langchain_core.tools import StructuredTool
from uipath.agent.models.agent import AgentInternalToolResourceConfig

from ._pims_read_tool_factory import build_pims_read_tool

PIMS_EXECUTION_TRACE_PATH = (
"pims_/api/v2/element-executions/case-instances/{instance_id}"
)


def create_get_execution_trace_tool(
resource: AgentInternalToolResourceConfig, llm: BaseChatModel

Check warning on line 15 in src/uipath_langchain/agent/tools/internal_tools/get_execution_trace_tool.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the unused function parameter "llm".

See more on https://sonarcloud.io/project/issues?id=UiPath_uipath-langchain-python&issues=AZ6Htj0dVWINsCiRgaCi&open=AZ6Htj0dVWINsCiRgaCi&pullRequest=879
) -> StructuredTool:
"""Create the GetExecutionTrace internal tool.

Returns the audit trail (``elementExecutions[]``, ``traceId``, etc.) from
the PIMs ``v2/element-executions/case-instances`` endpoint. Shared
HTTP/auth/folder/error logic lives in :func:`build_pims_read_tool`.
"""
return build_pims_read_tool(
resource,
path_template=PIMS_EXECUTION_TRACE_PATH,
subject="execution trace",
)
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
from .analyze_files_tool import create_analyze_file_tool
from .batch_transform_tool import create_batch_transform_tool
from .deeprag_tool import create_deeprag_tool
from .get_case_entity_tool import create_get_case_entity_tool
from .get_case_plan_tool import create_get_case_plan_tool
from .get_case_state_tool import create_get_case_state_tool
from .get_execution_trace_tool import create_get_execution_trace_tool

_INTERNAL_TOOL_HANDLERS: dict[
AgentInternalToolType,
Expand All @@ -37,6 +41,10 @@
AgentInternalToolType.ANALYZE_FILES: create_analyze_file_tool,
AgentInternalToolType.DEEP_RAG: create_deeprag_tool,
AgentInternalToolType.BATCH_TRANSFORM: create_batch_transform_tool,
AgentInternalToolType.GET_CASE_STATE: create_get_case_state_tool,
AgentInternalToolType.GET_CASE_PLAN: create_get_case_plan_tool,
AgentInternalToolType.GET_CASE_ENTITY: create_get_case_entity_tool,
AgentInternalToolType.GET_EXECUTION_TRACE: create_get_execution_trace_tool,
}


Expand Down
Loading
Loading