soprano-sdk 0.2.2__py3-none-any.whl → 0.2.4__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/__init__.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from .core.engine import WorkflowEngine, load_workflow
2
+ from .core.constants import MFAConfig
2
3
  from .tools import WorkflowTool
3
4
 
4
5
  __version__ = "0.1.0"
@@ -6,5 +7,6 @@ __version__ = "0.1.0"
6
7
  __all__ = [
7
8
  "WorkflowEngine",
8
9
  "load_workflow",
10
+ "MFAConfig",
9
11
  "WorkflowTool",
10
12
  ]
@@ -1,6 +1,6 @@
1
1
  import requests
2
- from typing import TypedDict, Literal, NotRequired
3
- from soprano_sdk.core.constants import MFARestAuthorizerEnv
2
+ from typing import TypedDict, Literal, NotRequired, Optional
3
+ from soprano_sdk.core.constants import MFAConfig
4
4
 
5
5
 
6
6
  class MFAChallenge(TypedDict):
@@ -28,15 +28,21 @@ def build_path(base_url: str, path: str):
28
28
  return f"{base_url.rstrip('/')}/{path.lstrip('/')}"
29
29
 
30
30
 
31
- def enforce_mfa_if_required(state: dict):
31
+ def enforce_mfa_if_required(state: dict, mfa_config: Optional[MFAConfig] = None):
32
+ if mfa_config is None:
33
+ mfa_config = state.get('_mfa_config') or MFAConfig()
34
+
32
35
  _mfa : MFAState = state['_mfa']
33
36
  if _mfa['status'] == 'COMPLETED':
34
37
  return True
35
38
  generate_token_response = requests.post(
36
39
  build_path(
37
- base_url=MFARestAuthorizerEnv.GENERATE_TOKEN_BASE_URL.get_from_env(),
38
- path=MFARestAuthorizerEnv.GENERATE_TOKEN_PATH.get_from_env()
39
- ), json=_mfa['post_payload'], timeout=30, headers={"Authorization": f"Bearer {state['bearer_token']}"}
40
+ base_url=mfa_config.generate_token_base_url,
41
+ path=mfa_config.generate_token_path
42
+ ),
43
+ json=_mfa['post_payload'],
44
+ timeout=mfa_config.api_timeout,
45
+ headers={"Authorization": f"Bearer {state['bearer_token']}"}
40
46
  )
41
47
  _, error = get_response(generate_token_response)
42
48
 
@@ -50,7 +56,10 @@ def enforce_mfa_if_required(state: dict):
50
56
  return False
51
57
 
52
58
 
53
- def mfa_validate_user_input(**state: dict):
59
+ def mfa_validate_user_input(mfa_config: Optional[MFAConfig] = None, **state: dict):
60
+ if mfa_config is None:
61
+ mfa_config = state.get('_mfa_config') or MFAConfig()
62
+
54
63
  _mfa : MFAState = state['_mfa']
55
64
  input_field_name = state['_active_input_field']
56
65
  if not state[input_field_name]:
@@ -61,9 +70,12 @@ def mfa_validate_user_input(**state: dict):
61
70
  post_payload.update({challenge_field_name: {"value": state[input_field_name]}})
62
71
  validate_token_response = requests.post(
63
72
  build_path(
64
- base_url=MFARestAuthorizerEnv.VALIDATE_TOKEN_BASE_URL.get_from_env(),
65
- path=MFARestAuthorizerEnv.VALIDATE_TOKEN_PATH.get_from_env()
66
- ), json=post_payload, timeout=30, headers={"Authorization": f"Bearer {state['bearer_token']}"}
73
+ base_url=mfa_config.validate_token_base_url,
74
+ path=mfa_config.validate_token_path
75
+ ),
76
+ json=post_payload,
77
+ timeout=mfa_config.api_timeout,
78
+ headers={"Authorization": f"Bearer {state['bearer_token']}"}
67
79
  )
68
80
  _mfa['retry_count'] += 1
69
81
  response, error = get_response(validate_token_response)
@@ -78,11 +90,11 @@ def mfa_validate_user_input(**state: dict):
78
90
 
79
91
  authorize = requests.post(
80
92
  build_path(
81
- base_url=MFARestAuthorizerEnv.AUTHORIZE_TOKEN_BASE_URL.get_from_env(),
82
- path=MFARestAuthorizerEnv.AUTHORIZE_TOKEN_PATH.get_from_env()
93
+ base_url=mfa_config.authorize_token_base_url,
94
+ path=mfa_config.authorize_token_path
83
95
  ),
84
96
  json=post_payload,
85
- timeout=30,
97
+ timeout=mfa_config.api_timeout,
86
98
  headers={"Authorization": f"Bearer {state['bearer_token']}"}
87
99
  )
88
100
  if authorize.status_code == 204:
@@ -116,14 +128,18 @@ class MFANodeConfig:
116
128
  )
117
129
 
118
130
  @classmethod
119
- def get_validate_user_input(cls, source_node: str, next_node: str, model_name: str):
131
+ def get_validate_user_input(cls, source_node: str, next_node: str, mfa_config: dict):
132
+ model_name = mfa_config['model']
133
+ max_attempts = mfa_config.get('max_attempts', 3)
134
+ on_max_attempts_reached = mfa_config.get('on_max_attempts_reached')
135
+
120
136
  input_field_name = f"{source_node}_mfa_input"
121
- return dict(
137
+ node_config = dict(
122
138
  id=f"{source_node}_mfa_validate",
123
139
  action="collect_input_with_agent",
124
140
  description="Collect Input for MFA value",
125
141
  field=input_field_name,
126
- max_attempts=3,
142
+ max_attempts=max_attempts,
127
143
  validator="soprano_sdk.authenticators.mfa.mfa_validate_user_input",
128
144
  agent=dict(
129
145
  name="MFA Input Data Collector",
@@ -152,3 +168,8 @@ class MFANodeConfig:
152
168
  )
153
169
  ]
154
170
  )
171
+
172
+ if on_max_attempts_reached:
173
+ node_config['on_max_attempts_reached'] = on_max_attempts_reached
174
+
175
+ return node_config
@@ -1,5 +1,7 @@
1
- import os
2
1
  from enum import Enum
2
+ from typing import Optional
3
+ from pydantic import Field
4
+ from pydantic_settings import BaseSettings, SettingsConfigDict
3
5
 
4
6
 
5
7
  class WorkflowKeys:
@@ -60,17 +62,53 @@ MAX_ATTEMPTS_MESSAGE = "I'm having trouble understanding your {field}. Please co
60
62
  WORKFLOW_COMPLETE_MESSAGE = "Workflow completed."
61
63
 
62
64
 
