Skip to content

Commit a1f22ab

Browse files
committed
Copy over "Adding skill" from README
1 parent 33bc946 commit a1f22ab

2 files changed

Lines changed: 144 additions & 2 deletions

File tree

book.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
[book]
22
language = "en"
3-
multilingual = false
43
src = "src"
54
title = "Dicio developer documentation"
65

76
[output.html]
87
git-repository-url = "https://github.com/DicioTeam/docs"
98
git-repository-icon = "fas-code-fork"
109
edit-url-template = "https://github.com/DicioTeam/docs/edit/main/{path}"
10+
admonitions = true

src/adding_skill.md

Lines changed: 143 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,143 @@
1-
# Chapter 1
1+
# Adding a new skill
2+
3+
A skill is a component that enables the assistant to **understand** some specific queries from the user and **act** accordingly. While reading the following instructions, keep in mind that the **javadoc** of the methods being implemented serves as documentation, and that the code of the already implemented skills can be used as reference.
4+
5+
> [!IMPORTANT]
6+
> Whenever you see `$skill_id$` and `$SkillId$`, replace them with the computer readable name of the skill you want to add, in snake_case and PascalCase, e.g. `weather` or `Weather`.
7+
8+
### 1. Reference sentences
9+
10+
The new skill most likely needs to interpret user input. The Dicio framework provides a *standard* way to define how to efficiently match user input and extract information from it, in the form of translatable reference sentences stored in YAML files. Note that *for some specific cases* the standard recognizer might not be wanted, in which case you can skip this section, and in section 3 extend `Skill<>` and implement `Skill.score()` manually, instead of extending `StandardRecognizerSkill<>`.
11+
12+
1. Edit the `app/src/main/sentences/skill_definitions.yml` file and add a definition for the new skill:
13+
```yaml
14+
# The unique ID of the skill.
15+
- id: $skill_id$
16+
# `SPECIFICITY` can be `high`, `medium` or `low`.
17+
# It should be chosen wisely: for example, a section that matches queries
18+
# about phone calls is very specific, while one that matches every question
19+
# about famous people has a lower specificity.
20+
specificity: SPECIFICITY
21+
# A list of definitions for the types of sentences this skill can interpret.
22+
# Can contain multiple sentences, e.g. the timer skill has the
23+
# "set", "cancel" and "query" sentences.
24+
sentences:
25+
# An ID for the sentence, must be unique amongst this skill's sentences.
26+
- id: SENTENCE_1_ID
27+
# (optional) If this sentence has some capturing groups, their IDs and
28+
# types must be listed here.
29+
captures:
30+
# An ID for the capturing group, must be unique amongst this
31+
# sentence's capturing groups
32+
- id: CAPTURING_GROUP_1_ID
33+
# Currently only string capturing groups are supported, but in
34+
# the future "number", "duration" and "date" will also be possible.
35+
# For the moment use "string" and then manually parse the string to
36+
# number, duration or date using dicio-numbers.
37+
type: string
38+
```
39+
40+
2. Create a file named `$skill_id$.yml` (e.g. `weather.yml`) under `app/src/main/sentences/en/`: it will contain the **sentences** the skill should recognize.
41+
3. For each of the sentence definitions in `skill_definitions.yml`, write the id of each sentence type followed by `:` and a list of sentences:
42+
```yaml
43+
SENTENCE_1_ID:
44+
- a<n?> sentence|phrase? alternative # ...
45+
- another sentence|phrase? alternative with .CAPTURING_GROUP_1_ID. # ...
46+
# ...
47+
# SENTENCE_2_ID: ... in case you have multiple sentence types
48+
```
49+
4. Write the reference sentences according to the [`dicio-sentences-language`'s syntax](https://github.com/Stypox/dicio-sentences-compiler#dicio-sentences-language).
50+
51+
5. Try to *build* the app: if it succeeds you did everything right, otherwise you will get errors pointing to syntax errors in the `.yml` files.
52+
53+
Here is an example of the weather skill definition in `skill_definitions.yml`:
54+
```yaml
55+
- id: weather
56+
specificity: high
57+
sentences:
58+
- id: current
59+
captures:
60+
- id: where
61+
type: string
62+
```
63+
64+
And these are the example contents of `app/src/main/sentences/en/weather.yml`:
65+
```yaml
66+
current:
67+
- (what is|s)|whats the weather like? (in|on .where.)?
68+
- weather (in|on? .where.)?
69+
- how is it outside
70+
```
71+
72+
### 2. Subpackage
73+
Create a **subpackage** that will contain all of the classes you are about to add: `org.stypox.dicio.skills.SKILLID` (e.g. `org.stypox.dicio.skills.weather`).
74+
75+
### 3. The Skill class
76+
Create a class named `$SkillId$Skill` (e.g. `WeatherSkill`): it will contain the code that interprets user input (i.e. the `score()` function) and that processes it to generate output (i.e. the `generateOutput()` function). The next few points assume that you want to use the *standard* recognizer with the skill definition and sentences you created in [step 1](#1-reference-sentences). In that case `score()` is actually already implemented and you don't need to provide an implementation yourself.
77+
78+
1. Have the `$SkillId$Skill` class implement `StandardRecognizerSkill<$SkillId$>`. You can import the `$SkillId$` class with `import org.stypox.dicio.sentences.Sentences.$SkillId$`. The `Sentences.$SkillId$` sealed class is generated based on `skill_definitions.yml`, and contains one subclass for each of the defined sentence types.
79+
2. The constructor of `Skill` takes `SkillInfo` (see [step 5](#5-skillinfo)), and moreover the constructor of `StandardRecognizerSkill` takes `StandardRecognizerData<$SkillId$>` (the data generated from the sentences, see [step 5](#5-skillinfo)). You should expose these two parameters in `$SkillId$Skill`'s constructor, too.
80+
3. Implement the following function: `override suspend fun generateOutput(ctx: SkillContext, inputData: $SkillId$): SkillOutput`. `inputData` is, again, an instance of `Sentences.$SkillId$` corresponding to the matched sentence type, and its fields contain type-safe information about the data captured in capturing groups (if any).
81+
4. Any code making *network requests or heavy calculations* should be put in `generateOutput` (which is a suspend function for this exact purpose). The returned `SkillOutput` should contain all of the data needed to actually show the output, i.e. it shouldn't do any more network requests or calculations (unless it's an interactive widget and the user presses some button, but that's not too relevant for the matter at hand).
82+
83+
This is a stub implementation of the `WeatherSkill`:
84+
85+
```kotlin
86+
package org.stypox.dicio.skills.weather
87+
import org.stypox.dicio.sentences.Sentences.Weather
88+
// ...
89+
class WeatherSkill(correspondingSkillInfo: SkillInfo, data: StandardRecognizerData<Weather>) :
90+
StandardRecognizerSkill<Weather>(correspondingSkillInfo, data) {
91+
override suspend fun generateOutput(ctx: SkillContext, inputData: Weather): SkillOutput {
92+
return // ...
93+
}
94+
}
95+
```
96+
97+
### 4. SkillOutput
98+
99+
Create a class named `$SkillId$Output` (e.g. `WeatherOutput`): it will contain the code that creates a Jetpack Compose UI and provides speech output.
100+
101+
1. The class should be constructed by `Skill.generateOutput()` with all of the data needed to display/speak output, and is meant to be serializable (so in most cases it is a `data class`). In some cases it might make sense to have multiple types of output (e.g. the weather has `Success` and `Failed` output types): in that case you can create a `sealed interface` and have both output types extend it.
102+
2. `getSpeechOutput()` returns a localized string that will be spoken via the configured Text To Speech service.
103+
3. `@Composable GraphicalOutput()` builds the UI that will be shown in a box on the home screen. The UI can be interactive and can act as a widget: for example the timer skill shows the ongoing countdown.
104+
4. _\[Optional\]_ `getNextSkills()` returns a list of skills that could continue the current conversation. If this list is non-empty, the next time the user asks something to the assistant, these skills will be considered before all other skills, and if any of these skills understands the user input well enough, the conversation continues. For example, if the user says "Call Mom", the assistant may answer with "Should I call mom?" and this method would return a skill that can understand a yes/no response.
105+
106+
This is a stub implementation of `WeatherOutput`:
107+
```kotlin
108+
data class WeatherOutput(
109+
val city: String,
110+
val description: String,
111+
// ...
112+
) : WeatherOutput {
113+
override fun getSpeechOutput(ctx: SkillContext): String = ctx.getString(
114+
R.string.skill_weather_in_city_there_is_description, city, description
115+
)
116+
117+
@Composable
118+
override fun GraphicalOutput(ctx: SkillContext) {
119+
// Jetpack Compose UI
120+
}
121+
}
122+
```
123+
124+
### 5. SkillInfo
125+
Create an `object` named `$SkillId$Info` (e.g. `WeatherInfo`) overriding `SkillInfo`: it will contain all of the **information needed to manage your skill**.
126+
1. This is not a class, but an `object`, because it makes no sense to instantiate it multiple times.
127+
2. Call the `SkillInfo` constructor with the `"$skill_id$"` string.
128+
3. Provide sensible values for `name()`, `sentenceExample()` and `icon()`.
129+
4. Override the `isAvailable()` method and return whether the skill can be used under the *circumstances* the user is in (e.g. check whether the recognizer sentences are translated into the user language with `Sentences.$SkillId$[ctx.sentencesLanguage] != null` (see [step 1](#1-reference-sentences) and step 5.5) or check whether `ctx.parserFormatter != null`, if your skill uses number parsing and formatting).
130+
5. Override the `build()` method so that it returns an instance of `$SkillId$Skill` with `$SkillId$Info` as `correspondingSkillInfo` and, if the skill uses standard recognizer sentences (see [step 1](#1-reference-sentences)), with `Sentences.$SkillId$[ctx.sentencesLanguage]` as `data`.
131+
4. _\[Optional\]_ If your skill wants to present some preferences to the user, it has to do so by overriding the `renderSettings` value (which by default returns `null` to indicate there are no preferences).
132+
133+
### 6. List skill inside SkillHandler
134+
Under `org.stypox.dicio.Skills.SkillHandler`, update the `allSkillInfoList` by adding `$SkillId$Info`; this will make the new skill finally visible to Dicio.
135+
136+
### 7. Add skill to README and descriptions
137+
Add your skill with a short description and an example in the README under [Skills](https://github.com/Stypox/dicio-android#skills) and in the [fastlane's long description](https://github.com/Stypox/dicio-android/tree/master/fastlane/metadata/android/en-US/full_description.txt).
138+
139+
### **Notes**
140+
- The `ctx: SkillContext` object, that appears here and there in the implementation, allows accessing the Android context, the number parser/formatter and other **resources** and services, similarly to Android's `context`.
141+
- The **names** used for things (files, classes, packages, sections, etc.) are not mandatory, but they help **avoiding confusion**, so try to stick to them.
142+
- When committing changes about a skill, prefix the commit message with "[\$SkillId\$]", e.g. "[Weather] Fix crash".
143+
- If you have any question, **don't hesitate** to ask. 😃

0 commit comments

Comments
 (0)