Friday's Party: Creating a Private, Agentic AI using Voice Assistant tools

Where do I add all this Context you’re always yammering on about, Nathan?

Here: Is it possible to define aliases for ToDo items? - #5 by NathanCu

But all of the entties support this. Note every word you put in here eats toks for context. be descriptive but be aware of the result. This will directly impact the context size and either be concise or use a model with a gigantic context window and be ready to burn toks.

Workflow Ala HA voice…

This is now technically possible in 2025.7.x. Someone please write a good example?

In today’s episode of

wierd stuff we haven’t confirmed but sure seems to be the case from the data…

Essentially what this means, HA’s default context does include name, alias, and entity_id but does NOT include device_id.

Given context crafting rules that means

until I create context for her to understand device ID IT DOES NOT EXIST in the Assist context space…

…therefore targeting by device ID without building your own resolution process for an LLM won’t work in most cases.

I believe it’s deliberately not possible to control via device_ID through Assist. In fact, it seems that they don’t want us doing it that way at all, but haven’t yet removed the ability to do so.

it’s a very lively summer and we had quite an adventure with an oven throwing fireballs and then thank g-d for carbon monoxide detectors and homepod sound detection. i will be catching up soon, i’ve just been in a funnel. <4

1 Like

Sounds like events you didn’t want. Hope all is ok.

Notes on Searching with OpenAI

If you turn on web search in your llm provider, make sure you tell it how to behave…

(I found a bug in the files crud handler had to fix before posting. Probably tomorrow. This will do till then.)

1 Like

A bunch of Tool updates to prep for File Tool

Script - Zen Index 2.6:
(REQUIRES Inspect)

alias: DojoTOOLS Zen Index
description: >-
  The Zen Indexer (2.6.0) is a DIRECT replacement and the next version of the Library
  ~INDEX~ Command.  ALWAYS prefer it over old index tools in place of THE
  LIBRARY ~INDEX~. Set logic on two lists of entity_ids (each resolved from a
  label or list of entities). - If only one side provided, returns that set. -
  If both, applies operator (AND, OR, NOT, XOR). - If neither, returns an error
  or (special case) full label index (operator = '*'). - Optional expand to
  resolve groups/areas. Returns matching entities, adjacent labels, expansion
  can include state and attribute list. result.simple ALWAYS returns a list (for
  chaining). If index_command is set, fires zen_indexer_request event and exits
  for recursive resolution.
fields:
  index_command:
    name: Zen Index Command
    description: >-
      Structured query string (if set, all other fields are ignored and this
      triggers recursive index resolution via event). If parsing fails or times
      out, will fall back to using the other fields.
    required: false
    selector:
      text: {}
  entities_1:
    name: Entities 1
    description: List of entity_ids for operand 1 (used if label_1 is blank)
    required: false
    selector:
      entity:
        multiple: true
  label_1:
    name: Label 1
    description: Label for operand 1 (used if entities_1 is empty)
    required: false
    selector:
      text: {}
  entities_2:
    name: Entities 2
    description: List of entity_ids for operand 2 (used if label_2 is blank)
    required: false
    selector:
      entity:
        multiple: true
  label_2:
    name: Label 2
    description: Label for operand 2 (used if entities_2 is empty)
    required: false
    selector:
      text: {}
  operator:
    name: Set Operator
    description: Logical set operator to apply between operand 1 and 2 (AND, OR, NOT, XOR)
    required: true
    default: AND
    selector:
      select:
        options:
          - AND
          - OR
          - NOT
          - XOR
  expand_entities:
    name: Expand results
    description: >-
      If true, expand each resulting entity to include its state and attribute
      keys (may produce a large output). Best practice: use labels to narrow
      results before expanding. Default: false.
    required: false
    default: false
    selector:
      boolean: {}
  timeout:
    name: Timeout
    description: >-
      Timeout (in seconds) to wait for index resolution via event if using
      index_command.  Default 2 seconds (max 5). If timeout occurs, script will
      fall back to using provided fields or full index.
    required: false
    default: 2
    selector:
      number:
        min: 0
        max: 5
        step: 0.25
