soprano-sdk 0.1.97__py3-none-any.whl → 0.1.98__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.
@@ -1,4 +1,3 @@
1
- import json
2
1
  from abc import ABC, abstractmethod
3
2
  from typing import Any, Dict, List
4
3
  from langgraph.graph.state import CompiledStateGraph
@@ -20,7 +20,7 @@ from ..validation import validate_workflow
20
20
  class WorkflowEngine:
21
21
  def __init__(self, yaml_path: str, configs: dict):
22
22
  self.yaml_path = yaml_path
23
- self.configs = configs
23
+ self.configs = configs or {}
24
24
  logger.info(f"Loading workflow from: {yaml_path}")
25
25
 
26
26
  try:
@@ -1,5 +1,4 @@
1
1
  import copy
2
- import logging
3
2
  import uuid
4
3
  from abc import ABC, abstractmethod
5
4
  from datetime import datetime
soprano_sdk/core/state.py CHANGED
@@ -1,5 +1,5 @@
1
1
  import types
2
- from typing import Annotated, Optional, Dict, List, Any, Type
2
+ from typing import Annotated, Optional, Dict, List, Any
3
3
 
4
4
  from typing_extensions import TypedDict
5
5
 
soprano_sdk/nodes/base.py CHANGED
@@ -1,6 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from typing import Dict, Any, Callable, List
3
- import logging
4
3
 
5
4
  from langgraph.errors import GraphInterrupt
6
5
 
@@ -77,10 +77,14 @@ class CallFunctionStrategy(ActionStrategy):
77
77
  ) -> Dict[str, Any]:
78
78
  for transition in self.transitions:
79
79
  check_value = result
80
- if 'path' in transition:
81
- check_value = get_nested_value(result, transition['path'])
82
-
83
- if check_value != transition['condition']:
80
+ if 'ref' in transition:
81
+ check_value = get_nested_value(result, transition['ref'])
82
+
83
+ condition = transition['condition']
84
+ if isinstance(condition, list):
85
+ if check_value not in condition:
86
+ continue
87
+ elif check_value != condition:
84
88
  continue
85
89
 
86
90
  next_dest = transition['next']
@@ -230,18 +230,16 @@ class CollectInputStrategy(ActionStrategy):
230
230
  def _validate_collected_input(self, state) -> Tuple[bool, Optional[str]]:
231
231
  if not self.validator:
232
232
  return True, None
233
- result = self.validator(**state)
234
- if isinstance(result, tuple):
235
- return result
236
- return result, None
233
+ return self.validator(**state)
237
234
 
238
235
  def _handle_pre_populated_field(self, state: Dict[str, Any], conversation: List) -> Dict[str, Any]:
239
236
  logger.info(f"Field '{self.field}' is populated, skipping collection")
240
237
 
241
- is_valid_input, message = self._validate_collected_input(state)
238
+ is_valid_input, validator_error_message = self._validate_collected_input(state)
242
239
  if not is_valid_input:
243
240
  self._set_status(state, "collecting")
244
- return self._handle_validation_failure(state, conversation, message=f"{state[self.field]}", role="user")
241
+ conversation.append({"role": "user", "content": f"{state[self.field]}"})
242
+ return self._handle_validation_failure(state, conversation, message=validator_error_message)
245
243
 
246
244
  if self.transitions:
247
245
  first_transition = self.transitions[0]
@@ -351,16 +349,18 @@ class CollectInputStrategy(ActionStrategy):
351
349
  conversation: List[Dict[str, str]],
352
350
  state: Dict[str, Any]
353
351
  ) -> str:
354
- if len(conversation) == 0:
355
- if not (prompt := self.agent_config.get('initial_message')):
356
- prompt = agent.invoke([{"role": "user", "content": ""}])
352
+ last_assistant_message = next((msg['content'] for msg in reversed(conversation) if msg['role'] == 'assistant'), None)
357
353
 
358
- prompt = self._render_template_string(prompt, state)
359
- conversation.append({"role": "assistant", "content": prompt})
354
+ if last_assistant_message is not None:
355
+ return last_assistant_message
360
356
 
361
- return prompt
357
+ if not (prompt := self.agent_config.get('initial_message')):
358
+ prompt = agent.invoke([{"role": "user", "content": ""}])
362
359
 
363
- return conversation[-1]['content']
360
+ prompt = self._render_template_string(prompt, state)
361
+ conversation.append({"role": "assistant", "content": prompt})
362
+
363
+ return prompt
364
364
 
365
365
  def _update_conversation(self, state: Dict[str, Any], conversation: List[Dict[str, str]]):
366
366
  state[WorkflowKeys.CONVERSATIONS][self._conversation_key] = conversation
@@ -421,8 +421,17 @@ class CollectInputStrategy(ActionStrategy):
421
421
  self._set_status(state, 'collecting')
422
422
 
423
423
  for transition in self.transitions:
424
- pattern = transition['pattern']
425
- if pattern not in agent_response:
424
+ patterns = transition['pattern']
425
+ if isinstance(patterns, str):
426
+ patterns = [patterns]
427
+
428
+ matched_pattern = None
429
+ for pattern in patterns:
430
+ if pattern in agent_response:
431
+ matched_pattern = pattern
432
+ break
433
+
434
+ if not matched_pattern:
426
435
  continue
427
436
 
428
437
  matched = True
@@ -430,12 +439,12 @@ class CollectInputStrategy(ActionStrategy):
430
439
 
431
440
  logger.info(f"Matched transition: {transition}")
432
441
 
433
- value = agent_response.split(pattern)[1].strip()
442
+ value = agent_response.split(matched_pattern)[1].strip()
434
443
  if value:
435
444
  self._store_field_value(state, value)
436
- is_valid_input, message = self._validate_collected_input(state)
445
+ is_valid_input, validation_error_message = self._validate_collected_input(state)
437
446
  if not is_valid_input:
438
- return self._handle_validation_failure(state, conversation, message=message)
447
+ return self._handle_validation_failure(state, conversation, message=validation_error_message)
439
448
  state[WorkflowKeys.MESSAGES] = [f"✓ {self._formatted_field_name} collected: {value}" ]
440
449
  else:
441
450
  state[WorkflowKeys.MESSAGES] = []
@@ -493,7 +502,10 @@ class CollectInputStrategy(ActionStrategy):
493
502
  raise RuntimeError(f"Transition in step '{self.step_id}' missing required properties for structured output routing")
494
503
 
495
504
  field_value = agent_response.get(ref_field)
496
- if field_value == match_value:
505
+ if isinstance(match_value, list):
506
+ if field_value in match_value:
507
+ return next_node
508
+ elif field_value == match_value:
497
509
  return next_node
498
510
 
499
511
  return None
soprano_sdk/tools.py CHANGED
@@ -5,7 +5,6 @@ Workflow Tools - Wraps workflows as callable tools for agent frameworks
5
5
  import uuid
6
6
  from typing import Optional, Dict, Any
7
7
 
8
- from langgraph.graph.state import CompiledStateGraph
9
8
 
10
9
  from .utils.logger import logger
11
10
 
@@ -7,14 +7,14 @@ def get_nested_value(data: Any, path: str) -> Any:
7
7
  if not path:
8
8
  return data
9
9
 
10
- if not path.strip().startswith('{{'):
11
- template_str = f'{{{{ {path} }}}}'
12
- else:
13
- template_str = path
10
+ template_str = f"{{{{ {path} }}}}"
14
11
 
15
12
  try:
16
13
  template = Template(template_str)
17
- result = template.render(result=data)
14
+ if isinstance(data, dict):
15
+ result = template.render(**data)
16
+ else:
17
+ result = template.render(data)
18
18
 
19
19
  if not result or result == '':
20
20
  return None
@@ -1,10 +1,8 @@
1
1
  from opentelemetry import trace
2
- from typing import Optional, Dict, Any
2
+ from typing import Any
3
3
  from contextlib import contextmanager
4
- import logging
5
4
 
6
5
  from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
7
- from opentelemetry.trace import Link
8
6
 
9
7
  from ..utils.logger import logger
10
8
 
@@ -164,10 +164,6 @@ WORKFLOW_SCHEMA = {
164
164
  "pattern": "^[a-zA-Z_][a-zA-Z0-9_.]*\\.[a-zA-Z_][a-zA-Z0-9_]*$",
165
165
  "description": "Function path (for call_function, format: module.function)"
166
166
  },
