fix: scope fal providers and Google TTS networking

This commit is contained in:
calesthio
2026-08-13 09:22:11 -07:00
parent b3434affe3
commit cffc18308f
4 changed files with 37 additions and 61 deletions

View File

@@ -192,7 +192,7 @@ The ASR tool (`qwen3-asr-flash-filetrans`) uses an async submit-poll pattern. Au
> **Broad single-key coverage.** One API key unlocks image and video providers across multiple models.
**Tools unlocked:** `flux_image`, `recraft_image`, `kling_video`, `veo_video`, `minimax_video`
**Tools unlocked:** `flux_image`, `recraft_image`, `kling_video`, `veo_video`, `minimax_video`, `fal_elevenlabs_tts`, `fal_elevenlabs_music`
**Env var:** `FAL_KEY`
#### Setup
@@ -225,6 +225,10 @@ No subscription — pure pay-as-you-go, no minimum spend.
**Free tier:** None — but $0 to start, you only pay for what you use.
The same key can also access ElevenLabs speech and music through fal.ai. Use
`fal_elevenlabs_tts` when direct ElevenLabs credentials are unavailable, or
select it through `tts_selector` with `preferred_provider: "fal.ai"`.
---
### Kling Official — Direct API

View File

@@ -37,6 +37,32 @@ def test_registry_discovers_fal_tts(monkeypatch):
assert tool.get_status() == ToolStatus.AVAILABLE
def test_tts_selector_routes_to_fal_provider(monkeypatch):
from tools.audio.tts_selector import TTSSelector
from tools.base_tool import ToolResult
monkeypatch.setenv("FAL_KEY", "test-key")
tool = FalElevenLabsTTS()
selector = TTSSelector()
monkeypatch.setattr(selector, "_providers", lambda: [tool])
monkeypatch.setattr(
selector,
"_select_best_tool",
lambda _inputs, _candidates, _context: (tool, None),
)
monkeypatch.setattr(
tool,
"execute",
lambda inputs: ToolResult(success=True, data={"received": inputs}),
)
result = selector.execute(
{"text": "hello", "preferred_provider": "fal.ai", "voice_id": "Rachel"}
)
assert result.success
assert result.data["selected_tool"] == "fal_elevenlabs_tts"
assert result.data["selected_provider"] == "fal.ai"
def test_execute_submits_once_and_downloads_audio(tmp_path, monkeypatch):
monkeypatch.setenv("FAL_KEY", "test-key")
output_path = tmp_path / "speech.mp3"

View File

@@ -34,26 +34,3 @@ def test_tts_key_uses_header_and_is_redacted_from_errors(monkeypatch, tmp_path):
assert result.success is False
assert secret not in result.error
assert "[REDACTED]" in result.error
def test_production_adapter_can_force_google_tts_to_ipv4(monkeypatch, tmp_path):
import socket
import requests
import urllib3.util.connection
monkeypatch.setenv("GOOGLE_TTS_API_KEY", "test-key")
monkeypatch.setenv("GOOGLE_TTS_FORCE_IPV4", "1")
original = urllib3.util.connection.allowed_gai_family
def inspect_request(url, **kwargs):
assert urllib3.util.connection.allowed_gai_family() == socket.AF_INET
raise requests.HTTPError("safe expected failure")
monkeypatch.setattr(requests, "post", inspect_request)
result = GoogleTTS().execute(
{"text": "safe test sentence", "output_path": str(tmp_path / "speech.mp3")}
)
assert result.success is False
assert urllib3.util.connection.allowed_gai_family is original

View File

@@ -8,10 +8,7 @@ from __future__ import annotations
import base64
import os
import socket
import threading
import time
from contextlib import contextmanager
from pathlib import Path
from typing import Any
@@ -33,32 +30,6 @@ from tools.google_credentials import (
)
_IPV4_REQUEST_LOCK = threading.Lock()
@contextmanager
def _google_tts_network_family():
"""Use IPv4 when the deployment key is restricted to the server's IPv4."""
force_ipv4 = os.environ.get("GOOGLE_TTS_FORCE_IPV4", "").lower() in {
"1",
"true",
"yes",
}
if not force_ipv4:
yield
return
import urllib3.util.connection
with _IPV4_REQUEST_LOCK:
original = urllib3.util.connection.allowed_gai_family
urllib3.util.connection.allowed_gai_family = lambda: socket.AF_INET
try:
yield
finally:
urllib3.util.connection.allowed_gai_family = original
class GoogleTTS(BaseTool):
name = "google_tts"
version = "0.1.0"
@@ -76,7 +47,6 @@ class GoogleTTS(BaseTool):
" GOOGLE_API_KEY or GEMINI_API_KEY remain supported for broader Google setups.\n"
" Google Cloud API key with Text-to-Speech enabled.\n"
" Enable the API at https://console.cloud.google.com/apis/library/texttospeech.googleapis.com\n"
" Set GOOGLE_TTS_FORCE_IPV4=1 only when the key is restricted to the caller's IPv4.\n"
"Auth option B — service account: set GOOGLE_APPLICATION_CREDENTIALS to the\n"
" path of a service-account JSON key (needs the 'google-auth' package)."
)
@@ -309,13 +279,12 @@ class GoogleTTS(BaseTool):
elif api_key:
headers["x-goog-api-key"] = api_key
with _google_tts_network_family():
response = requests.post(
url,
headers=headers,
json=payload,
timeout=120,
)
response = requests.post(
url,
headers=headers,
json=payload,
timeout=120,
)
response.raise_for_status()
audio_content = base64.b64decode(response.json()["audioContent"])