mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-19 08:28:10 +02:00
feat: add config v2 to simplify billing (#428)
* feat: add model config v2 * chore: centralize user org selection * chore: move preferences to platform settings * fix: decouple org preference and ai model preferences
This commit is contained in:
parent
49e68b49d5
commit
cdbd06c8d9
42 changed files with 5135 additions and 264 deletions
170
api/schemas/ai_model_configuration.py
Normal file
170
api/schemas/ai_model_configuration.py
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
|
||||
from api.schemas.user_configuration import EffectiveAIModelConfiguration
|
||||
from api.services.configuration.registry import (
|
||||
DograhEmbeddingsConfiguration,
|
||||
DograhLLMService,
|
||||
DograhSTTService,
|
||||
DograhTTSService,
|
||||
EmbeddingsConfig,
|
||||
LLMConfig,
|
||||
RealtimeConfig,
|
||||
ServiceProviders,
|
||||
STTConfig,
|
||||
TTSConfig,
|
||||
)
|
||||
|
||||
DOGRAH_SPEED_OPTIONS: tuple[float, ...] = (0.8, 1.0, 1.2)
|
||||
DOGRAH_DEFAULT_VOICE = "default"
|
||||
DOGRAH_DEFAULT_LANGUAGE = "multi"
|
||||
|
||||
|
||||
class DograhManagedAIModelConfiguration(BaseModel):
|
||||
api_key: str
|
||||
voice: str = DOGRAH_DEFAULT_VOICE
|
||||
speed: float = Field(default=1.0)
|
||||
language: str = DOGRAH_DEFAULT_LANGUAGE
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_speed(self):
|
||||
if self.speed not in DOGRAH_SPEED_OPTIONS:
|
||||
allowed = ", ".join(str(speed) for speed in DOGRAH_SPEED_OPTIONS)
|
||||
raise ValueError(f"Dograh speed must be one of: {allowed}")
|
||||
return self
|
||||
|
||||
|
||||
class BYOKPipelineAIModelConfiguration(BaseModel):
|
||||
llm: LLMConfig
|
||||
tts: TTSConfig
|
||||
stt: STTConfig
|
||||
embeddings: EmbeddingsConfig | None = None
|
||||
|
||||
@model_validator(mode="after")
|
||||
def reject_dograh_providers(self):
|
||||
_reject_dograh_provider("llm", self.llm)
|
||||
_reject_dograh_provider("tts", self.tts)
|
||||
_reject_dograh_provider("stt", self.stt)
|
||||
_reject_dograh_provider("embeddings", self.embeddings)
|
||||
return self
|
||||
|
||||
|
||||
class BYOKRealtimeAIModelConfiguration(BaseModel):
|
||||
realtime: RealtimeConfig
|
||||
llm: LLMConfig
|
||||
embeddings: EmbeddingsConfig | None = None
|
||||
|
||||
@model_validator(mode="after")
|
||||
def reject_dograh_providers(self):
|
||||
_reject_dograh_provider("llm", self.llm)
|
||||
_reject_dograh_provider("embeddings", self.embeddings)
|
||||
return self
|
||||
|
||||
|
||||
class BYOKAIModelConfiguration(BaseModel):
|
||||
mode: Literal["pipeline", "realtime"]
|
||||
pipeline: BYOKPipelineAIModelConfiguration | None = None
|
||||
realtime: BYOKRealtimeAIModelConfiguration | None = None
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_selected_mode(self):
|
||||
if self.mode == "pipeline" and self.pipeline is None:
|
||||
raise ValueError("byok.pipeline is required when byok.mode is pipeline")
|
||||
if self.mode == "realtime" and self.realtime is None:
|
||||
raise ValueError("byok.realtime is required when byok.mode is realtime")
|
||||
return self
|
||||
|
||||
|
||||
class OrganizationAIModelConfigurationV2(BaseModel):
|
||||
version: Literal[2] = 2
|
||||
mode: Literal["dograh", "byok"]
|
||||
dograh: DograhManagedAIModelConfiguration | None = None
|
||||
byok: BYOKAIModelConfiguration | None = None
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_selected_mode(self):
|
||||
if self.mode == "dograh" and self.dograh is None:
|
||||
raise ValueError("dograh configuration is required when mode is dograh")
|
||||
if self.mode == "byok" and self.byok is None:
|
||||
raise ValueError("byok configuration is required when mode is byok")
|
||||
return self
|
||||
|
||||
|
||||
class OrganizationAIModelConfigurationResponse(BaseModel):
|
||||
configuration: dict | None
|
||||
effective_configuration: dict
|
||||
source: Literal["organization_v2", "legacy_user_v1", "empty"]
|
||||
|
||||
|
||||
def compile_ai_model_configuration_v2(
|
||||
configuration: OrganizationAIModelConfigurationV2,
|
||||
) -> EffectiveAIModelConfiguration:
|
||||
if configuration.mode == "dograh":
|
||||
if configuration.dograh is None:
|
||||
raise ValueError("dograh configuration is required")
|
||||
return _compile_dograh_configuration(configuration.dograh)
|
||||
|
||||
if configuration.byok is None:
|
||||
raise ValueError("byok configuration is required")
|
||||
if configuration.byok.mode == "pipeline":
|
||||
if configuration.byok.pipeline is None:
|
||||
raise ValueError("byok.pipeline is required")
|
||||
pipeline = configuration.byok.pipeline
|
||||
return EffectiveAIModelConfiguration(
|
||||
llm=pipeline.llm,
|
||||
tts=pipeline.tts,
|
||||
stt=pipeline.stt,
|
||||
embeddings=pipeline.embeddings,
|
||||
is_realtime=False,
|
||||
)
|
||||
|
||||
if configuration.byok.realtime is None:
|
||||
raise ValueError("byok.realtime is required")
|
||||
realtime = configuration.byok.realtime
|
||||
return EffectiveAIModelConfiguration(
|
||||
llm=realtime.llm,
|
||||
realtime=realtime.realtime,
|
||||
embeddings=realtime.embeddings,
|
||||
is_realtime=True,
|
||||
)
|
||||
|
||||
|
||||
def _compile_dograh_configuration(
|
||||
configuration: DograhManagedAIModelConfiguration,
|
||||
) -> EffectiveAIModelConfiguration:
|
||||
return EffectiveAIModelConfiguration(
|
||||
llm=DograhLLMService(
|
||||
provider=ServiceProviders.DOGRAH,
|
||||
api_key=configuration.api_key,
|
||||
model="default",
|
||||
),
|
||||
tts=DograhTTSService(
|
||||
provider=ServiceProviders.DOGRAH,
|
||||
api_key=configuration.api_key,
|
||||
model="default",
|
||||
voice=configuration.voice,
|
||||
speed=configuration.speed,
|
||||
),
|
||||
stt=DograhSTTService(
|
||||
provider=ServiceProviders.DOGRAH,
|
||||
api_key=configuration.api_key,
|
||||
model="default",
|
||||
language=configuration.language,
|
||||
),
|
||||
embeddings=DograhEmbeddingsConfiguration(
|
||||
provider=ServiceProviders.DOGRAH,
|
||||
api_key=configuration.api_key,
|
||||
model="default",
|
||||
),
|
||||
is_realtime=False,
|
||||
)
|
||||
|
||||
|
||||
def _reject_dograh_provider(section: str, service) -> None:
|
||||
if service is None:
|
||||
return
|
||||
if getattr(service, "provider", None) == ServiceProviders.DOGRAH:
|
||||
raise ValueError(f"BYOK {section} cannot use Dograh provider")
|
||||
6
api/schemas/organization_preferences.py
Normal file
6
api/schemas/organization_preferences.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class OrganizationPreferences(BaseModel):
|
||||
test_phone_number: str | None = None
|
||||
timezone: str | None = None
|
||||
|
|
@ -11,7 +11,7 @@ from api.services.configuration.registry import (
|
|||
)
|
||||
|
||||
|
||||
class UserConfiguration(BaseModel):
|
||||
class EffectiveAIModelConfiguration(BaseModel):
|
||||
llm: LLMConfig | None = None
|
||||
stt: STTConfig | None = None
|
||||
tts: TTSConfig | None = None
|
||||
|
|
@ -31,3 +31,7 @@ class UserConfiguration(BaseModel):
|
|||
if isinstance(realtime, dict) and not realtime.get("api_key"):
|
||||
data.pop("realtime", None)
|
||||
return data
|
||||
|
||||
|
||||
# Backward-compatible alias for legacy persistence and existing call sites.
|
||||
UserConfiguration = EffectiveAIModelConfiguration
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue