# Garder votre compétence réactive

> SkillKit gère ressources en cache, sons variés, appels de service bornés, actualisation en arrière-plan et persistance facultative à votre place.

Source: https://docs.thalovant.com/fr/developers/skillkit-runtime/

---
{/* Last Edit: Codex - 2026-10-05 - Motive: Align the French SkillKit guides with the tested 0.24.2 tutorial and public helpers. */}

Une compétence devrait passer son temps à répondre à la personne, plutôt qu'à retrouver les mêmes
fichiers ou à attendre trop longtemps un service. SkillKit fournit de petits
outils pour ce travail répétitif. Choisissez seulement ceux dont votre compétence a besoin.

Commencez par [Créer un quiz éducatif](/fr/developers/skillkit-tutorial/) si vous
n'avez pas encore écrit de compétence. Ces exemples prolongent un projet existant. Installez avec
`python -m pip install --pre "thalovant-skillkit==0.24.2"`, et déclarez `thalovant-skillkit>=0.24.2`
dans les dépendances de votre projet.

## Choisir un outil

| Ce que vous ajoutez | Utilisez | Votre compétence décide toujours |
| --- | --- | --- |
| De nombreux sons empaquetés | `bundled_files` et `ShuffleBagPool` | Quel son convient au jeu et quel haut-parleur l'entend |
| Plusieurs réponses traduites | `self.speak_varied_dialog` | Les mots eux-mêmes et le moment d'inviter une réponse |
| Un service en ligne | `JsonServiceClient` et `RequestBudget` | La requête, la validation et la réponse d'indisponibilité |
| Un catalogue qui s'actualise de temps en temps | `PeriodicWorker` | La fraîcheur, l'annulation et le remplacement sûr du cache |
| Un état Redis/PostgreSQL facultatif | `JsonStateStore` | La validation des enregistrements, leur propriété et la récupération locale |
| Une partie ou un brouillon par joueur | `SessionStateStore` | Ce qu'un tour modifie, et quand une partie est terminée |
| Des alias ou des listes de blocage régionaux | `SkillResources.combined_lines` | Les langues que la fonctionnalité doit accepter |

Ces outils n'ajoutent pas d'appel de service à une compétence hors ligne. Les importer ne
démarre aucune tâche de fond et ne se connecte à aucun stockage. OVOS gère toujours la parole, l'audio,
l'acheminement des intentions, les conversations et les alarmes planifiées.

## Trouver les sons une fois, puis choisir vite

Imaginez une boîte de cartes de sons d'animaux. Vous faites d'abord la liste des cartes, puis vous piochez dans
cette liste chaque fois que quelqu'un demande. Vous ne déballez pas toute la boîte à chaque tour.

```python
from pathlib import Path
from thalovant_skillkit.assets import bundled_files
from thalovant_skillkit.selection import ShuffleBagPool

sound_dir = Path(__file__).parent / "sounds"
sounds = bundled_files(sound_dir, "*.ogg")
choices = ShuffleBagPool()

# Run inside an intent handler after adding at least two sounds to your package:
# chosen = choices.draw(sounds)[0]
# self.play_audio(str(chosen))
```

Créez `choices` une seule fois par instance de la compétence. `bundled_files` mémorise un tuple trié
de noms de fichiers; il ne charge pas l'audio en mémoire. Le cache contient au plus 128 combinaisons
répertoire/motif. Le premier appel parcourt le répertoire; les appels suivants
réutilisent la liste. N'oubliez pas d'inclure vos sons dans la wheel construite.

**Vérifiez votre résultat :** appelez `bundled_files(sound_dir, "*.ogg")` deux fois, puis examinez
`bundled_files.cache_info()`. Le deuxième appel fait augmenter `hits`. Piochez deux fois dans une
réserve qui contient au moins deux fichiers distincts : les choix diffèrent. Une réserve d'un seul fichier
ne peut renvoyer que ce fichier. La sélection seule ne fait pas jouer d'audio.

Pour les réponses, ajoutez plusieurs lignes au fichier `.dialog` traduit habituel et appelez :

```python
# Inside an intent handler; translate quiz.correct.dialog in each supported language.
self.speak_varied_dialog("quiz.correct", {"name": "Sam"})
```

Kit mémorise les choix, tandis qu'OVOS gère le rendu, les remplacements de ressources et
la parole. Si vous rechargez un remplacement avec des lignes différentes, son sac de sélection est
remplacé. Le `speak_dialog` ordinaire garde son comportement normal. Pour plusieurs joueurs,
conservez le dernier son de chaque joueur dans l'état de session et passez-le sous la forme `avoid=last_sound`.
Kit évite ce choix quand un autre est disponible.

## Donner un budget de temps au travail en ligne

Un délai d'expiration est la durée pendant laquelle une opération peut attendre. Un budget de réponse est le temps
partagé dont disposent toutes les opérations de cette réponse. Sans budget, trois
appels d'une seconde pourraient faire attendre quelqu'un environ trois secondes.