63
- class MFARestAuthorizerEnv(Enum):
64
- GENERATE_TOKEN_BASE_URL = 'GENERATE_TOKEN_BASE_URL'
65
- GENERATE_TOKEN_PATH = 'GENERATE_TOKEN_PATH'
66
- VALIDATE_TOKEN_BASE_URL = 'VALIDATE_TOKEN_BASE_URL'
67
- VALIDATE_TOKEN_PATH = 'VALIDATE_TOKEN_PATH'
68
- AUTHORIZE_TOKEN_BASE_URL = 'AUTHORIZE_TOKEN_BASE_URL'
69
- AUTHORIZE_TOKEN_PATH = 'AUTHORIZE_TOKEN_PATH'
70
-
71
- API_TIMEOUT = 'API_TIMEOUT'
72
-
73
- def get_from_env(self):
74
- if self == MFARestAuthorizerEnv.API_TIMEOUT:
75
- return int(os.getenv(self.value, '30'))
76
- return os.getenv(self.value)
65
+ class MFAConfig(BaseSettings):
66
+ """
67
+ Configuration for MFA REST API endpoints.
68
+
69
+ Values can be provided during initialization or will be automatically
70
+ loaded from environment variables with the same name (uppercase).
71
+
72
+ Example:
73
+ # Load from environment variables
74
+ config = MFAConfig()
75
+
76
+ # Or provide specific values
77
+ config = MFAConfig(
78
+ generate_token_base_url="https://api.example.com",
79
+ generate_token_path="/v1/mfa/generate"
80
+ )
81
+ """
82
+ generate_token_base_url: Optional[str] = Field(
83
+ default=None,
84
+ description="Base URL for the generate token endpoint"
85
+ )
86
+ generate_token_path: Optional[str] = Field(
87
+ default=None,
88
+ description="Path for the generate token endpoint"
89
+ )
90
+ validate_token_base_url: Optional[str] = Field(
91
+ default=None,
92
+ description="Base URL for the validate token endpoint"
93
+ )
94
+ validate_token_path: Optional[str] = Field(
95
+ default=None,
96
+ description="Path for the validate token endpoint"
97
+ )
98
+ authorize_token_base_url: Optional[str] = Field(
99
+ default=None,
100
+ description="Base URL for the authorize token endpoint"
101
+ )
102
+ authorize_token_path: Optional[str] = Field(
103
+ default=None,
104
+ description="Path for the authorize token endpoint"
105
+ )
106
+ api_timeout: int = Field(
107
+ default=30,
108
+ description="API request timeout in seconds"
109
+ )
110
+
111
+ model_config = SettingsConfigDict(
112
+ case_sensitive=False,
113
+ extra='ignore'
114
+ )
@@ -7,7 +7,7 @@ from langgraph.constants import START
7
7
  from langgraph.graph import StateGraph
8
8
  from langgraph.graph.state import CompiledStateGraph
9
9
 
10
- from .constants import WorkflowKeys
10
+ from .constants import WorkflowKeys, MFAConfig
11
11
  from .state import create_state_model
12
12
  from ..nodes.factory import NodeFactory
13
13
  from ..routing.router import WorkflowRouter
@@ -19,7 +19,7 @@ from soprano_sdk.authenticators.mfa import MFANodeConfig
19
19
 
20
20
  class WorkflowEngine:
21
21
 
22
- def __init__(self, yaml_path: str, configs: dict):
22
+ def __init__(self, yaml_path: str, configs: dict, mfa_config: Optional[MFAConfig] = None):
23
23
  self.yaml_path = yaml_path
24
24
  self.configs = configs or {}
25
25
  logger.info(f"Loading workflow from: {yaml_path}")
@@ -29,7 +29,7 @@ class WorkflowEngine:
29
29
  self.config = yaml.safe_load(f)
30
30
 
31
31
  logger.info("Validating workflow configuration")
32
- validate_workflow(self.config)
32
+ validate_workflow(self.config, mfa_config=mfa_config or MFAConfig())
33
33
 
34
34
  self.workflow_name = self.config['name']
35
35
  self.workflow_description = self.config['description']
@@ -37,6 +37,7 @@ class WorkflowEngine:
37
37
  self.mfa_validator_steps: set[str] = set()
38
38
  self.steps: list = self.load_steps()
39
39
  self.step_map = {step['id']: step for step in self.steps}
40
+ self.mfa_config = (mfa_config or MFAConfig()) if self.mfa_validator_steps else None
40
41
  self.data_fields = self.load_data()
41
42
 
42
43
  self.outcomes = self.config['outcomes']
@@ -208,8 +209,9 @@ class WorkflowEngine:
208
209
 
209
210
  if mfa_config := step.get('mfa'):
210
211
  mfa_data_collector = MFANodeConfig.get_validate_user_input(
211
- next_node=step_id, model_name=mfa_config['model'],
212
- source_node=step_id
212
+ next_node=step_id,
213
+ source_node=step_id,
214
+ mfa_config=mfa_config
213
215
  )
