uipath-langchain 0.1.28__py3-none-any.whl → 0.3.1__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.
Files changed (60) hide show
  1. uipath_langchain/_cli/_templates/langgraph.json.template +2 -4
  2. uipath_langchain/_cli/cli_new.py +1 -2
  3. uipath_langchain/_utils/_request_mixin.py +8 -0
  4. uipath_langchain/_utils/_settings.py +3 -2
  5. uipath_langchain/agent/guardrails/__init__.py +0 -16
  6. uipath_langchain/agent/guardrails/actions/__init__.py +2 -0
  7. uipath_langchain/agent/guardrails/actions/block_action.py +1 -1
  8. uipath_langchain/agent/guardrails/actions/escalate_action.py +265 -138
  9. uipath_langchain/agent/guardrails/actions/filter_action.py +290 -0
  10. uipath_langchain/agent/guardrails/actions/log_action.py +1 -1
  11. uipath_langchain/agent/guardrails/guardrail_nodes.py +193 -42
  12. uipath_langchain/agent/guardrails/guardrails_factory.py +235 -14
  13. uipath_langchain/agent/guardrails/types.py +0 -12
  14. uipath_langchain/agent/guardrails/utils.py +177 -0
  15. uipath_langchain/agent/react/agent.py +24 -9
  16. uipath_langchain/agent/react/constants.py +1 -2
  17. uipath_langchain/agent/react/file_type_handler.py +123 -0
  18. uipath_langchain/agent/{guardrails → react/guardrails}/guardrails_subgraph.py +119 -25
  19. uipath_langchain/agent/react/init_node.py +16 -1
  20. uipath_langchain/agent/react/job_attachments.py +125 -0
  21. uipath_langchain/agent/react/json_utils.py +183 -0
  22. uipath_langchain/agent/react/jsonschema_pydantic_converter.py +76 -0
  23. uipath_langchain/agent/react/llm_node.py +41 -10
  24. uipath_langchain/agent/react/llm_with_files.py +76 -0
  25. uipath_langchain/agent/react/router.py +48 -37
  26. uipath_langchain/agent/react/types.py +19 -1
  27. uipath_langchain/agent/react/utils.py +30 -4
  28. uipath_langchain/agent/tools/__init__.py +7 -1
  29. uipath_langchain/agent/tools/context_tool.py +151 -1
  30. uipath_langchain/agent/tools/escalation_tool.py +46 -15
  31. uipath_langchain/agent/tools/integration_tool.py +20 -16
  32. uipath_langchain/agent/tools/internal_tools/__init__.py +5 -0
  33. uipath_langchain/agent/tools/internal_tools/analyze_files_tool.py +113 -0
  34. uipath_langchain/agent/tools/internal_tools/internal_tool_factory.py +54 -0
  35. uipath_langchain/agent/tools/mcp_tool.py +86 -0
  36. uipath_langchain/agent/tools/process_tool.py +8 -1
  37. uipath_langchain/agent/tools/static_args.py +18 -40
  38. uipath_langchain/agent/tools/tool_factory.py +13 -5
  39. uipath_langchain/agent/tools/tool_node.py +133 -4
  40. uipath_langchain/agent/tools/utils.py +31 -0
  41. uipath_langchain/agent/wrappers/__init__.py +6 -0
  42. uipath_langchain/agent/wrappers/job_attachment_wrapper.py +62 -0
  43. uipath_langchain/agent/wrappers/static_args_wrapper.py +34 -0
  44. uipath_langchain/chat/__init__.py +4 -0
  45. uipath_langchain/chat/bedrock.py +16 -0
  46. uipath_langchain/chat/mapper.py +60 -42
  47. uipath_langchain/chat/openai.py +56 -26
  48. uipath_langchain/chat/supported_models.py +9 -0
  49. uipath_langchain/chat/vertex.py +62 -46
  50. uipath_langchain/embeddings/embeddings.py +18 -12
  51. uipath_langchain/runtime/factory.py +10 -5
  52. uipath_langchain/runtime/runtime.py +38 -35
  53. uipath_langchain/runtime/schema.py +72 -16
  54. uipath_langchain/runtime/storage.py +178 -71
  55. {uipath_langchain-0.1.28.dist-info → uipath_langchain-0.3.1.dist-info}/METADATA +7 -4
  56. uipath_langchain-0.3.1.dist-info/RECORD +90 -0
  57. uipath_langchain-0.1.28.dist-info/RECORD +0 -76
  58. {uipath_langchain-0.1.28.dist-info → uipath_langchain-0.3.1.dist-info}/WHEEL +0 -0
  59. {uipath_langchain-0.1.28.dist-info → uipath_langchain-0.3.1.dist-info}/entry_points.txt +0 -0
  60. {uipath_langchain-0.1.28.dist-info → uipath_langchain-0.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -3,10 +3,9 @@
3
3
  from enum import Enum
4
4
  from typing import Any
5
5
 
6
- from jsonschema_pydantic_converter import transform as create_model
7
- from langchain.tools import ToolRuntime
8
6
  from langchain_core.messages import ToolMessage
9
- from langchain_core.tools import StructuredTool
7
+ from langchain_core.messages.tool import ToolCall
8
+ from langchain_core.tools import BaseTool, StructuredTool
10
9
  from langgraph.types import Command, interrupt
11
10
  from uipath.agent.models.agent import (
12
11
  AgentEscalationChannel,
@@ -16,7 +15,10 @@ from uipath.agent.models.agent import (
16
15
  from uipath.eval.mocks import mockable
17
16
  from uipath.platform.common import CreateEscalation
18
17
 
19
- from ..react.types import AgentGraphNode, AgentTerminationSource
18
+ from uipath_langchain.agent.react.jsonschema_pydantic_converter import create_model
19
+
20
+ from ..react.types import AgentGraphNode, AgentGraphState, AgentTerminationSource
21
+ from .tool_node import ToolWrapperMixin
20
22
  from .utils import sanitize_tool_name
21
23
 
22
24
 
@@ -27,7 +29,11 @@ class EscalationAction(str, Enum):
27
29
  END = "end"
28
30
 
29
31
 
30
- def create_escalation_tool(resource: AgentEscalationResourceConfig) -> StructuredTool:
32
+ class StructuredToolWithWrapper(StructuredTool, ToolWrapperMixin):
33
+ pass
34
+
35
+
36
+ def create_escalation_tool(resource: AgentEscalationResourceConfig) -> BaseTool:
31
37
  """Uses interrupt() for Action Center human-in-the-loop."""
32
38
 
33
39
  tool_name: str = f"escalate_{sanitize_tool_name(resource.name)}"
@@ -48,10 +54,9 @@ def create_escalation_tool(resource: AgentEscalationResourceConfig) -> Structure
48
54
  description=resource.description,
49
55
  input_schema=input_model.model_json_schema(),
50
56
  output_schema=output_model.model_json_schema(),
57
+ example_calls=channel.properties.example_calls,
51
58
  )
52
- async def escalation_tool_fn(
53
- runtime: ToolRuntime, **kwargs: Any
54
- ) -> Command[Any] | Any:
59
+ async def escalation_tool_fn(**kwargs: Any) -> dict[str, Any]:
55
60
  task_title = channel.task_title or "Escalation Task"
56
61
 
57
62
  result = interrupt(
@@ -72,22 +77,41 @@ def create_escalation_tool(resource: AgentEscalationResourceConfig) -> Structure
72
77
  escalation_action = getattr(result, "action", None)
73
78
  escalation_output = getattr(result, "data", {})
74
79
 
75
- outcome = (
80
+ outcome_str = (
76
81
  channel.outcome_mapping.get(escalation_action)
77
82
  if channel.outcome_mapping and escalation_action
78
83
  else None
79
84
  )
85
+ outcome = (
86
+ EscalationAction(outcome_str) if outcome_str else EscalationAction.CONTINUE
87
+ )
80
88
 
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}"
89
+ return {
90
+ "action": outcome,
91
+ "output": escalation_output,
92
+ "escalation_action": escalation_action,
93
+ }
94
+
95
+ async def escalation_wrapper(
96
+ tool: BaseTool,
97
+ call: ToolCall,
98
+ state: AgentGraphState,
99
+ ) -> dict[str, Any] | Command[Any]:
100
+ result = await tool.ainvoke(call["args"])
101
+
102
+ if result["action"] == EscalationAction.END:
103
+ output_detail = f"Escalation output: {result['output']}"
104
+ termination_title = (
105
+ f"Agent run ended based on escalation outcome {result['action']} "
106
+ f"with directive {result['escalation_action']}"
107
+ )
84
108
 
85
109
  return Command(
86
110
  update={
87
111
  "messages": [
88
112
  ToolMessage(
89
113
  content=f"{termination_title}. {output_detail}",
90
- tool_call_id=runtime.tool_call_id,
114
+ tool_call_id=call["id"],
91
115
  )
92
116
  ],
93
117
  "termination": {
@@ -99,13 +123,20 @@ def create_escalation_tool(resource: AgentEscalationResourceConfig) -> Structure
99
123
  goto=AgentGraphNode.TERMINATE,
100
124
  )
101
125
 
102
- return escalation_output
126
+ return result["output"]
103
127
 
104
- tool = StructuredTool(
128
+ tool = StructuredToolWithWrapper(
105
129
  name=tool_name,
106
130
  description=resource.description,
107
131
  args_schema=input_model,
108
132
  coroutine=escalation_tool_fn,
133
+ metadata={
134
+ "tool_type": "escalation",
135
+ "display_name": channel.properties.app_name,
136
+ "channel_type": channel.type,
137
+ "assignee": assignee,
138
+ },
109
139
  )
140
+ tool.set_tool_wrappers(awrapper=escalation_wrapper)
110
141
 
111
142
  return tool
@@ -3,17 +3,21 @@
3
3
  import copy
4
4
  from typing import Any
5
5
 
6
- from jsonschema_pydantic_converter import transform as create_model
7
- from langchain.tools import ToolRuntime
8
6
  from langchain_core.tools import StructuredTool
9
7
  from uipath.agent.models.agent import AgentIntegrationToolResourceConfig
10
8
  from uipath.eval.mocks import mockable
11
9
  from uipath.platform import UiPath
12
10
  from uipath.platform.connections import ActivityMetadata, ActivityParameterLocationInfo
13
11
 
14
- from .static_args import handle_static_args
12
+ from uipath_langchain.agent.react.jsonschema_pydantic_converter import create_model
13
+ from uipath_langchain.agent.tools.tool_node import ToolWrapperMixin
14
+
15
15
  from .structured_tool_with_output_type import StructuredToolWithOutputType
16
- from .utils import sanitize_tool_name
16
+ from .utils import sanitize_dict_for_serialization, sanitize_tool_name
17
+
18
+
19
+ class StructuredToolWithStaticArgs(StructuredToolWithOutputType, ToolWrapperMixin):
20
+ pass
17
21
 
18
22
 
19
23
  def remove_asterisk_from_properties(fields: dict[str, Any]) -> dict[str, Any]:
@@ -149,33 +153,33 @@ def create_integration_tool(
149
153
  description=resource.description,
150
154
  input_schema=input_model.model_json_schema(),
151
155
  output_schema=output_model.model_json_schema(),
156
+ example_calls=resource.properties.example_calls,
152
157
  )
153
- async def integration_tool_fn(runtime: ToolRuntime, **kwargs: Any):
158
+ async def integration_tool_fn(**kwargs: Any):
154
159
  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
160
  result = await sdk.connections.invoke_activity_async(
164
161
  activity_metadata=activity_metadata,
165
162
  connection_id=connection_id,
166
- activity_input=args,
163
+ activity_input=sanitize_dict_for_serialization(kwargs),
167
164
  )
168
165
  except Exception:
169
166
  raise
170
167
 
171
168
  return result
172
169
 
173
- tool = StructuredToolWithOutputType(
170
+ from uipath_langchain.agent.wrappers.static_args_wrapper import (
171
+ get_static_args_wrapper,
172
+ )
173
+
174
+ wrapper = get_static_args_wrapper(resource)
175
+
176
+ tool = StructuredToolWithStaticArgs(
174
177
  name=tool_name,
175
178
  description=resource.description,
176
- args_schema=resource.input_schema,
179
+ args_schema=input_model,
177
180
  coroutine=integration_tool_fn,
178
181
  output_type=output_model,
179
182
  )
183
+ tool.set_tool_wrappers(awrapper=wrapper)
180
184
 
181
185
  return tool
@@ -0,0 +1,5 @@
1
+ """Internal Tool creation and management for LowCode agents."""
2
+
3
+ from .internal_tool_factory import create_internal_tool
4
+
5
+ __all__ = ["create_internal_tool"]
@@ -0,0 +1,113 @@
1
+ import uuid
2
+ from typing import Any
3
+
4
+ from langchain_core.language_models import BaseChatModel
5
+ from langchain_core.messages import AnyMessage, HumanMessage, SystemMessage
6
+ from langchain_core.tools import StructuredTool
7
+ from uipath.agent.models.agent import (
8
+ AgentInternalToolResourceConfig,
9
+ )
10
+ from uipath.eval.mocks import mockable
11
+ from uipath.platform import UiPath
12
+
13
+ from uipath_langchain.agent.react.jsonschema_pydantic_converter import create_model
14
+ from uipath_langchain.agent.react.llm_with_files import FileInfo, llm_call_with_files
15
+ from uipath_langchain.agent.tools.structured_tool_with_output_type import (
16
+ StructuredToolWithOutputType,
17
+ )
18
+ from uipath_langchain.agent.tools.tool_node import ToolWrapperMixin
19
+ from uipath_langchain.agent.tools.utils import sanitize_tool_name
20
+
21
+ ANALYZE_FILES_SYSTEM_MESSAGE = (
22
+ "Process the provided files to complete the given task. "
23
+ "Analyze the files contents thoroughly to deliver an accurate response "
24
+ "based on the extracted information."
25
+ )
26
+
27
+
28
+ class AnalyzeFileTool(StructuredToolWithOutputType, ToolWrapperMixin):
29
+ pass
30
+
31
+
32
+ def create_analyze_file_tool(
33
+ resource: AgentInternalToolResourceConfig, llm: BaseChatModel
34
+ ) -> StructuredTool:
35
+ from uipath_langchain.agent.wrappers.job_attachment_wrapper import (
36
+ get_job_attachment_wrapper,
37
+ )
38
+
39
+ tool_name = sanitize_tool_name(resource.name)
40
+ input_model = create_model(resource.input_schema)
41
+ output_model = create_model(resource.output_schema)
42
+
43
+ @mockable(
44
+ name=resource.name,
45
+ description=resource.description,
46
+ input_schema=input_model.model_json_schema(),
47
+ output_schema=output_model.model_json_schema(),
48
+ )
49
+ async def tool_fn(**kwargs: Any):
50
+ if "analysisTask" not in kwargs:
51
+ raise ValueError("Argument 'analysisTask' is not available")
52
+ if "attachments" not in kwargs:
53
+ raise ValueError("Argument 'attachments' is not available")
54
+
55
+ attachments = kwargs["attachments"]
56
+ analysisTask = kwargs["analysisTask"]
57
+
58
+ files = await _resolve_job_attachment_arguments(attachments)
59
+ messages: list[AnyMessage] = [
60
+ SystemMessage(content=ANALYZE_FILES_SYSTEM_MESSAGE),
61
+ HumanMessage(content=analysisTask),
62
+ ]
63
+ result = await llm_call_with_files(messages, files, llm)
64
+ return result
65
+
66
+ wrapper = get_job_attachment_wrapper()
67
+ tool = AnalyzeFileTool(
68
+ name=tool_name,
69
+ description=resource.description,
70
+ args_schema=input_model,
71
+ coroutine=tool_fn,
72
+ output_type=output_model,
73
+ )
74
+ tool.set_tool_wrappers(awrapper=wrapper)
75
+ return tool
76
+
77
+
78
+ async def _resolve_job_attachment_arguments(
79
+ attachments: list[Any],
80
+ ) -> list[FileInfo]:
81
+ """Resolve job attachments to FileInfo objects.
82
+
83
+ Args:
84
+ attachments: List of job attachment objects (dynamically typed from schema)
85
+
86
+ Returns:
87
+ List of FileInfo objects with blob URIs for each attachment
88
+ """
89
+ client = UiPath()
90
+ file_infos: list[FileInfo] = []
91
+
92
+ for attachment in attachments:
93
+ # Access using Pydantic field aliases (ID, FullName, MimeType)
94
+ # These are dynamically created from the JSON schema
95
+ attachment_id_value = getattr(attachment, "ID", None)
96
+ if attachment_id_value is None:
97
+ continue
98
+
99
+ attachment_id = uuid.UUID(attachment_id_value)
100
+ mime_type = getattr(attachment, "MimeType", "")
101
+
102
+ blob_info = await client.attachments.get_blob_file_access_uri_async(
103
+ key=attachment_id
104
+ )
105
+
106
+ file_info = FileInfo(
107
+ url=blob_info.uri,
108
+ name=blob_info.name,
109
+ mime_type=mime_type,
110
+ )
111
+ file_infos.append(file_info)
112
+
113
+ return file_infos
@@ -0,0 +1,54 @@
1
+ """Factory for creating internal agent tools.
2
+
3
+ This module provides a factory pattern for creating internal tools used by agents.
4
+ Internal tools are built-in tools that provide core functionality for agents, such as
5
+ file analysis, data processing, or other utilities that don't require external integrations.
6
+
7
+ Supported Internal Tools:
8
+ - ANALYZE_FILES: Tool for analyzing file contents and extracting information
9
+
10
+ Example:
11
+ >>> from uipath.agent.models.agent import AgentInternalToolResourceConfig
12
+ >>> resource = AgentInternalToolResourceConfig(...)
13
+ >>> tool = create_internal_tool(resource)
14
+ >>> # Use the tool in your agent workflow
15
+ """
16
+
17
+ from typing import Callable
18
+
19
+ from langchain_core.language_models import BaseChatModel
20
+ from langchain_core.tools import StructuredTool
21
+ from uipath.agent.models.agent import (
22
+ AgentInternalToolResourceConfig,
23
+ AgentInternalToolType,
24
+ )
25
+
26
+ from .analyze_files_tool import create_analyze_file_tool
27
+
28
+ _INTERNAL_TOOL_HANDLERS: dict[
29
+ AgentInternalToolType,
30
+ Callable[[AgentInternalToolResourceConfig, BaseChatModel], StructuredTool],
31
+ ] = {
32
+ AgentInternalToolType.ANALYZE_FILES: create_analyze_file_tool,
33
+ }
34
+
35
+
36
+ def create_internal_tool(
37
+ resource: AgentInternalToolResourceConfig, llm: BaseChatModel
38
+ ) -> StructuredTool:
39
+ """Create an internal tool based on the resource configuration.
40
+
41
+ Raises:
42
+ ValueError: If the tool type is not supported (no handler exists for it).
43
+
44
+ """
45
+ tool_type = resource.properties.tool_type
46
+
47
+ handler = _INTERNAL_TOOL_HANDLERS.get(tool_type)
48
+ if handler is None:
49
+ raise ValueError(
50
+ f"Unsupported internal tool type: {tool_type}. "
51
+ f"Supported types: {list[AgentInternalToolType](_INTERNAL_TOOL_HANDLERS.keys())}"
52
+ )
53
+
54
+ return handler(resource, llm)
@@ -0,0 +1,86 @@
1
+ import asyncio
2
+ import os
3
+ from collections import Counter, defaultdict
4
+ from contextlib import AsyncExitStack, asynccontextmanager
5
+ from itertools import chain
6
+
7
+ import httpx
8
+ from langchain_core.tools import BaseTool
9
+ from langchain_mcp_adapters.tools import load_mcp_tools
10
+ from mcp import ClientSession
11
+ from mcp.client.streamable_http import streamable_http_client
12
+ from uipath._utils._ssl_context import get_httpx_client_kwargs
13
+ from uipath.agent.models.agent import AgentMcpResourceConfig
14
+
15
+
16
+ def _deduplicate_tools(tools: list[BaseTool]) -> list[BaseTool]:
17
+ """Deduplicate tools by appending numeric suffix to duplicate names."""
18
+ counts = Counter(tool.name for tool in tools)
19
+ seen: defaultdict[str, int] = defaultdict(int)
20
+
21
+ for tool in tools:
22
+ if counts[tool.name] > 1:
23
+ seen[tool.name] += 1
24
+ tool.name = f"{tool.name}_{seen[tool.name]}"
25
+
26
+ return tools
27
+
28
+
29
+ def _filter_tools(tools: list[BaseTool], cfg: AgentMcpResourceConfig) -> list[BaseTool]:
30
+ """Filter tools to only include those in available_tools."""
31
+ allowed = {t.name for t in cfg.available_tools}
32
+ return [t for t in tools if t.name in allowed]
33
+
34
+
35
+ @asynccontextmanager
36
+ async def create_mcp_tools(
37
+ config: AgentMcpResourceConfig | list[AgentMcpResourceConfig],
38
+ max_concurrency: int = 5,
39
+ ):
40
+ """Connect to UiPath MCP server(s) and yield LangChain-compatible tools."""
41
+ if not (base_url := os.getenv("UIPATH_URL")):
42
+ raise ValueError("UIPATH_URL environment variable is not set")
43
+ if not (access_token := os.getenv("UIPATH_ACCESS_TOKEN")):
44
+ raise ValueError("UIPATH_ACCESS_TOKEN environment variable is not set")
45
+
46
+ configs = config if isinstance(config, list) else [config]
47
+ enabled = [c for c in configs if c.is_enabled is not False]
48
+
49
+ if not enabled:
50
+ yield []
51
+ return
52
+
53
+ base_url = base_url.rstrip("/")
54
+ semaphore = asyncio.Semaphore(max_concurrency)
55
+
56
+ default_client_kwargs = get_httpx_client_kwargs()
57
+ client_kwargs = {
58
+ **default_client_kwargs,
59
+ "headers": {"Authorization": f"Bearer {access_token}"},
60
+ "timeout": httpx.Timeout(60),
61
+ }
62
+
63
+ async def init_session(
64
+ session: ClientSession, cfg: AgentMcpResourceConfig
65
+ ) -> list[BaseTool]:
66
+ async with semaphore:
67
+ await session.initialize()
68
+ tools = await load_mcp_tools(session)
69
+ return _filter_tools(tools, cfg)
70
+
71
+ async def create_session(
72
+ stack: AsyncExitStack, cfg: AgentMcpResourceConfig
73
+ ) -> ClientSession:
74
+ url = f"{base_url}/agenthub_/mcp/{cfg.folder_path}/{cfg.slug}"
75
+ http_client = await stack.enter_async_context(
76
+ httpx.AsyncClient(**client_kwargs)
77
+ )
78
+ read, write, _ = await stack.enter_async_context(
79
+ streamable_http_client(url=url, http_client=http_client)
80
+ )
81
+ return await stack.enter_async_context(ClientSession(read, write))
82
+
83
+ async with AsyncExitStack() as stack:
84
+ sessions = [(await create_session(stack, cfg), cfg) for cfg in enabled]
85
+ results = await asyncio.gather(*[init_session(s, cfg) for s, cfg in sessions])
86
+ yield _deduplicate_tools(list(chain.from_iterable(results)))
@@ -2,13 +2,14 @@
2
2
 
3
3
  from typing import Any
4
4
 
5
- from jsonschema_pydantic_converter import transform as create_model
6
5
  from langchain_core.tools import StructuredTool
7
6
  from langgraph.types import interrupt
8
7
  from uipath.agent.models.agent import AgentProcessToolResourceConfig
9
8
  from uipath.eval.mocks import mockable
10
9
  from uipath.platform.common import InvokeProcess
11
10
 
11
+ from uipath_langchain.agent.react.jsonschema_pydantic_converter import create_model
12
+
12
13
  from .structured_tool_with_output_type import StructuredToolWithOutputType
13
14
  from .utils import sanitize_tool_name
14
15
 
@@ -27,6 +28,7 @@ def create_process_tool(resource: AgentProcessToolResourceConfig) -> StructuredT
27
28
  description=resource.description,
28
29
  input_schema=input_model.model_json_schema(),
29
30
  output_schema=output_model.model_json_schema(),
31
+ example_calls=resource.properties.example_calls,
30
32
  )
31
33
  async def process_tool_fn(**kwargs: Any):
32
34
  return interrupt(
@@ -44,6 +46,11 @@ def create_process_tool(resource: AgentProcessToolResourceConfig) -> StructuredT
44
46
  args_schema=input_model,
45
47
  coroutine=process_tool_fn,
46
48
  output_type=output_model,
49
+ metadata={
50
+ "tool_type": "process",
51
+ "display_name": process_name,
52
+ "folder_path": folder_path,
53
+ },
47
54
  )
48
55
 
49
56
  return tool
@@ -1,25 +1,27 @@
1
1
  """Handles static arguments for tool calls."""
2
2
 
3
- from typing import Any, Dict, List
3
+ from typing import Any
4
4
 
5
5
  from jsonpath_ng import parse # type: ignore[import-untyped]
6
- from langchain.tools import ToolRuntime
6
+ from pydantic import BaseModel
7
7
  from uipath.agent.models.agent import (
8
8
  AgentIntegrationToolParameter,
9
9
  AgentIntegrationToolResourceConfig,
10
10
  BaseAgentResourceConfig,
11
11
  )
12
12
 
13
+ from .utils import sanitize_dict_for_serialization
14
+
13
15
 
14
16
  def resolve_static_args(
15
17
  resource: BaseAgentResourceConfig,
16
- agent_input: Dict[str, Any],
17
- ) -> Dict[str, Any]:
18
+ agent_input: dict[str, Any],
19
+ ) -> dict[str, Any]:
18
20
  """Resolves static arguments for a given resource with a given input.
19
21
 
20
22
  Args:
21
23
  resource: The agent resource configuration.
22
- input: Othe input arguments passed to the agent.
24
+ input: The input arguments passed to the agent.
23
25
 
24
26
  Returns:
25
27
  A dictionary of expanded arguments to be used in the tool call.
@@ -35,9 +37,9 @@ def resolve_static_args(
35
37
 
36
38
 
37
39
  def resolve_integration_static_args(
38
- parameters: List[AgentIntegrationToolParameter],
39
- agent_input: Dict[str, Any],
40
- ) -> Dict[str, Any]:
40
+ parameters: list[AgentIntegrationToolParameter],
41
+ agent_input: dict[str, Any],
42
+ ) -> dict[str, Any]:
41
43
  """Resolves static arguments for an integration tool resource.
42
44
 
43
45
  Args:
@@ -48,7 +50,7 @@ def resolve_integration_static_args(
48
50
  A dictionary of expanded static arguments for the integration tool.
49
51
  """
50
52
 
51
- static_args: Dict[str, Any] = {}
53
+ static_args: dict[str, Any] = {}
52
54
  for param in parameters:
53
55
  value = None
54
56
 
@@ -75,34 +77,10 @@ def resolve_integration_static_args(
75
77
  return static_args
76
78
 
77
79
 
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
80
  def apply_static_args(
103
- static_args: Dict[str, Any],
104
- kwargs: Dict[str, Any],
105
- ) -> Dict[str, Any]:
81
+ static_args: dict[str, Any],
82
+ kwargs: dict[str, Any],
83
+ ) -> dict[str, Any]:
106
84
  """Applies static arguments to the given input arguments.
107
85
 
108
86
  Args:
@@ -113,7 +91,7 @@ def apply_static_args(
113
91
  Merged input arguments with static arguments applied.
114
92
  """
115
93
 
116
- sanitized_args = sanitize_for_serialization(kwargs)
94
+ sanitized_args = sanitize_dict_for_serialization(kwargs)
117
95
  for json_path, value in static_args.items():
118
96
  expr = parse(json_path)
119
97
  expr.update_or_create(sanitized_args, value)
@@ -122,8 +100,8 @@ def apply_static_args(
122
100
 
123
101
 
124
102
  def handle_static_args(
125
- resource: BaseAgentResourceConfig, runtime: ToolRuntime, input_args: Dict[str, Any]
126
- ) -> Dict[str, Any]:
103
+ resource: BaseAgentResourceConfig, state: BaseModel, input_args: dict[str, Any]
104
+ ) -> dict[str, Any]:
127
105
  """Resolves and applies static arguments for a tool call.
128
106
  Args:
129
107
  resource: The agent resource configuration.
@@ -133,6 +111,6 @@ def handle_static_args(
133
111
  A dictionary of input arguments with static arguments applied.
134
112
  """
135
113
 
136
- static_args = resolve_static_args(resource, dict(runtime.state))
114
+ static_args = resolve_static_args(resource, dict(state))
137
115
  merged_args = apply_static_args(static_args, input_args)
138
116
  return merged_args
@@ -1,10 +1,12 @@
1
1
  """Factory functions for creating tools from agent resources."""
2
2
 
3
- from langchain_core.tools import BaseTool, StructuredTool
3
+ from langchain_core.language_models import BaseChatModel
4
+ from langchain_core.tools import BaseTool
4
5
  from uipath.agent.models.agent import (
5
6
  AgentContextResourceConfig,
6
7
  AgentEscalationResourceConfig,
7
8
  AgentIntegrationToolResourceConfig,
9
+ AgentInternalToolResourceConfig,
8
10
  AgentProcessToolResourceConfig,
9
11
  BaseAgentResourceConfig,
10
12
  LowCodeAgentDefinition,
@@ -13,14 +15,17 @@ from uipath.agent.models.agent import (
13
15
  from .context_tool import create_context_tool
14
16
  from .escalation_tool import create_escalation_tool
15
17
  from .integration_tool import create_integration_tool
18
+ from .internal_tools import create_internal_tool
16
19
  from .process_tool import create_process_tool
17
20
 
18
21
 
19
- async def create_tools_from_resources(agent: LowCodeAgentDefinition) -> list[BaseTool]:
22
+ async def create_tools_from_resources(
23
+ agent: LowCodeAgentDefinition, llm: BaseChatModel
24
+ ) -> list[BaseTool]:
20
25
  tools: list[BaseTool] = []
21
26
 
22
27
  for resource in agent.resources:
23
- tool = await _build_tool_for_resource(resource)
28
+ tool = await _build_tool_for_resource(resource, llm)
24
29
  if tool is not None:
25
30
  tools.append(tool)
26
31
 
@@ -28,8 +33,8 @@ async def create_tools_from_resources(agent: LowCodeAgentDefinition) -> list[Bas
28
33
 
29
34
 
30
35
  async def _build_tool_for_resource(
31
- resource: BaseAgentResourceConfig,
32
- ) -> StructuredTool | None:
36
+ resource: BaseAgentResourceConfig, llm: BaseChatModel
37
+ ) -> BaseTool | None:
33
38
  if isinstance(resource, AgentProcessToolResourceConfig):
34
39
  return create_process_tool(resource)
35
40
 
@@ -42,4 +47,7 @@ async def _build_tool_for_resource(
42
47
  elif isinstance(resource, AgentIntegrationToolResourceConfig):
43
48
  return create_integration_tool(resource)
44
49
 
50
+ elif isinstance(resource, AgentInternalToolResourceConfig):
51
+ return create_internal_tool(resource, llm)
52
+
45
53
  return None