Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ TOGETHER_API_KEY=
MINIMAX_API_KEY=
MODULATE_API_KEY=
LMNT_API_KEY=
FLUXIONS_API_KEY=

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Place FLUXIONS_API_KEY before GLADIA_API_KEY.

dotenv-linter reports this key is out of order. Restore the expected order to keep the environment-file lint check passing.

🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 42-42: [UnorderedKey] The FLUXIONS_API_KEY key should go before the GLADIA_API_KEY key

(UnorderedKey)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.env.example at line 42, Reorder the environment entries in the .env.example
block so FLUXIONS_API_KEY appears before GLADIA_API_KEY, keeping the rest of the
variable order unchanged and aligned with the dotenv-linter expectation.

Source: Linters/SAST tools


# --- API (optional) ---
# Benchmarking-team key: requests with X-Internal-Key equal to this value see
Expand Down
1 change: 1 addition & 0 deletions runner/src/coval_bench/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ def _dataset_id_not_reserved(cls, value: str) -> str:
lmnt_api_key: SecretStr | None = None
modulate_api_key: SecretStr | None = None
speechify_api_key: SecretStr | None = None
fluxions_api_key: SecretStr | None = None

# Azure region hosting the Speech resource (e.g. "eastus"). Determines the
# region-scoped WebSocket host; required only when the Azure STT provider runs.
Expand Down
19 changes: 13 additions & 6 deletions runner/src/coval_bench/providers/tts/fluxions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
connect → send speak(voice, input) → recv {"type": "start"}
Comment thread
coval-cale marked this conversation as resolved.
→ recv binary s16le PCM @ 24 kHz frames → recv {"type": "done"}

Built-in voices are public, so the render path takes no credential and there is
no ``Settings`` key for this provider. ``verify_chunks`` is disabled: the
server's STT re-render pass multiplies TTFA without improving WER. Voice ids
are ``<name>.<catalog-hash>`` with a rotating hash, so the registry pins the
bare name and this module resolves it against ``GET /vui/voices`` before t0.
The websocket requires an API key, sent as a bearer ``Authorization`` header
on the handshake; unauthenticated connects are closed 1008 by a bot gate. The
voice catalog endpoint is public. ``verify_chunks`` is disabled: the server's
STT re-render pass multiplies TTFA without improving WER. Voice ids are
``<name>.<catalog-hash>`` with a rotating hash, so the registry pins the bare
name and this module resolves it against ``GET /vui/voices`` before t0.
"""

from __future__ import annotations
Expand Down Expand Up @@ -57,6 +58,10 @@ def __init__(self, settings: Settings, model: str, voice: str) -> None:
raise ValueError(f"Invalid Fluxions TTS model {model!r}. Valid: {_VALID_MODELS}")
if not voice:
raise ValueError("Fluxions TTS requires a voice")
api_key_secret = settings.fluxions_api_key
if api_key_secret is None:
raise ValueError("fluxions_api_key is required in Settings")
self._api_key = api_key_secret.get_secret_value()
self._model = model
self._voice = voice

Expand Down Expand Up @@ -107,7 +112,9 @@ async def synthesize(self, text: str) -> TTSResult:

try:
voice_id = await self._resolve_voice()
async with ws_client.connect(_WS_URL) as ws:
async with ws_client.connect(
_WS_URL, additional_headers={"Authorization": f"Bearer {self._api_key}"}
) as ws:
start = time.monotonic()
await ws.send(
json.dumps(
Expand Down
2 changes: 1 addition & 1 deletion runner/src/coval_bench/registries/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -756,7 +756,7 @@ class RegisteredModel(BaseModel, frozen=True, extra="forbid"):
status=_EARLY_ACCESS,
),
# No model id on the wire, only a voice, so "vui" is the bare surface name.
# Arena-disabled: keyless, so no env var for the key-parity gate to verify.
# Arena-disabled: FLUXIONS_API_KEY is not mounted on benchmarks-api yet.
RegisteredModel(
benchmark=_TTS,
provider="fluxions",
Expand Down
1 change: 1 addition & 0 deletions runner/src/coval_bench/registries/provider_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@
"palabra": "PALABRA_API_KEY",
"speechify": "SPEECHIFY_API_KEY",
"lmnt": "LMNT_API_KEY",
"fluxions": "FLUXIONS_API_KEY",
}
10 changes: 10 additions & 0 deletions runner/tests/providers/tts/test_fluxions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from unittest.mock import patch

import pytest
from pydantic import SecretStr

from coval_bench.config import Settings
from coval_bench.providers.tts import fluxions as fluxions_module
Expand All @@ -29,6 +30,7 @@ def _settings() -> Settings:
dataset_id="stt-v1",
runner_sha="test",
log_level="DEBUG",
fluxions_api_key=SecretStr("test-api-key"),
)


Expand Down Expand Up @@ -83,6 +85,7 @@ async def test_fluxions_tts_url_and_speak_frame(fluxions_settings: Settings) ->

def connect_side_effect(url: str, **kwargs: object) -> FakeWebSocket:
captured["url"] = url
captured["headers"] = kwargs.get("additional_headers")
return ws

provider = FluxionsTTSProvider(fluxions_settings, model="vui", voice=_VOICE)
Expand All @@ -95,6 +98,7 @@ def connect_side_effect(url: str, **kwargs: object) -> FakeWebSocket:

assert result.error is None
assert captured["url"] == "wss://api.fluxions.ai/vui/v1/tts/ws"
assert captured["headers"] == {"Authorization": "Bearer test-api-key"}
sent = [json.loads(m) for m in ws.sent if isinstance(m, str)]
assert sent == [
{
Expand Down Expand Up @@ -244,6 +248,12 @@ def test_fluxions_tts_missing_voice_raises(fluxions_settings: Settings) -> None:
FluxionsTTSProvider(fluxions_settings, model="vui", voice="")


def test_fluxions_tts_missing_api_key_raises(fluxions_settings: Settings) -> None:
fluxions_settings.fluxions_api_key = None
with pytest.raises(ValueError, match="fluxions_api_key"):
FluxionsTTSProvider(fluxions_settings, model="vui", voice=_VOICE)


def test_fluxions_tts_provider_name(fluxions_settings: Settings) -> None:
provider = FluxionsTTSProvider(fluxions_settings, model="vui", voice=_VOICE)
assert provider.name == "fluxions-vui"
Expand Down
Loading