214
216
  mfa_start = MFANodeConfig.get_call_function_template(
215
217
  source_node=step_id,
@@ -259,7 +261,7 @@ class WorkflowEngine:
259
261
  return data
260
262
 
261
263
 
262
- def load_workflow(yaml_path: str, checkpointer=None, config=None) -> Tuple[CompiledStateGraph, WorkflowEngine]:
264
+ def load_workflow(yaml_path: str, checkpointer=None, config=None, mfa_config: Optional[MFAConfig] = None) -> Tuple[CompiledStateGraph, WorkflowEngine]:
263
265
  """
264
266
  Load a workflow from YAML configuration.
265
267
 
@@ -270,6 +272,8 @@ def load_workflow(yaml_path: str, checkpointer=None, config=None) -> Tuple[Compi
270
272
  checkpointer: Optional checkpointer for state persistence.
271
273
  Defaults to InMemorySaver() if not provided.
272
274
  Example: MongoDBSaver for production persistence.
275
+ config: Optional configuration dictionary
276
+ mfa_config: Optional MFA configuration. If not provided, will load from environment variables.
273
277
 
274
278
  Returns:
275
279
  Tuple of (compiled_graph, engine) where:
@@ -278,11 +282,21 @@ def load_workflow(yaml_path: str, checkpointer=None, config=None) -> Tuple[Compi
278
282
 
279
283
  Example:
280
284
  ```python
285
+ # Load with environment variables
281
286
  graph, engine = load_workflow("workflow.yaml")
287
+
288
+ # Or provide MFA configuration explicitly
289
+ from soprano_sdk.core.constants import MFAConfig
290
+ mfa_config = MFAConfig(
291
+ generate_token_base_url="https://api.example.com",
292
+ generate_token_path="/v1/mfa/generate"
293
+ )
294
+ graph, engine = load_workflow("workflow.yaml", mfa_config=mfa_config)
295
+
282
296
  result = graph.invoke({}, config={"configurable": {"thread_id": "123"}})
283
297
  message = engine.get_outcome_message(result)
284
298
  ```
285
299
  """
286
- engine = WorkflowEngine(yaml_path, configs=config)
300
+ engine = WorkflowEngine(yaml_path, configs=config, mfa_config=mfa_config)
287
301
  graph = engine.build_graph(checkpointer=checkpointer)
288
302
  return graph, engine
soprano_sdk/core/state.py CHANGED
@@ -48,6 +48,7 @@ def create_state_model(data_fields: List[dict]):
48
48
  fields['_computed_fields'] = Annotated[List[str], replace]
49
49
  fields['error'] = Annotated[Optional[Dict[str, str]], replace]
50
50
  fields['_mfa'] = Annotated[Optional[Dict[str, str]], replace]
51
+ fields['_mfa_config'] = Annotated[Optional[Any], replace]
51
52
  fields['mfa_input'] = Annotated[Optional[Dict[str, str]], replace]
52
53
 
53
54
  return types.new_class('WorkflowState', (TypedDict,), {}, lambda ns: ns.update({'__annotations__': fields}))
@@ -39,6 +39,7 @@ class CallFunctionStrategy(ActionStrategy):
39
39
  if 'mfa' in self.step_config:
40
40
  state['_mfa'] = state.get('_mfa', {})
41
41
  state['_mfa']['post_payload'] = dict(transactionId=str(uuid.uuid4()))
42
+ state['_mfa_config'] = self.engine_context.mfa_config
42
43
  template_loader = self.engine_context.get_config_value("template_loader", Environment())
43
44
  for k, v in self.step_config['mfa']['payload'].items():
44
45
  state['_mfa']['post_payload'][k] = compile_values(template_loader, state, v)
@@ -92,6 +92,7 @@ class CollectInputStrategy(ActionStrategy):
92
92
  self.field = step_config.get('field')
93
93
  self.agent_config = step_config.get('agent', {})
94
94
  self.max_attempts = step_config.get('retry_limit') or engine_context.get_config_value("max_retry_limit", DEFAULT_MAX_ATTEMPTS)
95
+ self.on_max_attempts_reached = step_config.get('on_max_attempts_reached')
95
96
  self.transitions = self._get_transitions()
96
97
  self.next_step = self.step_config.get("next", None)
97
98
  self.is_structured_output = self.agent_config.get("structured_output", {}).get("enabled", False)
@@ -116,6 +117,9 @@ class CollectInputStrategy(ActionStrategy):
116
117
 
117
118
  def pre_execute(self, state: Dict[str, Any]) -> Dict[str, Any]:
118
119
  state['_active_input_field'] = self.step_config.get('field')
120
+ # Inject MFA config for MFA validator nodes
121
+ if self.step_id in self.engine_context.mfa_validator_steps:
122
+ state['_mfa_config'] = self.engine_context.mfa_config
119
123
 
120
124
  @property
121
125
  def _formatted_field_name(self) -> str:
@@ -269,7 +273,10 @@ class CollectInputStrategy(ActionStrategy):
269
273
  def _handle_max_attempts(self, state: Dict[str, Any]) -> Dict[str, Any]:
270
274
  logger.warning(f"Max attempts reached for field '{self.field}'")
271
275
  self._set_status(state, 'max_attempts')
272
- message = MAX_ATTEMPTS_MESSAGE.format(field=self.field)
276
+ if self.on_max_attempts_reached:
277
+ message = self.on_max_attempts_reached
278
+ else:
279
+ message = MAX_ATTEMPTS_MESSAGE.format(field=self.field)
273
280
  state[WorkflowKeys.MESSAGES] = [message]
274
281
  return state
275
282
 
soprano_sdk/tools.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Workflow Tools - Wraps workflows as callable tools for agent frameworks
3
3
  """
4
-
4
+ from __future__ import annotations
5
5
  import uuid
6
6
  from typing import Optional, Dict, Any
7
7
  from .utils.logger import logger
@@ -9,6 +9,7 @@ from .utils.logger import logger
9
9
  from langfuse.langchain import CallbackHandler
10
10
 
11
11
  from .core.engine import load_workflow
12
+ from .core.constants import MFAConfig
12
13
 
13
14
 
14
15
  class WorkflowTool:
@@ -25,7 +26,8 @@ class WorkflowTool:
25
26
  name: str,
26
27
  description: str,
27
28
  checkpointer=None,
28
- config: Optional[Dict]=None
29
+ config: Optional[Dict]=None,
30
+ mfa_config: Optional[MFAConfig] = None
29
31
  ):
30
32
  """Initialize workflow tool
31
33
 
@@ -39,9 +41,13 @@ class WorkflowTool:
39
41
  self.name = name
40
42
  self.description = description
41
43
  self.checkpointer = checkpointer
44
+ self.mfa_config = mfa_config
42
45
 
43
46
  # Load workflow
44
- self.graph, self.engine = load_workflow(yaml_path, checkpointer=checkpointer, config=config)
47
+ self.graph, self.engine = load_workflow(
48
+ yaml_path, checkpointer=checkpointer,
49
+ config=config, mfa_config=mfa_config
50
+ )
45
51
 
46
52
  def execute(
47
53
  self,
@@ -90,6 +90,10 @@ WORKFLOW_SCHEMA = {
90
90
  "maximum": 20,
91
91
  "description": "Maximum attempts (for collect_input_with_agent)"
92
92
  },
93
+ "on_max_attempts_reached": {
94
+ "type": "string",
95
+ "description": "Custom error message to display when max attempts are exhausted (for collect_input_with_agent)"
96
+ },
93
97
  "agent": {
94
98
  "type": "object",
95
99
  "description": "Agent configuration (for collect_input_with_agent)",
@@ -245,6 +249,16 @@ WORKFLOW_SCHEMA = {
245
249
  "type": "object",
246
250
  "description": "MFA payload data that is posted to the RESTAPI, Apart from the properties provided transactionId is sent by the framework in the post payload as an additional property, transactionId is the same throughout the MFA process",
247
251
  "additionalProperties": True
252
+ },
253
+ "max_attempts": {
254
+ "type": "integer",
255
+ "minimum": 1,
256
+ "maximum": 20,
257
+ "description": "Maximum number of attempts allowed for MFA validation (default: 3)"
258
+ },
259
+ "on_max_attempts_reached": {
260
+ "type": "string",
261
+ "description": "Custom error message to display when MFA max attempts are exhausted"
248
262
  }
249
263
  }
250
264
  },
@@ -1,9 +1,11 @@
1
- from typing import List, Set
1
+ from typing import List, Set, Optional, TYPE_CHECKING
2
2
 
3
3
  import jsonschema
4
4
 
5
5
  from .schema import WORKFLOW_SCHEMA
6
- from soprano_sdk.core.constants import MFARestAuthorizerEnv
6
+
7
+ if TYPE_CHECKING:
8
+ from soprano_sdk.core.constants import MFAConfig
7
9
 
8
10
 
9
11
  class ValidationResult:
@@ -21,8 +23,9 @@ class ValidationResult:
21
23
 
22
24
 
23
25
  class WorkflowValidator:
24
- def __init__(self, config: dict):
26
+ def __init__(self, config: dict, mfa_config: Optional['MFAConfig'] = None):
25
27
  self.config = config
28
+ self.mfa_config = mfa_config
26
29
  self.errors: List[str] = []
27
30
 
28
31
  def validate(self) -> ValidationResult:
@@ -110,20 +113,10 @@ class WorkflowValidator:
110
113
  )
