mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
fix(tts): honor OpenAI config for streaming
Use the shared OpenAI audio key resolver and prefer tts.openai.base_url over the global environment fallback in the Desktop streaming path. Add focused regression coverage for credential and endpoint propagation.
This commit is contained in:
parent
efc81a19a6
commit
861c2fecd2
2 changed files with 63 additions and 5 deletions
|
|
@ -130,9 +130,62 @@ def test_elevenlabs_available_reflects_key(monkeypatch):
|
|||
assert ts.ElevenLabsStreamer.available() is False
|
||||
|
||||
|
||||
def test_openai_available_reflects_key(monkeypatch):
|
||||
monkeypatch.setattr(ts, "get_env_value", lambda k, *a: "key" if k == "OPENAI_API_KEY" else None)
|
||||
def test_openai_available_reflects_audio_key_resolution(monkeypatch):
|
||||
monkeypatch.setattr(ts, "resolve_openai_audio_api_key", lambda: "voice-key")
|
||||
assert ts.OpenAIStreamer.available() is True
|
||||
monkeypatch.setattr(ts, "resolve_openai_audio_api_key", lambda: "")
|
||||
assert ts.OpenAIStreamer.available() is False
|
||||
|
||||
|
||||
def test_openai_streamer_prefers_configured_base_url(monkeypatch):
|
||||
captured = {}
|
||||
|
||||
class _Response:
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *_args):
|
||||
return False
|
||||
|
||||
def iter_bytes(self):
|
||||
yield b"\x01\x00"
|
||||
|
||||
class _StreamingCreate:
|
||||
@staticmethod
|
||||
def create(**kwargs):
|
||||
captured["request"] = kwargs
|
||||
return _Response()
|
||||
|
||||
class _OpenAI:
|
||||
def __init__(self, **kwargs):
|
||||
captured["client"] = kwargs
|
||||
self.audio = MagicMock()
|
||||
self.audio.speech.with_streaming_response = _StreamingCreate()
|
||||
|
||||
monkeypatch.setattr(ts, "resolve_openai_audio_api_key", lambda: "voice-key")
|
||||
monkeypatch.setattr(
|
||||
ts,
|
||||
"get_env_value",
|
||||
lambda key, *args: "https://env.example/v1" if key == "OPENAI_BASE_URL" else None,
|
||||
)
|
||||
monkeypatch.setattr("openai.OpenAI", _OpenAI)
|
||||
|
||||
config = {
|
||||
"provider": "openai",
|
||||
"openai": {
|
||||
"base_url": "http://local-tts.example/v1",
|
||||
"model": "tts-1",
|
||||
"voice": "local-voice",
|
||||
},
|
||||
}
|
||||
streamer = ts.resolve_streaming_provider(config)
|
||||
|
||||
assert streamer is not None
|
||||
assert list(streamer.stream("Streaming test.")) == [b"\x01\x00"]
|
||||
assert captured["client"] == {
|
||||
"api_key": "voice-key",
|
||||
"base_url": "http://local-tts.example/v1",
|
||||
}
|
||||
|
||||
|
||||
# ── Dispatch: chunked streamer path ──────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import time
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import Callable, Dict, Iterator, List, Optional
|
||||
|
||||
from tools.tool_backend_helpers import resolve_openai_audio_api_key
|
||||
from tools.tts_tool import _get_provider, get_env_value
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -202,14 +203,18 @@ class OpenAIStreamer(StreamingTTSProvider):
|
|||
|
||||
@staticmethod
|
||||
def available() -> bool:
|
||||
return bool(get_env_value("OPENAI_API_KEY"))
|
||||
return bool(resolve_openai_audio_api_key())
|
||||
|
||||
def stream(self, text: str) -> Iterator[bytes]:
|
||||
from openai import OpenAI
|
||||
|
||||
client = OpenAI(
|
||||
api_key=get_env_value("OPENAI_API_KEY"),
|
||||
base_url=get_env_value("OPENAI_BASE_URL") or None,
|
||||
api_key=resolve_openai_audio_api_key(),
|
||||
base_url=(
|
||||
self.section.get("base_url")
|
||||
or get_env_value("OPENAI_BASE_URL")
|
||||
or None
|
||||
),
|
||||
)
|
||||
model = self.section.get("model", "gpt-4o-mini-tts")
|
||||
voice = self.section.get("voice", "alloy")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue