Make it possible to update genai without restarting

This commit is contained in:
Nicolas Mowen 2026-04-03 16:16:10 -06:00
parent 3a79f3952e
commit a613062c8f
5 changed files with 23 additions and 25 deletions

View File

@ -16,7 +16,7 @@ from frigate.config import CameraConfig, FrigateConfig
from frigate.const import CLIPS_DIR, UPDATE_EVENT_DESCRIPTION from frigate.const import CLIPS_DIR, UPDATE_EVENT_DESCRIPTION
from frigate.data_processing.post.semantic_trigger import SemanticTriggerProcessor from frigate.data_processing.post.semantic_trigger import SemanticTriggerProcessor
from frigate.data_processing.types import PostProcessDataEnum from frigate.data_processing.types import PostProcessDataEnum
from frigate.genai import GenAIClient from frigate.genai.manager import GenAIClientManager
from frigate.models import Event from frigate.models import Event
from frigate.types import TrackedObjectUpdateTypesEnum from frigate.types import TrackedObjectUpdateTypesEnum
from frigate.util.builtin import EventsPerSecond, InferenceSpeed from frigate.util.builtin import EventsPerSecond, InferenceSpeed
@ -41,7 +41,7 @@ class ObjectDescriptionProcessor(PostProcessorApi):
embeddings: "Embeddings", embeddings: "Embeddings",
requestor: InterProcessRequestor, requestor: InterProcessRequestor,
metrics: DataProcessorMetrics, metrics: DataProcessorMetrics,
client: GenAIClient, genai_manager: GenAIClientManager,
semantic_trigger_processor: SemanticTriggerProcessor | None, semantic_trigger_processor: SemanticTriggerProcessor | None,
): ):
super().__init__(config, metrics, None) super().__init__(config, metrics, None)
@ -49,7 +49,7 @@ class ObjectDescriptionProcessor(PostProcessorApi):
self.embeddings = embeddings self.embeddings = embeddings
self.requestor = requestor self.requestor = requestor
self.metrics = metrics self.metrics = metrics
self.genai_client = client self.genai_manager = genai_manager
self.semantic_trigger_processor = semantic_trigger_processor self.semantic_trigger_processor = semantic_trigger_processor
self.tracked_events: dict[str, list[Any]] = {} self.tracked_events: dict[str, list[Any]] = {}
self.early_request_sent: dict[str, bool] = {} self.early_request_sent: dict[str, bool] = {}
@ -198,6 +198,9 @@ class ObjectDescriptionProcessor(PostProcessorApi):
if data_type != PostProcessDataEnum.tracked_object: if data_type != PostProcessDataEnum.tracked_object:
return return
if self.genai_manager.description_client is None:
return
state: str | None = frame_data.get("state", None) state: str | None = frame_data.get("state", None)
if state is not None: if state is not None:
@ -329,7 +332,7 @@ class ObjectDescriptionProcessor(PostProcessorApi):
"""Embed the description for an event.""" """Embed the description for an event."""
start = datetime.datetime.now().timestamp() start = datetime.datetime.now().timestamp()
camera_config = self.config.cameras[str(event.camera)] camera_config = self.config.cameras[str(event.camera)]
description = self.genai_client.generate_object_description( description = self.genai_manager.description_client.generate_object_description(
camera_config, thumbnails, event camera_config, thumbnails, event
) )

View File

