mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-19 22:58:22 +03:00
Support getting client via manager
This commit is contained in:
parent
7c066f661a
commit
1e4596eb99
@ -38,6 +38,7 @@ from frigate.config.camera.updater import (
|
|||||||
CameraConfigUpdateTopic,
|
CameraConfigUpdateTopic,
|
||||||
)
|
)
|
||||||
from frigate.ffmpeg_presets import FFMPEG_HWACCEL_VAAPI, _gpu_selector
|
from frigate.ffmpeg_presets import FFMPEG_HWACCEL_VAAPI, _gpu_selector
|
||||||
|
from frigate.genai import GenAIClientManager
|
||||||
from frigate.jobs.media_sync import (
|
from frigate.jobs.media_sync import (
|
||||||
get_current_media_sync_job,
|
get_current_media_sync_job,
|
||||||
get_media_sync_job_by_id,
|
get_media_sync_job_by_id,
|
||||||
@ -432,6 +433,7 @@ def config_set(request: Request, body: AppConfigSetBody):
|
|||||||
if body.requires_restart == 0 or body.update_topic:
|
if body.requires_restart == 0 or body.update_topic:
|
||||||
old_config: FrigateConfig = request.app.frigate_config
|
old_config: FrigateConfig = request.app.frigate_config
|
||||||
request.app.frigate_config = config
|
request.app.frigate_config = config
|
||||||
|
request.app.genai_manager = GenAIClientManager(config)
|
||||||
|
|
||||||
if body.update_topic:
|
if body.update_topic:
|
||||||
if body.update_topic.startswith("config/cameras/"):
|
if body.update_topic.startswith("config/cameras/"):
|
||||||
|
|||||||
@ -23,7 +23,6 @@ from frigate.api.defs.response.chat_response import (
|
|||||||
)
|
)
|
||||||
from frigate.api.defs.tags import Tags
|
from frigate.api.defs.tags import Tags
|
||||||
from frigate.api.event import events
|
from frigate.api.event import events
|
||||||
from frigate.genai import get_genai_client
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -383,7 +382,7 @@ async def chat_completion(
|
|||||||
6. Repeats until final answer
|
6. Repeats until final answer
|
||||||
7. Returns response to user
|
7. Returns response to user
|
||||||
"""
|
"""
|
||||||
genai_client = get_genai_client(request.app.frigate_config)
|
genai_client = request.app.genai_manager.tool_client
|
||||||
if not genai_client:
|
if not genai_client:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content={
|
content={
|
||||||
|
|||||||
@ -33,6 +33,7 @@ from frigate.comms.event_metadata_updater import (
|
|||||||
from frigate.config import FrigateConfig
|
from frigate.config import FrigateConfig
|
||||||
from frigate.config.camera.updater import CameraConfigUpdatePublisher
|
from frigate.config.camera.updater import CameraConfigUpdatePublisher
|
||||||
from frigate.embeddings import EmbeddingsContext
|
from frigate.embeddings import EmbeddingsContext
|
||||||
|
from frigate.genai import GenAIClientManager
|
||||||
from frigate.ptz.onvif import OnvifController
|
from frigate.ptz.onvif import OnvifController
|
||||||
from frigate.stats.emitter import StatsEmitter
|
from frigate.stats.emitter import StatsEmitter
|
||||||
from frigate.storage import StorageMaintainer
|
from frigate.storage import StorageMaintainer
|
||||||
@ -134,6 +135,7 @@ def create_fastapi_app(
|
|||||||
app.include_router(record.router)
|
app.include_router(record.router)
|
||||||
# App Properties
|
# App Properties
|
||||||
app.frigate_config = frigate_config
|
app.frigate_config = frigate_config
|
||||||
|
app.genai_manager = GenAIClientManager(frigate_config)
|
||||||
app.embeddings = embeddings
|
app.embeddings = embeddings
|
||||||
app.detected_frames_processor = detected_frames_processor
|
app.detected_frames_processor = detected_frames_processor
|
||||||
app.storage_maintainer = storage_maintainer
|
app.storage_maintainer = storage_maintainer
|
||||||
|
|||||||
@ -33,7 +33,6 @@ from frigate.api.defs.response.review_response import (
|
|||||||
ReviewSummaryResponse,
|
ReviewSummaryResponse,
|
||||||
)
|
)
|
||||||
from frigate.api.defs.tags import Tags
|
from frigate.api.defs.tags import Tags
|
||||||
from frigate.config import FrigateConfig
|
|
||||||
from frigate.embeddings import EmbeddingsContext
|
from frigate.embeddings import EmbeddingsContext
|
||||||
from frigate.models import Recordings, ReviewSegment, UserReviewStatus
|
from frigate.models import Recordings, ReviewSegment, UserReviewStatus
|
||||||
from frigate.review.types import SeverityEnum
|
from frigate.review.types import SeverityEnum
|
||||||
@ -747,9 +746,7 @@ async def set_not_reviewed(
|
|||||||
description="Use GenAI to summarize review items over a period of time.",
|
description="Use GenAI to summarize review items over a period of time.",
|
||||||
)
|
)
|
||||||
def generate_review_summary(request: Request, start_ts: float, end_ts: float):
|
def generate_review_summary(request: Request, start_ts: float, end_ts: float):
|
||||||
config: FrigateConfig = request.app.frigate_config
|
if not request.app.genai_manager.vision_client:
|
||||||
|
|
||||||
if not config.genai.provider:
|
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content=(
|
content=(
|
||||||
{
|
{
|
||||||
|
|||||||
@ -45,7 +45,7 @@ from .camera.audio import AudioConfig
|
|||||||
from .camera.birdseye import BirdseyeConfig
|
from .camera.birdseye import BirdseyeConfig
|
||||||
from .camera.detect import DetectConfig
|
from .camera.detect import DetectConfig
|
||||||
from .camera.ffmpeg import FfmpegConfig
|
from .camera.ffmpeg import FfmpegConfig
|
||||||
from .camera.genai import GenAIConfig
|
from .camera.genai import GenAIConfig, GenAIRoleEnum
|
||||||
from .camera.motion import MotionConfig
|
from .camera.motion import MotionConfig
|
||||||
from .camera.notification import NotificationConfig
|
from .camera.notification import NotificationConfig
|
||||||
from .camera.objects import FilterConfig, ObjectConfig
|
from .camera.objects import FilterConfig, ObjectConfig
|
||||||
@ -347,9 +347,9 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
default_factory=ModelConfig, title="Detection model configuration."
|
default_factory=ModelConfig, title="Detection model configuration."
|
||||||
)
|
)
|
||||||
|
|
||||||
# GenAI config
|
# GenAI config (named provider configs: name -> GenAIConfig)
|
||||||
genai: GenAIConfig = Field(
|
genai: Dict[str, GenAIConfig] = Field(
|
||||||
default_factory=GenAIConfig, title="Generative AI configuration."
|
default_factory=dict, title="Generative AI configuration (named providers)."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Camera config
|
# Camera config
|
||||||
@ -431,6 +431,18 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
# set notifications state
|
# set notifications state
|
||||||
self.notifications.enabled_in_config = self.notifications.enabled
|
self.notifications.enabled_in_config = self.notifications.enabled
|
||||||
|
|
||||||
|
# validate genai: each role (tools, vision, embeddings) at most once
|
||||||
|
role_to_name: dict[GenAIRoleEnum, str] = {}
|
||||||
|
for name, genai_cfg in self.genai.items():
|
||||||
|
for role in genai_cfg.roles:
|
||||||
|
if role in role_to_name:
|
||||||
|
raise ValueError(
|
||||||
|
f"GenAI role '{role.value}' is assigned to both "
|
||||||
|
f"'{role_to_name[role]}' and '{name}'; each role must have "
|
||||||
|
"exactly one provider."
|
||||||
|
)
|
||||||
|
role_to_name[role] = name
|
||||||
|
|
||||||
# set default min_score for object attributes
|
# set default min_score for object attributes
|
||||||
for attribute in self.model.all_attributes:
|
for attribute in self.model.all_attributes:
|
||||||
if not self.objects.filters.get(attribute):
|
if not self.objects.filters.get(attribute):
|
||||||
|
|||||||
@ -59,7 +59,7 @@ from frigate.data_processing.real_time.license_plate import (
|
|||||||
from frigate.data_processing.types import DataProcessorMetrics, PostProcessDataEnum
|
from frigate.data_processing.types import DataProcessorMetrics, PostProcessDataEnum
|
||||||
from frigate.db.sqlitevecq import SqliteVecQueueDatabase
|
from frigate.db.sqlitevecq import SqliteVecQueueDatabase
|
||||||
from frigate.events.types import EventTypeEnum, RegenerateDescriptionEnum
|
from frigate.events.types import EventTypeEnum, RegenerateDescriptionEnum
|
||||||
from frigate.genai import get_genai_client
|
from frigate.genai import GenAIClientManager
|
||||||
from frigate.models import Event, Recordings, ReviewSegment, Trigger
|
from frigate.models import Event, Recordings, ReviewSegment, Trigger
|
||||||
from frigate.util.builtin import serialize
|
from frigate.util.builtin import serialize
|
||||||
from frigate.util.file import get_event_thumbnail_bytes
|
from frigate.util.file import get_event_thumbnail_bytes
|
||||||
@ -144,7 +144,8 @@ class EmbeddingMaintainer(threading.Thread):
|
|||||||
self.frame_manager = SharedMemoryFrameManager()
|
self.frame_manager = SharedMemoryFrameManager()
|
||||||
|
|
||||||
self.detected_license_plates: dict[str, dict[str, Any]] = {}
|
self.detected_license_plates: dict[str, dict[str, Any]] = {}
|
||||||
self.genai_client = get_genai_client(config)
|
self.genai_manager = GenAIClientManager(config)
|
||||||
|
self.genai_client = self.genai_manager.vision_client
|
||||||
|
|
||||||
# model runners to share between realtime and post processors
|
# model runners to share between realtime and post processors
|
||||||
if self.config.lpr.enabled:
|
if self.config.lpr.enabled:
|
||||||
|
|||||||
@ -23,7 +23,6 @@ __all__ = [
|
|||||||
"GenAIConfig",
|
"GenAIConfig",
|
||||||
"GenAIProviderEnum",
|
"GenAIProviderEnum",
|
||||||
"PROVIDERS",
|
"PROVIDERS",
|
||||||
"get_genai_client",
|
|
||||||
"load_providers",
|
"load_providers",
|
||||||
"register_genai_provider",
|
"register_genai_provider",
|
||||||
]
|
]
|
||||||
@ -364,19 +363,6 @@ Guidelines:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_genai_client(config: FrigateConfig) -> Optional[GenAIClient]:
|
|
||||||
"""Get the GenAI client."""
|
|
||||||
if not config.genai.provider:
|
|
||||||
return None
|
|
||||||
|
|
||||||
load_providers()
|
|
||||||
provider = PROVIDERS.get(config.genai.provider)
|
|
||||||
if provider:
|
|
||||||
return provider(config.genai)
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def load_providers():
|
def load_providers():
|
||||||
package_dir = os.path.dirname(__file__)
|
package_dir = os.path.dirname(__file__)
|
||||||
for filename in os.listdir(package_dir):
|
for filename in os.listdir(package_dir):
|
||||||
|
|||||||
@ -1,13 +1,19 @@
|
|||||||
"""GenAI client manager for Frigate.
|
"""GenAI client manager for Frigate.
|
||||||
|
|
||||||
Manages GenAI provider clients and supports multiple providers. Configuration
|
Manages GenAI provider clients from Frigate config. Configuration is read only
|
||||||
is driven by FrigateConfig; _update_config is called on init and can be
|
in _update_config(); no other code should read config.genai. Exposes clients
|
||||||
called again when config is reloaded.
|
by role: tool_client, vision_client, embeddings_client.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
from frigate.config import FrigateConfig
|
from frigate.config import FrigateConfig
|
||||||
|
from frigate.config.camera.genai import GenAIRoleEnum
|
||||||
|
from frigate.genai import PROVIDERS, load_providers
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from frigate.genai import GenAIClient
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -17,13 +23,67 @@ class GenAIClientManager:
|
|||||||
|
|
||||||
def __init__(self, config: FrigateConfig) -> None:
|
def __init__(self, config: FrigateConfig) -> None:
|
||||||
self._config = config
|
self._config = config
|
||||||
|
self._tool_client: Optional[GenAIClient] = None
|
||||||
|
self._vision_client: Optional[GenAIClient] = None
|
||||||
|
self._embeddings_client: Optional[GenAIClient] = None
|
||||||
self._update_config()
|
self._update_config()
|
||||||
|
|
||||||
def _update_config(self) -> None:
|
def _update_config(self) -> None:
|
||||||
"""Update internal state from the current Frigate config.
|
"""Build role clients from current Frigate config.genai.
|
||||||
|
|
||||||
Called from __init__ and can be called again when config is reloaded
|
Called from __init__ and can be called again when config is reloaded.
|
||||||
to support multiple providers or config changes.
|
Each role (tools, vision, embeddings) gets the client for the provider
|
||||||
|
that has that role in its roles list.
|
||||||
"""
|
"""
|
||||||
# Placeholder for multi-provider setup; will be extended in later refactor steps.
|
|
||||||
pass
|
self._tool_client = None
|
||||||
|
self._vision_client = None
|
||||||
|
self._embeddings_client = None
|
||||||
|
|
||||||
|
if not self._config.genai:
|
||||||
|
return
|
||||||
|
|
||||||
|
load_providers()
|
||||||
|
|
||||||
|
for _name, genai_cfg in self._config.genai.items():
|
||||||
|
if not genai_cfg.provider:
|
||||||
|
continue
|
||||||
|
provider_cls = PROVIDERS.get(genai_cfg.provider)
|
||||||
|
if not provider_cls:
|
||||||
|
logger.warning(
|
||||||
|
"Unknown GenAI provider %s in config, skipping.",
|
||||||
|
genai_cfg.provider,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
client = provider_cls(genai_cfg)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(
|
||||||
|
"Failed to create GenAI client for provider %s: %s",
|
||||||
|
genai_cfg.provider,
|
||||||
|
e,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
for role in genai_cfg.roles:
|
||||||
|
if role == GenAIRoleEnum.tools:
|
||||||
|
self._tool_client = client
|
||||||
|
elif role == GenAIRoleEnum.vision:
|
||||||
|
self._vision_client = client
|
||||||
|
elif role == GenAIRoleEnum.embeddings:
|
||||||
|
self._embeddings_client = client
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tool_client(self) -> "Optional[GenAIClient]":
|
||||||
|
"""Client configured for the tools role (e.g. chat with function calling)."""
|
||||||
|
return self._tool_client
|
||||||
|
|
||||||
|
@property
|
||||||
|
def vision_client(self) -> "Optional[GenAIClient]":
|
||||||
|
"""Client configured for the vision role (e.g. review descriptions, object descriptions)."""
|
||||||
|
return self._vision_client
|
||||||
|
|
||||||
|
@property
|
||||||
|
def embeddings_client(self) -> "Optional[GenAIClient]":
|
||||||
|
"""Client configured for the embeddings role."""
|
||||||
|
return self._embeddings_client
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user