Trigger automation to follow "parallel" path - but I am not able to get it to work

I have been at this for a week now, and even though I don’t like it, I am literaly out of my depth.

I have been working on an automation that will do the following:

  1. Triggered by a detection of a person on an outside camera (eventually only between certain hours - but for now while testing - all hours).
  2. Take a snapshot and store it with a date and timestamp as well as the name of the camera
  3. Query LLM Vision to give me description of the feed of the camera
  4. Send me a notification of the description with the snapshot attached
  5. Send me a TTS voice message of the LLM description.

On the surface it seemed to be mostly working, but if there is a problem, timeout or insufficient tokens available on LLM Vision, the script got stuck and never finished.

I then decided to use a generic description (Person detected at Camera 6) and then call LLM Vision. If there was no response from LLM Vision within a certain timeframe, to just send the generic message and snapshot.

On the surface it seemed OK, but in reality, it just got stuck at LLM Vision again (no messages received).

I then decided to take a modified approach (now it is getting over complicated).

I created a script as below

driveway_llm_analysis:
  mode: parallel
  sequence:
    - service: llmvision.image_analyzer
      data:
        provider: redacted
        model: gemini-2.0-flash
        message: >
          Give a detailed description of the security camera footage observed.
        remember: false
        image_entity:
          - "{{ camera_entity }}"
        include_filename: true
        target_width: 1280
        max_tokens: 100
        generate_title: true
        expose_images: true
        response_variable: llm_out
    - service: input_text.set_value
      data:
        entity_id: input_text.driveway_analysis_title
        value: "{{ llm_out.title if llm_out.title is defined else '' }}"
    - service: input_text.set_value
      data:
        entity_id: input_text.driveway_analysis_text
        value: "{{ llm_out.choices[0].message.content if llm_out.choices is defined else '' }}"

I also created two text helpers called driveway_analysis_text and driveway_analysis_title.

The idea is that these helpers will have generic text related to the notification.
The automation then calls LLM Vision to update these two helpers. Should LLM Vision work, they will have good descriptions, if not, it will be generic.
The automation will have a 10s wait state to give the LLM Vision chance to react (or not).

After the 10s delay, the automation must send whatever is in these two helpers data to me. (Sounds workable [although too complicated IMO] enough in my mind)

Yet, it is not working and I have no idea what else to try (like I said - I have been at this for a week)

Below is the automation code

alias: Driveway Camera Person Detection Notification with LLM Vision and TTS
description: >
  Guaranteed driveway camera security alert with snapshot and TTS; LLM Vision
  analysis incorporated if available.
triggers:
  - entity_id: binary_sensor.camera_6_driveway_person_occupancy_2
    to: "on"
    trigger: state
conditions: null
actions:
  - variables:
      camera_entity: camera.camera_6_driveway_2
      dashboard_url: https://redacted.com:1234/security-cameras-frigate/frigate
      snapshot_filename: >-
        /config/www/tmp/driveway_person_{{ now().strftime('%Y%m%d_%H%M%S')
        }}.jpg
      snapshot_url: /local/tmp/driveway_person_{{ now().strftime('%Y%m%d_%H%M%S') }}.jpg
      analysis_title: Camera 6 Driveway person detection
      analysis_text: Person detected at Driveway Camera.
  - target:
      entity_id: "{{ camera_entity }}"
    data:
      filename: "{{ snapshot_filename }}"
    action: camera.snapshot
  - delay: "00:00:02"
  - data:
      camera_entity: "{{ camera_entity }}"
      response_variable: driveway_analysis
    action: script.driveway_llm_analysis
  - delay: "00:00:10"
  - variables:
      latest_title: >
        {% set r = states('input_text.driveway_analysis_title') %} {{ r if r
        else analysis_title }}
      latest_text: >
        {% set r = states('input_text.driveway_analysis_text') %} {{ r if r else
        analysis_text }}
  - data:
      title: "{{ latest_title }}"
      message: "{{ latest_text }}"
      data:
        image: "{{ snapshot_url }}"
        url: "{{ dashboard_url }}"
        clickAction: "{{ dashboard_url }}"
        tag: camera_person_detection
    action: notify.mobile_app_sm_s711b
  - data:
      message: "Security: {{ latest_text }}"
      data:
        tts_text: Security alert! {{ latest_text }}
    action: notify.mobile_app_sm_s711b
mode: single

Hello Leon Bester,

Start here maybe?
AI camera analysis.

Have you looked at the automation trace to see what happens? Maybe it aborts with an error for the timeout? In that case continue_on_error true might help. If it is scripting errors, then you know where to look. One thing I notice, llm_out can also be undefined so you cant test for data inside it.

I don’t think parallel mode will solve this problem. You could try this instead:

In the script:

  1. change parallel mode to restart
  2. change the text in the text helpers only if you got a “good” answer from LLM

In the automation:

  1. before calling your script, set both text helpers to default values
  2. run the script using script.turn_on - this is a non-blocking call that will return immediately, without a response (the new way to communicate with the script is through text helpers)
  3. use a wait_template to wait for max 10 seconds for the text to be changed by the script
  4. continue with the rest of the code

If HA cannot close your stuck script, it will fail to run a second time and a way must be found to prevent it from blocking in the first place. I’ve edited your code here, consider it a demo (it probably won’t work as is).

Script
driveway_llm_analysis:
  mode: restart
  sequence:
    - service: llmvision.image_analyzer
      data:
        provider: redacted
        model: gemini-2.0-flash
        message: >
          Give a detailed description of the security camera footage observed.
        remember: false
        image_entity:
          - "{{ camera_entity }}"
        include_filename: true
        target_width: 1280
        max_tokens: 100
        generate_title: true
        expose_images: true
        response_variable: llm_out
    - if: "{{ llm_out.title is defined and llm_out.choices is defined }}"
      then:
        - service: input_text.set_value
          data:
            entity_id: input_text.driveway_analysis_title
            value: "{{ llm_out.title }}"
        - service: input_text.set_value
          data:
            entity_id: input_text.driveway_analysis_text
            value: "{{ llm_out.choices[0].message.content }}"
Automation
alias: Driveway Camera Person Detection Notification with LLM Vision and TTS
description: >
  Guaranteed driveway camera security alert with snapshot and TTS; LLM Vision
  analysis incorporated if available.
triggers:
  - entity_id: binary_sensor.camera_6_driveway_person_occupancy_2
    to: "on"
    trigger: state
conditions: null
actions:
  - variables:
      camera_entity: camera.camera_6_driveway_2
      dashboard_url: https://redacted.com:1234/security-cameras-frigate/frigate
      snapshot_filename: >-
        /config/www/tmp/driveway_person_{{ now().strftime('%Y%m%d_%H%M%S')
        }}.jpg
      snapshot_url: /local/tmp/driveway_person_{{ now().strftime('%Y%m%d_%H%M%S') }}.jpg
      analysis_title: Camera 6 Driveway person detection
      analysis_text: Person detected at Driveway Camera.
  - service: input_text.set_value
    data:
      entity_id: input_text.driveway_analysis_title
      value: "{{ analysis_title }}"
  - service: input_text.set_value
    data:
      entity_id: input_text.driveway_analysis_text
      value: "{{ analysis_text }}"
  - target:
      entity_id: "{{ camera_entity }}"
    data:
      filename: "{{ snapshot_filename }}"
    action: camera.snapshot
  - delay: "00:00:02"
  - action: script.turn_on
    data:
      camera_entity: "{{ camera_entity }}"
    target:
      entity_id: script.driveway_llm_analysis
  - wait_template: "{{ states('input_text.driveway_analysis_text') != analysis_text }}"
    continue_on_timeout: true
    timeout: "10"
  - variables:
      latest_title: >
        {% set r = states('input_text.driveway_analysis_title') %} {{ r }}
      latest_text: >
        {% set r = states('input_text.driveway_analysis_text') %} {{ r }}
  - data:
      title: "{{ latest_title }}"
      message: "{{ latest_text }}"
      data:
        image: "{{ snapshot_url }}"
        url: "{{ dashboard_url }}"
        clickAction: "{{ dashboard_url }}"
        tag: camera_person_detection
    action: notify.mobile_app_sm_s711b
  - data:
      message: "Security: {{ latest_text }}"
      data:
        tts_text: Security alert! {{ latest_text }}
    action: notify.mobile_app_sm_s711b
mode: single

Thank you for all the excellent feedback. I will play with this in the next few days and revert as to which worked.

Amazing to have all the geniuses on this platform.

Apologies for the long time to update, I have been travelling.

In essence, I am making good progress, but I am stuck again.

In summary, what I am trying to achieve is the following: When I prompt Assist, it should tell me that it is starting to analyze the cameras, then run the analysis and return the result (with a bit humor where relevant :wink: )

Currently, I am using an automation and a script.

Atomation code:

alias: Camera Summary Trigger Handler
description: Handles conversation triggers and initial response for camera analysis
triggers:
  - command:
      - Summarize what is happening on [all] the [security] cameras
      - Is there anything of interest on the [security] cameras
      - What can you see on the [security] [CCTV] cameras
      - Anything of interest on the [security] [CCTV] cameras
      - What is happening on the [security] [CCTV] cameras
      - "[Please] analyze the [CCTV] [security] cameras"
      - Do an analysis of the [CCTV] [security] cameras
    trigger: conversation
