nardial 0.1.8__tar.gz → 0.2.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.
Files changed (58) hide show
  1. nardial-0.2.2/PKG-INFO +724 -0
  2. nardial-0.2.2/README.md +701 -0
  3. {nardial-0.1.8 → nardial-0.2.2}/pyproject.toml +9 -2
  4. {nardial-0.1.8 → nardial-0.2.2}/src/nardial/authoring/factory.py +35 -6
  5. {nardial-0.1.8 → nardial-0.2.2}/src/nardial/conversation_agent.py +68 -82
  6. {nardial-0.1.8 → nardial-0.2.2}/src/nardial/conversation_state.py +4 -4
  7. nardial-0.2.2/src/nardial/interaction_orchestrator.py +239 -0
  8. {nardial-0.1.8 → nardial-0.2.2}/src/nardial/mini_dialogs.py +65 -30
  9. nardial-0.2.2/src/nardial/providers/__init__.py +13 -0
  10. nardial-0.2.2/src/nardial/providers/device/__init__.py +21 -0
  11. nardial-0.2.2/src/nardial/providers/device/alphamini.py +58 -0
  12. nardial-0.2.2/src/nardial/providers/device/desktop.py +46 -0
  13. nardial-0.2.2/src/nardial/providers/device/nao.py +117 -0
  14. nardial-0.2.2/src/nardial/providers/device/pepper.py +117 -0
  15. nardial-0.2.2/src/nardial/providers/llm/__init__.py +13 -0
  16. nardial-0.2.2/src/nardial/providers/llm/echo.py +11 -0
  17. nardial-0.2.2/src/nardial/providers/llm/openai_gpt.py +25 -0
  18. nardial-0.2.2/src/nardial/providers/nlu/__init__.py +29 -0
  19. nardial-0.2.2/src/nardial/providers/nlu/dialogflow.py +35 -0
  20. nardial-0.2.2/src/nardial/providers/nlu/written_keyword.py +38 -0
  21. nardial-0.2.2/src/nardial/providers/tts/__init__.py +31 -0
  22. nardial-0.2.2/src/nardial/providers/tts/cacher.py +122 -0
  23. nardial-0.2.2/src/nardial/providers/tts/elevenlabs.py +124 -0
  24. nardial-0.2.2/src/nardial/providers/tts/google.py +100 -0
  25. nardial-0.2.2/src/nardial/providers/tts/naoqi.py +13 -0
  26. nardial-0.2.2/src/nardial/providers/tts/null.py +11 -0
  27. nardial-0.2.2/src/nardial/providers/vector_store/__init__.py +11 -0
  28. nardial-0.2.2/src/nardial/providers/vector_store/null.py +14 -0
  29. nardial-0.2.2/src/nardial/providers/vector_store/redis_store.py +104 -0
  30. {nardial-0.1.8 → nardial-0.2.2}/src/nardial/session_manager.py +1 -1
  31. nardial-0.2.2/src/nardial.egg-info/PKG-INFO +724 -0
  32. nardial-0.2.2/src/nardial.egg-info/SOURCES.txt +49 -0
  33. nardial-0.2.2/src/nardial.egg-info/requires.txt +16 -0
  34. nardial-0.2.2/tests/test_authoring_factory.py +76 -0
  35. nardial-0.2.2/tests/test_conversation_agent.py +108 -0
  36. {nardial-0.1.8 → nardial-0.2.2}/tests/test_llm.py +16 -4
  37. {nardial-0.1.8 → nardial-0.2.2}/tests/test_mini_dialogs_extra.py +27 -1
  38. nardial-0.2.2/tests/test_providers.py +163 -0
  39. nardial-0.1.8/PKG-INFO +0 -125
  40. nardial-0.1.8/README.md +0 -112
  41. nardial-0.1.8/src/nardial/interaction_orchestrator.py +0 -986
  42. nardial-0.1.8/src/nardial/tts_manager.py +0 -364
  43. nardial-0.1.8/src/nardial.egg-info/PKG-INFO +0 -125
  44. nardial-0.1.8/src/nardial.egg-info/SOURCES.txt +0 -26
  45. nardial-0.1.8/src/nardial.egg-info/requires.txt +0 -1
  46. {nardial-0.1.8 → nardial-0.2.2}/LICENSE +0 -0
  47. {nardial-0.1.8 → nardial-0.2.2}/setup.cfg +0 -0
  48. {nardial-0.1.8 → nardial-0.2.2}/src/nardial/__init__.py +0 -0
  49. {nardial-0.1.8 → nardial-0.2.2}/src/nardial/authoring/__init__.py +0 -0
  50. {nardial-0.1.8 → nardial-0.2.2}/src/nardial/authoring/export_html.py +0 -0
  51. {nardial-0.1.8 → nardial-0.2.2}/src/nardial/authoring/loader.py +0 -0
  52. {nardial-0.1.8 → nardial-0.2.2}/src/nardial/dialog_logic.py +0 -0
  53. {nardial-0.1.8 → nardial-0.2.2}/src/nardial/moves.py +0 -0
  54. {nardial-0.1.8 → nardial-0.2.2}/src/nardial/user_model.py +0 -0
  55. {nardial-0.1.8 → nardial-0.2.2}/src/nardial.egg-info/dependency_links.txt +0 -0
  56. {nardial-0.1.8 → nardial-0.2.2}/src/nardial.egg-info/top_level.txt +0 -0
  57. {nardial-0.1.8 → nardial-0.2.2}/tests/test_conversation_state.py +0 -0
  58. {nardial-0.1.8 → nardial-0.2.2}/tests/test_moves.py +0 -0
