soprano-sdk 0.2.12__py3-none-any.whl → 0.2.13__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/core/engine.py +11 -1
- soprano_sdk/nodes/collect_input.py +0 -11
- soprano_sdk/tools.py +62 -8
- {soprano_sdk-0.2.12.dist-info → soprano_sdk-0.2.13.dist-info}/METADATA +1 -1
- {soprano_sdk-0.2.12.dist-info → soprano_sdk-0.2.13.dist-info}/RECORD +7 -7
- {soprano_sdk-0.2.12.dist-info → soprano_sdk-0.2.13.dist-info}/WHEEL +0 -0
- {soprano_sdk-0.2.12.dist-info → soprano_sdk-0.2.13.dist-info}/licenses/LICENSE +0 -0
soprano_sdk/core/engine.py
CHANGED
|
@@ -56,7 +56,8 @@ class WorkflowEngine:
|
|
|
56
56
|
|
|
57
57
|
logger.info(
|
|
58
58
|
f"Workflow loaded: {self.workflow_name} v{self.workflow_version} "
|
|
59
|
-
f"({len(self.steps)} steps, {len(self.outcomes)} outcomes
|
|
59
|
+
f"({len(self.steps)} steps, {len(self.outcomes)} outcomes, "
|
|
60
|
+
f"{len(self.collector_node_field_map)} collector nodes)"
|
|
60
61
|
)
|
|
61
62
|
|
|
62
63
|
except Exception as e:
|
|
@@ -202,6 +203,7 @@ class WorkflowEngine:
|
|
|
202
203
|
def load_steps(self):
|
|
203
204
|
prepared_steps: list = []
|
|
204
205
|
mfa_redirects: Dict[str, str] = {}
|
|
206
|
+
self.collector_node_field_map: Dict[str, str] = {} # Map of node_id -> field
|
|
205
207
|
|
|
206
208
|
for step in self.config['steps']:
|
|
207
209
|
step_id = step['id']
|
|
@@ -228,6 +230,14 @@ class WorkflowEngine:
|
|
|
228
230
|
|
|
229
231
|
prepared_steps.append(step)
|
|
230
232
|
|
|
233
|
+
# Build collector node -> field map
|
|
234
|
+
for step in prepared_steps:
|
|
235
|
+
if step.get('action') == 'collect_input':
|
|
236
|
+
node_id = step.get('id')
|
|
237
|
+
field = step.get('field')
|
|
238
|
+
if node_id and field:
|
|
239
|
+
self.collector_node_field_map[node_id] = field
|
|
240
|
+
|
|
231
241
|
for step in prepared_steps:
|
|
232
242
|
if step['id'] in self.mfa_validator_steps: # MFA Validator
|
|
233
243
|
continue
|
|
@@ -196,17 +196,6 @@ class CollectInputStrategy(ActionStrategy):
|
|
|
196
196
|
if context_value is None:
|
|
197
197
|
return
|
|
198
198
|
|
|
199
|
-
# Check if this node has already executed - if so, don't overwrite the collected value
|
|
200
|
-
execution_order = state.get(WorkflowKeys.NODE_EXECUTION_ORDER, [])
|
|
201
|
-
if self.step_id in execution_order:
|
|
202
|
-
logger.info(f"Skipping context value for '{self.field}' - node '{self.step_id}' already executed")
|
|
203
|
-
span.add_event("context.value_skipped", {
|
|
204
|
-
"field": self.field,
|
|
205
|
-
"reason": "node_already_executed",
|
|
206
|
-
"existing_value": str(state.get(self.field))
|
|
207
|
-
})
|
|
208
|
-
return
|
|
209
|
-
|
|
210
199
|
logger.info(f"Using context value for '{self.field}': {context_value}")
|
|
211
200
|
state[self.field] = context_value
|
|
212
201
|
span.add_event("context.value_used", {"field": self.field, "value": str(context_value)})
|
soprano_sdk/tools.py
CHANGED
|
@@ -83,26 +83,35 @@ class WorkflowTool:
|
|
|
83
83
|
callback_handler = CallbackHandler()
|
|
84
84
|
config = {"configurable": {"thread_id": thread_id}, "callbacks": [callback_handler]}
|
|
85
85
|
|
|
86
|
-
span.add_event("context.updated", {"fields": list(initial_context.keys())})
|
|
87
|
-
|
|
88
86
|
state = self.graph.get_state(config)
|
|
89
87
|
|
|
90
|
-
#
|
|
91
|
-
# Note: collect_input nodes will check NODE_EXECUTION_ORDER to avoid
|
|
92
|
-
# overwriting already-collected values
|
|
93
|
-
self.engine.update_context(initial_context)
|
|
94
|
-
|
|
88
|
+
# Intelligently update context based on workflow state
|
|
95
89
|
if state.next:
|
|
90
|
+
# Workflow is resuming - only update fields that haven't been collected yet
|
|
96
91
|
span.set_attribute("workflow.resumed", True)
|
|
97
92
|
logger.info(f"[WorkflowTool] Resuming interrupted workflow {self.name} (thread: {thread_id})")
|
|
93
|
+
|
|
94
|
+
filtered_context = self._filter_already_collected_fields(state.values, initial_context)
|
|
95
|
+
self.engine.update_context(filtered_context)
|
|
96
|
+
|
|
97
|
+
span.add_event("context.updated", {
|
|
98
|
+
"fields": list(filtered_context.keys()),
|
|
99
|
+
"filtered_out": list(set(initial_context.keys()) - set(filtered_context.keys()))
|
|
100
|
+
})
|
|
101
|
+
|
|
98
102
|
result = self.graph.invoke(
|
|
99
|
-
Command(resume=user_message or "", update=
|
|
103
|
+
Command(resume=user_message or "", update=filtered_context),
|
|
100
104
|
config=config
|
|
101
105
|
)
|
|
102
106
|
|
|
103
107
|
else:
|
|
108
|
+
# Fresh start - update all fields from initial_context
|
|
104
109
|
span.set_attribute("workflow.resumed", False)
|
|
105
110
|
logger.info(f"[WorkflowTool] Starting fresh workflow {self.name} (thread: {thread_id})")
|
|
111
|
+
|
|
112
|
+
self.engine.update_context(initial_context)
|
|
113
|
+
span.add_event("context.updated", {"fields": list(initial_context.keys())})
|
|
114
|
+
|
|
106
115
|
result = self.graph.invoke(initial_context, config=config)
|
|
107
116
|
|
|
108
117
|
final_state = self.graph.get_state(config)
|
|
@@ -129,6 +138,51 @@ class WorkflowTool:
|
|
|
129
138
|
span.set_attribute("workflow.status", "completed")
|
|
130
139
|
return self.engine.get_outcome_message(result)
|
|
131
140
|
|
|
141
|
+
def _filter_already_collected_fields(
|
|
142
|
+
self,
|
|
143
|
+
current_state: Dict[str, Any],
|
|
144
|
+
initial_context: Optional[Dict[str, Any]]
|
|
145
|
+
) -> Dict[str, Any]:
|
|
146
|
+
"""
|
|
147
|
+
Filter initial_context to exclude fields that have already been collected.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
current_state: Current workflow state
|
|
151
|
+
initial_context: Context to filter
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Filtered context with only uncollected fields
|
|
155
|
+
"""
|
|
156
|
+
if not initial_context:
|
|
157
|
+
return {}
|
|
158
|
+
|
|
159
|
+
from .core.constants import WorkflowKeys
|
|
160
|
+
|
|
161
|
+
execution_order = current_state.get(WorkflowKeys.NODE_EXECUTION_ORDER, [])
|
|
162
|
+
|
|
163
|
+
node_to_field_map = self.engine.collector_node_field_map
|
|
164
|
+
|
|
165
|
+
# Determine which fields have already been collected
|
|
166
|
+
collected_fields = set()
|
|
167
|
+
for executed_node_id in execution_order:
|
|
168
|
+
if executed_node_id in node_to_field_map:
|
|
169
|
+
collected_fields.add(node_to_field_map[executed_node_id])
|
|
170
|
+
|
|
171
|
+
# Filter initial_context to exclude already-collected fields
|
|
172
|
+
filtered_context = {
|
|
173
|
+
field: value
|
|
174
|
+
for field, value in initial_context.items()
|
|
175
|
+
if field not in collected_fields
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if collected_fields:
|
|
179
|
+
logger.info(
|
|
180
|
+
f"[WorkflowTool] Filtered out already-collected fields: {collected_fields}. "
|
|
181
|
+
f"Updating context with: {list(filtered_context.keys())}"
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
return filtered_context
|
|
185
|
+
|
|
132
186
|
def resume(
|
|
133
187
|
self,
|
|
134
188
|
thread_id: str,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
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=
|
|
3
|
+
soprano_sdk/tools.py,sha256=dmJ0OZ7Bj3rvjBQvLzgWlYRFVtNJOyMO2jLqaS13cAc,10971
|
|
4
4
|
soprano_sdk/agents/__init__.py,sha256=Yzbtv6iP_ABRgZo0IUjy9vDofEvLFbOjuABw758176A,636
|
|
5
5
|
soprano_sdk/agents/adaptor.py,sha256=dRk4pU1UgUhsKRotOzILjeXk0Zzoj7GzB-SKyBgKbNs,3242
|
|
6
6
|
soprano_sdk/agents/factory.py,sha256=Aucfz4rZVKCXMAQtbGAqp1JR8aYwa66mokRmKkKGhYA,6699
|
|
@@ -9,14 +9,14 @@ soprano_sdk/authenticators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
|
|
|
9
9
|
soprano_sdk/authenticators/mfa.py,sha256=Vew9Nb8pIRTw9hKbEZTH3YScY-fZ_TLq4ZuCzc-wbr8,7387
|
|
10
10
|
soprano_sdk/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
soprano_sdk/core/constants.py,sha256=UPXlRbF7gsOUNOV0Lm0jvgFfgZX7JrsV6n9I5csMfns,3508
|
|
12
|
-
soprano_sdk/core/engine.py,sha256=
|
|
12
|
+
soprano_sdk/core/engine.py,sha256=HKYoqwDm541pWSWwEKHxLlL3PX90Ux_5l_-HqihgL-g,12245
|
|
13
13
|
soprano_sdk/core/rollback_strategies.py,sha256=NjDTtBCZlqyDql5PSwI9SMDLK7_BNlTxbW_cq_5gV0g,7783
|
|
14
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/async_function.py,sha256=v6WujLKm8NXX2iAkJ7Gz_QIVCtWFrpC6nnPyyfuCxXs,9354
|
|
17
17
|
soprano_sdk/nodes/base.py,sha256=idFyOGGPnjsASYnrOF_NIh7eFcSuJqw61EoVN_WCTaU,2360
|
|
18
18
|
soprano_sdk/nodes/call_function.py,sha256=afYBmj5Aditbkvb_7gD3CsXBEEUohcsC1_cdHfcOunE,5847
|
|
19
|
-
soprano_sdk/nodes/collect_input.py,sha256=
|
|
19
|
+
soprano_sdk/nodes/collect_input.py,sha256=0M_-orYlOFvEBAiW1dDnPef1i77n41CjdV8ZKpWqLBE,24562
|
|
20
20
|
soprano_sdk/nodes/factory.py,sha256=IbBzT4FKBnYw5PuSo7uDONV3HSFtoyqjBQQtXtUY2IY,1756
|
|
21
21
|
soprano_sdk/routing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
22
|
soprano_sdk/routing/router.py,sha256=Z218r4BMbmlL9282ombutAoKsIs1WHZ2d5YHnbCeet8,3698
|
|
@@ -29,7 +29,7 @@ soprano_sdk/utils/tracing.py,sha256=gSHeBDLe-MbAZ9rkzpCoGFveeMdR9KLaA6tteB0IWjk,
|
|
|
29
29
|
soprano_sdk/validation/__init__.py,sha256=ImChmO86jYHU90xzTttto2-LmOUOmvY_ibOQaLRz5BA,262
|
|
30
30
|
soprano_sdk/validation/schema.py,sha256=SlC4sq-ueEg0p_8Uox_cgPj9S-0AEEiOOlA1Vsu0DsE,15443
|
|
31
31
|
soprano_sdk/validation/validator.py,sha256=GaCvHvjwVe88Z8yatQsueiPnqtq1oo5uN75gogzpQT0,8940
|
|
32
|
-
soprano_sdk-0.2.
|
|
33
|
-
soprano_sdk-0.2.
|
|
34
|
-
soprano_sdk-0.2.
|
|
35
|
-
soprano_sdk-0.2.
|
|
32
|
+
soprano_sdk-0.2.13.dist-info/METADATA,sha256=UcRp0Owv8akIqBA-y5fQmQBgIFlFkelf9CfQ0iXIV4g,11343
|
|
33
|
+
soprano_sdk-0.2.13.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
34
|
+
soprano_sdk-0.2.13.dist-info/licenses/LICENSE,sha256=A1aBauSjPNtVehOXJe3WuvdU2xvM9H8XmigFMm6665s,1073
|
|
35
|
+
soprano_sdk-0.2.13.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|