Simplify, undoing the change allowing controlling ServiceSwitcher with immediate frames (SystemFrames). Service switcher frames are ControlFrames, which are easier to reason about. We can always build the immediate option later if needed (i.e. if there's sufficient user pull for it)

This commit is contained in:
Paul Kompfner
2025-09-16 14:32:03 -04:00
parent b814b70e1e
commit a12392182c
3 changed files with 30 additions and 147 deletions

View File

@@ -6,12 +6,10 @@
"""Unit tests for ServiceSwitcher and related components."""
import asyncio
import unittest
from pipecat.frames.frames import (
Frame,
ManuallySwitchServiceControlFrame,
ManuallySwitchServiceFrame,
TextFrame,
)
@@ -101,30 +99,6 @@ class TestServiceSwitcherStrategyManual(unittest.IsolatedAsyncioTestCase):
self.assertNotEqual(strategy.active_service, self.service2)
self.assertEqual(strategy.active_service, self.service3)
def test_handle_manually_switch_service_control_frame(self):
"""Test manual service switching with ManuallySwitchServiceControlFrame."""
strategy = ServiceSwitcherStrategyManual(self.services)
# Initially service1 should be active
self.assertEqual(strategy.active_service, self.service1)
self.assertNotEqual(strategy.active_service, self.service2)
# Switch to service2
switch_frame = ManuallySwitchServiceControlFrame(service=self.service2)
strategy.handle_frame(switch_frame, FrameDirection.DOWNSTREAM)
self.assertNotEqual(strategy.active_service, self.service1)
self.assertEqual(strategy.active_service, self.service2)
self.assertNotEqual(strategy.active_service, self.service3)
# Switch to service3
switch_frame = ManuallySwitchServiceControlFrame(service=self.service3)
strategy.handle_frame(switch_frame, FrameDirection.DOWNSTREAM)
self.assertNotEqual(strategy.active_service, self.service1)
self.assertNotEqual(strategy.active_service, self.service2)
self.assertEqual(strategy.active_service, self.service3)
def test_handle_frame_invalid_service(self):
"""Test that switching to an invalid service raises an error."""
strategy = ServiceSwitcherStrategyManual(self.services)
@@ -207,8 +181,8 @@ class TestServiceSwitcher(unittest.IsolatedAsyncioTestCase):
for i, frame in enumerate(text_frames):
self.assertEqual(frame.text, f"Hello {i + 1}")
async def test_service_switching_in_order(self):
"""Test that after service switching using ManuallySwitchServiceControlFrame, the new active service receives frames while others don't."""
async def test_service_switching(self):
"""Test that after service switching using ManuallySwitchServiceFrame, the new active service receives frames while others don't."""
switcher = ServiceSwitcher(self.services, ServiceSwitcherStrategyManual)
# Reset counters
@@ -219,11 +193,11 @@ class TestServiceSwitcher(unittest.IsolatedAsyncioTestCase):
await run_test(
switcher,
frames_to_send=[
TextFrame("Frame for service1"),
ManuallySwitchServiceControlFrame(service=self.service2),
TextFrame("Frame for service2"),
TextFrame("Hello 1"),
ManuallySwitchServiceFrame(service=self.service2),
TextFrame("Hello 2"),
],
expected_down_frames=[TextFrame, ManuallySwitchServiceControlFrame, TextFrame],
expected_down_frames=[TextFrame, ManuallySwitchServiceFrame, TextFrame],
)
# Verify service2 received the frame
@@ -240,45 +214,9 @@ class TestServiceSwitcher(unittest.IsolatedAsyncioTestCase):
self.assertEqual(len(service1_text_frames), 1)
self.assertEqual(len(service2_text_frames), 1)
self.assertEqual(len(service3_text_frames), 0)
self.assertEqual(service1_text_frames[0].text, "Frame for service1")
self.assertEqual(service2_text_frames[0].text, "Frame for service2")
async def test_service_switching_immediate(self):
"""Test that after service switching using ManuallySwitchServiceFrame, the new active service receives frames while others don't."""
switcher = ServiceSwitcher(self.services, ServiceSwitcherStrategyManual)
# Reset counters
for service in self.services:
service.reset_counters()
# Send a test frame, a switch frame, and another test frame
await run_test(
switcher,
frames_to_send=[
# Out of order on purpose - ManuallySwitchServiceFrame should jump the queue
TextFrame("Frame 1 for service2"),
ManuallySwitchServiceFrame(service=self.service2),
TextFrame("Frame 2 for service2"),
],
expected_down_frames=[ManuallySwitchServiceFrame, TextFrame, TextFrame],
)
# Verify service2 received the frame
service1_text_frames = [
f for f in self.service1.processed_frames if isinstance(f, TextFrame)
]
service2_text_frames = [
f for f in self.service2.processed_frames if isinstance(f, TextFrame)
]
service3_text_frames = [
f for f in self.service3.processed_frames if isinstance(f, TextFrame)
]
self.assertEqual(len(service1_text_frames), 0)
self.assertEqual(len(service2_text_frames), 2)
self.assertEqual(len(service3_text_frames), 0)
self.assertEqual(service2_text_frames[0].text, "Frame 1 for service2")
self.assertEqual(service2_text_frames[1].text, "Frame 2 for service2")
self.assertEqual(service1_text_frames[0].text, "Hello 1")
self.assertEqual(service2_text_frames[0].text, "Hello 2")
if __name__ == "__main__":