mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
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:
parent
2a4b1787c8
commit
805c1c340c
11 changed files with 83 additions and 11 deletions
|
|
@ -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'],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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]"`.
|
||||
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
||||
|
|
|
|||
|
|
@ -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" # 旧版回退键仍受支持
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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 API(Voxtral 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]"` 安装。
|
||||
|
||||
|
|
|
|||
|
|
@ -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**
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue