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.
Files changed (35) hide show
  1. uipath_langchain/_utils/_request_mixin.py +8 -0
  2. uipath_langchain/_utils/_settings.py +3 -2
  3. uipath_langchain/agent/guardrails/__init__.py +0 -16
  4. uipath_langchain/agent/guardrails/actions/__init__.py +2 -0
  5. uipath_langchain/agent/guardrails/actions/base_action.py +1 -0
  6. uipath_langchain/agent/guardrails/actions/block_action.py +2 -1
  7. uipath_langchain/agent/guardrails/actions/escalate_action.py +243 -35
  8. uipath_langchain/agent/guardrails/actions/filter_action.py +55 -0
  9. uipath_langchain/agent/guardrails/actions/log_action.py +2 -1
  10. uipath_langchain/agent/guardrails/guardrail_nodes.py +186 -22
  11. uipath_langchain/agent/guardrails/guardrails_factory.py +200 -4
  12. uipath_langchain/agent/guardrails/types.py +0 -12
  13. uipath_langchain/agent/guardrails/utils.py +146 -0
  14. uipath_langchain/agent/react/agent.py +25 -8
  15. uipath_langchain/agent/react/constants.py +1 -2
  16. uipath_langchain/agent/{guardrails → react/guardrails}/guardrails_subgraph.py +94 -19
  17. uipath_langchain/agent/react/llm_node.py +41 -10
  18. uipath_langchain/agent/react/router.py +48 -37
  19. uipath_langchain/agent/react/types.py +15 -1
  20. uipath_langchain/agent/react/utils.py +1 -1
  21. uipath_langchain/agent/tools/__init__.py +2 -0
  22. uipath_langchain/agent/tools/mcp_tool.py +86 -0
  23. uipath_langchain/chat/__init__.py +4 -0
  24. uipath_langchain/chat/bedrock.py +16 -0
  25. uipath_langchain/chat/openai.py +57 -26
  26. uipath_langchain/chat/supported_models.py +9 -0
  27. uipath_langchain/chat/vertex.py +271 -0
  28. uipath_langchain/embeddings/embeddings.py +18 -12
  29. uipath_langchain/runtime/schema.py +116 -23
  30. {uipath_langchain-0.1.24.dist-info → uipath_langchain-0.1.34.dist-info}/METADATA +9 -6
  31. {uipath_langchain-0.1.24.dist-info → uipath_langchain-0.1.34.dist-info}/RECORD +34 -31
  32. uipath_langchain/chat/gemini.py +0 -330
  33. {uipath_langchain-0.1.24.dist-info → uipath_langchain-0.1.34.dist-info}/WHEEL +0 -0
  34. {uipath_langchain-0.1.24.dist-info → uipath_langchain-0.1.34.dist-info}/entry_points.txt +0 -0
  35. {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 ..types import AgentGuardrailsGraphState
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 ..guardrail_nodes import _message_text
18
- from ..types import AgentGuardrailsGraphState, ExecutionStage
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(state, scope, execution_stage)
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, escalation_result.data, scope, execution_stage
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 _execution_stage_to_string(
125
+ def _process_escalation_response(
126
+ state: AgentGuardrailsGraphState,
127
+ escalation_result: Dict[str, Any],
128
+ scope: GuardrailScope,
99
129
  execution_stage: ExecutionStage,
100
- ) -> Literal["PreExecution", "PostExecution"]:
101
- """Convert ExecutionStage enum to string literal.
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
- execution_stage: The execution stage enum.
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
- "PreExecution" for PRE_EXECUTION, "PostExecution" for POST_EXECUTION.
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
- if execution_stage == ExecutionStage.PRE_EXECUTION:
110
- return "PreExecution"
111
- return "PostExecution"
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 _process_escalation_response(
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 and update state based on guardrail scope.
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
- scope: The guardrail scope (LLM/AGENT/TOOL).
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
- For LLM scope: Command to update messages with reviewed inputs/outputs.
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[Any](update={"messages": msgs})
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 enum.
339
+ execution_stage: The execution stage (PRE_EXECUTION or POST_EXECUTION).
214
340
 
215
341
  Returns:
216
- For non-LLM scope: Empty string.
217
- For LLM PreExecution: JSON string with message content.
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 messages in state",
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 = _message_text(last_message)
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 = _message_text(last_message)
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 _message_text(last_message)
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 ..types import AgentGuardrailsGraphState
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