2024-06-22 00:30:19 +03:00
""" Generative AI module for Frigate. """
2025-08-13 01:27:35 +03:00
import datetime
2024-06-22 00:30:19 +03:00
import importlib
2024-11-16 00:24:17 +03:00
import logging
2024-06-22 00:30:19 +03:00
import os
2025-08-10 14:57:54 +03:00
import re
from typing import Any , Optional
2024-06-22 00:30:19 +03:00
2024-10-14 15:23:10 +03:00
from playhouse . shortcuts import model_to_dict
2024-11-09 16:48:53 +03:00
from frigate . config import CameraConfig , FrigateConfig , GenAIConfig , GenAIProviderEnum
2025-08-13 01:27:35 +03:00
from frigate . const import CLIPS_DIR
2025-08-10 14:57:54 +03:00
from frigate . data_processing . post . types import ReviewMetadata
2024-10-12 15:19:24 +03:00
from frigate . models import Event
2024-06-22 00:30:19 +03:00
2024-11-16 00:24:17 +03:00
logger = logging . getLogger ( __name__ )
2024-06-22 00:30:19 +03:00
PROVIDERS = { }
def register_genai_provider ( key : GenAIProviderEnum ) :
""" Register a GenAI provider. """
def decorator ( cls ) :
PROVIDERS [ key ] = cls
return cls
return decorator
class GenAIClient :
""" Generative AI client for Frigate. """
def __init__ ( self , genai_config : GenAIConfig , timeout : int = 60 ) - > None :
self . genai_config : GenAIConfig = genai_config
self . timeout = timeout
self . provider = self . _init_provider ( )
2025-08-10 14:57:54 +03:00
def generate_review_description (
2025-08-11 22:17:25 +03:00
self ,
review_data : dict [ str , Any ] ,
thumbnails : list [ bytes ] ,
concerns : list [ str ] ,
preferred_language : str | None ,
2025-08-13 01:27:35 +03:00
debug_save : bool ,
2025-08-10 14:57:54 +03:00
) - > ReviewMetadata | None :
""" Generate a description for the review item activity. """
2025-08-11 22:17:25 +03:00
2025-08-13 18:28:01 +03:00
def get_concern_prompt ( ) - > str :
if concerns :
concern_list = " \n - " . join ( concerns )
return f """
- ` other_concerns ` ( list of strings ) : Include a list of any of the following concerns that are occurring :
- { concern_list } """
else :
return " "
2025-08-11 22:17:25 +03:00
2025-08-13 18:28:01 +03:00
def get_language_prompt ( ) - > str :
if preferred_language :
return f " Provide your answer in { preferred_language } "
else :
return " "
2025-08-11 22:17:25 +03:00
2025-08-10 14:57:54 +03:00
context_prompt = f """
2025-08-13 01:27:35 +03:00
Please analyze the sequence of images ( { len ( thumbnails ) } total ) taken in chronological order from the perspective of the { review_data [ " camera " ] . replace ( " _ " , " " ) } security camera .
2025-08-11 22:17:25 +03:00
2025-08-13 01:27:35 +03:00
Your task is to provide a clear , security - focused description of the scene that :
1. States exactly what is happening based on observable actions and movements .
2. Identifies and emphasizes behaviors that match patterns of suspicious activity .
3. Assigns a potential_threat_level based on the definitions below , applying them consistently .
2025-08-11 22:17:25 +03:00
2025-08-13 01:27:35 +03:00
Facts come first , but identifying security risks is the primary goal .
2025-08-11 22:17:25 +03:00
2025-08-13 01:27:35 +03:00
When forming your description :
2025-09-26 05:05:22 +03:00
- Describe the people and objects exactly as seen . Include any observable environmental changes ( e . g . , lighting changes triggered by activity ) .
2025-08-13 01:27:35 +03:00
- Time of day should * * increase suspicion only when paired with unusual or security - relevant behaviors * * . Do not raise the threat level for common residential activities ( e . g . , residents walking pets , retrieving mail , gardening , playing with pets , supervising children ) even at unusual hours , unless other suspicious indicators are present .
- Focus on behaviors that are uncharacteristic of innocent activity : loitering without clear purpose , avoiding cameras , inspecting vehicles / doors , changing behavior when lights activate , scanning surroundings without an apparent benign reason .
- * * Benign context override * * : If scanning or looking around is clearly part of an innocent activity ( such as playing with a dog , gardening , supervising children , or watching for a pet ) , do not treat it as suspicious .
2025-08-11 22:17:25 +03:00
2025-08-13 01:27:35 +03:00
Your response MUST be a flat JSON object with :
2025-08-11 22:17:25 +03:00
- ` scene ` ( string ) : A full description including setting , entities , actions , and any plausible supported inferences .
2025-08-13 01:27:35 +03:00
- ` confidence ` ( float ) : 0 - 1 confidence in the analysis .
- ` potential_threat_level ` ( integer ) : 0 , 1 , or 2 as defined below .
2025-08-13 18:28:01 +03:00
{ get_concern_prompt ( ) }
2025-08-11 22:17:25 +03:00
2025-08-13 01:27:35 +03:00
Threat - level definitions :
- 0 — Typical or expected activity for this location / time ( includes residents , guests , or known animals engaged in normal activities , even if they glance around or scan surroundings ) .
- 1 — Unusual or suspicious activity : At least one security - relevant behavior is present * * and not explainable by a normal residential activity * * .
- 2 — Active or immediate threat : Breaking in , vandalism , aggression , weapon display .
Sequence details :
2025-08-15 16:25:49 +03:00
- Frame 1 = earliest , Frame { len ( thumbnails ) } = latest
- Activity started at { review_data [ " start " ] } and lasted { review_data [ " duration " ] } seconds
- Detected objects : { " , " . join ( review_data [ " objects " ] ) }
- Verified recognized objects : { " , " . join ( review_data [ " recognized_objects " ] ) or " None " }
- Zones involved : { " , " . join ( z . replace ( " _ " , " " ) . title ( ) for z in review_data [ " zones " ] ) or " None " }
2025-08-13 01:27:35 +03:00
2025-08-11 22:17:25 +03:00
* * IMPORTANT : * *
- Values must be plain strings , floats , or integers — no nested objects , no extra commentary .
2025-08-13 18:28:01 +03:00
{ get_language_prompt ( ) }
2025-09-26 05:05:22 +03:00
"""
2025-08-10 19:24:08 +03:00
logger . debug (
f " Sending { len ( thumbnails ) } images to create review description on { review_data [ ' camera ' ] } "
)
2025-08-13 01:27:35 +03:00
if debug_save :
with open (
os . path . join (
CLIPS_DIR , " genai-requests " , review_data [ " id " ] , " prompt.txt "
) ,
" w " ,
) as f :
f . write ( context_prompt )
2025-08-10 14:57:54 +03:00
response = self . _send ( context_prompt , thumbnails )
2025-08-19 15:49:55 +03:00
if debug_save and response :
2025-08-15 16:25:49 +03:00
with open (
os . path . join (
CLIPS_DIR , " genai-requests " , review_data [ " id " ] , " response.txt "
) ,
" w " ,
) as f :
f . write ( response )
2025-08-10 14:57:54 +03:00
if response :
clean_json = re . sub (
r " \ n?```$ " , " " , re . sub ( r " ^```[a-zA-Z0-9]* \ n? " , " " , response )
)
try :
2025-08-15 16:25:49 +03:00
metadata = ReviewMetadata . model_validate_json ( clean_json )
if review_data [ " recognized_objects " ] :
metadata . potential_threat_level = 0
2025-09-26 05:05:22 +03:00
metadata . time = review_data [ " start " ]
2025-08-15 16:25:49 +03:00
return metadata
2025-08-10 14:57:54 +03:00
except Exception as e :
# rarely LLMs can fail to follow directions on output format
logger . warning (
f " Failed to parse review description as the response did not match expected format. { e } "
)
return None
else :
return None
2025-08-13 01:27:35 +03:00
def generate_review_summary (
2025-09-26 05:05:22 +03:00
self ,
start_ts : float ,
end_ts : float ,
segments : list [ dict [ str , Any ] ] ,
debug_save : bool ,
2025-08-13 01:27:35 +03:00
) - > str | None :
""" Generate a summary of review item descriptions over a period of time. """
2025-09-26 05:05:22 +03:00
time_range = f " { datetime . datetime . fromtimestamp ( start_ts ) . strftime ( ' % B %d , % Y at % I: % M % p ' ) } to { datetime . datetime . fromtimestamp ( end_ts ) . strftime ( ' % B %d , % Y at % I: % M % p ' ) } "
2025-08-13 01:27:35 +03:00
timeline_summary_prompt = f """
2025-09-26 05:05:22 +03:00
You are a security officer .
Time range : { time_range } .
2025-08-13 01:27:35 +03:00
Input : JSON list with " scene " , " confidence " , " potential_threat_level " ( 1 - 2 ) , " other_concerns " .
2025-09-26 05:05:22 +03:00
Task : Write a concise , human - presentable security report in markdown format .
Rules for the report :
- Title & overview
- Start with :
# Security Summary - {time_range}
- Write a 1 - 2 sentence situational overview capturing the general pattern of the period .
- Event details
- Present events in chronological order as a bullet list .
- * * If multiple events occur within the same minute or overlapping time range , COMBINE them into a single bullet . * *
- Summarize the distinct activities as sub - points under the shared timestamp .
- If no timestamp is given , preserve order but label as “ Time not specified . ”
- Use bold timestamps for clarity .
- Group bullets under subheadings when multiple events fall into the same category ( e . g . , Vehicle Activity , Porch Activity , Unusual Behavior ) .
- Threat levels
- Always show ( threat level : X ) for each event .
- If multiple events at the same time share the same threat level , only state it once .
- Final assessment
- End with a Final Assessment section .
- If all events are threat level 1 with no escalation :
Final assessment : Only normal residential activity observed during this period .
- If threat level 2 + events are present , clearly summarize them as Potential concerns requiring review .
- Conciseness
- Do not repeat benign clothing / appearance details unless they distinguish individuals .
- Summarize similar routine events instead of restating full scene descriptions .
"""
2025-08-13 01:27:35 +03:00
for item in segments :
timeline_summary_prompt + = f " \n { item } "
2025-09-26 05:05:22 +03:00
if debug_save :
with open (
os . path . join (
CLIPS_DIR , " genai-requests " , f " { start_ts } - { end_ts } " , " prompt.txt "
) ,
" w " ,
) as f :
f . write ( timeline_summary_prompt )
response = self . _send ( timeline_summary_prompt , [ ] )
if debug_save and response :
with open (
os . path . join (
CLIPS_DIR , " genai-requests " , f " { start_ts } - { end_ts } " , " response.txt "
) ,
" w " ,
) as f :
f . write ( response )
return response
2025-08-13 01:27:35 +03:00
2025-08-10 14:57:54 +03:00
def generate_object_description (
2024-09-16 17:46:11 +03:00
self ,
camera_config : CameraConfig ,
thumbnails : list [ bytes ] ,
2024-10-12 15:19:24 +03:00
event : Event ,
2024-06-22 00:30:19 +03:00
) - > Optional [ str ] :
""" Generate a description for the frame. """
2025-08-20 16:03:50 +03:00
try :
prompt = camera_config . genai . object_prompts . get (
event . label ,
camera_config . genai . prompt ,
) . format ( * * model_to_dict ( event ) )
except KeyError as e :
logger . error ( f " Invalid key in GenAI prompt: { e } " )
return None
2024-11-16 00:24:17 +03:00
logger . debug ( f " Sending images to genai provider with prompt: { prompt } " )
2024-06-22 00:30:19 +03:00
return self . _send ( prompt , thumbnails )
def _init_provider ( self ) :
""" Initialize the client. """
return None
def _send ( self , prompt : str , images : list [ bytes ] ) - > Optional [ str ] :
""" Submit a request to the provider. """
return None
2024-11-09 16:48:53 +03:00
def get_genai_client ( config : FrigateConfig ) - > Optional [ GenAIClient ] :
2024-06-22 00:30:19 +03:00
""" Get the GenAI client. """
2025-08-11 22:17:25 +03:00
if not config . genai . provider :
return None
2025-08-09 01:33:11 +03:00
load_providers ( )
provider = PROVIDERS . get ( config . genai . provider )
if provider :
return provider ( config . genai )
2024-11-09 16:48:53 +03:00
2024-06-22 00:30:19 +03:00
return None
def load_providers ( ) :
package_dir = os . path . dirname ( __file__ )
for filename in os . listdir ( package_dir ) :
if filename . endswith ( " .py " ) and filename != " __init__.py " :
module_name = f " frigate.genai. { filename [ : - 3 ] } "
importlib . import_module ( module_name )