uipath-core 0.1.0__py3-none-any.whl → 0.1.2__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/core/chat/__init__.py +20 -1
- uipath/core/chat/async_stream.py +11 -3
- uipath/core/chat/citation.py +8 -4
- uipath/core/chat/content.py +6 -1
- uipath/core/chat/error.py +34 -0
- uipath/core/chat/event.py +25 -1
- uipath/core/chat/exchange.py +3 -0
- uipath/core/chat/interrupt.py +35 -0
- uipath/core/chat/message.py +4 -0
- uipath/core/chat/tool.py +3 -1
- uipath/core/guardrails/__init__.py +44 -0
- uipath/core/guardrails/_deterministic_guardrails_service.py +79 -0
- uipath/core/guardrails/_evaluators.py +309 -0
- uipath/core/guardrails/guardrails.py +170 -0
- {uipath_core-0.1.0.dist-info → uipath_core-0.1.2.dist-info}/METADATA +1 -1
- uipath_core-0.1.2.dist-info/RECORD +31 -0
- uipath_core-0.1.0.dist-info/RECORD +0 -25
- {uipath_core-0.1.0.dist-info → uipath_core-0.1.2.dist-info}/WHEEL +0 -0
- {uipath_core-0.1.0.dist-info → uipath_core-0.1.2.dist-info}/licenses/LICENSE +0 -0
uipath/core/chat/__init__.py
CHANGED
|
@@ -66,13 +66,23 @@ from .conversation import (
|
|
|
66
66
|
UiPathConversationStartedEvent,
|
|
67
67
|
UiPathConversationStartEvent,
|
|
68
68
|
)
|
|
69
|
-
from .
|
|
69
|
+
from .error import (
|
|
70
|
+
UiPathConversationErrorEndEvent,
|
|
71
|
+
UiPathConversationErrorEvent,
|
|
72
|
+
UiPathConversationErrorStartEvent,
|
|
73
|
+
)
|
|
74
|
+
from .event import UiPathConversationEvent, UiPathConversationLabelUpdatedEvent
|
|
70
75
|
from .exchange import (
|
|
71
76
|
UiPathConversationExchange,
|
|
72
77
|
UiPathConversationExchangeEndEvent,
|
|
73
78
|
UiPathConversationExchangeEvent,
|
|
74
79
|
UiPathConversationExchangeStartEvent,
|
|
75
80
|
)
|
|
81
|
+
from .interrupt import (
|
|
82
|
+
UiPathConversationInterruptEndEvent,
|
|
83
|
+
UiPathConversationInterruptEvent,
|
|
84
|
+
UiPathConversationInterruptStartEvent,
|
|
85
|
+
)
|
|
76
86
|
from .message import (
|
|
77
87
|
UiPathConversationMessage,
|
|
78
88
|
UiPathConversationMessageEndEvent,
|
|
@@ -91,6 +101,11 @@ from .tool import (
|
|
|
91
101
|
__all__ = [
|
|
92
102
|
# Root
|
|
93
103
|
"UiPathConversationEvent",
|
|
104
|
+
"UiPathConversationLabelUpdatedEvent",
|
|
105
|
+
# Error
|
|
106
|
+
"UiPathConversationErrorStartEvent",
|
|
107
|
+
"UiPathConversationErrorEndEvent",
|
|
108
|
+
"UiPathConversationErrorEvent",
|
|
94
109
|
# Conversation
|
|
95
110
|
"UiPathConversationCapabilities",
|
|
96
111
|
"UiPathConversationStartEvent",
|
|
@@ -106,6 +121,10 @@ __all__ = [
|
|
|
106
121
|
"UiPathConversationMessageEndEvent",
|
|
107
122
|
"UiPathConversationMessageEvent",
|
|
108
123
|
"UiPathConversationMessage",
|
|
124
|
+
# Interrupt
|
|
125
|
+
"UiPathConversationInterruptStartEvent",
|
|
126
|
+
"UiPathConversationInterruptEndEvent",
|
|
127
|
+
"UiPathConversationInterruptEvent",
|
|
109
128
|
# Content
|
|
110
129
|
"UiPathConversationContentPartChunkEvent",
|
|
111
130
|
"UiPathConversationContentPartStartEvent",
|
uipath/core/chat/async_stream.py
CHANGED
|
@@ -4,11 +4,12 @@ from typing import Any
|
|
|
4
4
|
|
|
5
5
|
from pydantic import BaseModel, ConfigDict, Field
|
|
6
6
|
|
|
7
|
+
from .error import UiPathConversationErrorEvent
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
class UiPathConversationInputStreamChunkEvent(BaseModel):
|
|
9
11
|
"""Represents a single chunk of input stream data."""
|
|
10
12
|
|
|
11
|
-
input_stream_sequence: int | None = Field(None, alias="inputStreamSequence")
|
|
12
13
|
data: str
|
|
13
14
|
|
|
14
15
|
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
|
|
@@ -44,9 +45,16 @@ class UiPathConversationAsyncInputStreamEvent(BaseModel):
|
|
|
44
45
|
"""Encapsulates sub-events related to an asynchronous input stream."""
|
|
45
46
|
|
|
46
47
|
stream_id: str = Field(..., alias="streamId")
|
|
47
|
-
start: UiPathConversationAsyncInputStreamStartEvent | None =
|
|
48
|
-
|
|
48
|
+
start: UiPathConversationAsyncInputStreamStartEvent | None = Field(
|
|
49
|
+
None, alias="startAsyncInputStream"
|
|
50
|
+
)
|
|
51
|
+
end: UiPathConversationAsyncInputStreamEndEvent | None = Field(
|
|
52
|
+
None, alias="endAsyncInputStream"
|
|
53
|
+
)
|
|
49
54
|
chunk: UiPathConversationInputStreamChunkEvent | None = None
|
|
50
55
|
meta_event: dict[str, Any] | None = Field(None, alias="metaEvent")
|
|
56
|
+
error: UiPathConversationErrorEvent | None = Field(
|
|
57
|
+
None, alias="asyncInputStreamError"
|
|
58
|
+
)
|
|
51
59
|
|
|
52
60
|
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
|
uipath/core/chat/citation.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"""Citation events for message content."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from pydantic import BaseModel, ConfigDict, Field
|
|
6
6
|
|
|
7
|
+
from .error import UiPathConversationErrorEvent
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
class UiPathConversationCitationStartEvent(BaseModel):
|
|
9
11
|
"""Indicates the start of a citation target in a content part."""
|
|
@@ -14,7 +16,7 @@ class UiPathConversationCitationStartEvent(BaseModel):
|
|
|
14
16
|
class UiPathConversationCitationEndEvent(BaseModel):
|
|
15
17
|
"""Indicates the end of a citation target in a content part."""
|
|
16
18
|
|
|
17
|
-
sources: list[
|
|
19
|
+
sources: list[UiPathConversationCitationSource]
|
|
18
20
|
|
|
19
21
|
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
|
|
20
22
|
|
|
@@ -27,6 +29,7 @@ class UiPathConversationCitationEvent(BaseModel):
|
|
|
27
29
|
None, alias="startCitation"
|
|
28
30
|
)
|
|
29
31
|
end: UiPathConversationCitationEndEvent | None = Field(None, alias="endCitation")
|
|
32
|
+
error: UiPathConversationErrorEvent | None = Field(None, alias="citationError")
|
|
30
33
|
|
|
31
34
|
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
|
|
32
35
|
|
|
@@ -52,9 +55,10 @@ class UiPathConversationCitationSourceMedia(BaseModel):
|
|
|
52
55
|
class UiPathConversationCitationSource(BaseModel):
|
|
53
56
|
"""Represents a citation source, either a URL or media reference."""
|
|
54
57
|
|
|
55
|
-
title: str
|
|
58
|
+
title: str
|
|
59
|
+
number: int
|
|
56
60
|
|
|
57
|
-
# Union of Url or Media
|
|
61
|
+
# Union of Url or Media - these are optional
|
|
58
62
|
url: str | None = None
|
|
59
63
|
mime_type: str | None = Field(None, alias="mimeType")
|
|
60
64
|
download_url: str | None = Field(None, alias="downloadUrl")
|
uipath/core/chat/content.py
CHANGED
|
@@ -5,6 +5,7 @@ from typing import Any
|
|
|
5
5
|
from pydantic import BaseModel, ConfigDict, Field
|
|
6
6
|
|
|
7
7
|
from .citation import UiPathConversationCitation, UiPathConversationCitationEvent
|
|
8
|
+
from .error import UiPathConversationErrorEvent
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class UiPathConversationContentPartChunkEvent(BaseModel):
|
|
@@ -21,6 +22,9 @@ class UiPathConversationContentPartStartEvent(BaseModel):
|
|
|
21
22
|
|
|
22
23
|
mime_type: str = Field(..., alias="mimeType")
|
|
23
24
|
metadata: dict[str, Any] | None = Field(None, alias="metaData")
|
|
25
|
+
external_value: "UiPathExternalValue | None" = Field(None, alias="externalValue")
|
|
26
|
+
name: str | None = None
|
|
27
|
+
timestamp: str | None = None
|
|
24
28
|
|
|
25
29
|
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
|
|
26
30
|
|
|
@@ -49,6 +53,7 @@ class UiPathConversationContentPartEvent(BaseModel):
|
|
|
49
53
|
)
|
|
50
54
|
chunk: UiPathConversationContentPartChunkEvent | None = None
|
|
51
55
|
meta_event: dict[str, Any] | None = Field(None, alias="metaEvent")
|
|
56
|
+
error: UiPathConversationErrorEvent | None = Field(None, alias="contentPartError")
|
|
52
57
|
|
|
53
58
|
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
|
|
54
59
|
|
|
@@ -64,7 +69,7 @@ class UiPathInlineValue(BaseModel):
|
|
|
64
69
|
class UiPathExternalValue(BaseModel):
|
|
65
70
|
"""Used when a value is too large to be returned inline."""
|
|
66
71
|
|
|
67
|
-
|
|
72
|
+
uri: str
|
|
68
73
|
byte_count: int | None = Field(None, alias="byteCount")
|
|
69
74
|
|
|
70
75
|
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Common error event models used across all conversation event types."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class UiPathConversationErrorStartEvent(BaseModel):
|
|
9
|
+
"""Represents the start of an error condition."""
|
|
10
|
+
|
|
11
|
+
message: str
|
|
12
|
+
details: Any | None = None
|
|
13
|
+
|
|
14
|
+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class UiPathConversationErrorEndEvent(BaseModel):
|
|
18
|
+
"""Represents the end of an error condition."""
|
|
19
|
+
|
|
20
|
+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class UiPathConversationErrorEvent(BaseModel):
|
|
24
|
+
"""Encapsulates sub-events that represent the start and end of an error condition.
|
|
25
|
+
|
|
26
|
+
This is a common error event model used across all event types (conversation, exchange,
|
|
27
|
+
message, content part, citation, tool call, async input stream).
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
error_id: str = Field(..., alias="errorId")
|
|
31
|
+
start: UiPathConversationErrorStartEvent | None = Field(None, alias="startError")
|
|
32
|
+
end: UiPathConversationErrorEndEvent | None = Field(None, alias="endError")
|
|
33
|
+
|
|
34
|
+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
|
uipath/core/chat/event.py
CHANGED
|
@@ -12,11 +12,21 @@ from .conversation import (
|
|
|
12
12
|
UiPathConversationStartedEvent,
|
|
13
13
|
UiPathConversationStartEvent,
|
|
14
14
|
)
|
|
15
|
+
from .error import UiPathConversationErrorEvent
|
|
15
16
|
from .exchange import UiPathConversationExchangeEvent
|
|
16
17
|
from .meta import UiPathConversationMetaEvent
|
|
17
18
|
from .tool import UiPathConversationToolCallEvent
|
|
18
19
|
|
|
19
20
|
|
|
21
|
+
class UiPathConversationLabelUpdatedEvent(BaseModel):
|
|
22
|
+
"""Indicates the update of the conversation label."""
|
|
23
|
+
|
|
24
|
+
label: str
|
|
25
|
+
autogenerated: bool
|
|
26
|
+
|
|
27
|
+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
|
|
28
|
+
|
|
29
|
+
|
|
20
30
|
class UiPathConversationEvent(BaseModel):
|
|
21
31
|
"""The top-level event type representing an event in a conversation.
|
|
22
32
|
|
|
@@ -31,13 +41,17 @@ class UiPathConversationEvent(BaseModel):
|
|
|
31
41
|
)
|
|
32
42
|
start: UiPathConversationStartEvent | None = Field(
|
|
33
43
|
None,
|
|
44
|
+
alias="startSession",
|
|
34
45
|
description="Signals the start of an event stream concerning a conversation. This event does NOT necessarily mean this is a brand new conversation. It may be a continuation of an existing conversation.",
|
|
35
46
|
)
|
|
36
47
|
started: UiPathConversationStartedEvent | None = Field(
|
|
37
|
-
None,
|
|
48
|
+
None,
|
|
49
|
+
alias="sessionStarted",
|
|
50
|
+
description="Signals the acceptance of the start of a conversation.",
|
|
38
51
|
)
|
|
39
52
|
end: UiPathConversationEndEvent | None = Field(
|
|
40
53
|
None,
|
|
54
|
+
alias="endSession",
|
|
41
55
|
description="Signals the end of a conversation event stream. This does NOT mean the conversation is over. A new event stream for the conversation could be started in the future.",
|
|
42
56
|
)
|
|
43
57
|
exchange: UiPathConversationExchangeEvent | None = Field(
|
|
@@ -54,10 +68,20 @@ class UiPathConversationEvent(BaseModel):
|
|
|
54
68
|
alias="asyncToolCall",
|
|
55
69
|
description="Optional async tool call sub-event. This feature is not supported by all LLMs. Most tool calls are scoped to a message, and use the toolCall and toolResult properties defined by the ConversationMessage type.",
|
|
56
70
|
)
|
|
71
|
+
label_updated: "UiPathConversationLabelUpdatedEvent | None" = Field(
|
|
72
|
+
None,
|
|
73
|
+
alias="labelUpdated",
|
|
74
|
+
description="Indicates that the conversation's label has been updated. Useful for UIs to update the display name of live conversations.",
|
|
75
|
+
)
|
|
57
76
|
meta_event: UiPathConversationMetaEvent | None = Field(
|
|
58
77
|
None,
|
|
59
78
|
alias="metaEvent",
|
|
60
79
|
description="Allows additional events to be sent in the context of the enclosing event stream.",
|
|
61
80
|
)
|
|
81
|
+
error: UiPathConversationErrorEvent | None = Field(
|
|
82
|
+
None,
|
|
83
|
+
alias="conversationError",
|
|
84
|
+
description="Sent by the service to indicate the start and end of an error condition impacting a conversation. The client should not send any events for this conversation while the error condition is present.",
|
|
85
|
+
)
|
|
62
86
|
|
|
63
87
|
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
|
uipath/core/chat/exchange.py
CHANGED
|
@@ -20,6 +20,7 @@ from typing import Any
|
|
|
20
20
|
|
|
21
21
|
from pydantic import BaseModel, ConfigDict, Field
|
|
22
22
|
|
|
23
|
+
from .error import UiPathConversationErrorEvent
|
|
23
24
|
from .message import UiPathConversationMessage, UiPathConversationMessageEvent
|
|
24
25
|
|
|
25
26
|
|
|
@@ -28,6 +29,7 @@ class UiPathConversationExchangeStartEvent(BaseModel):
|
|
|
28
29
|
|
|
29
30
|
conversation_sequence: int | None = Field(None, alias="conversationSequence")
|
|
30
31
|
metadata: dict[str, Any] | None = Field(None, alias="metaData")
|
|
32
|
+
timestamp: str | None = None
|
|
31
33
|
|
|
32
34
|
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
|
|
33
35
|
|
|
@@ -50,6 +52,7 @@ class UiPathConversationExchangeEvent(BaseModel):
|
|
|
50
52
|
end: UiPathConversationExchangeEndEvent | None = Field(None, alias="endExchange")
|
|
51
53
|
message: UiPathConversationMessageEvent | None = None
|
|
52
54
|
meta_event: dict[str, Any] | None = Field(None, alias="metaEvent")
|
|
55
|
+
error: UiPathConversationErrorEvent | None = Field(None, alias="exchangeError")
|
|
53
56
|
|
|
54
57
|
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
|
|
55
58
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Interrupt events for human-in-the-loop patterns."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class UiPathConversationInterruptStartEvent(BaseModel):
|
|
9
|
+
"""Signals the start of an interrupt - a pause point where the agent needs external input."""
|
|
10
|
+
|
|
11
|
+
type: str
|
|
12
|
+
value: Any
|
|
13
|
+
|
|
14
|
+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class UiPathConversationInterruptEndEvent(BaseModel):
|
|
18
|
+
"""Signals the interrupt end event with the provided value."""
|
|
19
|
+
|
|
20
|
+
# Can be any type
|
|
21
|
+
model_config = ConfigDict(
|
|
22
|
+
validate_by_name=True, validate_by_alias=True, extra="allow"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class UiPathConversationInterruptEvent(BaseModel):
|
|
27
|
+
"""Encapsulates interrupt-related events within a message."""
|
|
28
|
+
|
|
29
|
+
interrupt_id: str = Field(..., alias="interruptId")
|
|
30
|
+
start: UiPathConversationInterruptStartEvent | None = Field(
|
|
31
|
+
None, alias="startInterrupt"
|
|
32
|
+
)
|
|
33
|
+
end: Any | None = Field(None, alias="endInterrupt")
|
|
34
|
+
|
|
35
|
+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
|
uipath/core/chat/message.py
CHANGED
|
@@ -5,6 +5,8 @@ from typing import Any
|
|
|
5
5
|
from pydantic import BaseModel, ConfigDict, Field
|
|
6
6
|
|
|
7
7
|
from .content import UiPathConversationContentPart, UiPathConversationContentPartEvent
|
|
8
|
+
from .error import UiPathConversationErrorEvent
|
|
9
|
+
from .interrupt import UiPathConversationInterruptEvent
|
|
8
10
|
from .tool import UiPathConversationToolCall, UiPathConversationToolCallEvent
|
|
9
11
|
|
|
10
12
|
|
|
@@ -39,7 +41,9 @@ class UiPathConversationMessageEvent(BaseModel):
|
|
|
39
41
|
None, alias="contentPart"
|
|
40
42
|
)
|
|
41
43
|
tool_call: UiPathConversationToolCallEvent | None = Field(None, alias="toolCall")
|
|
44
|
+
interrupt: UiPathConversationInterruptEvent | None = None
|
|
42
45
|
meta_event: dict[str, Any] | None = Field(None, alias="metaEvent")
|
|
46
|
+
error: UiPathConversationErrorEvent | None = Field(None, alias="messageError")
|
|
43
47
|
|
|
44
48
|
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
|
|
45
49
|
|
uipath/core/chat/tool.py
CHANGED
|
@@ -5,6 +5,7 @@ from typing import Any
|
|
|
5
5
|
from pydantic import BaseModel, ConfigDict, Field
|
|
6
6
|
|
|
7
7
|
from .content import InlineOrExternal
|
|
8
|
+
from .error import UiPathConversationErrorEvent
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class UiPathConversationToolCallResult(BaseModel):
|
|
@@ -23,7 +24,7 @@ class UiPathConversationToolCall(BaseModel):
|
|
|
23
24
|
|
|
24
25
|
tool_call_id: str = Field(..., alias="toolCallId")
|
|
25
26
|
name: str
|
|
26
|
-
|
|
27
|
+
input: InlineOrExternal | None = None
|
|
27
28
|
timestamp: str | None = None
|
|
28
29
|
result: UiPathConversationToolCallResult | None = None
|
|
29
30
|
|
|
@@ -62,5 +63,6 @@ class UiPathConversationToolCallEvent(BaseModel):
|
|
|
62
63
|
)
|
|
63
64
|
end: UiPathConversationToolCallEndEvent | None = Field(None, alias="endToolCall")
|
|
64
65
|
meta_event: dict[str, Any] | None = Field(None, alias="metaEvent")
|
|
66
|
+
error: UiPathConversationErrorEvent | None = Field(None, alias="toolCallError")
|
|
65
67
|
|
|
66
68
|
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""UiPath Guardrails Models.
|
|
2
|
+
|
|
3
|
+
This module contains models related to UiPath Guardrails.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from ._deterministic_guardrails_service import DeterministicGuardrailsService
|
|
7
|
+
from .guardrails import (
|
|
8
|
+
AllFieldsSelector,
|
|
9
|
+
ApplyTo,
|
|
10
|
+
BaseGuardrail,
|
|
11
|
+
BooleanRule,
|
|
12
|
+
DeterministicGuardrail,
|
|
13
|
+
FieldReference,
|
|
14
|
+
FieldSelector,
|
|
15
|
+
FieldSource,
|
|
16
|
+
GuardrailScope,
|
|
17
|
+
GuardrailSelector,
|
|
18
|
+
NumberRule,
|
|
19
|
+
Rule,
|
|
20
|
+
SelectorType,
|
|
21
|
+
SpecificFieldsSelector,
|
|
22
|
+
UniversalRule,
|
|
23
|
+
WordRule,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"DeterministicGuardrailsService",
|
|
28
|
+
"FieldSource",
|
|
29
|
+
"ApplyTo",
|
|
30
|
+
"FieldReference",
|
|
31
|
+
"SelectorType",
|
|
32
|
+
"AllFieldsSelector",
|
|
33
|
+
"SpecificFieldsSelector",
|
|
34
|
+
"FieldSelector",
|
|
35
|
+
"BaseGuardrail",
|
|
36
|
+
"DeterministicGuardrail",
|
|
37
|
+
"WordRule",
|
|
38
|
+
"NumberRule",
|
|
39
|
+
"BooleanRule",
|
|
40
|
+
"UniversalRule",
|
|
41
|
+
"Rule",
|
|
42
|
+
"GuardrailScope",
|
|
43
|
+
"GuardrailSelector",
|
|
44
|
+
]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
from ..tracing.decorators import traced
|
|
6
|
+
from ._evaluators import (
|
|
7
|
+
evaluate_boolean_rule,
|
|
8
|
+
evaluate_number_rule,
|
|
9
|
+
evaluate_universal_rule,
|
|
10
|
+
evaluate_word_rule,
|
|
11
|
+
)
|
|
12
|
+
from .guardrails import (
|
|
13
|
+
BooleanRule,
|
|
14
|
+
DeterministicGuardrail,
|
|
15
|
+
GuardrailValidationResult,
|
|
16
|
+
NumberRule,
|
|
17
|
+
UniversalRule,
|
|
18
|
+
WordRule,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DeterministicGuardrailsService(BaseModel):
|
|
23
|
+
@traced("evaluate_pre_deterministic_guardrail", run_type="uipath")
|
|
24
|
+
def evaluate_pre_deterministic_guardrail(
|
|
25
|
+
self,
|
|
26
|
+
input_data: dict[str, Any],
|
|
27
|
+
guardrail: DeterministicGuardrail,
|
|
28
|
+
) -> GuardrailValidationResult:
|
|
29
|
+
"""Evaluate deterministic guardrail rules against input data (pre-execution)."""
|
|
30
|
+
return self._evaluate_deterministic_guardrail(
|
|
31
|
+
input_data=input_data,
|
|
32
|
+
output_data={},
|
|
33
|
+
guardrail=guardrail,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
@traced("evaluate_post_deterministic_guardrails", run_type="uipath")
|
|
37
|
+
def evaluate_post_deterministic_guardrail(
|
|
38
|
+
self,
|
|
39
|
+
input_data: dict[str, Any],
|
|
40
|
+
output_data: dict[str, Any],
|
|
41
|
+
guardrail: DeterministicGuardrail,
|
|
42
|
+
) -> GuardrailValidationResult:
|
|
43
|
+
"""Evaluate deterministic guardrail rules against input and output data."""
|
|
44
|
+
return self._evaluate_deterministic_guardrail(
|
|
45
|
+
input_data=input_data,
|
|
46
|
+
output_data=output_data,
|
|
47
|
+
guardrail=guardrail,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def _evaluate_deterministic_guardrail(
|
|
52
|
+
input_data: dict[str, Any],
|
|
53
|
+
output_data: dict[str, Any],
|
|
54
|
+
guardrail: DeterministicGuardrail,
|
|
55
|
+
) -> GuardrailValidationResult:
|
|
56
|
+
"""Evaluate deterministic guardrail rules against input and output data."""
|
|
57
|
+
for rule in guardrail.rules:
|
|
58
|
+
if isinstance(rule, WordRule):
|
|
59
|
+
passed, reason = evaluate_word_rule(rule, input_data, output_data)
|
|
60
|
+
elif isinstance(rule, NumberRule):
|
|
61
|
+
passed, reason = evaluate_number_rule(rule, input_data, output_data)
|
|
62
|
+
elif isinstance(rule, BooleanRule):
|
|
63
|
+
passed, reason = evaluate_boolean_rule(rule, input_data, output_data)
|
|
64
|
+
elif isinstance(rule, UniversalRule):
|
|
65
|
+
passed, reason = evaluate_universal_rule(rule, output_data)
|
|
66
|
+
else:
|
|
67
|
+
return GuardrailValidationResult(
|
|
68
|
+
validation_passed=False,
|
|
69
|
+
reason=f"Unknown rule type: {type(rule)}",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if not passed:
|
|
73
|
+
return GuardrailValidationResult(
|
|
74
|
+
validation_passed=False, reason=reason or "Rule validation failed"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return GuardrailValidationResult(
|
|
78
|
+
validation_passed=True, reason="All deterministic guardrail rules passed"
|
|
79
|
+
)
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""Guardrail rule evaluators.
|
|
2
|
+
|
|
3
|
+
This module provides functions for evaluating different types of guardrail rules
|
|
4
|
+
against input and output data.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from enum import IntEnum
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from .guardrails import (
|
|
11
|
+
AllFieldsSelector,
|
|
12
|
+
ApplyTo,
|
|
13
|
+
BooleanRule,
|
|
14
|
+
FieldReference,
|
|
15
|
+
FieldSource,
|
|
16
|
+
NumberRule,
|
|
17
|
+
SpecificFieldsSelector,
|
|
18
|
+
UniversalRule,
|
|
19
|
+
WordRule,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ArrayDepth(IntEnum):
|
|
24
|
+
"""Array depth enumeration for path parsing."""
|
|
25
|
+
|
|
26
|
+
NONE = 0 # Not an array
|
|
27
|
+
SINGLE = 1 # Single array [*]
|
|
28
|
+
MATRIX = 2 # Matrix [*][*]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def extract_field_value(path: str, data: dict[str, Any]) -> list[Any]:
|
|
32
|
+
"""Extract field values from data using dot-notation path.
|
|
33
|
+
|
|
34
|
+
Supports array notation with [*] and [*][*] for arrays and matrices.
|
|
35
|
+
If an array is encountered at any point in the path, all elements are checked.
|
|
36
|
+
"""
|
|
37
|
+
if not isinstance(data, dict):
|
|
38
|
+
return []
|
|
39
|
+
|
|
40
|
+
results: list[Any] = []
|
|
41
|
+
|
|
42
|
+
def _parse_path_segment(segment: str) -> tuple[str, ArrayDepth]:
|
|
43
|
+
"""Parse a path segment to extract field name and array depth."""
|
|
44
|
+
if "[*][*]" in segment:
|
|
45
|
+
field_name = segment.replace("[*][*]", "")
|
|
46
|
+
return field_name, ArrayDepth.MATRIX
|
|
47
|
+
elif "[*]" in segment:
|
|
48
|
+
field_name = segment.replace("[*]", "")
|
|
49
|
+
return field_name, ArrayDepth.SINGLE
|
|
50
|
+
else:
|
|
51
|
+
return segment, ArrayDepth.NONE
|
|
52
|
+
|
|
53
|
+
def _traverse(current: Any, remaining_parts: list[str]) -> None:
|
|
54
|
+
"""Recursively traverse the path, handling arrays and matrices."""
|
|
55
|
+
if not remaining_parts:
|
|
56
|
+
# End of path, add current value
|
|
57
|
+
if current is not None:
|
|
58
|
+
if isinstance(current, list):
|
|
59
|
+
# If current is a list, add all elements
|
|
60
|
+
results.extend(current)
|
|
61
|
+
else:
|
|
62
|
+
results.append(current)
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
part = remaining_parts[0]
|
|
66
|
+
next_parts = remaining_parts[1:]
|
|
67
|
+
field_name, array_depth = _parse_path_segment(part)
|
|
68
|
+
|
|
69
|
+
if isinstance(current, dict):
|
|
70
|
+
if field_name not in current:
|
|
71
|
+
return
|
|
72
|
+
next_value = current.get(field_name)
|
|
73
|
+
|
|
74
|
+
if array_depth == ArrayDepth.MATRIX:
|
|
75
|
+
# Matrix [*][*] - expect 2D array
|
|
76
|
+
if isinstance(next_value, list):
|
|
77
|
+
for row in next_value:
|
|
78
|
+
if isinstance(row, list):
|
|
79
|
+
for item in row:
|
|
80
|
+
_traverse(item, next_parts)
|
|
81
|
+
else:
|
|
82
|
+
# Not a 2D array, treat as 1D
|
|
83
|
+
_traverse(row, next_parts)
|
|
84
|
+
elif array_depth == ArrayDepth.SINGLE:
|
|
85
|
+
# Array [*] - expect 1D array
|
|
86
|
+
if isinstance(next_value, list):
|
|
87
|
+
for item in next_value:
|
|
88
|
+
_traverse(item, next_parts)
|
|
89
|
+
else:
|
|
90
|
+
# Not an array, but path expects one - continue traversal
|
|
91
|
+
_traverse(next_value, next_parts)
|
|
92
|
+
else:
|
|
93
|
+
# No array notation, continue traversal
|
|
94
|
+
if isinstance(next_value, list):
|
|
95
|
+
# Array encountered without notation - check all elements
|
|
96
|
+
for item in next_value:
|
|
97
|
+
_traverse(item, next_parts)
|
|
98
|
+
else:
|
|
99
|
+
_traverse(next_value, next_parts)
|
|
100
|
+
elif isinstance(current, list):
|
|
101
|
+
# Current is an array - check all elements
|
|
102
|
+
for item in current:
|
|
103
|
+
_traverse(item, remaining_parts)
|
|
104
|
+
else:
|
|
105
|
+
# Cannot traverse further
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
path_parts = path.split(".")
|
|
109
|
+
_traverse(data, path_parts)
|
|
110
|
+
return results
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def get_fields_from_selector(
|
|
114
|
+
field_selector: AllFieldsSelector | SpecificFieldsSelector,
|
|
115
|
+
input_data: dict[str, Any],
|
|
116
|
+
output_data: dict[str, Any],
|
|
117
|
+
) -> list[tuple[Any, FieldReference]]:
|
|
118
|
+
"""Get field values and their references based on the field selector."""
|
|
119
|
+
fields: list[tuple[Any, FieldReference]] = []
|
|
120
|
+
|
|
121
|
+
if isinstance(field_selector, AllFieldsSelector):
|
|
122
|
+
# For "all" selector, we need to collect all fields from both input and output
|
|
123
|
+
# This is a simplified implementation - in practice, you might want to
|
|
124
|
+
# recursively collect all nested fields
|
|
125
|
+
for key, value in input_data.items():
|
|
126
|
+
fields.append(
|
|
127
|
+
(
|
|
128
|
+
value,
|
|
129
|
+
FieldReference(path=key, source=FieldSource.INPUT),
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
for key, value in output_data.items():
|
|
133
|
+
fields.append(
|
|
134
|
+
(
|
|
135
|
+
value,
|
|
136
|
+
FieldReference(path=key, source=FieldSource.OUTPUT),
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
elif isinstance(field_selector, SpecificFieldsSelector):
|
|
140
|
+
# For specific fields, extract values based on field references
|
|
141
|
+
for field_ref in field_selector.fields:
|
|
142
|
+
# Use FieldSource to determine whether to use input_data or output_data
|
|
143
|
+
if field_ref.source == FieldSource.INPUT:
|
|
144
|
+
data = input_data
|
|
145
|
+
elif field_ref.source == FieldSource.OUTPUT:
|
|
146
|
+
data = output_data
|
|
147
|
+
else:
|
|
148
|
+
# Unknown source, skip this field
|
|
149
|
+
continue
|
|
150
|
+
# Extract values (may return multiple if arrays are in the path)
|
|
151
|
+
values = extract_field_value(field_ref.path, data)
|
|
152
|
+
# Add each value as a separate field reference
|
|
153
|
+
for value in values:
|
|
154
|
+
fields.append((value, field_ref))
|
|
155
|
+
|
|
156
|
+
return fields
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def format_guardrail_error_message(
|
|
160
|
+
field_ref: FieldReference,
|
|
161
|
+
operator: str,
|
|
162
|
+
expected_value: str | None = None,
|
|
163
|
+
) -> str:
|
|
164
|
+
"""Format a guardrail error message following the standard pattern."""
|
|
165
|
+
source = "Input" if field_ref.source == FieldSource.INPUT else "Output"
|
|
166
|
+
message = f"{source} data didn't match the guardrail condition: [{field_ref.path}] {operator}"
|
|
167
|
+
if expected_value and expected_value.strip():
|
|
168
|
+
message += f" [{expected_value.strip()}]"
|
|
169
|
+
return message
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def evaluate_word_rule(
|
|
173
|
+
rule: WordRule, input_data: dict[str, Any], output_data: dict[str, Any]
|
|
174
|
+
) -> tuple[bool, str | None]:
|
|
175
|
+
"""Evaluate a word rule against input and output data."""
|
|
176
|
+
fields = get_fields_from_selector(rule.field_selector, input_data, output_data)
|
|
177
|
+
|
|
178
|
+
for field_value, field_ref in fields:
|
|
179
|
+
if field_value is None:
|
|
180
|
+
continue
|
|
181
|
+
|
|
182
|
+
# Word rules should only be applied to string values
|
|
183
|
+
# Skip non-string values (numbers, booleans, objects, arrays, etc.)
|
|
184
|
+
if not isinstance(field_value, str):
|
|
185
|
+
continue
|
|
186
|
+
|
|
187
|
+
field_str = field_value
|
|
188
|
+
|
|
189
|
+
# Use the custom function to evaluate the rule
|
|
190
|
+
try:
|
|
191
|
+
passed = rule.func(field_str)
|
|
192
|
+
except Exception:
|
|
193
|
+
# If function raises an exception, treat as failure
|
|
194
|
+
passed = False
|
|
195
|
+
|
|
196
|
+
if not passed:
|
|
197
|
+
reason = format_guardrail_error_message(
|
|
198
|
+
field_ref, "comparing function", None
|
|
199
|
+
)
|
|
200
|
+
return False, reason
|
|
201
|
+
|
|
202
|
+
return True, "All word rule validations passed"
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def evaluate_number_rule(
|
|
206
|
+
rule: NumberRule, input_data: dict[str, Any], output_data: dict[str, Any]
|
|
207
|
+
) -> tuple[bool, str | None]:
|
|
208
|
+
"""Evaluate a number rule against input and output data."""
|
|
209
|
+
fields = get_fields_from_selector(rule.field_selector, input_data, output_data)
|
|
210
|
+
|
|
211
|
+
for field_value, field_ref in fields:
|
|
212
|
+
if field_value is None:
|
|
213
|
+
continue
|
|
214
|
+
|
|
215
|
+
# Number rules should only be applied to numeric values
|
|
216
|
+
# Skip non-numeric values (strings, booleans, objects, arrays, etc.)
|
|
217
|
+
# Note: bool is a subclass of int in Python, so we must check for bool first
|
|
218
|
+
if isinstance(field_value, bool) or not isinstance(field_value, (int, float)):
|
|
219
|
+
continue
|
|
220
|
+
|
|
221
|
+
field_num = float(field_value)
|
|
222
|
+
|
|
223
|
+
# Use the custom function to evaluate the rule
|
|
224
|
+
try:
|
|
225
|
+
passed = rule.func(field_num)
|
|
226
|
+
except Exception:
|
|
227
|
+
# If function raises an exception, treat as failure
|
|
228
|
+
passed = False
|
|
229
|
+
|
|
230
|
+
if not passed:
|
|
231
|
+
reason = format_guardrail_error_message(
|
|
232
|
+
field_ref, "comparing function", None
|
|
233
|
+
)
|
|
234
|
+
return False, reason
|
|
235
|
+
|
|
236
|
+
return True, "All number rule validations passed"
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def evaluate_boolean_rule(
|
|
240
|
+
rule: BooleanRule,
|
|
241
|
+
input_data: dict[str, Any],
|
|
242
|
+
output_data: dict[str, Any],
|
|
243
|
+
) -> tuple[bool, str | None]:
|
|
244
|
+
"""Evaluate a boolean rule against input and output data."""
|
|
245
|
+
fields = get_fields_from_selector(rule.field_selector, input_data, output_data)
|
|
246
|
+
|
|
247
|
+
for field_value, field_ref in fields:
|
|
248
|
+
if field_value is None:
|
|
249
|
+
continue
|
|
250
|
+
|
|
251
|
+
# Boolean rules should only be applied to boolean values
|
|
252
|
+
# Skip non-boolean values (strings, numbers, objects, arrays, etc.)
|
|
253
|
+
if not isinstance(field_value, bool):
|
|
254
|
+
continue
|
|
255
|
+
|
|
256
|
+
field_bool = field_value
|
|
257
|
+
|
|
258
|
+
# Use the custom function to evaluate the rule
|
|
259
|
+
try:
|
|
260
|
+
passed = rule.func(field_bool)
|
|
261
|
+
except Exception:
|
|
262
|
+
# If function raises an exception, treat as failure
|
|
263
|
+
passed = False
|
|
264
|
+
|
|
265
|
+
if not passed:
|
|
266
|
+
reason = format_guardrail_error_message(
|
|
267
|
+
field_ref, "comparing function", None
|
|
268
|
+
)
|
|
269
|
+
return False, reason
|
|
270
|
+
|
|
271
|
+
return True, "All boolean rule validations passed"
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def evaluate_universal_rule(
|
|
275
|
+
rule: UniversalRule,
|
|
276
|
+
output_data: dict[str, Any],
|
|
277
|
+
) -> tuple[bool, str | None]:
|
|
278
|
+
"""Evaluate a universal rule against input and output data.
|
|
279
|
+
|
|
280
|
+
Universal rules trigger based on the apply_to scope and execution phase:
|
|
281
|
+
- Pre-execution (empty output_data):
|
|
282
|
+
- INPUT: triggers (validation_passed = False)
|
|
283
|
+
- OUTPUT: does not trigger (validation_passed = True)
|
|
284
|
+
- INPUT_AND_OUTPUT: triggers (validation_passed = False)
|
|
285
|
+
- Post-execution (output_data has data):
|
|
286
|
+
- INPUT: does not trigger (validation_passed = True)
|
|
287
|
+
- OUTPUT: triggers (validation_passed = False)
|
|
288
|
+
- INPUT_AND_OUTPUT: triggers (validation_passed = False)
|
|
289
|
+
"""
|
|
290
|
+
# Determine if this is pre-execution (no output data) or post-execution
|
|
291
|
+
is_pre_execution = not output_data or len(output_data) == 0
|
|
292
|
+
|
|
293
|
+
if rule.apply_to == ApplyTo.INPUT:
|
|
294
|
+
# INPUT: triggers in pre-execution, does not trigger in post-execution
|
|
295
|
+
if is_pre_execution:
|
|
296
|
+
return False, "Universal rule validation triggered (pre-execution, input)"
|
|
297
|
+
else:
|
|
298
|
+
return True, "Universal rule validation passed (post-execution, input)"
|
|
299
|
+
elif rule.apply_to == ApplyTo.OUTPUT:
|
|
300
|
+
# OUTPUT: does not trigger in pre-execution, triggers in post-execution
|
|
301
|
+
if is_pre_execution:
|
|
302
|
+
return True, "Universal rule validation passed (pre-execution, output)"
|
|
303
|
+
else:
|
|
304
|
+
return False, "Universal rule validation triggered (post-execution, output)"
|
|
305
|
+
elif rule.apply_to == ApplyTo.INPUT_AND_OUTPUT:
|
|
306
|
+
# INPUT_AND_OUTPUT: triggers in both phases
|
|
307
|
+
return False, "Universal rule validation triggered (input and output)"
|
|
308
|
+
else:
|
|
309
|
+
return False, f"Unknown apply_to value: {rule.apply_to}"
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Guardrails models for UiPath Platform."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Annotated, Callable, Literal
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GuardrailValidationResult(BaseModel):
|
|
10
|
+
"""Result returned from validating input with a given guardrail.
|
|
11
|
+
|
|
12
|
+
Attributes:
|
|
13
|
+
validation_passed: Indicates whether the input data passed the guardrail validation.
|
|
14
|
+
reason: Textual explanation describing why the validation passed or failed.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
18
|
+
|
|
19
|
+
validation_passed: bool = Field(
|
|
20
|
+
alias="validation_passed", description="Whether the input passed validation."
|
|
21
|
+
)
|
|
22
|
+
reason: str = Field(
|
|
23
|
+
alias="reason", description="Explanation for the validation result."
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class FieldSource(str, Enum):
|
|
28
|
+
"""Field source enumeration."""
|
|
29
|
+
|
|
30
|
+
INPUT = "input"
|
|
31
|
+
OUTPUT = "output"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ApplyTo(str, Enum):
|
|
35
|
+
"""Apply to enumeration."""
|
|
36
|
+
|
|
37
|
+
INPUT = "input"
|
|
38
|
+
INPUT_AND_OUTPUT = "inputAndOutput"
|
|
39
|
+
OUTPUT = "output"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class FieldReference(BaseModel):
|
|
43
|
+
"""Field reference model."""
|
|
44
|
+
|
|
45
|
+
path: str
|
|
46
|
+
source: FieldSource
|
|
47
|
+
|
|
48
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class SelectorType(str, Enum):
|
|
52
|
+
"""Selector type enumeration."""
|
|
53
|
+
|
|
54
|
+
ALL = "all"
|
|
55
|
+
SPECIFIC = "specific"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class AllFieldsSelector(BaseModel):
|
|
59
|
+
"""All fields selector."""
|
|
60
|
+
|
|
61
|
+
selector_type: Literal["all"] = Field(alias="$selectorType")
|
|
62
|
+
|
|
63
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class SpecificFieldsSelector(BaseModel):
|
|
67
|
+
"""Specific fields selector."""
|
|
68
|
+
|
|
69
|
+
selector_type: Literal["specific"] = Field(alias="$selectorType")
|
|
70
|
+
fields: list[FieldReference]
|
|
71
|
+
|
|
72
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
FieldSelector = Annotated[
|
|
76
|
+
AllFieldsSelector | SpecificFieldsSelector,
|
|
77
|
+
Field(discriminator="selector_type"),
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class RuleType(str, Enum):
|
|
82
|
+
"""Rule type enumeration."""
|
|
83
|
+
|
|
84
|
+
BOOLEAN = "boolean"
|
|
85
|
+
NUMBER = "number"
|
|
86
|
+
UNIVERSAL = "always"
|
|
87
|
+
WORD = "word"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class WordRule(BaseModel):
|
|
91
|
+
"""Word rule model."""
|
|
92
|
+
|
|
93
|
+
rule_type: Literal["word"] = Field(alias="$ruleType")
|
|
94
|
+
field_selector: FieldSelector = Field(alias="fieldSelector")
|
|
95
|
+
func: Callable[[str], bool] = Field(exclude=True)
|
|
96
|
+
|
|
97
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class UniversalRule(BaseModel):
|
|
101
|
+
"""Universal rule model."""
|
|
102
|
+
|
|
103
|
+
rule_type: Literal["always"] = Field(alias="$ruleType")
|
|
104
|
+
apply_to: ApplyTo = Field(alias="applyTo")
|
|
105
|
+
|
|
106
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class NumberRule(BaseModel):
|
|
110
|
+
"""Number rule model."""
|
|
111
|
+
|
|
112
|
+
rule_type: Literal["number"] = Field(alias="$ruleType")
|
|
113
|
+
field_selector: FieldSelector = Field(alias="fieldSelector")
|
|
114
|
+
func: Callable[[float], bool] = Field(exclude=True)
|
|
115
|
+
|
|
116
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class BooleanRule(BaseModel):
|
|
120
|
+
"""Boolean rule model."""
|
|
121
|
+
|
|
122
|
+
rule_type: Literal["boolean"] = Field(alias="$ruleType")
|
|
123
|
+
field_selector: FieldSelector = Field(alias="fieldSelector")
|
|
124
|
+
func: Callable[[bool], bool] = Field(exclude=True)
|
|
125
|
+
|
|
126
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
Rule = Annotated[
|
|
130
|
+
WordRule | NumberRule | BooleanRule | UniversalRule,
|
|
131
|
+
Field(discriminator="rule_type"),
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class GuardrailScope(str, Enum):
|
|
136
|
+
"""Guardrail scope enumeration."""
|
|
137
|
+
|
|
138
|
+
AGENT = "Agent"
|
|
139
|
+
LLM = "Llm"
|
|
140
|
+
TOOL = "Tool"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class GuardrailSelector(BaseModel):
|
|
144
|
+
"""Guardrail selector model."""
|
|
145
|
+
|
|
146
|
+
scopes: list[GuardrailScope] = Field(default=[GuardrailScope.TOOL])
|
|
147
|
+
match_names: list[str] | None = Field(None, alias="matchNames")
|
|
148
|
+
|
|
149
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class BaseGuardrail(BaseModel):
|
|
153
|
+
"""Base guardrail model."""
|
|
154
|
+
|
|
155
|
+
id: str
|
|
156
|
+
name: str
|
|
157
|
+
description: str | None = None
|
|
158
|
+
enabled_for_evals: bool = Field(True, alias="enabledForEvals")
|
|
159
|
+
selector: GuardrailSelector
|
|
160
|
+
|
|
161
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class DeterministicGuardrail(BaseGuardrail):
|
|
165
|
+
"""Deterministic guardrail model."""
|
|
166
|
+
|
|
167
|
+
guardrail_type: Literal["custom"] = Field(alias="custom")
|
|
168
|
+
rules: list[Rule]
|
|
169
|
+
|
|
170
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
uipath/core/__init__.py,sha256=KaOxKswriK_zGul3KzILhqnR3G0E5otuXny0IuuwTqA,280
|
|
2
|
+
uipath/core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
uipath/core/chat/__init__.py,sha256=1qwg2n6EhRS_u3vkXN1D40q_5bYNxj5BeQXUp7VtyQw,6013
|
|
4
|
+
uipath/core/chat/async_stream.py,sha256=XbfSAu67TKYzzHoeh-WkolP3WXqEGJ2fTtCX5XqfUKY,2163
|
|
5
|
+
uipath/core/chat/citation.py,sha256=UBZ51wk3PyaXr7uljicEH_8Q2dthBMxYvPR6j2dEhA4,2608
|
|
6
|
+
uipath/core/chat/content.py,sha256=fSbXwSusMuqWF9WQhr1fRQCP5aUMITrBRJ-WJ2jH1vc,3135
|
|
7
|
+
uipath/core/chat/conversation.py,sha256=CVCj4z0DYQsodciCpmiefMjvn8NmJsLTaZU49QiseUw,1723
|
|
8
|
+
uipath/core/chat/error.py,sha256=uXaI0jHYsRhOq8LRHVN5fwODX-uJX68PqJU26oe-4c4,1165
|
|
9
|
+
uipath/core/chat/event.py,sha256=SRif5ZQGEKQBmzcSl2fx0O_--zlKZ7lOVh15hRjLxP4,3832
|
|
10
|
+
uipath/core/chat/exchange.py,sha256=pkYt_pPDyzaJQAfDdqqUM36QLnz4rgBDR0DkBl03r90,2463
|
|
11
|
+
uipath/core/chat/interrupt.py,sha256=-3Fl1_6_7cS8ZV64AaFwDnlNglGBfACwaDAPxI3dTa0,1064
|
|
12
|
+
uipath/core/chat/message.py,sha256=nJJ1xc--b7Nnj5hXwoH4T4pYkZSoiSHzSXlaiPqac4g,2390
|
|
13
|
+
uipath/core/chat/meta.py,sha256=3t0eS9UHoAPHre97QTUeVbjDhnMX4zj4-qG6ju0B8wY,315
|
|
14
|
+
uipath/core/chat/tool.py,sha256=6e5pyX3hOWM5fIzr_fdG49Mbzz6XzJD3nsmha-yGa2k,2308
|
|
15
|
+
uipath/core/errors/__init__.py,sha256=gjxdLibZ0fjwgzPuLJY04P8dIX9rbSM2wQ97jP34ucE,278
|
|
16
|
+
uipath/core/errors/errors.py,sha256=5LajjuTfNW82ju07wT5mD3tXk0S-Ju7OqJqQpPN0F6g,486
|
|
17
|
+
uipath/core/guardrails/__init__.py,sha256=oPJnSJmeDlXNpN6XfnnZUGCDg_BkaG3Tk-chLM743C0,892
|
|
18
|
+
uipath/core/guardrails/_deterministic_guardrails_service.py,sha256=3WGcgpyUIvTXBvheEXjX-XguytxquWJkBevrVeM2tcc,2841
|
|
19
|
+
uipath/core/guardrails/_evaluators.py,sha256=nIODDFu0IjrgVcf0pshmXV0BNAEU-16-liwJv50Lcy4,11343
|
|
20
|
+
uipath/core/guardrails/guardrails.py,sha256=jM_In6kxttkuFoH5KiiE5iREcFAMY2wTxNQkg35ZQqw,4286
|
|
21
|
+
uipath/core/tracing/__init__.py,sha256=1XNLYZ4J76XkRrizGO486mS6yxzVXUbrldpvxTyJe3E,483
|
|
22
|
+
uipath/core/tracing/_utils.py,sha256=FiCFGOFa4czruhlSF87Q5Q4jX9KKPHZiw8k14K7W5v4,6636
|
|
23
|
+
uipath/core/tracing/decorators.py,sha256=ag_MFwZ0TywrhbpLKqQwF1guvRA9sYiItxao5LN9_Iw,10942
|
|
24
|
+
uipath/core/tracing/exporters.py,sha256=FClouEEQfk3F8J7G_NFoarDJM3R0-gA5jUxA5xRHx5s,1562
|
|
25
|
+
uipath/core/tracing/processors.py,sha256=R_652rtjPmfpUtaXoIcmfZrRZylVXFRNwjOmJUUxOQw,1408
|
|
26
|
+
uipath/core/tracing/span_utils.py,sha256=WYBrd6ZbawAs7r1Js-Zvo9_8GzkD9LhHNOls00bK_xI,12235
|
|
27
|
+
uipath/core/tracing/trace_manager.py,sha256=51rscJcepkTK4bWoCZdE-DFc9wt2F-aSuFBaSXmkHl0,3130
|
|
28
|
+
uipath_core-0.1.2.dist-info/METADATA,sha256=rRT3V6Rlvi1KVNC128ie__-tZ1ymkt1ZFZbbGGJ07m4,938
|
|
29
|
+
uipath_core-0.1.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
30
|
+
uipath_core-0.1.2.dist-info/licenses/LICENSE,sha256=-KBavWXepyDjimmzH5fVAsi-6jNVpIKFc2kZs0Ri4ng,1058
|
|
31
|
+
uipath_core-0.1.2.dist-info/RECORD,,
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
uipath/core/__init__.py,sha256=KaOxKswriK_zGul3KzILhqnR3G0E5otuXny0IuuwTqA,280
|
|
2
|
-
uipath/core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
uipath/core/chat/__init__.py,sha256=rjJd-tzN0U1_ATHqneW1rMQv_ye3O_CngSY6h7COYaY,5379
|
|
4
|
-
uipath/core/chat/async_stream.py,sha256=5KbqDh1NTuKyWilFwq-5ueJ1Mle0rNveLlAoIKJIvUo,1987
|
|
5
|
-
uipath/core/chat/citation.py,sha256=qxJTl6wmp0wr49Ug4u2TLKkN582c1MpsDcirwD3JXGo,2422
|
|
6
|
-
uipath/core/chat/content.py,sha256=-Con3JhrFR9mxgZerNVe2DYjFYjQY5zknyJvfsyeJrc,2853
|
|
7
|
-
uipath/core/chat/conversation.py,sha256=CVCj4z0DYQsodciCpmiefMjvn8NmJsLTaZU49QiseUw,1723
|
|
8
|
-
uipath/core/chat/event.py,sha256=mXE6cQzqHU27UW_PgXqpjH4hNRGeOlvXxZtF64fmjDA,2854
|
|
9
|
-
uipath/core/chat/exchange.py,sha256=wwkTO09S5-QhyzAAgWArhJ_1KXI8EMdZTEasnXUH-lI,2298
|
|
10
|
-
uipath/core/chat/message.py,sha256=ofTahX97xOzoUFU1ig3S2AKkHwS7xXqzi66N1m93NlQ,2141
|
|
11
|
-
uipath/core/chat/meta.py,sha256=3t0eS9UHoAPHre97QTUeVbjDhnMX4zj4-qG6ju0B8wY,315
|
|
12
|
-
uipath/core/chat/tool.py,sha256=uXeP6psjwNGqt5ZLEQHF83LH8ApvjSCwI5ouoDo0Jk0,2180
|
|
13
|
-
uipath/core/errors/__init__.py,sha256=gjxdLibZ0fjwgzPuLJY04P8dIX9rbSM2wQ97jP34ucE,278
|
|
14
|
-
uipath/core/errors/errors.py,sha256=5LajjuTfNW82ju07wT5mD3tXk0S-Ju7OqJqQpPN0F6g,486
|
|
15
|
-
uipath/core/tracing/__init__.py,sha256=1XNLYZ4J76XkRrizGO486mS6yxzVXUbrldpvxTyJe3E,483
|
|
16
|
-
uipath/core/tracing/_utils.py,sha256=FiCFGOFa4czruhlSF87Q5Q4jX9KKPHZiw8k14K7W5v4,6636
|
|
17
|
-
uipath/core/tracing/decorators.py,sha256=ag_MFwZ0TywrhbpLKqQwF1guvRA9sYiItxao5LN9_Iw,10942
|
|
18
|
-
uipath/core/tracing/exporters.py,sha256=FClouEEQfk3F8J7G_NFoarDJM3R0-gA5jUxA5xRHx5s,1562
|
|
19
|
-
uipath/core/tracing/processors.py,sha256=R_652rtjPmfpUtaXoIcmfZrRZylVXFRNwjOmJUUxOQw,1408
|
|
20
|
-
uipath/core/tracing/span_utils.py,sha256=WYBrd6ZbawAs7r1Js-Zvo9_8GzkD9LhHNOls00bK_xI,12235
|
|
21
|
-
uipath/core/tracing/trace_manager.py,sha256=51rscJcepkTK4bWoCZdE-DFc9wt2F-aSuFBaSXmkHl0,3130
|
|
22
|
-
uipath_core-0.1.0.dist-info/METADATA,sha256=sgs7Gqbu5d1UWPhfg8n5vQjbM5RFB43J2rYfSViy3QI,938
|
|
23
|
-
uipath_core-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
24
|
-
uipath_core-0.1.0.dist-info/licenses/LICENSE,sha256=-KBavWXepyDjimmzH5fVAsi-6jNVpIKFc2kZs0Ri4ng,1058
|
|
25
|
-
uipath_core-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|