soprano-sdk 0.2.11__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/agents/adaptor.py +3 -3
- soprano_sdk/core/engine.py +11 -1
- soprano_sdk/nodes/collect_input.py +4 -1
- soprano_sdk/tools.py +63 -4
- {soprano_sdk-0.2.11.dist-info → soprano_sdk-0.2.13.dist-info}/METADATA +2 -1
- {soprano_sdk-0.2.11.dist-info → soprano_sdk-0.2.13.dist-info}/RECORD +8 -8
- {soprano_sdk-0.2.11.dist-info → soprano_sdk-0.2.13.dist-info}/WHEEL +0 -0
- {soprano_sdk-0.2.11.dist-info → soprano_sdk-0.2.13.dist-info}/licenses/LICENSE +0 -0
soprano_sdk/agents/adaptor.py
CHANGED
|
@@ -20,7 +20,7 @@ class LangGraphAgentAdapter(AgentAdapter):
|
|
|
20
20
|
self.agent = agent
|
|
21
21
|
|
|
22
22
|
def invoke(self, messages: List[Dict[str, str]]) -> Dict[str, Any]:
|
|
23
|
-
logger.info("Invoking
|
|
23
|
+
logger.info("Invoking LangGraphAgentAdapter agent with messages")
|
|
24
24
|
response = self.agent.invoke({"messages": messages})
|
|
25
25
|
|
|
26
26
|
if structured_response := response.get('structured_response'):
|
|
@@ -44,7 +44,7 @@ class CrewAIAgentAdapter(AgentAdapter):
|
|
|
44
44
|
|
|
45
45
|
def invoke(self, messages: List[Dict[str, str]]) -> Any:
|
|
46
46
|
try:
|
|
47
|
-
logger.info("Invoking
|
|
47
|
+
logger.info("Invoking CrewAIAgentAdapter agent with messages")
|
|
48
48
|
result = self.agent.kickoff(messages, response_format=self.output_schema)
|
|
49
49
|
|
|
50
50
|
if structured_response := getattr(result, 'pydantic', None) :
|
|
@@ -65,7 +65,7 @@ class AgnoAgentAdapter(AgentAdapter):
|
|
|
65
65
|
|
|
66
66
|
def invoke(self, messages: List[Dict[str, str]]) -> Dict[str, Any]:
|
|
67
67
|
try:
|
|
68
|
-
logger.info("Invoking
|
|
68
|
+
logger.info("Invoking AgnoAgentAdapter agent with messages")
|
|
69
69
|
response = self.agent.run(messages)
|
|
70
70
|
agent_response = response.content if hasattr(response, 'content') else str(response)
|
|
71
71
|
|
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
|
|
@@ -195,6 +195,7 @@ class CollectInputStrategy(ActionStrategy):
|
|
|
195
195
|
context_value = self.engine_context.get_context_value(self.field)
|
|
196
196
|
if context_value is None:
|
|
197
197
|
return
|
|
198
|
+
|
|
198
199
|
logger.info(f"Using context value for '{self.field}': {context_value}")
|
|
199
200
|
state[self.field] = context_value
|
|
200
201
|
span.add_event("context.value_used", {"field": self.field, "value": str(context_value)})
|
|
@@ -375,7 +376,9 @@ class CollectInputStrategy(ActionStrategy):
|
|
|
375
376
|
if self.is_structured_output:
|
|
376
377
|
try:
|
|
377
378
|
response_dict = json.loads(agent_response) if isinstance(agent_response, str) else agent_response
|
|
378
|
-
|
|
379
|
+
bot_response = response_dict.get("bot_response", None)
|
|
380
|
+
# Treat empty or whitespace-only bot_response as None
|
|
381
|
+
prompt = bot_response if (bot_response and bot_response.strip()) else None
|
|
379
382
|
except (json.JSONDecodeError, TypeError, ValueError) as e:
|
|
380
383
|
logger.error(f"Error When Converting Structured Output {agent_response} to JSON {e}")
|
|
381
384
|
prompt = agent_response
|
soprano_sdk/tools.py
CHANGED
|
@@ -83,21 +83,35 @@ class WorkflowTool:
|
|
|
83
83
|
callback_handler = CallbackHandler()
|
|
84
84
|
config = {"configurable": {"thread_id": thread_id}, "callbacks": [callback_handler]}
|
|
85
85
|
|
|
86
|
-
self.engine.update_context(initial_context)
|
|
87
|
-
span.add_event("context.updated", {"fields": list(initial_context.keys())})
|
|
88
|
-
|
|
89
86
|
state = self.graph.get_state(config)
|
|
90
87
|
|
|
88
|
+
# Intelligently update context based on workflow state
|
|
91
89
|
if state.next:
|
|
90
|
+
# Workflow is resuming - only update fields that haven't been collected yet
|
|
92
91
|
span.set_attribute("workflow.resumed", True)
|
|
93
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
|
+
|
|
94
102
|
result = self.graph.invoke(
|
|
95
|
-
Command(resume=user_message or "", update=
|
|
103
|
+
Command(resume=user_message or "", update=filtered_context),
|
|
96
104
|
config=config
|
|
97
105
|
)
|
|
106
|
+
|
|
98
107
|
else:
|
|
108
|
+
# Fresh start - update all fields from initial_context
|
|
99
109
|
span.set_attribute("workflow.resumed", False)
|
|
100
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
|
+
|
|
101
115
|
result = self.graph.invoke(initial_context, config=config)
|
|
102
116
|
|
|
103
117
|
final_state = self.graph.get_state(config)
|
|
@@ -124,6 +138,51 @@ class WorkflowTool:
|
|
|
124
138
|
span.set_attribute("workflow.status", "completed")
|
|
125
139
|
return self.engine.get_outcome_message(result)
|
|
126
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
|
+
|
|
127
186
|
def resume(
|
|
128
187
|
self,
|
|
129
188
|
thread_id: str,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: soprano-sdk
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.13
|
|
4
4
|
Summary: YAML-driven workflow engine with AI agent integration for building conversational SOPs
|
|
5
5
|
Author: Arvind Thangamani
|
|
6
6
|
License: MIT
|
|
@@ -29,6 +29,7 @@ Requires-Dist: pyyaml>=6.0
|
|
|
29
29
|
Provides-Extra: dev
|
|
30
30
|
Requires-Dist: gradio>=5.46.0; extra == 'dev'
|
|
31
31
|
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff==0.14.13; extra == 'dev'
|
|
32
33
|
Provides-Extra: persistence
|
|
33
34
|
Requires-Dist: langgraph-checkpoint-mongodb>=0.2.0; extra == 'persistence'
|
|
34
35
|
Requires-Dist: pymongo>=4.0.0; extra == 'persistence'
|
|
@@ -1,22 +1,22 @@
|
|
|
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
|
-
soprano_sdk/agents/adaptor.py,sha256=
|
|
5
|
+
soprano_sdk/agents/adaptor.py,sha256=dRk4pU1UgUhsKRotOzILjeXk0Zzoj7GzB-SKyBgKbNs,3242
|
|
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
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
|