soprano-sdk 0.2.18__tar.gz → 0.2.20__tar.gz
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-0.2.18 → soprano_sdk-0.2.20}/PKG-INFO +108 -1
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/README.md +107 -0
- soprano_sdk-0.2.20/docs/framework_flow_diagrams.md +430 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/pyproject.toml +1 -1
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/agents/adaptor.py +5 -3
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/agents/structured_output.py +5 -1
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/core/constants.py +2 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/core/engine.py +1 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/core/state.py +2 -2
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/nodes/base.py +6 -1
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/nodes/collect_input.py +80 -7
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/nodes/factory.py +2 -0
- soprano_sdk-0.2.20/soprano_sdk/nodes/follow_up.py +346 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/routing/router.py +6 -2
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/tools.py +18 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/validation/schema.py +4 -0
- soprano_sdk-0.2.20/tests/test_base_node.py +232 -0
- soprano_sdk-0.2.20/tests/test_engine_failure_message.py +335 -0
- soprano_sdk-0.2.20/tests/test_follow_up.py +473 -0
- soprano_sdk-0.2.20/tests/test_out_of_scope.py +529 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/.claude/settings.local.json +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/.github/workflows/test_build_and_publish.yaml +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/.gitignore +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/.python-version +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/CLAUDE.md +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/LICENSE +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/concert_booking/__init__.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/concert_booking/booking_helpers.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/concert_booking/concert_ticket_booking.yaml +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/concert_booking/concert_tools.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/concert_booking/example_runner.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/concert_booking/tool.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/framework_example.yaml +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/greeting_functions.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/greeting_workflow.yaml +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/main.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/payment_async_functions.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/payment_async_workflow.yaml +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/persistence/README.md +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/persistence/conversation_based.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/persistence/entity_based.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/persistence/mongodb_demo.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/return_functions.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/return_workflow.yaml +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/structured_output_example.yaml +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/supervisors/README.md +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/supervisors/crewai_supervisor_ui.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/supervisors/langgraph_supervisor_ui.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/supervisors/tools/__init__.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/supervisors/tools/crewai_tools.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/supervisors/tools/langgraph_tools.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/supervisors/workflow_tools.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/test_payment_async.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/tools/__init__.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/tools/address.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/examples/validator.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/legacy/langgraph_demo.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/legacy/langgraph_selfloop_demo.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/legacy/langgraph_v.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/legacy/main.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/legacy/return_fsm.excalidraw +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/legacy/return_state_machine.png +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/legacy/ui.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/scripts/visualize_workflow.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/scripts/workflow_demo.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/scripts/workflow_demo_ui.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/__init__.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/agents/__init__.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/agents/factory.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/authenticators/__init__.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/authenticators/mfa.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/core/__init__.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/core/rollback_strategies.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/engine.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/nodes/__init__.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/nodes/async_function.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/nodes/call_function.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/routing/__init__.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/utils/__init__.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/utils/function.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/utils/logger.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/utils/template.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/utils/tool.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/utils/tracing.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/validation/__init__.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/soprano_sdk/validation/validator.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/tests/debug_jinja2.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/tests/test_adaptor_logging.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/tests/test_agent_factory.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/tests/test_async_function.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/tests/test_collect_input_refactor.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/tests/test_external_values.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/tests/test_inputs_validation.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/tests/test_jinja2_path.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/tests/test_jinja2_standalone.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/tests/test_mfa_scenarios.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/tests/test_persistence.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/tests/test_structured_output.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/tests/test_transition_routing.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/tests/test_workflow_tool_context_update.py +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/todo.md +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/workflow-visualizer/.eslintrc.cjs +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/workflow-visualizer/.gitignore +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/workflow-visualizer/README.md +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/workflow-visualizer/index.html +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/workflow-visualizer/package-lock.json +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/workflow-visualizer/package.json +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/workflow-visualizer/src/App.jsx +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/workflow-visualizer/src/CustomNode.jsx +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/workflow-visualizer/src/StepDetailsModal.jsx +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/workflow-visualizer/src/WorkflowGraph.jsx +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/workflow-visualizer/src/WorkflowInfoPanel.jsx +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/workflow-visualizer/src/assets/react.svg +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/workflow-visualizer/src/main.jsx +0 -0
- {soprano_sdk-0.2.18 → soprano_sdk-0.2.20}/workflow-visualizer/vite.config.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: soprano-sdk
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.20
|
|
4
4
|
Summary: YAML-driven workflow engine with AI agent integration for building conversational SOPs
|
|
5
5
|
Author: Arvind Thangamani
|
|
6
6
|
License: MIT
|
|
@@ -51,6 +51,9 @@ A YAML-driven workflow engine with AI agent integration for building conversatio
|
|
|
51
51
|
- **External Context Injection**: Support for pre-populated fields from external orchestrators
|
|
52
52
|
- **Pattern Matching**: Flexible transition logic based on patterns and conditions
|
|
53
53
|
- **Visualization**: Generate workflow graphs as images or Mermaid diagrams
|
|
54
|
+
- **Follow-up Conversations**: Handle user follow-up questions with full workflow context
|
|
55
|
+
- **Intent Detection**: Route users between collector nodes based on detected intent
|
|
56
|
+
- **Out-of-Scope Detection**: Signal when user queries are unrelated to the current workflow
|
|
54
57
|
|
|
55
58
|
## Installation
|
|
56
59
|
|
|
@@ -268,6 +271,108 @@ Calls a Python function with workflow state.
|
|
|
268
271
|
next: failure_step
|
|
269
272
|
```
|
|
270
273
|
|
|
274
|
+
### call_async_function
|
|
275
|
+
|
|
276
|
+
Calls an async function that may return a pending status, triggering an interrupt until the async operation completes.
|
|
277
|
+
|
|
278
|
+
```yaml
|
|
279
|
+
- id: verify_payment
|
|
280
|
+
action: call_async_function
|
|
281
|
+
function: "payments.start_verification"
|
|
282
|
+
output: verification_result
|
|
283
|
+
transitions:
|
|
284
|
+
- condition: "verified"
|
|
285
|
+
next: payment_approved
|
|
286
|
+
- condition: "failed"
|
|
287
|
+
next: payment_rejected
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### follow_up
|
|
291
|
+
|
|
292
|
+
Handles follow-up questions from users. Unlike `collect_input_with_agent` where the agent asks first, here the **user initiates** by asking questions. The agent responds using full workflow context.
|
|
293
|
+
|
|
294
|
+
```yaml
|
|
295
|
+
- id: handle_questions
|
|
296
|
+
action: follow_up
|
|
297
|
+
next: final_confirmation # Where to go when user says "done"
|
|
298
|
+
closure_patterns: # Optional: customize closure detection
|
|
299
|
+
- "ok"
|
|
300
|
+
- "thank you"
|
|
301
|
+
- "done"
|
|
302
|
+
agent:
|
|
303
|
+
name: "FollowUpAssistant"
|
|
304
|
+
model: "gpt-4o-mini"
|
|
305
|
+
description: "Answering questions about the order"
|
|
306
|
+
instructions: |
|
|
307
|
+
Help the user with any questions about their order.
|
|
308
|
+
Be concise and helpful.
|
|
309
|
+
detect_out_of_scope: true # Signal when user asks unrelated questions
|
|
310
|
+
transitions: # Optional: route based on patterns
|
|
311
|
+
- pattern: "ROUTE_TO_PAYMENT:"
|
|
312
|
+
next: payment_step
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Key features:**
|
|
316
|
+
- **User initiates**: No initial prompt - waits for user to ask a question
|
|
317
|
+
- **Full state context**: Agent sees all collected workflow data
|
|
318
|
+
- **Closure detection**: Detects "ok", "thanks", "done" → proceeds to next step
|
|
319
|
+
- **Intent change**: Routes to collector nodes when user wants to change data
|
|
320
|
+
- **Out-of-scope**: Signals to parent orchestrator for unrelated queries
|
|
321
|
+
|
|
322
|
+
## Interrupt Types
|
|
323
|
+
|
|
324
|
+
The workflow engine uses three interrupt types to pause execution and communicate with the caller:
|
|
325
|
+
|
|
326
|
+
| Type | Marker | Triggered By | Use Case |
|
|
327
|
+
|------|--------|--------------|----------|
|
|
328
|
+
| **USER_INPUT** | `__WORKFLOW_INTERRUPT__` | `collect_input_with_agent`, `follow_up` | Waiting for user input |
|
|
329
|
+
| **ASYNC** | `__ASYNC_INTERRUPT__` | `call_async_function` | Waiting for async operation callback |
|
|
330
|
+
| **OUT_OF_SCOPE** | `__OUT_OF_SCOPE_INTERRUPT__` | `collect_input_with_agent`, `follow_up` | User query unrelated to current task |
|
|
331
|
+
|
|
332
|
+
### Handling Interrupts
|
|
333
|
+
|
|
334
|
+
```python
|
|
335
|
+
result = graph.invoke({}, config=config)
|
|
336
|
+
|
|
337
|
+
if "__interrupt__" in result and result["__interrupt__"]:
|
|
338
|
+
interrupt_value = result["__interrupt__"][0].value
|
|
339
|
+
|
|
340
|
+
# Check interrupt type
|
|
341
|
+
if isinstance(interrupt_value, dict):
|
|
342
|
+
if interrupt_value.get("type") == "async":
|
|
343
|
+
# Async interrupt - wait for external callback
|
|
344
|
+
pending_metadata = interrupt_value.get("pending")
|
|
345
|
+
# ... handle async operation ...
|
|
346
|
+
result = graph.invoke(Command(resume=async_result), config=config)
|
|
347
|
+
|
|
348
|
+
elif interrupt_value.get("type") == "out_of_scope":
|
|
349
|
+
# Out-of-scope - user asking unrelated question
|
|
350
|
+
reason = interrupt_value.get("reason")
|
|
351
|
+
user_message = interrupt_value.get("user_message")
|
|
352
|
+
# ... route to different workflow or handle appropriately ...
|
|
353
|
+
else:
|
|
354
|
+
# User input interrupt - prompt is a string
|
|
355
|
+
prompt = interrupt_value
|
|
356
|
+
user_input = input(f"Bot: {prompt}\nYou: ")
|
|
357
|
+
result = graph.invoke(Command(resume=user_input), config=config)
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Out-of-Scope Detection
|
|
361
|
+
|
|
362
|
+
Data collector and follow-up nodes can detect when user queries are unrelated to the current task. This is useful for multi-workflow systems where a supervisor agent needs to route users to different SOPs.
|
|
363
|
+
|
|
364
|
+
**Configuration:**
|
|
365
|
+
```yaml
|
|
366
|
+
agent:
|
|
367
|
+
detect_out_of_scope: true # Enabled by default
|
|
368
|
+
scope_description: "collecting order information for returns" # Optional
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
**Response format:**
|
|
372
|
+
```
|
|
373
|
+
__OUT_OF_SCOPE_INTERRUPT__|{thread_id}|{workflow_name}|{"reason":"...","user_message":"..."}
|
|
374
|
+
```
|
|
375
|
+
|
|
271
376
|
## Examples
|
|
272
377
|
|
|
273
378
|
See the `examples/` directory for complete workflow examples:
|
|
@@ -411,6 +516,8 @@ Contributions are welcome! Please open an issue or submit a pull request.
|
|
|
411
516
|
- ✅ Database persistence (SqliteSaver, PostgresSaver supported)
|
|
412
517
|
- ✅ Pluggable checkpointer system
|
|
413
518
|
- ✅ Thread ID strategies and examples
|
|
519
|
+
- ✅ Follow-up node for conversational Q&A
|
|
520
|
+
- ✅ Out-of-scope detection for multi-workflow routing
|
|
414
521
|
- Additional action types (webhook, conditional branching, parallel execution)
|
|
415
522
|
- More workflow examples (customer onboarding, support ticketing, approval flows)
|
|
416
523
|
- Workflow testing utilities
|
|
@@ -10,6 +10,9 @@ A YAML-driven workflow engine with AI agent integration for building conversatio
|
|
|
10
10
|
- **External Context Injection**: Support for pre-populated fields from external orchestrators
|
|
11
11
|
- **Pattern Matching**: Flexible transition logic based on patterns and conditions
|
|
12
12
|
- **Visualization**: Generate workflow graphs as images or Mermaid diagrams
|
|
13
|
+
- **Follow-up Conversations**: Handle user follow-up questions with full workflow context
|
|
14
|
+
- **Intent Detection**: Route users between collector nodes based on detected intent
|
|
15
|
+
- **Out-of-Scope Detection**: Signal when user queries are unrelated to the current workflow
|
|
13
16
|
|
|
14
17
|
## Installation
|
|
15
18
|
|
|
@@ -227,6 +230,108 @@ Calls a Python function with workflow state.
|
|
|
227
230
|
next: failure_step
|
|
228
231
|
```
|
|
229
232
|
|
|
233
|
+
### call_async_function
|
|
234
|
+
|
|
235
|
+
Calls an async function that may return a pending status, triggering an interrupt until the async operation completes.
|
|
236
|
+
|
|
237
|
+
```yaml
|
|
238
|
+
- id: verify_payment
|
|
239
|
+
action: call_async_function
|
|
240
|
+
function: "payments.start_verification"
|
|
241
|
+
output: verification_result
|
|
242
|
+
transitions:
|
|
243
|
+
- condition: "verified"
|
|
244
|
+
next: payment_approved
|
|
245
|
+
- condition: "failed"
|
|
246
|
+
next: payment_rejected
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### follow_up
|
|
250
|
+
|
|
251
|
+
Handles follow-up questions from users. Unlike `collect_input_with_agent` where the agent asks first, here the **user initiates** by asking questions. The agent responds using full workflow context.
|
|
252
|
+
|
|
253
|
+
```yaml
|
|
254
|
+
- id: handle_questions
|
|
255
|
+
action: follow_up
|
|
256
|
+
next: final_confirmation # Where to go when user says "done"
|
|
257
|
+
closure_patterns: # Optional: customize closure detection
|
|
258
|
+
- "ok"
|
|
259
|
+
- "thank you"
|
|
260
|
+
- "done"
|
|
261
|
+
agent:
|
|
262
|
+
name: "FollowUpAssistant"
|
|
263
|
+
model: "gpt-4o-mini"
|
|
264
|
+
description: "Answering questions about the order"
|
|
265
|
+
instructions: |
|
|
266
|
+
Help the user with any questions about their order.
|
|
267
|
+
Be concise and helpful.
|
|
268
|
+
detect_out_of_scope: true # Signal when user asks unrelated questions
|
|
269
|
+
transitions: # Optional: route based on patterns
|
|
270
|
+
- pattern: "ROUTE_TO_PAYMENT:"
|
|
271
|
+
next: payment_step
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Key features:**
|
|
275
|
+
- **User initiates**: No initial prompt - waits for user to ask a question
|
|
276
|
+
- **Full state context**: Agent sees all collected workflow data
|
|
277
|
+
- **Closure detection**: Detects "ok", "thanks", "done" → proceeds to next step
|
|
278
|
+
- **Intent change**: Routes to collector nodes when user wants to change data
|
|
279
|
+
- **Out-of-scope**: Signals to parent orchestrator for unrelated queries
|
|
280
|
+
|
|
281
|
+
## Interrupt Types
|
|
282
|
+
|
|
283
|
+
The workflow engine uses three interrupt types to pause execution and communicate with the caller:
|
|
284
|
+
|
|
285
|
+
| Type | Marker | Triggered By | Use Case |
|
|
286
|
+
|------|--------|--------------|----------|
|
|
287
|
+
| **USER_INPUT** | `__WORKFLOW_INTERRUPT__` | `collect_input_with_agent`, `follow_up` | Waiting for user input |
|
|
288
|
+
| **ASYNC** | `__ASYNC_INTERRUPT__` | `call_async_function` | Waiting for async operation callback |
|
|
289
|
+
| **OUT_OF_SCOPE** | `__OUT_OF_SCOPE_INTERRUPT__` | `collect_input_with_agent`, `follow_up` | User query unrelated to current task |
|
|
290
|
+
|
|
291
|
+
### Handling Interrupts
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
result = graph.invoke({}, config=config)
|
|
295
|
+
|
|
296
|
+
if "__interrupt__" in result and result["__interrupt__"]:
|
|
297
|
+
interrupt_value = result["__interrupt__"][0].value
|
|
298
|
+
|
|
299
|
+
# Check interrupt type
|
|
300
|
+
if isinstance(interrupt_value, dict):
|
|
301
|
+
if interrupt_value.get("type") == "async":
|
|
302
|
+
# Async interrupt - wait for external callback
|
|
303
|
+
pending_metadata = interrupt_value.get("pending")
|
|
304
|
+
# ... handle async operation ...
|
|
305
|
+
result = graph.invoke(Command(resume=async_result), config=config)
|
|
306
|
+
|
|
307
|
+
elif interrupt_value.get("type") == "out_of_scope":
|
|
308
|
+
# Out-of-scope - user asking unrelated question
|
|
309
|
+
reason = interrupt_value.get("reason")
|
|
310
|
+
user_message = interrupt_value.get("user_message")
|
|
311
|
+
# ... route to different workflow or handle appropriately ...
|
|
312
|
+
else:
|
|
313
|
+
# User input interrupt - prompt is a string
|
|
314
|
+
prompt = interrupt_value
|
|
315
|
+
user_input = input(f"Bot: {prompt}\nYou: ")
|
|
316
|
+
result = graph.invoke(Command(resume=user_input), config=config)
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Out-of-Scope Detection
|
|
320
|
+
|
|
321
|
+
Data collector and follow-up nodes can detect when user queries are unrelated to the current task. This is useful for multi-workflow systems where a supervisor agent needs to route users to different SOPs.
|
|
322
|
+
|
|
323
|
+
**Configuration:**
|
|
324
|
+
```yaml
|
|
325
|
+
agent:
|
|
326
|
+
detect_out_of_scope: true # Enabled by default
|
|
327
|
+
scope_description: "collecting order information for returns" # Optional
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Response format:**
|
|
331
|
+
```
|
|
332
|
+
__OUT_OF_SCOPE_INTERRUPT__|{thread_id}|{workflow_name}|{"reason":"...","user_message":"..."}
|
|
333
|
+
```
|
|
334
|
+
|
|
230
335
|
## Examples
|
|
231
336
|
|
|
232
337
|
See the `examples/` directory for complete workflow examples:
|
|
@@ -370,6 +475,8 @@ Contributions are welcome! Please open an issue or submit a pull request.
|
|
|
370
475
|
- ✅ Database persistence (SqliteSaver, PostgresSaver supported)
|
|
371
476
|
- ✅ Pluggable checkpointer system
|
|
372
477
|
- ✅ Thread ID strategies and examples
|
|
478
|
+
- ✅ Follow-up node for conversational Q&A
|
|
479
|
+
- ✅ Out-of-scope detection for multi-workflow routing
|
|
373
480
|
- Additional action types (webhook, conditional branching, parallel execution)
|
|
374
481
|
- More workflow examples (customer onboarding, support ticketing, approval flows)
|
|
375
482
|
- Workflow testing utilities
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
# Soprano SDK Framework Flow Diagrams
|
|
2
|
+
|
|
3
|
+
## 1. High-Level Architecture
|
|
4
|
+
|
|
5
|
+
```mermaid
|
|
6
|
+
graph TB
|
|
7
|
+
subgraph "Entry Layer"
|
|
8
|
+
WT[WorkflowTool]
|
|
9
|
+
YML[YAML Workflow Definition]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
subgraph "Engine Layer"
|
|
13
|
+
WE[WorkflowEngine]
|
|
14
|
+
FR[FunctionRepository]
|
|
15
|
+
TR[ToolRepository]
|
|
16
|
+
CS[ContextStore]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
subgraph "Graph Layer"
|
|
20
|
+
SG[LangGraph StateGraph]
|
|
21
|
+
CP[Checkpointer]
|
|
22
|
+
WR[WorkflowRouter]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
subgraph "Node Execution Layer"
|
|
26
|
+
NF[NodeFactory]
|
|
27
|
+
CIS[CollectInputStrategy]
|
|
28
|
+
CFS[CallFunctionStrategy]
|
|
29
|
+
AFS[AsyncFunctionStrategy]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
subgraph "Agent Layer"
|
|
33
|
+
AF[AgentFactory]
|
|
34
|
+
LA[LangGraph Adapter]
|
|
35
|
+
CA[CrewAI Adapter]
|
|
36
|
+
AA[Agno Adapter]
|
|
37
|
+
PA[PydanticAI Adapter]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
YML --> WE
|
|
41
|
+
WT --> WE
|
|
42
|
+
WE --> FR
|
|
43
|
+
WE --> TR
|
|
44
|
+
WE --> CS
|
|
45
|
+
WE --> SG
|
|
46
|
+
SG --> CP
|
|
47
|
+
SG --> WR
|
|
48
|
+
WR --> NF
|
|
49
|
+
NF --> CIS
|
|
50
|
+
NF --> CFS
|
|
51
|
+
NF --> AFS
|
|
52
|
+
CIS --> AF
|
|
53
|
+
AF --> LA
|
|
54
|
+
AF --> CA
|
|
55
|
+
AF --> AA
|
|
56
|
+
AF --> PA
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 2. Workflow Execution Flow
|
|
60
|
+
|
|
61
|
+
```mermaid
|
|
62
|
+
flowchart TD
|
|
63
|
+
START([Start]) --> CHECK{Check State}
|
|
64
|
+
|
|
65
|
+
CHECK -->|Fresh Start| INIT[Initialize State<br/>with initial_context]
|
|
66
|
+
CHECK -->|Resuming| FILTER[Filter Already<br/>Collected Fields]
|
|
67
|
+
|
|
68
|
+
INIT --> INVOKE[graph.invoke<br/>initial_context]
|
|
69
|
+
FILTER --> RESUME[graph.invoke<br/>Command resume=value]
|
|
70
|
+
|
|
71
|
+
INVOKE --> RESULT{Check Result}
|
|
72
|
+
RESUME --> RESULT
|
|
73
|
+
|
|
74
|
+
RESULT -->|Has __interrupt__| PARSE[Parse Interrupt Value]
|
|
75
|
+
RESULT -->|No Interrupt| COMPLETE[Get Outcome Message]
|
|
76
|
+
|
|
77
|
+
PARSE --> INT_TYPE{Interrupt Type?}
|
|
78
|
+
|
|
79
|
+
INT_TYPE -->|USER_INPUT| UI_INT["__WORKFLOW_INTERRUPT__|<br/>thread_id|workflow|prompt"]
|
|
80
|
+
INT_TYPE -->|ASYNC| ASYNC_INT["__ASYNC_INTERRUPT__|<br/>thread_id|workflow|metadata"]
|
|
81
|
+
INT_TYPE -->|OUT_OF_SCOPE| OOS_INT["__OUT_OF_SCOPE_INTERRUPT__|<br/>thread_id|workflow|payload"]
|
|
82
|
+
|
|
83
|
+
UI_INT --> RETURN_INT[Return to Caller]
|
|
84
|
+
ASYNC_INT --> RETURN_INT
|
|
85
|
+
OOS_INT --> RETURN_INT
|
|
86
|
+
COMPLETE --> END_SUCCESS([Workflow Complete])
|
|
87
|
+
|
|
88
|
+
RETURN_INT --> WAIT[Wait for Resume]
|
|
89
|
+
WAIT --> CHECK
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 3. Node Execution Flow (CollectInputStrategy)
|
|
93
|
+
|
|
94
|
+
```mermaid
|
|
95
|
+
flowchart TD
|
|
96
|
+
NODE_START([Node Invoked]) --> INIT_STATE[Initialize State]
|
|
97
|
+
INIT_STATE --> CTX[Apply Context Values]
|
|
98
|
+
|
|
99
|
+
CTX --> PRE_POP{Field<br/>Pre-populated?}
|
|
100
|
+
PRE_POP -->|Yes| VALIDATE_PRE[Validate & Route]
|
|
101
|
+
PRE_POP -->|No| MAX_ATT{Max Attempts<br/>Reached?}
|
|
102
|
+
|
|
103
|
+
MAX_ATT -->|Yes| MAX_FAIL[Set max_attempts status<br/>Return error message]
|
|
104
|
+
MAX_ATT -->|No| CREATE_AGENT[Create Agent<br/>with Instructions]
|
|
105
|
+
|
|
106
|
+
CREATE_AGENT --> ENHANCE[Enhance Instructions:<br/>+ Intent Detection<br/>+ Out-of-Scope Detection]
|
|
107
|
+
|
|
108
|
+
ENHANCE --> GEN_PROMPT[Generate Prompt]
|
|
109
|
+
GEN_PROMPT --> INTERRUPT_UI[/"INTERRUPT<br/>(USER_INPUT)"/]
|
|
110
|
+
|
|
111
|
+
INTERRUPT_UI --> GET_INPUT[Get User Input]
|
|
112
|
+
GET_INPUT --> ADD_CONV[Add to Conversation]
|
|
113
|
+
ADD_CONV --> INVOKE_AGENT[Invoke Agent]
|
|
114
|
+
|
|
115
|
+
INVOKE_AGENT --> STRUCT{Structured<br/>Output?}
|
|
116
|
+
|
|
117
|
+
STRUCT -->|Yes| PARSE_STRUCT[Parse JSON Response]
|
|
118
|
+
STRUCT -->|No| CHECK_PATTERN[Check Response Pattern]
|
|
119
|
+
|
|
120
|
+
PARSE_STRUCT --> OOS_CHECK{out_of_scope<br/>set?}
|
|
121
|
+
OOS_CHECK -->|Yes| OOS_HANDLE[/"INTERRUPT<br/>(OUT_OF_SCOPE)"/]
|
|
122
|
+
OOS_CHECK -->|No| INTENT_CHECK{intent_change<br/>set?}
|
|
123
|
+
|
|
124
|
+
INTENT_CHECK -->|Yes| ROLLBACK[Rollback to<br/>Target Node]
|
|
125
|
+
INTENT_CHECK -->|No| BOT_RESP{bot_response<br/>set?}
|
|
126
|
+
|
|
127
|
+
BOT_RESP -->|Yes| SELF_LOOP[Add to conversation<br/>Self-loop]
|
|
128
|
+
BOT_RESP -->|No| MATCH_TRANS[Match Transitions]
|
|
129
|
+
|
|
130
|
+
CHECK_PATTERN --> OOS_PAT{Starts with<br/>OUT_OF_SCOPE:?}
|
|
131
|
+
OOS_PAT -->|Yes| OOS_HANDLE
|
|
132
|
+
OOS_PAT -->|No| INTENT_PAT{Starts with<br/>INTENT_CHANGE:?}
|
|
133
|
+
|
|
134
|
+
INTENT_PAT -->|Yes| ROLLBACK
|
|
135
|
+
INTENT_PAT -->|No| MATCH_TRANS
|
|
136
|
+
|
|
137
|
+
MATCH_TRANS --> TRANS_FOUND{Transition<br/>Matched?}
|
|
138
|
+
TRANS_FOUND -->|Yes| VALIDATE[Validate Input]
|
|
139
|
+
TRANS_FOUND -->|No| SELF_LOOP
|
|
140
|
+
|
|
141
|
+
VALIDATE --> VALID{Valid?}
|
|
142
|
+
VALID -->|Yes| STORE[Store Value<br/>Set Status]
|
|
143
|
+
VALID -->|No| VAL_FAIL[Validation Error<br/>Self-loop]
|
|
144
|
+
|
|
145
|
+
STORE --> ROUTE[Route to Next Step]
|
|
146
|
+
VALIDATE_PRE --> ROUTE
|
|
147
|
+
MAX_FAIL --> NODE_END([Return State])
|
|
148
|
+
ROUTE --> NODE_END
|
|
149
|
+
SELF_LOOP --> NODE_END
|
|
150
|
+
VAL_FAIL --> NODE_END
|
|
151
|
+
ROLLBACK --> NODE_END
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## 4. Async Function Node Flow
|
|
155
|
+
|
|
156
|
+
```mermaid
|
|
157
|
+
flowchart TD
|
|
158
|
+
ASYNC_START([AsyncFunction Node]) --> CHECK_PENDING{Status =<br/>pending?}
|
|
159
|
+
|
|
160
|
+
CHECK_PENDING -->|Yes| HAS_META{Has Pending<br/>Metadata?}
|
|
161
|
+
HAS_META -->|Yes| ASYNC_INT[/"INTERRUPT<br/>(ASYNC)"/]
|
|
162
|
+
HAS_META -->|No| CALL_FUNC[Call Async Function]
|
|
163
|
+
|
|
164
|
+
CHECK_PENDING -->|No| CALL_FUNC
|
|
165
|
+
|
|
166
|
+
CALL_FUNC --> RESULT{Result Status?}
|
|
167
|
+
|
|
168
|
+
RESULT -->|pending| STORE_META[Store Pending Metadata]
|
|
169
|
+
RESULT -->|completed| SYNC_COMPLETE[Handle Sync Completion]
|
|
170
|
+
|
|
171
|
+
STORE_META --> SET_PENDING[Set status = pending]
|
|
172
|
+
SET_PENDING --> ASYNC_INT
|
|
173
|
+
|
|
174
|
+
ASYNC_INT --> WAIT_RESUME[Wait for External Result]
|
|
175
|
+
WAIT_RESUME --> RESUME[Resume with Result]
|
|
176
|
+
RESUME --> CLEAN_META[Clean Pending Metadata]
|
|
177
|
+
CLEAN_META --> ROUTE_RESULT[Route Based on Result]
|
|
178
|
+
|
|
179
|
+
SYNC_COMPLETE --> STORE_RESULT[Store in Output Field]
|
|
180
|
+
STORE_RESULT --> ROUTE_RESULT
|
|
181
|
+
|
|
182
|
+
ROUTE_RESULT --> CHECK_TRANS{Match<br/>Transitions?}
|
|
183
|
+
CHECK_TRANS -->|Yes| NEXT_STEP[Route to Matched Step]
|
|
184
|
+
CHECK_TRANS -->|No| SIMPLE{Has next_step?}
|
|
185
|
+
|
|
186
|
+
SIMPLE -->|Yes| NEXT_SIMPLE[Route to next_step]
|
|
187
|
+
SIMPLE -->|No| ASYNC_END([END])
|
|
188
|
+
|
|
189
|
+
NEXT_STEP --> ASYNC_END
|
|
190
|
+
NEXT_SIMPLE --> ASYNC_END
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## 5. Routing Logic
|
|
194
|
+
|
|
195
|
+
```mermaid
|
|
196
|
+
flowchart TD
|
|
197
|
+
ROUTE_START([Routing Decision]) --> GET_STATUS[Get _status from State]
|
|
198
|
+
GET_STATUS --> PARSE[Parse: step_id_suffix]
|
|
199
|
+
|
|
200
|
+
PARSE --> COLLECT{suffix =<br/>collecting?}
|
|
201
|
+
COLLECT -->|Yes| SELF_LOOP[Self-Loop to Same Node]
|
|
202
|
+
COLLECT -->|No| PENDING{suffix =<br/>pending?}
|
|
203
|
+
|
|
204
|
+
PENDING -->|Yes| SELF_LOOP
|
|
205
|
+
PENDING -->|No| ERROR{suffix =<br/>error?}
|
|
206
|
+
|
|
207
|
+
ERROR -->|Yes| GO_END[Route to END]
|
|
208
|
+
ERROR -->|No| MAX{suffix =<br/>max_attempts?}
|
|
209
|
+
|
|
210
|
+
MAX -->|Yes| GO_END
|
|
211
|
+
MAX -->|No| CHECK_STEP{suffix in<br/>step_map?}
|
|
212
|
+
|
|
213
|
+
CHECK_STEP -->|Yes| ROUTE_STEP[Route to Target Step]
|
|
214
|
+
CHECK_STEP -->|No| CHECK_OUT{suffix in<br/>outcome_map?}
|
|
215
|
+
|
|
216
|
+
CHECK_OUT -->|Yes| GO_END
|
|
217
|
+
CHECK_OUT -->|No| GO_END
|
|
218
|
+
|
|
219
|
+
SELF_LOOP --> ROUTE_END([Return Route Target])
|
|
220
|
+
GO_END --> ROUTE_END
|
|
221
|
+
ROUTE_STEP --> ROUTE_END
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## 6. Intent Change & Rollback Flow
|
|
225
|
+
|
|
226
|
+
```mermaid
|
|
227
|
+
flowchart TD
|
|
228
|
+
INTENT_START([Intent Change Detected]) --> EXTRACT[Extract Target Node]
|
|
229
|
+
EXTRACT --> GET_STRATEGY[Get Rollback Strategy]
|
|
230
|
+
|
|
231
|
+
GET_STRATEGY --> STRAT_TYPE{Strategy Type?}
|
|
232
|
+
|
|
233
|
+
STRAT_TYPE -->|history_based| FIND_SNAP[Find Snapshot<br/>Before Target Node]
|
|
234
|
+
STRAT_TYPE -->|dependency_based| BUILD_DEPS[Build Dependency<br/>Graph]
|
|
235
|
+
|
|
236
|
+
FIND_SNAP --> RESTORE_SNAP[Restore State<br/>from Snapshot]
|
|
237
|
+
BUILD_DEPS --> CLEAR_DEPS[Clear Target Field<br/>& Dependents]
|
|
238
|
+
|
|
239
|
+
RESTORE_SNAP --> RESTORE_CTX[Restore Context Values]
|
|
240
|
+
CLEAR_DEPS --> RESTORE_CTX
|
|
241
|
+
|
|
242
|
+
RESTORE_CTX --> SET_STATUS[Set Status:<br/>step_id_target_node]
|
|
243
|
+
SET_STATUS --> INTENT_END([Return to<br/>Target Node])
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## 7. Agent Creation Flow
|
|
247
|
+
|
|
248
|
+
```mermaid
|
|
249
|
+
flowchart TD
|
|
250
|
+
AGENT_START([Create Agent]) --> GET_MODEL[Get Model Config]
|
|
251
|
+
GET_MODEL --> LOAD_TOOLS[Load Tools from<br/>ToolRepository]
|
|
252
|
+
|
|
253
|
+
LOAD_TOOLS --> GET_COLLECTORS[Get Collector Nodes<br/>for Intent Detection]
|
|
254
|
+
GET_COLLECTORS --> RENDER_INST[Render Instructions<br/>with Jinja2]
|
|
255
|
+
|
|
256
|
+
RENDER_INST --> HAS_COLLECTORS{Has Other<br/>Collectors?}
|
|
257
|
+
HAS_COLLECTORS -->|Yes| ADD_INTENT[Add Intent<br/>Detection Instructions]
|
|
258
|
+
HAS_COLLECTORS -->|No| CHECK_OOS{Out-of-Scope<br/>Enabled?}
|
|
259
|
+
|
|
260
|
+
ADD_INTENT --> CHECK_OOS
|
|
261
|
+
|
|
262
|
+
CHECK_OOS -->|Yes| ADD_OOS[Add Out-of-Scope<br/>Detection Instructions]
|
|
263
|
+
CHECK_OOS -->|No| CREATE_STRUCT{Structured<br/>Output?}
|
|
264
|
+
|
|
265
|
+
ADD_OOS --> CREATE_STRUCT
|
|
266
|
+
|
|
267
|
+
CREATE_STRUCT -->|Yes| BUILD_MODEL[Build Pydantic Model<br/>with bot_response,<br/>intent_change, out_of_scope]
|
|
268
|
+
CREATE_STRUCT -->|No| FACTORY[AgentFactory.create_agent]
|
|
269
|
+
|
|
270
|
+
BUILD_MODEL --> FACTORY
|
|
271
|
+
|
|
272
|
+
FACTORY --> FRAMEWORK{Framework?}
|
|
273
|
+
|
|
274
|
+
FRAMEWORK -->|langgraph| LG[LangGraphAgentAdapter]
|
|
275
|
+
FRAMEWORK -->|crewai| CR[CrewAIAgentAdapter]
|
|
276
|
+
FRAMEWORK -->|agno| AG[AgnoAgentAdapter]
|
|
277
|
+
FRAMEWORK -->|pydantic_ai| PA[PydanticAIAgentAdapter]
|
|
278
|
+
|
|
279
|
+
LG --> AGENT_END([Return Adapter])
|
|
280
|
+
CR --> AGENT_END
|
|
281
|
+
AG --> AGENT_END
|
|
282
|
+
PA --> AGENT_END
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## 8. Complete Workflow Lifecycle
|
|
286
|
+
|
|
287
|
+
```mermaid
|
|
288
|
+
sequenceDiagram
|
|
289
|
+
participant C as Client/Caller
|
|
290
|
+
participant WT as WorkflowTool
|
|
291
|
+
participant G as LangGraph
|
|
292
|
+
participant N as Node Strategy
|
|
293
|
+
participant A as Agent
|
|
294
|
+
participant E as External System
|
|
295
|
+
|
|
296
|
+
C->>WT: execute(thread_id, context)
|
|
297
|
+
WT->>G: invoke(initial_context)
|
|
298
|
+
|
|
299
|
+
loop For Each Node
|
|
300
|
+
G->>N: execute(state)
|
|
301
|
+
|
|
302
|
+
alt CollectInput Node
|
|
303
|
+
N->>N: Generate prompt
|
|
304
|
+
N-->>G: INTERRUPT (USER_INPUT)
|
|
305
|
+
G-->>WT: Result with __interrupt__
|
|
306
|
+
WT-->>C: __WORKFLOW_INTERRUPT__|...|prompt
|
|
307
|
+
C->>WT: resume(thread_id, user_input)
|
|
308
|
+
WT->>G: invoke(Command resume=input)
|
|
309
|
+
G->>N: continue execution
|
|
310
|
+
N->>A: invoke(conversation)
|
|
311
|
+
A-->>N: response
|
|
312
|
+
|
|
313
|
+
alt Out of Scope
|
|
314
|
+
N-->>G: INTERRUPT (OUT_OF_SCOPE)
|
|
315
|
+
G-->>WT: Result with __interrupt__
|
|
316
|
+
WT-->>C: __OUT_OF_SCOPE_INTERRUPT__|...|payload
|
|
317
|
+
else Intent Change
|
|
318
|
+
N->>N: Rollback to target
|
|
319
|
+
N-->>G: Route to target node
|
|
320
|
+
else Normal Flow
|
|
321
|
+
N->>N: Validate & Store
|
|
322
|
+
N-->>G: Route to next
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
else CallFunction Node
|
|
326
|
+
N->>N: Load & Execute Function
|
|
327
|
+
N-->>G: Route based on result
|
|
328
|
+
|
|
329
|
+
else AsyncFunction Node
|
|
330
|
+
N->>E: Call async function
|
|
331
|
+
E-->>N: {status: pending, ...}
|
|
332
|
+
N-->>G: INTERRUPT (ASYNC)
|
|
333
|
+
G-->>WT: Result with __interrupt__
|
|
334
|
+
WT-->>C: __ASYNC_INTERRUPT__|...|metadata
|
|
335
|
+
|
|
336
|
+
Note over C,E: External processing...
|
|
337
|
+
|
|
338
|
+
C->>WT: resume(thread_id, async_result)
|
|
339
|
+
WT->>G: invoke(Command resume=result)
|
|
340
|
+
G->>N: continue with result
|
|
341
|
+
N-->>G: Route based on result
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
G-->>WT: Final state (no interrupt)
|
|
346
|
+
WT->>WT: Get outcome message
|
|
347
|
+
WT-->>C: Workflow complete message
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## 9. State Structure
|
|
351
|
+
|
|
352
|
+
```mermaid
|
|
353
|
+
classDiagram
|
|
354
|
+
class WorkflowState {
|
|
355
|
+
+str _status
|
|
356
|
+
+str _outcome_id
|
|
357
|
+
+List~Dict~ _messages
|
|
358
|
+
+Dict _conversations
|
|
359
|
+
+List _state_history
|
|
360
|
+
+List _node_execution_order
|
|
361
|
+
+Dict _node_field_map
|
|
362
|
+
+Dict _collector_nodes
|
|
363
|
+
+Dict _computed_fields
|
|
364
|
+
+str error
|
|
365
|
+
+Any ...user_defined_fields
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
class Conversation {
|
|
369
|
+
+str field_conversation
|
|
370
|
+
+List~Message~ messages
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
class Message {
|
|
374
|
+
+str role
|
|
375
|
+
+str content
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
class StateSnapshot {
|
|
379
|
+
+str snapshot_id
|
|
380
|
+
+str node_about_to_execute
|
|
381
|
+
+int execution_index
|
|
382
|
+
+str timestamp
|
|
383
|
+
+Dict state
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
WorkflowState "1" *-- "*" Conversation
|
|
387
|
+
Conversation "1" *-- "*" Message
|
|
388
|
+
WorkflowState "1" *-- "*" StateSnapshot
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## 10. Interrupt Types Summary
|
|
392
|
+
|
|
393
|
+
```mermaid
|
|
394
|
+
graph LR
|
|
395
|
+
subgraph "USER_INPUT"
|
|
396
|
+
UI_T[Trigger: CollectInputStrategy]
|
|
397
|
+
UI_D["Data: prompt string"]
|
|
398
|
+
UI_R["Resume: user response"]
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
subgraph "ASYNC"
|
|
402
|
+
A_T[Trigger: AsyncFunctionStrategy]
|
|
403
|
+
A_D["Data: {type, step_id, pending}"]
|
|
404
|
+
A_R["Resume: async result"]
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
subgraph "OUT_OF_SCOPE"
|
|
408
|
+
O_T[Trigger: CollectInputStrategy]
|
|
409
|
+
O_D["Data: {type, step_id,<br/>reason, user_message}"]
|
|
410
|
+
O_R["Action: Escalate to<br/>parent orchestrator"]
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
UI_T --> UI_D --> UI_R
|
|
414
|
+
A_T --> A_D --> A_R
|
|
415
|
+
O_T --> O_D --> O_R
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## Usage
|
|
421
|
+
|
|
422
|
+
These diagrams are written in Mermaid format. To view them:
|
|
423
|
+
|
|
424
|
+
1. **GitHub/GitLab**: Markdown files with Mermaid are rendered automatically
|
|
425
|
+
2. **VS Code**: Install "Markdown Preview Mermaid Support" extension
|
|
426
|
+
3. **Online**: Use [Mermaid Live Editor](https://mermaid.live/)
|
|
427
|
+
4. **Export to PNG/SVG**: Use mermaid-cli (`npm install -g @mermaid-js/mermaid-cli`)
|
|
428
|
+
```bash
|
|
429
|
+
mmdc -i framework_flow_diagrams.md -o output.png
|
|
430
|
+
```
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "soprano-sdk"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.20"
|
|
8
8
|
description = "YAML-driven workflow engine with AI agent integration for building conversational SOPs"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|