fix(stt): resolve API keys from ~/.hermes/.env via get_env_value (#17140)

Widen #17163 to the sibling file tools/transcription_tools.py, which had
the same class of bug. STT provider call sites and the _get_provider
selection gate called os.getenv(...) directly and missed keys that only
lived in ~/.hermes/.env.

Same pattern as tts_tool.py: one guarded top-level import of
get_env_value (falls back to os.getenv on ImportError), then every
API-key and paired-base-URL lookup swapped over.

Call sites migrated:
- _transcribe_groq    — GROQ_API_KEY
- _transcribe_mistral — MISTRAL_API_KEY
- _transcribe_xai     — XAI_API_KEY, XAI_STT_BASE_URL
- _get_provider       — GROQ/MISTRAL/XAI_API_KEY in explicit + auto branches

Module-level defaults (DEFAULT_STT_MODEL, GROQ_BASE_URL, etc.) stay on
os.getenv — they're import-time constants, not runtime config, and the
dotenv fallback would add no value there.

New regression tests in tests/tools/test_transcription_dotenv_fallback.py
(8 cases) mirror briandevans' TTS tests: per-provider dotenv-key
forwarding, selection-gate dotenv visibility, and an end-to-end probe
that patches hermes_cli.config.load_env to simulate ~/.hermes/.env
carrying the key while os.environ does not.
This commit is contained in:
teknium1
2026-04-29 05:04:43 -07:00
committed by Teknium
parent 33967b4e52
commit 9e63062b6c
2 changed files with 224 additions and 10 deletions

View File

@@ -42,6 +42,12 @@ from tools.tool_backend_helpers import managed_nous_tools_enabled, resolve_opena
logger = logging.getLogger(__name__)
try:
from hermes_cli.config import get_env_value
except ImportError:
def get_env_value(name, default=None):
return os.getenv(name, default)
# ---------------------------------------------------------------------------
# Optional imports — graceful degradation
# ---------------------------------------------------------------------------
@@ -222,7 +228,7 @@ def _get_provider(stt_config: dict) -> str:
return "none"
if provider == "groq":
if _HAS_OPENAI and os.getenv("GROQ_API_KEY"):
if _HAS_OPENAI and get_env_value("GROQ_API_KEY"):
return "groq"
logger.warning(
"STT provider 'groq' configured but GROQ_API_KEY not set"
@@ -238,7 +244,7 @@ def _get_provider(stt_config: dict) -> str:
return "none"
if provider == "mistral":
if _HAS_MISTRAL and os.getenv("MISTRAL_API_KEY"):
if _HAS_MISTRAL and get_env_value("MISTRAL_API_KEY"):
return "mistral"
logger.warning(
"STT provider 'mistral' configured but mistralai package "
@@ -247,7 +253,7 @@ def _get_provider(stt_config: dict) -> str:
return "none"
if provider == "xai":
if os.getenv("XAI_API_KEY"):
if get_env_value("XAI_API_KEY"):
return "xai"
logger.warning(
"STT provider 'xai' configured but XAI_API_KEY not set"
@@ -262,16 +268,16 @@ def _get_provider(stt_config: dict) -> str:
return "local"
if _has_local_command():
return "local_command"
if _HAS_OPENAI and os.getenv("GROQ_API_KEY"):
if _HAS_OPENAI and get_env_value("GROQ_API_KEY"):
logger.info("No local STT available, using Groq Whisper API")
return "groq"
if _HAS_OPENAI and _has_openai_audio_backend():
logger.info("No local STT available, using OpenAI Whisper API")
return "openai"
if _HAS_MISTRAL and os.getenv("MISTRAL_API_KEY"):
if _HAS_MISTRAL and get_env_value("MISTRAL_API_KEY"):
logger.info("No local STT available, using Mistral Voxtral Transcribe API")
return "mistral"
if os.getenv("XAI_API_KEY"):
if get_env_value("XAI_API_KEY"):
logger.info("No local STT available, using xAI Grok STT API")
return "xai"
return "none"
@@ -527,7 +533,7 @@ def _transcribe_local_command(file_path: str, model_name: str) -> Dict[str, Any]
def _transcribe_groq(file_path: str, model_name: str) -> Dict[str, Any]:
"""Transcribe using Groq Whisper API (free tier available)."""
api_key = os.getenv("GROQ_API_KEY")
api_key = get_env_value("GROQ_API_KEY")
if not api_key:
return {"success": False, "transcript": "", "error": "GROQ_API_KEY not set"}
@@ -640,7 +646,7 @@ def _transcribe_mistral(file_path: str, model_name: str) -> Dict[str, Any]:
Uses the ``mistralai`` Python SDK to call ``/v1/audio/transcriptions``.
Requires ``MISTRAL_API_KEY`` environment variable.
"""
api_key = os.getenv("MISTRAL_API_KEY")
api_key = get_env_value("MISTRAL_API_KEY")
if not api_key:
return {"success": False, "transcript": "", "error": "MISTRAL_API_KEY not set"}
@@ -680,7 +686,7 @@ def _transcribe_xai(file_path: str, model_name: str) -> Dict[str, Any]:
Supports Inverse Text Normalization, diarization, and word-level timestamps.
Requires ``XAI_API_KEY`` environment variable.
"""
api_key = os.getenv("XAI_API_KEY")
api_key = get_env_value("XAI_API_KEY")
if not api_key:
return {"success": False, "transcript": "", "error": "XAI_API_KEY not set"}
@@ -688,7 +694,7 @@ def _transcribe_xai(file_path: str, model_name: str) -> Dict[str, Any]:
xai_config = stt_config.get("xai", {})
base_url = str(
xai_config.get("base_url")
or os.getenv("XAI_STT_BASE_URL")
or get_env_value("XAI_STT_BASE_URL")
or XAI_STT_BASE_URL
).strip().rstrip("/")
language = str(