Some assistant custom responses examples

Hey all,

Quick intro

I’m new to the home assistant community but I’ve been using HA for years.

I received the home assistant voice preview yesterday and started putting together some custom intents (custom responses). I found that there is not really a quick setup guide for intents or a lot of working examples so I thought I’d start this topic.

Apologies if a topic already exists - I did have a quick look but I didnt see any other similar topics.

Dropdown the above if you want to know my background.

First thing I’ve noticed is that custom responses are split into two parts. “intents” are sentances that the assistant will look for when you interact with it, so they’re basically triggers. “intent_script” are the actual customised responses or actions that your assistant will use.

First step, setup “intents” (custom triggers)

Create a custom_sentences folder in the config folder (where your configuration.yaml exists), then create a folder named the language you intend to use inside the “custom_sentences” folder e.g. en, de, pl. This is where you’ll store your custom intents yaml.

Create a yaml file with whatever name you want inside the language folder you created, you can have multiple files or just one big file, see example below.

Inside this custom file yaml that you created will be your custom intents - this is what your assistant will listen for to trigger your custom actions or response.

Here are some of my working “intent” examples:

language: "en"
intents:
  YearOfVoice:
    data:
      - sentences:
        - "how is the year of voice going"

  SetVolumePercent:
    data:
      - sentences:
          - "{media_player} (volume | vol.) [to] {volume_percent} (% | percent)"
          - "(set|change) {media_player} (volume | vol.) to {volume_percent} (% | percent)"
          - "(set|change) [the] (volume | vol.) (in | for) {media_player} to {volume_percent} (% | percent)"

  SetVolumeStep:
    data:
      - sentences:
          - "{media_player} (volume | vol.) [level] [to] {volume_step}"
          - "(set|change) {media_player} (volume | vol.) [level] to {volume_step}"
          - "(set|change) [the] (volume | vol.) [level] (in | for) {media_player} to {volume_step}"


  CustomGetWeather:
    data:
      - sentences:
          - "(what is | what's | whats) {ord_day} weather [going to be | forecast] [like]"
          - "(what is | what's | whats) the weather [going to be | forecast] [like]"
          - "(what is | what's | whats) the weather [looking | going to be | forecast] [like | for] {ord_day}"
          - "(hows | how's | how is) {ord_day} weather [looking | going to be | forecast] [like]"
          - "(hows | how's | how is) the weather [looking | going to be | forecast] [like]"
          - "(hows | how's | how is) the weather [looking | going to be | forecast] [like | for] {ord_day}"
          - "{ord_day} weather (report | forecast)"

  CustomGetRain:
    data:
      - sentences:
          - "is it (going to | gonna) rain"
          - "(can it | could it | is it | will it) [going to | gonna] rain {ord_day}"

  CustomGetSolarPower:
    data:
      - sentences:
          - "(what is | what's | whats) the [current] {solar_query} [currently | right now | at the moment]"
          - "how much [solar] power (are we | is being) (generating | generated | created | creating | made | making) [currently | right now | at the moment]"

  CustomGetLoadPower:
    data:
      - sentences:
          - "(what is | what's | whats) the [current] {load_query} [use | usage] [currently | right now | at the moment]"
          - "how much [load] power (are we | is being) (using | used | drawing | drawed) [currently | right now | at the moment]"

  CustomGetGridPower:
    data:
      - sentences:
          - "(what is | what's | whats) the [current] {grid_query} (use | usage) [currently | right now | at the moment]"
          - "how much grid power (are we | is being) (using | used | drawing | drawed) [currently | right now | at the moment]"

  CustomGetBatteryStatus:
    data:
      - sentences:
          - "(what is | what's | whats) the [current] {battery_query} [currently | right now | at the moment]"
          - "(hows | how's | how is | how does) the {battery_query} [look | looking | doing | fairing | getting on | holding up] [currently | right now | at the moment]"
          - "how much battery (charge | power) (is remaining | is left | remains) [currently | right now | at the moment]"