sequence:
  - variables:
      is_index_command: "{{ index_command is defined and index_command | length > 0 }}"
      correlation_id: |-
        {% if index_command is defined and index_command | length > 0 %}
          {{ now().isoformat() ~ '-' ~ (range(1000) | random) }}
        {% else %}
          ''
        {% endif %}
      timeout_seconds: "{{ timeout | default(2) }}"
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ is_index_command }}"
        sequence:
          - alias: Send Index Command to Parser
            event: zen_indexer_request
            event_data:
              index_command: "{{ index_command }}"
              correlation_id: "{{ correlation_id }}"
          - alias: Wait for Parser Response ({{ timeout_seconds }}s)
            wait_for_trigger:
              - event_type: zen_index_response
                event_data:
                  correlation_id: "{{ id }}"
                trigger: event
            timeout:
              seconds: "{{ timeout_seconds }}"
          - choose:
              - conditions:
                  - condition: template
                    value_template: "{{ wait.trigger is defined and wait.trigger is not none }}"
                sequence:
                  - variables:
                      simple: "{{ wait.trigger.event.data.response.result.simple }}"
                      index_timeout: false
              - conditions: []
                sequence:
                  - variables:
                      simple: []
                      index_timeout: true
                      timeout_error_msg: Indexer call timeout {{ timeout_seconds }}s
        alias: Send Index Command to Parser and resolve to fields
  - variables:
      index_command: ""
      op1: >-
        {% set ents = simple if (simple is defined and simple) else [] %} {% if
        ents | length == 0 %}
          {% set ents = entities_1 if (entities_1 is defined and entities_1) else [] %}
          {% if ents | length == 0 and label_1 %}
            {{ label_entities(label_1) }}    # If still empty and label_1 provided, retrieve entities by label_1
          {% else %}
            {{ ents }}
          {% endif %}
        {% else %}
          {{ ents }}
        {% endif %}
      op2: >-
        {% set ents = entities_2 if (entities_2 is defined and entities_2) else
        [] %} {% if ents | length == 0 and label_2 %}
          {{ label_entities(label_2) }}
        {% else %}
          {{ ents }}
        {% endif %}
      op1_empty: "{{ op1 | length == 0 }}"
      op2_empty: "{{ op2 | length == 0 }}"
      setop: "{{ operator | default('AND') | upper }}"
      entities_base: |-
        {% if not op1_empty and op2_empty %}
          {{ op1 }}
        {% elif not op2_empty and op1_empty %}
          {{ op2 }}
        {% elif not op1_empty and not op2_empty %}
          {% if setop == 'AND' %}
            {{ op1 | intersect(op2) }}
          {% elif setop == 'OR' %}
            {{ op1 + op2 | unique | list }}
          {% elif setop == 'NOT' %}
            {{ op1 | difference(op2) }}
          {% elif setop == 'XOR' %}
            {{ op1 | symmetric_difference(op2) }}
          {% else %}
            {{ op1 + op2 | unique | list }}
          {% endif %}
        {% else %}
          []   # Both op1 and op2 empty: no entities in base set (will trigger full index fallback below)
        {% endif %}
  - if:
      - condition: template
        value_template: "{{ expand_entities }}"
        alias: if expand_entities
    then:
      - action: script.dojotools_zen_inspect
        metadata: {}
        data:
          entity_id: "{{entities_base}}"
        response_variable: response
      - stop: Pass Variable
        response_variable: response
      - variables:
          expanded_entities: "{{ response }}"
    else:
      - variables:
          expanded_entities: "{{ entities_base | tojson }}"
  - variables:
      result_simple: "{{ entities_base | tojson }}"
      adjacent_labels: |-
        {%- set all_labels = [] -%} {%- for e in entities_base -%}
          {%- set all_labels = all_labels + (labels(e) | list) -%}
        {%- endfor -%} {{ all_labels | unique | list | tojson }}
      result_index: |-
        {%- set index_list = [] -%} {%- for e in entities_base -%}
          {%- set index_list = index_list + [[ e, (labels(e) | list) ]] -%}
        {%- endfor -%} {{ index_list | tojson }}
      error_msg: |-
        {% set msg = '' %} {% if index_timeout is defined and index_timeout %}
          {% set msg = 'Indexer call timeout ' ~ timeout_seconds ~ 's. Fallback path used.' %}
        {% elif op1_empty and op2_empty %}
          {% set msg = 'Both operands are empty. Returned full label index.' %}
        {% endif %} {{ msg }}  # msg will be empty string if no error
      label_entity_logic_result_raw: |-
        { 
          "result": {
            "simple": {{ result_simple }},
            "expanded": {{ expanded_entities }},
            "adjacent_labels": {{ adjacent_labels }},
            "index": {% if op1_empty and op2_empty %} {{ labels() | list | tojson }} {% else %} {{ result_index }} {% endif %}
          },
          "operator": "{{ "*" if (op1_empty and op2_empty) else setop }}",
          "inputs": {
            "entities_1": {{ (entities_1 | default([])) | tojson }},
            "label_1": {{ (label_1 | default('')) | tojson }},
            "entities_2": {{ (entities_2 | default([])) | tojson }},
            "label_2": {{ (label_2 | default('')) | tojson }},
            "expand_entities": {{ (expand_entities | default(false)) | tojson }}
          },
          "error": {{ (error_msg if error_msg != '' else none) | tojson }}
        }
      label_entity_logic_result: "{{ label_entity_logic_result_raw | from_json | default({}) }}"
  - stop: Zen Index Call Complete
    response_variable: label_entity_logic_result