```python
from thalovant_skillkit.network import JsonServiceClient, RequestBudget

# Create these once in your skill instance.
budget = RequestBudget()
service = JsonServiceClient("garden-watering", "1.0.0", budget=budget)

# Use your configured service URL. An empty URL means no service is configured.
service_url = ""
with budget.limit(2.0):
    result = service.post(service_url, {"question": "does the garden need water?"},
                          timeout=1.0)

if result is None:
    answer = "I cannot check the garden right now."
else:
    # Validate the service's fields before using them in your real skill.
    answer = str(result.get("answer", "I could not find an answer."))
```

**Vérifiez votre résultat :** avec l'URL vide, `result` vaut `None` immédiatement et le
texte de repli est sélectionné. Avec un service configuré, un objet JSON valide devient
un dictionnaire. Une requête échouée, une réponse invalide ou un budget épuisé renvoie
`None`; votre compétence décide comment répondre.

Le client envoie des en-têtes d'identification et d'identifiant de requête, plafonne les réponses à 2 Mio par
défaut et tente chaque requête une seule fois. Il ne réessaie pas les requêtes POST qui ont peut-être
déjà effectué une action. Après un échec, il ignore cette URL pendant 30 secondes
par défaut, pour que les tours suivants n'attendent pas à répétition le même service en panne.
Les autres URL restent disponibles. Le délai de récupération compte au plus 128 entrées. Les réponses
réussies ne sont ni mises en cache ni partagées entre haut-parleurs.

Les échéances sont distinctes pour les fils d'exécution concurrents, et un budget imbriqué ne peut pas prolonger
son parent. Elles sont vérifiées entre les lectures; elles ne peuvent pas interrompre de force un
socket bloqué ni une résolution DNS. Utilisez toujours aussi un délai d'expiration de transport fini. Une lecture
bloquée peut se terminer jusqu'à un délai d'expiration de socket après l'épuisement du budget.

Pour une compétence qui se charge hors ligne mais qui a besoin d'Internet pour certaines fonctionnalités, utilisez ces
attributs de classe au lieu de recopier une propriété `RuntimeRequirements` :

```python
REQUIRES_INTERNET = True
INTERNET_BEFORE_LOAD = False
NO_INTERNET_FALLBACK = True
```

Votre compétence doit réellement implémenter sa réponse hors ligne. Ces indicateurs décrivent ce
comportement à OVOS; ils ne créent pas la réponse.

## Actualiser loin de la réponse

News actualise les adresses audio en arrière-plan. Une personne qui écoute peut alors utiliser une adresse
récente sans attendre que chaque flux soit vérifié de nouveau. Le même modèle convient
à un petit catalogue public qui tolère d'être un peu vieux.

```python
from thalovant_skillkit.workers import PeriodicWorker

# In your skill's initialize(), after any required super().initialize():
self.refresh = PeriodicWorker(self.refresh_catalog, interval=600,
                              name="garden-catalog-refresh")
self.refresh.start()

# In shutdown(), before super().shutdown():
self.refresh.stop(timeout=1.0)
```

Implémentez `refresh_catalog(self, stop_event)`. Bornez chaque opération réseau, vérifiez
`stop_event.is_set()` entre les éléments et ne remplacez un instantané complet qu'après
l'avoir validé. Gardez les verrous brefs; ne tenez pas un verrou de cache pendant un téléchargement.

**Vérifiez votre résultat :** une première actualisation s'exécute immédiatement. La suivante commence 600
secondes après la fin de cette passe. Appeler `start()` de nouveau ne crée pas de deuxième
tâche de fond. Les exceptions terminent la passe en cours et sont journalisées; la passe suivante attend l'intervalle
normal. `stop()` interrompt l'attente inactive et indique si le fil d'exécution s'est terminé.
Il ne peut pas tuer un rappel qui ignore l'annulation ou qui bloque indéfiniment.

Utilisez le planificateur d'OVOS pour la minuterie ou l'alarme d'une personne. Cette tâche de fond sert au travail
d'actualisation facultatif, pas aux échéances exactes. La compétence décide combien de temps les données restent utilisables
et quoi dire si elles sont trop vieilles. Ne mettez pas de détails privés dans les messages d'exception.

## Sauvegarder l'état sans recopier le code du serveur de stockage

Utilisez `JsonStateStore` quand votre compétence a besoin d'un stockage partagé facultatif. Sans
serveur de stockage configuré, un nouveau magasin renvoie `None` depuis `load()`, pour que votre compétence puisse
utiliser sa solution de repli locale.

```python
from thalovant_skillkit.storage import JsonStateStore

store = JsonStateStore("household", {}, env_prefix="GARDEN", default_key="watering")
records = store.load()
```

Installez `redis` pour Redis/Sentinel et `psycopg` pour PostgreSQL si vous les utilisez.
Les réglages ont priorité sur l'environnement préfixé de la compétence, qui a lui-même priorité sur les variables
d'environnement génériques du serveur de stockage. Vérifiez le résultat de chaque sauvegarde : configurer un stockage partagé
ne garantit pas à lui seul qu'un enregistrement a été sauvegardé. Votre compétence conserve la récupération locale, valide les enregistrements et
vérifie à qui ils appartiennent. N'utilisez pas cet outil pour contourner l'API de stockage propre à un service.

