nardial 0.3.2__tar.gz → 0.4.1__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.
Files changed (86) hide show
  1. {nardial-0.3.2 → nardial-0.4.1}/PKG-INFO +265 -33
  2. {nardial-0.3.2 → nardial-0.4.1}/README.md +263 -31
  3. {nardial-0.3.2 → nardial-0.4.1}/pyproject.toml +3 -2
  4. nardial-0.4.1/src/nardial/agenda/__init__.py +0 -0
  5. nardial-0.4.1/src/nardial/agenda/items.py +239 -0
  6. nardial-0.4.1/src/nardial/agenda/resolver.py +59 -0
  7. nardial-0.4.1/src/nardial/agenda/session_plan.py +95 -0
  8. nardial-0.4.1/src/nardial/agenda/slot_bounds.py +48 -0
  9. nardial-0.4.1/src/nardial/authoring/__init__.py +1 -0
  10. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/authoring/factory.py +146 -116
  11. nardial-0.4.1/src/nardial/authoring/loader.py +138 -0
  12. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/conversation_agent.py +1 -1
  13. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/conversation_state.py +91 -0
  14. nardial-0.4.1/src/nardial/dialog_registry.py +63 -0
  15. nardial-0.4.1/src/nardial/dialog_types.py +50 -0
  16. nardial-0.4.1/src/nardial/eligibility.py +205 -0
  17. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/interaction_orchestrator.py +2 -0
  18. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/mini_dialogs.py +454 -33
  19. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/moves.py +159 -5
  20. nardial-0.4.1/src/nardial/providers/device/reachy_mini.py +52 -0
  21. nardial-0.4.1/src/nardial/providers/nlu/whisper.py +54 -0
  22. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/screen/pepper_tablet.py +0 -4
  23. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/screen/web/static/screen.js +1 -1
  24. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/tts/elevenlabs.py +1 -1
  25. nardial-0.4.1/src/nardial/session_manager.py +365 -0
  26. {nardial-0.3.2 → nardial-0.4.1}/src/nardial.egg-info/PKG-INFO +265 -33
  27. {nardial-0.3.2 → nardial-0.4.1}/src/nardial.egg-info/SOURCES.txt +20 -2
  28. nardial-0.4.1/tests/test_agenda_items.py +327 -0
  29. nardial-0.4.1/tests/test_agenda_resolver.py +73 -0
  30. {nardial-0.3.2 → nardial-0.4.1}/tests/test_authoring_factory.py +46 -0
  31. nardial-0.4.1/tests/test_conversation_state.py +156 -0
  32. nardial-0.4.1/tests/test_dialog_registry.py +89 -0
  33. nardial-0.4.1/tests/test_dialog_types.py +150 -0
  34. nardial-0.4.1/tests/test_eligibility_rules.py +240 -0
  35. {nardial-0.3.2 → nardial-0.4.1}/tests/test_moves.py +17 -1
  36. nardial-0.4.1/tests/test_session_manager.py +411 -0
  37. nardial-0.4.1/tests/test_session_plan.py +113 -0
  38. nardial-0.4.1/tests/test_slot_bounds.py +58 -0
  39. nardial-0.4.1/tests/test_type_definitions.py +302 -0
  40. nardial-0.3.2/src/nardial/authoring/__init__.py +0 -1
  41. nardial-0.3.2/src/nardial/authoring/loader.py +0 -70
  42. nardial-0.3.2/src/nardial/dialog_logic.py +0 -391
  43. nardial-0.3.2/src/nardial/session_manager.py +0 -219
  44. nardial-0.3.2/tests/test_conversation_state.py +0 -57
  45. {nardial-0.3.2 → nardial-0.4.1}/LICENSE +0 -0
  46. {nardial-0.3.2 → nardial-0.4.1}/setup.cfg +0 -0
  47. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/__init__.py +0 -0
  48. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/authoring/export_html.py +0 -0
  49. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/events/__init__.py +0 -0
  50. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/events/bus.py +0 -0
  51. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/events/types.py +0 -0
  52. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/__init__.py +0 -0
  53. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/device/__init__.py +0 -0
  54. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/device/alphamini.py +0 -0
  55. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/device/desktop.py +0 -0
  56. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/device/nao.py +0 -0
  57. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/device/pepper.py +0 -0
  58. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/llm/__init__.py +0 -0
  59. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/llm/echo.py +0 -0
  60. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/llm/openai_gpt.py +0 -0
  61. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/nlu/__init__.py +0 -0
  62. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/nlu/dialogflow.py +0 -0
  63. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/nlu/written_keyword.py +0 -0
  64. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/screen/__init__.py +0 -0
  65. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/screen/null.py +0 -0
  66. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/screen/sic_adapter.py +0 -0
  67. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/screen/web/static/screen.css +0 -0
  68. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/screen/web/static/socket.io.min.js +0 -0
  69. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/screen/web/templates/index.html +0 -0
  70. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/tts/__init__.py +0 -0
  71. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/tts/cacher.py +0 -0
  72. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/tts/google.py +0 -0
  73. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/tts/naoqi.py +0 -0
  74. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/tts/null.py +0 -0
  75. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/vector_store/__init__.py +0 -0
  76. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/vector_store/null.py +0 -0
  77. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/providers/vector_store/redis_store.py +0 -0
  78. {nardial-0.3.2 → nardial-0.4.1}/src/nardial/user_model.py +0 -0
  79. {nardial-0.3.2 → nardial-0.4.1}/src/nardial.egg-info/dependency_links.txt +0 -0
  80. {nardial-0.3.2 → nardial-0.4.1}/src/nardial.egg-info/requires.txt +0 -0
  81. {nardial-0.3.2 → nardial-0.4.1}/src/nardial.egg-info/top_level.txt +0 -0
  82. {nardial-0.3.2 → nardial-0.4.1}/tests/test_conversation_agent.py +0 -0
  83. {nardial-0.3.2 → nardial-0.4.1}/tests/test_event_bus.py +0 -0
  84. {nardial-0.3.2 → nardial-0.4.1}/tests/test_llm.py +0 -0
  85. {nardial-0.3.2 → nardial-0.4.1}/tests/test_mini_dialogs_extra.py +0 -0
  86. {nardial-0.3.2 → nardial-0.4.1}/tests/test_providers.py +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nardial
