mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-26 10:17:09 +00:00
45175a2a54
Quick commit to uniformize the S3 related settings case.
227 lines
7.7 KiB
Python
227 lines
7.7 KiB
Python
"""Application configuration and settings."""
|
|
|
|
import logging
|
|
from functools import cached_property, lru_cache
|
|
from typing import Annotated, List, Mapping, Optional, Set
|
|
|
|
from fastapi import Depends
|
|
from pydantic import (
|
|
BaseModel,
|
|
ConfigDict,
|
|
Field,
|
|
SecretStr,
|
|
model_validator,
|
|
)
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
from summary.core.types import Url
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class AuthorizedTenant(BaseModel):
|
|
"""Authorized tenant configuration."""
|
|
|
|
model_config = ConfigDict(frozen=True)
|
|
|
|
id: str = Field(title="Tenant Id", description="A unique ID for the tenant.")
|
|
api_key: SecretStr = Field(title="API key for using the summary API", min_length=8)
|
|
webhook_url: Url = Field(
|
|
title="Webhook URL", description="The URL to send the summary to."
|
|
)
|
|
webhook_api_key: SecretStr = Field(
|
|
title="Webhook API Key",
|
|
description="The api_key to authenticate the webhook request.",
|
|
)
|
|
allowed_push_to_docs: bool = Field(
|
|
title="Allow Push to Docs",
|
|
description="Whether to allow pushing transcript"
|
|
" and summaries to docs for this tenant.",
|
|
default=False,
|
|
)
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
"""Configuration settings loaded from environment variables and .env file."""
|
|
|
|
model_config = SettingsConfigDict(env_file=".env", frozen=True)
|
|
|
|
app_name: str = "summary"
|
|
app_api_v2_str: str = "/api/v2"
|
|
|
|
# Authorized Tenants
|
|
# Using env variables to store authorized tenants for now
|
|
# to avoid any other external dependency (DB)
|
|
authorized_tenants: tuple[AuthorizedTenant, ...] = Field(min_length=1)
|
|
|
|
# Audio recordings
|
|
recording_max_duration: Optional[int] = None
|
|
codec_to_extension: dict[str, str] = Field(
|
|
default_factory=lambda: {
|
|
"aac": ".m4a",
|
|
"alac": ".m4a",
|
|
"mp3": ".mp3",
|
|
"opus": ".opus",
|
|
"vorbis": ".ogg",
|
|
"flac": ".flac",
|
|
}
|
|
)
|
|
|
|
# Celery settings
|
|
celery_broker_url: str = "redis://redis/0"
|
|
celery_result_backend: str = "redis://redis/0"
|
|
celery_max_retries: int = 1
|
|
|
|
# v2 tasks
|
|
transcribe_queue_v2: str = "transcribe-queue-v2"
|
|
summarize_queue_v2: str = "summarize-queue-v2"
|
|
call_webhook_queue_v2: str = "call-webhook-queue-v2"
|
|
|
|
# Minio settings
|
|
aws_storage_bucket_name: str
|
|
aws_s3_endpoint_url: str
|
|
aws_s3_access_key_id: str
|
|
aws_s3_secret_access_key: SecretStr
|
|
aws_s3_secure_access: bool = True
|
|
aws_s3_region_name: str | None = None
|
|
aws_transcript_path: str = "transcripts"
|
|
aws_summary_path: str = "summaries"
|
|
|
|
# AI-related settings
|
|
whisperx_api_key: SecretStr
|
|
whisperx_base_url: Url = "https://api.openai.com/v1"
|
|
whisperx_asr_model: str = "whisper-1"
|
|
# ISO 639-1 language code (e.g., "en", "fr", "es")
|
|
whisperx_default_language: Optional[str] = None
|
|
whisperx_allowed_languages: Set[str] = {"en", "fr", "de", "nl"}
|
|
llm_base_url: str
|
|
llm_api_key: SecretStr
|
|
llm_model: str
|
|
|
|
# Transcription processing
|
|
hallucination_patterns: List[str] = ["Vap'n'Roll Thierry"]
|
|
|
|
# Speaker to user assignment
|
|
is_resolve_speaker_identities_enabled: bool = True
|
|
resolve_speaker_identities_default_overlap_threshold: float = 0.5
|
|
resolve_speaker_identities_enable_split_on_words: bool = True
|
|
resolve_speaker_identities_max_word_duration: float = 1 # seconds
|
|
|
|
# Webhook-related settings
|
|
webhook_max_retries: int = 2
|
|
webhook_status_forcelist: List[int] = [502, 503, 504]
|
|
webhook_backoff_factor: float = 0.1
|
|
app_external_user_agent: str = "summary"
|
|
|
|
# Summary related settings
|
|
is_summary_enabled: bool = True
|
|
|
|
# Docs service configuration
|
|
is_lasuite_docs_integration_enabled: bool = False
|
|
lasuite_docs_base_url: Url = "https://example.com"
|
|
lasuite_docs_server_to_server_api_key: SecretStr = Field(
|
|
title="API key for using docs server to server api", default="NO_API_KEY"
|
|
)
|
|
|
|
# Sentry
|
|
sentry_is_enabled: bool = False
|
|
sentry_dsn: Optional[str] = None
|
|
|
|
# Posthog (analytics)
|
|
posthog_enabled: bool = False
|
|
posthog_api_key: Optional[str] = None
|
|
posthog_api_host: Optional[str] = "https://eu.i.posthog.com"
|
|
posthog_transcript_request: str = "transcript-request"
|
|
posthog_transcript_failure: str = "transcript-failure"
|
|
posthog_transcript_success: str = "transcript-success"
|
|
posthog_summary_request: str = "summary-request"
|
|
posthog_summary_failure: str = "summary-failure"
|
|
posthog_summary_success: str = "summary-success"
|
|
|
|
# Langfuse (LLM Observability)
|
|
langfuse_enabled: bool = False
|
|
langfuse_host: Optional[str] = None
|
|
langfuse_public_key: Optional[str] = None
|
|
langfuse_secret_key: Optional[SecretStr] = None
|
|
langfuse_environment: Optional[str] = "development"
|
|
|
|
# TaskTracker
|
|
task_tracker_redis_url: str = "redis://redis/0"
|
|
task_tracker_prefix: str = "task_metadata:"
|
|
|
|
@model_validator(mode="after")
|
|
def validate_authorized_tenants(self):
|
|
"""Validate authorized tenants configuration."""
|
|
tenant_ids = {tenant.id for tenant in self.authorized_tenants}
|
|
|
|
if len(tenant_ids) != len(self.authorized_tenants):
|
|
raise ValueError("Duplicate tenant ids are not allowed")
|
|
|
|
api_keys = {
|
|
tenant.api_key.get_secret_value() for tenant in self.authorized_tenants
|
|
}
|
|
|
|
if len(api_keys) != len(self.authorized_tenants):
|
|
raise ValueError("Duplicate application API api_keys are not allowed")
|
|
return self
|
|
|
|
@model_validator(mode="after")
|
|
def validate_docs_config(self):
|
|
"""Validate docs integration configuration."""
|
|
if not self.is_lasuite_docs_integration_enabled:
|
|
return self
|
|
|
|
if not self.lasuite_docs_base_url:
|
|
raise ValueError(
|
|
"lasuite_docs_base_url is required when docs integration is enabled"
|
|
)
|
|
if self.lasuite_docs_server_to_server_api_key.get_secret_value() in {
|
|
"",
|
|
"NO_API_KEY",
|
|
}:
|
|
raise ValueError(
|
|
"Valid lasuite_docs_server_to_server_api_key is required when"
|
|
" docs integration is enabled"
|
|
)
|
|
return self
|
|
|
|
@cached_property
|
|
def authorized_tenant_api_keys(self) -> frozenset[str]:
|
|
"""Return a frozenset of authorized tenant API api_keys."""
|
|
return frozenset(
|
|
app.api_key.get_secret_value() for app in self.authorized_tenants
|
|
)
|
|
|
|
@cached_property
|
|
def authorized_tenant_by_id(self) -> Mapping[str, AuthorizedTenant]:
|
|
"""Return a dict of authorized tenants by ID."""
|
|
return {app.id: app for app in self.authorized_tenants}
|
|
|
|
@cached_property
|
|
def authorized_tenant_by_api_key(self) -> Mapping[str, AuthorizedTenant]:
|
|
"""Return the authorized tenant for a given API api_key."""
|
|
return {app.api_key.get_secret_value(): app for app in self.authorized_tenants}
|
|
|
|
def get_authorized_tenant(
|
|
self, *, tenant_id: str | None = None, api_key: str | None = None
|
|
) -> AuthorizedTenant:
|
|
"""Return the authorized tenant for a given API api_key or id."""
|
|
if tenant_id is None and api_key is None:
|
|
raise ValueError("Either tenant_id or api_key must be provided")
|
|
if tenant_id is not None and api_key is not None:
|
|
raise ValueError("Only one of tenant_id or api_key can be provided")
|
|
|
|
if tenant_id is not None:
|
|
return self.authorized_tenant_by_id[tenant_id]
|
|
else:
|
|
return self.authorized_tenant_by_api_key[api_key]
|
|
|
|
|
|
@lru_cache
|
|
def get_settings():
|
|
"""Load and cache application settings."""
|
|
return Settings()
|
|
|
|
|
|
SettingsDeps = Annotated[Settings, Depends(get_settings)]
|