chore: fix dograh v2 speed option

This commit is contained in:
Abhishek Kumar 2026-06-19 21:24:48 +05:30
parent c554256db1
commit 8006d0edcd
5 changed files with 100 additions and 30 deletions

View file

@ -17,7 +17,10 @@ from api.enums import OrganizationConfigurationKey, PostHogEvent
from api.schemas.ai_model_configuration import ( from api.schemas.ai_model_configuration import (
DOGRAH_DEFAULT_LANGUAGE, DOGRAH_DEFAULT_LANGUAGE,
DOGRAH_DEFAULT_VOICE, DOGRAH_DEFAULT_VOICE,
DOGRAH_SPEED_MAX,
DOGRAH_SPEED_MIN,
DOGRAH_SPEED_OPTIONS, DOGRAH_SPEED_OPTIONS,
DOGRAH_SPEED_STEP,
OrganizationAIModelConfigurationResponse, OrganizationAIModelConfigurationResponse,
OrganizationAIModelConfigurationV2, OrganizationAIModelConfigurationV2,
) )
@ -265,6 +268,11 @@ async def get_model_configuration_v2_defaults(
"voices": [DOGRAH_DEFAULT_VOICE], "voices": [DOGRAH_DEFAULT_VOICE],
"allow_custom_input": _dograh_allows_custom_voice(), "allow_custom_input": _dograh_allows_custom_voice(),
"speeds": list(DOGRAH_SPEED_OPTIONS), "speeds": list(DOGRAH_SPEED_OPTIONS),
"speed_range": {
"min": DOGRAH_SPEED_MIN,
"max": DOGRAH_SPEED_MAX,
"step": DOGRAH_SPEED_STEP,
},
"languages": DOGRAH_STT_LANGUAGES, "languages": DOGRAH_STT_LANGUAGES,
"defaults": { "defaults": {
"voice": DOGRAH_DEFAULT_VOICE, "voice": DOGRAH_DEFAULT_VOICE,

View file

@ -18,6 +18,9 @@ from api.services.configuration.registry import (
TTSConfig, TTSConfig,
) )
DOGRAH_SPEED_MIN = 0.5
DOGRAH_SPEED_MAX = 2.0
DOGRAH_SPEED_STEP = 0.1
DOGRAH_SPEED_OPTIONS: tuple[float, ...] = (0.8, 1.0, 1.2) DOGRAH_SPEED_OPTIONS: tuple[float, ...] = (0.8, 1.0, 1.2)
DOGRAH_DEFAULT_VOICE = "default" DOGRAH_DEFAULT_VOICE = "default"
DOGRAH_DEFAULT_LANGUAGE = "multi" DOGRAH_DEFAULT_LANGUAGE = "multi"
@ -49,16 +52,9 @@ class EffectiveAIModelConfiguration(BaseModel):
class DograhManagedAIModelConfiguration(BaseModel): class DograhManagedAIModelConfiguration(BaseModel):
api_key: str api_key: str
voice: str = DOGRAH_DEFAULT_VOICE voice: str = DOGRAH_DEFAULT_VOICE
speed: float = Field(default=1.0) speed: float = Field(default=1.0, ge=DOGRAH_SPEED_MIN, le=DOGRAH_SPEED_MAX)
language: str = DOGRAH_DEFAULT_LANGUAGE 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): class BYOKPipelineAIModelConfiguration(BaseModel):
llm: LLMConfig llm: LLMConfig

View file

@ -16,7 +16,8 @@ from api.enums import OrganizationConfigurationKey
from api.schemas.ai_model_configuration import ( from api.schemas.ai_model_configuration import (
DOGRAH_DEFAULT_LANGUAGE, DOGRAH_DEFAULT_LANGUAGE,
DOGRAH_DEFAULT_VOICE, DOGRAH_DEFAULT_VOICE,
DOGRAH_SPEED_OPTIONS, DOGRAH_SPEED_MAX,
DOGRAH_SPEED_MIN,
BYOKAIModelConfiguration, BYOKAIModelConfiguration,
BYOKPipelineAIModelConfiguration, BYOKPipelineAIModelConfiguration,
BYOKRealtimeAIModelConfiguration, BYOKRealtimeAIModelConfiguration,
@ -436,7 +437,11 @@ def _convert_any_dograh_legacy_configuration(
dograh_key: str, dograh_key: str,
) -> OrganizationAIModelConfigurationV2: ) -> OrganizationAIModelConfigurationV2:
speed = getattr(configuration.tts, "speed", 1.0) speed = getattr(configuration.tts, "speed", 1.0)
if speed not in DOGRAH_SPEED_OPTIONS: try:
speed = float(speed)
except (TypeError, ValueError):
speed = 1.0
if not DOGRAH_SPEED_MIN <= speed <= DOGRAH_SPEED_MAX:
speed = 1.0 speed = 1.0
return OrganizationAIModelConfigurationV2( return OrganizationAIModelConfigurationV2(
mode="dograh", mode="dograh",

View file

@ -59,13 +59,27 @@ def test_dograh_v2_compiles_to_effective_managed_pipeline_with_embeddings():
assert effective.managed_service_version == 2 assert effective.managed_service_version == 2
def test_dograh_v2_rejects_non_predefined_speed(): def test_dograh_v2_accepts_numeric_speed_in_registry_range():
config = OrganizationAIModelConfigurationV2(
mode="dograh",
dograh=DograhManagedAIModelConfiguration(
api_key="mps-secret",
speed=1.5,
),
)
effective = compile_ai_model_configuration_v2(config)
assert effective.tts.speed == 1.5
def test_dograh_v2_rejects_out_of_range_speed():
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
OrganizationAIModelConfigurationV2( OrganizationAIModelConfigurationV2(
mode="dograh", mode="dograh",
dograh=DograhManagedAIModelConfiguration( dograh=DograhManagedAIModelConfiguration(
api_key="mps-secret", api_key="mps-secret",
speed=1.5, speed=2.5,
), ),
) )
@ -238,6 +252,33 @@ def test_legacy_all_dograh_pipeline_converts_to_dograh_v2():
assert config.dograh.api_key == "mps-secret" assert config.dograh.api_key == "mps-secret"
def test_legacy_dograh_pipeline_conversion_preserves_numeric_speed():
legacy = EffectiveAIModelConfiguration(
llm=DograhLLMService(
provider="dograh",
api_key=["mps-secret"],
model="default",
),
tts=DograhTTSService(
provider="dograh",
api_key=["mps-secret"],
model="default",
voice="default",
speed=1.5,
),
stt=DograhSTTService(
provider="dograh",
api_key=["mps-secret"],
model="default",
),
)
config = convert_legacy_ai_model_configuration_to_v2(legacy)
assert config.mode == "dograh"
assert config.dograh.speed == 1.5
def test_legacy_mixed_dograh_pipeline_converts_to_dograh_v2(): def test_legacy_mixed_dograh_pipeline_converts_to_dograh_v2():
legacy = EffectiveAIModelConfiguration( legacy = EffectiveAIModelConfiguration(
llm=OpenAILLMService( llm=OpenAILLMService(

View file

@ -25,6 +25,11 @@ interface DograhDefaults {
voices: string[]; voices: string[];
allow_custom_input?: boolean; allow_custom_input?: boolean;
speeds: number[]; speeds: number[];
speed_range?: {
min: number;
max: number;
step?: number;
};
languages: string[]; languages: string[];
defaults: { defaults: {
voice: string; voice: string;
@ -66,6 +71,11 @@ function firstApiKey(value: unknown): string {
return typeof value === "string" ? value : ""; return typeof value === "string" ? value : "";
} }
function numberOrDefault(value: unknown, fallback: number): number {
const parsed = typeof value === "number" ? value : Number(value);
return Number.isFinite(parsed) ? parsed : fallback;
}
function asRecord(value: unknown): Record<string, unknown> | null { function asRecord(value: unknown): Record<string, unknown> | null {
return value && typeof value === "object" && !Array.isArray(value) return value && typeof value === "object" && !Array.isArray(value)
? value as Record<string, unknown> ? value as Record<string, unknown>
@ -170,7 +180,7 @@ function buildDograhState(
return { return {
api_key: String(configuredDograh.api_key || ""), api_key: String(configuredDograh.api_key || ""),
voice: String(configuredDograh.voice || fallback.voice), voice: String(configuredDograh.voice || fallback.voice),
speed: Number(configuredDograh.speed || fallback.speed), speed: numberOrDefault(configuredDograh.speed, fallback.speed),
language: String(configuredDograh.language || fallback.language), language: String(configuredDograh.language || fallback.language),
}; };
} }
@ -182,7 +192,7 @@ function buildDograhState(
return { return {
api_key: firstApiKey(llm?.api_key || tts?.api_key || stt?.api_key), api_key: firstApiKey(llm?.api_key || tts?.api_key || stt?.api_key),
voice: String(tts?.voice || fallback.voice), voice: String(tts?.voice || fallback.voice),
speed: Number(tts?.speed || fallback.speed), speed: numberOrDefault(tts?.speed, fallback.speed),
language: String(stt?.language || fallback.language), language: String(stt?.language || fallback.language),
}; };
} }
@ -272,6 +282,7 @@ export function AIModelConfigurationV2Editor({
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const allowCustomVoice = defaults.dograh.allow_custom_input ?? false; const allowCustomVoice = defaults.dograh.allow_custom_input ?? false;
const dograhSpeedRange = defaults.dograh.speed_range ?? { min: 0.5, max: 2.0, step: 0.1 };
useEffect(() => { useEffect(() => {
const rawConfiguration = asRecord(configuration); const rawConfiguration = asRecord(configuration);
@ -288,6 +299,15 @@ export function AIModelConfigurationV2Editor({
setIsSavingDograh(true); setIsSavingDograh(true);
setError(null); setError(null);
try { try {
if (
!Number.isFinite(dograh.speed)
|| dograh.speed < dograhSpeedRange.min
|| dograh.speed > dograhSpeedRange.max
) {
throw new Error(
`Dograh speed must be between ${dograhSpeedRange.min} and ${dograhSpeedRange.max}.`,
);
}
await onSave({ await onSave({
version: 2, version: 2,
mode: "dograh", mode: "dograh",
@ -413,22 +433,22 @@ export function AIModelConfigurationV2Editor({
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label>Speed</Label> <Label htmlFor="dograh-speed">Speed</Label>
<Select <Input
value={String(dograh.speed)} id="dograh-speed"
onValueChange={(speed) => setDograh({ ...dograh, speed: Number(speed) })} type="number"
> min={dograhSpeedRange.min}
<SelectTrigger className="w-full"> max={dograhSpeedRange.max}
<SelectValue placeholder="Select speed" /> step={dograhSpeedRange.step ?? 0.1}
</SelectTrigger> value={dograh.speed}
<SelectContent> onChange={(event) => {
{defaults.dograh.speeds.map((speed) => ( const speed = event.currentTarget.valueAsNumber;
<SelectItem key={speed} value={String(speed)}> setDograh({
{speed}x ...dograh,
</SelectItem> speed: Number.isFinite(speed) ? speed : defaults.dograh.defaults.speed,
))} });
</SelectContent> }}
</Select> />
</div> </div>
<div className="space-y-2 sm:col-span-2"> <div className="space-y-2 sm:col-span-2">