3
- Version: 0.3.2
3
+ Version: 0.4.1
4
4
  Summary: Nardial Dialog System
5
5
  Author-email: Mike Ligthart <m.e.u.ligthart@vu.nl>
6
- Maintainer: Clara Fayyad, Landon Brown, Evelyne Prodescu
6
+ Maintainer: Clara Fayyad, Landon Brown, Evelyne Prodescu, Eva Biesot
7
7
  License-Expression: AGPL-3.0-only
8
8
  Requires-Python: >=3.12
9
9
  Description-Content-Type: text/markdown
@@ -42,6 +42,7 @@ It lets you author complete conversations declaratively in JSON, then drive them
42
42
  4. [Defining Dialogs in JSON](#defining-dialogs-in-json)
43
43
  - [Dialog Structure](#dialog-structure)
44
44
  - [Dialog Types](#dialog-types)
45
+ - [Custom dialog types](#custom-dialog-types)
45
46
  - [Move Types](#move-types)
46
47
  - [Key JSON Attributes](#key-json-attributes)
47
48
  5. [Demos / Creating a Session](#demos--creating-a-session)
@@ -157,23 +158,24 @@ NarDialPy is built around a set of provider protocols. Each protocol defines a r
157
158
 
158
159
  ### Available Providers
159
160
 
160
- | Role | Provider | Import path | Requires |
161
- |---|---|---|---|
162
- | **Device** | `DesktopAdapter` | `nardial.providers.device.desktop` | base |
163
- | | `PepperAdapter` | `nardial.providers.device.pepper` | base |
164
- | | `NaoAdapter` | `nardial.providers.device.nao` | base |
165
- | | `AlphaminiAdapter` | `nardial.providers.device.alphamini` | `social-interaction-cloud[alphamini]` |
166
- | **TTS** | `GoogleTTSProvider` | `nardial.providers.tts.google` | `nardial[google-tts]` |
167
- | | `ElevenLabsTTSProvider` | `nardial.providers.tts.elevenlabs` | `nardial[elevenlabs]` |
168
- | | `NaoqiTTSProvider` | `nardial.providers.tts.naoqi` | base (uses device's built-in TTS) |
169
- | | `NullTTSProvider` | `nardial.providers.tts.null` | base (prints to terminal) |
170
- | **NLU** | `DialogflowNLUProvider` | `nardial.providers.nlu.dialogflow` | `nardial[dialogflow]` |
171
- | | `WrittenKeywordNLUProvider` | `nardial.providers.nlu.written_keyword` | base (keyboard input) |
172
- | **LLM** | `OpenAIGPTProvider` | `nardial.providers.llm.openai_gpt` | `nardial[openai]` |
173
- | | `EchoLLMProvider` | `nardial.providers.llm.echo` | base (echoes user input) |
174
- | **Screen** | `ScreenProvider` / `SICScreenAdapter` / `PepperTabletScreenAdapter` | `nardial.providers.screen` | browser display via SIC webserver |
161
+ | Role | Provider | Import path | Requires |
162
+ |---|---|----------------------------------------------|---|
163
+ | **Device** | `DesktopAdapter` | `nardial.providers.device.desktop` | base |
164
+ | | `PepperAdapter` | `nardial.providers.device.pepper` | base |
165
+ | | `NaoAdapter` | `nardial.providers.device.nao` | base |
166
+ | | `AlphaminiAdapter` | `nardial.providers.device.alphamini` | `social-interaction-cloud[alphamini]` |
167
+ | | `ReachyMiniAdapter` | `nardial.providers.reachy_mini` | `social-interaction-cloud[reachy-mini]` |
168
+ | **TTS** | `GoogleTTSProvider` | `nardial.providers.tts.google` | `nardial[google-tts]` |
169
+ | | `ElevenLabsTTSProvider` | `nardial.providers.tts.elevenlabs` | `nardial[elevenlabs]` |
170
+ | | `NaoqiTTSProvider` | `nardial.providers.tts.naoqi` | base (uses device's built-in TTS) |
171
+ | | `NullTTSProvider` | `nardial.providers.tts.null` | base (prints to terminal) |
172
+ | **NLU** | `DialogflowNLUProvider` | `nardial.providers.nlu.dialogflow` | `nardial[dialogflow]` |
173
+ | | `WrittenKeywordNLUProvider` | `nardial.providers.nlu.written_keyword` | base (keyboard input) |
174
+ | **LLM** | `OpenAIGPTProvider` | `nardial.providers.llm.openai_gpt` | `nardial[openai]` |
175
+ | | `EchoLLMProvider` | `nardial.providers.llm.echo` | base (echoes user input) |
176
+ | **Screen** | `ScreenProvider` / `SICScreenAdapter` / `PepperTabletScreenAdapter` | `nardial.providers.screen` | browser display via SIC webserver |
175
177
  | **Vector store** | `RedisVectorStoreProvider` | `nardial.providers.vector_store.redis_store` | base + running Redis |
176
- | | `NullVectorStoreProvider` | `nardial.providers.vector_store.null` | base |
178
+ | | `NullVectorStoreProvider` | `nardial.providers.vector_store.null` | base |
177
179
 
178
180
  ---
179
181
 
@@ -282,6 +284,20 @@ manager = SessionManager(
282
284
  manager.run()
283
285
  ```
284
286
 
287
+ If a previous run crashed mid-session, pass `resume=True` to continue that same incomplete session (one whose `ended_at` is still `None`) instead of starting a new one — dialogs already completed in it are skipped:
288
+
289
+ ```python
290
+ manager = SessionManager(
291
+ agent=agent,
292
+ dialog_file="dialogs/my_dialogs.json",
293
+ participant_id="user_42",
294
+ resume=True,
295
+ )
296
+ manager.run()
297
+ ```
298
+
299
+ See [Developer Flow](docs/DEVELOPER_README.md) for more on session/state handling.
300
+
285
301
  ---
286
302
 
287
303
  ## Defining Dialogs in JSON
@@ -295,10 +311,11 @@ Every dialog has the following shared fields:
295
311
  | Field | Type | Required | Description |
296
312
  |---|---|---|---|
297
313
  | `id` | string | ✅ | Unique identifier referenced in `session_agenda` and `dependencies` |
298
- | `type` | string | ✅ | Dialog type: `"functional"`, `"chitchat"`, `"narrative"`, or `"llm_based"` |
314
+ | `type` | string | ✅ | Dialog type: `"functional"`, `"chitchat"`, `"narrative"`, `"llm_based"`, or a [custom type](#custom-dialog-types) |
299
315
  | `moves` | array | ✅ | Ordered list of move objects the robot will perform |
300
316
  | `dependencies` | array of strings | | Dialog IDs that must have been completed before this dialog may run |
301
317
  | `variable_dependencies` | array | | Variables that must exist in the user model before this dialog may run |
318
+ | `prerequisites` | array | | Actions run just before the dialog's first move: call a registered Python function, or set user-model variables (see [below](#optional-top-level-prerequisites)) |
302
319
 
303
320
  Example:
304
321
  ```json
@@ -338,6 +355,51 @@ Dialogs can define reusable character voices:
338
355
  - Each value must contain a `voice_settings` object.
339
356
  - `voice_settings` is validated at runtime against the active TTS provider.
340
357
 
358
+ #### Optional Top-Level `prerequisites`
359
+
360
+ Dialogs can list actions to run just before their first move, each time the dialog runs:
361
+
362
+ ```json
363
+ {
364
+ "id": "multiplication",
365
+ "type": "chitchat",
366
+ "prerequisites": [
367
+ { "execute": "choose_math_level", "args": [true, false] , "skip_dialog": true},
368
+ { "set_variable": { "math_left": 4, "math_right": 3 } }
369
+ ],
370
+ "moves": [
371
+ { "type": "say", "text": "What is %math_left% times %math_right%?" }
372
+ ]
373
+ }
374
+ ```
375
+
376
+ Each entry does one of two things:
377
+
378
+ | Key | Value | What happens |
379
+ |---|---|---|
380
+ | `execute` | name of a registered function | Calls that Python function. Optional `args`: a list is passed as separate arguments (`[3, 4]` calls `f(3, 4)`), any other value as a single argument, and without `args` the function is called with none. |
381
+ | `set_variable` | object | Merges the object into the user model, so its values can be used in `%variable%` placeholders and later dialogs. |
382
+
383
+ In every prerequisite, `"skip_dialog` can be set to `true` of `false`, based on whether the dialog needs to be skipped if the prerequisite cannot be completed. `"skip_dialog:true` means that the dialog will be skipped if the prerequisite is not completed. If `"skip_dialog: false` is chosen, the dialog will still run, even if the prerequisite could not be completed. The default is `false`.
384
+ Functions are registered in Python under the name used in `execute`, before the session runs:
385
+
386
+ ```python
387
+ from nardial.mini_dialogs import MiniDialog
388
+
389
+ @MiniDialog.register("choose_math_level")
390
+ def choose_math_level(last_correct, needed_help):
391
+ if last_correct and not needed_help:
392
+ level = 2
393
+ else:
394
+ level = 1
395
+ return level
396
+
397
+ ```
398
+
399
+ - Prerequisites run in the order listed, after the dialog has been picked. They can't change whether the dialog itself is eligible, only what happens once it runs.
400
+ - Registered functions must be regular (not `async`) functions. They get only the `args` from the JSON, not the dialog or the user model.
401
+ - If a function isn't registered or raises an error, the error is printed and the remaining prerequisites and the dialog still run.
402
+
341
403
  ---
342
404
 
343
405
  #### 1.`functional`
@@ -465,6 +527,95 @@ A fully LLM-driven dialog where the robot and user engage in a free-form multi-t
465
527
  }
466
528
  ```
467
529
 
530
+ #### Custom dialog types
531
+
532
+ You can define your own dialog type in JSON. A custom type is based on an existing type and changes the rules that decide when its dialogs may run.
533
+
534
+ Type definitions go in their own JSON file (or a directory of them), separate from your dialogs. Mixing them is an error: a type definition in a dialog file, or a dialog in a types file, is reported and skipped. Pass the types file to `SessionManager` as `dialog_types_path`. It is loaded before the dialogs, so dialogs can use the new types:
535
+
536
+ ```python
537
+ manager = SessionManager(
538
+ session_agenda=[...],
539
+ agent=agent,
540
+ dialog_types_path="dialog_types/my_types.json",
541
+ dialog_json_path="dialog_json/my_dialogs.json",
542
+ )
543
+ ```
544
+
545
+ Without `SessionManager`, call `load_dialog_types(path)` (from `nardial.authoring`) before loading the dialogs. Within the types file(s), the order of definitions doesn't matter, even when one type extends another.
546
+
547
+ A type definition looks like this:
548
+
549
+ ```json
550
+ {
551
+ "define_type": "repeatable_chitchat",
552
+ "extends": "chitchat",
553
+ "description": "Chitchat that may come back in later sessions, but runs at most once per session.",
554
+ "remove_rules": ["exclude_if_seen"],
555
+ "add_rules": [{ "rule": "exclude_if_seen", "scope": "session" }]
556
+ }
557
+ ```
558
+
559
+ | Field | Type | Required | Description |
560
+ |---|---|---|---|
561
+ | `define_type` | string | ✅ | Name of the new type, used as `type` in dialogs |
562
+ | `extends` | string | ✅ | Type it is based on: a built-in type or another custom type |
563
+ | `description` | string | | Free-text description |
564
+ | `remove_rules` | array of strings | | Names of the base type's rules to drop |
565
+ | `add_rules` | array | | Rules to add: a rule name, or an object with `"rule"` plus that rule's settings |
566
+
567
+ Dialogs of the new type use the same fields as dialogs of the type it extends:
568
+
569
+ ```json
570
+ {
571
+ "id": "music_lately",
572
+ "type": "repeatable_chitchat",
573
+ "topics": ["music"],
574
+ "moves": [{ "type": "say", "text": "What music have you been listening to lately?" }]
575
+ }
576
+ ```
577
+
578
+ They are also picked by the same agenda slots. For example, a `chitchat_slot` can choose a `repeatable_chitchat` dialog.
579
+
580
+ **Built-in types and their rules:**
581
+
582
+ | Type | Rules |
583
+ |---|---|
584
+ | `functional` | `dependency_met` |
585
+ | `narrative` | `exclude_if_seen`, `dependency_met`, `variable_dependency_met`, `narrative_ordering` |
586
+ | `chitchat` | `exclude_if_seen`, `dependency_met`, `variable_dependency_met` |
587
+ | `llm_based` | `exclude_if_seen`, `dependency_met`, `variable_dependency_met` |
588
+
589
+ **Available rules.** A dialog may run only when all of its type's rules allow it.
590
+
591
+ | Rule | Settings | Allows the dialog to run when |
592
+ |---|---|---|
593
+ | `exclude_if_seen` | `scope`: `"participant"` (default) or `"session"` | it hasn't been completed before (by this participant, or in this session) |
594
+ | `dependency_met` | | every dialog in its `dependencies` has been completed |
595
+ | `variable_dependency_met` | | every required variable in its `variable_dependencies` is set in the user model |
596
+ | `narrative_ordering` | | every earlier `position` in its `thread` has been completed |
597
+
598
+ **Your own rules.** Any other rule is written in Python and registered under a name, which `add_rules` can then use. Settings from the JSON are passed to the constructor:
599
+
600
+ ```python
601
+ from nardial.eligibility import EligibilityRule, register_rule
602
+
603
+ @register_rule("from_session")
604
+ class FromSessionRule(EligibilityRule):
605
+ def __init__(self, min):
606
+ self.min = min
607
+
608
+ def is_eligible(self, dialog, context):
609
+ # context.session_index: session number starting at 1, or None when unknown
610
+ return context.session_index is not None and context.session_index >= self.min
611
+ ```
612
+
613
+ ```json
614
+ { "define_type": "returning_chitchat", "extends": "chitchat", "add_rules": [{ "rule": "from_session", "min": 2 }] }
615
+ ```
616
+
617
+ Import the module that registers the rule before loading your dialogs. A rule can read `context.user_model`, `context.completed_ids`, `context.session_completed_ids`, `context.session_index` and `context.registry`. See `docs/DEVELOPER_README.md` for more, including defining types directly in Python.
618
+
468
619
  ---
469
620
 
470
621
  ### Move Types
@@ -489,6 +640,22 @@ Speaks a piece of text. Variable placeholders (`%variable_name%`) are replaced a
489
640
 
490
641
  ---
491
642
 
643
+ #### `say_options`
644
+
645
+ Speaks one randomly chosen response from a predefined list.
646
+
647
+ | Field | Type | Required | Description |
648
+ |---|---|---|---|
649
+ | `type` | string | ✅ | `"say_options"` |
650
+ | `options` | array of strings | ✅ | Candidate utterances to choose from at runtime |
651
+ | `character` | string | | Optional character name from top-level `characters` |
652
+
653
+ ```json
654
+ { "type": "say_options", "options": ["Hi!", "Hello!", "Hey there!"] }
655
+ ```
656
+
657
+ ---
658
+
492
659
  #### `ask_open`
493
660
 
494
661
  Asks a free-text question and listens for any spoken reply. The answer can be stored in a variable and used to drive branching.
@@ -502,6 +669,7 @@ Asks a free-text question and listens for any spoken reply. The answer can be st
502
669
  | `default_outcome` | string | | Outcome label when no answer or no match is found |
503
670
  | `add_interest_from_answer` | boolean | | If `true`, adds the answer to the user's topics of interest |
504
671
  | `llm_followup` | string | | System prompt for an LLM-generated follow-up sentence after the user replies |
672
+ | `llm_extract_entity` | boolean | | If `true`, the LLM extracts the key entity from the answer (e.g. `"dog"` from `"my favorite animal is a dog"`) before it is stored; unrelated answers store nothing. Default `false` |
505
673
 
506
674
  ```json
507
675
  {
@@ -601,11 +769,12 @@ Starts a multi-turn LLM-driven exchange *within* an otherwise scripted dialog. U
601
769
 
602
770
  Selects and executes a list of sub-moves based on the current outcome or the value of a user model variable.
603
771
 
604
- | Field | Type | Required | Description |
605
- |---|---|---|---|
606
- | `type` | string | ✅ | `"branch"` |
607
- | `on` | string | ✅ | `"outcome"` to branch on the last question's result, or a variable name to branch on its stored value |
608
- | `cases` | object | ✅ | Maps condition values to arrays of sub-moves |
772
+ | Field | Type | Required | Description |
773
+ |-------------|---|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
774
+ | `type` | string | ✅ | `"branch"` |
775
+ | `on` | string | ✅ | `"outcome"` to branch on the last question's result, `"variables"` to branch based on whether to variabels in the user model have the same value, or a variable name to branch on its stored value |
776
+ | `cases` | object | ✅ | Maps condition values to arrays of sub-moves |
777
+ | `variables` | list | | In case of branching on `"variables"`, a list of the variables that need to be compared |
609
778
 
610
779
  ```json
611
780
  {
@@ -635,6 +804,20 @@ Branching on a stored variable (e.g. to react to an answer from an earlier dialo
635
804
  }
636
805
  ```
637
806
 
807
+ Branching on comparison of two stored variables (e.g. to check whether a given answer is correct). Chooses branch "true" if the values of the variables are the same, branch "false" if not.
808
+
809
+ ```json
810
+ {
811
+ "type": "branch",
812
+ "on": "variables",
813
+ "variables": ["given_answer", "correct_answer"],
814
+ "cases": {
815
+ "true": [{ "type": "say", "text": "That's correct!"}],
816
+ "false": [{ "type": "say", "text": "That's incorrect. Let's try again."}]
817
+ }
818
+ }
819
+ ```
820
+
638
821
  ---
639
822
 
640
823
  #### `play`
@@ -701,14 +884,15 @@ Pause execution for a fixed duration before proceeding to the next move. Useful
701
884
 
702
885
  Suspend dialog execution until a matching web input event arrives or until a timeout elapses. The web UI can present buttons or a short input and emit `web_input` events which this move listens for.
703
886
 
704
- | Field | Type | Required | Description |
705
- |---|---|---|---|
706
- | `type` | string | ✅ | `"wait_for_web_input"` |
707
- | `prompt` | string | | Hint text shown in the web UI (not spoken) |
887
+ | Field | Type | Required | Description |
888
+ |---|---|---|---------------------------------------------------------------------------------|
889
+ | `type` | string | ✅ | `"wait_for_web_input"` |
890
+ | `prompt` | string | | Hint text shown in the web UI (not spoken) |
708
891
  | `options` | array of strings | | Accepted `value` strings from the web event (if omitted, any value is accepted) |
709
- | `timeout` | number | | Seconds to wait before falling back (omit for indefinite wait) |
710
- | `outcomes` | object | | Maps option value → outcome label for branching |
711
- | `default_outcome` | string | | Outcome used on timeout or when no event matches |
892
+ | `timeout` | number | | Seconds to wait before falling back (omit for indefinite wait) |
893
+ | `outcomes` | object | | Maps option value → outcome label for branching |
894
+ | `default_outcome` | string | | Outcome used on timeout or when no event matches |
895
+ | `set_variable` | string | | Stores the user's choice in the user model |
712
896
 
713
897
  ```json
714
898
  {
@@ -717,7 +901,8 @@ Suspend dialog execution until a matching web input event arrives or until a tim
717
901
  "options": ["smile","thumbs","surprised"],
718
902
  "timeout": 15,
719
903
  "outcomes": {"smile": "picked_smile"},
720
- "default_outcome": "no_choice"
904
+ "default_outcome": "no_choice",
905
+ "set_variable": "sticker"
721
906
  }
722
907
  ```
723
908
 
@@ -797,6 +982,49 @@ Clear the display (show a blank/black screen). No parameters — the next displa
797
982
 
798
983
  ---
799
984
 
985
+ #### `keyboard_input`
986
+
987
+ Display keyboard input on screen. Suspend dialog execution until keyboard input is given or until a timeout elapses.
988
+
989
+ | Field | Type | Required | Description |
990
+ |---|---|---|----------------------------------------------------------------|
991
+ | `type` | string | ✅ | `"keyboard_input"` |
992
+ | `prompt` | string | | Hint text shown in the web UI (not spoken) |
993
+ | `timeout` | number | | Seconds to wait before falling back (omit for indefinite wait) |
994
+ | `outcomes` | object | | Maps option value → outcome label for branching |
995
+ | `default_outcome` | string | | Outcome used on timeout or when no event matches |
996
+ | `set_variable` | string | | Stores the user's choice in the user model |
997
+
998
+ ```json
999
+ {
1000
+ "type": "keyboard_input",
1001
+ "prompt": "How old are you?",
1002
+ "timeout": 15,
1003
+ "default_outcome": "no_age_given",
1004
+ "set_variable": "age"
1005
+ }
1006
+ ```
1007
+
1008
+ ---
1009
+
1010
+ #### `go_to_dialog`
1011
+
1012
+ Move to a specified new minidialog.
1013
+
1014
+ | Field | Type | Required | Description |
1015
+ |-------------------|---|----------|----------------------------------------------------------------|
1016
+ | `type` | string | ✅ | `"go_to_dialog"` |
1017
+ | `dialog_id` | string |✅ | ID of the dialog to go to |
1018
+
1019
+ ```json
1020
+ {
1021
+ "type": "go_to_dialog",
1022
+ "dialog_id": "favorite_animal"
1023
+ }
1024
+ ```
1025
+
1026
+ ---
1027
+
800
1028
  ### Key JSON Attributes
801
1029
 
802
1030
  | Attribute | Where used | Description |
@@ -808,11 +1036,13 @@ Clear the display (show a blank/black screen). No parameters — the next displa
808
1036
 
809
1037
  All you need is a minimal Python script that wires up the device, loads the dialog JSON, and runs the session. You can follow the included demos to get started quickly.
810
1038
 
811
- Two ready-to-run demos are included in the `examples/` directory:
1039
+ Ready-to-run demos are included in the `examples/` directory:
812
1040
  * Demo 1 — General Conversation (`demo_general_conversation.py`): A simple four-step conversation using a mix of narrative and functional dialogs
813
1041
  * Demo 2 — Structured Conversation (`demo_structured_conversation.py`): A more complete example that demonstrates all dialog types and move types, including `ask_llm`, `play`, `motion_sequence`, and `animation`
814
1042
  * Demo 3 — Screen Display (`demo_screen_provider.py`): Shows the browser-based screen UI with transcripts, images, iframes, HTML snippets, buttons, and text input
815
1043
  * Demo 4 — Pepper Tablet (`demo_pepper_tablet.py`): Uses the same screen UI on Pepper's tablet through the SIC webserver
1044
+ * Demo 5 — Agenda System (`demo_agenda_system.py`): Uses every agenda-item type (`narrative_slot`, `chitchat_slot`, `functional_slot`, `llm_dialog_ref`, plain dialog ids) so `SessionManager` picks the next dialog dynamically instead of following a fixed list — see the [Agenda System Guide](docs/AGENDA_SYSTEM_GUIDE.md) for a plain-language walkthrough
1045
+ * Demo 6 — Custom Dialog Type (`demo_custom_dialog_type.py`): Writes a custom eligibility rule in Python and defines two new dialog types in JSON (see [Custom dialog types](#custom-dialog-types)): a chitchat that comes back every session, and one that only runs for participants who said they have a pet
816
1046
 
817
1047
  You can find additional demos in the [SIC Applications repository](https://github.com/Social-AI-VU/sic_applications/tree/main/demos/nardial)
818
1048
 
@@ -820,6 +1050,8 @@ You can find additional demos in the [SIC Applications repository](https://githu
820
1050
 
821
1051
  ## Development
822
1052
 
1053
+ For an architecture-level explanation of how JSON dialogs become running conversations, see [Developer Flow](docs/DEVELOPER_README.md). If you're building an application and want to understand dynamic session agendas rather than runtime internals, see the [Agenda System Guide](docs/AGENDA_SYSTEM_GUIDE.md) instead.
1054
+
823
1055
  Run tests from the repository root:
824
1056
 
825
1057
  ```bash