first git
CI / ruff (push) Waiting to run
CI / mypy (push) Waiting to run
CI / pytest (push) Waiting to run
CI / package (push) Waiting to run
CI / Install smoke (${{ matrix.label }}) (linux, ubuntu-latest) (push) Blocked by required conditions
CI / Install smoke (${{ matrix.label }}) (macos-arm64, macos-14) (push) Blocked by required conditions
CI / ruff (push) Waiting to run
CI / mypy (push) Waiting to run
CI / pytest (push) Waiting to run
CI / package (push) Waiting to run
CI / Install smoke (${{ matrix.label }}) (linux, ubuntu-latest) (push) Blocked by required conditions
CI / Install smoke (${{ matrix.label }}) (macos-arm64, macos-14) (push) Blocked by required conditions
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
from queue import Queue
|
||||
from threading import Event, Thread
|
||||
|
||||
from openai.types.realtime.realtime_response_create_params import RealtimeResponseCreateParams
|
||||
|
||||
from speech_to_speech.LLM.lm_output_processor import LMOutputProcessor
|
||||
from speech_to_speech.pipeline.messages import EndOfResponse, LLMResponseChunk, TTSInput
|
||||
from speech_to_speech.pipeline.speculative_turns import SpeculativeTurnTracker
|
||||
|
||||
|
||||
def _processor(tracker: SpeculativeTurnTracker) -> LMOutputProcessor:
|
||||
processor = LMOutputProcessor.__new__(LMOutputProcessor)
|
||||
processor.setup(text_output_queue=Queue(), speculative_turns=tracker)
|
||||
return processor
|
||||
|
||||
|
||||
def test_stale_end_of_response_is_not_forwarded_to_tts():
|
||||
tracker = SpeculativeTurnTracker()
|
||||
tracker.observe("turn_1", 1)
|
||||
processor = _processor(tracker)
|
||||
|
||||
outputs = list(processor.process(EndOfResponse(turn_id="turn_1", turn_revision=0)))
|
||||
|
||||
assert outputs == []
|
||||
|
||||
|
||||
def test_latest_end_of_response_is_forwarded_to_tts():
|
||||
tracker = SpeculativeTurnTracker()
|
||||
tracker.observe("turn_1", 1)
|
||||
processor = _processor(tracker)
|
||||
|
||||
outputs = list(processor.process(EndOfResponse(turn_id="turn_1", turn_revision=1)))
|
||||
|
||||
assert len(outputs) == 1
|
||||
assert outputs[0].turn_id == "turn_1"
|
||||
assert outputs[0].turn_revision == 1
|
||||
|
||||
|
||||
def test_cancel_generation_is_forwarded_to_tts():
|
||||
tracker = SpeculativeTurnTracker()
|
||||
tracker.observe("turn_1", 0)
|
||||
processor = _processor(tracker)
|
||||
|
||||
outputs = list(
|
||||
processor.process(
|
||||
LLMResponseChunk(
|
||||
text="hello",
|
||||
turn_id="turn_1",
|
||||
turn_revision=0,
|
||||
cancel_generation=7,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
assert len(outputs) == 1
|
||||
assert outputs[0].cancel_generation == 7
|
||||
|
||||
|
||||
def test_text_only_chunk_is_not_forwarded_to_tts():
|
||||
tracker = SpeculativeTurnTracker()
|
||||
tracker.observe("turn_1", 0)
|
||||
processor = _processor(tracker)
|
||||
|
||||
outputs = list(
|
||||
processor.process(
|
||||
LLMResponseChunk(
|
||||
text="hello",
|
||||
turn_id="turn_1",
|
||||
turn_revision=0,
|
||||
response=RealtimeResponseCreateParams(output_modalities=["text"]),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
assert outputs == []
|
||||
# The assistant text still reaches clients even when TTS is skipped.
|
||||
event = processor.text_output_queue.get_nowait()
|
||||
assert event.text == "hello"
|
||||
|
||||
|
||||
def test_audio_chunk_is_forwarded_to_tts():
|
||||
tracker = SpeculativeTurnTracker()
|
||||
tracker.observe("turn_1", 0)
|
||||
processor = _processor(tracker)
|
||||
|
||||
outputs = list(
|
||||
processor.process(
|
||||
LLMResponseChunk(
|
||||
text="hello",
|
||||
turn_id="turn_1",
|
||||
turn_revision=0,
|
||||
response=RealtimeResponseCreateParams(output_modalities=["audio"]),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
assert len(outputs) == 1
|
||||
assert isinstance(outputs[0], TTSInput)
|
||||
assert outputs[0].text == "hello"
|
||||
|
||||
|
||||
def test_empty_modalities_is_forwarded_to_tts():
|
||||
tracker = SpeculativeTurnTracker()
|
||||
tracker.observe("turn_1", 0)
|
||||
processor = _processor(tracker)
|
||||
|
||||
outputs = list(
|
||||
processor.process(
|
||||
LLMResponseChunk(
|
||||
text="hello",
|
||||
turn_id="turn_1",
|
||||
turn_revision=0,
|
||||
response=RealtimeResponseCreateParams(output_modalities=[]),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
assert len(outputs) == 1
|
||||
assert isinstance(outputs[0], TTSInput)
|
||||
|
||||
|
||||
def test_pending_reopen_holds_assistant_chunk_until_cancelled():
|
||||
tracker = SpeculativeTurnTracker()
|
||||
tracker.observe("turn_1", 0)
|
||||
candidate_revision = tracker.begin_reopen_candidate("turn_1", 0)
|
||||
processor = _processor(tracker)
|
||||
done = Event()
|
||||
outputs = []
|
||||
|
||||
def run_processor():
|
||||
outputs.extend(
|
||||
processor.process(
|
||||
LLMResponseChunk(
|
||||
text="hello",
|
||||
turn_id="turn_1",
|
||||
turn_revision=0,
|
||||
)
|
||||
)
|
||||
)
|
||||
done.set()
|
||||
|
||||
thread = Thread(target=run_processor)
|
||||
thread.start()
|
||||
|
||||
assert not done.wait(0.05)
|
||||
tracker.cancel_reopen_candidate("turn_1", candidate_revision)
|
||||
assert done.wait(1.0)
|
||||
thread.join(timeout=1.0)
|
||||
|
||||
assert len(outputs) == 1
|
||||
assert outputs[0].text == "hello"
|
||||
event = processor.text_output_queue.get_nowait()
|
||||
assert event.text == "hello"
|
||||
|
||||
|
||||
def test_reopen_grace_holds_assistant_chunk_until_elapsed():
|
||||
tracker = SpeculativeTurnTracker()
|
||||
tracker.observe("turn_1", 0)
|
||||
tracker.start_reopen_grace("turn_1", 0, grace_s=0.08)
|
||||
processor = _processor(tracker)
|
||||
done = Event()
|
||||
outputs = []
|
||||
|
||||
def run_processor():
|
||||
outputs.extend(
|
||||
processor.process(
|
||||
LLMResponseChunk(
|
||||
text="hello",
|
||||
turn_id="turn_1",
|
||||
turn_revision=0,
|
||||
)
|
||||
)
|
||||
)
|
||||
done.set()
|
||||
|
||||
thread = Thread(target=run_processor)
|
||||
thread.start()
|
||||
|
||||
assert not done.wait(0.02)
|
||||
assert done.wait(1.0)
|
||||
thread.join(timeout=1.0)
|
||||
|
||||
assert len(outputs) == 1
|
||||
assert outputs[0].text == "hello"
|
||||
event = processor.text_output_queue.get_nowait()
|
||||
assert event.text == "hello"
|
||||
|
||||
|
||||
def test_confirmed_reopen_drops_held_assistant_chunk():
|
||||
tracker = SpeculativeTurnTracker()
|
||||
tracker.observe("turn_1", 0)
|
||||
candidate_revision = tracker.begin_reopen_candidate("turn_1", 0)
|
||||
processor = _processor(tracker)
|
||||
done = Event()
|
||||
outputs = []
|
||||
|
||||
def run_processor():
|
||||
outputs.extend(
|
||||
processor.process(
|
||||
LLMResponseChunk(
|
||||
text="hello",
|
||||
turn_id="turn_1",
|
||||
turn_revision=0,
|
||||
)
|
||||
)
|
||||
)
|
||||
done.set()
|
||||
|
||||
thread = Thread(target=run_processor)
|
||||
thread.start()
|
||||
|
||||
assert not done.wait(0.05)
|
||||
assert tracker.confirm_reopen_candidate("turn_1", 0, candidate_revision)
|
||||
assert done.wait(1.0)
|
||||
thread.join(timeout=1.0)
|
||||
|
||||
assert outputs == []
|
||||
assert processor.text_output_queue.empty()
|
||||
Reference in New Issue
Block a user