111
114
 
112
115
  def _validate_authorizer(self, step):
113
-
114
- def _validate_rest_fields():
115
- if mfa_authorizer['type'] == 'REST':
116
- for field in MFARestAuthorizerEnv:
117
- is_present = field.get_from_env()
118
- if not is_present:
119
- self.errors.append(f"`{field.value}` needs to be set as Environment for REST MFA")
120
- else:
121
- self.errors.append(f"step({step['id']}) -> mfa -> type is unsupported")
122
-
123
116
  mfa_authorizer = step.get("mfa", None)
124
117
  if mfa_authorizer is None:
125
118
  return
126
-
119
+
127
120
  if mfa_authorizer and step['action'] != 'call_function':
128
121
  self.errors.append(
129
122
  f"MFA is enabled in step({step['id']}). MFA is supported only for `call_function` nodes"
@@ -133,7 +126,31 @@ class WorkflowValidator:
133
126
  if not model:
134
127
  self.errors.append(f"step({step['id']}) -> mfa -> model is missing")
135
128
 
136
- _validate_rest_fields()
129
+ mfa_type = mfa_authorizer.get('type')
130
+ if mfa_type and mfa_type != 'REST':
131
+ self.errors.append(f"step({step['id']}) -> mfa -> type '{mfa_type}' is unsupported. Only 'REST' is supported.")
132
+
133
+ # Validate mfa_config if provided
134
+ if self.mfa_config is not None:
135
+ missing_fields = []
136
+ if not self.mfa_config.generate_token_base_url:
137
+ missing_fields.append('generate_token_base_url')
138
+ if not self.mfa_config.generate_token_path:
139
+ missing_fields.append('generate_token_path')
140
+ if not self.mfa_config.validate_token_base_url:
141
+ missing_fields.append('validate_token_base_url')
142
+ if not self.mfa_config.validate_token_path:
143
+ missing_fields.append('validate_token_path')
144
+ if not self.mfa_config.authorize_token_base_url:
145
+ missing_fields.append('authorize_token_base_url')
146
+ if not self.mfa_config.authorize_token_path:
147
+ missing_fields.append('authorize_token_path')
148
+
149
+ if missing_fields:
150
+ self.errors.append(
151
+ f"MFA configuration is missing required fields: {', '.join(missing_fields)}. "
152
+ f"Either provide them via mfa_config parameter or set corresponding environment variables."
153
+ )
137
154
 
138
155
 
139
156
  def _validate_data_fields(self):
@@ -200,8 +217,8 @@ class WorkflowValidator:
200
217
  )
