diff --git a/frigate/genai/manager.py b/frigate/genai/manager.py index 044a271408..6390c839a4 100644 --- a/frigate/genai/manager.py +++ b/frigate/genai/manager.py @@ -1,15 +1,15 @@ """GenAI client manager for Frigate. -Manages GenAI provider clients from Frigate config. Configuration is read only -in _update_config(); no other code should read config.genai. Exposes clients -by role: chat_client, description_client, embeddings_client. +Manages GenAI provider clients from Frigate config. Clients are created lazily +on first access so that providers whose roles are never used (e.g. chat when +no chat feature is active) are never initialized. """ import logging from typing import TYPE_CHECKING, Optional from frigate.config import FrigateConfig -from frigate.config.camera.genai import GenAIRoleEnum +from frigate.config.camera.genai import GenAIConfig, GenAIRoleEnum if TYPE_CHECKING: from frigate.genai import GenAIClient @@ -21,24 +21,22 @@ class GenAIClientManager: """Manages GenAI provider clients from Frigate config.""" def __init__(self, config: FrigateConfig) -> None: - self._chat_client: Optional[GenAIClient] = None - self._description_client: Optional[GenAIClient] = None - self._embeddings_client: Optional[GenAIClient] = None + self._configs: dict[str, GenAIConfig] = {} + self._role_map: dict[GenAIRoleEnum, str] = {} self._clients: dict[str, "GenAIClient"] = {} self.update_config(config) def update_config(self, config: FrigateConfig) -> None: - """Build role clients from current Frigate config.genai. + """Store provider configs and build the role→name mapping. Called from __init__ and can be called again when config is reloaded. - Each role (chat, descriptions, embeddings) gets the client for the - provider that has that role in its roles list. + Clients are not created here; they are instantiated lazily on first + access via a role property or list_models(). """ from frigate.genai import PROVIDERS, load_providers - self._chat_client = None - self._description_client = None - self._embeddings_client = None + self._configs = {} + self._role_map = {} self._clients = {} if not config.genai: @@ -46,51 +44,72 @@ class GenAIClientManager: load_providers() - for _name, genai_cfg in config.genai.items(): + for name, genai_cfg in config.genai.items(): if not genai_cfg.provider: continue - provider_cls = PROVIDERS.get(genai_cfg.provider) - if not provider_cls: + if genai_cfg.provider not in PROVIDERS: 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 - self._clients[_name] = client + self._configs[name] = genai_cfg for role in genai_cfg.roles: - if role == GenAIRoleEnum.chat: - self._chat_client = client - elif role == GenAIRoleEnum.descriptions: - self._description_client = client - elif role == GenAIRoleEnum.embeddings: - self._embeddings_client = client + self._role_map[role] = name + + def _get_client(self, name: str) -> "Optional[GenAIClient]": + """Return the client for *name*, creating it on first access.""" + if name in self._clients: + return self._clients[name] + + from frigate.genai import PROVIDERS + + genai_cfg = self._configs.get(name) + if not genai_cfg: + return None + + provider_cls = PROVIDERS.get(genai_cfg.provider) + if not provider_cls: + return None + + 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, + ) + return None + + self._clients[name] = client + return client @property def chat_client(self) -> "Optional[GenAIClient]": """Client configured for the chat role (e.g. chat with function calling).""" - return self._chat_client + name = self._role_map.get(GenAIRoleEnum.chat) + return self._get_client(name) if name else None @property def description_client(self) -> "Optional[GenAIClient]": """Client configured for the descriptions role (e.g. review descriptions, object descriptions).""" - return self._description_client + name = self._role_map.get(GenAIRoleEnum.descriptions) + return self._get_client(name) if name else None @property def embeddings_client(self) -> "Optional[GenAIClient]": """Client configured for the embeddings role.""" - return self._embeddings_client + name = self._role_map.get(GenAIRoleEnum.embeddings) + return self._get_client(name) if name else None def list_models(self) -> dict[str, list[str]]: """Return available models keyed by config entry name.""" - return {name: client.list_models() for name, client in self._clients.items()} + result: dict[str, list[str]] = {} + for name in self._configs: + client = self._get_client(name) + if client: + result[name] = client.list_models() + return result diff --git a/web/vite.config.ts b/web/vite.config.ts index cb1a580bf3..98a9afde1a 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -4,7 +4,7 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react-swc"; import monacoEditorPlugin from "vite-plugin-monaco-editor"; -const proxyHost = process.env.PROXY_HOST || "localhost:5000"; +const proxyHost = process.env.PROXY_HOST || "192.168.50.106:5002"; // https://vitejs.dev/config/ export default defineConfig({