@ -22,6 +22,7 @@ from frigate.config.camera.review import GenAIReviewConfig, ImageSourceEnum
from frigate.const import CACHE_DIR, CLIPS_DIR, UPDATE_REVIEW_DESCRIPTION from frigate.const import CACHE_DIR, CLIPS_DIR, UPDATE_REVIEW_DESCRIPTION
from frigate.data_processing.types import PostProcessDataEnum from frigate.data_processing.types import PostProcessDataEnum
from frigate.genai import GenAIClient from frigate.genai import GenAIClient
from frigate.genai.manager import GenAIClientManager
from frigate.models import Recordings, ReviewSegment from frigate.models import Recordings, ReviewSegment
from frigate.util.builtin import EventsPerSecond, InferenceSpeed from frigate.util.builtin import EventsPerSecond, InferenceSpeed
from frigate.util.image import get_image_from_recording from frigate.util.image import get_image_from_recording
@ -41,12 +42,12 @@ class ReviewDescriptionProcessor(PostProcessorApi):
config: FrigateConfig, config: FrigateConfig,
requestor: InterProcessRequestor, requestor: InterProcessRequestor,
metrics: DataProcessorMetrics, metrics: DataProcessorMetrics,
client: GenAIClient, genai_manager: GenAIClientManager,
): ):
super().__init__(config, metrics, None) super().__init__(config, metrics, None)
self.requestor = requestor self.requestor = requestor
self.metrics = metrics self.metrics = metrics
self.genai_client = client self.genai_manager = genai_manager
self.review_desc_speed = InferenceSpeed(self.metrics.review_desc_speed) self.review_desc_speed = InferenceSpeed(self.metrics.review_desc_speed)
self.review_desc_dps = EventsPerSecond() self.review_desc_dps = EventsPerSecond()
self.review_desc_dps.start() self.review_desc_dps.start()
@ -63,7 +64,7 @@ class ReviewDescriptionProcessor(PostProcessorApi):
Estimates ~1 token per 1250 pixels. Targets 98% context utilization with safety margin. Estimates ~1 token per 1250 pixels. Targets 98% context utilization with safety margin.
Capped at 20 frames. Capped at 20 frames.
""" """
context_size = self.genai_client.get_context_size() context_size = self.genai_manager.description_client.get_context_size()
camera_config = self.config.cameras[camera] camera_config = self.config.cameras[camera]
detect_width = camera_config.detect.width detect_width = camera_config.detect.width
@ -111,6 +112,9 @@ class ReviewDescriptionProcessor(PostProcessorApi):
if data_type != PostProcessDataEnum.review: if data_type != PostProcessDataEnum.review:
return return
if self.genai_manager.description_client is None:
return
camera = data["after"]["camera"] camera = data["after"]["camera"]
camera_config = self.config.cameras[camera] camera_config = self.config.cameras[camera]
@ -200,7 +204,7 @@ class ReviewDescriptionProcessor(PostProcessorApi):
target=run_analysis, target=run_analysis,
args=( args=(
self.requestor, self.requestor,
self.genai_client, self.genai_manager.description_client,
self.review_desc_speed, self.review_desc_speed,
camera_config, camera_config,
final_data, final_data,
@ -316,7 +320,7 @@ class ReviewDescriptionProcessor(PostProcessorApi):
os.path.join(CLIPS_DIR, "genai-requests", f"{start_ts}-{end_ts}") os.path.join(CLIPS_DIR, "genai-requests", f"{start_ts}-{end_ts}")
).mkdir(parents=True, exist_ok=True) ).mkdir(parents=True, exist_ok=True)
return self.genai_client.generate_review_summary( return self.genai_manager.description_client.generate_review_summary(
start_ts, start_ts,
end_ts, end_ts,
events_with_context, events_with_context,

View File

@ -202,7 +202,7 @@ class EmbeddingMaintainer(threading.Thread):
# post processors # post processors
self.post_processors: list[PostProcessorApi] = [] self.post_processors: list[PostProcessorApi] = []
if self.genai_manager.description_client is not None and any( if any(
c.review.genai.enabled_in_config for c in self.config.cameras.values() c.review.genai.enabled_in_config for c in self.config.cameras.values()
): ):
self.post_processors.append( self.post_processors.append(
@ -210,7 +210,7 @@ class EmbeddingMaintainer(threading.Thread):
self.config, self.config,
self.requestor, self.requestor,
self.metrics, self.metrics,
self.genai_manager.description_client, self.genai_manager,
) )
) )
@ -248,7 +248,7 @@ class EmbeddingMaintainer(threading.Thread):
) )
self.post_processors.append(semantic_trigger_processor) self.post_processors.append(semantic_trigger_processor)
if self.genai_manager.description_client is not None and any( if any(
c.objects.genai.enabled_in_config for c in self.config.cameras.values() c.objects.genai.enabled_in_config for c in self.config.cameras.values()
): ):
self.post_processors.append( self.post_processors.append(
@ -257,7 +257,7 @@ class EmbeddingMaintainer(threading.Thread):
self.embeddings, self.embeddings,
self.requestor, self.requestor,
self.metrics, self.metrics,
self.genai_manager.description_client, self.genai_manager,
semantic_trigger_processor, semantic_trigger_processor,
) )
) )

View File

@ -3,14 +3,6 @@ import type { SectionConfigOverrides } from "./types";
const genai: SectionConfigOverrides = { const genai: SectionConfigOverrides = {
base: { base: {
sectionDocs: "/configuration/genai/config", sectionDocs: "/configuration/genai/config",
restartRequired: [
"*.provider",
"*.api_key",
"*.base_url",
"*.model",
"*.provider_options",
"*.runtime_options",
],
advancedFields: ["*.base_url", "*.provider_options", "*.runtime_options"], advancedFields: ["*.base_url", "*.provider_options", "*.runtime_options"],
hiddenFields: ["genai.enabled_in_config"], hiddenFields: ["genai.enabled_in_config"],
uiSchema: { uiSchema: {

View File

@ -44,10 +44,9 @@ export function GenAIModelWidget(props: WidgetProps) {
const fieldClassName = getSizedFieldClassName(options, "sm"); const fieldClassName = getSizedFieldClassName(options, "sm");
const providerKey = useMemo(() => getProviderKey(id), [id]); const providerKey = useMemo(() => getProviderKey(id), [id]);
const { data: allModels } = useSWR<Record<string, string[]>>( const { data: allModels } = useSWR<Record<string, string[]>>("genai/models", {
"genai/models", revalidateOnFocus: false,
{ revalidateOnFocus: false }, });
);
const models = useMemo(() => { const models = useMemo(() => {
if (!allModels || !providerKey) return []; if (!allModels || !providerKey) return [];