About making inexpensive models smarter by providing tools and context. (local models, gpt-5-mini, gpt-4.1-mini, gpt-4o-mini ...)

Well, this one is for handling Input Selects.

Not that I was in need for that one, as Nathan already has one here for you.

But I created a custom GPT in ChatGPT yesterday and tried to feed its prompt with all the learnings from the past.
About at which topics or details it fails almost every single time because of HA specific behavior regarding Jinja syntax / features.

And indeed, it was the first time where I didn’t need to fix a lot bugs manually in the generated code.
Simply worked on the first try. :partying_face:

Ok, it’s more on the simple side of scripting. But still, it gave me a glimmer of hope. :wink:

alias: Input Select Tool
icon: mdi:form-select
mode: parallel
description: >
  Assist LLM tool for  - reading available options of input_select entities -
  selecting one of those options.

  Purpose:
    - "get_options": Return the available options of an existing input_select.* entity
      (including the current value).
    - "select_option": Select one existing option for the given input_select.

  IMPORTANT:
    - You MUST provide the exact entity_id (e.g., "input_select.house_mode"). Do not invent entity names.
    - To find entities, use "Entity Index" tool.
    - Only works with the "input_select.*" domain.

  Parameters:
    - operation          [required]: "get_options" | "select_option"
    - entity_id          [required]: exact entity_id
    - desired_option     [required for select_option]: String; must match one of the existing options.

  Expected Output:
    - Success (get_options):
        result = {
          entity_id: string,
          current: string,
          options: [string, ...]
        }
        error  = null
    - Success (select_option):
        result = {
          entity_id: string,
          previous: string,
          selected: string,
          current: string,
          success: boolean
        }
        error  = null
    - Error (any operation):
        result = null
        error  = {
          error: string,
          (optional) allowed_operations: [...],
          (optional) expected_domain: "input_select",
          (optional) received: any,
          (optional) allowed_options: [string, ...]
        }
fields:
  operation:
    name: Operation
    required: true
    selector:
      select:
        options:
          - get_options
          - select_option
  entity_id:
    name: Entity ID (input_select.* only)
    required: true
    selector:
      entity:
        domain: input_select
  desired_option:
    name: Desired Option (for select_option)
    required: false
    selector:
      text: null
sequence:
  - action: logbook.log
    data:
      name: LLM INPUT SELECT TOOL (RAW)
      message: >
        operation={{ operation }}, entity_id={{ entity_id }}, desired_option={{
        desired_option }}
      entity_id: "{{ this.entity_id }}"
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ operation in ['get_options','select_option'] }}"
        sequence: []
    default:
      - variables:
          error_result: |
            {{ {
              'error': 'unsupported operation',
              'allowed_operations': ['get_options','select_option'],
              'received': operation
            } }}
      - stop: Unsupported operation
        response_variable: error_result
  - variables:
      eid: "{{ entity_id | string }}"
      entity_exists: "{{ states(eid) is not none and states(eid) != 'unknown' }}"
      is_input_select: "{{ eid | regex_search('^input_select\\..+') }}"
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ entity_exists and is_input_select }}"
        sequence: []
    default:
      - variables:
          error_result: |
            {{ {
              'error': 'Invalid or unknown entity_id. Provide an existing input_select entity.',
              'expected_domain': 'input_select',
              'received': eid
            } }}
      - stop: Invalid entity
        response_variable: error_result
  - variables:
      options_list_raw: "{{ state_attr(eid, 'options') }}"
      options_list: >
        {%- set ns = namespace(list=[]) -%} {%- if options_list_raw is iterable
        and not (options_list_raw is string) -%}
          {%- for o in options_list_raw -%}
            {%- if o is string -%}
              {%- set ns.list = ns.list + [ o ] -%}
            {%- else -%}
              {%- set ns.list = ns.list + [ (o|string) ] -%}
            {%- endif -%}
          {%- endfor -%}
        {%- endif -%} {{ ns.list }}
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ options_list | length > 0 }}"
        sequence: []
    default:
      - variables:
          error_result: |
            {{ {
              'error': 'Entity has no options attribute or it is empty.',
              'received': { 'entity_id': eid }
            } }}
      - stop: No options available
        response_variable: error_result
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ operation == 'get_options' }}"
        sequence:
          - variables:
              result: |
                {{ {
                  'entity_id': eid,
                  'current': states(eid),
                  'options': options_list,
                  'count': (options_list | length)
                } }}
          - stop: ""
            response_variable: result
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ operation == 'select_option' }}"
        sequence:
          - choose:
              - conditions:
                  - condition: template
                    value_template: >
                      {{ desired_option is defined and (desired_option | string
                      | trim) != '' }}
                sequence: []
            default:
              - variables:
                  error_result: |
                    {{ {
                      'error': 'Missing parameter: desired_option (string) for select_option.'
                    } }}
              - stop: Missing desired_option
                response_variable: error_result
          - variables:
              want: "{{ desired_option | string }}"
              ci: "{{ true }}"
              matches: >
                {%- set ns = namespace(list=[]) -%} {%- for opt in options_list
                -%}
                  {%- if ci -%}
                    {%- if (opt | lower) == (want | lower) -%}
                      {%- set ns.list = ns.list + [ opt ] -%}
                    {%- endif -%}
                  {%- else -%}
                    {%- if opt == want -%}
                      {%- set ns.list = ns.list + [ opt ] -%}
                    {%- endif -%}
                  {%- endif -%}
                {%- endfor -%} {{ ns.list }}
              resolved_option: |
                {%- if (matches | length) == 1 -%}
                  {{ matches[0] }}
                {%- else -%}
                  {{ none }}
                {%- endif -%}
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ operation == 'select_option' and (resolved_option is string) }}"
        sequence: []
    default:
      - variables:
          error_result: |
            {{ {
              'error': 'desired_option not found or ambiguous for this entity.',
              'allowed_options': options_list
            } }}
      - stop: Invalid desired_option
        response_variable: error_result
  - variables:
      prev: "{{ states(eid) }}"
  - action: input_select.select_option
    target:
      entity_id: "{{ eid }}"
    data:
      option: "{{ resolved_option }}"
  - variables:
      curr: "{{ states(eid) }}"
      result: |
        {{ {
          'entity_id': eid,
          'previous': prev,
          'selected': resolved_option,
          'current': curr,
          'success': (curr == resolved_option)
        } }}
  - stop: ""
    response_variable: result

I added this to the LLMs prompt:

Switching modes or selections from input_select entities:

If asked about changing modes, setting things to specific values or start something that sounds like one out of multiple choices, this often is realized by a input_select in the system.

You can list them with the ‘Entity Index’ tool and the tag ‘Selections’.

To get the possible values to select use the ‘get_options’ action of the ‘Input Select Tool’ tool.

If I tell you that something is available as input_select, use this knowledge.

I think it’s a quite nice pattern.

One example: I created an input select with no-selection, charge, complete-cleaning, <area1>, <area2>, drive-to-maintenance-position, …

In one part of my prompt I give the LLM application specific context that isn’t self-explaining.
I simply added there, that it can control the vacuum cleaner with an input select and that the action starts immediately once it selects an option.

Worked perfectly fine.

I bet there will be quite some use cases where this saves you from writing specific LLM scripts for a device / application.

1 Like