167
- "inputs": {
168
- "type": "object",
169
- "description": "Input mapping for function"
170
- },
171
167
  "output": {
172
168
  "type": "string",
173
169
  "description": "Output field name"
@@ -183,8 +179,11 @@ WORKFLOW_SCHEMA = {
183
179
  "type": "object",
184
180
  "properties": {
185
181
  "pattern": {
186
- "type": "string",
187
- "description": "Pattern to match in response (for non-structured output)"
182
+ "anyOf": [
183
+ {"type": "string"},
184
+ {"type": "array", "items": {"type": "string"}}
185
+ ],
186
+ "description": "Pattern(s) to match in response (for non-structured output)"
188
187
  },
189
188
  "match": {
190
189
  "description": "Value to match against a field (for structured output)"
@@ -196,10 +195,6 @@ WORKFLOW_SCHEMA = {
196
195
  "condition": {
197
196
  "description": "Condition to evaluate (for call_function)"
198
197
  },
199
- "path": {
200
- "type": "string",
201
- "description": "Dot-notation path to the value for evaluation (optional)"
202
- },
203
198
  "next": {
204
199
  "type": "string",
205
200
  "description": "Next step or outcome ID"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soprano-sdk
3
- Version: 0.1.97
3
+ Version: 0.1.98
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,32 +1,32 @@
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=_lSZJoVwjy2RjCs4yXDupMlMSSApYB-XX57qYuy7mmE,7497
3
+ soprano_sdk/tools.py,sha256=cYEto0cmQ5GZ4zsH_YPfbaaDbzl5aO3rAOm7KlzTFVk,7444
4
4
  soprano_sdk/agents/__init__.py,sha256=Yzbtv6iP_ABRgZo0IUjy9vDofEvLFbOjuABw758176A,636
5
- soprano_sdk/agents/adaptor.py,sha256=IMMgo9_KLI82i1eenOaojw7UE0jjx9vjm8mjfsodKSM,3226
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/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  soprano_sdk/core/constants.py,sha256=pEwW_NeHhxs7aG457uiBCs65czAapozY6r9JAegc01Y,1451
10
- soprano_sdk/core/engine.py,sha256=WEyqGaBasGnSqlBAkFSQIZXJb7T6OEsN-DWznIBAzNs,8341
11
- soprano_sdk/core/rollback_strategies.py,sha256=UZyDBRmbi4kS7D9cGJT_mzSZzfqgbUoeTnOGcW1WOc0,7798
12
- soprano_sdk/core/state.py,sha256=h0Uo4uCwBAGTWrmzpDbcTwH6lI97-fXU9ek0tc3p2bM,2617
10
+ soprano_sdk/core/engine.py,sha256=aN805CtG7TDBkBbIJSbAbLo_5tKqrfve394L_DzZq8s,8347
11
+ soprano_sdk/core/rollback_strategies.py,sha256=NjDTtBCZlqyDql5PSwI9SMDLK7_BNlTxbW_cq_5gV0g,7783
12
+ soprano_sdk/core/state.py,sha256=ICyFhio1VESQxzYNwKrS-gk3QDc0OrwJLQtVnZ9c9LU,2611
13
13
  soprano_sdk/nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- soprano_sdk/nodes/base.py,sha256=H6wvvN3kUeXluZAt5Hf3iocO9NjTElAH4fTb2-1JKr0,2233
15
- soprano_sdk/nodes/call_function.py,sha256=UM-JpaG6DutuaFhOrXxEcT_fnK85aH6sFCVkfeYIfwc,4350
16
- soprano_sdk/nodes/collect_input.py,sha256=lAt1lonezXab0pVdzRGXPa1POAuIOmkMcAj0Kxwfi3I,22277
14
+ soprano_sdk/nodes/base.py,sha256=bD8P_SLW65yQ1QaCQ5aN8eQCe50sdHh4wdcQzdY6wGA,2218
15
+ soprano_sdk/nodes/call_function.py,sha256=23Q9wUY1F9dLMW2qSAApA8Sw5SyvtS70RBCMhZxvRu0,4506
16
+ soprano_sdk/nodes/collect_input.py,sha256=2Z5XztS_mBF43asds60-ZlSUUu2bFDEIPdWQY6R1c0k,22827
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
20
20
  soprano_sdk/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
21
  soprano_sdk/utils/function.py,sha256=yqkY4MlHOenv-Q3NciiovK1lamyrGQljpy6Q41wviy8,1216
22
22
  soprano_sdk/utils/logger.py,sha256=hMYaNHt5syGOXRkglTUKzkgfSbWerix_pHQntcYyep8,157
23
- soprano_sdk/utils/template.py,sha256=EssXvRDWdyITVWp52weYNIwnqj9ilJaoQotWFIg4rGQ,678
23
+ soprano_sdk/utils/template.py,sha256=MG_B9TMx1ShpnSGo7s7TO-VfQzuFByuRNhJTvZ668kM,685
24
24
  soprano_sdk/utils/tool.py,sha256=hWN826HIKmLdswLCTURLH8hWlb2WU0MB8nIUErbpB-8,1877
25
- soprano_sdk/utils/tracing.py,sha256=iSJlTAaiGzgBvZhLISCGAd9_7F2HRzhcIUNHuaFv_Zc,2059
25
+ soprano_sdk/utils/tracing.py,sha256=gSHeBDLe-MbAZ9rkzpCoGFveeMdR9KLaA6tteB0IWjk,1991
26
26
  soprano_sdk/validation/__init__.py,sha256=ImChmO86jYHU90xzTttto2-LmOUOmvY_ibOQaLRz5BA,262
27
- soprano_sdk/validation/schema.py,sha256=uJJZRDgwzWT2W8amd_W8mUAULvDnHJhiMEl-5so1ZK0,13559
27
+ soprano_sdk/validation/schema.py,sha256=HzqRp-5rl3GzX5KtFckAbBh8S7zklvQmRERIyfByS2w,13335
28
28
  soprano_sdk/validation/validator.py,sha256=l2P24wiCWBNTZ9-dRbgWwK48BGaR1xIdnBxzSCu0RPM,6498
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,,
29
+ soprano_sdk-0.1.98.dist-info/METADATA,sha256=yQkfqKo19uFrYSjqaFddjivMmAlU6IdMjHF3-NpkPkc,11269
30
+ soprano_sdk-0.1.98.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
31
+ soprano_sdk-0.1.98.dist-info/licenses/LICENSE,sha256=A1aBauSjPNtVehOXJe3WuvdU2xvM9H8XmigFMm6665s,1073
32
+ soprano_sdk-0.1.98.dist-info/RECORD,,