lists:
  media_player:
    values:
      - in: "dan's room"
        out: "media_player.dans_bedroom_voice_assistant"
      - in: "dans room"
        out: "media_player.dans_bedroom_voice_assistant"
  volume_percent:
    range:
      from: 0
      to: 100
  volume_step:
    range:
      from: 0
      to: 10
  ord_day:
    values:
      - "today"
      - "today's"
      - "todays"
      - "tomorrow"
      - "tomorrow's"
      - "tomorrows"
  solar_query:
    values:
      - "solar power"
      - "solar generation"
      - "solar power generation"
      - "power generation"
  load_query:
    values:
      - "power"
      - "load"
      - "load power"
  grid_query:
    values:
      - "grid"
      - "grid power"
  battery_query:
    values:
      - "battery"
      - "battery charge"
      - "battery level"
      - "battery charge level"
      - "battery status"
      - "battery charge status"
      - "battery state"
      - "battery state of charge"
      - "battery charge state"

Now you have some working custom triggers that your assistant will listen out for.

Second step, setup “intent_script” (custom actions and responses)

These are inserted into your configuration.yaml and are linked via the intent header you set in the custom intents e.g. YearOfVoice, CustomGetWeather, etc.

Here are my working “intent_script” examples.

intent_script:
  YearOfVoice:
    speech:
      text: "Great! We're at over 40 languages and counting."

  SetVolumePercent:
    action:
      service: "media_player.volume_set"
      data:
        entity_id: "{{ media_player }}"
        volume_level: "{{ volume_percent / 100.0 }}"
    speech:
      text: "Volume {{ volume_percent|int }} percent"

  SetVolumeStep:
    action:
      service: "media_player.volume_set"
      data:
        entity_id: "{{ media_player }}"
        volume_level: "{{ volume_step / 10.0 }}"
    speech:
      text: "Volume {{ volume_step|int }}"

  CustomGetWeather:
    action:
      - service: weather.get_forecasts
        data:
          type: daily
        target:
          entity_id: weather.openweathermap
        response_variable: w
      - stop: ""
        response_variable: w
    speech:
      text: |
        {% set day = 1 if (ord_day is search('tomorrow') or now().hour > 21) else 0 %}
        {% set next = action_response['weather.openweathermap'].forecast[day] %}
        The forecast for {{ 'tomorrow' if day else 'today'}} is a high of
        {{ next.temperature}} degrees and {{ next.condition }} with a
        {{ next.precipitation_probability }} percent chance of rain.

  CustomGetRain:
    action:
      - service: weather.get_forecasts
        data:
          type: daily
        target:
          entity_id: weather.openweathermap
        response_variable: w
      - stop: ""
        response_variable: w
    speech:
      text: |
        {% set day = 1 if (ord_day is search('tomorrow') or now().hour > 21) else 0 %}
        {% set next = action_response['weather.openweathermap'].forecast[day] %}
        There is a {{ next.precipitation_probability }} percent chance of rain {{ 'tomorrow' if day else 'today'}}.

  CustomGetSolarPower:
    speech:
      text: |
        {% set response_lead = 'Solar generation' if (solar_query is search('solar generation')) else 'Solar power generation' %}
        {% set response_lead = 'Solar power' if (solar_query is search('solar power')) else response_lead %}
        {{ response_lead }} is currently {{ '{0:,.0f}'.format(states.sensor.pv_power.state | int) }} watts.

  CustomGetLoadPower:
    speech:
      text: "Load power usage is currently {{ '{0:,.0f}'.format(states.sensor.load_power.state | int) }} watts."

  CustomGetGridPower:
    speech:
      text: "Grid power usage is currently {{ '{0:,.0f}'.format(states.sensor.grid_power.state | int) }} watts."

  CustomGetBatteryStatus:
    speech:
      text: "The battery is {{ states.sensor.battery_state_of_charge.state }}%."

Here are examples of output from my custom responses.

Q: is it going to rain
A: There is a 0 percent chance of rain today.

Q: hows the weather looking tomorrow
A: The forecast for tomorrow is a high of 15.8 degrees and sunny with a 0 percent chance of rain.

Q: whats the solar generation currently
A: Solar generation is currently 2,459 watts.

Q: how much power are we using
A: Load power usage is currently 1,232 watts.

Q: whats the battery status
A: The battery is 55%.

Q: whats the grid usage
A: Grid power usage is currently 0 watts.

They work great with the voice assistant and feel natural but I guess that depends on how you speak to your voice assistant.

Don’t forget to reload “Intent Script” in developer tools - YAML configuration reloading after making changes.

I hope this topic helps some people get started with their custom responses using the voice assistant. Please feel free to add more cool examples as I’m sure I’m only scratching the surface.

My next project is to get a reminder / timer / alarm system working as I use my voice assistant when cooking etc. it would also be nice to control the voice volume via the assistant without having to tell the voice assistant where it is.

1 Like

Note if you want to have additional slots besides ‘name’, ‘area’, ‘value’ those edits go in

\custom_sentences(your language code)\slot_types.yaml

This allows you to create custom slots for say. Hey can you set the ‘name’ to ‘whatever_slot’ for your intents.

2 Likes

Hey Nathan,

Thanks for the reply!

Can you explain any further or provide examples?

I’m really new to this and I’m curious what is the use case for the slots?

Many thanks! :slight_smile:

Got you - Answered in Friday’s Party post #1.

Friday’s Party: Creating a Private, Agentic AI using Voice Assistant tools - Configuration / Voice Assistant - Home Assistant Community

Sorry (No, I’m not) it’s lonnnnnnnng. :wink:

1 Like

I made a little bit of a breakthrough using your wildcard example.

I added a custom intent to set the color of my lights based on preset values just like alexa.

Intents:

language: "en"
intents:
  CustomLightColour:
    data:
      - sentences:
          - "{name} (color | colour) [to] {device_kelvin}"
          - "(set|change) {name} (color | colour) to {device_kelvin}"
          - "(set|change) [the] (color | colour) (of | for) {name} to {device_kelvin}"

lists:
  device_kelvin:
    values:
      - in: "cool"
        out: "5025"
      - in: "daylight"
        out: "4273"
      - in: "white"
        out: "3521"
      - in: "soft white"
        out: "2857"
      - in: "warm"
        out: "2610"

custom_sentences/en/slot_types.yaml

lists:
  name:
    wildcard: true

Intent Script:

  CustomLightColour:
    action:
      service: "light.turn_on"
      data:
        entity_id: "{{ targets.entities }}"
        color_temp_kelvin: "{{ device_kelvin }}"
    speech:
      text: "{{ name }} colour changed."

Only thing I would like to have done is used the in value from the list in the speech response. I haven’t found a way to do this yet.

2 Likes

At least before the global var change in 2025.4 you had to perform the calculations down in the speech: clause again just so the var was available.

Basically like

description
paramerers:
  name:
  custom_foo_slot:
action:
 (do things with stuff here) 
speech:
  (do the same things here except omit the action call, I don't know if 2025.4 changes context so if the var is manipulated in the action clause it's available here in the speech clause)

My gut says yes but I literally installed 2025.4 in prod 30 minutes ago and have tk write an intent to find out…

Edit it does NOT change.

And im very happy it helped. Glad to gave you along for the Party. Just wait till we get to the hot tub.

1 Like

You do not need to specify a type for the global {name} variable.
Use the dictionary functionality to reverse match names to numbers.

1 Like

Hey mchk,

Thank you for the suggestion, I really appreciate the guidance!

Do you have an example of how to use this dictionary functionality?

It is a little frustrating that you can’t pull the “in” value from the list. The intent_script now looks like this to get the desired speech response. It converts the kelvin number back to the colour name.

  CustomLightColour:
    action:
      service: "light.turn_on"
      data:
        entity_id: "{{ targets.entities }}"
        color_temp_kelvin: "{{ device_kelvin }}"
    speech:
      text: |
        {% set kelvin2colour = 'cool' if (device_kelvin is search('5025')) else 'colour' %}
        {% set kelvin2colour = 'daylight' if (device_kelvin is search('4273')) else kelvin2colour %}
        {% set kelvin2colour = 'white' if (device_kelvin is search('3521')) else kelvin2colour %}
        {% set kelvin2colour = 'soft white' if (device_kelvin is search('2857')) else kelvin2colour %}
        {% set kelvin2colour = 'warm' if (device_kelvin is search('2610')) else kelvin2colour %}
        {{ name }} changed to {{ kelvin2colour }}.

Q: set dans light colour to daylight
A: Dans Light changed to daylight.

