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.
@@ -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
- if agent_response := getattr(result, 'raw', None) :
54
- return agent_response
55
-
56
- if isinstance(agent_response, dict):
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 str(result)
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
- model_name = mfa_config['model']
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=dict(
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:",
@@ -234,7 +234,7 @@ WORKFLOW_SCHEMA = {
234
234
  "mfa": {
235
235
  "type": "object",
236
236
  "description": "Multi-factor authentication configuration",
237
- "required": ["type", "model", "payload"],
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.")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soprano-sdk
3
- Version: 0.2.16
3
+ Version: 0.2.18
4
4
  Summary: YAML-driven workflow engine with AI agent integration for building conversational SOPs
5
5
  Author: Arvind Thangamani
6
6
  License: MIT
@@ -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=0EjLf4sVWboLclLwsm_EbKS0WPeAAd1U8J6YuFB7wFA,3342
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=Vew9Nb8pIRTw9hKbEZTH3YScY-fZ_TLq4ZuCzc-wbr8,7387
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=SlC4sq-ueEg0p_8Uox_cgPj9S-0AEEiOOlA1Vsu0DsE,15443
31
- soprano_sdk/validation/validator.py,sha256=GaCvHvjwVe88Z8yatQsueiPnqtq1oo5uN75gogzpQT0,8940
32
- soprano_sdk-0.2.16.dist-info/METADATA,sha256=khucbkyEa4EdkicgGz7xBJMQn6lrxCgSjZOv0fd-EPA,11374
33
- soprano_sdk-0.2.16.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
34
- soprano_sdk-0.2.16.dist-info/licenses/LICENSE,sha256=A1aBauSjPNtVehOXJe3WuvdU2xvM9H8XmigFMm6665s,1073
35
- soprano_sdk-0.2.16.dist-info/RECORD,,
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,,