feat(stt): support OpenAI gpt-transcribe transcription model

Adds gpt-transcribe (OpenAI's new file-transcription model, $0.0045/min)
to the OpenAI STT provider:

- OPENAI_MODELS set: gpt-transcribe is recognized so provider
  auto-correction keeps it on OpenAI and rejects it on Groq
- Language hint wiring: gpt-transcribe replaces the singular
  'language' field with a 'languages' list; the API rejects the legacy
  field, so the hint is sent via extra_body {languages: [..]}
- Config comment (DEFAULT_CONFIG), cli-config.yaml.example, desktop
  settings enum, and docs (en + zh-Hans) updated
- Tests: model pass-through, languages-list hint shape, legacy singular
  hint preserved for gpt-4o-transcribe, Groq auto-correction

gpt-live-transcribe (realtime WebSocket, $0.017/min) is NOT wired here:
the file-based STT pipeline has no realtime session path; it belongs in
a future realtime/voice-mode integration.
This commit is contained in:
Teknium 2026-07-28 21:37:21 -07:00
parent 2a4b1787c8
commit 805c1c340c
11 changed files with 83 additions and 11 deletions

View file

@ -349,7 +349,7 @@ export const ENUM_OPTIONS: Record<string, string[]> = {
'kittentts',
'piper'
],
'stt.openai.model': ['whisper-1', 'gpt-4o-mini-transcribe', 'gpt-4o-transcribe'],
'stt.openai.model': ['whisper-1', 'gpt-4o-mini-transcribe', 'gpt-4o-transcribe', 'gpt-transcribe'],
'stt.mistral.model': ['voxtral-mini-latest', 'voxtral-mini-2602'],
'tts.openai.model': ['gpt-4o-mini-tts', 'tts-1', 'tts-1-hd'],
'tts.elevenlabs.model_id': ['eleven_multilingual_v2', 'eleven_turbo_v2_5', 'eleven_flash_v2_5'],

View file

@ -1143,7 +1143,7 @@ stt:
# model: "whisper-large-v3-turbo"
# language: "" # blank = stt.language > HERMES_LOCAL_STT_LANGUAGE > auto-detect
openai:
model: "whisper-1" # whisper-1 | gpt-4o-mini-transcribe | gpt-4o-transcribe
model: "whisper-1" # whisper-1 | gpt-4o-mini-transcribe | gpt-4o-transcribe | gpt-transcribe
language: "" # auto-detect; set to "en", "es", "fr", etc. to force
# mistral:
# model: "voxtral-mini-latest" # voxtral-mini-latest | voxtral-mini-2602

View file

@ -2338,7 +2338,7 @@ DEFAULT_CONFIG = {
"language": "", # auto-detect by default; set to "en", "es", "fr", etc. to force
},
"openai": {
"model": "whisper-1", # whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe
"model": "whisper-1", # whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe, gpt-transcribe
"language": "", # auto-detect by default; set to "en", "es", "fr", etc. to force
},
"mistral": {

View file

@ -1068,6 +1068,70 @@ class TestModelAutoCorrection:
call_kwargs = mock_client.audio.transcriptions.create.call_args
assert call_kwargs.kwargs["model"] == "gpt-4o-mini-transcribe"
def test_gpt_transcribe_model_not_overridden(self, monkeypatch, sample_wav):
monkeypatch.setenv("VOICE_TOOLS_OPENAI_KEY", "sk-test")
mock_client = MagicMock()
mock_client.audio.transcriptions.create.return_value = "test"
with patch("tools.transcription_tools._HAS_OPENAI", True), \
patch("openai.OpenAI", return_value=mock_client):
from tools.transcription_tools import _transcribe_openai
_transcribe_openai(sample_wav, "gpt-transcribe")
call_kwargs = mock_client.audio.transcriptions.create.call_args
assert call_kwargs.kwargs["model"] == "gpt-transcribe"
assert call_kwargs.kwargs["response_format"] == "json"
def test_gpt_transcribe_language_hint_uses_languages_list(self, monkeypatch, sample_wav):
"""gpt-transcribe rejects the singular ``language`` field; the hint
must be sent as a ``languages`` list via extra_body instead."""
monkeypatch.setenv("VOICE_TOOLS_OPENAI_KEY", "sk-test")
mock_client = MagicMock()
mock_client.audio.transcriptions.create.return_value = "test"
with patch("tools.transcription_tools._HAS_OPENAI", True), \
patch("openai.OpenAI", return_value=mock_client), \
patch("tools.transcription_tools._resolve_stt_language", return_value="fr"):
from tools.transcription_tools import _transcribe_openai
_transcribe_openai(sample_wav, "gpt-transcribe")
call_kwargs = mock_client.audio.transcriptions.create.call_args
assert "language" not in call_kwargs.kwargs
assert call_kwargs.kwargs["extra_body"] == {"languages": ["fr"]}
def test_legacy_openai_model_language_hint_uses_singular_field(self, monkeypatch, sample_wav):
monkeypatch.setenv("VOICE_TOOLS_OPENAI_KEY", "sk-test")
mock_client = MagicMock()
mock_client.audio.transcriptions.create.return_value = "test"
with patch("tools.transcription_tools._HAS_OPENAI", True), \
patch("openai.OpenAI", return_value=mock_client), \
patch("tools.transcription_tools._resolve_stt_language", return_value="fr"):
from tools.transcription_tools import _transcribe_openai
_transcribe_openai(sample_wav, "gpt-4o-transcribe")
call_kwargs = mock_client.audio.transcriptions.create.call_args
assert call_kwargs.kwargs["language"] == "fr"
assert "extra_body" not in call_kwargs.kwargs
def test_gpt_transcribe_rejected_on_groq(self, monkeypatch, sample_wav):
"""gpt-transcribe is OpenAI-only and must be auto-corrected on Groq."""
monkeypatch.setenv("GROQ_API_KEY", "gsk-test")
mock_client = MagicMock()
mock_client.audio.transcriptions.create.return_value = "test"
with patch("tools.transcription_tools._HAS_OPENAI", True), \
patch("openai.OpenAI", return_value=mock_client):
from tools.transcription_tools import _transcribe_groq, DEFAULT_GROQ_STT_MODEL
_transcribe_groq(sample_wav, "gpt-transcribe")
call_kwargs = mock_client.audio.transcriptions.create.call_args
assert call_kwargs.kwargs["model"] == DEFAULT_GROQ_STT_MODEL
def test_unknown_model_passes_through_groq(self, monkeypatch, sample_wav):
"""A model not in either known set should not be overridden."""
monkeypatch.setenv("GROQ_API_KEY", "gsk-test")

View file

@ -128,7 +128,7 @@ LOCAL_NATIVE_AUDIO_FORMATS = {".wav", ".aiff", ".aif"}
MAX_FILE_SIZE = 25 * 1024 * 1024 # 25 MB
# Known model sets for auto-correction
OPENAI_MODELS = {"whisper-1", "gpt-4o-mini-transcribe", "gpt-4o-transcribe"}
OPENAI_MODELS = {"whisper-1", "gpt-4o-mini-transcribe", "gpt-4o-transcribe", "gpt-transcribe"}
GROQ_MODELS = {"whisper-large-v3", "whisper-large-v3-turbo", "distil-whisper-large-v3-en"}
# Singleton for the local model — loaded once, reused across calls
@ -1824,7 +1824,13 @@ def _transcribe_openai(
"response_format": "text" if model_name == "whisper-1" else "json",
}
if language:
create_kwargs["language"] = language
if model_name == "gpt-transcribe":
# gpt-transcribe replaces the singular ``language``
# field with a ``languages`` list; the API rejects
# requests that send the legacy field.
create_kwargs["extra_body"] = {"languages": [language]}
else:
create_kwargs["language"] = language
logger.debug("Using language hint '%s' for OpenAI STT", language)
return client.audio.transcriptions.create(**create_kwargs)

View file

@ -1721,7 +1721,7 @@ stt:
groq:
language: "" # per-provider override of stt.language
openai:
model: "whisper-1" # whisper-1 | gpt-4o-mini-transcribe | gpt-4o-transcribe
model: "whisper-1" # whisper-1 | gpt-4o-mini-transcribe | gpt-4o-transcribe | gpt-transcribe
language: "" # per-provider override of stt.language
# model: "whisper-1" # Legacy fallback key still respected
```

View file

@ -465,7 +465,7 @@ stt:
groq:
language: "" # optional ISO-639-1 hint; blank = use HERMES_LOCAL_STT_LANGUAGE if set, else auto-detect
openai:
model: "whisper-1" # whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe
model: "whisper-1" # whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe, gpt-transcribe
mistral:
model: "voxtral-mini-latest" # voxtral-mini-latest, voxtral-mini-2602
xai:
@ -487,7 +487,7 @@ stt:
**Groq API** — Requires `GROQ_API_KEY`. Good cloud fallback when you want a free hosted STT option. Set `stt.groq.language` (or the global `HERMES_LOCAL_STT_LANGUAGE` env var) to skip Whisper's auto-detect and reduce latency on known-language audio.
**OpenAI API** — Accepts `VOICE_TOOLS_OPENAI_KEY` first and falls back to `OPENAI_API_KEY`. Supports `whisper-1`, `gpt-4o-mini-transcribe`, and `gpt-4o-transcribe`.
**OpenAI API** — Accepts `VOICE_TOOLS_OPENAI_KEY` first and falls back to `OPENAI_API_KEY`. Supports `whisper-1`, `gpt-4o-mini-transcribe`, `gpt-4o-transcribe`, and `gpt-transcribe`.
**Mistral API (Voxtral Transcribe)** — Requires `MISTRAL_API_KEY`. Uses Mistral's [Voxtral Transcribe](https://docs.mistral.ai/capabilities/audio/speech_to_text/) models. Supports 13 languages, speaker diarization, and word-level timestamps. Install with `cd ~/.hermes/hermes-agent && uv pip install -e ".[mistral]"`.

View file

@ -484,6 +484,7 @@ DISCORD_ALLOWED_USERS=...
| **Groq** | `whisper-large-v3` | Fast (~1s) | Better | Free tier | Yes |
| **OpenAI** | `whisper-1` | Fast (~1s) | Good | Paid | Yes |
| **OpenAI** | `gpt-4o-transcribe` | Medium (~2s) | Best | Paid | Yes |
| **OpenAI** | `gpt-transcribe` | Fast | Best | Paid ($0.0045/min) | Yes |
| **Mistral** | `voxtral-mini-latest` | Fast | Good | Paid | Yes |
| **xAI** | `grok-stt` | Fast | Good | Paid | Yes |

View file

@ -1272,7 +1272,7 @@ stt:
local:
model: "base" # tiny、base、small、medium、large-v3
openai:
model: "whisper-1" # whisper-1 | gpt-4o-mini-transcribe | gpt-4o-transcribe
model: "whisper-1" # whisper-1 | gpt-4o-mini-transcribe | gpt-4o-transcribe | gpt-transcribe
# model: "whisper-1" # 旧版回退键仍受支持
```

View file

@ -399,7 +399,7 @@ stt:
local:
model: "base" # tiny, base, small, medium, large-v3
openai:
model: "whisper-1" # whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe
model: "whisper-1" # whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe, gpt-transcribe
mistral:
model: "voxtral-mini-latest" # voxtral-mini-latest, voxtral-mini-2602
xai:
@ -420,7 +420,7 @@ stt:
**Groq API** — 需要 `GROQ_API_KEY`。当你需要免费托管 STT 选项时,是良好的云端备选方案。
**OpenAI API** — 优先使用 `VOICE_TOOLS_OPENAI_KEY`,回退至 `OPENAI_API_KEY`。支持 `whisper-1``gpt-4o-mini-transcribe``gpt-4o-transcribe`。
**OpenAI API** — 优先使用 `VOICE_TOOLS_OPENAI_KEY`,回退至 `OPENAI_API_KEY`。支持 `whisper-1``gpt-4o-mini-transcribe``gpt-4o-transcribe``gpt-transcribe`。
**Mistral APIVoxtral Transcribe** — 需要 `MISTRAL_API_KEY`。使用 Mistral 的 [Voxtral Transcribe](https://docs.mistral.ai/capabilities/audio/speech_to_text/) 模型。支持 13 种语言、说话人分离和词级时间戳。通过 `cd ~/.hermes/hermes-agent && uv pip install -e ".[mistral]"` 安装。

View file

@ -458,6 +458,7 @@ DISCORD_ALLOWED_USERS=...
| **Groq** | `whisper-large-v3` | 快(约 1 秒) | 较好 | 免费额度 | 是 |
| **OpenAI** | `whisper-1` | 快(约 1 秒) | 良好 | 付费 | 是 |
| **OpenAI** | `gpt-4o-transcribe` | 中等(约 2 秒) | 最佳 | 付费 | 是 |
| **OpenAI** | `gpt-transcribe` | 快 | 最佳 | 付费($0.0045/分钟) | 是 |
提供商优先级(自动回退):**本地** > **groq** > **openai**