mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 05:17:15 +00:00
refactor(gateway): move the voice helpers into the transcription mixin
655aef56d89 moved the transcription methods into GatewayInboundTranscriptionMixin but left _prepend_media_prefix (with its _EMPTY_TEXT_PLACEHOLDER), _untranscribed_audio_note and _transcribe_one_clip in run_inbound.py, though only the mixin calls them. They move unchanged; asyncio is imported for _transcribe_one_clip. No behaviour change: the voice/STT suites pass (14 files, 260 tests). Review finding by @kshitijk4poor on #140973 Refs #134889 (MSG-22) Refs #140719
This commit is contained in:
1 parent
7cf9ce7d80
commit
653c79bb5a
2 files changed
+44
-43
No files matched your search
@@ -1838,46 +1838,3 @@ class GatewayInboundMixin(GatewayInboundHooksMixin, GatewayInboundTranscriptionM
|
||||
return user_text
|
||||
prefix = "\n\n".join(enriched_parts)
|
||||
return f"{prefix}\n\n{user_text}" if user_text else prefix
|
||||
|
||||
_EMPTY_TEXT_PLACEHOLDER = "(The user sent a message with no text content)"
|
||||
|
||||
@classmethod
|
||||
def _prepend_media_prefix(cls, prefix: str, user_text: str) -> str:
|
||||
"""``prefix`` + the user's text; the Discord empty-content placeholder is dropped as redundant."""
|
||||
if user_text and user_text.strip() != cls._EMPTY_TEXT_PLACEHOLDER:
|
||||
return f"{prefix}\n\n{user_text}"
|
||||
return prefix
|
||||
|
||||
@staticmethod
|
||||
def _untranscribed_audio_note(path: str) -> str:
|
||||
"""One minimal neutral marker for every STT failure. Never mention "no STT provider" or setup
|
||||
steps — persisted in history they make the model keep volunteering STT-setup advice."""
|
||||
from tools.credential_files import to_agent_visible_cache_path
|
||||
agent_path = to_agent_visible_cache_path(os.path.abspath(path))
|
||||
return f"[voice message could not be transcribed automatically; the audio is available at: {agent_path}]"
|
||||
|
||||
async def _transcribe_one_clip(self, path: str, transcribe_audio, transcribe_audio_local_fallback) -> tuple[Optional[str], str]:
|
||||
"""``(transcript_or_None, note)`` for one clip via configured STT with local fallback."""
|
||||
result = await asyncio.to_thread(transcribe_audio, path, None, "gateway")
|
||||
if not result.get("success"):
|
||||
fallback = await asyncio.to_thread(transcribe_audio_local_fallback, path)
|
||||
if fallback.get("success"):
|
||||
logger.info("Configured STT failed for %s; recovered with local STT", path)
|
||||
result = fallback
|
||||
if not result["success"]:
|
||||
logger.info("Voice transcription failed for %s: %s", path, result.get("error", "unknown error"))
|
||||
return None, self._untranscribed_audio_note(path)
|
||||
transcript = result["transcript"]
|
||||
# STT may return success=True with an empty/whitespace transcript (silence, cut-off);
|
||||
# empty quotes make the agent reply to nothing and can loop, so emit a sentinel note.
|
||||
# See #41603.
|
||||
if not (transcript or "").strip():
|
||||
return None, (
|
||||
"[The user sent a voice message but it came through "
|
||||
"empty or inaudible — speech-to-text returned no "
|
||||
"words. Do not guess at the content; ask the user "
|
||||
"to resend or type it out.]"
|
||||
)
|
||||
# Plain quoted line: a "The user sent a voice message..." wrapper read as a meta-instruction
|
||||
# and made the LLM comment on voice mode instead.
|
||||
return transcript, f'"{transcript}"'
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional
|
||||
@@ -141,3 +142,46 @@ class GatewayInboundTranscriptionMixin:
|
||||
except Exception as trans_exc:
|
||||
logger.warning("%s transcription failed: %s", log_context, trans_exc)
|
||||
return text, []
|
||||
|
||||
_EMPTY_TEXT_PLACEHOLDER = "(The user sent a message with no text content)"
|
||||
|
||||
@classmethod
|
||||
def _prepend_media_prefix(cls, prefix: str, user_text: str) -> str:
|
||||
"""``prefix`` + the user's text; the Discord empty-content placeholder is dropped as redundant."""
|
||||
if user_text and user_text.strip() != cls._EMPTY_TEXT_PLACEHOLDER:
|
||||
return f"{prefix}\n\n{user_text}"
|
||||
return prefix
|
||||
|
||||
@staticmethod
|
||||
def _untranscribed_audio_note(path: str) -> str:
|
||||
"""One minimal neutral marker for every STT failure. Never mention "no STT provider" or setup
|
||||
steps — persisted in history they make the model keep volunteering STT-setup advice."""
|
||||
from tools.credential_files import to_agent_visible_cache_path
|
||||
agent_path = to_agent_visible_cache_path(os.path.abspath(path))
|
||||
return f"[voice message could not be transcribed automatically; the audio is available at: {agent_path}]"
|
||||
|
||||
async def _transcribe_one_clip(self, path: str, transcribe_audio, transcribe_audio_local_fallback) -> tuple[Optional[str], str]:
|
||||
"""``(transcript_or_None, note)`` for one clip via configured STT with local fallback."""
|
||||
result = await asyncio.to_thread(transcribe_audio, path, None, "gateway")
|
||||
if not result.get("success"):
|
||||
fallback = await asyncio.to_thread(transcribe_audio_local_fallback, path)
|
||||
if fallback.get("success"):
|
||||
logger.info("Configured STT failed for %s; recovered with local STT", path)
|
||||
result = fallback
|
||||
if not result["success"]:
|
||||
logger.info("Voice transcription failed for %s: %s", path, result.get("error", "unknown error"))
|
||||
return None, self._untranscribed_audio_note(path)
|
||||
transcript = result["transcript"]
|
||||
# STT may return success=True with an empty/whitespace transcript (silence, cut-off);
|
||||
# empty quotes make the agent reply to nothing and can loop, so emit a sentinel note.
|
||||
# See #41603.
|
||||
if not (transcript or "").strip():
|
||||
return None, (
|
||||
"[The user sent a voice message but it came through "
|
||||
"empty or inaudible — speech-to-text returned no "
|
||||
"words. Do not guess at the content; ask the user "
|
||||
"to resend or type it out.]"
|
||||
)
|
||||
# Plain quoted line: a "The user sent a voice message..." wrapper read as a meta-instruction
|
||||
# and made the LLM comment on voice mode instead.
|
||||
return transcript, f'"{transcript}"'
|
||||
Reference in new issue
Block a user