add jina clip as service provider

This commit is contained in:
user 2025-12-10 14:06:08 -05:00
parent 39fc9e37e1
commit 37dc0fd7cb
3 changed files with 102 additions and 3 deletions

View File

@ -82,13 +82,15 @@ Switching between V1 and V2 requires reindexing your embeddings. The embeddings
### Remote Providers
Frigate can be configured to use remote services for generating embeddings. This is done by setting the `provider` field to `openai` or `ollama`.
Frigate can be configured to use remote services for generating embeddings. This is done by setting the `provider` field to `openai`, `ollama`, or `clip_as_service`.
For vision embeddings, remote providers use a two-step process:
#### OpenAI and Ollama
For OpenAI and Ollama, vision embeddings use a two-step process:
1. A text description of the image is generated using the configured GenAI provider.
2. An embedding is created from that description using the configured remote embedding provider.
This means that you must have a GenAI provider configured to use vision embeddings with a remote provider.
This means that you must have a GenAI provider configured to use vision embeddings with these providers.
```yaml
semantic_search:
@ -99,6 +101,21 @@ semantic_search:
vision_model_prompt: "A detailed description of the image for semantic search."
```
#### Jina CLIP-as-Service
Frigate supports [Jina CLIP-as-Service](https://clip-as-service.jina.ai/) which provides a multi-modal embedding service that can be hosted locally or remotely. This provider supports both text and image embeddings directly, without requiring a separate GenAI provider for image descriptions.
You can run CLIP-as-Service using their [getting started guide](https://clip-as-service.jina.ai/user-guides/server/).
```yaml
semantic_search:
enabled: True
provider: clip_as_service
remote:
url: "http://localhost:51000"
# model is typically handled by the service configuration
```
### GPU Acceleration
The CLIP models are downloaded in ONNX format, and the `large` model can be accelerated using GPU hardware, when available. This depends on the Docker build that is used. You can also target a specific device in a multi-GPU installation.

View File

@ -118,6 +118,7 @@ class SemanticSearchProviderEnum(str, Enum):
local = "local"
openai = "openai"
ollama = "ollama"
clip_as_service = "clip_as_service"
class RemoteSemanticSearchConfig(FrigateBaseModel):

View File

@ -0,0 +1,81 @@
"""Clip-as-service embedding client for Frigate."""
import base64
import logging
from typing import Optional
import requests
from frigate.config import SemanticSearchProviderEnum
from frigate.embeddings.remote import (
RemoteEmbeddingClient,
register_embedding_provider,
)
logger = logging.getLogger(__name__)
@register_embedding_provider(SemanticSearchProviderEnum.clip_as_service)
class ClipAsServiceEmbeddingClient(RemoteEmbeddingClient):
"""Remote embedding client for Frigate using clip-as-service."""
def _init_provider(self):
"""Initialize the client."""
return True
def embed_texts(self, texts: list[str]) -> Optional[list[list[float]]]:
"""Get embeddings for a list of texts."""
if not self.config.semantic_search.remote.url:
logger.error("Clip-as-service URL is not configured.")
return None
payload = {
"data": [{"text": t} for t in texts],
"exec_endpoint": "/"
}
try:
response = requests.post(
f"{self.config.semantic_search.remote.url}/post",
json=payload,
timeout=self.timeout,
)
response.raise_for_status()
data = response.json()
if "data" in data:
return [item["embedding"] for item in data["data"]]
return None
except Exception as e:
logger.warning("Clip-as-service error: %s", str(e))
return None
def embed_images(self, images: list[bytes]) -> Optional[list[list[float]]]:
"""Get embeddings for a list of images."""
if not self.config.semantic_search.remote.url:
logger.error("Clip-as-service URL is not configured.")
return None
payload_data = []
for img_bytes in images:
b64_str = base64.b64encode(img_bytes).decode("utf-8")
payload_data.append({"blob": b64_str})
payload = {
"data": payload_data,
"exec_endpoint": "/"
}
try:
response = requests.post(
f"{self.config.semantic_search.remote.url}/post",
json=payload,
timeout=self.timeout,
)
response.raise_for_status()
data = response.json()
if "data" in data:
return [item["embedding"] for item in data["data"]]
return None
except Exception as e:
logger.warning("Clip-as-service error: %s", str(e))
return None