diff --git a/control_music_full_llm.yaml b/control_music_full_llm.yaml new file mode 100644 index 0000000..4c83dd3 --- /dev/null +++ b/control_music_full_llm.yaml @@ -0,0 +1,284 @@ +blueprint: + name: Voice - Control Music + author: luuquangvu + source_url: https://github.com/luuquangvu/tutorials/blob/dev/control_music_full_llm.yaml + description: |- + # Tool for controlling music used for Voice Assistant + + ### Blueprint setup + + ### Optional + + - Adjust the prompts for each field. Descriptions guide the LLM to provide accurate music selection. + + ### Note + + - Provide a concise and precise description for the script. This helps the LLM understand it should use this script for music playback. + - Make sure to expose the script to Assist after saving. + - Do not alter the default script name. + - Once the script is created, click the three dots in the top right corner, choose "Edit in YAML," and remove the `description: ...` line to restore the default. This step is important because it helps the LLM better understand the script's purpose. + + ### Credit + + - Special thanks to the original blueprint from [music-assistant/voice-support](https://github.com/music-assistant/voice-support). This version has been refined and optimized specifically for use with Gemini. + domain: script + homeassistant: + min_version: 2024.6.0 + input: + music_assistant_settings: + name: Settings for Music Assistant playback + icon: mdi:music + description: + You can use these settings to configure how Music Assistant playback + is handled. + input: + default_player: + name: Default Player + description: |- + The default Music Assistant player which will be used if it is not clear from the request in which area or on which player the request should be played. + + Leave empty if you do not want a default player. + selector: + entity: + filter: + - integration: music_assistant + domain: + - media_player + multiple: false + default: + play_continuously: + name: Radio Mode + description: |- + Determines if the "radio_mode" setting should be set for the play media action. + + When set to "Use player settings" it will use the "Don't stop the music" setting on the Music Assistant Player. + + When set to "Always" the player will continuously add new songs to the playlist. + + When set to "Never" the player will stop playing after the songs selected by the LLM are finished. + selector: + select: + options: + - Use player settings + - Always + - Never + multiple: false + custom_value: false + sort: false + default: Use player settings + prompt_settings: + name: Prompt settings for the LLM + icon: mdi:robot + description: + You can use these settings to fine-tune the prompts for your specific + LLM (model). In most cases the defaults should be fine. + collapsed: true + input: + media_type_prompt: + name: Media Type Prompt + description: The prompt that the LLM will use to provide the media_type. + selector: + text: + multiline: true + multiple: false + default: |- + Mandatory. Values: track (single/list), album, artist, playlist, radio. Default to 'track' for genre/mood requests and list matching songs in media_id. + artist_prompt: + name: Artist Prompt + description: The prompt that the LLM will use to provide the artist. + selector: + text: + multiline: true + multiple: false + default: + Requested artist or track artist. Empty string if unknown or multiple artists requested. + album_prompt: + name: Album Prompt + description: The prompt that the LLM will use to provide the album. + selector: + text: + multiline: true + multiple: false + default: + Requested album. Empty string if unknown or multiple albums requested. + media_id_prompt: + name: Media ID Prompt + description: The prompt that the LLM will use to provide the media_id. + selector: + text: + multiline: true + multiple: false + default: |- + Mandatory. Specific name(s). + - Tracks/Albums: Provide names (semicolon separated). If ambiguous, use "Artist - Track". + - Artists/Playlists/Radio: Provide exact name. Remove "playlist" keyword (e.g., "Favorites"). + media_description_prompt: + name: Media Description Prompt + description: The prompt that the LLM will use to provide the media description. + selector: + text: + multiline: true + multiple: false + default: + The "media_description" key is used to describe the media which + will be played. This can be taken from the voice command query, but it + should be only the part which is relevant for the media. So if the voice + request is "Play the best Queen songs on the living room player" the value + for "media_description" should be "the best Queen songs" + area_prompt: + name: Area Prompt + description: The prompt that the LLM will use to provide the area. + selector: + text: + multiline: true + multiple: false + default: + Requested area(s). Default to the area of the initiating device. Omit if player is specified unless both are explicitly named. + media_player_prompt: + name: Media Player Prompt + description: The prompt that the LLM will use to provide the media player. + selector: + text: + multiline: true + multiple: false + default: + Target Music Assistant media_player entity_id(s). Use ONLY if specifically mentioned. + shuffle_prompt: + name: Shuffle Prompt + description: The prompt that the LLM will use to determine whether to turn on shuffle or not. + selector: + text: + multiline: true + multiple: false + default: + Mandatory. Boolean. Set true ONLY if the user specifically mentions shuffling. Otherwise false. Do not guess. + addition_conditions_actions: + name: Additional actions + icon: mdi:wrench + description: + You can add additional actions to be performed after the Music + Assistant play media action is started. Variables which can always be used + are media_id, media_type, album, artist. Based on the request, the variables + area and media_player can also be available. + collapsed: true + input: + actions: + name: Additional actions + selector: + action: {} + default: [] +mode: parallel +max_exceeded: silent +description: Plays music via Music Assistant. Supports tracks, albums, artists, playlists, and radio. Requires media type, ID, and shuffle status. +fields: + media_type: + selector: + select: + options: + - track + - album + - artist + - playlist + - radio + name: Media Type + description: !input media_type_prompt + required: true + artist: + selector: + text: + name: Artist + description: !input artist_prompt + required: true + album: + selector: + text: + name: Album + description: !input album_prompt + required: true + media_id: + selector: + text: + name: Media ID + description: !input media_id_prompt + required: true + media_description: + selector: + text: + name: Media Description + description: !input media_description_prompt + required: true + area: + selector: + area: + entity: + integration: music_assistant + domain: media_player + multiple: true + name: Area + description: !input area_prompt + media_player: + selector: + entity: + filter: + integration: music_assistant + domain: media_player + multiple: true + name: Media Player + description: !input media_player_prompt + shuffle: + selector: + boolean: + name: Shuffle + description: !input shuffle_prompt +sequence: + - variables: + version: 20260208 + default_player: !input default_player + shuffle: "{{ shuffle | default(false, true) | bool(false) }}" + player_data: >- + {% set ma = integration_entities('music_assistant') %} + {% set ma_names = ma | map('state_attr', 'friendly_name') | list %} + {% set players_in = media_player if media_player is list else ([media_player] if media_player else []) %} + {% set ns = namespace(players=[]) %} + {% for player in players_in %} + {% if player in ma %} + {% set ns.players = ns.players + [player] %} + {% elif player in ma_names %} + {% set entity = ma | select('is_state_attr', 'friendly_name', player) | list %} + {% set ns.players = ns.players + entity %} + {% endif %} + {% endfor %} + {{ ns.players }} + target_data: + area_id: "{{ area | default('NA', true) }}" + entity_id: >- + {{ 'NA' if area | default and not player_data else (player_data or default_player) | default('NA', true) }} + invalid_target: + response: Unable to find valid target + - if: + - condition: template + value_template: "{{ not (area | default or player_data or default_player) }}" + then: + - stop: No valid target for Music Assistant Voice script found + response_variable: invalid_target + - variables: + play_continuously: !input play_continuously + action_data: + media_id: >- + {{ media_id if media_id is list else (media_id.split(';') | map('trim') | list if ';' in media_id else media_id) }} + media_type: "{{ media_type }}" + artist: "{{ artist | default('NA', true) }}" + album: "{{ album | default('NA', true) }}" + radio_mode: >- + {{ false if play_continuously == 'Never' else play_continuously == 'Always' and media_type != 'radio' or 'NA' }} + - alias: Play music using Music Assistant + action: music_assistant.play_media + data: "{{ dict(action_data.items() | rejectattr('1', 'eq', 'NA')) }}" + target: "{{ dict(target_data.items() | rejectattr('1', 'eq', 'NA')) }}" + - alias: Enable/Disable shuffle on a Music Assistant Media Player + action: media_player.shuffle_set + continue_on_error: true + data: + shuffle: "{{ shuffle }}" + target: "{{ dict(target_data.items() | rejectattr('1', 'eq', 'NA')) }}" + - sequence: !input actions diff --git a/create_calendar_event_full_llm.yaml b/create_calendar_event_full_llm.yaml index 7579d6f..56fea6f 100644 --- a/create_calendar_event_full_llm.yaml +++ b/create_calendar_event_full_llm.yaml @@ -170,7 +170,7 @@ sequence: - condition: template value_template: >- {% set feats = (state_attr(calendar, 'supported_features') | int(0)) -%} - {{ (feats % 2) != 1 }} + {{ not (feats | bitwise_and(1)) }} then: - alias: Set variable for error message variables: diff --git a/create_lunar_events.yaml b/create_lunar_events.yaml index 536fbad..1d762c7 100644 --- a/create_lunar_events.yaml +++ b/create_lunar_events.yaml @@ -111,7 +111,7 @@ sequence: - condition: template value_template: >- {% set feats = (state_attr(calendar, 'supported_features') | int(0)) -%} - {{ (feats % 2) != 1 }} + {{ not (feats | bitwise_and(1)) }} then: - alias: Stop the script stop: The event could not be added because the calendar entity lacks write permissions. diff --git a/fan_speed_and_oscillation_control_full_llm.yaml b/fan_speed_and_oscillation_control_full_llm.yaml index 312a4ae..72405bb 100644 --- a/fan_speed_and_oscillation_control_full_llm.yaml +++ b/fan_speed_and_oscillation_control_full_llm.yaml @@ -229,7 +229,7 @@ sequence: {% for entity_id in devices -%} {% set feats = (state_attr(entity_id, 'supported_features') | int(0)) -%} {% set device_name = state_attr(entity_id, 'friendly_name') | default(entity_id) -%} - {% set osc_ok = (((feats // 2) % 2) == 1) or (state_attr(entity_id, 'oscillating') is not none) -%} + {% set osc_ok = (feats | bitwise_and(2)) or (state_attr(entity_id, 'oscillating') is not none) -%} {% if not osc_ok -%} {% set result.values = result.values + [device_name] -%} {% endif -%} @@ -246,7 +246,7 @@ sequence: {% for entity_id in devices -%} {% set feats = (state_attr(entity_id, 'supported_features') | int(0)) -%} {% set device_name = state_attr(entity_id, 'friendly_name') | default(entity_id) -%} - {% set speed_ok = ((feats % 2) == 1) or (state_attr(entity_id, 'percentage') is not none) or (state_attr(entity_id, 'percentage_step') is not none) -%} + {% set speed_ok = (feats | bitwise_and(1)) or (state_attr(entity_id, 'percentage') is not none) or (state_attr(entity_id, 'percentage_step') is not none) -%} {% if not speed_ok -%} {% set result.values = result.values + [device_name] -%} {% endif -%} diff --git a/memory_tool_local.yaml b/memory_tool_local.yaml index ae830cd..610e1cd 100644 --- a/memory_tool_local.yaml +++ b/memory_tool_local.yaml @@ -1,5 +1,5 @@ blueprint: - name: Voice - Memory Tool + name: Voice - Memory Tool Local author: luuquangvu source_url: https://github.com/luuquangvu/tutorials/blob/dev/memory_tool_local.yaml description: |- diff --git a/weather_forecast_full_llm.yaml b/weather_forecast_full_llm.yaml new file mode 100644 index 0000000..c38dd18 --- /dev/null +++ b/weather_forecast_full_llm.yaml @@ -0,0 +1,254 @@ +blueprint: + name: Voice - Get Home Weather Forecast + author: luuquangvu + source_url: https://github.com/luuquangvu/tutorials/blob/dev/weather_forecast_full_llm.yaml + description: |- + # Tool for requesting the weather forecast used for Voice Assistant + + ### Blueprint setup + + #### Required + + * Set a weather entity to use for the forecasts + + ### Optional + + - Adjust the prompts for each field used in the script. The descriptions guide the LLM to provide the correct input. + + ### Note + + - Provide a concise and precise description for the script. This will be utilized by the LLM to understand it should use this script for requesting the weather forecast. + - Make sure to expose the script to Assist after the script has been saved. + - Do not alter the default script name. + - Once the script is created, click the three dots in the top right corner, choose "Edit in YAML," and remove the `description: ...` line to restore the default. This step is important because it helps the LLM better understand the script's purpose. + + ### Credit + + - Special thanks to the original blueprint from [TheFes/ha-blueprints](https://github.com/TheFes/ha-blueprints). This version has been refined and optimized specifically for use with Gemini. + domain: script + homeassistant: + min_version: 2024.10.0 + input: + weather_settings: + name: Weather settings + icon: mdi:weather-lightning-rainy + description: Here you need to select the weather entity you want to use + input: + weather_entity: + name: Weather entity + description: + Select the weather entity to use for the forecast. Needs to + be a weather entity which provides an hourly and daily forecast + selector: + entity: + multiple: false + filter: + - domain: + - weather + calc_average: + name: Calculate averages + description: + "When this is enabled, the LLM will only receive the average + of the data for the selected period, instead of the data for each hour/day. + For text fields like the current condition, the most frequent value will + be sent. + + When disabled all data for each hour or day will be sent." + selector: + boolean: {} + default: true + prompt_settings: + name: Prompt settings for the LLM + icon: mdi:robot + description: + You can use these settings to finetune the prompts for your specific + LLM (model). In most cases the defaults should be fine. + collapsed: true + input: + time_period_type_prompt: + name: Time period type prompt + description: + The prompt which will be used to the LLM can provide the type + for the time period (days or hours) + selector: + text: + multiline: true + multiple: false + default: |- + Mandatory. 'daily' for full days (today, weekend, etc.); 'hourly' for partial days (this morning, at 3 PM, etc.). Output exactly: daily|hourly. + time_period_length_prompt: + name: Time period length prompt + description: + The prompt which will be used to the LLM can provide the length + of the time period. + selector: + text: + multiline: true + multiple: false + default: |- + Mandatory. Integer length. Number of days (if daily) or hours (if hourly). E.g., 1 (today), 2 (weekend), 6 (afternoon/evening periods). + date_prompt: + name: Date prompt + description: + The prompt which will be used to the LLM can provide the start + date for the forecast period + selector: + text: + multiline: true + multiple: false + default: |- + Mandatory. Start date (YYYY-MM-DD). Default today. For 'night' requests, use next day's date unless current time is < 05:00:00. + time_prompt: + name: Time prompt + description: + The prompt which will be used to the LLM can provide the start + time for the forecast period + selector: + text: + multiline: true + multiple: false + default: |- + Mandatory. Start time (HH:MM:SS). Full day = 00:00:00. Morning = 06:00:00, Afternoon = 12:00:00, Evening = 18:00:00, Night = 00:00:00. +mode: parallel +max_exceeded: silent +description: Retrieves home weather forecasts for specific periods (hourly or daily). Mandatory input for period type, length, start date, and time. Use 'daily' for full days and 'hourly' for partial day requests. +fields: + time_period_type: + name: Time Period Type + description: !input time_period_type_prompt + selector: + select: + options: + - daily + - hourly + required: true + time_period_length: + name: Time Period Length + description: !input time_period_length_prompt + selector: + number: + min: 1 + max: 48 + required: true + start_date: + name: Start Date + description: !input date_prompt + selector: + date: + required: true + start_time: + name: Start Time + description: !input time_prompt + selector: + time: + required: true +sequence: + - variables: + version: 20260208 + weather_entity: !input weather_entity + supported: > + {% set sf = state_attr(weather_entity, 'supported_features') | int(0) %} + {% set d = ['daily'] if sf | bitwise_and(1) else [] %} + {% set h = ['hourly'] if sf | bitwise_and(2) else [] %} + {% set td = ['twice_daily'] if sf | bitwise_and(4) else [] %} + {{ d + h + td }} + time_period_type: > + {{ + 'twice_daily' + if time_period_type not in supported + and time_period_type == 'daily' + and 'twice_daily' in supported + else time_period_type + }} + start_date: "{{ start_date | as_datetime(default='') }}" + start_time: + "{{ start_time | default('00:00:00') | as_timedelta | default('00:00:00', + true) }}" + start: + "{{ (start_date | as_datetime + as_timedelta(start_time)) | as_local if + start_date else 'NA' }}" + end: "{% if start != 'NA' %} + + {% set start = as_datetime(start) %} + + {% set add = time_period_length | default(1) | int(1) %} + + {% set type = time_period_type | default if time_period_type | default in ['daily', 'hourly'] + else 'daily' %} + + {{ (start + timedelta(days = add if type == 'daily' else 0, hours = add if type == 'hourly' + else 0)).isoformat() }} + + {% endif %}" + - alias: Check if variables were set correctly + if: + - condition: template + value_template: "{{ start == 'NA' or (end | as_datetime | default(now(), true) < now()) }}" + then: + - alias: Set variable for eror message + variables: + response: + error: + Unable to provide forecast because not all parameters were set by the + AI agent + - alias: Stop the script + stop: + Unable to determine the date for the weather forecast, or the date is in + the past + response_variable: response + - if: "{{ time_period_type not in supported }}" + then: + - alias: Set variable for eror message + variables: + response: + error: + Unable to provide forecast because the weather provider doesn't provide + the {{ time_period_type }} forecast data + - alias: Stop the script + stop: Weather provider doesn't provide {{ time_period_type }} forecast data + response_variable: response + else: + - action: weather.get_forecasts + data: + type: "{{ time_period_type }}" + target: + entity_id: !input weather_entity + response_variable: weather_response + - variables: + calc_average: !input calc_average + selected_data: + forecast: "{% set ns = namespace(forecast=[]) %} + + {% for item in weather_response[weather_entity].forecast %} + + {% if start | as_datetime <= item.datetime | as_datetime | as_local < end | as_datetime %} + + {% set ns.forecast = ns.forecast + [item] %} + + {% endif %} + + {% endfor %} {{ ns.forecast }}" + averaged: + averaged_weather_data_for_period: + "{% set ns = namespace(combined={}) %} + + {% set weather_data = selected_data.forecast %} + + {% set forecast_keys = (weather_data[0] | default({})).keys() | reject('eq', 'datetime') %} + + {% for item in forecast_keys %} + + {% set combine = weather_data | selectattr(item, 'defined') | map(attribute=item) | list %} + + {% set combine = combine | statistical_mode if combine | count > 0 and combine[0] is string else combine | average(none) %} + + {% set combine = combine | round(1 if combine < 1 else 0) if combine | is_number else combine %} + + {% set ns.combined = (dict(ns.combined, **{item: combine})) if combine is not none else ns.combined %} + + {% endfor %} + + {{ dict(start_of_period=start, end_of_period=end, **ns.combined) }}" + response: "{{ averaged if calc_average else selected_data }}" + - stop: "" + response_variable: response