You set the dictionary and get a comparable value in the next step using your {{ device_kelvin }} variable.
Check out this example usage.

1 Like

Sorry, I still don’t follow what you’re saying even after looking at the common.yaml.

Can you show an example?

{% set color_temp_dict = {
    '5025': 'cool',
    '4273': 'daylight',
    '3521': 'white',
    '2857': 'soft white',
    '2610': 'warm'
} %}

{% if device_kelvin in color_temp_dict %}
    {{ color_temp_dict[device_kelvin] }}
{% else %}
    Unknown color temperature
{% endif %}

Where did you get that slot_types idea from? This is not required, you can put them anywhere you want.

Why are you even doing the mapping in the sentences? Just pass it on as is, and only do the mapping in the intent script actions.

Yeah you’re right, at this stage I might as well do that.

I’ve only added values to the list for colour_name so that it filters out unknown colours at the speech level before it tries to perform an action. It could be a wildcard and you’d just get an “response error” if it can’t find the colour during the action.

language: "en"
intents:
  CustomLightColour:
    data:
      - sentences:
          - "{name} (color | colour) [to] {colour_name}"
          - "(set|change) {name} [color | colour] to {colour_name}"
          - "(set|change) [the] (color | colour) (of | for) {name} to {colour_name}"

lists:
  name:
    wildcard: true
  colour_name:
    values:
      - "cool"
      - "daylight"
      - "white"
      - "soft white"
      - "warm"

Implemented below, is that how you would do it?

intent_script:
  CustomLightColour:
    action:
      service: "light.turn_on"
      data:
        entity_id: "{{ targets.entities }}"
      data_template:
        color_temp_kelvin: >
          {% set colour2kelvin = {
            'cool':'5025',
            'daylight':'4273',
            'white':'3521',
            'soft white':'2857',
            'warm':'2610'
          } %}
          {% if colour_name in colour2kelvin %}
          {{ colour2kelvin[colour_name] }}
          {% endif %}
    speech:
      text: "{{ name }} changed to {{ colour_name }}."

You still need to list the colour names in the intents or you’ll get an error response in intent script actions if it doesnt find the kelvin value in the dictionary.

1 Like

9 months building and 53 posts worth of research.

intent scripts if you use a CUSTOM name it absolutely is. Most of the time you don’t NEEED one. On standard scripts it is NOT. If it’s not intended to work that way the documentation for slot_types is completely in error.

If it doesn’t now this is new since January. Because as of then that’s ABSOLUTELY how it works. And I’ve had it vetted by a few independent sources and dev.

My query_librar{query:‘’} relies on it and I can kill it dead if I remove my wildcard for query in slot types.

There is nothing special in the name slot_types and I don’t think it was ever mentioned anywhere in the documentation. That is why I’m asking where did you come up with it. It looks like something that AI made up.

2 Likes

Go read Friday’s Party I’ve been working on this since this time last year.

The library was created to work around the issue of only being able to send through one slot (was well known most of last year we all kept banging heads) the we realized as long as you used one of the keys in the file blammo it’d work but how do you get extra keys

Read the code and it points there.

We figure d it out around November and that’s what allows me to use a multi slot intent for tasks and calendar now instead of the twelve Hass builtin ones. (they need to be massively consolidated)

And I wish it were made up it’s a massive PITA.

And it took that long because this stuff is so poorly doc’d. That’s why I started writing the Friday post in the first place, in an attempt to get this in one place. See the limits post it’s particularly interesting.

You’re right, I deleted that file and moved the wildcards under my lists section and it still works.

1 Like

Hell yeah then it changed since January :heart:. Because I fought that thing for three months. You don’t see disappointed me here

Thats good to know.

Im still favoring standard scripts lately though.

Traces. Sooooooo much easier.

(but. We still need to Consolidate the intents.)

Im off to try fhe same thing it’s. One. Less thing on the checklist.

1 Like

This code is for a response.

    speech:
      text:>-
      {% set colour2kelvin = {
        'cool':'5025',
        'daylight':'4273',
        'white':'3521',
        'soft white':'2857',
        'warm':'2610'  
      } %}
      {{ name }} changed to {{ colour2kelvin[device_kelvin] }}.