soprano-sdk 0.1.96__py3-none-any.whl → 0.1.97__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.
@@ -20,7 +20,7 @@ def create_structured_output_model(
20
20
  if not fields:
21
21
  raise ValueError("At least one field definition is required")
22
22
 
23
- field_definitions = {"bot_response": (Optional[str], Field(None, description="bot response for the user query"))}
23
+ field_definitions = {"bot_response": (Optional[str], Field(None, description="bot response for the user query, only use this for clarification or asking for more information"))}
24
24
 
25
25
  if needs_intent_change:
26
26
  field_definitions["intent_change"] = (Optional[str], Field(None, description="node name for handling new intent"))
@@ -61,6 +61,12 @@ IF the user's query continues with the SAME intent OR does not match any intent
61
61
  - Proceed with your normal response
62
62
  - Do NOT mention intent detection
63
63
  - Answer the user's question as configured
64
+
65
+ BOT RESPONSE RULES:
66
+ - If the user is asking a question or needs information, provide a helpful and concise response
67
+ - If the user input is unclear or does not provide enough information, ask for clarification or more details
68
+ - { "populate bot_response field to respond back to the user" if with_structured_output else ""}
69
+ - Do not respond or use bot_response if the user provides a valid input
64
70
  """
65
71
 
66
72
  def _create_rollback_strategy(strategy_name: str) -> RollbackStrategy:
@@ -178,7 +184,8 @@ class CollectInputStrategy(ActionStrategy):
178
184
  return template_loader.from_string(template_str).render(state)
179
185
 
180
186
  def _apply_context_value(self, state: Dict[str, Any], span) -> None:
181
- if not (context_value := self.engine_context.get_context_value(self.field)):
187
+ context_value = self.engine_context.get_context_value(self.field)
188
+ if context_value is None:
182
189
  return
183
190
  logger.info(f"Using context value for '{self.field}': {context_value}")
184
191
  state[self.field] = context_value
@@ -231,7 +238,7 @@ class CollectInputStrategy(ActionStrategy):
231
238
  def _handle_pre_populated_field(self, state: Dict[str, Any], conversation: List) -> Dict[str, Any]:
232
239
  logger.info(f"Field '{self.field}' is populated, skipping collection")
233
240
 
234
- is_valid_input, _ = self._validate_collected_input(state)
241
+ is_valid_input, message = self._validate_collected_input(state)
235
242
  if not is_valid_input:
236
243
  self._set_status(state, "collecting")
237
244
  return self._handle_validation_failure(state, conversation, message=f"{state[self.field]}", role="user")
@@ -391,6 +398,11 @@ class CollectInputStrategy(ActionStrategy):
391
398
  workflow_steps=workflow_steps
392
399
  )
393
400
 
401
+ for key, value in restored_state.items():
402
+ context_value = self.engine_context.get_context_value(key)
403
+ if context_value is not None:
404
+ restored_state[key] = context_value
405
+
394
406
  if not restored_state:
395
407
  logger.warning(f"Rollback strategy returned empty state for node '{target_node}'")
396
408
  return {}
@@ -472,25 +484,17 @@ class CollectInputStrategy(ActionStrategy):
472
484
  return state
473
485
 
474
486
  def _find_matching_transition(self, agent_response: Any) -> Optional[str]:
475
- is_structured_output = isinstance(agent_response, dict)
476
-
477
487
  for transition in self.transitions:
478
- if is_structured_output:
479
- next_node = transition.get("next")
480
- match_value = transition.get("match")
481
- ref_field = transition.get("ref")
488
+ next_node = transition.get("next")
489
+ match_value = transition.get("match")
490
+ ref_field = transition.get("ref")
482
491
 
483
- if not all([next_node, match_value, ref_field]):
484
- raise RuntimeError(f"Transition in step '{self.step_id}' missing required properties for structured output routing")
492
+ if not next_node or not ref_field or match_value is None:
493
+ raise RuntimeError(f"Transition in step '{self.step_id}' missing required properties for structured output routing")
485
494
 
486
- if field_value := agent_response.get(ref_field):
487
- if field_value == match_value:
488
- return next_node
489
- else:
490
- next_node = transition.get("next")
491
- pattern = transition.get("pattern")
492
- if pattern in agent_response:
493
- return next_node
495
+ field_value = agent_response.get(ref_field)
496
+ if field_value == match_value:
497
+ return next_node
494
498
 
495
499
  return None
496
500
 
soprano_sdk/tools.py CHANGED
@@ -78,35 +78,23 @@ class WorkflowTool:
78
78
  ) as span:
79
79
  callback_handler = CallbackHandler()
80
80
  config = {"configurable": {"thread_id": thread_id}, "callbacks": [callback_handler]}
81
-
82
- update_context = {}
83
- engine_context_data = {}
84
- for key, value in initial_context.items():
85
- if key in self.engine.collect_input_fields:
86
- engine_context_data[key] = value
87
- continue
88
- if value:
89
- update_context[key] = value
90
-
91
- if engine_context_data:
92
- self.engine.update_context(engine_context_data)
93
- span.add_event("context.updated", {"fields": list(engine_context_data.keys())})
81
+
82
+ self.engine.update_context(initial_context)
83
+ span.add_event("context.updated", {"fields": list(initial_context.keys())})
94
84
 
95
85
  state = self.graph.get_state(config)
96
86
 
97
87
  if state.next:
98
- # Workflow is interrupted and waiting for input
99
88
  span.set_attribute("workflow.resumed", True)
100
89
  logger.info(f"[WorkflowTool] Resuming interrupted workflow {self.name} (thread: {thread_id})")
101
90
  result = self.graph.invoke(
102
- Command(resume=user_message or "", update=update_context),
91
+ Command(resume=user_message or "", update=initial_context),
103
92
  config=config
104
93
  )
105
94
  else:
106
- # Workflow is fresh or completed, start/restart
107
95
  span.set_attribute("workflow.resumed", False)
108
96
  logger.info(f"[WorkflowTool] Starting fresh workflow {self.name} (thread: {thread_id})")
109
- result = self.graph.invoke(update_context, config=config)
97
+ result = self.graph.invoke(initial_context, config=config)
110
98
 
111
99
  final_state = self.graph.get_state(config)
112
100
  if not final_state.next and self.checkpointer:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soprano-sdk
3
- Version: 0.1.96
3
+ Version: 0.1.97
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,10 +1,10 @@
1
1
  soprano_sdk/__init__.py,sha256=y3c4i7Q7SAPS2Tee7V0TzWdhgMxBWfDJJ98eqD1HxGI,188
2
2
  soprano_sdk/engine.py,sha256=EFK91iTHjp72otLN6Kg-yeLye2J3CAKN0QH4FI2taL8,14838
3
- soprano_sdk/tools.py,sha256=A0qFEwn208GCYc-_7ZbZKR7N3HG5-6TA3Ma1RPo6YnM,8029
3
+ soprano_sdk/tools.py,sha256=_lSZJoVwjy2RjCs4yXDupMlMSSApYB-XX57qYuy7mmE,7497
4
4
  soprano_sdk/agents/__init__.py,sha256=Yzbtv6iP_ABRgZo0IUjy9vDofEvLFbOjuABw758176A,636
5
5
  soprano_sdk/agents/adaptor.py,sha256=IMMgo9_KLI82i1eenOaojw7UE0jjx9vjm8mjfsodKSM,3226
6
6
  soprano_sdk/agents/factory.py,sha256=Aucfz4rZVKCXMAQtbGAqp1JR8aYwa66mokRmKkKGhYA,6699
7
- soprano_sdk/agents/structured_output.py,sha256=LDBWCMJFclOvcFB3OJpu37tO0Ct_M-L2PIH5MCYSjLI,3262
7
+ soprano_sdk/agents/structured_output.py,sha256=7DSVzfMPsZAqBwI3v6XL15qG5Gh4jJ-qddcVPaa3gdc,3326
8
8
  soprano_sdk/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  soprano_sdk/core/constants.py,sha256=pEwW_NeHhxs7aG457uiBCs65czAapozY6r9JAegc01Y,1451
10
10
  soprano_sdk/core/engine.py,sha256=WEyqGaBasGnSqlBAkFSQIZXJb7T6OEsN-DWznIBAzNs,8341
@@ -13,7 +13,7 @@ soprano_sdk/core/state.py,sha256=h0Uo4uCwBAGTWrmzpDbcTwH6lI97-fXU9ek0tc3p2bM,261
13
13
  soprano_sdk/nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  soprano_sdk/nodes/base.py,sha256=H6wvvN3kUeXluZAt5Hf3iocO9NjTElAH4fTb2-1JKr0,2233
15
15
  soprano_sdk/nodes/call_function.py,sha256=UM-JpaG6DutuaFhOrXxEcT_fnK85aH6sFCVkfeYIfwc,4350
16
- soprano_sdk/nodes/collect_input.py,sha256=LhDsqtlBKK-uIz90QIODDoR5PZbnv4rWyj-hvTR64do,21987
16
+ soprano_sdk/nodes/collect_input.py,sha256=lAt1lonezXab0pVdzRGXPa1POAuIOmkMcAj0Kxwfi3I,22277
17
17
  soprano_sdk/nodes/factory.py,sha256=l-Gysfgnao-o2dphhnbjjxcH3ojZanZNYN3CBH9dDbA,1624
18
18
  soprano_sdk/routing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  soprano_sdk/routing/router.py,sha256=SrNciTIXXdC9bAbbO5bX7PN9mlRbITjr4RZdNm4jEVA,3450
@@ -26,7 +26,7 @@ soprano_sdk/utils/tracing.py,sha256=iSJlTAaiGzgBvZhLISCGAd9_7F2HRzhcIUNHuaFv_Zc,
26
26
  soprano_sdk/validation/__init__.py,sha256=ImChmO86jYHU90xzTttto2-LmOUOmvY_ibOQaLRz5BA,262
27
27
  soprano_sdk/validation/schema.py,sha256=uJJZRDgwzWT2W8amd_W8mUAULvDnHJhiMEl-5so1ZK0,13559
28
28
  soprano_sdk/validation/validator.py,sha256=l2P24wiCWBNTZ9-dRbgWwK48BGaR1xIdnBxzSCu0RPM,6498
29
- soprano_sdk-0.1.96.dist-info/METADATA,sha256=eLSUnBX8Gg_37Okmm3g82Jm4UZp6j4pA64Y3cSd0YbQ,11269
30
- soprano_sdk-0.1.96.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
31
- soprano_sdk-0.1.96.dist-info/licenses/LICENSE,sha256=A1aBauSjPNtVehOXJe3WuvdU2xvM9H8XmigFMm6665s,1073
32
- soprano_sdk-0.1.96.dist-info/RECORD,,
29
+ soprano_sdk-0.1.97.dist-info/METADATA,sha256=opdDgT9ecUMXJJP7Dr93lnrPgwXsFqMfeJ1jRnCeh38,11269
30
+ soprano_sdk-0.1.97.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
31
+ soprano_sdk-0.1.97.dist-info/licenses/LICENSE,sha256=A1aBauSjPNtVehOXJe3WuvdU2xvM9H8XmigFMm6665s,1073
32
+ soprano_sdk-0.1.97.dist-info/RECORD,,