Aller au contenu
Console
Try one

    SDK Python

    Installez le SDK Python de Thalovant pour vos agents, services, outils en ligne de commande, carnets et clients de test.

    Utilisez le SDK Python quand votre client ou votre agent s’exécute en Python.

    Le nom du paquet est thalovant. Python 3.10 ou plus récent est requis.

    Installez le paquet avec pip install thalovant :

    Fenêtre de terminal
    python -m pip install thalovant

    Épinglez la version quand vous avez besoin d’une compilation reproductible. La version actuelle figure dans Versions prises en charge.

    Depuis la version 0.9.0, le paquet installe deux bibliothèques, aiohttp et cryptography, et n’importe ni l’une ni l’autre avant la première connexion ou le premier appel à l’API. Des extras ajoutent le reste :

    • thalovant[mqtt] pour MQTT.
    • thalovant[yaml] pour lire le fichier de configuration avec PyYAML. Sans lui, un petit lecteur intégré gère ce que contient un fichier de configuration.
    • thalovant[listing] pour les règles linguistiques qu’utilise la liste des intentions.

    Le SDK est bâti sur asyncio. AsyncThalovantClient et AsyncThalovantControlPlane font le travail, et ThalovantClient et ThalovantControlPlane exécutent le même code sur un fil d’exécution privé; un script n’a donc pas besoin de sa propre boucle d’événements. Le code écrit pour 0.8 fonctionne sans changement; consultez Passer à 0.9.

    Ce flux découvre un hub public, crée une identité de client et envoie une requête.

    from thalovant import ThalovantClient, ThalovantControlPlane
    api = ThalovantControlPlane()
    hub = api.list_public_hubs(limit=1)["data"][0]
    print(hub["title"], hub["slug"])
    api.login("[email protected]", "password")
    result = api.create_client_identity(
    hub["id"],
    name="python-demo-client",
    preferred_protocols=("wss", "https", "mqtt"),
    )
    with ThalovantClient(result.identity, protocol="wss") as client:
    reply = client.ask("Tell me a short clean joke.")
    print(reply.text)

    ThalovantControlPlane() utilise https://api.thalovant.com par défaut.

    Le même flux dans une application asyncio :

    import asyncio
    from thalovant import AsyncThalovantClient, AsyncThalovantControlPlane
    async def main():
    async with AsyncThalovantControlPlane() as api:
    await api.login("[email protected]", "password")
    result = await api.create_client_identity("hub-id", name="python-demo-client")
    async with AsyncThalovantClient(result.identity) as client:
    reply = await client.ask("Tell me a short clean joke.")
    print(reply.text)
    asyncio.run(main())

    Python 0.7.4 offre des utilitaires pratiques pour les clients vocaux. Les versions synchrone et asynchrone de ask() acceptent stt_lang, pipeline et location. stt_lang transmet l’indice de langue du moteur de reconnaissance; pipeline choisit les étapes d’intention configurées sous la session de la requête. build_location() construit un indice d’emplacement, et request_context() fusionne ces options avec votre contexte existant.

    from thalovant import build_location, speakable
    location = build_location(city="Toronto", country="Canada", timezone="America/Toronto")
    reply = client.ask("What is the weather?", stt_lang="en-ca", location=location)
    print(reply.text, reply.lang)
    print(speakable("[please] tell me about {city}", slots={"city": "Toronto"}))

    reply.lang indique la langue de réponse du hub. reply.media_events conserve la parole et les événements mycroft.audio.queue intégrés dans leur ordre d’arrivée. Pour un événement audio, event.audio_bytes() décode son extrait hexadécimal; il ne va chercher ni chemin ni URL à partir de l’événement. Les extraits invalides lèvent ValueError. Les limites de collecte sont de 4 Mio par extrait et de 16 Mio par réponse; reply.dropped_media compte les extraits omis. Gardez reply_settle_seconds au-dessus de zéro pour collecter les extraits qui arrivent avec la parole.

    HubIntent.examples(speakable=True, slots={...}) et thalovant intents --speakable produisent des phrases d’exemple. Quand vous limitez les résultats, les phrases sources complètes passent avant les phrases dérivées des emplacements (slots). Ces méthodes pratiques propres à Python sont distinctes des fonctionnalités communes de gestion des compétences que prennent en charge les sept SDK gérés.

    Se connecter avec l’authentification multifacteur

    Section intitulée « Se connecter avec l’authentification multifacteur »

    Les comptes où l’authentification multifacteur (AMF) est activée doivent fournir un code TOTP ou un code de récupération à usage unique avec la connexion. Sans l’un ou l’autre, l’API refuse la connexion avec HTTP 401 et le code mfa_required. Les deux paramètres ne sont envoyés que s’ils sont fournis. La prise en charge de l’AMF exige le SDK 0.4.21 ou plus récent.

    api.login("[email protected]", "password", otp_code="123456")
    # Or use a one-time recovery code instead:
    api.login("[email protected]", "password", recovery_code="abcd-efgh-ijkl")

    La connexion par appareil demande à votre navigateur d’approuver la connexion; vos scripts, vos outils en ligne de commande et vos agents ne manipulent donc jamais votre mot de passe. Le SDK affiche un code court et l’adresse https://dash.thalovant.com/activate, ouvre cette page quand il le peut, puis attend que vous approuviez la demande dans le tableau de bord avec votre connexion habituelle, y compris la connexion Google ou l’AMF. Après l’approbation, le SDK détient un jeton d’API révocable à portée limitée et se comporte exactement comme après api.login(...).

    La connexion par appareil exige le SDK 0.4.22 ou plus récent. L’approbation de la demande exige un forfait payant pour l’espace de travail; un forfait Free reçoit HTTP 402.

    api.login_with_browser()
    # Optional: narrow the scopes and label the token in the dashboard.
    api.login_with_browser(scopes=["hubs:read", "clients:write"], client_name="my-cli")

    Gérez le jeton obtenu sur la page API Tokens du tableau de bord.

    Une application qui affiche le code dans son propre écran, comme un assistant de configuration, exécute le flux par appareil une étape à la fois. begin_device_login() retourne le code et l’adresse à afficher. poll_device_login() demande une fois si la personne a approuvé la demande :

    import asyncio
    from thalovant import ThalovantDeviceLoginPending
    async def sign_in(api):
    authorization = await api.begin_device_login(scopes=["hubs:read", "clients:write"])
    show(authorization.verification_uri, authorization.user_code) # your screen
    interval = authorization.interval
    while True:
    await asyncio.sleep(interval)
    try:
    return await api.poll_device_login(authorization)
    except ThalovantDeviceLoginPending as pending:
    interval = pending.interval # longer after the API asks to slow down

    Une interrogation retourne le jeton et le conserve sur api, ou lève ThalovantDeviceLoginPending, ThalovantDeviceLoginExpired ou ThalovantDeviceLoginDenied. Le jeton dure 365 jours et n’a pas de jeton de rafraîchissement. Quand la personne retire votre application, appelez api.revoke_api_token() : un jeton peut toujours se révoquer lui-même, quelles que soient ses portées. Le ThalovantControlPlane synchrone offre les mêmes méthodes, appelées sans await. Les deux exigent le SDK 0.9.0 ou plus récent.

    Passez un jeton d’API enregistré quand le processus doit démarrer déjà authentifié, comme les tâches de CI et les services. Créez-en un sur la page API Tokens ou avec login_with_browser; la page affiche les portées, l’expiration et la dernière utilisation, et peut révoquer le jeton en tout temps.

    import os
    from thalovant import ThalovantControlPlane
    api = ThalovantControlPlane(access_token=os.environ["THALOVANT_API_TOKEN"])
    # Ready immediately; no login call needed.

    Utilisez cette méthode quand l’identité a été téléchargée depuis Thalovant, conservée dans un volume secret ou enregistrée dans votre configuration SDK protégée.

    from thalovant import ThalovantClient
    with ThalovantClient.from_config(profile="prod", protocol="wss") as client:
    reply = client.ask("What can this hub do?")
    print(reply.text)
    from thalovant import ThalovantClient
    with ThalovantClient.from_identity_file("_identity.json") as client:
    reply = client.ask("What can this hub do?")
    print(reply.text)

    Les variables d’environnement fonctionnent aussi :

    from thalovant import ThalovantClient
    with ThalovantClient.from_env(protocol="https") as client:
    print(client.ask("Say hello.").text)

    Le SDK se connecte par WSS, par HTTPS ou par MQTT sur TLS. Chaque connexion est chiffrée et authentifiée avant que quoi que ce soit ne soit envoyé.

    Passez noise_state_dir à ThalovantClient ou à un transport pour choisir le répertoire privé et persistant où le SDK conserve ses clés et les identités de hub qu’il a enregistrées. Gardez ce répertoire d’un redémarrage à l’autre, et utilisez une identité différente pour chaque processus client qui s’exécute en même temps.

    Une identité de hub enregistrée endommagée lève ThalovantConnectionError sans remplacer l’état enregistré; restaurez-la avant de réessayer. Une connexion dont le délai expire pendant l’authentification par HTTPS lève ThalovantTimeoutError après avoir libéré la connexion en échec.

    connect(timeout=None) ne retourne qu’une fois la connexion authentifiée et prête, dans un seul budget de l’appelant. La valeur par défaut est le délai de connexion configuré, plus le délai d’authentification, plus une seconde, avec un minimum de 0,1 seconde. Un délai explicite, positif et fini couvre toute la tentative de connexion; la marge supplémentaire de préparation introduite dans 0.5.7 est retirée. Les budgets invalides ou expirés lèvent une erreur de connexion dont la cause est TimeoutError.

    close(timeout=None) borne aussi la durée d’attente de son appelant. Le nettoyage conserve la propriété de la connexion jusqu’à ce qu’il soit réellement terminé. Utilisez wait_closed(timeout=None) pour observer ce nettoyage avant de réutiliser l’identité de client; ici, None signifie une attente sans limite. Le client asynchrone suit les mêmes règles et conserve la propriété du nettoyage après l’annulation d’une tâche.

    ask(timeout=12) regroupe la connexion, l’envoi et la collecte de la réponse dans un même budget. Les options de constructeur reply_settle_seconds=0.25 et empty_reply_wait_seconds=5.0 fixent des fenêtres qui commencent à la première parole non vide ou au premier événement « handled » ou « soft-miss » sans parole. La parole démarre la stabilisation sans exiger « handled ». Ask exige l’ID de requête correspondant et accepte l’ID de session du runtime. Les échecs graves figent les réponses partielles; les écritures incertaines de l’application ne sont jamais réessayées automatiquement.

    Les abonnements on() actifs survivent aux reconnexions, et les inscriptions et retraits simultanés restent cohérents avec la restauration de la session : un gestionnaire est inscrit une fois par session, et subscription.close() l’empêche de revenir lors des reconnexions suivantes. Une inscription qui échoue n’est pas conservée pour une reconnexion future.

    listen met au plus 256 événements en mémoire tampon par défaut; donnez à max_buffered_events une valeur positive pour changer la limite. Un débordement lève ThalovantRuntimeError et retire l’abonnement. Son délai inclut la connexion et l’inscription, et retire un abonnement en pause à son expiration. Fermez l’itérateur quand vous avez terminé; l’itérateur asynchrone retire aussi son écouteur en cas d’annulation.

    Les certificats TLS sont vérifiés par défaut. Pour une autorité de certification privée, configurez la confiance envers cette autorité; ThalovantClient(self_signed=True) accepte explicitement un point de terminaison de développement autosigné. Cette option désactive la vérification des certificats; laissez-la à False pour un usage normal. Une connexion n’est utilisable qu’une fois authentifiée. HTTPS conserve sa session lors d’une reconnexion; après la perte de la connexion au courtier, MQTT s’authentifie de nouveau avant d’envoyer des messages de l’application.

    Utilisez de nouveaux ID de requête pour Ask et de nouveaux ID de query pour Query; des ID générés sont la valeur par défaut. Un collecteur concurrent qui porte le même ID est refusé sur le même client. Les deux espaces de noms sont indépendants. Ne réutilisez pas un ID pour une opération logique ultérieure, même après une annulation : une réponse retardée peut encore le porter. Réutilisez un ID de session pour le contexte d’une conversation.

    identity = result.identity
    print(identity.enabled_protocols())
    print(identity.endpoint_for("wss"))
    print(identity.endpoint_for("https"))
    print(identity.endpoint_for("mqtt"))
    for protocol in ("wss", "https", "mqtt"):
    if not identity.supports_protocol(protocol):
    continue
    if protocol == "mqtt" and identity.mqtt is None:
    continue
    with ThalovantClient(identity, protocol=protocol) as client:
    print(protocol, client.ask(f"Reply over {protocol}.").text)

    MQTT exige les identifiants de courtier identity.mqtt retournés pour ce client.

    Pour les détails du courtier, consultez Connexion MQTT.

    Utilisez une conversation quand des tours liés doivent partager une même session.

    with ThalovantClient.from_identity_file("_identity.json") as client:
    with client.conversation(lang="en-us") as convo:
    print(convo.ask("Remember that my favorite color is blue.").text)
    print(convo.ask("What color did I mention?").text)
    from thalovant import ThalovantClient, build_client_context
    context = build_client_context(
    user_id="user-42",
    user_name="Ada",
    source="checkout-kiosk",
    platform="kiosk",
    locale="en-US",
    metadata={"trace_id": "req-2026-06-09-001"},
    )
    with ThalovantClient.from_identity_file("_identity.json") as client:
    reply = client.ask("Show the next instruction.", context=context)
    print(reply.text)
    from thalovant import EVENT_SPEAK, ThalovantClient
    with ThalovantClient.from_identity_file("_identity.json", protocol="wss") as client:
    for event in client.listen(EVENT_SPEAK, timeout=30, max_events=3):
    print(event.text)

    L’itérateur observe les événements correspondants pendant toute sa durée de vie. Pour une requête et une réponse corrélées, utilisez ask().

    Depuis le SDK 0.4.36, un client connecté peut lister chaque intention à laquelle son hub répond, par langue, avec les phrases qu’une personne prononce pour atteindre chacune d’elles. L’inventaire est lu par la propre session du client; aucun jeton de plan de contrôle n’entre donc en jeu. Pour savoir ce que le hub retourne et quels types de messages la connexion doit être autorisée à publier, consultez Que peut-on demander à mon hub?.

    from thalovant import ThalovantClient, ThalovantPolicyDeniedError
    with ThalovantClient.from_identity_file("_identity.json") as client:
    try:
    inventory = client.intents(["en-us", "fr-fr"])
    except ThalovantPolicyDeniedError as denied:
    print("refused:", denied.denied_type, "allowed:", denied.allowed)
    raise
    print(inventory.source, inventory.languages)
    for skill in inventory.skills:
    print(skill.skill_id)
    for intent in skill.intents:
    print(" ", intent.id, intent.engine, intent.examples("fr-fr"))

    intents() envoie ovos.intent.list une fois par langue, en demandant la définition de chaque ligne en même temps, et ne décrit que les intentions à base de phrases que la liste ne contenait pas déjà. describe=False s’arrête à la liste et retourne les noms, les moteurs et l’état d’activation, sans les phrases. examples(lang, limit=2) classe les phrases complètes avant les préfixes incomplets et les motifs à emplacements, puis privilégie les phrases plus complètes, jusqu’à huit mots. La limite compte les exemples retournés après le retrait des résultats vides et des doublons. phrases_for(lang) trouve la variante de langue inscrite la plus proche : fr et fr-CA peuvent sélectionner fr-FR. Les langues inconnues ne retournent aucun exemple.

    Un hub qui refuse une query envoie hive.policy.denied, que le SDK lève aussitôt sous forme de ThalovantPolicyDeniedError avec denied_type, code, reason et la liste allowed. Avec fallback=False, un refus de ovos.intent.list lève l’erreur tout de suite. Avec la valeur par défaut fallback=True, le SDK interroge plutôt les manifestes des moteurs et ne retourne que les noms : inventory.source vaut "engine-manifests", inventory.denied vaut ("ovos.intent.list",) et inventory.has_phrases est faux. L’erreur est levée quand ces queries sont refusées elles aussi, et chaque fois que ovos.intent.describe est refusée.

    Depuis 0.5.7, le repli par défaut gère aussi une requête ovos.intent.list restée sans réponse. Le résultat utilise toujours source="engine-manifests" et denied=("ovos.intent.list",); dans ce cas, denied consigne la query restée sans réponse. Avec fallback=False, l’expiration du délai de la liste lève ThalovantTimeoutError. L’opération lève aussi une erreur de délai si les manifestes des moteurs ne répondent jamais.

    Un hub qui accepte la query et y répond avec ok: false est un cas différent, et depuis la version 0.4.40, les deux queries se comportent différemment à cet égard. Une liste qui échoue lève ThalovantRuntimeError avec le texte d’erreur du hub lui-même, parce qu’une liste en échec n’est pas un hub vide. Une description qui échoue ne retourne aucune définition et ne lève rien : cela signifie que le hub ne connaît pas cette inscription, et l’intention garde donc sa place dans l’inventaire, sans phrases.

    Les deux queries sont offertes séparément. client.list_intents("en-us") retourne les lignes d’inscription, et client.describe_intent(skill_id, intent_name, "en-us") les définitions derrière une intention; chacune accepte le mot-clé timeout. AsyncThalovantClient offre les trois.

    Depuis la ligne de commande :

    Fenêtre de terminal
    thalovant --identity _identity.json intents --lang en-us --lang fr-fr
    thalovant --identity _identity.json intents --json

    --all affiche chaque phrase au lieu de deux par intention. Quand le repli ne fournit que les noms, la commande écrit un avertissement sur stderr. Cet avertissement peut suivre un refus explicite ou une liste restée sans réponse; il n’établit pas à lui seul un refus par politique.

    AsyncThalovantClient, AsyncThalovantControlPlane et AsyncHubSession s’exécutent sur votre boucle d’événements et ne la bloquent jamais : les fichiers d’identité, la dérivation de clés et le chargement des certificats se font dans l’exécuteur de la boucle. Une application qui possède déjà une aiohttp.ClientSession la passe avec session=. Le SDK l’utilise telle quelle et ne la ferme jamais :

    api = AsyncThalovantControlPlane(access_token=token, session=http)
    client = AsyncThalovantClient(identity, session=http)

    AsyncHubSession maintient une connexion au hub pour une application de longue durée :

    from thalovant import AsyncHubSession
    session = AsyncHubSession.for_identity(identity, session=http)
    session.on_state_change(lambda up: print("hub link", "up" if up else "down"))
    session.on("mycroft.volume.set", on_volume) # follows every reconnect
    await session.connect() # one attempt; raises if it fails
    runner = asyncio.create_task(session.run()) # keeps that link, redials after a drop
    reply = await session.ask("What time is it?")
    await session.close()

    connect() fait une seule tentative, pour qu’une étape de configuration puisse signaler un échec tout de suite. run() maintient le lien ouvert par connect() et, après une coupure, recompose après 10, 20, 40 et 80 secondes, puis toutes les 120 secondes. Une nouvelle connexion est refusée tant que son hub ne l’a pas admise; run() réessaie donc après un refus pendant dix minutes avant de lever ThalovantHubRefusedError. Chaque tentative est journalisée au niveau DEBUG.

    Un client ordinaire se reconnecte aussi de lui-même quand le hub coupe son lien : après environ cinq secondes, puis en doublant l’intervalle jusqu’à une minute, jusqu’à ce que le lien revienne ou que le client soit fermé. Un client qui ne fait qu’écouter récupère ses événements après un redémarrage du hub. auto_reconnect=False désactive ce comportement.

    Une intégration Home Assistant détient une connexion de type home_assistant à un hub. Une compétence domotique du hub lui envoie thalovant.home.request avec ce que la personne a dit, et l’intégration répond avec thalovant.home.response :

    from thalovant import AsyncHubSession, AsyncThalovantControlPlane, HomeAnswer, answer_home_requests
    api = AsyncThalovantControlPlane(access_token=token, session=http)
    result = await api.create_client_identity(
    hub_id, name="Home Assistant", connection_type="home_assistant"
    )
    await api.wait_for_admission(result)
    async def handle(request):
    speech = await ask_home_assistant(request.utterance, request.lang)
    return HomeAnswer(speech=speech, response_type="action_done")
    session = AsyncHubSession.for_identity(result.identity, session=http)
    stop = answer_home_requests(session, handle)
    await session.connect()
    runner = asyncio.create_task(session.run())

    create_client_identity(..., connection_type=...) vérifie que l’API a bien créé ce type de connexion. Sinon, le SDK supprime ce qu’il a créé et lève ThalovantUnsupportedConnectionTypeError. Un forfait qui ne permet pas ce type lève ThalovantPlanError, et un hub qui a déjà son lien Home Assistant lève ThalovantAlreadyLinkedError.

    wait_for_admission() retourne une fois que le hub a admis la connexion, ce qui prend environ quatre-vingt-dix secondes. Elle lève ThalovantAdmissionTimeoutError quand l’attente expire; cette erreur est une ThalovantConnectionError, ce qui vous permet de garder la connexion et d’attendre de nouveau plus tard.

    Quand l’API est complètement injoignable, à cause du DNS, de la connexion, de TLS ou d’un délai expiré, tout appel au plan de contrôle lève ThalovantAPIUnreachableError. Cette erreur est à la fois une ThalovantAPIError et une ThalovantConnectionError, et n’a pas de code d’état; vous pouvez ainsi distinguer « réessayez plus tard » d’un refus.

    answer_home_requests() répond à chaque requête au plus une fois, par le même chemin que celui de la requête, et jamais après les dix secondes du hub : votre gestionnaire en obtient neuf, et l’envoi de la réponse obtient le reste. Une réponse qui ne pourrait arriver qu’après l’abandon du hub n’est pas envoyée. La parole est envoyée en texte brut. Quand votre gestionnaire lève une exception, manque de temps ou répond hors du contrat, le SDK envoie une réponse error avec un code d’erreur, et le hub prononce sa propre phrase pour ce cas dans la langue de l’appareil. delete_client() retire la connexion quand la personne retire l’intégration.

    Pendant l’attente de l’admission, un jeton révoqué lève ThalovantAuthError plutôt qu’un échec d’admission, et en cas de limite de débit, le SDK attend le temps indiqué par l’API. Un lien de longue durée démarré avec run() s’arrête aussitôt avec ThalovantHubKeyChangedError si la clé du hub n’est plus celle que le SDK a épinglée au premier contact : une nouvelle tentative ne peut rien y changer, et le SDK ne remplace jamais l’épinglage de lui-même.

    ask

    Envoyer une requête et recevoir une réponse normalisée.

    listen

    Attendre des événements du hub avec un délai d’expiration.

    send_action

    Envoyer une action de bouton, de menu ou d’outil.

    send_code

    Envoyer une valeur de code QR, un numéro de série, un code-barres ou un code saisi.

    intents

    Lister ce qu’on peut demander au hub, par langue, avec les phrases qui atteignent chaque intention.

    Pour la liste complète des méthodes, consultez Fonctions des SDK. Utilisez api.get_operation(operation_id) pour suivre une commande acceptée; consultez Opérations.

    Depuis le SDK 0.4.25, le plan de contrôle peut créer des hubs, des groupes de runtime et des installations de compétences. Parcourir le catalogue avec api.list_marketplace_skills() exige hubs:read et fonctionne avec n’importe quel forfait. create_hub, create_runtime_group, install_runtime_group_skill, release_hub et release_runtime_group exigent hubs:write et un forfait payant.

    group = api.create_runtime_group({"name": "kiosks"})
    hub = api.create_hub({"name": "joke-garden", "runtime_group_id": group["id"], "spec": {}})
    hub = api.get_hub(hub["id"])
    api.update_hub(hub["id"], {"active": False}, etag=hub["etag"])

    update_hub et delete_hub exigent l’etag actuel du hub. Consultez Provisionner des hubs pour le flux complet, les champs immuables et le tableau des erreurs.

    À partir du SDK 0.5.15, list_hub_skills, install_hub_skill, update_hub_skill et remove_hub_skill gèrent les compétences d’un hub selon son identifiant. list_hub_skills retourne une HubSkillList dont les lignes se trouvent sous .data (for skill in api.list_hub_skills("hub-id").data:), les écritures retournent une HubSkillOperation, et wait=True interroge cette opération avec un budget d’interrogation par défaut de 120 secondes. Les mêmes actions existent en ligne de commande : thalovant skills list, thalovant skills add, thalovant skills update et thalovant skills remove, chacune avec --hub <hub-id>. Consultez Ajouter une compétence à un hub.

    Les lignes mal formées de la liste des compétences sont rejetées plutôt qu’omises en silence. Aucune nouvelle lecture d’état ne commence à l’échéance d’interrogation ou après; une lecture HTTP déjà en cours conserve son délai de requête configuré. Si une lecture d’état échoue, l’erreur conserve l’ID de l’opération acceptée pour que vous puissiez reprendre avec get_operation sans répéter l’écriture. Une erreur d’API sous-jacente assainie est conservée comme cause; les exceptions inattendues sont omises de la chaîne affichée.

    SymptômeVérification
    Missing Thalovant API access tokenAppelez api.login(...) ou api.login_with_browser() avant les actions privées de l’API, ou passez access_token= à ThalovantControlPlane.
    HTTP 401 avec le code mfa_requiredPassez otp_code ou recovery_code à api.login(...).
    API access requires a paid planPassez l’espace de travail à un forfait payant avant de provisionner des ressources privées par l’API.
    Unsupported protocolActivez ce protocole sur le hub et créez une nouvelle identité.
    MQTT échoue immédiatementConfirmez que l’identité possède des identifiants de courtier mqtt.
    ThalovantPolicyDeniedErrorLa connexion n’a peut-être pas le droit de publier le type de message que nomme l’erreur. Ajoutez-le aux types de messages de la connexion dans le tableau de bord, puis reconnectez-vous. Consultez Que peut-on demander à mon hub?.
    ovos.intent.list failed: ...Le hub a accepté la liste, mais n’a pas pu y répondre. Vérifiez l’état en direct du hub, puis redemandez. Consultez Que peut-on demander à mon hub?.
    1. Commencez par WSS.
    2. N’ajoutez HTTPS que pour les clients requête-réponse.
    3. N’ajoutez MQTT que si le client a besoin de trafic qui passe par un courtier.

    L’inventaire comprend fallbacks et fallbacks_known. Utilisez inventory.may_answer("en-us") pour une vérification prudente de la langue; une prise en charge du repli inconnue peut tout de même répondre. La sonde prend au plus 1,5 seconde, connexion, envoi et attente compris; un résultat vide confirmé diffère d’un refus, d’un échec, d’un silence ou d’une enveloppe de réponse mal formée. Les tableaux acceptés conservent les gestionnaires utilisables et ignorent les lignes invalides. Consultez la découverte des replis.

    Les redirections du plan de contrôle sont refusées. Les appels authentifiés à l’API et les corps de requête exigent HTTPS, sauf pour les points de terminaison de développement explicites en boucle locale (loopback). Consultez Sécurité des identifiants d’API.

    Mises à jour simultanées de la configuration du runtime

    Section intitulée « Mises à jour simultanées de la configuration du runtime »

    update_runtime_group_config(id, config) préserve les clés enregistrées sans rapport avec votre changement en lisant la configuration et sa révision, en fusionnant vos changements, puis en envoyant un PUT conditionnel. Une mise à jour en conflit retourne HTTP 412; l’utilitaire relit la configuration et réapplique vos changements d’origine, jusqu’à trois tentatives d’écriture. Les listes sont remplacées, et personas n’est remplacé que si vous le fournissez.

    Cela exige une API qui prend en charge les révisions de configuration et le PUT /v1/runtime-groups/{id}/config conditionnel. Les serveurs plus anciens échouent sans écrire. Les échecs réseau et les autres erreurs HTTP ne sont pas réessayés. Un échec HTTP final expose ThalovantAPIError.status_code. Coordonnez tout processus d’écriture qui utilise merge=False, qui conserve le remplacement inconditionnel par PATCH.

    control.update_runtime_group_config(runtime_group_id, {"lang": "fr-fr"})

    Le SDK peut lister, installer, mettre à jour et retirer les compétences du groupe de runtime d’un hub, et en consulter l’historique. Chaque hub qui partage ce groupe de runtime est touché. L’UUID d’un hub sélectionne le groupe; il ne crée pas d’installation isolée pour ce hub. Un jeton restreint doit couvrir tous les hubs desservis par le groupe.

    Les lectures exigent hubs:inspect (hubs:read l’inclut). Les écritures exigent hubs:write, un forfait payant admissible et la propriété. Utilisez latest ou une version publiée précise pour l’installation et les mises à jour. Une écriture acceptée retourne un operation_id; l’acceptation ne signifie pas que le runtime a fini d’installer la compétence.

    ActionMéthode
    Listerlist_hub_skills
    Historiquelist_hub_skill_history
    Installerinstall_hub_skill
    Mettre à jourupdate_hub_skill
    Retirerremove_hub_skill

    L’historique retourne l’enveloppe JSON de l’API avec des entrées event et operation, de la plus récente à la plus ancienne, en préservant les champs acteur et version qui peuvent être nuls. La limite va de 1 à 200.

    Utilisez l’option d’attente existante pour suivre l’opération acceptée. Un échec d’interrogation conserve l’ID de son opération; examinez cette opération au lieu de répéter l’écriture.

    Les fusions de configuration prennent un instantané de la configuration imbriquée et des personas avant la première lecture; une modification faite par l’appelant ne peut donc pas changer la charge utile entre les nouvelles tentatives après un conflit. L’utilitaire de contexte de requête copie aussi une session existante quand aucun pipeline n’est fourni.

    Depuis la version 0.9.0, le SDK exige aiohttp 3.11 et cryptography 48.0.1 ou plus récents; il s’installe donc à côté de Home Assistant 2026.9, qui épingle cryptography 48.0.1. cryptography 50.0.0 corrige une faille dans le déchiffrement PKCS#7, que le SDK n’appelle jamais; une application qui utilise elle-même PKCS#7 devrait exiger 50.0.0. Les Mac Intel restent sous 49.0.0, parce que les versions suivantes ne fournissent aucune wheel macOS x86_64.

    Avant cryptography 50.0.0, la dérivation de clé lors d’une première connexion retient le verrou de l’interpréteur Python pendant environ un dixième de seconde; une boucle asyncio s’interrompt donc pendant ce temps. La clé est ensuite mise en cache : cela se produit une fois par identité et par hub.

    Listes d’intentions adaptées aux paramètres régionaux

    Section intitulée « Listes d’intentions adaptées aux paramètres régionaux »

    Installez les données linguistiques facultatives quand les exemples doivent se lire comme des phrases :

    Fenêtre de terminal
    python -m pip install 'thalovant[listing]==0.7.6'
    from thalovant import as_sentence, speakable
    print(as_sentence("quelle heure est-il", "fr-CA")) # Quelle heure est-il?
    print(speakable("volume {level} pour cent", lang="fr")) # volume cinquante pour cent
    examples = intent.examples("fr-CA", sentence=True)

    sentence=True implique le rendu prononçable, la mise en majuscule et une ponctuation adaptée aux paramètres régionaux. Omettre lang utilise les premiers paramètres régionaux inscrits, y compris leurs exemples d’emplacements. Des slots explicites remplacent les valeurs d’échantillon de la langue. Le paquet facultatif thalovant-languages détient ces règles; sans lui, les phrases restent capitalisées et sans ponctuation, et les emplacements gardent leur nom. Les langues inconnues ne reçoivent aucune ponctuation devinée. Un préfixe incomplet reste sans ponctuation.

    Les versions 0.7.4 à 0.8.7 de Python supprimaient un avertissement OVOS répété au sujet de l’ancien format d’emplacement imbriqué du hub. Depuis la version 0.9.0, le SDK n’utilise plus les bibliothèques OVOS; l’avertissement n’apparaît donc jamais.

    Les données de la liste décrivent maintenant 270 langues, y compris des variantes régionales. L’espagnol qué hora es devient Qué hora es?, et le français coupe le son devient Coupe le son.. Les langues sans règles, comme tlh, restent sans ponctuation.

    HubSession détient une connexion au hub réutilisable. Fournissez une fabrique qui retourne un client connecté et qui nettoie une tentative de connexion échouée ou annulée. Les abonnements aux événements survivent au remplacement du client. Go et Rust exposent un flux d’événements persistant; les autres SDK gérés exposent des descripteurs d’abonnement. Fermez la session quand son propriétaire s’arrête; la fermeture attend les opérations acceptées et est définitive.

    Les tentatives de connexion en arrière-plan attendent 10, 20, 40, 80, puis 120 secondes. Les appels au premier plan peuvent essayer immédiatement. Votre application gère l’ordonnancement des sondes : utilisez le délai de sonde signalé (60 secondes pendant la rétention, 5 secondes pendant une panne). Le SDK ne rejoue jamais un Ask ou un Emit accepté après la perte d’une réponse, parce qu’un Ask peut déclencher une action. Le délai d’une requête s’applique à l’opération sous-jacente; l’attente de la session et votre fabrique de connexions ont des budgets distincts.

    from thalovant import HubSession
    # connect_client returns a connected client and cleans up failed attempts.
    session = HubSession(connect_client, warm=False)
    try:
    reply = session.ask("What is the weather?")
    finally:
    session.close()

    Inventory, Skill et Intent offrent une vue présentable distincte de l’inventaire natif des intentions du runtime. Les paramètres régionaux inconnus du catalogue restent inconnus; des phrases observées pour une langue ne prouvent pas que le catalogue la prend en charge. Les exemples choisissent les paramètres régionaux pris en charge les plus proches. Une limite non positive retourne le bassin brut de phrases (Rust utilise zéro pour sa limite non signée). Le JSON du cache inclut l’ordre explicite des langues des intentions, pour que la sérialisation ne puisse pas changer la langue d’exemple par défaut.

    InventoryCache est facultatif, utilise par défaut une durée de vie (TTL) d’une heure, et retourne un échec de cache pour les données invalides, expirées ou illisibles. Les écritures utilisent des fichiers temporaires privés et uniques et un remplacement atomique. Sous POSIX, les fichiers de cache sont lisibles et modifiables par leur propriétaire seulement; Windows utilise les ACL du répertoire de l’utilisateur. Les clés de cache distinguent le mode, le chemin de l’identité et le nom d’hôte complet normalisé du hub. Les fichiers de cache Python existants produisent un échec de cache unique après ce changement de clé. N’utilisez jamais les caches d’inventaire pour conserver des identifiants.

    OriginPreference donne à une adresse préférée son propre court budget de connexion et la met en pause après un échec. Dans les SDK autres que Python, la fabrique doit implémenter la liaison à l’adresse sur son propre transport, conserver l’hôte public pour TLS/SNI et terminer le nettoyage des tentatives échouées avant de retourner. Les restrictions de transport et de plateforme s’appliquent toujours. Python utilise une substitution de résolveur sérialisée et limitée à une portée; évitez de bloquer du travail de résolution sans rapport à l’intérieur de cette portée. La validation TLS reste activée.

    HubSession enveloppe des clients synchrones. Sa méthode OriginPreference.close() réessaie le nettoyage d’une tentative de connexion échouée; les clients retournés avec succès appartiennent toujours à leur appelant.

    Une réponse expose reply.pipeline_ids, reply.skill_ids et reply.claimed.

    Les ID de pipeline et de compétence sont des chaînes non vides, dans l’ordre de première apparition, sans doublons. Une réponse réussie dont les seuls ID de pipeline contiennent le texte fallback (sensible à la casse) est signalée comme non revendiquée. Toute étape autre qu’un repli rend revendiquée une réponse réussie. Les réponses réussies des anciens hubs sans estampilles d’étape restent revendiquées. Les réponses en échec ou non traitées ne sont jamais revendiquées. Les estampilles mal formées qui ne sont pas des chaînes sont ignorées.

    Servez-vous-en comme d’un indice pour décider s’il faut poursuivre une conversation. Le texte de repli et l’état de réussite existant sont conservés. Ces estampilles ne constituent pas une identité de pair vérifiée et ne doivent pas autoriser d’actions.

    Rien n’est obligatoire. Chaque nom public, chaque signature et chaque comportement de 0.8.7 est toujours là. Ce qui change :

    • Le paquet installe aiohttp et cryptography, et rien d’autre. Si votre code importait hivemind-bus-client, ovos-bus-client, ovos-utils, requests, PyYAML, paho-mqtt ou ovos-spec-tools sans les déclarer, déclarez-les maintenant. MQTT exige thalovant[mqtt].
    • Les clés et les identités de hub enregistrées restent dans les mêmes fichiers et le même format. Rien n’est migré, et les hubs épinglés se reconnectent sans poignée de main de premier contact.
    • Chaque client synchrone possède un fil d’exécution privé pour sa boucle d’événements. Les gestionnaires passés à on() s’exécutent toujours hors de cette boucle; un gestionnaire peut donc appeler le client pendant qu’un autre fil attend dans ask().
    • ThalovantControlPlane(session=...) accepte toujours une session de type requests. Une aiohttp.ClientSession va plutôt à AsyncThalovantControlPlane.
    • Les nouvelles erreurs sont des sous-classes de ThalovantAPIError ou de ThalovantConnectionError, les erreurs que 0.8.7 levait aux mêmes endroits; les clauses except existantes les interceptent donc toujours.