uipath-langchain 0.0.133__py3-none-any.whl → 0.1.24__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- uipath_langchain/_cli/cli_init.py +130 -191
- uipath_langchain/_cli/cli_new.py +2 -3
- uipath_langchain/_resources/AGENTS.md +21 -0
- uipath_langchain/_resources/REQUIRED_STRUCTURE.md +92 -0
- uipath_langchain/_tracing/__init__.py +3 -2
- uipath_langchain/_tracing/_instrument_traceable.py +11 -12
- uipath_langchain/_utils/_request_mixin.py +327 -51
- uipath_langchain/_utils/_settings.py +2 -2
- uipath_langchain/agent/exceptions/__init__.py +6 -0
- uipath_langchain/agent/exceptions/exceptions.py +11 -0
- uipath_langchain/agent/guardrails/__init__.py +21 -0
- uipath_langchain/agent/guardrails/actions/__init__.py +11 -0
- uipath_langchain/agent/guardrails/actions/base_action.py +23 -0
- uipath_langchain/agent/guardrails/actions/block_action.py +41 -0
- uipath_langchain/agent/guardrails/actions/escalate_action.py +274 -0
- uipath_langchain/agent/guardrails/actions/log_action.py +57 -0
- uipath_langchain/agent/guardrails/guardrail_nodes.py +125 -0
- uipath_langchain/agent/guardrails/guardrails_factory.py +70 -0
- uipath_langchain/agent/guardrails/guardrails_subgraph.py +247 -0
- uipath_langchain/agent/guardrails/types.py +20 -0
- uipath_langchain/agent/react/__init__.py +14 -0
- uipath_langchain/agent/react/agent.py +113 -0
- uipath_langchain/agent/react/constants.py +2 -0
- uipath_langchain/agent/react/init_node.py +20 -0
- uipath_langchain/agent/react/llm_node.py +43 -0
- uipath_langchain/agent/react/router.py +97 -0
- uipath_langchain/agent/react/terminate_node.py +82 -0
- uipath_langchain/agent/react/tools/__init__.py +7 -0
- uipath_langchain/agent/react/tools/tools.py +50 -0
- uipath_langchain/agent/react/types.py +39 -0
- uipath_langchain/agent/react/utils.py +49 -0
- uipath_langchain/agent/tools/__init__.py +17 -0
- uipath_langchain/agent/tools/context_tool.py +53 -0
- uipath_langchain/agent/tools/escalation_tool.py +111 -0
- uipath_langchain/agent/tools/integration_tool.py +181 -0
- uipath_langchain/agent/tools/process_tool.py +49 -0
- uipath_langchain/agent/tools/static_args.py +138 -0
- uipath_langchain/agent/tools/structured_tool_with_output_type.py +14 -0
- uipath_langchain/agent/tools/tool_factory.py +45 -0
- uipath_langchain/agent/tools/tool_node.py +22 -0
- uipath_langchain/agent/tools/utils.py +11 -0
- uipath_langchain/chat/__init__.py +4 -0
- uipath_langchain/chat/bedrock.py +187 -0
- uipath_langchain/chat/gemini.py +330 -0
- uipath_langchain/chat/mapper.py +309 -0
- uipath_langchain/chat/models.py +248 -35
- uipath_langchain/chat/openai.py +132 -0
- uipath_langchain/chat/supported_models.py +42 -0
- uipath_langchain/embeddings/embeddings.py +131 -34
- uipath_langchain/middlewares.py +0 -6
- uipath_langchain/retrievers/context_grounding_retriever.py +7 -9
- uipath_langchain/runtime/__init__.py +36 -0
- uipath_langchain/runtime/_serialize.py +46 -0
- uipath_langchain/runtime/config.py +61 -0
- uipath_langchain/runtime/errors.py +43 -0
- uipath_langchain/runtime/factory.py +315 -0
- uipath_langchain/runtime/graph.py +159 -0
- uipath_langchain/runtime/runtime.py +453 -0
- uipath_langchain/runtime/schema.py +349 -0
- uipath_langchain/runtime/storage.py +115 -0
- uipath_langchain/vectorstores/context_grounding_vectorstore.py +90 -110
- {uipath_langchain-0.0.133.dist-info → uipath_langchain-0.1.24.dist-info}/METADATA +42 -22
- uipath_langchain-0.1.24.dist-info/RECORD +76 -0
- {uipath_langchain-0.0.133.dist-info → uipath_langchain-0.1.24.dist-info}/WHEEL +1 -1
- uipath_langchain-0.1.24.dist-info/entry_points.txt +5 -0
- uipath_langchain/_cli/_runtime/_context.py +0 -21
- uipath_langchain/_cli/_runtime/_conversation.py +0 -298
- uipath_langchain/_cli/_runtime/_exception.py +0 -17
- uipath_langchain/_cli/_runtime/_input.py +0 -139
- uipath_langchain/_cli/_runtime/_output.py +0 -234
- uipath_langchain/_cli/_runtime/_runtime.py +0 -379
- uipath_langchain/_cli/_utils/_graph.py +0 -199
- uipath_langchain/_cli/cli_dev.py +0 -44
- uipath_langchain/_cli/cli_eval.py +0 -78
- uipath_langchain/_cli/cli_run.py +0 -82
- uipath_langchain/_tracing/_oteladapter.py +0 -222
- uipath_langchain/_tracing/_utils.py +0 -28
- uipath_langchain/builder/agent_config.py +0 -191
- uipath_langchain/tools/preconfigured.py +0 -191
- uipath_langchain-0.0.133.dist-info/RECORD +0 -41
- uipath_langchain-0.0.133.dist-info/entry_points.txt +0 -2
- /uipath_langchain/{tools/__init__.py → py.typed} +0 -0
- {uipath_langchain-0.0.133.dist-info → uipath_langchain-0.1.24.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Control flow tools for agent execution."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from langchain_core.tools import BaseTool, StructuredTool
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
from uipath.agent.react import (
|
|
8
|
+
END_EXECUTION_TOOL,
|
|
9
|
+
RAISE_ERROR_TOOL,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def create_end_execution_tool(
|
|
14
|
+
agent_output_schema: type[BaseModel] | None = None,
|
|
15
|
+
) -> StructuredTool:
|
|
16
|
+
"""Never executed - routing intercepts and extracts args for successful termination."""
|
|
17
|
+
input_schema = agent_output_schema or END_EXECUTION_TOOL.args_schema
|
|
18
|
+
|
|
19
|
+
async def end_execution_fn(**kwargs: Any) -> dict[str, Any]:
|
|
20
|
+
return kwargs
|
|
21
|
+
|
|
22
|
+
return StructuredTool(
|
|
23
|
+
name=END_EXECUTION_TOOL.name,
|
|
24
|
+
description=END_EXECUTION_TOOL.description,
|
|
25
|
+
args_schema=input_schema,
|
|
26
|
+
coroutine=end_execution_fn,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def create_raise_error_tool() -> StructuredTool:
|
|
31
|
+
"""Never executed - routing intercepts and raises AgentTerminationException."""
|
|
32
|
+
|
|
33
|
+
async def raise_error_fn(**kwargs: Any) -> dict[str, Any]:
|
|
34
|
+
return kwargs
|
|
35
|
+
|
|
36
|
+
return StructuredTool(
|
|
37
|
+
name=RAISE_ERROR_TOOL.name,
|
|
38
|
+
description=RAISE_ERROR_TOOL.description,
|
|
39
|
+
args_schema=RAISE_ERROR_TOOL.args_schema,
|
|
40
|
+
coroutine=raise_error_fn,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def create_flow_control_tools(
|
|
45
|
+
agent_output_schema: type[BaseModel] | None = None,
|
|
46
|
+
) -> list[BaseTool]:
|
|
47
|
+
return [
|
|
48
|
+
create_end_execution_tool(agent_output_schema),
|
|
49
|
+
create_raise_error_tool(),
|
|
50
|
+
]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
from typing import Annotated
|
|
3
|
+
|
|
4
|
+
from langchain_core.messages import AnyMessage
|
|
5
|
+
from langgraph.graph.message import add_messages
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AgentTerminationSource(StrEnum):
|
|
10
|
+
ESCALATION = "escalation"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AgentTermination(BaseModel):
|
|
14
|
+
"""Agent Graph Termination model."""
|
|
15
|
+
|
|
16
|
+
source: AgentTerminationSource
|
|
17
|
+
title: str
|
|
18
|
+
detail: str = ""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AgentGraphState(BaseModel):
|
|
22
|
+
"""Agent Graph state for standard loop execution."""
|
|
23
|
+
|
|
24
|
+
messages: Annotated[list[AnyMessage], add_messages] = []
|
|
25
|
+
termination: AgentTermination | None = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AgentGraphNode(StrEnum):
|
|
29
|
+
INIT = "init"
|
|
30
|
+
AGENT = "agent"
|
|
31
|
+
LLM = "llm"
|
|
32
|
+
TOOLS = "tools"
|
|
33
|
+
TERMINATE = "terminate"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AgentGraphConfig(BaseModel):
|
|
37
|
+
recursion_limit: int = Field(
|
|
38
|
+
default=50, ge=1, description="Maximum recursion limit for the agent graph"
|
|
39
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""ReAct Agent loop utilities."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Sequence
|
|
4
|
+
|
|
5
|
+
from langchain_core.messages import AIMessage, BaseMessage
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
from uipath.agent.react import END_EXECUTION_TOOL
|
|
8
|
+
from uipath.utils.dynamic_schema import jsonschema_to_pydantic
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def resolve_input_model(
|
|
12
|
+
input_schema: dict[str, Any] | None,
|
|
13
|
+
) -> type[BaseModel]:
|
|
14
|
+
"""Resolve the input model from the input schema."""
|
|
15
|
+
if input_schema:
|
|
16
|
+
return jsonschema_to_pydantic(input_schema)
|
|
17
|
+
|
|
18
|
+
return BaseModel
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def resolve_output_model(
|
|
22
|
+
output_schema: dict[str, Any] | None,
|
|
23
|
+
) -> type[BaseModel]:
|
|
24
|
+
"""Fallback to default end_execution tool schema when no agent output schema is provided."""
|
|
25
|
+
if output_schema:
|
|
26
|
+
return jsonschema_to_pydantic(output_schema)
|
|
27
|
+
|
|
28
|
+
return END_EXECUTION_TOOL.args_schema
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def count_successive_completions(messages: Sequence[BaseMessage]) -> int:
|
|
32
|
+
"""Count consecutive AIMessages without tool calls at end of message history."""
|
|
33
|
+
if not messages:
|
|
34
|
+
return 0
|
|
35
|
+
|
|
36
|
+
count = 0
|
|
37
|
+
for message in reversed(messages):
|
|
38
|
+
if not isinstance(message, AIMessage):
|
|
39
|
+
break
|
|
40
|
+
|
|
41
|
+
if message.tool_calls:
|
|
42
|
+
break
|
|
43
|
+
|
|
44
|
+
if not message.content:
|
|
45
|
+
break
|
|
46
|
+
|
|
47
|
+
count += 1
|
|
48
|
+
|
|
49
|
+
return count
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Tool creation and management for LowCode agents."""
|
|
2
|
+
|
|
3
|
+
from .context_tool import create_context_tool
|
|
4
|
+
from .integration_tool import create_integration_tool
|
|
5
|
+
from .process_tool import create_process_tool
|
|
6
|
+
from .tool_factory import (
|
|
7
|
+
create_tools_from_resources,
|
|
8
|
+
)
|
|
9
|
+
from .tool_node import create_tool_node
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"create_tools_from_resources",
|
|
13
|
+
"create_tool_node",
|
|
14
|
+
"create_context_tool",
|
|
15
|
+
"create_process_tool",
|
|
16
|
+
"create_integration_tool",
|
|
17
|
+
]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Context tool creation for semantic index retrieval."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from langchain_core.documents import Document
|
|
6
|
+
from langchain_core.tools import StructuredTool
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
from uipath.agent.models.agent import AgentContextResourceConfig
|
|
9
|
+
from uipath.eval.mocks import mockable
|
|
10
|
+
|
|
11
|
+
from uipath_langchain.retrievers import ContextGroundingRetriever
|
|
12
|
+
|
|
13
|
+
from .structured_tool_with_output_type import StructuredToolWithOutputType
|
|
14
|
+
from .utils import sanitize_tool_name
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def create_context_tool(resource: AgentContextResourceConfig) -> StructuredTool:
|
|
18
|
+
tool_name = sanitize_tool_name(resource.name)
|
|
19
|
+
retriever = ContextGroundingRetriever(
|
|
20
|
+
index_name=resource.index_name,
|
|
21
|
+
folder_path=resource.folder_path,
|
|
22
|
+
number_of_results=resource.settings.result_count,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
class ContextInputSchemaModel(BaseModel):
|
|
26
|
+
query: str = Field(
|
|
27
|
+
..., description="The query to search for in the knowledge base"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
class ContextOutputSchemaModel(BaseModel):
|
|
31
|
+
documents: list[Document] = Field(
|
|
32
|
+
..., description="List of retrieved documents."
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
input_model = ContextInputSchemaModel
|
|
36
|
+
output_model = ContextOutputSchemaModel
|
|
37
|
+
|
|
38
|
+
@mockable(
|
|
39
|
+
name=resource.name,
|
|
40
|
+
description=resource.description,
|
|
41
|
+
input_schema=input_model.model_json_schema(),
|
|
42
|
+
output_schema=output_model.model_json_schema(),
|
|
43
|
+
)
|
|
44
|
+
async def context_tool_fn(query: str) -> dict[str, Any]:
|
|
45
|
+
return {"documents": await retriever.ainvoke(query)}
|
|
46
|
+
|
|
47
|
+
return StructuredToolWithOutputType(
|
|
48
|
+
name=tool_name,
|
|
49
|
+
description=resource.description,
|
|
50
|
+
args_schema=input_model,
|
|
51
|
+
coroutine=context_tool_fn,
|
|
52
|
+
output_type=output_model,
|
|
53
|
+
)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Escalation tool creation for Action Center integration."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from jsonschema_pydantic_converter import transform as create_model
|
|
7
|
+
from langchain.tools import ToolRuntime
|
|
8
|
+
from langchain_core.messages import ToolMessage
|
|
9
|
+
from langchain_core.tools import StructuredTool
|
|
10
|
+
from langgraph.types import Command, interrupt
|
|
11
|
+
from uipath.agent.models.agent import (
|
|
12
|
+
AgentEscalationChannel,
|
|
13
|
+
AgentEscalationRecipientType,
|
|
14
|
+
AgentEscalationResourceConfig,
|
|
15
|
+
)
|
|
16
|
+
from uipath.eval.mocks import mockable
|
|
17
|
+
from uipath.platform.common import CreateEscalation
|
|
18
|
+
|
|
19
|
+
from ..react.types import AgentGraphNode, AgentTerminationSource
|
|
20
|
+
from .utils import sanitize_tool_name
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class EscalationAction(str, Enum):
|
|
24
|
+
"""Actions that can be taken after an escalation completes."""
|
|
25
|
+
|
|
26
|
+
CONTINUE = "continue"
|
|
27
|
+
END = "end"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def create_escalation_tool(resource: AgentEscalationResourceConfig) -> StructuredTool:
|
|
31
|
+
"""Uses interrupt() for Action Center human-in-the-loop."""
|
|
32
|
+
|
|
33
|
+
tool_name: str = f"escalate_{sanitize_tool_name(resource.name)}"
|
|
34
|
+
channel: AgentEscalationChannel = resource.channels[0]
|
|
35
|
+
|
|
36
|
+
input_model: Any = create_model(channel.input_schema)
|
|
37
|
+
output_model: Any = create_model(channel.output_schema)
|
|
38
|
+
|
|
39
|
+
assignee: str | None = (
|
|
40
|
+
channel.recipients[0].value
|
|
41
|
+
if channel.recipients
|
|
42
|
+
and channel.recipients[0].type == AgentEscalationRecipientType.USER_EMAIL
|
|
43
|
+
else None
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
@mockable(
|
|
47
|
+
name=resource.name,
|
|
48
|
+
description=resource.description,
|
|
49
|
+
input_schema=input_model.model_json_schema(),
|
|
50
|
+
output_schema=output_model.model_json_schema(),
|
|
51
|
+
)
|
|
52
|
+
async def escalation_tool_fn(
|
|
53
|
+
runtime: ToolRuntime, **kwargs: Any
|
|
54
|
+
) -> Command[Any] | Any:
|
|
55
|
+
task_title = channel.task_title or "Escalation Task"
|
|
56
|
+
|
|
57
|
+
result = interrupt(
|
|
58
|
+
CreateEscalation(
|
|
59
|
+
title=task_title,
|
|
60
|
+
data=kwargs,
|
|
61
|
+
assignee=assignee,
|
|
62
|
+
app_name=channel.properties.app_name,
|
|
63
|
+
app_folder_path=channel.properties.folder_name,
|
|
64
|
+
app_version=channel.properties.app_version,
|
|
65
|
+
priority=channel.priority,
|
|
66
|
+
labels=channel.labels,
|
|
67
|
+
is_actionable_message_enabled=channel.properties.is_actionable_message_enabled,
|
|
68
|
+
actionable_message_metadata=channel.properties.actionable_message_meta_data,
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
escalation_action = getattr(result, "action", None)
|
|
73
|
+
escalation_output = getattr(result, "data", {})
|
|
74
|
+
|
|
75
|
+
outcome = (
|
|
76
|
+
channel.outcome_mapping.get(escalation_action)
|
|
77
|
+
if channel.outcome_mapping and escalation_action
|
|
78
|
+
else None
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if outcome == EscalationAction.END:
|
|
82
|
+
output_detail = f"Escalation output: {escalation_output}"
|
|
83
|
+
termination_title = f"Agent run ended based on escalation outcome {outcome} with directive {escalation_action}"
|
|
84
|
+
|
|
85
|
+
return Command(
|
|
86
|
+
update={
|
|
87
|
+
"messages": [
|
|
88
|
+
ToolMessage(
|
|
89
|
+
content=f"{termination_title}. {output_detail}",
|
|
90
|
+
tool_call_id=runtime.tool_call_id,
|
|
91
|
+
)
|
|
92
|
+
],
|
|
93
|
+
"termination": {
|
|
94
|
+
"source": AgentTerminationSource.ESCALATION,
|
|
95
|
+
"title": termination_title,
|
|
96
|
+
"detail": output_detail,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
goto=AgentGraphNode.TERMINATE,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return escalation_output
|
|
103
|
+
|
|
104
|
+
tool = StructuredTool(
|
|
105
|
+
name=tool_name,
|
|
106
|
+
description=resource.description,
|
|
107
|
+
args_schema=input_model,
|
|
108
|
+
coroutine=escalation_tool_fn,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
return tool
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Process tool creation for UiPath process execution."""
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from jsonschema_pydantic_converter import transform as create_model
|
|
7
|
+
from langchain.tools import ToolRuntime
|
|
8
|
+
from langchain_core.tools import StructuredTool
|
|
9
|
+
from uipath.agent.models.agent import AgentIntegrationToolResourceConfig
|
|
10
|
+
from uipath.eval.mocks import mockable
|
|
11
|
+
from uipath.platform import UiPath
|
|
12
|
+
from uipath.platform.connections import ActivityMetadata, ActivityParameterLocationInfo
|
|
13
|
+
|
|
14
|
+
from .static_args import handle_static_args
|
|
15
|
+
from .structured_tool_with_output_type import StructuredToolWithOutputType
|
|
16
|
+
from .utils import sanitize_tool_name
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def remove_asterisk_from_properties(fields: dict[str, Any]) -> dict[str, Any]:
|
|
20
|
+
"""
|
|
21
|
+
Fix bug in integration service.
|
|
22
|
+
"""
|
|
23
|
+
fields = copy.deepcopy(fields)
|
|
24
|
+
|
|
25
|
+
def fix_types(props: dict[str, Any]) -> None:
|
|
26
|
+
type_ = props.get("type", None)
|
|
27
|
+
if "$ref" in props:
|
|
28
|
+
props["$ref"] = props["$ref"].replace("[*]", "")
|
|
29
|
+
if type_ == "object":
|
|
30
|
+
properties = {}
|
|
31
|
+
for k, v in props.get("properties", {}).items():
|
|
32
|
+
# Remove asterisks!
|
|
33
|
+
k = k.replace("[*]", "")
|
|
34
|
+
properties[k] = v
|
|
35
|
+
if isinstance(v, dict):
|
|
36
|
+
fix_types(v)
|
|
37
|
+
if "properties" in props:
|
|
38
|
+
props["properties"] = properties
|
|
39
|
+
if type_ == "array":
|
|
40
|
+
fix_types(props.get("items", {}))
|
|
41
|
+
|
|
42
|
+
definitions = {}
|
|
43
|
+
for k, value in fields.get("$defs", fields.get("definitions", {})).items():
|
|
44
|
+
k = k.replace("[*]", "")
|
|
45
|
+
definitions[k] = value
|
|
46
|
+
fix_types(value)
|
|
47
|
+
if "definitions" in fields:
|
|
48
|
+
fields["definitions"] = definitions
|
|
49
|
+
|
|
50
|
+
fix_types(fields)
|
|
51
|
+
return fields
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def extract_top_level_field(param_name: str) -> str:
|
|
55
|
+
"""Extract the top-level field name from a jsonpath parameter name.
|
|
56
|
+
|
|
57
|
+
Examples:
|
|
58
|
+
metadata.field.test -> metadata
|
|
59
|
+
attachments[*] -> attachments
|
|
60
|
+
attachments[0].filename -> attachments
|
|
61
|
+
simple_field -> simple_field
|
|
62
|
+
"""
|
|
63
|
+
# Split by '.' to get the first part
|
|
64
|
+
first_part = param_name.split(".")[0]
|
|
65
|
+
|
|
66
|
+
# Remove array notation if present (e.g., "attachments[*]" -> "attachments")
|
|
67
|
+
if "[" in first_part:
|
|
68
|
+
first_part = first_part.split("[")[0]
|
|
69
|
+
|
|
70
|
+
return first_part
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def convert_to_activity_metadata(
|
|
74
|
+
resource: AgentIntegrationToolResourceConfig,
|
|
75
|
+
) -> ActivityMetadata:
|
|
76
|
+
"""Convert AgentIntegrationToolResourceConfig to ActivityMetadata."""
|
|
77
|
+
|
|
78
|
+
# normalize HTTP method (GETBYID -> GET)
|
|
79
|
+
http_method = resource.properties.method
|
|
80
|
+
if http_method == "GETBYID":
|
|
81
|
+
http_method = "GET"
|
|
82
|
+
|
|
83
|
+
param_location_info = ActivityParameterLocationInfo()
|
|
84
|
+
# because of nested fields and array notation, use a set to avoid duplicates
|
|
85
|
+
body_fields_set = set()
|
|
86
|
+
|
|
87
|
+
# mapping parameter locations
|
|
88
|
+
for param in resource.properties.parameters:
|
|
89
|
+
param_name = param.name
|
|
90
|
+
field_location = param.field_location
|
|
91
|
+
|
|
92
|
+
if field_location == "query":
|
|
93
|
+
param_location_info.query_params.append(param_name)
|
|
94
|
+
elif field_location == "path":
|
|
95
|
+
param_location_info.path_params.append(param_name)
|
|
96
|
+
elif field_location == "header":
|
|
97
|
+
param_location_info.header_params.append(param_name)
|
|
98
|
+
elif field_location in ("multipart", "file"):
|
|
99
|
+
param_location_info.multipart_params.append(param_name)
|
|
100
|
+
elif field_location == "body":
|
|
101
|
+
# extract top-level field from jsonpath parameter name
|
|
102
|
+
top_level_field = extract_top_level_field(param_name)
|
|
103
|
+
body_fields_set.add(top_level_field)
|
|
104
|
+
else:
|
|
105
|
+
# default to body field - extract top-level field
|
|
106
|
+
top_level_field = extract_top_level_field(param_name)
|
|
107
|
+
body_fields_set.add(top_level_field)
|
|
108
|
+
|
|
109
|
+
param_location_info.body_fields = list(body_fields_set)
|
|
110
|
+
|
|
111
|
+
# determine content type
|
|
112
|
+
content_type = "application/json"
|
|
113
|
+
if resource.properties.body_structure is not None:
|
|
114
|
+
shorthand_type = resource.properties.body_structure.get("contentType", "json")
|
|
115
|
+
if shorthand_type == "multipart":
|
|
116
|
+
content_type = "multipart/form-data"
|
|
117
|
+
|
|
118
|
+
return ActivityMetadata(
|
|
119
|
+
object_path=resource.properties.tool_path,
|
|
120
|
+
method_name=http_method,
|
|
121
|
+
content_type=content_type,
|
|
122
|
+
parameter_location_info=param_location_info,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def create_integration_tool(
|
|
127
|
+
resource: AgentIntegrationToolResourceConfig,
|
|
128
|
+
) -> StructuredTool:
|
|
129
|
+
"""Creates a StructuredTool for invoking an Integration Service connector activity."""
|
|
130
|
+
tool_name: str = sanitize_tool_name(resource.name)
|
|
131
|
+
if resource.properties.connection.id is None:
|
|
132
|
+
raise ValueError("Connection ID cannot be None for integration tool.")
|
|
133
|
+
connection_id: str = resource.properties.connection.id
|
|
134
|
+
|
|
135
|
+
activity_metadata = convert_to_activity_metadata(resource)
|
|
136
|
+
|
|
137
|
+
input_model = create_model(resource.input_schema)
|
|
138
|
+
# note: IS tools output schemas were recently added and are most likely not present in all resources
|
|
139
|
+
output_model: Any = (
|
|
140
|
+
create_model(remove_asterisk_from_properties(resource.output_schema))
|
|
141
|
+
if resource.output_schema
|
|
142
|
+
else create_model({"type": "object", "properties": {}})
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
sdk = UiPath()
|
|
146
|
+
|
|
147
|
+
@mockable(
|
|
148
|
+
name=resource.name,
|
|
149
|
+
description=resource.description,
|
|
150
|
+
input_schema=input_model.model_json_schema(),
|
|
151
|
+
output_schema=output_model.model_json_schema(),
|
|
152
|
+
)
|
|
153
|
+
async def integration_tool_fn(runtime: ToolRuntime, **kwargs: Any):
|
|
154
|
+
try:
|
|
155
|
+
# we manually validating here and not passing input_model to StructuredTool
|
|
156
|
+
# because langchain itself will block their own injected arguments (like runtime) if the model is strict
|
|
157
|
+
val_args = input_model.model_validate(kwargs)
|
|
158
|
+
args = handle_static_args(
|
|
159
|
+
resource=resource,
|
|
160
|
+
runtime=runtime,
|
|
161
|
+
input_args=val_args.model_dump(),
|
|
162
|
+
)
|
|
163
|
+
result = await sdk.connections.invoke_activity_async(
|
|
164
|
+
activity_metadata=activity_metadata,
|
|
165
|
+
connection_id=connection_id,
|
|
166
|
+
activity_input=args,
|
|
167
|
+
)
|
|
168
|
+
except Exception:
|
|
169
|
+
raise
|
|
170
|
+
|
|
171
|
+
return result
|
|
172
|
+
|
|
173
|
+
tool = StructuredToolWithOutputType(
|
|
174
|
+
name=tool_name,
|
|
175
|
+
description=resource.description,
|
|
176
|
+
args_schema=resource.input_schema,
|
|
177
|
+
coroutine=integration_tool_fn,
|
|
178
|
+
output_type=output_model,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
return tool
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Process tool creation for UiPath process execution."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from jsonschema_pydantic_converter import transform as create_model
|
|
6
|
+
from langchain_core.tools import StructuredTool
|
|
7
|
+
from langgraph.types import interrupt
|
|
8
|
+
from uipath.agent.models.agent import AgentProcessToolResourceConfig
|
|
9
|
+
from uipath.eval.mocks import mockable
|
|
10
|
+
from uipath.platform.common import InvokeProcess
|
|
11
|
+
|
|
12
|
+
from .structured_tool_with_output_type import StructuredToolWithOutputType
|
|
13
|
+
from .utils import sanitize_tool_name
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def create_process_tool(resource: AgentProcessToolResourceConfig) -> StructuredTool:
|
|
17
|
+
"""Uses interrupt() to suspend graph execution until process completes (handled by runtime)."""
|
|
18
|
+
tool_name: str = sanitize_tool_name(resource.name)
|
|
19
|
+
process_name = resource.properties.process_name
|
|
20
|
+
folder_path = resource.properties.folder_path
|
|
21
|
+
|
|
22
|
+
input_model: Any = create_model(resource.input_schema)
|
|
23
|
+
output_model: Any = create_model(resource.output_schema)
|
|
24
|
+
|
|
25
|
+
@mockable(
|
|
26
|
+
name=resource.name,
|
|
27
|
+
description=resource.description,
|
|
28
|
+
input_schema=input_model.model_json_schema(),
|
|
29
|
+
output_schema=output_model.model_json_schema(),
|
|
30
|
+
)
|
|
31
|
+
async def process_tool_fn(**kwargs: Any):
|
|
32
|
+
return interrupt(
|
|
33
|
+
InvokeProcess(
|
|
34
|
+
name=process_name,
|
|
35
|
+
input_arguments=kwargs,
|
|
36
|
+
process_folder_path=folder_path,
|
|
37
|
+
process_folder_key=None,
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
tool = StructuredToolWithOutputType(
|
|
42
|
+
name=tool_name,
|
|
43
|
+
description=resource.description,
|
|
44
|
+
args_schema=input_model,
|
|
45
|
+
coroutine=process_tool_fn,
|
|
46
|
+
output_type=output_model,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return tool
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Handles static arguments for tool calls."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List
|
|
4
|
+
|
|
5
|
+
from jsonpath_ng import parse # type: ignore[import-untyped]
|
|
6
|
+
from langchain.tools import ToolRuntime
|
|
7
|
+
from uipath.agent.models.agent import (
|
|
8
|
+
AgentIntegrationToolParameter,
|
|
9
|
+
AgentIntegrationToolResourceConfig,
|
|
10
|
+
BaseAgentResourceConfig,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def resolve_static_args(
|
|
15
|
+
resource: BaseAgentResourceConfig,
|
|
16
|
+
agent_input: Dict[str, Any],
|
|
17
|
+
) -> Dict[str, Any]:
|
|
18
|
+
"""Resolves static arguments for a given resource with a given input.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
resource: The agent resource configuration.
|
|
22
|
+
input: Othe input arguments passed to the agent.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
A dictionary of expanded arguments to be used in the tool call.
|
|
26
|
+
static_args: Dict[str, Any] = {}
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
if isinstance(resource, AgentIntegrationToolResourceConfig):
|
|
30
|
+
return resolve_integration_static_args(
|
|
31
|
+
resource.properties.parameters, agent_input
|
|
32
|
+
)
|
|
33
|
+
else:
|
|
34
|
+
return {} # to be implemented for other resource types in the future
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def resolve_integration_static_args(
|
|
38
|
+
parameters: List[AgentIntegrationToolParameter],
|
|
39
|
+
agent_input: Dict[str, Any],
|
|
40
|
+
) -> Dict[str, Any]:
|
|
41
|
+
"""Resolves static arguments for an integration tool resource.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
resource: The AgentIntegrationToolResourceConfig instance.
|
|
45
|
+
input: The input arguments passed to the agent.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
A dictionary of expanded static arguments for the integration tool.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
static_args: Dict[str, Any] = {}
|
|
52
|
+
for param in parameters:
|
|
53
|
+
value = None
|
|
54
|
+
|
|
55
|
+
# static parameter, use the defined static value
|
|
56
|
+
if param.field_variant == "static":
|
|
57
|
+
value = param.value
|
|
58
|
+
# argument parameter, extract value from agent input
|
|
59
|
+
elif param.field_variant == "argument":
|
|
60
|
+
if (
|
|
61
|
+
not isinstance(param.value, str)
|
|
62
|
+
or not param.value.startswith("{{")
|
|
63
|
+
or not param.value.endswith("}}")
|
|
64
|
+
):
|
|
65
|
+
raise ValueError(
|
|
66
|
+
f"Parameter value must be in the format '{{argument_name}}' when field_variant is 'argument', got {param.value}"
|
|
67
|
+
)
|
|
68
|
+
arg_name = param.value[2:-2].strip()
|
|
69
|
+
# currently only support top-level arguments
|
|
70
|
+
value = agent_input.get(arg_name)
|
|
71
|
+
|
|
72
|
+
if value is not None:
|
|
73
|
+
static_args[param.name] = value
|
|
74
|
+
|
|
75
|
+
return static_args
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def sanitize_for_serialization(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
79
|
+
"""Convert Pydantic models in args to dicts."""
|
|
80
|
+
converted_args: Dict[str, Any] = {}
|
|
81
|
+
for key, value in args.items():
|
|
82
|
+
# handle Pydantic model
|
|
83
|
+
if hasattr(value, "model_dump"):
|
|
84
|
+
converted_args[key] = value.model_dump()
|
|
85
|
+
|
|
86
|
+
elif isinstance(value, list):
|
|
87
|
+
# handle list of Pydantic models
|
|
88
|
+
converted_list = []
|
|
89
|
+
for item in value:
|
|
90
|
+
if hasattr(item, "model_dump"):
|
|
91
|
+
converted_list.append(item.model_dump())
|
|
92
|
+
else:
|
|
93
|
+
converted_list.append(item)
|
|
94
|
+
converted_args[key] = converted_list
|
|
95
|
+
|
|
96
|
+
# handle regular value or unexpected type
|
|
97
|
+
else:
|
|
98
|
+
converted_args[key] = value
|
|
99
|
+
return converted_args
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def apply_static_args(
|
|
103
|
+
static_args: Dict[str, Any],
|
|
104
|
+
kwargs: Dict[str, Any],
|
|
105
|
+
) -> Dict[str, Any]:
|
|
106
|
+
"""Applies static arguments to the given input arguments.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
static_args: Dictionary of static arguments {json_path: value} to apply.
|
|
110
|
+
kwargs: Original input arguments to the tool.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Merged input arguments with static arguments applied.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
sanitized_args = sanitize_for_serialization(kwargs)
|
|
117
|
+
for json_path, value in static_args.items():
|
|
118
|
+
expr = parse(json_path)
|
|
119
|
+
expr.update_or_create(sanitized_args, value)
|
|
120
|
+
|
|
121
|
+
return sanitized_args
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def handle_static_args(
|
|
125
|
+
resource: BaseAgentResourceConfig, runtime: ToolRuntime, input_args: Dict[str, Any]
|
|
126
|
+
) -> Dict[str, Any]:
|
|
127
|
+
"""Resolves and applies static arguments for a tool call.
|
|
128
|
+
Args:
|
|
129
|
+
resource: The agent resource configuration.
|
|
130
|
+
runtime: The tool runtime providing the current state.
|
|
131
|
+
input_args: The original input arguments to the tool.
|
|
132
|
+
Returns:
|
|
133
|
+
A dictionary of input arguments with static arguments applied.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
static_args = resolve_static_args(resource, dict(runtime.state))
|
|
137
|
+
merged_args = apply_static_args(static_args, input_args)
|
|
138
|
+
return merged_args
|