Rename RequestMetadataFrame to ServiceSwitcherRequestMetadataFrame with service targeting
Add a `service` field so the frame targets a specific service, allowing ServiceSwitcher.push_frame to consume it only when the targeted service matches the active service. STTService and test mocks now push the frame downstream after handling instead of silently consuming it.
This commit is contained in:
1
changelog/3692.changed.md
Normal file
1
changelog/3692.changed.md
Normal file
@@ -0,0 +1 @@
|
||||
- Renamed `RequestMetadataFrame` to `ServiceSwitcherRequestMetadataFrame` and added a `service` field to target a specific service. The frame is now pushed downstream by services after handling instead of being silently consumed.
|
||||
@@ -1721,16 +1721,19 @@ class STTMetadataFrame(ServiceMetadataFrame):
|
||||
|
||||
|
||||
@dataclass
|
||||
class RequestMetadataFrame(ControlFrame):
|
||||
"""Request services to re-emit their metadata frames.
|
||||
class ServiceSwitcherRequestMetadataFrame(ControlFrame):
|
||||
"""Request a service to re-emit its metadata frames.
|
||||
|
||||
Used by ServiceSwitcher when switching active services to ensure
|
||||
downstream processors receive updated metadata from the newly active service.
|
||||
Services that receive this frame should re-push their metadata frame
|
||||
(e.g., STTMetadataFrame for STT services).
|
||||
|
||||
Parameters:
|
||||
service: The target service that should re-emit its metadata.
|
||||
"""
|
||||
|
||||
pass
|
||||
service: "FrameProcessor"
|
||||
|
||||
|
||||
#
|
||||
|
||||
@@ -12,9 +12,9 @@ from typing import Any, Generic, List, Optional, Type, TypeVar
|
||||
from pipecat.frames.frames import (
|
||||
Frame,
|
||||
ManuallySwitchServiceFrame,
|
||||
RequestMetadataFrame,
|
||||
ServiceMetadataFrame,
|
||||
ServiceSwitcherFrame,
|
||||
ServiceSwitcherRequestMetadataFrame,
|
||||
)
|
||||
from pipecat.pipeline.parallel_pipeline import ParallelPipeline
|
||||
from pipecat.processors.filters.function_filter import FunctionFilter
|
||||
@@ -220,16 +220,19 @@ class ServiceSwitcher(ParallelPipeline, Generic[StrategyType]):
|
||||
async def push_frame(self, frame: Frame, direction: FrameDirection = FrameDirection.DOWNSTREAM):
|
||||
"""Push a frame out of the service switcher.
|
||||
|
||||
Suppresses `RequestMetadataFrame` (internal to the switcher) and
|
||||
`ServiceMetadataFrame` from inactive services so only the active
|
||||
service's metadata reaches downstream processors. One case this happens
|
||||
is with `StartFrame` since all the filters let it pass, and `StartFrame`
|
||||
causes the service to generate `ServiceMetadataFrame`.
|
||||
Suppresses `ServiceSwitcherRequestMetadataFrame` targeting the active
|
||||
service (since it has already been handled) and `ServiceMetadataFrame`
|
||||
from inactive services so only the active service's metadata reaches
|
||||
downstream processors. One case this happens is with `StartFrame` since
|
||||
all the filters let it pass, and `StartFrame` causes the service to
|
||||
generate `ServiceMetadataFrame`.
|
||||
|
||||
"""
|
||||
# Don't let RequestMetadataFrame out.
|
||||
if isinstance(frame, RequestMetadataFrame):
|
||||
return
|
||||
# Consume ServiceSwitcherRequestMetadataFrame once the targeted service
|
||||
# has handled it (i.e. the active service).
|
||||
if isinstance(frame, ServiceSwitcherRequestMetadataFrame):
|
||||
if frame.service == self.strategy.active_service:
|
||||
return
|
||||
|
||||
# Only let metadata from the active service escape.
|
||||
if isinstance(frame, ServiceMetadataFrame):
|
||||
@@ -255,6 +258,6 @@ class ServiceSwitcher(ParallelPipeline, Generic[StrategyType]):
|
||||
|
||||
# If we switched to a new service, request its metadata.
|
||||
if service:
|
||||
await service.queue_frame(RequestMetadataFrame())
|
||||
await service.queue_frame(ServiceSwitcherRequestMetadataFrame(service=service))
|
||||
else:
|
||||
await super().process_frame(frame, direction)
|
||||
|
||||
@@ -21,7 +21,7 @@ from pipecat.frames.frames import (
|
||||
Frame,
|
||||
InterruptionFrame,
|
||||
MetricsFrame,
|
||||
RequestMetadataFrame,
|
||||
ServiceSwitcherRequestMetadataFrame,
|
||||
StartFrame,
|
||||
STTMetadataFrame,
|
||||
STTMuteFrame,
|
||||
@@ -264,9 +264,9 @@ class STTService(AIService):
|
||||
# Push StartFrame first, then metadata so downstream receives them in order
|
||||
await self.push_frame(frame, direction)
|
||||
await self._push_stt_metadata()
|
||||
elif isinstance(frame, RequestMetadataFrame):
|
||||
# Don't push the RequestMetadataFrame, just push the metadata
|
||||
elif isinstance(frame, ServiceSwitcherRequestMetadataFrame):
|
||||
await self._push_stt_metadata()
|
||||
await self.push_frame(frame, direction)
|
||||
elif isinstance(frame, AudioRawFrame):
|
||||
# In this service we accumulate audio internally and at the end we
|
||||
# push a TextFrame. We also push audio downstream in case someone
|
||||
|
||||
@@ -13,8 +13,8 @@ from dataclasses import dataclass
|
||||
from pipecat.frames.frames import (
|
||||
Frame,
|
||||
ManuallySwitchServiceFrame,
|
||||
RequestMetadataFrame,
|
||||
ServiceMetadataFrame,
|
||||
ServiceSwitcherRequestMetadataFrame,
|
||||
StartFrame,
|
||||
SystemFrame,
|
||||
TextFrame,
|
||||
@@ -68,7 +68,7 @@ class MockMetadataFrame(ServiceMetadataFrame):
|
||||
class MockMetadataService(FrameProcessor):
|
||||
"""A mock service that emits ServiceMetadataFrame like STT services.
|
||||
|
||||
Pushes MockMetadataFrame on StartFrame and RequestMetadataFrame.
|
||||
Pushes MockMetadataFrame on StartFrame and ServiceSwitcherRequestMetadataFrame.
|
||||
"""
|
||||
|
||||
def __init__(self, test_name: str, **kwargs):
|
||||
@@ -84,9 +84,9 @@ class MockMetadataService(FrameProcessor):
|
||||
if isinstance(frame, StartFrame):
|
||||
await self.push_frame(frame, direction)
|
||||
await self._push_metadata()
|
||||
elif isinstance(frame, RequestMetadataFrame):
|
||||
# Don't push RequestMetadataFrame downstream (it's internal)
|
||||
elif isinstance(frame, ServiceSwitcherRequestMetadataFrame):
|
||||
await self._push_metadata()
|
||||
await self.push_frame(frame, direction)
|
||||
else:
|
||||
await self.push_frame(frame, direction)
|
||||
|
||||
@@ -472,9 +472,11 @@ class TestServiceSwitcherMetadata(unittest.IsolatedAsyncioTestCase):
|
||||
expected_up_frames=[],
|
||||
)
|
||||
|
||||
# service2 should have received RequestMetadataFrame after becoming active
|
||||
# service2 should have received ServiceSwitcherRequestMetadataFrame after becoming active
|
||||
request_frames = [
|
||||
f for f in self.service2.processed_frames if isinstance(f, RequestMetadataFrame)
|
||||
f
|
||||
for f in self.service2.processed_frames
|
||||
if isinstance(f, ServiceSwitcherRequestMetadataFrame)
|
||||
]
|
||||
self.assertEqual(len(request_frames), 1)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user