feat(telegram): skip-STT audio path + 2GB cap via local Bot API server
Two coordinated changes that unblock downstream audio pipelines (diarization, custom transcription, archival) on attachments larger than the public Bot API's 20MB getFile ceiling. - `stt.enabled: false` no longer drops voice/audio with a generic "transcription disabled" note. The gateway probes the cached file's duration (wave → mutagen → ffprobe ladder) and surfaces `[The user sent a voice message: <abs path> (duration: M:SS)]` to the agent so a skill or tool can pick up the raw file. The previous placeholder is replaced rather than appended when present. - `platforms.telegram.extra.base_url` set → adapter auto-lifts its document size cap from 20MB to 2GB (the local telegram-bot-api `--local` ceiling) and the "too large" reply reports the active limit dynamically. No new config knob; presence of `base_url` is the opt-in. - `platforms.telegram.extra.local_mode: true` wires `Application.builder().local_mode(True)` on the python-telegram-bot builder. PTB then reads files from disk instead of HTTP, which is required when telegram-bot-api runs in `--local` mode (the server returns absolute filesystem paths, not `/file/bot...` URLs). - gateway/run.py: rewrites the `stt.enabled: false` branch of `_enrich_message_with_transcription`. New `_format_duration` + `_probe_audio_duration` helpers. - gateway/platforms/telegram.py: `_max_doc_bytes` instance attribute derived from `extra.base_url`; `local_mode` builder wiring; dynamic "too large" message. - tests/gateway/test_stt_config.py: covers path-surfacing with and without an existing user message, and placeholder replacement. - tests/gateway/test_telegram_max_doc_bytes.py: 3 cases — default 20MB without base_url, 2GB when set, empty-string base_url keeps default. - website/docs/user-guide/messaging/telegram.md: new "Skipping STT" subsection under Voice Messages and a full "Large Files (>20MB) via Local Bot API Server" walkthrough (api_id/api_hash, docker-compose, one-time `logOut` migration, `platforms.telegram.extra` config, the `local_mode` disk-access requirement, the silent HTTP-fallback 404). - website/docs/user-guide/features/voice-mode.md: documents the `stt.enabled` knob in the config reference. - `pytest tests/gateway/test_telegram_max_doc_bytes.py tests/gateway/test_stt_config.py` → 9/9 passing. - Verified end-to-end on a live deployment: gateway log shows `Using custom Telegram base_url: http://...` and `Using Telegram local_mode (read files from disk)` on startup; voice messages above 20MB cache to disk and surface their path to the agent. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
committed by
Teknium
co-authored by
Claude Opus 4.7
parent
6265b3a132
commit
ad2531be08
+71
-9
@@ -918,6 +918,59 @@ def _build_media_placeholder(event) -> str:
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
def _format_duration(seconds: float) -> str:
|
||||
total = int(round(seconds))
|
||||
if total < 0:
|
||||
total = 0
|
||||
hours, rem = divmod(total, 3600)
|
||||
minutes, secs = divmod(rem, 60)
|
||||
if hours:
|
||||
return f"{hours}:{minutes:02d}:{secs:02d}"
|
||||
return f"{minutes}:{secs:02d}"
|
||||
|
||||
|
||||
async def _probe_audio_duration(path: str) -> Optional[str]:
|
||||
"""Best-effort duration probe. Returns formatted MM:SS / HH:MM:SS, or None on failure."""
|
||||
ext = os.path.splitext(path)[1].lower()
|
||||
|
||||
if ext == ".wav":
|
||||
try:
|
||||
def _wav_duration() -> float:
|
||||
import wave
|
||||
with wave.open(path, "rb") as wf:
|
||||
frames = wf.getnframes()
|
||||
rate = wf.getframerate() or 1
|
||||
return frames / float(rate)
|
||||
secs = await asyncio.to_thread(_wav_duration)
|
||||
return _format_duration(secs)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if ext in (".ogg", ".opus", ".oga"):
|
||||
try:
|
||||
def _ogg_duration() -> float:
|
||||
from mutagen.oggopus import OggOpus
|
||||
return float(OggOpus(path).info.length)
|
||||
secs = await asyncio.to_thread(_ogg_duration)
|
||||
return _format_duration(secs)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"ffprobe", "-v", "error", "-show_entries", "format=duration",
|
||||
"-of", "default=noprint_wrappers=1:nokey=1", path,
|
||||
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=5.0)
|
||||
if proc.returncode == 0:
|
||||
return _format_duration(float(stdout.decode().strip()))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _dequeue_pending_event(adapter, session_key: str) -> MessageEvent | None:
|
||||
"""Consume and return the full pending event for a session.
|
||||
|
||||
@@ -14151,16 +14204,25 @@ class GatewayRunner:
|
||||
The enriched message string with transcriptions prepended.
|
||||
"""
|
||||
if not getattr(self.config, "stt_enabled", True):
|
||||
disabled_note = "[The user sent voice message(s), but transcription is disabled in config."
|
||||
if self._has_setup_skill():
|
||||
disabled_note += (
|
||||
" You have a skill called hermes-agent-setup that can help "
|
||||
"users configure Hermes features including voice, tools, and more."
|
||||
)
|
||||
disabled_note += "]"
|
||||
notes = []
|
||||
for path in audio_paths:
|
||||
abs_path = os.path.abspath(path)
|
||||
duration_str = await _probe_audio_duration(abs_path)
|
||||
if duration_str:
|
||||
notes.append(
|
||||
f"[The user sent a voice message: {abs_path} (duration: {duration_str})]"
|
||||
)
|
||||
else:
|
||||
notes.append(f"[The user sent a voice message: {abs_path}]")
|
||||
if not notes:
|
||||
return user_text
|
||||
prefix = "\n\n".join(notes)
|
||||
_placeholder = "(The user sent a message with no text content)"
|
||||
if user_text and user_text.strip() == _placeholder:
|
||||
return prefix
|
||||
if user_text:
|
||||
return f"{disabled_note}\n\n{user_text}"
|
||||
return disabled_note
|
||||
return f"{prefix}\n\n{user_text}"
|
||||
return prefix
|
||||
|
||||
from tools.transcription_tools import transcribe_audio
|
||||
|
||||
|
||||
Reference in New Issue
Block a user