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:
aml1973 2026-07-23 14:02:42 -07:00 committed by Teknium
parent efc81a19a6
commit 861c2fecd2
2 changed files with 63 additions and 5 deletions

View file

@ -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 ──────────────────────────────────────

View file

@ -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")