Files
frigate/frigate/genai/openai.py
T

119 lines
4.2 KiB
Python
Raw Normal View History

2024-06-21 17:30:19 -04:00
"""OpenAI Provider for Frigate AI."""
import base64
2024-09-24 15:04:35 -05:00
import logging
2024-06-21 17:30:19 -04:00
from typing import Optional
from httpx import TimeoutException
from openai import OpenAI
from frigate.config import GenAIProviderEnum
from frigate.genai import GenAIClient, register_genai_provider
2024-09-24 15:04:35 -05:00
logger = logging.getLogger(__name__)
2024-06-21 17:30:19 -04:00
@register_genai_provider(GenAIProviderEnum.openai)
class OpenAIClient(GenAIClient):
"""Generative AI client for Frigate using OpenAI."""
provider: OpenAI
2025-10-31 12:40:31 -06:00
context_size: Optional[int] = None
2024-06-21 17:30:19 -04:00
def _init_provider(self):
"""Initialize the client."""
2026-01-12 20:36:38 -07:00
# Extract context_size from provider_options as it's not a valid OpenAI client parameter
# It will be used in get_context_size() instead
provider_opts = {
k: v
for k, v in self.genai_config.provider_options.items()
if k != "context_size"
}
return OpenAI(api_key=self.genai_config.api_key, **provider_opts)
2024-06-21 17:30:19 -04:00
def _send(self, prompt: str, images: list[bytes]) -> Optional[str]:
"""Submit a request to OpenAI."""
encoded_images = [base64.b64encode(image).decode("utf-8") for image in images]
2025-03-03 11:53:24 -08:00
messages_content = []
for image in encoded_images:
messages_content.append(
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{image}",
"detail": "low",
},
}
)
messages_content.append(
{
"type": "text",
"text": prompt,
}
)
2024-06-21 17:30:19 -04:00
try:
result = self.provider.chat.completions.create(
model=self.genai_config.model,
messages=[
{
"role": "user",
2025-03-03 11:53:24 -08:00
"content": messages_content,
2024-06-21 17:30:19 -04:00
},
],
timeout=self.timeout,
2026-01-12 20:36:38 -07:00
**self.genai_config.runtime_options,
2024-06-21 17:30:19 -04:00
)
if (
result is not None
and hasattr(result, "choices")
and len(result.choices) > 0
):
return result.choices[0].message.content.strip()
return None
except (TimeoutException, Exception) as e:
2024-09-24 15:04:35 -05:00
logger.warning("OpenAI returned an error: %s", str(e))
2024-06-21 17:30:19 -04:00
return None
2025-10-02 09:17:25 -06:00
def get_context_size(self) -> int:
"""Get the context window size for OpenAI."""
2025-10-31 12:40:31 -06:00
if self.context_size is not None:
return self.context_size
2026-01-12 20:36:38 -07:00
# First check provider_options for manually specified context size
# This is necessary for llama.cpp and other OpenAI-compatible servers
# that don't expose the configured runtime context size in the API response
if "context_size" in self.genai_config.provider_options:
self.context_size = self.genai_config.provider_options["context_size"]
logger.debug(
f"Using context size {self.context_size} from provider_options for model {self.genai_config.model}"
)
return self.context_size
2025-10-31 12:40:31 -06:00
try:
models = self.provider.models.list()
for model in models.data:
if model.id == self.genai_config.model:
if hasattr(model, "max_model_len") and model.max_model_len:
self.context_size = model.max_model_len
logger.debug(
f"Retrieved context size {self.context_size} for model {self.genai_config.model}"
)
return self.context_size
except Exception as e:
logger.debug(
f"Failed to fetch model context size from API: {e}, using default"
)
# Default to 128K for ChatGPT models, 8K for others
model_name = self.genai_config.model.lower()
if "gpt" in model_name:
2025-10-31 12:40:31 -06:00
self.context_size = 128000
else:
self.context_size = 8192
logger.debug(
f"Using default context size {self.context_size} for model {self.genai_config.model}"
)
return self.context_size