blueprint: name: AI Event Summary (v1.4.1) author: valentinfrlch description: 'AI-powered summaries for security camera events. Sends a notification with a preview to your phone that is updated dynamically when the AI summary is available. ' domain: automation source_url: https://raw.githubusercontent.com/valentinfrlch/ha-llmvision/refs/heads/main/blueprints/event_summary.yaml input: important: name: Important (Experimental) description: 'Use AI to classify events as Critical, Normal or Low. Notifications are sent only for events classified as Normal or higher. Critical events override ''Do Not Disturb'' settings. Use with caution: AI can make mistakes. ' default: false selector: boolean: {} remember: name: Remember description: Stores this event in the Timeline so you can ask about it. If important is enabled, only events classified as Normal or Critical will be saved. default: false selector: boolean: {} use_memory: name: Use Memory description: Use information stored in memory to provide additional context. Memory must be set up. default: false selector: boolean: {} message: name: Prompt description: Model prompt for the video_analyzer action default: 'Summarize the events based on a series of images captured at short intervals. Focus only on moving subjects such as people, vehicles, and other active elements. Ignore static objects and scenery. Provide a clear and concise account of movements and interactions. Do not mention or imply the existence of images—present the information as if directly observing the events. If no movement is detected, respond with: ''No activity observed.''' selector: text: multiline: true multiple: false run_conditions: name: Run Conditions description: All conditions must be true in order for the blueprint to run. default: [] selector: condition: {} notify_device: name: Notify Device description: The devices to send the notification to. Multiple devices may be used. Only works with Home Assistant mobile app. default: [] selector: device: multiple: true filter: - integration: mobile_app notification_delivery: name: Notification Delivery description: "Controls how notifications are delivered. \n \n **Dynamic** immediately notifies with a live preview and updates the notification silently with a summary once it is available. \n **Consolidated** Delays the notification until the event summary is generated. Use this if you're receiving multiple notifications for the same event." default: Dynamic selector: select: options: - Dynamic - Consolidated custom_value: false multiple: false sort: false camera_entities: name: Camera Entities description: List of camera entities to monitor default: [] selector: entity: multiple: true filter: - domain: - camera trigger_state: name: Trigger State description: Automation starts when one of your camera changes to this state. default: recording selector: text: multiline: false multiple: false motion_sensors: name: Motion Sensor description: Set if your cameras don't change state (Frigate). Use the same order used for camera entities. default: [] selector: entity: multiple: true filter: - domain: - binary_sensor preview_mode: name: Preview Mode description: "Choose between a live preview or a snapshot of the event. \n\n **Important:** Live Preview is only supported on iOS." default: Snapshot selector: select: options: - Snapshot - Live Preview custom_value: false multiple: false sort: false cooldown: name: Cooldown description: Time in minutes to wait before running again. Strongly recommended for busy areas. default: 10 selector: number: min: 0.0 max: 60.0 mode: slider step: 1.0 tap_navigate: name: Tap Navigate description: Home Assistant dashboard to navigate to when notification is opened (e.g. /lovelace/cameras). default: /lovelace/0 selector: text: multiline: false multiple: false duration: name: Duration description: Duration to record for analysis (in seconds). default: 5 selector: number: min: 1.0 max: 60.0 mode: slider step: 1.0 max_frames: name: Max Frames description: How many frames to analyze. Picks frames with the most movement. default: 3 selector: number: min: 1.0 max: 60.0 mode: slider step: 1.0 provider: name: Provider description: Provider to use for analysis. See docs for additional information. selector: config_entry: integration: llmvision model: name: Model description: Which model to use. Depends on chosen provider. default: gpt-4o-mini selector: text: multiline: false multiple: false target_width: name: Target Width description: Downscale images (uses less tokens and speeds up processing) default: 1280 selector: number: min: 512.0 max: 3840.0 mode: slider step: 1.0 max_tokens: name: Maximum Tokens description: Maximum number of tokens to generate. Use this to control the length of the summaries. default: 20 selector: number: min: 1.0 max: 100.0 mode: slider step: 1.0 temperature: name: Temperature description: Randomness. Lower is more accurate, higher is more creative. default: 0.1 selector: number: min: 0.1 max: 1.0 step: 0.1 mode: slider variables: important: !input important remember: !input remember cooldown: !input cooldown preview_mode: !input preview_mode notify_devices: !input notify_device notification_delivery: !input notification_delivery device_name_map: "{% set ns = namespace(device_names=[]) %} {% for device_id in notify_devices %}\n {% set device_name = device_attr(device_id, \"name\") %}\n \ {% set sanitized_name = \"mobile_app_\" + device_name | slugify %}\n {% set ns.device_names = ns.device_names + [sanitized_name] %}\n{% endfor %} {{ ns.device_names }}\n" camera_entities_list: !input camera_entities motion_sensors_list: !input motion_sensors camera_entity: "{% if motion_sensors_list and not trigger.entity_id.startswith(\"camera\") %}\n {% set index = motion_sensors_list.index(trigger.entity_id) %}\n {{ camera_entities_list[index] }}\n{% else %}\n {{ trigger.entity_id }}\n{% endif %}\n" tag: '{{ camera_entity + int(as_timestamp(now()))|string }} ' group: '{{ camera_entity }} ' label: Motion detected camera: '{{ camera_entity.replace("camera.", "").replace("_", " ")|capitalize }} ' importance_prompt: 'Classify the security event based on this image. Choose from the following options: "passive" for unimportant events, "time-sensitive" for notable but non-critical events such as a person at the front door, and "critical" only for potential burglaries or highly suspicious activity. Respond with one of these options exactly, without additional explanation. ' max_exceeded: silent mode: single triggers: - trigger: state entity_id: !input camera_entities to: !input trigger_state id: camera_trigger - trigger: state entity_id: !input motion_sensors to: 'on' id: motion_sensor_trigger condition: - condition: and conditions: !input run_conditions action: - choose: - conditions: - condition: template value_template: '{{ important }}' sequence: - action: llmvision.image_analyzer data: image_entity: '{{[camera_entity]}}' provider: !input provider model: !input model message: '{{importance_prompt}}' include_filename: true target_width: 1280 max_tokens: 3 temperature: 0.1 response_variable: importance - choose: - conditions: - condition: template value_template: '{{ important and importance.response_text|lower == ''passive'' }}' sequence: - stop: Event is not important - choose: - conditions: - condition: template value_template: '{{ notification_delivery == ''Dynamic'' }}' sequence: - alias: Send instant notification to notify devices repeat: for_each: '{{device_name_map}}' sequence: - action: notify.{{ repeat.item }} data: title: '{{ label }}' message: '{{camera}} has detected activity.' data: entity_id: '{{camera_entity}}' url: !input tap_navigate clickAction: !input tap_navigate tag: '{{tag}}' group: '{{group}}' alert_once: true push: interruption-level: '{{importance.response_text|lower if importance is defined else ''active''}}' - alias: Analyze event action: llmvision.stream_analyzer data: image_entity: '{{[camera_entity]}}' duration: !input duration provider: !input provider model: !input model message: !input message use_memory: !input use_memory remember: !input remember expose_images: '{{preview_mode == ''Snapshot'' or remember}}' generate_title: !input remember include_filename: true max_frames: !input max_frames target_width: !input target_width max_tokens: !input max_tokens temperature: !input temperature response_variable: response - alias: Update label with title variables: label: '{{response.title}}' - choose: - conditions: - condition: template value_template: '{{ preview_mode==''Snapshot'' }}' sequence: - alias: (Snapshot) Update notification on notify devices repeat: for_each: '{{device_name_map}}' sequence: - action: notify.{{ repeat.item }} data: title: '{{ label }}' message: '{{response.response_text}}' data: image: '{{response.key_frame.replace(''/config/www/'',''/local/'') }}' url: !input tap_navigate clickAction: !input tap_navigate tag: '{{tag}}' group: '{{group}}' push: interruption-level: '{{''passive'' if notification_delivery==''Dynamic'' else ''active''}}' - conditions: - condition: template value_template: '{{ preview_mode==''Live Preview'' }}' sequence: - alias: (Live Preview) Update notification on notify devices repeat: for_each: '{{device_name_map}}' sequence: - action: notify.{{ repeat.item }} data: title: '{{ label }}' message: '{{response.response_text}}' data: entity_id: '{{camera_entity}}' url: !input tap_navigate clickAction: !input tap_navigate tag: '{{tag}}' group: '{{group}}' push: interruption-level: '{{''passive'' if notification_delivery==''Dynamic'' else ''active''}}' - delay: 00:{{cooldown|int}}:00