pyscript icon indicating copy to clipboard operation
pyscript copied to clipboard

[FR] Sentence Trigger

Open contagon opened this issue 1 year ago • 7 comments

For all the new voice assistant stuff being developed currently, it'd be awesome if sentence triggers made their way to pyscript. Something like this would be awesome,

@sentence_trigger("activate dance mode")
def dance_mode():
    dance_lights.turn_on()
    return "dance like no one is watching!"

where the return value is the "conversation response" and spoken by the triggered voice assistant.

I had planned to give this a try once I did the easier webhook triggers (in #592), but handling the return response ended up not as straightforward as I had expected and I frankly haven't found the time. Happy to collaborate if someone wants to tackle this or do it together. I think we could track any progress that happens here if @craigbarratt is open to a PR :)

Note, this is also similar to the request in discussion #591.

contagon avatar Dec 28 '24 02:12 contagon

@craigbarratt this one is for you :)

ALERTua avatar Dec 28 '24 08:12 ALERTua

This would be an awesome feature! I would most definitely merge a well-written PR that supports this.

Could the trigger include something like wildcards, which could be any word(s) that are passed as parameters to the function? That would make it a lot more flexible, since you could provide verbal parameters to the trigger. I'm not sure if the voice assistant code would support that.

craigbarratt avatar Jan 05 '25 01:01 craigbarratt

I think wildcards would definitely be doable - mostly since HA already supports them as part of the sentence trigger. I'd imagine we can hook into that. The docs refer to them as "slots".

I imagine we could get it to work as,

@sentence_trigger("turn on the lights in {room}")
def turn_on_room(slots):
    if slots.room == "kitchen":
          ...
    return f"turned on lights in {slots.room}"

or separate each wildcard into a function parameter could work as well.

contagon avatar Jan 06 '25 19:01 contagon

I'd absolutely love to have this, my Voice PE just arrived and I thought about routing maybe just all sentences I have from a single automation to the same pyscript function and continuing from there, but that seems like quite a workaround (and if I can't do magic quite limited with the slot stuff). After a quick glance at the trigger code inside pyscript I don't think I feel confident enough in my understandings of HA-Core workings and the pyscript architecture to write this extension for pyscript myself, but I would very much love and use it, if it arrives! :D 💯

Bjaux avatar Jan 07 '25 21:01 Bjaux

@contagon since I now have voice pe in all my rooms and started reading more heavily into everything voice & assist related - do you have any code with a starting point for this feature that you could share? I still don't believe that I'm ready to tackle this alone but I'd be glad to collaborate or just tinker around an unfinished solution :)

Bjaux avatar Aug 11 '25 08:08 Bjaux

I unfortunately never got around to starting it! Had the best intentions, then life pulled me in other directions.

If you want to take a stab at it, I'd be happy to guide you through some of it (to the best of my knowledge, which isn't 100%). You could use the previous PR for the webhook trigger (#592) as a guide.

contagon avatar Aug 23 '25 11:08 contagon

Just an evening experiment — a sentence_trigger for Pyscript, written in Pyscript 🥇

from sentence_trigger import sentence_trigger

@sentence_trigger("hey {slot1}")
def test(slot1: str):
    return f"{slot1} is awesome!"

Image
Show code

Memory leaks, no error handling, undefined behavior, messy code, just for testing.

from typing import Any

from hassil import RecognizeResult
from homeassistant.components.conversation import get_agent_manager, ConversationInput
from homeassistant.components.conversation.trigger import TriggerDetails

TRIGGERS = {}


def sentence_trigger(sentence: str):
    self = vars()['self']  # pyscript magic

    def on_trigger(user_input: ConversationInput, result: RecognizeResult):
        kwargs: dict[str, Any] = {}
        for entity_name, entity in result.entities.items():
            kwargs[entity_name] = entity.value
        return self.decorated_func(**kwargs)

    def decorator(func):
        global TRIGGERS
        if sentence in TRIGGERS:
            log.info(f"remove old sentence_trigger {sentence}")
            TRIGGERS[sentence]()
        self.decorated_func = func
        unregister_trigger = get_agent_manager(hass).register_trigger(
            TriggerDetails(sentences=[sentence], callback=on_trigger))
        TRIGGERS[sentence] = unregister_trigger
        log.info(f"register sentence_trigger : {sentence}")
        return None

    return decorator


@time_trigger("shutdown")
def cleanup():
    global TRIGGERS
    for sentence, unregister_trigger in TRIGGERS.items():
        log.info(f"unregister sentence trigger {sentence}")
        unregister_trigger()

dmamelin avatar Oct 16 '25 22:10 dmamelin