mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-29 18:46:59 +00:00
feat(xai-tts): wire text_normalization parameter
xAI TTS supports a text_normalization boolean that normalizes written-form text (numbers, abbreviations, symbols) into spoken-form before synthesis. This parameter was not being sent, leaving users with literal number/symbol pronunciation. - Add DEFAULT_XAI_TEXT_NORMALIZATION_DEFAULT = False - Read tts.xai.text_normalization from config (via _xai_bool_config) - Attach text_normalization: true to the POST payload when enabled - Omit the field entirely when unset or explicitly false (API default) - Add 3 tests: default omission, enabled passthrough, explicit false
This commit is contained in:
parent
462b3cf994
commit
d1e9344716
2 changed files with 87 additions and 0 deletions
|
|
@ -580,3 +580,77 @@ def test_generate_xai_tts_provider_speed_overrides_global(tmp_path, monkeypatch)
|
|||
)
|
||||
|
||||
assert captured["json"]["speed"] == 0.7
|
||||
|
||||
|
||||
def test_generate_xai_tts_omits_text_normalization_by_default(tmp_path, monkeypatch):
|
||||
"""text_normalization is not sent when unset (API default is False)."""
|
||||
captured = {}
|
||||
|
||||
fake_response = Mock()
|
||||
fake_response.content = b"mp3"
|
||||
fake_response.raise_for_status.return_value = None
|
||||
|
||||
def fake_post(url, headers, json, timeout):
|
||||
captured["json"] = json
|
||||
return fake_response
|
||||
|
||||
monkeypatch.setenv("XAI_API_KEY", "test-xai-key")
|
||||
monkeypatch.setattr("requests.post", fake_post)
|
||||
|
||||
_generate_xai_tts(
|
||||
"Hello world.",
|
||||
str(tmp_path / "out.mp3"),
|
||||
{"xai": {"voice_id": "ara", "language": "en"}},
|
||||
)
|
||||
|
||||
assert "text_normalization" not in captured["json"]
|
||||
|
||||
|
||||
def test_generate_xai_tts_sends_text_normalization_when_enabled(tmp_path, monkeypatch):
|
||||
"""tts.xai.text_normalization: true flows into the POST body."""
|
||||
captured = {}
|
||||
|
||||
fake_response = Mock()
|
||||
fake_response.content = b"mp3"
|
||||
fake_response.raise_for_status.return_value = None
|
||||
|
||||
def fake_post(url, headers, json, timeout):
|
||||
captured["json"] = json
|
||||
return fake_response
|
||||
|
||||
monkeypatch.setenv("XAI_API_KEY", "test-xai-key")
|
||||
monkeypatch.setattr("requests.post", fake_post)
|
||||
|
||||
_generate_xai_tts(
|
||||
"Hello world.",
|
||||
str(tmp_path / "out.mp3"),
|
||||
{"xai": {"voice_id": "ara", "language": "en", "text_normalization": True}},
|
||||
)
|
||||
|
||||
assert captured["json"]["text_normalization"] is True
|
||||
|
||||
|
||||
def test_generate_xai_tts_omits_text_normalization_when_explicit_false(
|
||||
tmp_path, monkeypatch
|
||||
):
|
||||
"""text_normalization: false is the API default; field is not sent."""
|
||||
captured = {}
|
||||
|
||||
fake_response = Mock()
|
||||
fake_response.content = b"mp3"
|
||||
fake_response.raise_for_status.return_value = None
|
||||
|
||||
def fake_post(url, headers, json, timeout):
|
||||
captured["json"] = json
|
||||
return fake_response
|
||||
|
||||
monkeypatch.setenv("XAI_API_KEY", "test-xai-key")
|
||||
monkeypatch.setattr("requests.post", fake_post)
|
||||
|
||||
_generate_xai_tts(
|
||||
"Hello world.",
|
||||
str(tmp_path / "out.mp3"),
|
||||
{"xai": {"voice_id": "ara", "language": "en", "text_normalization": False}},
|
||||
)
|
||||
|
||||
assert "text_normalization" not in captured["json"]
|
||||
|
|
|
|||
|
|
@ -235,6 +235,10 @@ DEFAULT_XAI_SPEED_DEFAULT = 1.0
|
|||
# xAI TTS `optimize_streaming_latency` accepts 0, 1, or 2; 0 (best quality) is
|
||||
# the API default (omitted => default). Values >0 trade quality for time-to-first-audio.
|
||||
DEFAULT_XAI_OPTIMIZE_STREAMING_LATENCY_DEFAULT = 0
|
||||
# xAI TTS `text_normalization` is a boolean (default False). When enabled,
|
||||
# the model normalizes written-form text (numbers, abbreviations, symbols)
|
||||
# into spoken-form before generating audio.
|
||||
DEFAULT_XAI_TEXT_NORMALIZATION_DEFAULT = False
|
||||
DEFAULT_GEMINI_TTS_MODEL = "gemini-2.5-flash-preview-tts"
|
||||
DEFAULT_GEMINI_TTS_VOICE = "Kore"
|
||||
DEFAULT_GEMINI_TTS_BASE_URL = "https://generativelanguage.googleapis.com/v1beta"
|
||||
|
|
@ -1594,6 +1598,12 @@ def _generate_xai_tts(text: str, output_path: str, tts_config: Dict[str, Any]) -
|
|||
optimize_streaming_latency = None
|
||||
if optimize_streaming_latency is not None:
|
||||
optimize_streaming_latency = max(0, min(2, optimize_streaming_latency))
|
||||
# ``tts.xai.text_normalization`` enables spoken-form normalization
|
||||
# (numbers, abbreviations, symbols → words). Defaults to False.
|
||||
text_normalization = _xai_bool_config(
|
||||
xai_config.get("text_normalization"),
|
||||
DEFAULT_XAI_TEXT_NORMALIZATION_DEFAULT,
|
||||
)
|
||||
if auto_speech_tags:
|
||||
text = _apply_xai_auto_speech_tags(text)
|
||||
if creds.get("provider") == "xai-oauth":
|
||||
|
|
@ -1637,6 +1647,9 @@ def _generate_xai_tts(text: str, output_path: str, tts_config: Dict[str, Any]) -
|
|||
and optimize_streaming_latency != DEFAULT_XAI_OPTIMIZE_STREAMING_LATENCY_DEFAULT
|
||||
):
|
||||
payload["optimize_streaming_latency"] = optimize_streaming_latency
|
||||
# Only attach `text_normalization` when explicitly enabled (default is False).
|
||||
if text_normalization:
|
||||
payload["text_normalization"] = True
|
||||
|
||||
response = requests.post(
|
||||
f"{base_url}/tts",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue