soprano-sdk 0.2.19__py3-none-any.whl → 0.2.21__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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soprano-sdk
3
- Version: 0.2.19
3
+ Version: 0.2.21
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,11 @@ 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
57
+ - **Outcome Humanization**: LLM-powered transformation of outcome messages into natural, context-aware responses
58
+ - **Per-Turn Localization**: Dynamic language and script switching for multi-language support
54
59
 
55
60
  ## Installation
56
61
 
@@ -268,6 +273,247 @@ Calls a Python function with workflow state.
268
273
  next: failure_step
269
274
  ```
270
275
 
276
+ ### call_async_function
277
+
278
+ Calls an async function that may return a pending status, triggering an interrupt until the async operation completes.
279
+
280
+ ```yaml
281
+ - id: verify_payment
282
+ action: call_async_function
283
+ function: "payments.start_verification"
284
+ output: verification_result
285
+ transitions:
286
+ - condition: "verified"
287
+ next: payment_approved
288
+ - condition: "failed"
289
+ next: payment_rejected
290
+ ```
291
+
292
+ ### follow_up
293
+
294
+ 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.
295
+
296
+ ```yaml
297
+ - id: handle_questions
298
+ action: follow_up
299
+ next: final_confirmation # Where to go when user says "done"
300
+ closure_patterns: # Optional: customize closure detection
301
+ - "ok"
302
+ - "thank you"
303
+ - "done"
304
+ agent:
305
+ name: "FollowUpAssistant"
306
+ model: "gpt-4o-mini"
307
+ description: "Answering questions about the order"
308
+ instructions: |
309
+ Help the user with any questions about their order.
310
+ Be concise and helpful.
311
+ detect_out_of_scope: true # Signal when user asks unrelated questions
312
+ transitions: # Optional: route based on patterns
313
+ - pattern: "ROUTE_TO_PAYMENT:"
314
+ next: payment_step
315
+ ```
316
+
317
+ **Key features:**
318
+ - **User initiates**: No initial prompt - waits for user to ask a question
319
+ - **Full state context**: Agent sees all collected workflow data
320
+ - **Closure detection**: Detects "ok", "thanks", "done" → proceeds to next step
321
+ - **Intent change**: Routes to collector nodes when user wants to change data
322
+ - **Out-of-scope**: Signals to parent orchestrator for unrelated queries
323
+
324
+ ## Interrupt Types
325
+
326
+ The workflow engine uses three interrupt types to pause execution and communicate with the caller:
327
+
328
+ | Type | Marker | Triggered By | Use Case |
329
+ |------|--------|--------------|----------|
330
+ | **USER_INPUT** | `__WORKFLOW_INTERRUPT__` | `collect_input_with_agent`, `follow_up` | Waiting for user input |
331
+ | **ASYNC** | `__ASYNC_INTERRUPT__` | `call_async_function` | Waiting for async operation callback |
332
+ | **OUT_OF_SCOPE** | `__OUT_OF_SCOPE_INTERRUPT__` | `collect_input_with_agent`, `follow_up` | User query unrelated to current task |
333
+
334
+ ### Handling Interrupts
335
+
336
+ ```python
337
+ result = graph.invoke({}, config=config)
338
+
339
+ if "__interrupt__" in result and result["__interrupt__"]:
340
+ interrupt_value = result["__interrupt__"][0].value
341
+
342
+ # Check interrupt type
343
+ if isinstance(interrupt_value, dict):
344
+ if interrupt_value.get("type") == "async":
345
+ # Async interrupt - wait for external callback
346
+ pending_metadata = interrupt_value.get("pending")
347
+ # ... handle async operation ...
348
+ result = graph.invoke(Command(resume=async_result), config=config)
349
+
350
+ elif interrupt_value.get("type") == "out_of_scope":
351
+ # Out-of-scope - user asking unrelated question
352
+ reason = interrupt_value.get("reason")
353
+ user_message = interrupt_value.get("user_message")
354
+ # ... route to different workflow or handle appropriately ...
355
+ else:
356
+ # User input interrupt - prompt is a string
357
+ prompt = interrupt_value
358
+ user_input = input(f"Bot: {prompt}\nYou: ")
359
+ result = graph.invoke(Command(resume=user_input), config=config)
360
+ ```
361
+
362
+ ### Out-of-Scope Detection
363
+
364
+ 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.
365
+
366
+ **Configuration:**
367
+ ```yaml
368
+ agent:
369
+ detect_out_of_scope: true # Disabled by default, set to true to enable
370
+ scope_description: "collecting order information for returns" # Optional
371
+ ```
372
+
373
+ **Response format:**
374
+ ```
375
+ __OUT_OF_SCOPE_INTERRUPT__|{thread_id}|{workflow_name}|{"reason":"...","user_message":"..."}
376
+ ```
377
+
378
+ ## Outcome Humanization
379
+
380
+ Outcome messages can be automatically humanized using an LLM to transform template-based messages into natural, context-aware responses. This feature uses the full conversation history to generate responses that match the tone and context of the interaction.
381
+
382
+ ### How It Works
383
+
384
+ 1. **Template rendering**: The outcome message template is first rendered with state values (e.g., `{{order_id}}` → `1234`)
385
+ 2. **LLM humanization**: The rendered message is passed to an LLM along with the conversation history
386
+ 3. **Natural response**: The LLM generates a warm, conversational response while preserving all factual details
387
+
388
+ ### Configuration
389
+
390
+ Humanization is **enabled by default**. Configure it at the workflow level:
391
+
392
+ ```yaml
393
+ name: "Return Processing Workflow"
394
+ version: "1.0"
395
+
396
+ # Humanization configuration (optional - enabled by default)
397
+ humanization_agent:
398
+ model: "gpt-4o" # Override model for humanization (optional)
399
+ base_url: "https://custom-api.com/v1" # Override base URL (optional)
400
+ instructions: | # Custom instructions (optional)
401
+ You are a friendly customer service representative.
402
+ Rewrite the message to be warm and empathetic.
403
+ Always thank the customer for their patience.
404
+
405
+ outcomes:
406
+ - id: success
407
+ type: success
408
+ message: "Return approved for order {{order_id}}. Reason: {{return_reason}}."
409
+
410
+ - id: technical_error
411
+ type: failure
412
+ humanize: false # Disable humanization for this specific outcome
413
+ message: "Error code: {{error_code}}. Contact support."
414
+ ```
415
+
416
+ ### Example Transformation
417
+
418
+ | Template Message | Humanized Response |
419
+ |-----------------|-------------------|
420
+ | `"Return approved for order 1234. Reason: damaged item."` | `"Great news! I've approved the return for your order #1234. I completely understand about the damaged item - that's so frustrating. You'll receive an email shortly with return instructions. Is there anything else I can help you with?"` |
421
+
422
+ ### Disabling Humanization
423
+
424
+ **Globally** (for entire workflow):
425
+ ```yaml
426
+ humanization_agent:
427
+ enabled: false
428
+ ```
429
+
430
+ **Per-outcome**:
431
+ ```yaml
432
+ outcomes:
433
+ - id: error_code
434
+ type: failure
435
+ humanize: false # Keep exact message for debugging/logging
436
+ message: "Error: {{error_code}}"
437
+ ```
438
+
439
+ ### Model Configuration
440
+
441
+ The humanization agent inherits the workflow's runtime `model_config`. You can override specific settings:
442
+
443
+ ```python
444
+ config = {
445
+ "model_config": {
446
+ "model_name": "gpt-4o-mini", # Base model for all agents
447
+ "api_key": os.getenv("OPENAI_API_KEY"),
448
+ }
449
+ }
450
+
451
+ # In YAML, humanization_agent.model overrides model_name for humanization only
452
+ ```
453
+
454
+ ## Per-Turn Localization
455
+
456
+ The framework supports per-turn localization, allowing dynamic language and script switching during workflow execution. Each call to `execute()` can specify a different target language/script.
457
+
458
+ ### How It Works
459
+
460
+ 1. **Per-turn parameters**: Pass `target_language` and `target_script` to `execute()`
461
+ 2. **Instruction injection**: Localization instructions are prepended to agent system prompts
462
+ 3. **No extra LLM calls**: The same agent that generates the response handles localization
463
+
464
+ ### Usage
465
+
466
+ **Per-turn language switching:**
467
+ ```python
468
+ from soprano_sdk import WorkflowTool
469
+
470
+ tool = WorkflowTool(
471
+ yaml_path="return_workflow.yaml",
472
+ name="return_processor",
473
+ description="Process returns",
474
+ checkpointer=checkpointer,
475
+ config=config
476
+ )
477
+
478
+ # Turn 1: English (no localization)
479
+ result = tool.execute(thread_id="123", user_message="hi")
480
+
481
+ # Turn 2: Switch to Tamil
482
+ result = tool.execute(
483
+ thread_id="123",
484
+ user_message="my order id is 1234",
485
+ target_language="Tamil",
486
+ target_script="Tamil"
487
+ )
488
+
489
+ # Turn 3: Back to English (no localization params)
490
+ result = tool.execute(thread_id="123", user_message="yes")
491
+ ```
492
+
493
+ ### YAML Defaults (Optional)
494
+
495
+ You can set default localization in the workflow YAML. These are used when `target_language`/`target_script` are not passed to `execute()`:
496
+
497
+ ```yaml
498
+ name: "Return Workflow"
499
+ version: "1.0"
500
+
501
+ localization:
502
+ language: "Tamil"
503
+ script: "Tamil"
504
+ instructions: | # Optional: custom instructions
505
+ Use formal Tamil suitable for customer service.
506
+ Always be polite and respectful.
507
+
508
+ # ... rest of workflow
509
+ ```
510
+
511
+ ### Key Points
512
+
513
+ - **Localization affects**: Data collector prompts, follow-up responses, and humanized outcome messages
514
+ - **Outcome messages require humanization**: If `humanize: false`, outcome messages stay in English (template output)
515
+ - **Per-turn override**: Runtime parameters always override YAML defaults
516
+
271
517
  ## Examples
272
518
 
273
519
  See the `examples/` directory for complete workflow examples:
@@ -411,6 +657,10 @@ Contributions are welcome! Please open an issue or submit a pull request.
411
657
  - ✅ Database persistence (SqliteSaver, PostgresSaver supported)
412
658
  - ✅ Pluggable checkpointer system
413
659
  - ✅ Thread ID strategies and examples
660
+ - ✅ Follow-up node for conversational Q&A
661
+ - ✅ Out-of-scope detection for multi-workflow routing
662
+ - ✅ Outcome humanization with LLM
663
+ - ✅ Per-turn localization for multi-language support
414
664
  - Additional action types (webhook, conditional branching, parallel execution)
415
665
  - More workflow examples (customer onboarding, support ticketing, approval flows)
416
666
  - Workflow testing utilities
@@ -1,25 +1,26 @@
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=dmJ0OZ7Bj3rvjBQvLzgWlYRFVtNJOyMO2jLqaS13cAc,10971
3
+ soprano_sdk/tools.py,sha256=D2EGT513OGjAA5h0RSy79OA34jgM30S8d49AG4JdL4M,13653
4
4
  soprano_sdk/agents/__init__.py,sha256=Yzbtv6iP_ABRgZo0IUjy9vDofEvLFbOjuABw758176A,636
5
5
  soprano_sdk/agents/adaptor.py,sha256=5y9e2F_ZILOPrkunvv0GhjVdniAnOOTaD4j3Ig-xd3c,5041
6
6
  soprano_sdk/agents/factory.py,sha256=CvXhpvtjf_Hb4Ce8WKAa0FzyY5Jm6lssvIIfSXu7jPY,9788
7
- soprano_sdk/agents/structured_output.py,sha256=7DSVzfMPsZAqBwI3v6XL15qG5Gh4jJ-qddcVPaa3gdc,3326
7
+ soprano_sdk/agents/structured_output.py,sha256=VnSRmgarbgsozQSdCr9fVSjclwi04GJ6Nz5lut9qiUk,3593
8
8
  soprano_sdk/authenticators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  soprano_sdk/authenticators/mfa.py,sha256=xnur1lxbx4r_pUaNPD8vtQiNccQTjbPnqgi_vqdRZaQ,7336
10
10
  soprano_sdk/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- soprano_sdk/core/constants.py,sha256=UPXlRbF7gsOUNOV0Lm0jvgFfgZX7JrsV6n9I5csMfns,3508
12
- soprano_sdk/core/engine.py,sha256=HKYoqwDm541pWSWwEKHxLlL3PX90Ux_5l_-HqihgL-g,12245
11
+ soprano_sdk/core/constants.py,sha256=gByXYM7Lz7I7Sm2-tnn8alpaxuyuU_jfBmCpkigJHy4,4973
12
+ soprano_sdk/core/engine.py,sha256=VvqV4N6mDKHmsw8NBXkegyWdwWOivjQVe1R2nv3zBTw,19704
13
13
  soprano_sdk/core/rollback_strategies.py,sha256=NjDTtBCZlqyDql5PSwI9SMDLK7_BNlTxbW_cq_5gV0g,7783
14
- soprano_sdk/core/state.py,sha256=k8ojLfWgjES3p9XWMeGU5s4UK-Xa5T8mS4VtZzTrcDw,2961
14
+ soprano_sdk/core/state.py,sha256=BZWL5W9zecjNe-IFg_4zv0lSLPqRST67mN01Zi8H2mk,2976
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
- soprano_sdk/nodes/base.py,sha256=idFyOGGPnjsASYnrOF_NIh7eFcSuJqw61EoVN_WCTaU,2360
17
+ soprano_sdk/nodes/base.py,sha256=gjgAe5dqNk5ItOETLhwF6is7l9BJtrAouLXFOiDKA3E,2601
18
18
  soprano_sdk/nodes/call_function.py,sha256=afYBmj5Aditbkvb_7gD3CsXBEEUohcsC1_cdHfcOunE,5847
19
- soprano_sdk/nodes/collect_input.py,sha256=PySlghXOWDl6AYKgimY_7BnVFN7odYG656aBK_z4ACE,24617
20
- soprano_sdk/nodes/factory.py,sha256=IbBzT4FKBnYw5PuSo7uDONV3HSFtoyqjBQQtXtUY2IY,1756
19
+ soprano_sdk/nodes/collect_input.py,sha256=h4fLJPvLPRz2k6l-KfyeJLOwWAr3ALjtzyRWmJgAyiQ,28021
20
+ soprano_sdk/nodes/factory.py,sha256=i37whA0ugMSwbmWk6H798Suhq6J108a5VenuFqxCVu8,1863
21
+ soprano_sdk/nodes/follow_up.py,sha256=iVCKCCuqgU-kkLBi4iYBa_bB7j5QI8U2KNGHpgYPmRA,14165
21
22
  soprano_sdk/routing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- soprano_sdk/routing/router.py,sha256=Z218r4BMbmlL9282ombutAoKsIs1WHZ2d5YHnbCeet8,3698
23
+ soprano_sdk/routing/router.py,sha256=ECZn7NIVrVQ5l5eZr8sfVNT0MShixnQEsqh4YO6XwRs,3882
23
24
  soprano_sdk/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
25
  soprano_sdk/utils/function.py,sha256=yqkY4MlHOenv-Q3NciiovK1lamyrGQljpy6Q41wviy8,1216
25
26
  soprano_sdk/utils/logger.py,sha256=hMYaNHt5syGOXRkglTUKzkgfSbWerix_pHQntcYyep8,157
@@ -27,9 +28,9 @@ soprano_sdk/utils/template.py,sha256=MG_B9TMx1ShpnSGo7s7TO-VfQzuFByuRNhJTvZ668kM
27
28
  soprano_sdk/utils/tool.py,sha256=hWN826HIKmLdswLCTURLH8hWlb2WU0MB8nIUErbpB-8,1877
28
29
  soprano_sdk/utils/tracing.py,sha256=gSHeBDLe-MbAZ9rkzpCoGFveeMdR9KLaA6tteB0IWjk,1991
29
30
  soprano_sdk/validation/__init__.py,sha256=ImChmO86jYHU90xzTttto2-LmOUOmvY_ibOQaLRz5BA,262
30
- soprano_sdk/validation/schema.py,sha256=eRGXXMDlJyaH0XYMOXY1azvJlJwOdbHFrHjB1yuGROg,15434
31
+ soprano_sdk/validation/schema.py,sha256=NGnxP3TKcB-cylVcdOkxYhTPBbHnbmKTG_EamfA1VdY,17682
31
32
  soprano_sdk/validation/validator.py,sha256=f-e2MMRL70asOIXr_0Fsd5CgGKVRiQp7AaYsHA45Km0,8792
32
- soprano_sdk-0.2.19.dist-info/METADATA,sha256=TZNLyUlHQBsw6aXwsmhz2-V5ThgE1cPNuPyhL1zeRDM,11374
33
- soprano_sdk-0.2.19.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
34
- soprano_sdk-0.2.19.dist-info/licenses/LICENSE,sha256=A1aBauSjPNtVehOXJe3WuvdU2xvM9H8XmigFMm6665s,1073
35
- soprano_sdk-0.2.19.dist-info/RECORD,,
33
+ soprano_sdk-0.2.21.dist-info/METADATA,sha256=YalPq6Je3QAYK_JW5cJrDnVkF9iBgqYcctM93OZXMaE,20410
34
+ soprano_sdk-0.2.21.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
35
+ soprano_sdk-0.2.21.dist-info/licenses/LICENSE,sha256=A1aBauSjPNtVehOXJe3WuvdU2xvM9H8XmigFMm6665s,1073
36
+ soprano_sdk-0.2.21.dist-info/RECORD,,