diff --git a/frigate/genai/__init__.py b/frigate/genai/__init__.py index fdbf406091..438661f5d3 100644 --- a/frigate/genai/__init__.py +++ b/frigate/genai/__init__.py @@ -329,6 +329,13 @@ Guidelines: """ return True + def list_models(self) -> list[str]: + """Return the list of model names available from this provider. + + Providers should override this to query their backend. + """ + return [] + def get_context_size(self) -> int: """Get the context window size for this provider in tokens.""" return 4096 diff --git a/frigate/genai/azure-openai.py b/frigate/genai/azure-openai.py index 7cd8894a4e..66d7d1568e 100644 --- a/frigate/genai/azure-openai.py +++ b/frigate/genai/azure-openai.py @@ -82,6 +82,14 @@ class OpenAIClient(GenAIClient): return str(result.choices[0].message.content.strip()) return None + def list_models(self) -> list[str]: + """Return available model IDs from Azure OpenAI.""" + try: + return sorted(m.id for m in self.provider.models.list().data) + except Exception as e: + logger.warning("Failed to list Azure OpenAI models: %s", e) + return [] + def get_context_size(self) -> int: """Get the context window size for Azure OpenAI.""" return 128000 diff --git a/frigate/genai/gemini.py b/frigate/genai/gemini.py index d9644950dc..b0e8eeb301 100644 --- a/frigate/genai/gemini.py +++ b/frigate/genai/gemini.py @@ -87,6 +87,14 @@ class GeminiClient(GenAIClient): return None return description + def list_models(self) -> list[str]: + """Return available model names from Gemini.""" + try: + return sorted(m.name for m in self.provider.models.list()) + except Exception as e: + logger.warning("Failed to list Gemini models: %s", e) + return [] + def get_context_size(self) -> int: """Get the context window size for Gemini.""" # Gemini Pro Vision has a 1M token context window diff --git a/frigate/genai/llama_cpp.py b/frigate/genai/llama_cpp.py index da69286846..51e8d160d2 100644 --- a/frigate/genai/llama_cpp.py +++ b/frigate/genai/llama_cpp.py @@ -236,6 +236,23 @@ class LlamaCppClient(GenAIClient): """Whether the loaded model supports tool/function calling.""" return self._supports_tools + def list_models(self) -> list[str]: + """Return available model IDs from the llama.cpp server.""" + if self.provider is None: + return [] + try: + response = requests.get(f"{self.provider}/v1/models", timeout=10) + response.raise_for_status() + models = [] + for m in response.json().get("data", []): + models.append(m.get("id", "unknown")) + for alias in m.get("aliases", []): + models.append(alias) + return sorted(models) + except Exception as e: + logger.warning("Failed to list llama.cpp models: %s", e) + return [] + def get_context_size(self) -> int: """Get the context window size for llama.cpp. diff --git a/frigate/genai/manager.py b/frigate/genai/manager.py index e236f28032..044a271408 100644 --- a/frigate/genai/manager.py +++ b/frigate/genai/manager.py @@ -24,6 +24,7 @@ class GenAIClientManager: self._chat_client: Optional[GenAIClient] = None self._description_client: Optional[GenAIClient] = None self._embeddings_client: Optional[GenAIClient] = None + self._clients: dict[str, "GenAIClient"] = {} self.update_config(config) def update_config(self, config: FrigateConfig) -> None: @@ -38,6 +39,7 @@ class GenAIClientManager: self._chat_client = None self._description_client = None self._embeddings_client = None + self._clients = {} if not config.genai: return @@ -64,6 +66,8 @@ class GenAIClientManager: ) continue + self._clients[_name] = client + for role in genai_cfg.roles: if role == GenAIRoleEnum.chat: self._chat_client = client @@ -86,3 +90,7 @@ class GenAIClientManager: def embeddings_client(self) -> "Optional[GenAIClient]": """Client configured for the embeddings role.""" return self._embeddings_client + + 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()} diff --git a/frigate/genai/ollama.py b/frigate/genai/ollama.py index 2af1a6350a..78b2c371d6 100644 --- a/frigate/genai/ollama.py +++ b/frigate/genai/ollama.py @@ -132,6 +132,15 @@ class OllamaClient(GenAIClient): logger.warning("Ollama returned an error: %s", str(e)) return None + def list_models(self) -> list[str]: + """Return available model names from the Ollama server.""" + try: + response = self.provider.list() + return sorted(m.get("name", m.get("model", "")) for m in response.get("models", [])) + except Exception as e: + logger.warning("Failed to list Ollama models: %s", e) + return [] + def get_context_size(self) -> int: """Get the context window size for Ollama.""" return int( diff --git a/frigate/genai/openai.py b/frigate/genai/openai.py index 55fb21cc1d..88108e730d 100644 --- a/frigate/genai/openai.py +++ b/frigate/genai/openai.py @@ -86,6 +86,14 @@ class OpenAIClient(GenAIClient): logger.warning("OpenAI returned an error: %s", str(e)) return None + def list_models(self) -> list[str]: + """Return available model IDs from the OpenAI-compatible API.""" + try: + return sorted(m.id for m in self.provider.models.list().data) + except Exception as e: + logger.warning("Failed to list OpenAI models: %s", e) + return [] + def get_context_size(self) -> int: """Get the context window size for OpenAI.""" if self.context_size is not None: