nardial 0.3.3__tar.gz → 0.4.2__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.
- {nardial-0.3.3 → nardial-0.4.2}/PKG-INFO +300 -33
- {nardial-0.3.3 → nardial-0.4.2}/README.md +297 -31
- {nardial-0.3.3 → nardial-0.4.2}/pyproject.toml +4 -2
- nardial-0.4.2/src/nardial/agenda/__init__.py +0 -0
- nardial-0.4.2/src/nardial/agenda/items.py +239 -0
- nardial-0.4.2/src/nardial/agenda/resolver.py +59 -0
- nardial-0.4.2/src/nardial/agenda/session_plan.py +95 -0
- nardial-0.4.2/src/nardial/agenda/slot_bounds.py +48 -0
- nardial-0.4.2/src/nardial/authoring/__init__.py +1 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/authoring/factory.py +146 -116
- nardial-0.4.2/src/nardial/authoring/loader.py +138 -0
- nardial-0.4.2/src/nardial/controls.py +184 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/conversation_agent.py +16 -6
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/conversation_state.py +91 -0
- nardial-0.4.2/src/nardial/dialog_registry.py +63 -0
- nardial-0.4.2/src/nardial/dialog_types.py +50 -0
- nardial-0.4.2/src/nardial/eligibility.py +205 -0
- nardial-0.4.2/src/nardial/events/sources.py +68 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/interaction_orchestrator.py +89 -4
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/mini_dialogs.py +468 -35
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/moves.py +169 -7
- nardial-0.4.2/src/nardial/providers/device/reachy_mini.py +52 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/nlu/__init__.py +2 -0
- nardial-0.4.2/src/nardial/providers/nlu/dialogflow.py +65 -0
- nardial-0.4.2/src/nardial/providers/nlu/whisper.py +69 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/screen/web/static/screen.js +11 -1
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/tts/elevenlabs.py +1 -1
- nardial-0.4.2/src/nardial/session_manager.py +378 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial.egg-info/PKG-INFO +300 -33
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial.egg-info/SOURCES.txt +24 -2
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial.egg-info/requires.txt +1 -0
- nardial-0.4.2/tests/test_agenda_items.py +327 -0
- nardial-0.4.2/tests/test_agenda_resolver.py +73 -0
- {nardial-0.3.3 → nardial-0.4.2}/tests/test_authoring_factory.py +46 -0
- nardial-0.4.2/tests/test_controls.py +319 -0
- {nardial-0.3.3 → nardial-0.4.2}/tests/test_conversation_agent.py +20 -0
- nardial-0.4.2/tests/test_conversation_state.py +156 -0
- nardial-0.4.2/tests/test_dialog_registry.py +89 -0
- nardial-0.4.2/tests/test_dialog_types.py +150 -0
- nardial-0.4.2/tests/test_eligibility_rules.py +240 -0
- nardial-0.4.2/tests/test_listen_timeout.py +171 -0
- {nardial-0.3.3 → nardial-0.4.2}/tests/test_moves.py +17 -1
- nardial-0.4.2/tests/test_session_manager.py +434 -0
- nardial-0.4.2/tests/test_session_plan.py +113 -0
- nardial-0.4.2/tests/test_slot_bounds.py +58 -0
- nardial-0.4.2/tests/test_type_definitions.py +302 -0
- nardial-0.3.3/src/nardial/authoring/__init__.py +0 -1
- nardial-0.3.3/src/nardial/authoring/loader.py +0 -70
- nardial-0.3.3/src/nardial/dialog_logic.py +0 -391
- nardial-0.3.3/src/nardial/providers/nlu/dialogflow.py +0 -38
- nardial-0.3.3/src/nardial/session_manager.py +0 -219
- nardial-0.3.3/tests/test_conversation_state.py +0 -57
- {nardial-0.3.3 → nardial-0.4.2}/LICENSE +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/setup.cfg +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/__init__.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/authoring/export_html.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/events/__init__.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/events/bus.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/events/types.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/__init__.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/device/__init__.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/device/alphamini.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/device/desktop.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/device/nao.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/device/pepper.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/llm/__init__.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/llm/echo.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/llm/openai_gpt.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/nlu/written_keyword.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/screen/__init__.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/screen/null.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/screen/pepper_tablet.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/screen/sic_adapter.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/screen/web/static/screen.css +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/screen/web/static/socket.io.min.js +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/screen/web/templates/index.html +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/tts/__init__.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/tts/cacher.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/tts/google.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/tts/naoqi.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/tts/null.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/vector_store/__init__.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/vector_store/null.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/providers/vector_store/redis_store.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial/user_model.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial.egg-info/dependency_links.txt +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/src/nardial.egg-info/top_level.txt +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/tests/test_event_bus.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/tests/test_llm.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/tests/test_mini_dialogs_extra.py +0 -0
- {nardial-0.3.3 → nardial-0.4.2}/tests/test_providers.py +0 -0
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nardial
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.2
|
|
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
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Requires-Dist: social-interaction-cloud
|
|
12
|
+
Requires-Dist: pynput>=1.7
|
|
12
13
|
Provides-Extra: google-tts
|
|
13
14
|
Requires-Dist: social-interaction-cloud[google-tts]; extra == "google-tts"
|
|
14
15
|
Provides-Extra: elevenlabs
|
|
@@ -42,6 +43,7 @@ It lets you author complete conversations declaratively in JSON, then drive them
|
|
|
42
43
|
4. [Defining Dialogs in JSON](#defining-dialogs-in-json)
|
|
43
44
|
- [Dialog Structure](#dialog-structure)
|
|
44
45
|
- [Dialog Types](#dialog-types)
|
|
46
|
+
- [Custom dialog types](#custom-dialog-types)
|
|
45
47
|
- [Move Types](#move-types)
|
|
46
48
|
- [Key JSON Attributes](#key-json-attributes)
|
|
47
49
|
5. [Demos / Creating a Session](#demos--creating-a-session)
|
|
@@ -157,23 +159,24 @@ NarDialPy is built around a set of provider protocols. Each protocol defines a r
|
|
|
157
159
|
|
|
158
160
|
### Available Providers
|
|
159
161
|
|
|
160
|
-
| Role | Provider | Import path
|
|
161
|
-
|
|
162
|
-
| **Device** | `DesktopAdapter` | `nardial.providers.device.desktop`
|
|
163
|
-
| | `PepperAdapter` | `nardial.providers.device.pepper`
|
|
164
|
-
| | `NaoAdapter` | `nardial.providers.device.nao`
|
|
165
|
-
| | `AlphaminiAdapter` | `nardial.providers.device.alphamini`
|
|
166
|
-
|
|
|
167
|
-
| | `
|
|
168
|
-
| | `
|
|
169
|
-
| | `
|
|
170
|
-
|
|
|
171
|
-
| | `
|
|
172
|
-
|
|
|
173
|
-
| | `
|
|
174
|
-
|
|
|
162
|
+
| Role | Provider | Import path | Requires |
|
|
163
|
+
|---|---|----------------------------------------------|---|
|
|
164
|
+
| **Device** | `DesktopAdapter` | `nardial.providers.device.desktop` | base |
|
|
165
|
+
| | `PepperAdapter` | `nardial.providers.device.pepper` | base |
|
|
166
|
+
| | `NaoAdapter` | `nardial.providers.device.nao` | base |
|
|
167
|
+
| | `AlphaminiAdapter` | `nardial.providers.device.alphamini` | `social-interaction-cloud[alphamini]` |
|
|
168
|
+
| | `ReachyMiniAdapter` | `nardial.providers.reachy_mini` | `social-interaction-cloud[reachy-mini]` |
|
|
169
|
+
| **TTS** | `GoogleTTSProvider` | `nardial.providers.tts.google` | `nardial[google-tts]` |
|
|
170
|
+
| | `ElevenLabsTTSProvider` | `nardial.providers.tts.elevenlabs` | `nardial[elevenlabs]` |
|
|
171
|
+
| | `NaoqiTTSProvider` | `nardial.providers.tts.naoqi` | base (uses device's built-in TTS) |
|
|
172
|
+
| | `NullTTSProvider` | `nardial.providers.tts.null` | base (prints to terminal) |
|
|
173
|
+
| **NLU** | `DialogflowNLUProvider` | `nardial.providers.nlu.dialogflow` | `nardial[dialogflow]` |
|
|
174
|
+
| | `WrittenKeywordNLUProvider` | `nardial.providers.nlu.written_keyword` | base (keyboard input) |
|
|
175
|
+
| **LLM** | `OpenAIGPTProvider` | `nardial.providers.llm.openai_gpt` | `nardial[openai]` |
|
|
176
|
+
| | `EchoLLMProvider` | `nardial.providers.llm.echo` | base (echoes user input) |
|
|
177
|
+
| **Screen** | `ScreenProvider` / `SICScreenAdapter` / `PepperTabletScreenAdapter` | `nardial.providers.screen` | browser display via SIC webserver |
|
|
175
178
|
| **Vector store** | `RedisVectorStoreProvider` | `nardial.providers.vector_store.redis_store` | base + running Redis |
|
|
176
|
-
| | `NullVectorStoreProvider` | `nardial.providers.vector_store.null`
|
|
179
|
+
| | `NullVectorStoreProvider` | `nardial.providers.vector_store.null` | base |
|
|
177
180
|
|
|
178
181
|
---
|
|
179
182
|
|
|
@@ -267,6 +270,37 @@ Then pass `device` and `tts` to `ConversationAgent` as above.
|
|
|
267
270
|
|
|
268
271
|
---
|
|
269
272
|
|
|
273
|
+
### Pause and skip controls
|
|
274
|
+
|
|
275
|
+
Bind keys to pause the interaction or to skip a listen that takes too long by passing `controls` in an `InteractionConfig`. Keys work globally (the terminal does not need focus), with or without `SessionManager`:
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
from nardial.interaction_orchestrator import InteractionConfig
|
|
279
|
+
|
|
280
|
+
agent = ConversationAgent(
|
|
281
|
+
..., # device and providers as in the setups above
|
|
282
|
+
int_config=InteractionConfig(
|
|
283
|
+
controls={
|
|
284
|
+
"start_paused": True, # wait for the pause key before starting
|
|
285
|
+
"toggle_pause": ["space"], # pause / resume
|
|
286
|
+
"skip_listen": ["s"], # stop a listen that takes too long
|
|
287
|
+
},
|
|
288
|
+
),
|
|
289
|
+
)
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
| Setting | Effect |
|
|
293
|
+
|---|---|
|
|
294
|
+
| `toggle_pause` | Pauses the interaction. Speech in progress is stopped and spoken again from the beginning after resume. Listening in progress is stopped, what was heard so far is kept, and the dialog continues after the listen once resumed. Works in every dialog type, including LLM dialogs. |
|
|
295
|
+
| `skip_listen` | Stops the current listen early. What was said so far is still recognised and used as the answer: Dialogflow gets a `StopListeningMessage`, and local Whisper gets an end-of-audio signal and then transcribes what it recorded (this can take a few seconds; Whisper waits up to 30 s for it, which a faster model can lower with `LocalWhisperNLUProvider(..., stop_grace_seconds=5)`). The written keyword provider cannot stop early and gives up after 3 seconds with an empty answer. |
|
|
296
|
+
| `start_paused` | `true` makes the interaction wait for `toggle_pause` before it starts. |
|
|
297
|
+
|
|
298
|
+
A question whose listen was stopped by a pause or skip is not asked again, even when the answer is empty: the dialog continues with whatever was heard, and an empty answer follows the move's `default_outcome`.
|
|
299
|
+
|
|
300
|
+
Key names are single characters (`"p"`) or [pynput key names](https://pynput.readthedocs.io/en/latest/keyboard.html#pynput.keyboard.Key) (`"space"`, `"enter"`, `"f5"`). Moves that are not speech or listening (e.g. `timed_wait`, `wait_for_button`) are not paused; the pause takes effect at the next speech or listen.
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
270
304
|
### Using SessionManager
|
|
271
305
|
|
|
272
306
|
`SessionManager` wraps `ConversationAgent` and adds dialog loading, eligibility checking, and session state:
|
|
@@ -282,6 +316,20 @@ manager = SessionManager(
|
|
|
282
316
|
manager.run()
|
|
283
317
|
```
|
|
284
318
|
|
|
319
|
+
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:
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
manager = SessionManager(
|
|
323
|
+
agent=agent,
|
|
324
|
+
dialog_file="dialogs/my_dialogs.json",
|
|
325
|
+
participant_id="user_42",
|
|
326
|
+
resume=True,
|
|
327
|
+
)
|
|
328
|
+
manager.run()
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
See [Developer Flow](docs/DEVELOPER_README.md) for more on session/state handling.
|
|
332
|
+
|
|
285
333
|
---
|
|
286
334
|
|
|
287
335
|
## Defining Dialogs in JSON
|
|
@@ -295,10 +343,11 @@ Every dialog has the following shared fields:
|
|
|
295
343
|
| Field | Type | Required | Description |
|
|
296
344
|
|---|---|---|---|
|
|
297
345
|
| `id` | string | ✅ | Unique identifier referenced in `session_agenda` and `dependencies` |
|
|
298
|
-
| `type` | string | ✅ | Dialog type: `"functional"`, `"chitchat"`, `"narrative"`,
|
|
346
|
+
| `type` | string | ✅ | Dialog type: `"functional"`, `"chitchat"`, `"narrative"`, `"llm_based"`, or a [custom type](#custom-dialog-types) |
|
|
299
347
|
| `moves` | array | ✅ | Ordered list of move objects the robot will perform |
|
|
300
348
|
| `dependencies` | array of strings | | Dialog IDs that must have been completed before this dialog may run |
|
|
301
349
|
| `variable_dependencies` | array | | Variables that must exist in the user model before this dialog may run |
|
|
350
|
+
| `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
351
|
|
|
303
352
|
Example:
|
|
304
353
|
```json
|
|
@@ -338,6 +387,51 @@ Dialogs can define reusable character voices:
|
|
|
338
387
|
- Each value must contain a `voice_settings` object.
|
|
339
388
|
- `voice_settings` is validated at runtime against the active TTS provider.
|
|
340
389
|
|
|
390
|
+
#### Optional Top-Level `prerequisites`
|
|
391
|
+
|
|
392
|
+
Dialogs can list actions to run just before their first move, each time the dialog runs:
|
|
393
|
+
|
|
394
|
+
```json
|
|
395
|
+
{
|
|
396
|
+
"id": "multiplication",
|
|
397
|
+
"type": "chitchat",
|
|
398
|
+
"prerequisites": [
|
|
399
|
+
{ "execute": "choose_math_level", "args": [true, false] , "skip_dialog": true},
|
|
400
|
+
{ "set_variable": { "math_left": 4, "math_right": 3 } }
|
|
401
|
+
],
|
|
402
|
+
"moves": [
|
|
403
|
+
{ "type": "say", "text": "What is %math_left% times %math_right%?" }
|
|
404
|
+
]
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Each entry does one of two things:
|
|
409
|
+
|
|
410
|
+
| Key | Value | What happens |
|
|
411
|
+
|---|---|---|
|
|
412
|
+
| `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. |
|
|
413
|
+
| `set_variable` | object | Merges the object into the user model, so its values can be used in `%variable%` placeholders and later dialogs. |
|
|
414
|
+
|
|
415
|
+
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`.
|
|
416
|
+
Functions are registered in Python under the name used in `execute`, before the session runs:
|
|
417
|
+
|
|
418
|
+
```python
|
|
419
|
+
from nardial.mini_dialogs import MiniDialog
|
|
420
|
+
|
|
421
|
+
@MiniDialog.register("choose_math_level")
|
|
422
|
+
def choose_math_level(last_correct, needed_help):
|
|
423
|
+
if last_correct and not needed_help:
|
|
424
|
+
level = 2
|
|
425
|
+
else:
|
|
426
|
+
level = 1
|
|
427
|
+
return level
|
|
428
|
+
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
- 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.
|
|
432
|
+
- Registered functions must be regular (not `async`) functions. They get only the `args` from the JSON, not the dialog or the user model.
|
|
433
|
+
- If a function isn't registered or raises an error, the error is printed and the remaining prerequisites and the dialog still run.
|
|
434
|
+
|
|
341
435
|
---
|
|
342
436
|
|
|
343
437
|
#### 1.`functional`
|
|
@@ -465,6 +559,95 @@ A fully LLM-driven dialog where the robot and user engage in a free-form multi-t
|
|
|
465
559
|
}
|
|
466
560
|
```
|
|
467
561
|
|
|
562
|
+
#### Custom dialog types
|
|
563
|
+
|
|
564
|
+
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.
|
|
565
|
+
|
|
566
|
+
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:
|
|
567
|
+
|
|
568
|
+
```python
|
|
569
|
+
manager = SessionManager(
|
|
570
|
+
session_agenda=[...],
|
|
571
|
+
agent=agent,
|
|
572
|
+
dialog_types_path="dialog_types/my_types.json",
|
|
573
|
+
dialog_json_path="dialog_json/my_dialogs.json",
|
|
574
|
+
)
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
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.
|
|
578
|
+
|
|
579
|
+
A type definition looks like this:
|
|
580
|
+
|
|
581
|
+
```json
|
|
582
|
+
{
|
|
583
|
+
"define_type": "repeatable_chitchat",
|
|
584
|
+
"extends": "chitchat",
|
|
585
|
+
"description": "Chitchat that may come back in later sessions, but runs at most once per session.",
|
|
586
|
+
"remove_rules": ["exclude_if_seen"],
|
|
587
|
+
"add_rules": [{ "rule": "exclude_if_seen", "scope": "session" }]
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
| Field | Type | Required | Description |
|
|
592
|
+
|---|---|---|---|
|
|
593
|
+
| `define_type` | string | ✅ | Name of the new type, used as `type` in dialogs |
|
|
594
|
+
| `extends` | string | ✅ | Type it is based on: a built-in type or another custom type |
|
|
595
|
+
| `description` | string | | Free-text description |
|
|
596
|
+
| `remove_rules` | array of strings | | Names of the base type's rules to drop |
|
|
597
|
+
| `add_rules` | array | | Rules to add: a rule name, or an object with `"rule"` plus that rule's settings |
|
|
598
|
+
|
|
599
|
+
Dialogs of the new type use the same fields as dialogs of the type it extends:
|
|
600
|
+
|
|
601
|
+
```json
|
|
602
|
+
{
|
|
603
|
+
"id": "music_lately",
|
|
604
|
+
"type": "repeatable_chitchat",
|
|
605
|
+
"topics": ["music"],
|
|
606
|
+
"moves": [{ "type": "say", "text": "What music have you been listening to lately?" }]
|
|
607
|
+
}
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
They are also picked by the same agenda slots. For example, a `chitchat_slot` can choose a `repeatable_chitchat` dialog.
|
|
611
|
+
|
|
612
|
+
**Built-in types and their rules:**
|
|
613
|
+
|
|
614
|
+
| Type | Rules |
|
|
615
|
+
|---|---|
|
|
616
|
+
| `functional` | `dependency_met` |
|
|
617
|
+
| `narrative` | `exclude_if_seen`, `dependency_met`, `variable_dependency_met`, `narrative_ordering` |
|
|
618
|
+
| `chitchat` | `exclude_if_seen`, `dependency_met`, `variable_dependency_met` |
|
|
619
|
+
| `llm_based` | `exclude_if_seen`, `dependency_met`, `variable_dependency_met` |
|
|
620
|
+
|
|
621
|
+
**Available rules.** A dialog may run only when all of its type's rules allow it.
|
|
622
|
+
|
|
623
|
+
| Rule | Settings | Allows the dialog to run when |
|
|
624
|
+
|---|---|---|
|
|
625
|
+
| `exclude_if_seen` | `scope`: `"participant"` (default) or `"session"` | it hasn't been completed before (by this participant, or in this session) |
|
|
626
|
+
| `dependency_met` | | every dialog in its `dependencies` has been completed |
|
|
627
|
+
| `variable_dependency_met` | | every required variable in its `variable_dependencies` is set in the user model |
|
|
628
|
+
| `narrative_ordering` | | every earlier `position` in its `thread` has been completed |
|
|
629
|
+
|
|
630
|
+
**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:
|
|
631
|
+
|
|
632
|
+
```python
|
|
633
|
+
from nardial.eligibility import EligibilityRule, register_rule
|
|
634
|
+
|
|
635
|
+
@register_rule("from_session")
|
|
636
|
+
class FromSessionRule(EligibilityRule):
|
|
637
|
+
def __init__(self, min):
|
|
638
|
+
self.min = min
|
|
639
|
+
|
|
640
|
+
def is_eligible(self, dialog, context):
|
|
641
|
+
# context.session_index: session number starting at 1, or None when unknown
|
|
642
|
+
return context.session_index is not None and context.session_index >= self.min
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
```json
|
|
646
|
+
{ "define_type": "returning_chitchat", "extends": "chitchat", "add_rules": [{ "rule": "from_session", "min": 2 }] }
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
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.
|
|
650
|
+
|
|
468
651
|
---
|
|
469
652
|
|
|
470
653
|
### Move Types
|
|
@@ -489,6 +672,22 @@ Speaks a piece of text. Variable placeholders (`%variable_name%`) are replaced a
|
|
|
489
672
|
|
|
490
673
|
---
|
|
491
674
|
|
|
675
|
+
#### `say_options`
|
|
676
|
+
|
|
677
|
+
Speaks one randomly chosen response from a predefined list.
|
|
678
|
+
|
|
679
|
+
| Field | Type | Required | Description |
|
|
680
|
+
|---|---|---|---|
|
|
681
|
+
| `type` | string | ✅ | `"say_options"` |
|
|
682
|
+
| `options` | array of strings | ✅ | Candidate utterances to choose from at runtime |
|
|
683
|
+
| `character` | string | | Optional character name from top-level `characters` |
|
|
684
|
+
|
|
685
|
+
```json
|
|
686
|
+
{ "type": "say_options", "options": ["Hi!", "Hello!", "Hey there!"] }
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
---
|
|
690
|
+
|
|
492
691
|
#### `ask_open`
|
|
493
692
|
|
|
494
693
|
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 +701,8 @@ Asks a free-text question and listens for any spoken reply. The answer can be st
|
|
|
502
701
|
| `default_outcome` | string | | Outcome label when no answer or no match is found |
|
|
503
702
|
| `add_interest_from_answer` | boolean | | If `true`, adds the answer to the user's topics of interest |
|
|
504
703
|
| `llm_followup` | string | | System prompt for an LLM-generated follow-up sentence after the user replies |
|
|
704
|
+
| `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` |
|
|
705
|
+
| `timeout` | number | | Seconds to listen for an answer. Defaults to `InteractionConfig(listen_timeout=...)` (10 s) |
|
|
505
706
|
|
|
506
707
|
```json
|
|
507
708
|
{
|
|
@@ -527,6 +728,7 @@ Asks a yes/no question. The detected intent (`"yes"`, `"no"`, `"dontknow"`) driv
|
|
|
527
728
|
| `default_outcome` | string | | Outcome label used as fallback |
|
|
528
729
|
| `add_interest` | string | | Topic added to interest list when the user answers `"yes"` |
|
|
529
730
|
| `llm_followup` | string | | System prompt for an LLM-generated follow-up sentence |
|
|
731
|
+
| `timeout` | number | | Seconds to listen for an answer. Defaults to `InteractionConfig(listen_timeout=...)` (10 s) |
|
|
530
732
|
|
|
531
733
|
```json
|
|
532
734
|
{
|
|
@@ -555,6 +757,7 @@ Presents a multiple-choice question. The selected option drives branching and ca
|
|
|
555
757
|
| `default_outcome` | string | | Outcome label when nothing matches |
|
|
556
758
|
| `add_interest_from_variable` | string | | After storing, adds the named variable's value as a topic of interest |
|
|
557
759
|
| `llm_followup` | string | | System prompt for an LLM-generated follow-up sentence |
|
|
760
|
+
| `timeout` | number | | Seconds to listen for an answer. Defaults to `InteractionConfig(listen_timeout=...)` (10 s) |
|
|
558
761
|
|
|
559
762
|
```json
|
|
560
763
|
{
|
|
@@ -601,11 +804,12 @@ Starts a multi-turn LLM-driven exchange *within* an otherwise scripted dialog. U
|
|
|
601
804
|
|
|
602
805
|
Selects and executes a list of sub-moves based on the current outcome or the value of a user model variable.
|
|
603
806
|
|
|
604
|
-
| Field
|
|
605
|
-
|
|
606
|
-
| `type`
|
|
607
|
-
| `on`
|
|
608
|
-
| `cases`
|
|
807
|
+
| Field | Type | Required | Description |
|
|
808
|
+
|-------------|---|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
809
|
+
| `type` | string | ✅ | `"branch"` |
|
|
810
|
+
| `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 |
|
|
811
|
+
| `cases` | object | ✅ | Maps condition values to arrays of sub-moves |
|
|
812
|
+
| `variables` | list | | In case of branching on `"variables"`, a list of the variables that need to be compared |
|
|
609
813
|
|
|
610
814
|
```json
|
|
611
815
|
{
|
|
@@ -635,6 +839,20 @@ Branching on a stored variable (e.g. to react to an answer from an earlier dialo
|
|
|
635
839
|
}
|
|
636
840
|
```
|
|
637
841
|
|
|
842
|
+
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.
|
|
843
|
+
|
|
844
|
+
```json
|
|
845
|
+
{
|
|
846
|
+
"type": "branch",
|
|
847
|
+
"on": "variables",
|
|
848
|
+
"variables": ["given_answer", "correct_answer"],
|
|
849
|
+
"cases": {
|
|
850
|
+
"true": [{ "type": "say", "text": "That's correct!"}],
|
|
851
|
+
"false": [{ "type": "say", "text": "That's incorrect. Let's try again."}]
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
```
|
|
855
|
+
|
|
638
856
|
---
|
|
639
857
|
|
|
640
858
|
#### `play`
|
|
@@ -701,14 +919,15 @@ Pause execution for a fixed duration before proceeding to the next move. Useful
|
|
|
701
919
|
|
|
702
920
|
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
921
|
|
|
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)
|
|
922
|
+
| Field | Type | Required | Description |
|
|
923
|
+
|---|---|---|---------------------------------------------------------------------------------|
|
|
924
|
+
| `type` | string | ✅ | `"wait_for_web_input"` |
|
|
925
|
+
| `prompt` | string | | Hint text shown in the web UI (not spoken) |
|
|
708
926
|
| `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
|
|
927
|
+
| `timeout` | number | | Seconds to wait before falling back (omit for indefinite wait) |
|
|
928
|
+
| `outcomes` | object | | Maps option value → outcome label for branching |
|
|
929
|
+
| `default_outcome` | string | | Outcome used on timeout or when no event matches |
|
|
930
|
+
| `set_variable` | string | | Stores the user's choice in the user model |
|
|
712
931
|
|
|
713
932
|
```json
|
|
714
933
|
{
|
|
@@ -717,7 +936,8 @@ Suspend dialog execution until a matching web input event arrives or until a tim
|
|
|
717
936
|
"options": ["smile","thumbs","surprised"],
|
|
718
937
|
"timeout": 15,
|
|
719
938
|
"outcomes": {"smile": "picked_smile"},
|
|
720
|
-
"default_outcome": "no_choice"
|
|
939
|
+
"default_outcome": "no_choice",
|
|
940
|
+
"set_variable": "sticker"
|
|
721
941
|
}
|
|
722
942
|
```
|
|
723
943
|
|
|
@@ -797,6 +1017,49 @@ Clear the display (show a blank/black screen). No parameters — the next displa
|
|
|
797
1017
|
|
|
798
1018
|
---
|
|
799
1019
|
|
|
1020
|
+
#### `keyboard_input`
|
|
1021
|
+
|
|
1022
|
+
Display keyboard input on screen. Suspend dialog execution until keyboard input is given or until a timeout elapses.
|
|
1023
|
+
|
|
1024
|
+
| Field | Type | Required | Description |
|
|
1025
|
+
|---|---|---|----------------------------------------------------------------|
|
|
1026
|
+
| `type` | string | ✅ | `"keyboard_input"` |
|
|
1027
|
+
| `prompt` | string | | Hint text shown in the web UI (not spoken) |
|
|
1028
|
+
| `timeout` | number | | Seconds to wait before falling back (omit for indefinite wait) |
|
|
1029
|
+
| `outcomes` | object | | Maps option value → outcome label for branching |
|
|
1030
|
+
| `default_outcome` | string | | Outcome used on timeout or when no event matches |
|
|
1031
|
+
| `set_variable` | string | | Stores the user's choice in the user model |
|
|
1032
|
+
|
|
1033
|
+
```json
|
|
1034
|
+
{
|
|
1035
|
+
"type": "keyboard_input",
|
|
1036
|
+
"prompt": "How old are you?",
|
|
1037
|
+
"timeout": 15,
|
|
1038
|
+
"default_outcome": "no_age_given",
|
|
1039
|
+
"set_variable": "age"
|
|
1040
|
+
}
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
---
|
|
1044
|
+
|
|
1045
|
+
#### `go_to_dialog`
|
|
1046
|
+
|
|
1047
|
+
Move to a specified new minidialog.
|
|
1048
|
+
|
|
1049
|
+
| Field | Type | Required | Description |
|
|
1050
|
+
|-------------------|---|----------|----------------------------------------------------------------|
|
|
1051
|
+
| `type` | string | ✅ | `"go_to_dialog"` |
|
|
1052
|
+
| `dialog_id` | string |✅ | ID of the dialog to go to |
|
|
1053
|
+
|
|
1054
|
+
```json
|
|
1055
|
+
{
|
|
1056
|
+
"type": "go_to_dialog",
|
|
1057
|
+
"dialog_id": "favorite_animal"
|
|
1058
|
+
}
|
|
1059
|
+
```
|
|
1060
|
+
|
|
1061
|
+
---
|
|
1062
|
+
|
|
800
1063
|
### Key JSON Attributes
|
|
801
1064
|
|
|
802
1065
|
| Attribute | Where used | Description |
|
|
@@ -808,11 +1071,13 @@ Clear the display (show a blank/black screen). No parameters — the next displa
|
|
|
808
1071
|
|
|
809
1072
|
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
1073
|
|
|
811
|
-
|
|
1074
|
+
Ready-to-run demos are included in the `examples/` directory:
|
|
812
1075
|
* Demo 1 — General Conversation (`demo_general_conversation.py`): A simple four-step conversation using a mix of narrative and functional dialogs
|
|
813
1076
|
* 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
1077
|
* 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
1078
|
* Demo 4 — Pepper Tablet (`demo_pepper_tablet.py`): Uses the same screen UI on Pepper's tablet through the SIC webserver
|
|
1079
|
+
* 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
|
|
1080
|
+
* 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
1081
|
|
|
817
1082
|
You can find additional demos in the [SIC Applications repository](https://github.com/Social-AI-VU/sic_applications/tree/main/demos/nardial)
|
|
818
1083
|
|
|
@@ -820,6 +1085,8 @@ You can find additional demos in the [SIC Applications repository](https://githu
|
|
|
820
1085
|
|
|
821
1086
|
## Development
|
|
822
1087
|
|
|
1088
|
+
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.
|
|
1089
|
+
|
|
823
1090
|
Run tests from the repository root:
|
|
824
1091
|
|
|
825
1092
|
```bash
|