conditions: []
actions:
  - set_conversation_response: >-
      I am sending your footage to the omniscient Skynet so The Terminators can
      analyze it... Hold tight! Analysis starting now.
  - action: script.turn_on
    target:
      entity_id: script.all_camera_summary_v4
    data: {}
mode: single

This, in turn enages the script (which runs fine on it’s own), but does not return the data generated to the Assist conversation. Script code below:

alias: All Camera Summary v4
description: >-
  Give a description/summary of what is happening on the various CCTV/Security
  Cameras around the house
sequence:
  - parallel:
      - action: camera.snapshot
        metadata: {}
        data:
          filename: /config/www/tmp/cam1/cam1_latest.jpg
        target:
          entity_id: camera.camera_1_office_2
      - action: camera.snapshot
        metadata: {}
        data:
          filename: /config/www/tmp/cam2/cam2_latest.jpg
        target:
          entity_id: camera.camera_2_front_yard_2
      - action: camera.snapshot
        metadata: {}
        data:
          filename: /config/www/tmp/cam3/cam3_latest.jpg
        target:
          entity_id: camera.camera_3_entertainment_area_2
      - action: camera.snapshot
        metadata: {}
        data:
          filename: /config/www/tmp/cam5/cam5_latest.jpg
        target:
          entity_id: camera.camera_5_main_garden_2
      - action: camera.snapshot
        metadata: {}
        data:
          filename: /config/www/tmp/cam6/cam6_latest.jpg
        target:
          entity_id: camera.camera_6_driveway_2
      - action: camera.snapshot
        metadata: {}
        data:
          filename: /config/www/tmp/cam7/cam7_latest.jpg
        target:
          entity_id: camera.camera_7_back_garden_2
      - action: camera.snapshot
        metadata: {}
        data:
          filename: /config/www/tmp/cam9/cam9_latest.jpg
        target:
          entity_id: camera.camera_9_study2_2
      - action: camera.snapshot
        metadata: {}
        data:
          filename: /config/www/tmp/cam9/cam10_latest.jpg
        target:
          entity_id: camera.camera_10_carport_2
  - delay: "00:00:02"
  - variables:
      cam1_result:
        response_text: >-
          Camera 1 (Office): Analysis failed (quota or error). Skynet is low on
          power!
  - action: llmvision.image_analyzer
    metadata: {}
    data:
      remember: false
      use_memory: false
      include_filename: false
      target_width: 512
      max_tokens: 75
      generate_title: false
      expose_images: true
      provider: redacted # Place your own token here
      message: >-
        Provide ONLY ONE description of this security camera footage (Office) in
        25 words or less. Make it slightly witty UNLESS a security risk or
        person is detected, in which case be serious and report only the
        details. No date/time info or multiple options.
      image_file: /config/www/tmp/cam1/cam1_latest.jpg
      image_entity:
        - camera.camera_1_office_2
    response_variable: cam1_result
  - delay: "00:00:03"
  - variables:
      cam2_result:
        response_text: >-
          Camera 2 (Front Yard): Analysis failed (quota or error). Skynet is low
          on power!
  - action: llmvision.image_analyzer
    metadata: {}
    data:
      remember: false
      use_memory: false
      include_filename: false
      target_width: 512
      max_tokens: 75
      generate_title: false
      expose_images: true
      provider: redacted # Place your own token here
      message: >-
        Provide ONLY ONE description of this security camera footage (Front
        Yard) in 25 words or less. Make it slightly witty UNLESS a security risk
        or person is detected, in which case be serious and report only the
        details. No date/time info or multiple options.
      image_file: /config/www/tmp/cam2/cam2_latest.jpg
      image_entity:
        - camera.camera_2_front_yard_2
    response_variable: cam2_result
  - delay: "00:00:03"
  - variables:
      cam3_result:
        response_text: >-
          Camera 3 (Entertainment Area): Analysis failed (quota or error).
          Skynet is low on power!
  - action: llmvision.image_analyzer
    metadata: {}
    data:
      remember: false
      use_memory: false
      include_filename: false
      target_width: 512
      max_tokens: 75
      generate_title: false
      expose_images: true
      provider: redacted # Place your own token here
      message: >-
        Provide ONLY ONE description of this security camera footage
        (Entertainment Area) in 25 words or less. Make it slightly witty UNLESS
        a security risk or person is detected, in which case be serious and
        report only the details. No date/time info or multiple options.
      image_file: /config/www/tmp/cam3/cam3_latest.jpg
      image_entity:
        - camera.camera_3_entertainment_area_2
    response_variable: cam3_result
  - delay: "00:00:03"
  - variables:
      cam5_result:
        response_text: >-
          Camera 5 (Main Garden): Analysis failed (quota or error). Skynet is
          low on power!
  - action: llmvision.image_analyzer
    metadata: {}
    data:
      remember: false
      use_memory: false
      include_filename: false
      target_width: 512
      max_tokens: 75
      generate_title: false
      expose_images: true
      provider: redacted # Place your own token here
      message: >-
        Provide ONLY ONE description of this security camera footage (Main
        Garden) in 25 words or less. Make it slightly witty UNLESS a security
        risk or person is detected, in which case be serious and report only the
        details. No date/time info or multiple options.
      image_file: /config/www/tmp/cam5/cam5_latest.jpg
      image_entity:
        - camera.camera_5_main_garden_2
    response_variable: cam5_result
  - delay: "00:00:03"
  - set_conversation_response: Analysis halfway done... Continuing with remaining cameras.
  - variables:
      cam6_result:
        response_text: >-
          Camera 6 (Driveway): Analysis failed (quota or error). Skynet is low
          on power!
  - action: llmvision.image_analyzer
    metadata: {}
    data:
      remember: false
      use_memory: false
      include_filename: false
      target_width: 512
      max_tokens: 75
      generate_title: false
      expose_images: true
      provider: redacted # Place your own token here
      message: >-
        Provide ONLY ONE description of this security camera footage (Driveway)
        in 25 words or less. Make it slightly witty UNLESS a security risk or
        person is detected, in which case be serious and report only the
        details. No date/time info or multiple options.
      image_file: /config/www/tmp/cam6/cam6_latest.jpg
      image_entity:
        - camera.camera_6_driveway_2
    response_variable: cam6_result
  - delay: "00:00:03"
  - variables:
      cam7_result:
        response_text: >-
          Camera 7 (Back Garden): Analysis failed (quota or error). Skynet is
          low on power!
  - action: llmvision.image_analyzer
    metadata: {}
    data:
      remember: false
      use_memory: false
      include_filename: false
      target_width: 512
      max_tokens: 75
      generate_title: false
      expose_images: true
      provider: redacted # Place your own token here
      message: >-
        Provide ONLY ONE description of this security camera footage (Back
        Garden) in 25 words or less. Make it slightly witty UNLESS a security
        risk or person is detected, in which case be serious and report only the
        details. No date/time info or multiple options.
      image_file: /config/www/tmp/cam7/cam7_latest.jpg
      image_entity:
        - camera.camera_7_back_garden_2
    response_variable: cam7_result
  - delay: "00:00:03"
  - variables:
      cam9_result:
        response_text: >-
          Camera 9 (Study): Analysis failed (quota or error). Skynet is low on
          power!
  - action: llmvision.image_analyzer
    metadata: {}
    data:
      remember: false
      use_memory: false
      include_filename: false
      target_width: 512
      max_tokens: 75
      generate_title: false
      expose_images: true
      provider: redacted # Place your own token here
      message: >-
        Provide ONLY ONE description of this security camera footage (Study) in
        25 words or less. Make it slightly witty UNLESS a security risk or
        person is detected, in which case be serious and report only the
        details. No date/time info or multiple options.
      image_file: /config/www/tmp/cam9/cam9_latest.jpg
      image_entity:
        - camera.camera_9_study2_2
    response_variable: cam9_result
  - delay: "00:00:03"
  - variables:
      cam10_result:
        response_text: >-
          Camera 10 (Carport): Analysis failed (quota or error). Skynet is low
          on power!
  - action: llmvision.image_analyzer
    metadata: {}
    data:
      remember: false
      use_memory: false
      include_filename: false
      target_width: 512
      max_tokens: 75
      generate_title: false
      expose_images: true
      provider: redacted # Place your own token here
      message: >-
        Provide ONLY ONE description of this security camera footage (Carport)
        in 25 words or less. Make it slightly witty UNLESS a security risk or
        person is detected, in which case be serious and report only the
        details. No date/time info or multiple options.
      image_file: /config/www/tmp/cam9/cam10_latest.jpg
      image_entity:
        - camera.camera_10_carport_2
    response_variable: cam10_result
  - set_conversation_response: |-
      Here's the camera summary from Skynet:
      Camera 1: {{ cam1_result.response_text | default('No data') }} 
      Camera 2: {{ cam2_result.response_text | default('No data') }}
      Camera 3: {{ cam3_result.response_text | default('No data') }}
      Camera 5: {{ cam5_result.response_text | default('No data') }}
      Camera 6: {{ cam6_result.response_text | default('No data') }}
      Camera 7: {{ cam7_result.response_text | default('No data') }}
      Camera 9: {{ cam9_result.response_text | default('No data') }}
      Camera 10: {{ cam10_result.response_text | default('No data') }}
      All clear unless noted—stay vigilant! (If incomplete, check AI quota.)
mode: single

The only output I get in the conversation is as below…

Me: Do an analysis of the cameras

Response: I am sending your footage to the omniscient Skynet so The Terminators can analyze it… Hold tight! Analysis starting now.

In Traces I can see the response is generated. Any ideas please?