201
218
 
202
219
 
203
- def validate_workflow(config: dict) -> ValidationResult:
204
- validator = WorkflowValidator(config)
220
+ def validate_workflow(config: dict, mfa_config: Optional['MFAConfig'] = None) -> ValidationResult:
221
+ validator = WorkflowValidator(config, mfa_config=mfa_config)
205
222
  result = validator.validate()
206
223
 
207
224
  if not result.is_valid:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soprano-sdk
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: YAML-driven workflow engine with AI agent integration for building conversational SOPs
5
5
  Author: Arvind Thangamani
6
6
  License: MIT
@@ -1,21 +1,21 @@
1
- soprano_sdk/__init__.py,sha256=y3c4i7Q7SAPS2Tee7V0TzWdhgMxBWfDJJ98eqD1HxGI,188
1
+ soprano_sdk/__init__.py,sha256=YZVl_SwQ0C-E_5_f1AwUe_hPcbgCt8k7k4_WAHM8vjE,243
2
2
  soprano_sdk/engine.py,sha256=EFK91iTHjp72otLN6Kg-yeLye2J3CAKN0QH4FI2taL8,14838
3
- soprano_sdk/tools.py,sha256=bjJzapnQp31iLDx8jHp_0X2FcS2kY8uqf6hsgD3uDx4,7442
3
+ soprano_sdk/tools.py,sha256=v2QGEaXxKzCBZCTdmhZXMXiVcwccJivdQdCxETkp-lQ,7656
4
4
  soprano_sdk/agents/__init__.py,sha256=Yzbtv6iP_ABRgZo0IUjy9vDofEvLFbOjuABw758176A,636
5
5
  soprano_sdk/agents/adaptor.py,sha256=Cm02YKFclrESu-Qq4CTknCgU7KaA7Z_2FspnQDkEVfU,3214
6
6
  soprano_sdk/agents/factory.py,sha256=Aucfz4rZVKCXMAQtbGAqp1JR8aYwa66mokRmKkKGhYA,6699
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=st71m2R2Ag8l4dEbvARbIXV6kdqACBO9i5DaPK0rlWs,5429
9
+ soprano_sdk/authenticators/mfa.py,sha256=7aTueLu2jLxl213Q5_BkNQ534RgEwf6lodQa_PaJFWs,5980
10
10
  soprano_sdk/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- soprano_sdk/core/constants.py,sha256=-Ij4bbSDsyxQ_NZ5NiusJeepWhoe13WGuzN3LlnXqWY,2017
12
- soprano_sdk/core/engine.py,sha256=BNwmydKlqm64ZIj7VVVqSbn9ebKvOmUvHSr5XZBdQo4,10473
11
+ soprano_sdk/core/constants.py,sha256=F7gL3hkpq5mZgbgiBcEERmzxYJ4zZG2ZFx1YQp5Z580,3130
12
+ soprano_sdk/core/engine.py,sha256=UTFJimrgUzfdDZgc4rW5nvkOKevAEGW8C5Zr1D0tgcs,11270
13
13
  soprano_sdk/core/rollback_strategies.py,sha256=NjDTtBCZlqyDql5PSwI9SMDLK7_BNlTxbW_cq_5gV0g,7783