Consultez le [contrat de stockage](https://github.com/thalovant/thalovant-skillkit/blob/main/docs/reference.md#optional-shared-persistence)
pour les clés de configuration, les réglages, les valeurs de retour et le comportement en cas d'échec de connexion.

## Garder une partie par joueur

Deux personnes peuvent jouer au quiz en même temps, et un pointage partagé mélangerait leurs parties. `SessionStateStore` garde une entrée par identifiant de session : la compétence lit l'identifiant dans le message et stocke la partie de ce joueur sous cet identifiant.

```python
from thalovant_skillkit.sessions import SessionStateStore

# Once per skill instance: at most 128 players, forgotten after 90 quiet seconds.
self.games = SessionStateStore(max_entries=128, default_ttl=90)

# In a turn:
with self.games.lock:
    game = self.games.get(session_id)
    ...
    self.games[session_id] = updated_game
```

Écrire une entrée relance son compte à rebours; la lire ne le relance pas. Quand le magasin est plein, l'entrée la plus ancienne s'en va. L'expiration est vérifiée quand le magasin est utilisé : rien ne s'exécute en arrière-plan. Le magasin appartient à une seule instance de la compétence; une deuxième instance ne voit pas ses parties. Tenez le verrou le temps de la décision, pas pendant un appel réseau : le quiz réserve le tour du joueur, libère le verrou pendant qu'il récupère les questions, puis vérifie qu'aucun Stop n'est arrivé entre-temps.

**Vérifiez votre résultat :** lancez deux parties avec des identifiants de session différents et répondez dans l'une; le pointage de l'autre ne change pas. Avancez une horloge au-delà du TTL : la partie disparaît. Lisez le [`game.py`](/fr/developers/skillkit-tutorial/#leçon-3--créer-le-jeu) du tutoriel pour la version complète.

## Savoir quand actualiser un cache

| Données en cache | Quand elles changent | Comment les actualiser |
| --- | --- | --- |
| Noms des fichiers sonores fournis | Vous installez ou modifiez les ressources du paquet | Un redémarrage du processus, ou `bundled_files.cache_clear()` entre deux tours |
| Texte de langue et alias combinés | Vous installez ou modifiez des traductions | Une nouvelle instance de ressources, ou `self.locale_resources.clear_cache()` entre deux tours; une partie déjà en cours garde les questions avec lesquelles elle a commencé |
| Historique de sélection des dialogues et des sons | Les choix ou leur clé explicite changent | Kit remplace une réserve modifiée; l'éviction n'oublie que l'historique |
| Délai de récupération après un échec de service | Son intervalle expire | Kit autorise automatiquement une nouvelle requête |
| Réponses de service ou état personnel | Dépend de l'application | Aucun cache générique des réponses n'est ajouté; définissez au besoin une politique de fraîcheur circonscrite |

`combined_lines(lang, folder, filename)` rassemble les alias ou motifs acceptés dans
l'ordre de préférence des langues, y compris la langue par défaut configurée. Son cache de résultats
combinés contient au plus 512 entrées. Les dialogues parlés devraient utiliser la recherche de dialogue habituelle
pour répondre dans une seule langue. Vider le cache ne coordonne pas les modifications de fichiers
simultanées : rechargez entre deux tours. Utilisez des lectures de répertoire ordinaires pour les fichiers
que les personnes téléversent en direct, pas le cache des fichiers fournis.

`matches_literal_intent` garde aussi en cache les phrases normalisées de chaque
ressource et ensemble de langues compatibles. Réutilisez `self.locale_resources`
entre les tours et passez un nom de ressource fixe, comme `pocket_quiz.start`.
Votre compétence n’a pas besoin d’un deuxième cache. Après une modification des
ressources, `clear_cache()` vide aussi ces correspondances et la recherche des
fichiers jumeaux vocaux.

## Mesurer la partie que vous avez modifiée

Mesurez la même action avant et après votre modification, avec les mêmes versions de Python et
d'OVOS. Réchauffez d'abord le cache, répétez l'action et comparez plusieurs exécutions
sans que d'autres tests se disputent le processeur. Mesurez aussi un processus neuf : une requête rapide
à chaud peut quand même avoir un démarrage lent.

Séparez le temps de sélection local de la latence du service, de la synthèse vocale et du haut-parleur.
Mettre une liste de sons en cache accélère la sélection; cela ne fait pas démarrer plus tôt un appareil audio
lent. Testez à la fois les accès au cache et son invalidation, et utilisez un banc d'essai sans réseau
pour le travail local. La [référence SkillKit](https://github.com/thalovant/thalovant-skillkit/blob/main/docs/reference.md)
et les [vérifications audio](/fr/developers/writing-a-skill/#vérifier-le-son-et-lacheminement-de-stop) couvrent
les contrats des outils et les vérifications supplémentaires pour de vrais tours parlés.
