soprano-sdk 0.2.16__py3-none-any.whl → 0.2.18__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.
- soprano_sdk/agents/adaptor.py +56 -10
- soprano_sdk/authenticators/mfa.py +42 -39
- soprano_sdk/validation/schema.py +1 -1
- soprano_sdk/validation/validator.py +0 -4
- {soprano_sdk-0.2.16.dist-info → soprano_sdk-0.2.18.dist-info}/METADATA +1 -1
- {soprano_sdk-0.2.16.dist-info → soprano_sdk-0.2.18.dist-info}/RECORD +8 -8
- {soprano_sdk-0.2.16.dist-info → soprano_sdk-0.2.18.dist-info}/WHEEL +0 -0
- {soprano_sdk-0.2.16.dist-info → soprano_sdk-0.2.18.dist-info}/licenses/LICENSE +0 -0
soprano_sdk/agents/adaptor.py
CHANGED
|
@@ -6,6 +6,9 @@ from pydantic import BaseModel
|
|
|
6
6
|
from pydantic_ai.agent import Agent as PydanticAIAgent
|
|
7
7
|
from crewai.agent import Agent as CrewAIAgent
|
|
8
8
|
from ..utils.logger import logger
|
|
9
|
+
import json
|
|
10
|
+
import ast
|
|
11
|
+
|
|
9
12
|
|
|
10
13
|
class AgentAdapter(ABC):
|
|
11
14
|
|
|
@@ -37,26 +40,69 @@ class LangGraphAgentAdapter(AgentAdapter):
|
|
|
37
40
|
|
|
38
41
|
|
|
39
42
|
class CrewAIAgentAdapter(AgentAdapter):
|
|
40
|
-
|
|
43
|
+
|
|
41
44
|
def __init__(self, agent: CrewAIAgent, output_schema: BaseModel):
|
|
42
45
|
self.agent = agent
|
|
43
|
-
self.output_schema=output_schema
|
|
44
|
-
|
|
46
|
+
self.output_schema = output_schema
|
|
47
|
+
|
|
48
|
+
def _convert_to_dict(self, response: Any) -> Dict[str, Any]:
|
|
49
|
+
"""
|
|
50
|
+
Convert response to dict using 3 strategies:
|
|
51
|
+
1. Check if already a dict
|
|
52
|
+
2. Try json.loads()
|
|
53
|
+
3. Try ast.literal_eval()
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
# Strategy 1: Already a dict
|
|
57
|
+
if isinstance(response, dict):
|
|
58
|
+
logger.info("Response is already a dict")
|
|
59
|
+
return response
|
|
60
|
+
|
|
61
|
+
# Convert to string for parsing
|
|
62
|
+
response_str = str(response)
|
|
63
|
+
|
|
64
|
+
# Strategy 2: Try json.loads()
|
|
65
|
+
try:
|
|
66
|
+
parsed = json.loads(response_str)
|
|
67
|
+
logger.info("Successfully parsed with json.loads()")
|
|
68
|
+
return parsed
|
|
69
|
+
except (json.JSONDecodeError, ValueError, TypeError) as e:
|
|
70
|
+
logger.error(f"json.loads() failed: {e}")
|
|
71
|
+
|
|
72
|
+
# Strategy 3: Try ast.literal_eval()
|
|
73
|
+
try:
|
|
74
|
+
parsed = ast.literal_eval(response_str)
|
|
75
|
+
if isinstance(parsed, dict):
|
|
76
|
+
logger.info("Successfully parsed with ast.literal_eval()")
|
|
77
|
+
return parsed
|
|
78
|
+
except (ValueError, SyntaxError, TypeError) as e:
|
|
79
|
+
logger.error(f"ast.literal_eval() failed: {e}")
|
|
80
|
+
|
|
81
|
+
# No schema and all parsing failed - return as string
|
|
82
|
+
logger.error("No schema provided and parsing failed, returning raw response")
|
|
83
|
+
return response_str
|
|
84
|
+
|
|
45
85
|
def invoke(self, messages: List[Dict[str, str]]) -> Any:
|
|
46
86
|
try:
|
|
47
87
|
logger.info("Invoking CrewAIAgentAdapter agent with messages")
|
|
48
88
|
result = self.agent.kickoff(messages, response_format=self.output_schema)
|
|
49
89
|
|
|
50
|
-
if structured_response := getattr(result, 'pydantic', None)
|
|
90
|
+
if structured_response := getattr(result, 'pydantic', None):
|
|
91
|
+
logger.info("Got pydantic structured response")
|
|
51
92
|
return structured_response.model_dump()
|
|
52
93
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
94
|
+
agent_response = getattr(result, 'raw', None)
|
|
95
|
+
if agent_response is None:
|
|
96
|
+
agent_response = str(result)
|
|
97
|
+
|
|
98
|
+
logger.info(f"Processing raw response type: {type(agent_response)}")
|
|
99
|
+
|
|
100
|
+
if not self.output_schema:
|
|
101
|
+
logger.info("No output schema provided, returning raw response")
|
|
57
102
|
return agent_response
|
|
58
|
-
|
|
59
|
-
return
|
|
103
|
+
|
|
104
|
+
return self._convert_to_dict(agent_response)
|
|
105
|
+
|
|
60
106
|
except Exception as e:
|
|
61
107
|
raise RuntimeError(f"CrewAI agent invocation failed: {e}")
|
|
62
108
|
|
|
@@ -137,7 +137,47 @@ class MFANodeConfig:
|
|
|
137
137
|
|
|
138
138
|
@classmethod
|
|
139
139
|
def get_validate_user_input(cls, source_node: str, next_node: str, mfa_config: dict):
|
|
140
|
-
|
|
140
|
+
agent_config = dict(
|
|
141
|
+
name="MFA Input Data Collector",
|
|
142
|
+
initial_message="{{_mfa.message}}",
|
|
143
|
+
instructions="""
|
|
144
|
+
You are an authentication value extractor. Your job is to identify and extract MFA codes from user input, or detect if the user wants to cancel the authentication flow.
|
|
145
|
+
|
|
146
|
+
**Task:**
|
|
147
|
+
- Read the user's message carefully
|
|
148
|
+
- First, check if the user wants to cancel, stop, or exit the authentication process
|
|
149
|
+
- If they want to cancel, output: MFA_CANCELLED:
|
|
150
|
+
- Otherwise, extract ONLY the OTP/MFA code value and output in the format shown below
|
|
151
|
+
|
|
152
|
+
**Cancellation Detection:**
|
|
153
|
+
If the user expresses any intent to cancel, stop, exit, abort, or quit the authentication process, respond with: MFA_CANCELLED
|
|
154
|
+
|
|
155
|
+
Examples of cancellation phrases:
|
|
156
|
+
* "cancel" → MFA_CANCELLED:
|
|
157
|
+
* "I want to stop" → MFA_CANCELLED:
|
|
158
|
+
* "exit" → MFA_CANCELLED:
|
|
159
|
+
* "nevermind" → MFA_CANCELLED:
|
|
160
|
+
* "I don't want to continue" → MFA_CANCELLED:
|
|
161
|
+
* "stop this" → MFA_CANCELLED:
|
|
162
|
+
* "forget it" → MFA_CANCELLED:
|
|
163
|
+
* "abort" → MFA_CANCELLED:
|
|
164
|
+
* "quit" → MFA_CANCELLED:
|
|
165
|
+
|
|
166
|
+
**OTP Capture Examples:**
|
|
167
|
+
* "1234" → MFA_CAPTURED:1234
|
|
168
|
+
* "2345e" → MFA_CAPTURED:2345e
|
|
169
|
+
* "the code is 567890" → MFA_CAPTURED:567890
|
|
170
|
+
* "my otp is 123456" → MFA_CAPTURED:123456
|
|
171
|
+
|
|
172
|
+
**Output Format:**
|
|
173
|
+
- For OTP/MFA codes: MFA_CAPTURED:<otp_value>
|
|
174
|
+
- For cancellation: MFA_CANCELLED:
|
|
175
|
+
|
|
176
|
+
""")
|
|
177
|
+
|
|
178
|
+
if mfa_model := mfa_config.get('model'):
|
|
179
|
+
agent_config.update(model=mfa_model)
|
|
180
|
+
|
|
141
181
|
max_attempts = mfa_config.get('max_attempts', 3)
|
|
142
182
|
on_max_attempts_reached = mfa_config.get('on_max_attempts_reached')
|
|
143
183
|
|
|
@@ -149,44 +189,7 @@ class MFANodeConfig:
|
|
|
149
189
|
field=input_field_name,
|
|
150
190
|
max_attempts=max_attempts,
|
|
151
191
|
validator="soprano_sdk.authenticators.mfa.mfa_validate_user_input",
|
|
152
|
-
agent=
|
|
153
|
-
name="MFA Input Data Collector",
|
|
154
|
-
model=model_name,
|
|
155
|
-
initial_message="{{_mfa.message}}",
|
|
156
|
-
instructions="""
|
|
157
|
-
You are an authentication value extractor. Your job is to identify and extract MFA codes from user input, or detect if the user wants to cancel the authentication flow.
|
|
158
|
-
|
|
159
|
-
**Task:**
|
|
160
|
-
- Read the user's message carefully
|
|
161
|
-
- First, check if the user wants to cancel, stop, or exit the authentication process
|
|
162
|
-
- If they want to cancel, output: MFA_CANCELLED:
|
|
163
|
-
- Otherwise, extract ONLY the OTP/MFA code value and output in the format shown below
|
|
164
|
-
|
|
165
|
-
**Cancellation Detection:**
|
|
166
|
-
If the user expresses any intent to cancel, stop, exit, abort, or quit the authentication process, respond with: MFA_CANCELLED
|
|
167
|
-
|
|
168
|
-
Examples of cancellation phrases:
|
|
169
|
-
* "cancel" → MFA_CANCELLED:
|
|
170
|
-
* "I want to stop" → MFA_CANCELLED:
|
|
171
|
-
* "exit" → MFA_CANCELLED:
|
|
172
|
-
* "nevermind" → MFA_CANCELLED:
|
|
173
|
-
* "I don't want to continue" → MFA_CANCELLED:
|
|
174
|
-
* "stop this" → MFA_CANCELLED:
|
|
175
|
-
* "forget it" → MFA_CANCELLED:
|
|
176
|
-
* "abort" → MFA_CANCELLED:
|
|
177
|
-
* "quit" → MFA_CANCELLED:
|
|
178
|
-
|
|
179
|
-
**OTP Capture Examples:**
|
|
180
|
-
* "1234" → MFA_CAPTURED:1234
|
|
181
|
-
* "2345e" → MFA_CAPTURED:2345e
|
|
182
|
-
* "the code is 567890" → MFA_CAPTURED:567890
|
|
183
|
-
* "my otp is 123456" → MFA_CAPTURED:123456
|
|
184
|
-
|
|
185
|
-
**Output Format:**
|
|
186
|
-
- For OTP/MFA codes: MFA_CAPTURED:<otp_value>
|
|
187
|
-
- For cancellation: MFA_CANCELLED:
|
|
188
|
-
|
|
189
|
-
"""),
|
|
192
|
+
agent=agent_config,
|
|
190
193
|
transitions=[
|
|
191
194
|
dict(
|
|
192
195
|
pattern="MFA_CAPTURED:",
|
soprano_sdk/validation/schema.py
CHANGED
|
@@ -234,7 +234,7 @@ WORKFLOW_SCHEMA = {
|
|
|
234
234
|
"mfa": {
|
|
235
235
|
"type": "object",
|
|
236
236
|
"description": "Multi-factor authentication configuration",
|
|
237
|
-
"required": ["type", "
|
|
237
|
+
"required": ["type", "payload"],
|
|
238
238
|
"properties": {
|
|
239
239
|
"model": {
|
|
240
240
|
"type": "string",
|
|
@@ -122,10 +122,6 @@ class WorkflowValidator:
|
|
|
122
122
|
f"MFA is enabled in step({step['id']}). MFA is supported only for `call_function` nodes"
|
|
123
123
|
)
|
|
124
124
|
|
|
125
|
-
model = mfa_authorizer.get('model')
|
|
126
|
-
if not model:
|
|
127
|
-
self.errors.append(f"step({step['id']}) -> mfa -> model is missing")
|
|
128
|
-
|
|
129
125
|
mfa_type = mfa_authorizer.get('type')
|
|
130
126
|
if mfa_type and mfa_type != 'REST':
|
|
131
127
|
self.errors.append(f"step({step['id']}) -> mfa -> type '{mfa_type}' is unsupported. Only 'REST' is supported.")
|
|
@@ -2,11 +2,11 @@ soprano_sdk/__init__.py,sha256=YZVl_SwQ0C-E_5_f1AwUe_hPcbgCt8k7k4_WAHM8vjE,243
|
|
|
2
2
|
soprano_sdk/engine.py,sha256=EFK91iTHjp72otLN6Kg-yeLye2J3CAKN0QH4FI2taL8,14838
|
|
3
3
|
soprano_sdk/tools.py,sha256=dmJ0OZ7Bj3rvjBQvLzgWlYRFVtNJOyMO2jLqaS13cAc,10971
|
|
4
4
|
soprano_sdk/agents/__init__.py,sha256=Yzbtv6iP_ABRgZo0IUjy9vDofEvLFbOjuABw758176A,636
|
|
5
|
-
soprano_sdk/agents/adaptor.py,sha256=
|
|
5
|
+
soprano_sdk/agents/adaptor.py,sha256=xJ3MBTU91fQxA9O7V5Xds6m8-n_NxkF0YiKGaWKLmrQ,4959
|
|
6
6
|
soprano_sdk/agents/factory.py,sha256=CvXhpvtjf_Hb4Ce8WKAa0FzyY5Jm6lssvIIfSXu7jPY,9788
|
|
7
7
|
soprano_sdk/agents/structured_output.py,sha256=7DSVzfMPsZAqBwI3v6XL15qG5Gh4jJ-qddcVPaa3gdc,3326
|
|
8
8
|
soprano_sdk/authenticators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
soprano_sdk/authenticators/mfa.py,sha256=
|
|
9
|
+
soprano_sdk/authenticators/mfa.py,sha256=xnur1lxbx4r_pUaNPD8vtQiNccQTjbPnqgi_vqdRZaQ,7336
|
|
10
10
|
soprano_sdk/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
soprano_sdk/core/constants.py,sha256=UPXlRbF7gsOUNOV0Lm0jvgFfgZX7JrsV6n9I5csMfns,3508
|
|
12
12
|
soprano_sdk/core/engine.py,sha256=HKYoqwDm541pWSWwEKHxLlL3PX90Ux_5l_-HqihgL-g,12245
|
|
@@ -27,9 +27,9 @@ soprano_sdk/utils/template.py,sha256=MG_B9TMx1ShpnSGo7s7TO-VfQzuFByuRNhJTvZ668kM
|
|
|
27
27
|
soprano_sdk/utils/tool.py,sha256=hWN826HIKmLdswLCTURLH8hWlb2WU0MB8nIUErbpB-8,1877
|
|
28
28
|
soprano_sdk/utils/tracing.py,sha256=gSHeBDLe-MbAZ9rkzpCoGFveeMdR9KLaA6tteB0IWjk,1991
|
|
29
29
|
soprano_sdk/validation/__init__.py,sha256=ImChmO86jYHU90xzTttto2-LmOUOmvY_ibOQaLRz5BA,262
|
|
30
|
-
soprano_sdk/validation/schema.py,sha256=
|
|
31
|
-
soprano_sdk/validation/validator.py,sha256=
|
|
32
|
-
soprano_sdk-0.2.
|
|
33
|
-
soprano_sdk-0.2.
|
|
34
|
-
soprano_sdk-0.2.
|
|
35
|
-
soprano_sdk-0.2.
|
|
30
|
+
soprano_sdk/validation/schema.py,sha256=eRGXXMDlJyaH0XYMOXY1azvJlJwOdbHFrHjB1yuGROg,15434
|
|
31
|
+
soprano_sdk/validation/validator.py,sha256=f-e2MMRL70asOIXr_0Fsd5CgGKVRiQp7AaYsHA45Km0,8792
|
|
32
|
+
soprano_sdk-0.2.18.dist-info/METADATA,sha256=hr2us3VV7sfP7P74YQxTmViJ1L8jkmCLtYeOGDyvvhE,11374
|
|
33
|
+
soprano_sdk-0.2.18.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
34
|
+
soprano_sdk-0.2.18.dist-info/licenses/LICENSE,sha256=A1aBauSjPNtVehOXJe3WuvdU2xvM9H8XmigFMm6665s,1073
|
|
35
|
+
soprano_sdk-0.2.18.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|