fix(gateway): STT config resolution, stream consumer flood control fallback
Three targeted fixes from user-reported issues: 1. STT config resolution (transcription_tools.py): _has_openai_audio_backend() and _resolve_openai_audio_client_config() now check stt.openai.api_key/base_url in config.yaml FIRST, before falling back to env vars. Fixes voice transcription breaking when using a custom OpenAI-compatible endpoint via config.yaml. 2. Stream consumer flood control fallback (stream_consumer.py): When an edit fails mid-stream (e.g., Telegram flood control returns failure for waits >5s), reset _already_sent to False so the normal final send path delivers the complete response. Previously, a truncated partial was left as the final message. 3. Telegram edit_message comment alignment (telegram.py): Clarify that long flood waits return failure so streaming can fall back to a normal final send.
This commit is contained in:
@@ -901,9 +901,8 @@ class TelegramAdapter(BasePlatformAdapter):
|
||||
pass # best-effort truncation
|
||||
return SendResult(success=True, message_id=message_id)
|
||||
# Flood control / RetryAfter — short waits are retried inline,
|
||||
# long waits (>5s) return a failure so the caller can decide
|
||||
# whether to wait or degrade gracefully. (grammY auto-retry
|
||||
# pattern: maxDelaySeconds threshold.)
|
||||
# long waits return a failure immediately so streaming can fall back
|
||||
# to a normal final send instead of leaving a truncated partial.
|
||||
retry_after = getattr(e, "retry_after", None)
|
||||
if retry_after is not None or "retry after" in err_str:
|
||||
wait = retry_after if retry_after else 1.0
|
||||
@@ -912,12 +911,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
||||
self.name, wait,
|
||||
)
|
||||
if wait > 5.0:
|
||||
# Long wait — return failure immediately so callers
|
||||
# (progress edits, stream consumer) aren't blocked.
|
||||
return SendResult(
|
||||
success=False,
|
||||
error=f"flood_control:{wait}",
|
||||
)
|
||||
return SendResult(success=False, error=f"flood_control:{wait}")
|
||||
await asyncio.sleep(wait)
|
||||
try:
|
||||
await self._bot.edit_message_text(
|
||||
|
||||
@@ -174,12 +174,12 @@ class GatewayStreamConsumer:
|
||||
self._already_sent = True
|
||||
self._last_sent_text = text
|
||||
else:
|
||||
# Edit not supported by this adapter — stop streaming,
|
||||
# let the normal send path handle the final response.
|
||||
# Without this guard, adapters like Signal/Email would
|
||||
# flood the chat with a new message every edit_interval.
|
||||
# If an edit fails mid-stream (especially Telegram flood control),
|
||||
# stop progressive edits and let the normal final send path deliver
|
||||
# the complete answer instead of leaving the user with a partial.
|
||||
logger.debug("Edit failed, disabling streaming for this adapter")
|
||||
self._edit_supported = False
|
||||
self._already_sent = False
|
||||
else:
|
||||
# Editing not supported — skip intermediate updates.
|
||||
# The final response will be sent by the normal path.
|
||||
|
||||
@@ -127,8 +127,11 @@ def is_stt_enabled(stt_config: Optional[dict] = None) -> bool:
|
||||
|
||||
|
||||
def _has_openai_audio_backend() -> bool:
|
||||
"""Return True when OpenAI audio can use direct credentials or the managed gateway."""
|
||||
return bool(resolve_openai_audio_api_key() or resolve_managed_tool_gateway("openai-audio"))
|
||||
"""Return True when OpenAI audio can use config credentials, env credentials, or the managed gateway."""
|
||||
stt_config = _load_stt_config()
|
||||
openai_cfg = stt_config.get("openai", {})
|
||||
cfg_api_key = openai_cfg.get("api_key", "")
|
||||
return bool(cfg_api_key or resolve_openai_audio_api_key() or resolve_managed_tool_gateway("openai-audio"))
|
||||
|
||||
|
||||
def _find_binary(binary_name: str) -> Optional[str]:
|
||||
@@ -577,13 +580,20 @@ def transcribe_audio(file_path: str, model: Optional[str] = None) -> Dict[str, A
|
||||
|
||||
def _resolve_openai_audio_client_config() -> tuple[str, str]:
|
||||
"""Return direct OpenAI audio config or a managed gateway fallback."""
|
||||
stt_config = _load_stt_config()
|
||||
openai_cfg = stt_config.get("openai", {})
|
||||
cfg_api_key = openai_cfg.get("api_key", "")
|
||||
cfg_base_url = openai_cfg.get("base_url", "")
|
||||
if cfg_api_key:
|
||||
return cfg_api_key, (cfg_base_url or OPENAI_BASE_URL)
|
||||
|
||||
direct_api_key = resolve_openai_audio_api_key()
|
||||
if direct_api_key:
|
||||
return direct_api_key, OPENAI_BASE_URL
|
||||
|
||||
managed_gateway = resolve_managed_tool_gateway("openai-audio")
|
||||
if managed_gateway is None:
|
||||
message = "Neither VOICE_TOOLS_OPENAI_KEY nor OPENAI_API_KEY is set"
|
||||
message = "Neither stt.openai.api_key in config nor VOICE_TOOLS_OPENAI_KEY/OPENAI_API_KEY is set"
|
||||
if managed_nous_tools_enabled():
|
||||
message += ", and the managed OpenAI audio gateway is unavailable"
|
||||
raise ValueError(message)
|
||||
|
||||
Reference in New Issue
Block a user