mode: parallel
max: 10

Automation - Index Event Handler:

alias: DojoTools Zen Index Event Handler
description: >
  Event Listener (2.0.0) to recurse index calls.  REQUIRES DojoTOOLS Zen Index (2.0.0 or
  greater)
triggers:
  - event_type: zen_indexer_request
    trigger: event
actions:
  - variables:
      index_command: "{{ trigger.event.data.index_command | default('{}', true) }}"
      correlation_id: "{{ trigger.event.data.correlation_id }}"
      depth: "{{ trigger.event.data.depth if 'depth' in trigger.event.data else 1 }}"
      is_mapping: |-
        {% set cmd = index_command | trim %} {% if cmd.startswith('{') %}
          {{ (cmd | from_json) is mapping }}
        {% else %}
          false
        {% endif %}
      parsed_cmd: >
        {% set reserved = ['AND', 'OR', 'XOR', 'NOT'] %} {% set true_tokens =
        ['true', 't', 'y', 'yes', 'on', '1'] %} {% set false_tokens = ['false',
        'f', 'n', 'no', 'off', '0'] %} {% set raw = index_command | string |
        trim %} {% set is_json = raw.lstrip().startswith('{') %} {% if is_json
        %}
          {{ raw }}
        {% elif raw == "" or raw == "*" %}
          { "label_1": "*", "operator": "AND", "expand_entities": false }
        {% else %}
          {% set tokens = raw.split() %}
          {% set exp = false %}
          {% if tokens[-1] | lower in true_tokens %}
            {% set exp = true %}
            {% set tokens = tokens[:-1] %}
          {% elif tokens[-1] | lower in false_tokens %}
            {% set tokens = tokens[:-1] %}
          {% endif %}
          {% set op_index = namespace(idx = none) %}
          {% for i in range(tokens | length) %}
            {% if tokens[i] | upper in reserved %}
              {% set op_index.idx = i %}
              {% break %}
            {% endif %}
          {% endfor %}
          {% if op_index.idx is not none %}
            {% set label_1 = tokens[:op_index.idx] | join(" ") | trim("'\"") %}
            {% set op = tokens[op_index.idx] | upper %}
            {% set label_2 = tokens[op_index.idx+1:] | join(" ") | trim("'\"") %}
          {% else %}
            {% set label_1 = tokens | join(" ") | trim("'\"") %}
            {% set op = "AND" %}
            {% set label_2 = "" %}
          {% endif %}
          {
            "label_1": "{{ label_1 }}",
            "label_2": "{{ label_2 }}",
            "operator": "{{ op }}",
            "expand_entities": {{ exp }}
          }
        {% endif %}
      entities_1: >-
        {% set ents = parsed_cmd.entities_1 if parsed_cmd.entities_1 is defined
        and parsed_cmd.entities_1 else [] %} {% if ents is string %}
          {% set ents = ents | from_json | default([]) %}
        {% endif %} {% if ents | length == 0 and parsed_cmd.label_1 %}
          {{ label_entities(parsed_cmd.label_1) | default([]) }}
        {% else %}
          {{ ents }}
        {% endif %}
      label_1: "{{ parsed_cmd.label_1 if parsed_cmd.label_1 is defined else '' }}"
      entities_2: >-
        {% set ents = parsed_cmd.entities_2 if parsed_cmd.entities_2 is defined
        and parsed_cmd.entities_2 else [] %} {% if ents is string %}
          {% set ents = ents | from_json | default([]) %}
        {% endif %} {% if ents | length == 0 and parsed_cmd.label_1 %}
          {{ label_entities(parsed_cmd.label_1) | default([]) }}
        {% else %}
          {{ ents }}
        {% endif %}
      label_2: "{{ parsed_cmd.label_2 if parsed_cmd.label_2 is defined else '' }}"
      operator: |-
        {% if parsed_cmd.operator is defined and parsed_cmd.operator %}
          {{ parsed_cmd.operator }}
        {% else %}
          AND
        {% endif %}
      expand_entities: >-
        {{ parsed_cmd.expand_entities if parsed_cmd.expand_entities is defined
        else false }}
  - alias: Dispatch Zen Index Command
    data:
      entities_1: "{{ entities_1 }}"
      label_1: "{{ label_1 }}"
      entities_2: "{{ entities_2 }}"
      label_2: "{{ label_2 }}"
      operator: "{{ operator }}"
      expand_entities: "{{ expand_entities }}"
    response_variable: zen_index_response
    action: script.dojotools_zen_index
  - alias: Return Zen Indexer Result
    event: zen_index_response
    event_data:
      correlation_id: "{{ correlation_id }}"
      response: "{{ zen_index_response }}"
