uipath-langchain 0.1.24__py3-none-any.whl → 0.1.34__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/_utils/_request_mixin.py +8 -0
- uipath_langchain/_utils/_settings.py +3 -2
- uipath_langchain/agent/guardrails/__init__.py +0 -16
- uipath_langchain/agent/guardrails/actions/__init__.py +2 -0
- uipath_langchain/agent/guardrails/actions/base_action.py +1 -0
- uipath_langchain/agent/guardrails/actions/block_action.py +2 -1
- uipath_langchain/agent/guardrails/actions/escalate_action.py +243 -35
- uipath_langchain/agent/guardrails/actions/filter_action.py +55 -0
- uipath_langchain/agent/guardrails/actions/log_action.py +2 -1
- uipath_langchain/agent/guardrails/guardrail_nodes.py +186 -22
- uipath_langchain/agent/guardrails/guardrails_factory.py +200 -4
- uipath_langchain/agent/guardrails/types.py +0 -12
- uipath_langchain/agent/guardrails/utils.py +146 -0
- uipath_langchain/agent/react/agent.py +25 -8
- uipath_langchain/agent/react/constants.py +1 -2
- uipath_langchain/agent/{guardrails → react/guardrails}/guardrails_subgraph.py +94 -19
- uipath_langchain/agent/react/llm_node.py +41 -10
- uipath_langchain/agent/react/router.py +48 -37
- uipath_langchain/agent/react/types.py +15 -1
- uipath_langchain/agent/react/utils.py +1 -1
- uipath_langchain/agent/tools/__init__.py +2 -0
- uipath_langchain/agent/tools/mcp_tool.py +86 -0
- uipath_langchain/chat/__init__.py +4 -0
- uipath_langchain/chat/bedrock.py +16 -0
- uipath_langchain/chat/openai.py +57 -26
- uipath_langchain/chat/supported_models.py +9 -0
- uipath_langchain/chat/vertex.py +271 -0
- uipath_langchain/embeddings/embeddings.py +18 -12
- uipath_langchain/runtime/schema.py +116 -23
- {uipath_langchain-0.1.24.dist-info → uipath_langchain-0.1.34.dist-info}/METADATA +9 -6
- {uipath_langchain-0.1.24.dist-info → uipath_langchain-0.1.34.dist-info}/RECORD +34 -31
- uipath_langchain/chat/gemini.py +0 -330
- {uipath_langchain-0.1.24.dist-info → uipath_langchain-0.1.34.dist-info}/WHEEL +0 -0
- {uipath_langchain-0.1.24.dist-info → uipath_langchain-0.1.34.dist-info}/entry_points.txt +0 -0
- {uipath_langchain-0.1.24.dist-info → uipath_langchain-0.1.34.dist-info}/licenses/LICENSE +0 -0
|
@@ -137,6 +137,8 @@ class UiPathRequestMixin(BaseModel):
|
|
|
137
137
|
max_tokens: int | None = 1000
|
|
138
138
|
frequency_penalty: float | None = None
|
|
139
139
|
presence_penalty: float | None = None
|
|
140
|
+
agenthub_config: str | None = None
|
|
141
|
+
byo_connection_id: str | None = None
|
|
140
142
|
|
|
141
143
|
logger: logging.Logger | None = None
|
|
142
144
|
max_retries: int | None = 5
|
|
@@ -748,6 +750,12 @@ class UiPathRequestMixin(BaseModel):
|
|
|
748
750
|
"Authorization": f"Bearer {self.access_token}",
|
|
749
751
|
"X-UiPath-LlmGateway-TimeoutSeconds": str(self.default_request_timeout),
|
|
750
752
|
}
|
|
753
|
+
if self.agenthub_config:
|
|
754
|
+
self._auth_headers["X-UiPath-AgentHub-Config"] = self.agenthub_config
|
|
755
|
+
if self.byo_connection_id:
|
|
756
|
+
self._auth_headers["X-UiPath-LlmGateway-ByoIsConnectionId"] = (
|
|
757
|
+
self.byo_connection_id
|
|
758
|
+
)
|
|
751
759
|
if self.is_normalized and self.model_name:
|
|
752
760
|
self._auth_headers["X-UiPath-LlmGateway-NormalizedApi-ModelName"] = (
|
|
753
761
|
self.model_name
|
|
@@ -5,6 +5,7 @@ from typing import Any
|
|
|
5
5
|
import httpx
|
|
6
6
|
from pydantic import Field
|
|
7
7
|
from pydantic_settings import BaseSettings
|
|
8
|
+
from uipath._utils._ssl_context import get_httpx_client_kwargs
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class UiPathCachedPathsSettings(BaseSettings):
|
|
@@ -58,7 +59,7 @@ def get_uipath_token_header(
|
|
|
58
59
|
client_secret=settings.client_secret,
|
|
59
60
|
grant_type="client_credentials",
|
|
60
61
|
)
|
|
61
|
-
with httpx.Client() as client:
|
|
62
|
+
with httpx.Client(**get_httpx_client_kwargs()) as client:
|
|
62
63
|
res = client.post(url_get_token, data=token_credentials)
|
|
63
64
|
res_json = res.json()
|
|
64
65
|
uipath_token_header = res_json.get("access_token")
|
|
@@ -79,7 +80,7 @@ async def get_token_header_async(
|
|
|
79
80
|
grant_type="client_credentials",
|
|
80
81
|
)
|
|
81
82
|
|
|
82
|
-
with httpx.Client() as client:
|
|
83
|
+
with httpx.Client(**get_httpx_client_kwargs()) as client:
|
|
83
84
|
res_json = client.post(url_get_token, data=token_credentials).json()
|
|
84
85
|
uipath_token_header = res_json.get("access_token")
|
|
85
86
|
|
|
@@ -1,21 +1,5 @@
|
|
|
1
|
-
from .guardrail_nodes import (
|
|
2
|
-
create_agent_guardrail_node,
|
|
3
|
-
create_llm_guardrail_node,
|
|
4
|
-
create_tool_guardrail_node,
|
|
5
|
-
)
|
|
6
1
|
from .guardrails_factory import build_guardrails_with_actions
|
|
7
|
-
from .guardrails_subgraph import (
|
|
8
|
-
create_agent_guardrails_subgraph,
|
|
9
|
-
create_llm_guardrails_subgraph,
|
|
10
|
-
create_tool_guardrails_subgraph,
|
|
11
|
-
)
|
|
12
2
|
|
|
13
3
|
__all__ = [
|
|
14
|
-
"create_llm_guardrails_subgraph",
|
|
15
|
-
"create_agent_guardrails_subgraph",
|
|
16
|
-
"create_tool_guardrails_subgraph",
|
|
17
|
-
"create_llm_guardrail_node",
|
|
18
|
-
"create_agent_guardrail_node",
|
|
19
|
-
"create_tool_guardrail_node",
|
|
20
4
|
"build_guardrails_with_actions",
|
|
21
5
|
]
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from .base_action import GuardrailAction
|
|
2
2
|
from .block_action import BlockAction
|
|
3
3
|
from .escalate_action import EscalateAction
|
|
4
|
+
from .filter_action import FilterAction
|
|
4
5
|
from .log_action import LogAction
|
|
5
6
|
|
|
6
7
|
__all__ = [
|
|
@@ -8,4 +9,5 @@ __all__ = [
|
|
|
8
9
|
"BlockAction",
|
|
9
10
|
"LogAction",
|
|
10
11
|
"EscalateAction",
|
|
12
|
+
"FilterAction",
|
|
11
13
|
]
|
|
@@ -18,6 +18,7 @@ class GuardrailAction(ABC):
|
|
|
18
18
|
guardrail: BaseGuardrail,
|
|
19
19
|
scope: GuardrailScope,
|
|
20
20
|
execution_stage: ExecutionStage,
|
|
21
|
+
guarded_component_name: str,
|
|
21
22
|
) -> GuardrailActionNode:
|
|
22
23
|
"""Create and return the Action node to execute on validation failure."""
|
|
23
24
|
...
|
|
@@ -6,7 +6,7 @@ from uipath.runtime.errors import UiPathErrorCategory, UiPathErrorCode
|
|
|
6
6
|
from uipath_langchain.agent.guardrails.types import ExecutionStage
|
|
7
7
|
|
|
8
8
|
from ...exceptions import AgentTerminationException
|
|
9
|
-
from
|
|
9
|
+
from ...react.types import AgentGuardrailsGraphState
|
|
10
10
|
from .base_action import GuardrailAction, GuardrailActionNode
|
|
11
11
|
|
|
12
12
|
|
|
@@ -26,6 +26,7 @@ class BlockAction(GuardrailAction):
|
|
|
26
26
|
guardrail: BaseGuardrail,
|
|
27
27
|
scope: GuardrailScope,
|
|
28
28
|
execution_stage: ExecutionStage,
|
|
29
|
+
guarded_component_name: str,
|
|
29
30
|
) -> GuardrailActionNode:
|
|
30
31
|
raw_node_name = f"{scope.name}_{execution_stage.name}_{guardrail.name}_block"
|
|
31
32
|
node_name = re.sub(r"\W+", "_", raw_node_name.lower()).strip("_")
|
|
@@ -14,8 +14,9 @@ from uipath.platform.guardrails import (
|
|
|
14
14
|
from uipath.runtime.errors import UiPathErrorCode
|
|
15
15
|
|
|
16
16
|
from ...exceptions import AgentTerminationException
|
|
17
|
-
from
|
|
18
|
-
from ..types import
|
|
17
|
+
from ...react.types import AgentGuardrailsGraphState
|
|
18
|
+
from ..types import ExecutionStage
|
|
19
|
+
from ..utils import _extract_tool_args_from_message, get_message_content
|
|
19
20
|
from .base_action import GuardrailAction, GuardrailActionNode
|
|
20
21
|
|
|
21
22
|
|
|
@@ -34,6 +35,14 @@ class EscalateAction(GuardrailAction):
|
|
|
34
35
|
version: int,
|
|
35
36
|
assignee: str,
|
|
36
37
|
):
|
|
38
|
+
"""Initialize EscalateAction with escalation app configuration.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
app_name: Name of the escalation app.
|
|
42
|
+
app_folder_path: Folder path where the escalation app is located.
|
|
43
|
+
version: Version of the escalation app.
|
|
44
|
+
assignee: User or role assigned to handle the escalation.
|
|
45
|
+
"""
|
|
37
46
|
self.app_name = app_name
|
|
38
47
|
self.app_folder_path = app_folder_path
|
|
39
48
|
self.version = version
|
|
@@ -45,13 +54,27 @@ class EscalateAction(GuardrailAction):
|
|
|
45
54
|
guardrail: BaseGuardrail,
|
|
46
55
|
scope: GuardrailScope,
|
|
47
56
|
execution_stage: ExecutionStage,
|
|
57
|
+
guarded_component_name: str,
|
|
48
58
|
) -> GuardrailActionNode:
|
|
59
|
+
"""Create a HITL escalation node for the guardrail.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
guardrail: The guardrail that triggered this escalation action.
|
|
63
|
+
scope: The guardrail scope (LLM/AGENT/TOOL).
|
|
64
|
+
execution_stage: The execution stage (PRE_EXECUTION or POST_EXECUTION).
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
A tuple of (node_name, node_function) where the node function triggers
|
|
68
|
+
a HITL interruption and processes the escalation response.
|
|
69
|
+
"""
|
|
49
70
|
node_name = _get_node_name(execution_stage, guardrail, scope)
|
|
50
71
|
|
|
51
72
|
async def _node(
|
|
52
73
|
state: AgentGuardrailsGraphState,
|
|
53
74
|
) -> Dict[str, Any] | Command[Any]:
|
|
54
|
-
input = _extract_escalation_content(
|
|
75
|
+
input = _extract_escalation_content(
|
|
76
|
+
state, scope, execution_stage, guarded_component_name
|
|
77
|
+
)
|
|
55
78
|
escalation_field = _execution_stage_to_escalation_field(execution_stage)
|
|
56
79
|
|
|
57
80
|
data = {
|
|
@@ -75,7 +98,11 @@ class EscalateAction(GuardrailAction):
|
|
|
75
98
|
|
|
76
99
|
if escalation_result.action == "Approve":
|
|
77
100
|
return _process_escalation_response(
|
|
78
|
-
state,
|
|
101
|
+
state,
|
|
102
|
+
escalation_result.data,
|
|
103
|
+
scope,
|
|
104
|
+
execution_stage,
|
|
105
|
+
guarded_component_name,
|
|
79
106
|
)
|
|
80
107
|
|
|
81
108
|
raise AgentTerminationException(
|
|
@@ -95,46 +122,58 @@ def _get_node_name(
|
|
|
95
122
|
return node_name
|
|
96
123
|
|
|
97
124
|
|
|
98
|
-
def
|
|
125
|
+
def _process_escalation_response(
|
|
126
|
+
state: AgentGuardrailsGraphState,
|
|
127
|
+
escalation_result: Dict[str, Any],
|
|
128
|
+
scope: GuardrailScope,
|
|
99
129
|
execution_stage: ExecutionStage,
|
|
100
|
-
|
|
101
|
-
|
|
130
|
+
guarded_node_name: str,
|
|
131
|
+
) -> Dict[str, Any] | Command[Any]:
|
|
132
|
+
"""Process escalation response and route to appropriate handler based on scope.
|
|
102
133
|
|
|
103
134
|
Args:
|
|
104
|
-
|
|
135
|
+
state: The current agent graph state.
|
|
136
|
+
escalation_result: The result from the escalation interrupt containing reviewed inputs/outputs.
|
|
137
|
+
scope: The guardrail scope (LLM/AGENT/TOOL).
|
|
138
|
+
execution_stage: The execution stage (PRE_EXECUTION or POST_EXECUTION).
|
|
105
139
|
|
|
106
140
|
Returns:
|
|
107
|
-
|
|
141
|
+
For LLM/TOOL scope: Command to update messages with reviewed inputs/outputs, or empty dict.
|
|
142
|
+
For AGENT scope: Empty dict (no message alteration).
|
|
108
143
|
"""
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
144
|
+
match scope:
|
|
145
|
+
case GuardrailScope.LLM:
|
|
146
|
+
return _process_llm_escalation_response(
|
|
147
|
+
state, escalation_result, execution_stage
|
|
148
|
+
)
|
|
149
|
+
case GuardrailScope.TOOL:
|
|
150
|
+
return _process_tool_escalation_response(
|
|
151
|
+
state, escalation_result, execution_stage, guarded_node_name
|
|
152
|
+
)
|
|
153
|
+
case GuardrailScope.AGENT:
|
|
154
|
+
return {}
|
|
112
155
|
|
|
113
156
|
|
|
114
|
-
def
|
|
157
|
+
def _process_llm_escalation_response(
|
|
115
158
|
state: AgentGuardrailsGraphState,
|
|
116
159
|
escalation_result: Dict[str, Any],
|
|
117
|
-
scope: GuardrailScope,
|
|
118
160
|
execution_stage: ExecutionStage,
|
|
119
161
|
) -> Dict[str, Any] | Command[Any]:
|
|
120
|
-
"""Process escalation response
|
|
162
|
+
"""Process escalation response for LLM scope guardrails.
|
|
163
|
+
|
|
164
|
+
Updates message content or tool calls based on reviewed inputs/outputs from escalation.
|
|
121
165
|
|
|
122
166
|
Args:
|
|
123
167
|
state: The current agent graph state.
|
|
124
|
-
escalation_result: The result from the escalation interrupt.
|
|
125
|
-
|
|
126
|
-
execution_stage: The hook type ("PreExecution" or "PostExecution").
|
|
168
|
+
escalation_result: The result from the escalation interrupt containing reviewed inputs/outputs.
|
|
169
|
+
execution_stage: The execution stage (PRE_EXECUTION or POST_EXECUTION).
|
|
127
170
|
|
|
128
171
|
Returns:
|
|
129
|
-
|
|
130
|
-
For non-LLM scope: Empty dict (no message alteration).
|
|
172
|
+
Command to update messages with reviewed inputs/outputs, or empty dict if no updates needed.
|
|
131
173
|
|
|
132
174
|
Raises:
|
|
133
175
|
AgentTerminationException: If escalation response processing fails.
|
|
134
176
|
"""
|
|
135
|
-
if scope != GuardrailScope.LLM:
|
|
136
|
-
return {}
|
|
137
|
-
|
|
138
177
|
try:
|
|
139
178
|
reviewed_field = (
|
|
140
179
|
"ReviewedInputs"
|
|
@@ -191,7 +230,93 @@ def _process_escalation_response(
|
|
|
191
230
|
if content_list:
|
|
192
231
|
last_message.content = content_list[-1]
|
|
193
232
|
|
|
194
|
-
return Command
|
|
233
|
+
return Command(update={"messages": msgs})
|
|
234
|
+
except Exception as e:
|
|
235
|
+
raise AgentTerminationException(
|
|
236
|
+
code=UiPathErrorCode.EXECUTION_ERROR,
|
|
237
|
+
title="Escalation rejected",
|
|
238
|
+
detail=str(e),
|
|
239
|
+
) from e
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _process_tool_escalation_response(
|
|
243
|
+
state: AgentGuardrailsGraphState,
|
|
244
|
+
escalation_result: Dict[str, Any],
|
|
245
|
+
execution_stage: ExecutionStage,
|
|
246
|
+
tool_name: str,
|
|
247
|
+
) -> Dict[str, Any] | Command[Any]:
|
|
248
|
+
"""Process escalation response for TOOL scope guardrails.
|
|
249
|
+
|
|
250
|
+
Updates the tool call arguments (PreExecution) or tool message content (PostExecution)
|
|
251
|
+
for the specific tool matching the tool_name. For PreExecution, finds the tool call
|
|
252
|
+
with the matching name and updates only that tool call's args with the reviewed dict.
|
|
253
|
+
For PostExecution, updates the tool message content.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
state: The current agent graph state.
|
|
257
|
+
escalation_result: The result from the escalation interrupt containing reviewed inputs/outputs.
|
|
258
|
+
execution_stage: The execution stage (PRE_EXECUTION or POST_EXECUTION).
|
|
259
|
+
tool_name: Name of the tool to update. Only the tool call matching this name will be updated.
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
Command to update messages with reviewed tool call args or content, or empty dict if no updates needed.
|
|
263
|
+
|
|
264
|
+
Raises:
|
|
265
|
+
AgentTerminationException: If escalation response processing fails.
|
|
266
|
+
"""
|
|
267
|
+
try:
|
|
268
|
+
reviewed_field = (
|
|
269
|
+
"ReviewedInputs"
|
|
270
|
+
if execution_stage == ExecutionStage.PRE_EXECUTION
|
|
271
|
+
else "ReviewedOutputs"
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
msgs = state.messages.copy()
|
|
275
|
+
if not msgs or reviewed_field not in escalation_result:
|
|
276
|
+
return {}
|
|
277
|
+
|
|
278
|
+
last_message = msgs[-1]
|
|
279
|
+
if execution_stage == ExecutionStage.PRE_EXECUTION:
|
|
280
|
+
if not isinstance(last_message, AIMessage):
|
|
281
|
+
return {}
|
|
282
|
+
|
|
283
|
+
# Get reviewed tool calls args from escalation result
|
|
284
|
+
reviewed_inputs_json = escalation_result[reviewed_field]
|
|
285
|
+
if not reviewed_inputs_json:
|
|
286
|
+
return {}
|
|
287
|
+
|
|
288
|
+
reviewed_tool_calls_args = json.loads(reviewed_inputs_json)
|
|
289
|
+
if not isinstance(reviewed_tool_calls_args, dict):
|
|
290
|
+
return {}
|
|
291
|
+
|
|
292
|
+
# Find and update only the tool call with matching name
|
|
293
|
+
if last_message.tool_calls:
|
|
294
|
+
tool_calls = list(last_message.tool_calls)
|
|
295
|
+
for tool_call in tool_calls:
|
|
296
|
+
call_name = (
|
|
297
|
+
tool_call.get("name")
|
|
298
|
+
if isinstance(tool_call, dict)
|
|
299
|
+
else getattr(tool_call, "name", None)
|
|
300
|
+
)
|
|
301
|
+
if call_name == tool_name:
|
|
302
|
+
# Update args for the matching tool call
|
|
303
|
+
if isinstance(reviewed_tool_calls_args, dict):
|
|
304
|
+
if isinstance(tool_call, dict):
|
|
305
|
+
tool_call["args"] = reviewed_tool_calls_args
|
|
306
|
+
else:
|
|
307
|
+
tool_call.args = reviewed_tool_calls_args
|
|
308
|
+
break
|
|
309
|
+
last_message.tool_calls = tool_calls
|
|
310
|
+
else:
|
|
311
|
+
if not isinstance(last_message, ToolMessage):
|
|
312
|
+
return {}
|
|
313
|
+
|
|
314
|
+
# PostExecution: update tool message content
|
|
315
|
+
reviewed_outputs_json = escalation_result[reviewed_field]
|
|
316
|
+
if reviewed_outputs_json:
|
|
317
|
+
last_message.content = reviewed_outputs_json
|
|
318
|
+
|
|
319
|
+
return Command(update={"messages": msgs})
|
|
195
320
|
except Exception as e:
|
|
196
321
|
raise AgentTerminationException(
|
|
197
322
|
code=UiPathErrorCode.EXECUTION_ERROR,
|
|
@@ -204,35 +329,60 @@ def _extract_escalation_content(
|
|
|
204
329
|
state: AgentGuardrailsGraphState,
|
|
205
330
|
scope: GuardrailScope,
|
|
206
331
|
execution_stage: ExecutionStage,
|
|
332
|
+
guarded_node_name: str,
|
|
207
333
|
) -> str | list[str | Dict[str, Any]]:
|
|
208
334
|
"""Extract escalation content from state based on guardrail scope and execution stage.
|
|
209
335
|
|
|
210
336
|
Args:
|
|
211
337
|
state: The current agent graph state.
|
|
212
338
|
scope: The guardrail scope (LLM/AGENT/TOOL).
|
|
213
|
-
execution_stage: The execution stage
|
|
339
|
+
execution_stage: The execution stage (PRE_EXECUTION or POST_EXECUTION).
|
|
214
340
|
|
|
215
341
|
Returns:
|
|
216
|
-
For
|
|
217
|
-
For
|
|
218
|
-
For LLM PostExecution: JSON array with tool call content and message content.
|
|
219
|
-
"""
|
|
220
|
-
if scope != GuardrailScope.LLM:
|
|
221
|
-
return ""
|
|
342
|
+
str or list[str | Dict[str, Any]]: For LLM scope, returns JSON string or list with message/tool call content.
|
|
343
|
+
For AGENT scope, returns empty string. For TOOL scope, returns JSON string or list with tool-specific content.
|
|
222
344
|
|
|
345
|
+
Raises:
|
|
346
|
+
AgentTerminationException: If no messages are found in state.
|
|
347
|
+
"""
|
|
223
348
|
if not state.messages:
|
|
224
349
|
raise AgentTerminationException(
|
|
225
350
|
code=UiPathErrorCode.EXECUTION_ERROR,
|
|
226
351
|
title="Invalid state message",
|
|
227
|
-
detail="No
|
|
352
|
+
detail="No message found into agent state",
|
|
228
353
|
)
|
|
229
354
|
|
|
355
|
+
match scope:
|
|
356
|
+
case GuardrailScope.LLM:
|
|
357
|
+
return _extract_llm_escalation_content(state, execution_stage)
|
|
358
|
+
case GuardrailScope.AGENT:
|
|
359
|
+
return _extract_agent_escalation_content(state, execution_stage)
|
|
360
|
+
case GuardrailScope.TOOL:
|
|
361
|
+
return _extract_tool_escalation_content(
|
|
362
|
+
state, execution_stage, guarded_node_name
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _extract_llm_escalation_content(
|
|
367
|
+
state: AgentGuardrailsGraphState, execution_stage: ExecutionStage
|
|
368
|
+
) -> str | list[str | Dict[str, Any]]:
|
|
369
|
+
"""Extract escalation content for LLM scope guardrails.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
state: The current agent graph state.
|
|
373
|
+
execution_stage: The execution stage (PRE_EXECUTION or POST_EXECUTION).
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
str or list[str | Dict[str, Any]]: For PreExecution, returns JSON string with message content or empty string.
|
|
377
|
+
For PostExecution, returns JSON string (array) with tool call content and message content.
|
|
378
|
+
Returns empty string if no content found.
|
|
379
|
+
"""
|
|
230
380
|
last_message = state.messages[-1]
|
|
231
381
|
if execution_stage == ExecutionStage.PRE_EXECUTION:
|
|
232
382
|
if isinstance(last_message, ToolMessage):
|
|
233
383
|
return last_message.content
|
|
234
384
|
|
|
235
|
-
content =
|
|
385
|
+
content = get_message_content(last_message)
|
|
236
386
|
return json.dumps(content) if content else ""
|
|
237
387
|
|
|
238
388
|
# For AI messages, process tool calls if present
|
|
@@ -250,14 +400,56 @@ def _extract_escalation_content(
|
|
|
250
400
|
):
|
|
251
401
|
content_list.append(json.dumps(args["content"]))
|
|
252
402
|
|
|
253
|
-
message_content =
|
|
403
|
+
message_content = get_message_content(last_message)
|
|
254
404
|
if message_content:
|
|
255
405
|
content_list.append(message_content)
|
|
256
406
|
|
|
257
407
|
return json.dumps(content_list)
|
|
258
408
|
|
|
259
409
|
# Fallback for other message types
|
|
260
|
-
return
|
|
410
|
+
return get_message_content(last_message)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _extract_agent_escalation_content(
|
|
414
|
+
state: AgentGuardrailsGraphState, execution_stage: ExecutionStage
|
|
415
|
+
) -> str | list[str | Dict[str, Any]]:
|
|
416
|
+
"""Extract escalation content for AGENT scope guardrails.
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
state: The current agent graph state.
|
|
420
|
+
execution_stage: The execution stage (PRE_EXECUTION or POST_EXECUTION).
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
str: Empty string (AGENT scope guardrails do not extract escalation content).
|
|
424
|
+
"""
|
|
425
|
+
return ""
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def _extract_tool_escalation_content(
|
|
429
|
+
state: AgentGuardrailsGraphState, execution_stage: ExecutionStage, tool_name: str
|
|
430
|
+
) -> str | list[str | Dict[str, Any]]:
|
|
431
|
+
"""Extract escalation content for TOOL scope guardrails.
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
state: The current agent graph state.
|
|
435
|
+
execution_stage: The execution stage (PRE_EXECUTION or POST_EXECUTION).
|
|
436
|
+
tool_name: Optional tool name to filter tool calls. If provided, only extracts args for matching tool.
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
str or list[str | Dict[str, Any]]: For PreExecution, returns JSON string with tool call arguments
|
|
440
|
+
for the specified tool name, or empty string if not found. For PostExecution, returns string with
|
|
441
|
+
tool message content, or empty string if message type doesn't match.
|
|
442
|
+
"""
|
|
443
|
+
last_message = state.messages[-1]
|
|
444
|
+
if execution_stage == ExecutionStage.PRE_EXECUTION:
|
|
445
|
+
args = _extract_tool_args_from_message(last_message, tool_name)
|
|
446
|
+
if args:
|
|
447
|
+
return json.dumps(args)
|
|
448
|
+
return ""
|
|
449
|
+
else:
|
|
450
|
+
if not isinstance(last_message, ToolMessage):
|
|
451
|
+
return ""
|
|
452
|
+
return last_message.content
|
|
261
453
|
|
|
262
454
|
|
|
263
455
|
def _execution_stage_to_escalation_field(
|
|
@@ -272,3 +464,19 @@ def _execution_stage_to_escalation_field(
|
|
|
272
464
|
"Inputs" for PRE_EXECUTION, "Outputs" for POST_EXECUTION.
|
|
273
465
|
"""
|
|
274
466
|
return "Inputs" if execution_stage == ExecutionStage.PRE_EXECUTION else "Outputs"
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _execution_stage_to_string(
|
|
470
|
+
execution_stage: ExecutionStage,
|
|
471
|
+
) -> Literal["PreExecution", "PostExecution"]:
|
|
472
|
+
"""Convert ExecutionStage enum to string literal.
|
|
473
|
+
|
|
474
|
+
Args:
|
|
475
|
+
execution_stage: The execution stage enum.
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
"PreExecution" for PRE_EXECUTION, "PostExecution" for POST_EXECUTION.
|
|
479
|
+
"""
|
|
480
|
+
if execution_stage == ExecutionStage.PRE_EXECUTION:
|
|
481
|
+
return "PreExecution"
|
|
482
|
+
return "PostExecution"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from uipath.platform.guardrails import BaseGuardrail, GuardrailScope
|
|
5
|
+
from uipath.runtime.errors import UiPathErrorCategory, UiPathErrorCode
|
|
6
|
+
|
|
7
|
+
from uipath_langchain.agent.guardrails.types import ExecutionStage
|
|
8
|
+
|
|
9
|
+
from ...exceptions import AgentTerminationException
|
|
10
|
+
from ...react.types import AgentGuardrailsGraphState
|
|
11
|
+
from .base_action import GuardrailAction, GuardrailActionNode
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FilterAction(GuardrailAction):
|
|
15
|
+
"""Action that filters inputs/outputs on guardrail failure.
|
|
16
|
+
|
|
17
|
+
For now, filtering is only supported for non-AGENT and non-LLM scopes.
|
|
18
|
+
If invoked for ``GuardrailScope.AGENT`` or ``GuardrailScope.LLM``, this action
|
|
19
|
+
raises an exception to indicate the operation is not supported yet.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def action_node(
|
|
23
|
+
self,
|
|
24
|
+
*,
|
|
25
|
+
guardrail: BaseGuardrail,
|
|
26
|
+
scope: GuardrailScope,
|
|
27
|
+
execution_stage: ExecutionStage,
|
|
28
|
+
guarded_component_name: str,
|
|
29
|
+
) -> GuardrailActionNode:
|
|
30
|
+
"""Create a guardrail action node that performs filtering.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
guardrail: The guardrail responsible for the validation.
|
|
34
|
+
scope: The scope in which the guardrail applies.
|
|
35
|
+
execution_stage: Whether this runs before or after execution.
|
|
36
|
+
guarded_component_name: Name of the guarded component.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
A tuple containing the node name and the async node callable.
|
|
40
|
+
"""
|
|
41
|
+
raw_node_name = f"{scope.name}_{execution_stage.name}_{guardrail.name}_filter"
|
|
42
|
+
node_name = re.sub(r"\W+", "_", raw_node_name.lower()).strip("_")
|
|
43
|
+
|
|
44
|
+
async def _node(_state: AgentGuardrailsGraphState) -> dict[str, Any]:
|
|
45
|
+
if scope in (GuardrailScope.AGENT, GuardrailScope.LLM):
|
|
46
|
+
raise AgentTerminationException(
|
|
47
|
+
code=UiPathErrorCode.EXECUTION_ERROR,
|
|
48
|
+
title="Guardrail filter action not supported",
|
|
49
|
+
detail=f"FilterAction is not supported for scope [{scope.name}] at this time.",
|
|
50
|
+
category=UiPathErrorCategory.USER,
|
|
51
|
+
)
|
|
52
|
+
# No-op for other scopes for now.
|
|
53
|
+
return {}
|
|
54
|
+
|
|
55
|
+
return node_name, _node
|
|
@@ -6,7 +6,7 @@ from uipath.platform.guardrails import BaseGuardrail, GuardrailScope
|
|
|
6
6
|
|
|
7
7
|
from uipath_langchain.agent.guardrails.types import ExecutionStage
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from ...react.types import AgentGuardrailsGraphState
|
|
10
10
|
from .base_action import GuardrailAction, GuardrailActionNode
|
|
11
11
|
|
|
12
12
|
logger = logging.getLogger(__name__)
|
|
@@ -31,6 +31,7 @@ class LogAction(GuardrailAction):
|
|
|
31
31
|
guardrail: BaseGuardrail,
|
|
32
32
|
scope: GuardrailScope,
|
|
33
33
|
execution_stage: ExecutionStage,
|
|
34
|
+
guarded_component_name: str,
|
|
34
35
|
) -> GuardrailActionNode:
|
|
35
36
|
"""Create a guardrail action node that logs validation failures.
|
|
36
37
|
|