Merge pull request #3692 from pipecat-ai/mb/request-metadata-updates
Rename RequestMetadataFrame to ServiceSwitcherRequestMetadataFrame with service targeting
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
|
@dataclass
|
||||||
class RequestMetadataFrame(ControlFrame):
|
class ServiceSwitcherRequestMetadataFrame(ControlFrame):
|
||||||
"""Request services to re-emit their metadata frames.
|
"""Request a service to re-emit its metadata frames.
|
||||||
|
|
||||||
Used by ServiceSwitcher when switching active services to ensure
|
Used by ServiceSwitcher when switching active services to ensure
|
||||||
downstream processors receive updated metadata from the newly active service.
|
downstream processors receive updated metadata from the newly active service.
|
||||||
Services that receive this frame should re-push their metadata frame
|
Services that receive this frame should re-push their metadata frame
|
||||||
(e.g., STTMetadataFrame for STT services).
|
(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 (
|
from pipecat.frames.frames import (
|
||||||
Frame,
|
Frame,
|
||||||
ManuallySwitchServiceFrame,
|
ManuallySwitchServiceFrame,
|
||||||
RequestMetadataFrame,
|
|
||||||
ServiceMetadataFrame,
|
ServiceMetadataFrame,
|
||||||
ServiceSwitcherFrame,
|
ServiceSwitcherFrame,
|
||||||
|
ServiceSwitcherRequestMetadataFrame,
|
||||||
)
|
)
|
||||||
from pipecat.pipeline.parallel_pipeline import ParallelPipeline
|
from pipecat.pipeline.parallel_pipeline import ParallelPipeline
|
||||||
from pipecat.processors.filters.function_filter import FunctionFilter
|
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):
|
async def push_frame(self, frame: Frame, direction: FrameDirection = FrameDirection.DOWNSTREAM):
|
||||||
"""Push a frame out of the service switcher.
|
"""Push a frame out of the service switcher.
|
||||||
|
|
||||||
Suppresses `RequestMetadataFrame` (internal to the switcher) and
|
Suppresses `ServiceSwitcherRequestMetadataFrame` targeting the active
|
||||||
`ServiceMetadataFrame` from inactive services so only the active
|
service (since it has already been handled) and `ServiceMetadataFrame`
|
||||||
service's metadata reaches downstream processors. One case this happens
|
from inactive services so only the active service's metadata reaches
|
||||||
is with `StartFrame` since all the filters let it pass, and `StartFrame`
|
downstream processors. One case this happens is with `StartFrame` since
|
||||||
causes the service to generate `ServiceMetadataFrame`.
|
all the filters let it pass, and `StartFrame` causes the service to
|
||||||
|
generate `ServiceMetadataFrame`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Don't let RequestMetadataFrame out.
|
# Consume ServiceSwitcherRequestMetadataFrame once the targeted service
|
||||||
if isinstance(frame, RequestMetadataFrame):
|
# has handled it (i.e. the active service).
|
||||||
return
|
if isinstance(frame, ServiceSwitcherRequestMetadataFrame):
|
||||||
|
if frame.service == self.strategy.active_service:
|
||||||
|
return
|
||||||
|
|
||||||
# Only let metadata from the active service escape.
|
# Only let metadata from the active service escape.
|
||||||
if isinstance(frame, ServiceMetadataFrame):
|
if isinstance(frame, ServiceMetadataFrame):
|
||||||
@@ -255,6 +258,6 @@ class ServiceSwitcher(ParallelPipeline, Generic[StrategyType]):
|
|||||||
|
|
||||||
# If we switched to a new service, request its metadata.
|
# If we switched to a new service, request its metadata.
|
||||||
if service:
|
if service:
|
||||||
await service.queue_frame(RequestMetadataFrame())
|
await service.queue_frame(ServiceSwitcherRequestMetadataFrame(service=service))
|
||||||
else:
|
else:
|
||||||
await super().process_frame(frame, direction)
|
await super().process_frame(frame, direction)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from pipecat.frames.frames import (
|
|||||||
Frame,
|
Frame,
|
||||||
InterruptionFrame,
|
InterruptionFrame,
|
||||||
MetricsFrame,
|
MetricsFrame,
|
||||||
RequestMetadataFrame,
|
ServiceSwitcherRequestMetadataFrame,
|
||||||
StartFrame,
|
StartFrame,
|
||||||
STTMetadataFrame,
|
STTMetadataFrame,
|
||||||
STTMuteFrame,
|
STTMuteFrame,
|
||||||
@@ -264,9 +264,9 @@ class STTService(AIService):
|
|||||||
# Push StartFrame first, then metadata so downstream receives them in order
|
# Push StartFrame first, then metadata so downstream receives them in order
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
await self._push_stt_metadata()
|
await self._push_stt_metadata()
|
||||||
elif isinstance(frame, RequestMetadataFrame):
|
elif isinstance(frame, ServiceSwitcherRequestMetadataFrame):
|
||||||
# Don't push the RequestMetadataFrame, just push the metadata
|
|
||||||
await self._push_stt_metadata()
|
await self._push_stt_metadata()
|
||||||
|
await self.push_frame(frame, direction)
|
||||||
elif isinstance(frame, AudioRawFrame):
|
elif isinstance(frame, AudioRawFrame):
|
||||||
# In this service we accumulate audio internally and at the end we
|
# In this service we accumulate audio internally and at the end we
|
||||||
# push a TextFrame. We also push audio downstream in case someone
|
# push a TextFrame. We also push audio downstream in case someone
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ from dataclasses import dataclass
|
|||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
Frame,
|
Frame,
|
||||||
ManuallySwitchServiceFrame,
|
ManuallySwitchServiceFrame,
|
||||||
RequestMetadataFrame,
|
|
||||||
ServiceMetadataFrame,
|
ServiceMetadataFrame,
|
||||||
|
ServiceSwitcherRequestMetadataFrame,
|
||||||
StartFrame,
|
StartFrame,
|
||||||
SystemFrame,
|
SystemFrame,
|
||||||
TextFrame,
|
TextFrame,
|
||||||
@@ -68,7 +68,7 @@ class MockMetadataFrame(ServiceMetadataFrame):
|
|||||||
class MockMetadataService(FrameProcessor):
|
class MockMetadataService(FrameProcessor):
|
||||||
"""A mock service that emits ServiceMetadataFrame like STT services.
|
"""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):
|
def __init__(self, test_name: str, **kwargs):
|
||||||
@@ -84,9 +84,9 @@ class MockMetadataService(FrameProcessor):
|
|||||||
if isinstance(frame, StartFrame):
|
if isinstance(frame, StartFrame):
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
await self._push_metadata()
|
await self._push_metadata()
|
||||||
elif isinstance(frame, RequestMetadataFrame):
|
elif isinstance(frame, ServiceSwitcherRequestMetadataFrame):
|
||||||
# Don't push RequestMetadataFrame downstream (it's internal)
|
|
||||||
await self._push_metadata()
|
await self._push_metadata()
|
||||||
|
await self.push_frame(frame, direction)
|
||||||
else:
|
else:
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
|
|
||||||
@@ -472,9 +472,11 @@ class TestServiceSwitcherMetadata(unittest.IsolatedAsyncioTestCase):
|
|||||||
expected_up_frames=[],
|
expected_up_frames=[],
|
||||||
)
|
)
|
||||||
|
|
||||||
# service2 should have received RequestMetadataFrame after becoming active
|
# service2 should have received ServiceSwitcherRequestMetadataFrame after becoming active
|
||||||
request_frames = [
|
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)
|
self.assertEqual(len(request_frames), 1)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user