mode: queued

Script - Inspect: (Attribute dumper, plus, note bug still outputs stringified JSON)

alias: DojoTOOLS Zen Inspect
mode: parallel
description: >-
  Inspect (1.2.0) Deep dive on one or more entities.  THis tool lprovides the expand function of
  the Zen Indexer. Accepts a list of entity_ids - dumps attributes Exposes
  volume headers for volumes in the library, Highlights device trackers for
  person domain entities and more! Optional inference support is coming soon for
  an deeper analysis on provided data.
fields:
  entity_id:
    required: true
    selector:
      entity:
        multiple: true
  infer:
    required: false
    selector:
      boolean: {}
    description: Use local inference to add context to the result (VERY EXPENSIVE call)
  timeout:
    selector:
      number:
        min: 30
        max: 300
        step: 0.25
    name: Timeout
    description: >-
      Timeout, in seconds to wait for the inference return in seconds (Default -
      60, Min - 30, Max - 300, Step - 5)
    default: 60
    required: false
sequence:
  - variables:
      entity_list: "{{ entity_id }}"
      results: []
  - repeat:
      for_each: "{{ entity_list }}"
      sequence:
        - variables:
            current_entity: "{{ repeat.item }}"
            labels_raw: "{{ labels(current_entity) }}"
            adjacent_labels: "{{ labels_raw | unique | sort | list | tojson }}"
            raw_volume: "{{ state_attr(current_entity, 'variables') }}"
            is_person: "{{ current_entity.startswith('person.') }}"
            user_id: "{{ state_attr(current_entity, 'user_id') if is_person else none }}"
            last_updated: >-
              {{ as_timestamp(states[current_entity].last_updated, default=0) |
              timestamp_local }}
            device_trackers: >-
              {{ (state_attr(current_entity, 'device_trackers') if is_person
              else [] ) | tojson }}
            parsed_volume: |
              {% if raw_volume is string and raw_volume.startswith('{') %}
                {{ raw_volume | from_json }}
              {% elif raw_volume is mapping %}
                {{ raw_volume }}
              {% else %}
                {} 
              {% endif %}
            cab_valid: |
              {{ raw_volume is mapping and
                 raw_volume.AI_Cabinet_VolumeInfo is defined and
                 raw_volume.AI_Cabinet_VolumeInfo.value is mapping and
                 raw_volume.AI_Cabinet_VolumeInfo.value.validation == "ALLYOURBASEAREBELONGTOUS" }}
            cab_response: |
              {% if cab_valid %}
                {{
                  {
                    "id": raw_volume.AI_Cabinet_VolumeInfo.value.id | default(""),
                    "friendly_name": raw_volume.AI_Cabinet_VolumeInfo.value.friendly_name | default(""),
                    "description": raw_volume.AI_Cabinet_VolumeInfo.value.description | default(""),
                    "timestamp": raw_volume.AI_Cabinet_VolumeInfo.timestamp | default(""),
                    "flags": raw_volume.AI_Cabinet_VolumeInfo.flags | default({}),
                    "acls": {
                      "owner": (
                        raw_volume.AI_Cabinet_VolumeInfo.acls.owner[0].entity_id
                        if raw_volume.AI_Cabinet_VolumeInfo.acls is defined and
                           raw_volume.AI_Cabinet_VolumeInfo.acls.owner is defined and
                           raw_volume.AI_Cabinet_VolumeInfo.acls.owner[0].entity_id is defined
                        else ""
                      )
                    },
                    "index": (
                      raw_volume._label_index.value
                      if raw_volume._label_index is defined and raw_volume._label_index.value is defined
                      else []
                    )
                  }
                }}
              {% else %}
                { "defined": false, "error": "Invalid or missing AI Cabinet volume." }
              {% endif %}
            attr_dict: >
              {%- set attrs = states[current_entity].attributes if
              states[current_entity] else {} %}

              {%- set keys = attrs.keys() | list %}

              {%- set ns = namespace(vals=[]) %}

              {%- for v in attrs.values() %}
                {%- if v is none %}
                  {%- set ns.vals = ns.vals + [none] %}
                {%- elif v is datetime %}
                  {%- set ns.vals = ns.vals + [v.isoformat()] %}
                {%- elif v is mapping %}
                  {%- set ns.vals = ns.vals + [v] %}
                {%- elif v is iterable and not v is string %}
                  {%- set ns.vals = ns.vals + [v | list] %}
                {%- elif v is string or v is number or v is boolean %}
                  {%- set ns.vals = ns.vals + [v] %}
                {%- else %}
                  {# If it's anything else (enum, object, who-knows-what): DROP IT! #}
                {%- endif %}
              {%- endfor %}

              {{ dict(zip(keys, ns.vals)) }}
            fallback_response: |-
              {{
                {
                  "entity": current_entity | default(''),
                  "state": states(current_entity) | default(''),
                  "last_updated": last_updated,
                  "labels": labels_raw | default([]),
                  "attributes": attr_dict,
                  "user_id": user_id | default(''),
                  "device_trackers": device_trackers | default([])
                } | tojson
              }}
        - variables:
            inference: {}
            result: |-
              {{
                {
                  "entity": current_entity,
                  "inspection": fallback_response,
                  "inference": {
                    "unsupported": "infer in development, coming soon",
                  }
                } | tojson
              }}
            results: "{{ results + [result] }}"
  - variables:
      final_response: |
        {{
          {
            "results": results
          } | tojson 
        }}
  - stop: Multi-entity investigate complete
    response_variable: final_response

Script - Calculator:

alias: Zen DojoTOOLS Calculator
mode: parallel
description: >-
  Ultimate calculator for AI use. Accepts two numbers and an operator. Supports
  add, subtract, multiply, divide, exponent, sqrt, cbrt, sin, cos, tan,
  degrees_to_radians, radians_to_degrees, log, ln, percent_of,
  increase_by_percent, decrease_by_percent, round, floor, ceil, modulus,
  fahrenheit_to_celsius, celsius_to_fahrenheit, and a GUID Generator! Returns
  JSON output.  Drops inputs as necessary if operator does not require one or
  both. (v.1.3.0)
fields:
  number_1:
    name: First Number
    required: false
    selector:
      number:
        min: -1000000000000
        max: 1000000000000
        mode: box
  number_2:
    name: Second Number
    required: false
    selector:
      number:
        min: -1000000000000
        max: 1000000000000
        mode: box
  operator:
    name: Operator
    required: true
    selector:
      select:
        options:
          - add
          - subtract
          - multiply
          - divide
          - exponent
          - sqrt
          - cbrt
          - sin
          - cos
          - tan
          - degrees_to_radians
          - radians_to_degrees
          - log
          - ln
          - percent_of
          - increase_by_percent
          - decrease_by_percent
          - round
          - floor
          - ceil
          - modulus
          - fahrenheit_to_celsius
          - celsius_to_fahrenheit
          - guidgen
        translation_key: operator
        custom_value: false
sequence:
  - variables:
      n1: "{{ number_1 | default(0) | float(0) }}"
      n2: "{{ number_2 | default(0) | float(0) }}"
      op: "{{ operator | lower }}"
      result: |
        {% if op == 'add' %}
          {{ n1 + n2 }}
        {% elif op == 'subtract' %}
          {{ n1 - n2 }}
        {% elif op == 'multiply' %}
          {{ n1 * n2 }}
        {% elif op == 'divide' %}
          {% if n2 != 0 %}
            {{ n1 / n2 }}
          {% else %}
            'Error: Division by zero'
          {% endif %}
        {% elif op == 'exponent' %}
          {{ n1 ** n2 }}
        {% elif op == 'sqrt' %}
          {% if n1 >= 0 %}
            {{ n1 ** 0.5 }}
          {% else %}
            'Error: Square root of negative'
          {% endif %}
        {% elif op == 'cbrt' %}
          {{ n1 ** (1/3) }}
        {% elif op == 'sin' %}
          {{ n1 | sin }}
        {% elif op == 'cos' %}
          {{ n1 | cos }}
        {% elif op == 'tan' %}
          {{ n1 | tan }}
        {% elif op == 'degrees_to_radians' %}
          {{ n1 * 3.141592653589793 / 180 }}
        {% elif op == 'radians_to_degrees' %}
          {{ n1 * 180 / 3.141592653589793 }}
        {% elif op == 'log' %}
          {% if n1 > 0 %}
            {{ n1 | log10 }}
          {% else %}
            'Error: Log undefined for n1 ≤ 0'
          {% endif %}
        {% elif op == 'ln' %}
          {% if n1 > 0 %}
            {{ n1 | log }}
          {% else %}
            'Error: Natural log undefined for n1 ≤ 0'
          {% endif %}
        {% elif op == 'percent_of' %}
          {{ (n1 * n2) / 100 }}
        {% elif op == 'increase_by_percent' %}
          {{ n1 * (1 + n2/100) }}
        {% elif op == 'decrease_by_percent' %}
          {{ n1 * (1 - n2/100) }}
        {% elif op == 'round' %}
          {{ n1 | round(n2 | int(0)) }}
        {% elif op == 'floor' %}
          {{ n1 | floor }}
        {% elif op == 'ceil' %}
          {{ n1 | ceil }}
        {% elif op == 'modulus' %}
          {% if n2 != 0 %}
            {{ n1 % n2 }}
          {% else %}
            'Error: Modulus by zero'
          {% endif %}
        {% elif op == 'fahrenheit_to_celsius' %}
          {{ (n1 - 32) * 5 / 9 }}
        {% elif op == 'celsius_to_fahrenheit' %}
          {{ (n1 * 9 / 5) + 32 }}
        {% elif op == 'guidgen' %}
          {% macro random_string(len) -%}{% for i in range(0,len) -%}{{ [0,1,2,3,4,5,6,7,8,9,"a","b","c","d","e","f"]|random }}{% endfor %}{%- endmacro -%}
          {% macro random_guid() -%} {{ random_string(8) + "-" + random_string(4) + "-" + random_string(4) + "-" + random_string(4) + "-" + random_string(12) }}{%- endmacro -%}
          {% set myGUID = random_guid() %}
          {{ myGUID }}
        {% else %}
          'Error: Unknown operator'
        {% endif %}
      out: |
        {{
          {
            "number_1": n1,
            "number_2": n2,
            "operator": op,
            "result": result
          } | tojson
        }}
  - stop: Calculator done
    response_variable: out

Script - Die Roller:

alias: Zen DojoTOOLs Dice Roller (1.0.0)
mode: parallel
description: >-
  Rolls dice (DnD style), flips a coin, or generates a random number. Supports
  classic dice notation (e.g., '2d6', 'd20'), coin flip, random integer, and
  random float.
fields:
  mode:
    name: Mode
    required: true
    selector:
      select:
        options:
          - Dice
          - Random Integer
          - Random Float
          - Coin Flip
  dice_notation:
    name: Dice Notation (e.g. 3d6, d20)
    required: false
    selector:
      text: null
    description: >-
      Only used if Mode is 'Dice'. Format XdY or dY (e.g. 2d8, d20) only
      supports one dice type per roll (e.g., 3d6 or 2d20 but not 2d6+1d8)
  min:
    name: Minimum Value
    required: false
    selector:
      number:
        min: -1000000000000
        max: 1000000000000
        step: 1
        mode: box
    description: Only used for random number modes.
  max:
    name: Maximum Value
    required: false
    selector:
      number:
        min: -1000000000000
        max: 1000000000000
        step: 1
        mode: box
    description: Only used for random number modes.
sequence:
  - variables:
      md: "{{ mode | lower }}"
      dice: "{{ dice_notation | default('d6') }}"
      minval: "{{ min | int(1) }}"
      maxval: "{{ max | int(6) }}"
      result: >
        {% set m = dice.lower().replace(' ', '') %} {% set parts = m.split('d')
        %} {% set n = 1 if parts[0] == '' else parts[0] | int(1) %} {% set die =
        parts[1] | int(6) %} {% if md == 'dice' %}
          {% set faces = range(1, die+1) %}
          {% set ns = namespace(rolls=[]) %}
          {% for _ in range(n) %}
            {% set ns.rolls = ns.rolls + [faces | random] %}
          {% endfor %}
          {{ {
            "mode": "dice",
            "dice": m,
            "individual_rolls": ns.rolls,
            "total": ns.rolls | sum
          } }}
        {% elif md == 'random integer' %}
          {% if maxval >= minval %}
            {{ {
              "mode": "random_integer",
              "min": minval,
              "max": maxval,
              "value": (range(minval, maxval+1) | random)
            } }}
          {% else %}
            {{ { "error": "Max must be >= min" } }}
          {% endif %}
        {% elif md == 'random float' %}
          {% if maxval >= minval %}
            {% set f = (range(0, 100000) | random) / 100000 %}
            {% set val = minval + (maxval - minval) * f %}
            {{ {
              "mode": "random_float",
              "min": minval,
              "max": maxval,
              "value": val
            } }}
          {% else %}
            {{ { "error": "Max must be >= min" } }}
          {% endif %}
        {% elif md == 'coin flip' %}
          {{ {
            "mode": "coin_flip",
            "result": ['heads', 'tails'] | random
          } }}
        {% else %}
          {{ { "error": "Unknown mode" } }}
        {% endif %}
      out: |
        {{ result }}
  - stop: Dice/Random complete
    response_variable: out
icon: mdi:dice-d20-outline
1 Like

How to link entity_id to aliases:

Dirty but it’ll do until we have better.

Yes… For the record… Friday’s local inference target platform just became gpt-oss:20b on an Nvidia RTX 5060ti 16g or better. I can EGPU it into iona for about $1200

Translation : that’s o.1 performance at approx 100/mo plus power requirements if you ground correctly. Also the performance milestone I had hoped to hit later this year at approximately 2-3k$ so… Yes, game sufficiently changed. OAI… Good game. gpt-oss:20b is a MoE (mixture of experts) and specifically tuned for tools… If you had to habd pick a model for HA… This is the one.

Now this also means whatever is waiting in the wings for paid gpt5… Ohmy.

1 Like

Was updating Ollama et all today to give the 20B model a shot myself but it seems 2025.8 broke all of my input_text entities. I suppose with all of the changes it’s time for a refactor anyway. Anyone else?

e: Looks like this was because some had the ‘max’ attribute set in the config (artifacts from my own attempts at larger text storage)

1 Like

Nope

We good here… In fact just updated a few AI task entries and am about to finish up files.

1 Like

I got tired of waiting… When gpt-oss dropped it was time… Bug guns for Friday Local.

1 Like

lol, what are we looking at here?

1 Like

Nvidia RTX 5070ti 16G on an EGPU setup. It started with looking for a card to train my wake word… Then. Well…

1 Like

You learn something new every day. What’s the data bandwidth of that cable?

2 Likes

TB4 - 40gbps if your machine can do it. Another option is occulink! Direct PCIe (my machine doesn’t have it) the card will absolutely outperform the link.

Bonus the link also doubles as an atx header and a USB PD link. :rofl:

1 Like

Also haven’t heard about EGPU so far. Pretty cool. :upside_down_face:

1 Like

Remember I had that Digits pre-order? :smiling_imp: I took the money I’d saved so far and dumped it into this rig the price kept going up and. This was realistic and with the new model… :sunglasses: I put power to it 30 minutes ago and so far… No magic smoke.

The most immediate effect I noticed from gpt-oss is that it’s tool use training will blow through whatever prompting you have in place. Interested in seeing if that’s also just my environment.

1 Like