14
- soprano_sdk/core/state.py,sha256=ENf81OOe4C2IEYGWUcaBwJ3O1P6VRg4DVirOXFXjioA,2899
14
+ soprano_sdk/core/state.py,sha256=k8ojLfWgjES3p9XWMeGU5s4UK-Xa5T8mS4VtZzTrcDw,2961
15
15
  soprano_sdk/nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  soprano_sdk/nodes/base.py,sha256=idFyOGGPnjsASYnrOF_NIh7eFcSuJqw61EoVN_WCTaU,2360
17
- soprano_sdk/nodes/call_function.py,sha256=Quys2Rf2JitV1fnTpnEIpIoCCOQDzhWBriRXO59UqqI,5469
18
- soprano_sdk/nodes/collect_input.py,sha256=2zhUXYrWlMJ8KH3qo39hZdPiYmc0_moNvvuP3tl3RGY,23381
17
+ soprano_sdk/nodes/call_function.py,sha256=PdmSSVXkMT5ptxeUjmKqND-wvqbaXp1i61QzIzFOfgQ,5535
18
+ soprano_sdk/nodes/collect_input.py,sha256=lEltZOU5ALvc57q8I_4SjzzEapVejy9mS0E73Jf7-sk,23759
19
19
  soprano_sdk/nodes/factory.py,sha256=l-Gysfgnao-o2dphhnbjjxcH3ojZanZNYN3CBH9dDbA,1624
20
20
  soprano_sdk/routing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
21
  soprano_sdk/routing/router.py,sha256=SrNciTIXXdC9bAbbO5bX7PN9mlRbITjr4RZdNm4jEVA,3450
@@ -26,9 +26,9 @@ soprano_sdk/utils/template.py,sha256=MG_B9TMx1ShpnSGo7s7TO-VfQzuFByuRNhJTvZ668kM
26
26
  soprano_sdk/utils/tool.py,sha256=hWN826HIKmLdswLCTURLH8hWlb2WU0MB8nIUErbpB-8,1877
27
27
  soprano_sdk/utils/tracing.py,sha256=gSHeBDLe-MbAZ9rkzpCoGFveeMdR9KLaA6tteB0IWjk,1991
28
28
  soprano_sdk/validation/__init__.py,sha256=ImChmO86jYHU90xzTttto2-LmOUOmvY_ibOQaLRz5BA,262
29
- soprano_sdk/validation/schema.py,sha256=R6bm4Pzgx8p08o77FN9FFSKTLTKOCf7WuThrR6rV2FI,14578
30
- soprano_sdk/validation/validator.py,sha256=mUTweaQyDaPqHWQDlsE9AFwnB3_MQAEOBrj1hwKQK68,7888
31
- soprano_sdk-0.2.2.dist-info/METADATA,sha256=lH2Z9tMo_U7riXwJ_Ucgmp7I0foTtlfsU4MWKBscQPU,11297
32
- soprano_sdk-0.2.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
33
- soprano_sdk-0.2.2.dist-info/licenses/LICENSE,sha256=A1aBauSjPNtVehOXJe3WuvdU2xvM9H8XmigFMm6665s,1073
34
- soprano_sdk-0.2.2.dist-info/RECORD,,
29
+ soprano_sdk/validation/schema.py,sha256=7B60_8xIt5bLXTYEGEDwU2DwmQn2f4QFyyIca2FYM9s,15420
30
+ soprano_sdk/validation/validator.py,sha256=GaCvHvjwVe88Z8yatQsueiPnqtq1oo5uN75gogzpQT0,8940
31
+ soprano_sdk-0.2.4.dist-info/METADATA,sha256=W-e-Si4pewUkpuWewKz_pmkWvUeRVR8HPwQ-Z-7tpTQ,11297
32
+ soprano_sdk-0.2.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
33
+ soprano_sdk-0.2.4.dist-info/licenses/LICENSE,sha256=A1aBauSjPNtVehOXJe3WuvdU2xvM9H8XmigFMm6665s,1073
34
+ soprano_sdk-0.2.4.dist-info/RECORD,,