Skip to content
Open
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
21 changes: 18 additions & 3 deletions astrbot/core/provider/sources/fishaudio_tts_api_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ class ServeTTSRequest(BaseModel):
latency: Literal["normal", "balanced"] = "normal"


DEFAULT_TIMEOUT_SECONDS = 20


@register_provider_adapter(
"fishaudio_tts_api",
"FishAudio TTS API",
Expand All @@ -57,10 +60,22 @@ def __init__(
"api_base",
"https://api.fish-audio.cn/v1",
)
raw_timeout = provider_config.get("timeout", DEFAULT_TIMEOUT_SECONDS)
try:
self.timeout: int = int(provider_config.get("timeout", 20))
except ValueError:
self.timeout = 20
timeout = int(raw_timeout)
except (TypeError, ValueError, OverflowError):
timeout = -1
if timeout <= 0:
# httpx reads a zero or negative timeout as "expire immediately", so
# every request fails with an empty ConnectTimeout. The dashboard
# writes 0 when this numeric field is cleared, and an empty value in
# a hand-edited config file is None.
logger.warning(
f"[FishAudio TTS] 无效的 timeout 配置 {raw_timeout!r},"
f"使用默认值 {DEFAULT_TIMEOUT_SECONDS}s。",
)
timeout = DEFAULT_TIMEOUT_SECONDS
self.timeout: int = timeout
self.proxy: str = provider_config.get("proxy", "")
if self.proxy:
logger.info(f"[FishAudio TTS] 使用代理: {self.proxy}")
Expand Down
44 changes: 44 additions & 0 deletions tests/test_fishaudio_tts_timeout_guard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""A cleared FishAudio ``timeout`` must not break every TTS request.

``timeout`` is a dashboard numeric field (``astrbot/core/config/default.py``
declares it with ``"type": "int"``), and the dashboard writes ``0`` when such a
field is cleared, while an empty ``timeout:`` in a hand-edited provider config
loads as ``None``. httpx reads a non-positive timeout as "expire immediately" -
measured with a local server that answers after 0.4s: ``AsyncClient(timeout=0)``
raises ``ConnectTimeout`` whose ``str()`` is empty, while ``timeout=20`` returns
200 - so ``get_audio`` failed on every synthesis with a blank error message, and
``None`` escaped the original ``except ValueError`` and aborted provider
construction with a bare ``TypeError``.
"""

import pytest

from astrbot.core.provider.sources.fishaudio_tts_api_source import (
ProviderFishAudioTTSAPI,
)

# The default documented in astrbot/core/config/default.py.
EXPECTED_DEFAULT = 20

BASE_CONFIG = {"api_key": "test-key", "model": "s2-pro"}


def _provider(**overrides) -> ProviderFishAudioTTSAPI:
return ProviderFishAudioTTSAPI({**BASE_CONFIG, **overrides}, {})


@pytest.mark.parametrize(
"timeout",
[0, -5, 0.4, None, "", "abc", float("inf")],
)
def test_unusable_timeout_falls_back_to_default(timeout):
assert _provider(timeout=timeout).timeout == EXPECTED_DEFAULT


@pytest.mark.parametrize("timeout", [45, "45", 45.9])
def test_positive_timeout_is_kept(timeout):
assert _provider(timeout=timeout).timeout == 45


def test_missing_timeout_uses_default():
assert _provider().timeout == EXPECTED_DEFAULT
Loading