Skip to content
Closed
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
1 change: 1 addition & 0 deletions cli-config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -1006,6 +1006,7 @@ stt:
local:
model: "base" # tiny | base | small | medium | large-v3 | turbo
# language: "" # auto-detect; set to "en", "es", "fr", etc. to force
# initial_prompt: "" # Optional faster-whisper prompt, e.g. bias Chinese output to simplified Chinese
Comment thread
BlackishGreen33 marked this conversation as resolved.
openai:
model: "whisper-1" # whisper-1 | gpt-4o-mini-transcribe | gpt-4o-transcribe
# mistral:
Expand Down
1 change: 1 addition & 0 deletions hermes_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2132,6 +2132,7 @@ def _ensure_hermes_home_managed(home: Path):
"local": {
"model": "base", # tiny, base, small, medium, large-v3
"language": "", # auto-detect by default; set to "en", "es", "fr", etc. to force
"initial_prompt": "",
},
"openai": {
"model": "whisper-1", # whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe
Expand Down
87 changes: 87 additions & 0 deletions tests/tools/test_transcription.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,27 @@ def test_too_large(self, tmp_path):
assert "too large" in result["error"]


# ---------------------------------------------------------------------------
# Config resolution
# ---------------------------------------------------------------------------


class TestLoadSttConfig:

def test_merges_default_local_initial_prompt(self, monkeypatch, tmp_path):
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
(tmp_path / "config.yaml").write_text(
"stt:\n local:\n model: small\n",
encoding="utf-8",
)

from tools.transcription_tools import _load_stt_config
local_config = _load_stt_config()["local"]

assert local_config["model"] == "small"
assert local_config["initial_prompt"] == ""


# ---------------------------------------------------------------------------
# Local transcription
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -152,6 +173,72 @@ def test_successful_transcription(self, tmp_path):
assert result["success"] is True
assert result["transcript"] == "Hello world"

def test_passes_initial_prompt_when_configured(self, tmp_path):
audio_file = tmp_path / "test.ogg"
audio_file.write_bytes(b"fake audio")

mock_info = MagicMock(language="zh", duration=2.5)
mock_model = MagicMock()
mock_model.transcribe.return_value = ([], mock_info)

fake_fw = _fake_faster_whisper_module(mock_model)
with patch("tools.transcription_tools._HAS_FASTER_WHISPER", True), \
patch("tools.transcription_tools._load_stt_config", return_value={
"local": {"initial_prompt": "以下是普通话的句子,使用简体中文。"},
}), \
patch.dict("sys.modules", {"faster_whisper": fake_fw}), \
patch("tools.transcription_tools._local_model", None):
from tools.transcription_tools import _transcribe_local
result = _transcribe_local(str(audio_file), "base")

assert result["success"] is True
assert mock_model.transcribe.call_args.kwargs["initial_prompt"] == (
"以下是普通话的句子,使用简体中文。"
)

def test_omits_blank_initial_prompt(self, tmp_path):
audio_file = tmp_path / "test.ogg"
audio_file.write_bytes(b"fake audio")

mock_info = MagicMock(language="en", duration=2.5)
mock_model = MagicMock()
mock_model.transcribe.return_value = ([], mock_info)

fake_fw = _fake_faster_whisper_module(mock_model)
with patch("tools.transcription_tools._HAS_FASTER_WHISPER", True), \
patch("tools.transcription_tools._load_stt_config", return_value={
"local": {"initial_prompt": " "},
}), \
patch.dict("sys.modules", {"faster_whisper": fake_fw}), \
patch("tools.transcription_tools._local_model", None):
from tools.transcription_tools import _transcribe_local
result = _transcribe_local(str(audio_file), "base")

assert result["success"] is True
assert "initial_prompt" not in mock_model.transcribe.call_args.kwargs

def test_accepts_null_local_config(self, monkeypatch, tmp_path):
monkeypatch.delenv("HERMES_LOCAL_STT_LANGUAGE", raising=False)
audio_file = tmp_path / "test.ogg"
audio_file.write_bytes(b"fake audio")

mock_info = MagicMock(language="en", duration=2.5)
mock_model = MagicMock()
mock_model.transcribe.return_value = ([], mock_info)

fake_fw = _fake_faster_whisper_module(mock_model)
with patch("tools.transcription_tools._HAS_FASTER_WHISPER", True), \
patch("tools.transcription_tools._load_stt_config", return_value={
"local": None,
}), \
patch.dict("sys.modules", {"faster_whisper": fake_fw}), \
patch("tools.transcription_tools._local_model", None):
from tools.transcription_tools import _transcribe_local
result = _transcribe_local(str(audio_file), "base")

assert result["success"] is True
assert mock_model.transcribe.call_args.kwargs == {"beam_size": 5}

def test_not_installed(self):
with patch("tools.transcription_tools._HAS_FASTER_WHISPER", False):
from tools.transcription_tools import _transcribe_local
Expand Down
6 changes: 5 additions & 1 deletion tools/transcription_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -1129,14 +1129,18 @@ def _transcribe_local(file_path: str, model_name: str) -> Dict[str, Any]:
_local_model_name = model_name

# Language: config.yaml (stt.local.language) > env var > auto-detect.
local_config = _load_stt_config().get("local") or {}
_forced_lang = (
(_load_stt_config().get("local") or {}).get("language")
local_config.get("language")
or os.getenv(LOCAL_STT_LANGUAGE_ENV)
or None
)
transcribe_kwargs = {"beam_size": 5}
if _forced_lang:
transcribe_kwargs["language"] = _forced_lang
initial_prompt = local_config.get("initial_prompt")
if isinstance(initial_prompt, str) and initial_prompt.strip():
transcribe_kwargs["initial_prompt"] = initial_prompt

try:
segments, info = _local_model.transcribe(file_path, **transcribe_kwargs)
Expand Down
Loading