nardial-0.2.2/PKG-INFO ADDED
@@ -0,0 +1,724 @@
1
+ Metadata-Version: 2.4
2
+ Name: nardial
3
+ Version: 0.2.2
4
+ Summary: Nardial Dialog System
5
+ Author-email: Mike Ligthart <m.e.u.ligthart@vu.nl>
6
+ Maintainer: Clara Fayyad, Landon Brown, Evelyne Prodescu
7
+ License-Expression: AGPL-3.0-only
8
+ Requires-Python: >=3.12
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: social-interaction-cloud
12
+ Provides-Extra: google-tts
13
+ Requires-Dist: social-interaction-cloud[google-tts]; extra == "google-tts"
14
+ Provides-Extra: elevenlabs
15
+ Requires-Dist: social-interaction-cloud[elevenlabs-tts]; extra == "elevenlabs"
16
+ Provides-Extra: dialogflow
17
+ Requires-Dist: social-interaction-cloud[dialogflow]; extra == "dialogflow"
18
+ Provides-Extra: openai
19
+ Requires-Dist: social-interaction-cloud[openai-gpt]; extra == "openai"
20
+ Provides-Extra: all
21
+ Requires-Dist: nardial[dialogflow,elevenlabs,google-tts,openai]; extra == "all"
22
+ Dynamic: license-file
23
+
24
+ # NarDialPy
25
+
26
+ **NarDialPy** is a Python package for building and running **narrative-driven, structured dialog systems** — designed for social robots and conversational agents.
27
+
28
+ It lets you author complete conversations declaratively in JSON, then drive them from Python using voice, NLU, and LLM services. The package handles session flow, branching logic, topic tracking, and personalization so you can focus on what the robot says and how conversations unfold.
29
+
30
+ ---
31
+
32
+ ## Table of Contents
33
+
34
+ 1. [What is nardial?](#what-is-nardial)
35
+ 2. [Prerequisites & Setup](#prerequisites--setup)
36
+ 3. [Providers & Initialization](#providers--initialization)
37
+ 4. [Defining Dialogs in JSON](#defining-dialogs-in-json)
38
+ - [Dialog Structure](#dialog-structure)
39
+ - [Dialog Types](#dialog-types)
40
+ - [Move Types](#move-types)
41
+ - [Key JSON Attributes](#key-json-attributes)
42
+ 5. [Demos / Creating a Session](#demos--creating-a-session)
43
+ 6. [Development](#development)
44
+
45
+ ---
46
+
47
+ ## What is nardial?
48
+
49
+ The `nardial` package provides the building blocks for authoring and executing multi-turn conversations:
50
+
51
+ | Component | Description |
52
+ |---|---|
53
+ | **Dialog JSON** | Conversations are written as structured JSON files. Each file holds one or more *dialogs*, each containing a sequence of *moves* that the robot performs. |
54
+ | **Session Manager** | Loads your dialog JSON, resolves a session agenda, and runs dialogs in order — checking dependencies and tracking state. |
55
+ | **ConversationAgent** | The runtime bridge to the hardware: it calls TTS, STT, LLM, and motion services on your chosen device. |
56
+ | **Dialog Logic** | Checks eligibility rules (dependencies, variable requirements) before executing each dialog. |
57
+
58
+ Typical use case:
59
+
60
+ - A social robot (Pepper, NAO, desktop agent) runs a structured conversation with a child or adult participant.
61
+ - The conversation is broken into named dialog blocks (greeting, story, chitchat, goodbye) authored in JSON.
62
+ - Python code wires up the device, credentials, and agenda — the JSON drives the actual content and branching.
63
+
64
+ Additional community demos are in the [SIC Applications repository](https://github.com/Social-AI-VU/sic_applications/tree/main/demos/nardial).
65
+
66
+ ---
67
+
68
+ ## Prerequisites & Setup
69
+
70
+ ### 1. Python IDE
71
+
72
+ Recommended: [PyCharm](https://www.jetbrains.com/help/pycharm/installation-guide.html) or [VS Code](https://code.visualstudio.com/download)
73
+
74
+ ### 2. Python
75
+
76
+ - Version: **3.12**
77
+ - Download: https://www.python.org/downloads/release/python-3120/
78
+ - ⚠️ Ensure Python is added to your system `PATH`
79
+
80
+ ### 3. In your project, create and activate your python virtual environment
81
+ ```bash
82
+ # Windows
83
+ cd your_project_folder
84
+ python -m venv venv_myproject
85
+ venv_myproject\Scripts\activate
86
+
87
+ # macOS / Linux
88
+ cd your_project_folder
89
+ python -m venv venv_myproject
90
+ source venv_myproject/bin/activate
91
+ ```
92
+
93
+ ### 4. Install NarDial
94
+
95
+ Install the core package, then add extras for each service you intend to use:
96
+
97
+ ```bash
98
+ pip install nardial
99
+ ```
100
+
101
+ | Extra | Enables | Install command |
102
+ |---|---|---|
103
+ | `google-tts` | Google Cloud Text-to-Speech | `pip install "nardial[google-tts]"` |
104
+ | `elevenlabs` | ElevenLabs Text-to-Speech | `pip install "nardial[elevenlabs]"` |
105
+ | `dialogflow` | Google Dialogflow NLU | `pip install "nardial[dialogflow]"` |
106
+ | `openai` | OpenAI GPT | `pip install "nardial[openai]"` |
107
+ | `all` | All of the above | `pip install "nardial[all]"` |
108
+
109
+ For robot devices, install the matching SIC device extra directly:
110
+
111
+ ```bash
112
+ pip install "social-interaction-cloud[alphamini]" # Alphamini
113
+ ```
114
+
115
+ Pepper and NAO are included in the base SIC package.
116
+
117
+
118
+ ### 5. Configure Credentials
119
+ * Create a `conf` folder at the root of your project.
120
+ * Create a Google cloud project (for Dialogflow and google-tts services) and save the generated keyfile to `conf/google/google_keyfile.json`. Instructions [here](https://socialrobotics.atlassian.net/wiki/spaces/CBSR/pages/2205155343/Getting+a+google+dialogflow+key).
121
+ * Create an OpenAI account, and generate an [API key](https://developers.openai.com/api/docs/quickstart) and save it to `conf/openai/.openai_env` with the following content:
122
+ ```bash
123
+ OPENAI_API_KEY="your key"
124
+ ```
125
+
126
+ > ⚠️ Never commit credential files to version control.
127
+
128
+ ### 6. Start Required Services
129
+
130
+ Run these in separate terminals before starting any demo:
131
+
132
+ ```bash
133
+ # Windows
134
+ conf/redis/redis-server.exe conf/redis/redis.conf
135
+
136
+ # macOS / Linux
137
+ redis-server conf/redis/redis.conf
138
+ ```
139
+
140
+ ```bash
141
+ run-dialogflow
142
+ run-google-tts
143
+ run-gpt
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Providers & Initialization
149
+
150
+ NarDialPy is built around a set of provider protocols. Each protocol defines a role (device, TTS, NLU, LLM, vector store) and multiple concrete implementations are available. You pick one implementation per role, instantiate it, and pass everything into `ConversationAgent` or `SessionManager`.
151
+
152
+ ### Available Providers
153
+
154
+ | Role | Provider | Import path | Requires |
155
+ |---|---|---|---|
156
+ | **Device** | `DesktopAdapter` | `nardial.providers.device.desktop` | base |
157
+ | | `PepperAdapter` | `nardial.providers.device.pepper` | base |
158
+ | | `NaoAdapter` | `nardial.providers.device.nao` | base |
159
+ | | `AlphaminiAdapter` | `nardial.providers.device.alphamini` | `social-interaction-cloud[alphamini]` |
160
+ | **TTS** | `GoogleTTSProvider` | `nardial.providers.tts.google` | `nardial[google-tts]` |
161
+ | | `ElevenLabsTTSProvider` | `nardial.providers.tts.elevenlabs` | `nardial[elevenlabs]` |
162
+ | | `NaoqiTTSProvider` | `nardial.providers.tts.naoqi` | base (uses device's built-in TTS) |
163
+ | | `NullTTSProvider` | `nardial.providers.tts.null` | base (prints to terminal) |
164
+ | **NLU** | `DialogflowNLUProvider` | `nardial.providers.nlu.dialogflow` | `nardial[dialogflow]` |
165
+ | | `WrittenKeywordNLUProvider` | `nardial.providers.nlu.written_keyword` | base (keyboard input) |
166
+ | **LLM** | `OpenAIGPTProvider` | `nardial.providers.llm.openai_gpt` | `nardial[openai]` |
167
+ | | `EchoLLMProvider` | `nardial.providers.llm.echo` | base (echoes user input) |
168
+ | **Vector store** | `RedisVectorStoreProvider` | `nardial.providers.vector_store.redis_store` | base + running Redis |
169
+ | | `NullVectorStoreProvider` | `nardial.providers.vector_store.null` | base |
170
+
171
+ ---
172
+
173
+ ### Minimal setup (no external services)
174
+
175
+ Good for local development and testing — all I/O goes through the terminal:
176
+
177
+ ```python
178
+ import logging
179
+ from sic_framework.devices.desktop import Desktop
180
+
181
+ from nardial.providers.device.desktop import DesktopAdapter
182
+ from nardial.providers.tts.null import NullTTSProvider
183
+ from nardial.providers.nlu.written_keyword import WrittenKeywordNLUProvider
184
+ from nardial.conversation_agent import ConversationAgent
185
+
186
+ desktop = Desktop()
187
+ device = DesktopAdapter(desktop)
188
+ device.setup(logger=logging.getLogger())
189
+
190
+ agent = ConversationAgent(
191
+ device=device,
192
+ tts_provider=NullTTSProvider(),
193
+ nlu_provider=WrittenKeywordNLUProvider(),
194
+ )
195
+ ```
196
+
197
+ ---
198
+
199
+ ### Desktop with cloud services
200
+
201
+ ```python
202
+ import json, logging
203
+ from sic_framework.devices.desktop import Desktop
204
+ from sic_framework.services.dialogflow.dialogflow import DialogflowConf
205
+
206
+ from nardial.providers.device.desktop import DesktopAdapter
207
+ from nardial.providers.tts.google import GoogleTTSProvider, GoogleTTSConf
208
+ from nardial.providers.tts.cacher import TTSCacher
209
+ from nardial.providers.nlu.dialogflow import DialogflowNLUProvider
210
+ from nardial.providers.llm.openai_gpt import OpenAIGPTProvider
211
+ from nardial.conversation_agent import ConversationAgent
212
+
213
+ desktop = Desktop()
214
+ device = DesktopAdapter(desktop)
215
+ device.setup(logger=logging.getLogger())
216
+
217
+ tts = GoogleTTSProvider(
218
+ conf=GoogleTTSConf(speaking_rate=0.9, google_tts_voice_name="en-US-Neural2-F"),
219
+ device=device,
220
+ keyfile_path="conf/google/google_keyfile.json",
221
+ tts_cacher=TTSCacher(tts_cache_dir="tts_cache"),
222
+ )
223
+
224
+ nlu = DialogflowNLUProvider(
225
+ conf=DialogflowConf(keyfile_json=json.load(open("conf/google/google_keyfile.json"))),
226
+ mic=desktop.mic,
227
+ )
228
+
229
+ llm = OpenAIGPTProvider(api_key="<YOUR_OPENAI_KEY>")
230
+
231
+ agent = ConversationAgent(
232
+ device=device,
233
+ tts_provider=tts,
234
+ nlu_provider=nlu,
235
+ llm_provider=llm,
236
+ )
237
+ ```
238
+
239
+ ---
240
+
241
+ ### Pepper robot
242
+
243
+ Swap the device adapter and TTS provider — everything else stays the same:
244
+
245
+ ```python
246
+ import logging
247
+ from sic_framework.devices import Pepper
248
+
249
+ from nardial.providers.device.pepper import PepperAdapter
250
+ from nardial.providers.tts.naoqi import NaoqiTTSProvider
251
+
252
+ pepper = Pepper(ip="<PEPPER_IP>")
253
+ device = PepperAdapter(pepper)
254
+ device.setup(logger=logging.getLogger())
255
+
256
+ tts = NaoqiTTSProvider(device=device, language="en")
257
+ ```
258
+
259
+ Then pass `device` and `tts` to `ConversationAgent` as above.
260
+
261
+ ---
262
+
263
+ ### Using SessionManager
264
+
265
+ `SessionManager` wraps `ConversationAgent` and adds dialog loading, eligibility checking, and session state:
266
+
267
+ ```python
268
+ from nardial.session_manager import SessionManager
269
+
270
+ manager = SessionManager(
271
+ agent=agent,
272
+ dialog_file="dialogs/my_dialogs.json",
273
+ participant_id="user_42",
274
+ )
275
+ manager.run()
276
+ ```
277
+
278
+ ---
279
+
280
+ ## Defining Dialogs in JSON
281
+
282
+ All conversation content lives in a JSON file (or directory of JSON files). The file is a JSON array of dialog objects.
283
+
284
+ ### Dialog Structure
285
+
286
+ Every dialog has the following shared fields:
287
+
288
+ | Field | Type | Required | Description |
289
+ |---|---|---|---|
290
+ | `id` | string | ✅ | Unique identifier referenced in `session_agenda` and `dependencies` |
291
+ | `type` | string | ✅ | Dialog type: `"functional"`, `"chitchat"`, `"narrative"`, or `"llm_based"` |
292
+ | `moves` | array | ✅ | Ordered list of move objects the robot will perform |
293
+ | `dependencies` | array of strings | | Dialog IDs that must have been completed before this dialog may run |
294
+ | `variable_dependencies` | array | | Variables that must exist in the user model before this dialog may run |
295
+
296
+ Example:
297
+ ```json
298
+ {
299
+ "id": "greeting",
300
+ "type": "functional",
301
+ "functional_type": "greeting",
302
+ "moves": [
303
+ { "type": "say", "text": "Hi! I am your robot assistant." }
304
+ ]
305
+ }
306
+ ```
307
+
308
+ ---
309
+
310
+ ### Dialog Types
311
+
312
+ #### Optional Top-Level `characters`
313
+
314
+ Dialogs can define reusable character voices:
315
+
316
+ ```json
317
+ {
318
+ "characters": {
319
+ "narrator": {
320
+ "voice_settings": {
321
+ "voice_id": "KTPVrSVAEUSJRClDzBw7",
322
+ "language": "en"
323
+ }
324
+ }
325
+ }
326
+ }
327
+ ```
328
+
329
+ - `characters` is optional.
330
+ - Each key is a character name.
331
+ - Each value must contain a `voice_settings` object.
332
+ - `voice_settings` is validated at runtime against the active TTS provider.
333
+
334
+ ---
335
+
336
+ #### 1.`functional`
337
+
338
+ Utility dialogs for session management — greetings, farewells, and structural transitions.
339
+
340
+ | Extra field | Type | Required | Description |
341
+ |---|---|---|---|
342
+ | `functional_type` | string | ✅ | `"greeting"` or `"farewell"` |
343
+
344
+ ```json
345
+ {
346
+ "id": "welcome",
347
+ "type": "functional",
348
+ "functional_type": "greeting",
349
+ "moves": [
350
+ { "type": "say", "text": "Hello! What is your name?" },
351
+ {
352
+ "type": "ask_open",
353
+ "text": "Please tell me your name.",
354
+ "set_variable": "first_name",
355
+ }
356
+ ]
357
+ }
358
+ ```
359
+
360
+ ---
361
+
362
+ #### 2. `chitchat`
363
+
364
+ Short, theme-based conversations on everyday topics. Chitchat dialogs can be seeded with topics of interest so the system selects contextually relevant ones.
365
+
366
+ | Extra field | Type | Required | Description |
367
+ |---|---|---|---|
368
+ | `theme` | string | ✅ | Broad category (e.g. `"nature"`, `"animals"`, `"robots"`) |
369
+ | `topics` | array of strings | | Specific keywords used for relevance matching |
370
+
371
+ ```json
372
+ {
373
+ "id": "favorite_animal",
374
+ "type": "chitchat",
375
+ "theme": "animals",
376
+ "topics": ["animals", "pets"],
377
+ "moves": [
378
+ {
379
+ "type": "ask_open",
380
+ "text": "What is your favorite animal?",
381
+ "set_variable": "favorite_animal",
382
+ "add_interest_from_answer": true
383
+ },
384
+ { "type": "say", "text": "I like %favorite_animal% too!" }
385
+ ]
386
+ }
387
+ ```
388
+
389
+ ---
390
+
391
+ #### 3. `narrative`
392
+
393
+ Story-based dialogs that belong to a named thread and must be delivered in a specific order. Use `position` to sequence them and `dependencies` to enforce ordering.
394
+
395
+ | Extra field | Type | Required | Description |
396
+ |---|---|---|---|
397
+ | `thread` | string | ✅ | Story thread name (e.g. `"dreams"`) — groups related narrative dialogs |
398
+ | `position` | integer | ✅ | Order within the thread (1, 2, 3, …) |
399
+
400
+ ```json
401
+ {
402
+ "id": "dream_intro",
403
+ "type": "narrative",
404
+ "thread": "dreams",
405
+ "position": 1,
406
+ "moves": [
407
+ { "type": "say", "text": "Sometimes I make up stories when I'm turned off." },
408
+ {
409
+ "type": "ask_yesno",
410
+ "text": "Do you ever dream?",
411
+ "set_variable": "dreams_yesno",
412
+ "outcomes": { "yes": "dreams_yes" },
413
+ "default_outcome": "dreams_no"
414
+ },
415
+ {
416
+ "type": "branch",
417
+ "on": "outcome",
418
+ "cases": {
419
+ "dreams_yes": [
420
+ { "type": "say", "text": "That's great! Dreams are fascinating." }
421
+ ],
422
+ "dreams_no": [
423
+ { "type": "say", "text": "No problem, I'll tell you about mine!" }
424
+ ]
425
+ }
426
+ }
427
+ ]
428
+ }
429
+ ```
430
+
431
+ ---
432
+
433
+ #### 4. `llm_based`
434
+
435
+ A fully LLM-driven dialog where the robot and user engage in a free-form multi-turn exchange guided by a system prompt. No `moves` array is needed — the LLM generates all responses.
436
+
437
+ | Extra field | Type | Required | Description |
438
+ |---|---|---|---|
439
+ | `prompt` | string | ✅ | System prompt guiding the LLM's behavior |
440
+ | `max_turns` | integer | | Maximum back-and-forth turns (default: 5) |
441
+ | `speak_first` | boolean | | If `true` (default), the robot speaks first; if `false`, it listens first |
442
+ | `duration` | number | | Time limit in seconds for the whole exchange |
443
+ | `quit_phrases` | array of strings | | User utterances that end the exchange early |
444
+ | `quit_signal` | string | | Token the LLM can embed to signal it wants to end (default: `"<<QUIT>>"`) |
445
+ | `rag_enabled` | boolean | | Enable Retrieval-Augmented Generation (RAG). When `true`, the LLM retrieves context from a vector index before generating responses. Requires configuring a compatible RAG backend through `social-interaction-cloud`. |
446
+ | `index_name` | string | Required when `rag_enabled` is `true` | Name of the RAG index to query |
447
+
448
+ ```json
449
+ {
450
+ "id": "free_chat",
451
+ "type": "llm_based",
452
+ "prompt": "You are a friendly robot. Chat warmly with the child about their day. Ask follow-up questions. End with <<QUIT>> when the topic is exhausted.",
453
+ "max_turns": 6,
454
+ "speak_first": true,
455
+ "quit_signal": "<<QUIT>>",
456
+ "quit_phrases": ["goodbye", "stop", "done"],
457
+ "moves": []
458
+ }
459
+ ```
460
+
461
+ ---
462
+
463
+ ### Move Types
464
+
465
+ Moves are the individual steps inside a dialog's `moves` array. The robot executes them in order.
466
+
467
+ ---
468
+
469
+ #### `say`
470
+
471
+ Speaks a piece of text. Variable placeholders (`%variable_name%`) are replaced at runtime.
472
+
473
+ | Field | Type | Required | Description |
474
+ |---|---|---|---|
475
+ | `type` | string | ✅ | `"say"` |
476
+ | `text` | string | ✅ | Text to speak. Use `%var%` to insert stored variables. |
477
+ | `character` | string | | Optional character name from top-level `characters`; if omitted, default provider voice settings are used. |
478
+
479
+ ```json
480
+ { "type": "say", "text": "Nice to meet you, %first_name%!" }
481
+ ```
482
+
483
+ ---
484
+
485
+ #### `ask_open`
486
+
487
+ Asks a free-text question and listens for any spoken reply. The answer can be stored in a variable and used to drive branching.
488
+
489
+ | Field | Type | Required | Description |
490
+ |---|---|---|---|
491
+ | `type` | string | ✅ | `"ask_open"` |
492
+ | `text` | string | ✅ | The question to ask |
493
+ | `set_variable` | string | | Stores the extracted answer in the user model under this name |
494
+ | `outcomes` | object | | Maps answer values to outcome labels. Use `"*"` as a wildcard for any non-empty answer. |
495
+ | `default_outcome` | string | | Outcome label when no answer or no match is found |
496
+ | `add_interest_from_answer` | boolean | | If `true`, adds the answer to the user's topics of interest |
497
+ | `llm_followup` | string | | System prompt for an LLM-generated follow-up sentence after the user replies |
498
+
499
+ ```json
500
+ {
501
+ "type": "ask_open",
502
+ "text": "What is your favorite color?",
503
+ "set_variable": "favorite_color",
504
+ "add_interest_from_answer": true,
505
+ }
506
+ ```
507
+
508
+ ---
509
+
510
+ #### `ask_yesno`
511
+
512
+ Asks a yes/no question. The detected intent (`"yes"`, `"no"`, `"dontknow"`) drives branching.
513
+
514
+ | Field | Type | Required | Description |
515
+ |---|---|---|---|
516
+ | `type` | string | ✅ | `"ask_yesno"` |
517
+ | `text` | string | ✅ | The yes/no question to ask |
518
+ | `set_variable` | string | | Stores the answer in the user model |
519
+ | `outcomes` | object | | Maps `"yes"` / `"no"` / `"dontknow"` to outcome labels |
520
+ | `default_outcome` | string | | Outcome label used as fallback |
521
+ | `add_interest` | string | | Topic added to interest list when the user answers `"yes"` |
522
+ | `llm_followup` | string | | System prompt for an LLM-generated follow-up sentence |
523
+
524
+ ```json
525
+ {
526
+ "type": "ask_yesno",
527
+ "text": "Do you like dogs?",
528
+ "set_variable": "likes_dogs",
529
+ "add_interest": "dogs",
530
+ "outcomes": { "yes": "likes_dogs_yes" },
531
+ "default_outcome": "likes_dogs_no"
532
+ }
533
+ ```
534
+
535
+ ---
536
+
537
+ #### `ask_options`
538
+
539
+ Presents a multiple-choice question. The selected option drives branching and can be stored as a variable.
540
+
541
+ | Field | Type | Required | Description |
542
+ |---|---|---|---|
543
+ | `type` | string | ✅ | `"ask_options"` |
544
+ | `text` | string | ✅ | The question to ask |
545
+ | `options` | array of strings | ✅ | The choices presented to the user |
546
+ | `set_variable` | string | | Stores the selected option in the user model |
547
+ | `outcomes` | object | | Maps option values to outcome labels |
548
+ | `default_outcome` | string | | Outcome label when nothing matches |
549
+ | `add_interest_from_variable` | string | | After storing, adds the named variable's value as a topic of interest |
550
+ | `llm_followup` | string | | System prompt for an LLM-generated follow-up sentence |
551
+
552
+ ```json
553
+ {
554
+ "type": "ask_options",
555
+ "text": "Which activity do you prefer — reading, walking, or cooking?",
556
+ "options": ["reading", "walking", "cooking"],
557
+ "set_variable": "preferred_activity",
558
+ "outcomes": {
559
+ "reading": "chose_reading",
560
+ "walking": "chose_walking",
561
+ "cooking": "chose_cooking"
562
+ },
563
+ "default_outcome": "chose_other"
564
+ }
565
+ ```
566
+
567
+ ---
568
+
569
+ #### `ask_llm`
570
+
571
+ Starts a multi-turn LLM-driven exchange *within* an otherwise scripted dialog. Useful for a single free-form segment inside a structured conversation.
572
+
573
+ | Field | Type | Required | Description |
574
+ |---|---|---|---|
575
+ | `type` | string | ✅ | `"ask_llm"` |
576
+ | `prompt` | string | ✅ | System prompt for the LLM |
577
+ | `max_turns` | integer | | Maximum turns (default: 5) |
578
+ | `set_variable` | string | | Stores the user's last reply in the user model |
579
+ | `quit_phrases` | array of strings | | User phrases that end the exchange early |
580
+ | `quit_signal` | string | | Token the LLM emits to signal it wants to stop |
581
+
582
+ ```json
583
+ {
584
+ "type": "ask_llm",
585
+ "prompt": "Ask one follow-up question to help the user commit to their plan for today. Keep it short and friendly.",
586
+ "max_turns": 1,
587
+ "set_variable": "plan_commitment"
588
+ }
589
+ ```
590
+
591
+ ---
592
+
593
+ #### `branch`
594
+
595
+ Selects and executes a list of sub-moves based on the current outcome or the value of a user model variable.
596
+
597
+ | Field | Type | Required | Description |
598
+ |---|---|---|---|
599
+ | `type` | string | ✅ | `"branch"` |
600
+ | `on` | string | ✅ | `"outcome"` to branch on the last question's result, or a variable name to branch on its stored value |
601
+ | `cases` | object | ✅ | Maps condition values to arrays of sub-moves |
602
+
603
+ ```json
604
+ {
605
+ "type": "branch",
606
+ "on": "outcome",
607
+ "cases": {
608
+ "chose_reading": [
609
+ { "type": "say", "text": "Reading is a wonderful way to relax." }
610
+ ],
611
+ "chose_walking": [
612
+ { "type": "say", "text": "A walk sounds refreshing!" }
613
+ ]
614
+ }
615
+ }
616
+ ```
617
+
618
+ Branching on a stored variable (e.g. to react to an answer from an earlier dialog):
619
+
620
+ ```json
621
+ {
622
+ "type": "branch",
623
+ "on": "energy_level",
624
+ "cases": {
625
+ "high": [{ "type": "say", "text": "Start with a longer session." }],
626
+ "low": [{ "type": "say", "text": "Begin with just 10 calm minutes." }]
627
+ }
628
+ }
629
+ ```
630
+
631
+ ---
632
+
633
+ #### `play`
634
+
635
+ Plays an audio file through the device's speakers.
636
+
637
+ | Field | Type | Required | Description |
638
+ |---|---|---|---|
639
+ | `type` | string | ✅ | `"play"` |
640
+ | `audio` | string | ✅ | Path to the audio file (`.wav` or `.mp3`) |
641
+
642
+ ```json
643
+ { "type": "play", "audio": "audio/chime.wav" }
644
+ ```
645
+
646
+ ---
647
+
648
+ #### `motion_sequence`
649
+
650
+ Plays a predefined motion sequence on the robot (Pepper / NAO).
651
+
652
+ | Field | Type | Required | Description |
653
+ |---|---|---|---|
654
+ | `type` | string | ✅ | `"motion_sequence"` |
655
+ | `motion_sequence` | string | ✅ | Path or name of the motion sequence file |
656
+
657
+ ```json
658
+ { "type": "motion_sequence", "motion_sequence": "motions/Stand/Emotions/Positive/Happy_1" }
659
+ ```
660
+
661
+ ---
662
+
663
+ #### `animation`
664
+
665
+ Triggers a named animation behavior on the robot.
666
+
667
+ | Field | Type | Required | Description |
668
+ |---|---|---|---|
669
+ | `type` | string | ✅ | `"animation"` |
670
+ | `animation_name` | string | ✅ | Name of the animation to play |
671
+
672
+ ```json
673
+ { "type": "animation", "animation_name": "animations/Stand/Gestures/Enthusiastic_4" }
674
+ ```
675
+
676
+ ---
677
+
678
+ ### Key JSON Attributes
679
+
680
+ | Attribute | Where used | Description |
681
+ |---|---|---|
682
+ | `id` | dialog | Unique dialog identifier |
683
+ | `type` | dialog | Dialog type: `functional`, `chitchat`, `narrative`, `llm_based` |
684
+ | `functional_type` | functional dialog | `"greeting"` or `"farewell"` |
685
+ | `thread` | narrative dialog | Story thread name |
686
+ | `position` | narrative dialog | Order within the thread |
687
+ | `theme` | chitchat dialog | Broad topic category |
688
+ | `topics` | chitchat dialog | Keywords for relevance matching |
689
+ | `prompt` | llm_based dialog / `ask_llm` | LLM system prompt |
690
+ | `moves` | dialog | Ordered list of move objects |
691
+ | `dependencies` | dialog | Dialog IDs that must be completed first |
692
+ | `variable_dependencies` | dialog | Variables that must exist in the user model |
693
+ | `set_variable` | move | Saves the user's answer into the user model |
694
+ | `outcomes` | move | Maps answers/intents to outcome labels for branching |
695
+ | `default_outcome` | move | Fallback outcome when no match is found |
696
+ | `add_interest` | `ask_yesno` move | Adds a fixed topic when user says yes |
697
+ | `add_interest_from_answer` | `ask_open` move | Adds the spoken answer as a topic of interest |
698
+ | `add_interest_from_variable` | `ask_open` / `ask_options` move | Adds the stored variable's value as a topic |
699
+ | `llm_followup` | ask moves | System prompt for an inline LLM follow-up response |
700
+ | `%variable%` | `text` values | Placeholder replaced at runtime with the stored variable value |
701
+
702
+ ---
703
+
704
+ ## Demos / Creating a Session
705
+
706
+ 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.
707
+
708
+ Two ready-to-run demos are included in the `examples/` directory:
709
+ * Demo 1 — General Conversation (`demo_general_conversation.py`): A simple four-step conversation using a mix of narrative and functional dialogs
710
+ * 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`
711
+
712
+ You can find additional demos in the [SIC Applications repository](https://github.com/Social-AI-VU/sic_applications/tree/main/demos/nardial)
713
+
714
+ ---
715
+
716
+ ## Development
717
+
718
+ Run tests from the repository root:
719
+
720
+ ```bash
721
+ python -m pytest -q
722
+ ```
723
+
724
+ ---