diff --git a/CHANGELOG.md b/CHANGELOG.md
index b09927a49..23471fbbf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -54,8 +54,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Introduced a new `Aggregation` dataclass to represent both the aggregated `text` and
a string identifying the `type` of aggregation (ex. "sentence", "word", "my custom
aggregation")
- # MRKB TODO -- don't break. leave pattern_id as-is and remove 'type', so that the old
- remove param can remain
- **BREAKING**: `BaseTextAggregator.text` now returns an `Aggregation` (instead of `str`).
To update: `aggregated_text = myAggregator.text` -> `aggregated_text = myAggregator.text.text`
- **BREAKING**: `BaseTextAggregator.aggregate()` now returns `Optional[Aggregation]`
@@ -68,36 +66,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `SimpleTextAggregator`, `SkipTagsAggregator`, `PatternPairAggregator` updated to
produce/consume `Aggregation` objects.
-- Augmented the `PatterPairAggregator`:
- - **BREAKING CHANGES**: Support for the items below resulted in two breaking changes to the
- `PatternPairAggregator` methods
- 1. The `add_pattern_pair` method arguments have changed:
- - The `pattern_id` argument is now `type`
- - The `remove_match` argument has been replaced with the `action` argument. To update,
- change `remove_match: True` to `action: MatchAction.REMOVE` or `remove_match: False` to
- `action: MatchAction.KEEP`
- 2. The `PatternMatch` type returned to handlers registered via `on_pattern_match` has been
- updated to subclass from the new `Aggregation` type, which means that `content` has been
- replaced with `text` and `pattern_id` has been replaced with `type`:
- ```
- async dev on_match_tag(match: PatternMatch):
- pattern = match.type # instead of match.pattern_id
- text = match.text # instead of match.content
- ```
- - `PatternPairAggregator` now supports `type` and `action` per pattern.
- - New `MatchAction` enum: `REMOVE`, `KEEP`, `AGGREGATE`, allowing customization for how
- a match should be handled.
- - `REMOVE`: The text along with its delimiters will be removed from the streaming text.
- Sentence aggregation will continue on as if this text did not exist.
- - `KEEP`: The delimiters will be removed, but the content between them will be kept.
- Sentence aggregation will continue on with the internal text included.
- - `AGGREGATE`: The delimiters will be removed and the content between will be treated
- as a separate aggregation. Any text before the start of the pattern will be
- returned early, whether or not a complete sentence was found. Then the pattern
- will be returned. Then the aggregation will continue on sentence matching after
- the closing delimiter is found. The content between the delimiters is not
- aggregated by sentence. It is aggregated as one single block of text.
- - `PatternMatch` now extends `Aggregation` and provides richer info to handlers.
+- Augmented the `PatternPairAggregator`:
+ - Introduced a new, preferred version of `add_pattern` to support a new option for treating a
+ match as a separate aggregation returned from `aggregate()`. This replaces the now
+ deprecated `add_pattern_pair` method and you provide a `MatchAction` in lieu of the `remove_match` field.
+ - `MatchAction` enum: `REMOVE`, `KEEP`, `AGGREGATE`, allowing customization for how
+ a match should be handled.
+ - `REMOVE`: The text along with its delimiters will be removed from the streaming text.
+ Sentence aggregation will continue on as if this text did not exist.
+ - `KEEP`: The delimiters will be removed, but the content between them will be kept.
+ Sentence aggregation will continue on with the internal text included.
+ - `AGGREGATE`: The delimiters will be removed and the content between will be treated
+ as a separate aggregation. Any text before the start of the pattern will be
+ returned early, whether or not a complete sentence was found. Then the pattern
+ will be returned. Then the aggregation will continue on sentence matching after
+ the closing delimiter is found. The content between the delimiters is not
+ aggregated by sentence. It is aggregated as one single block of text.
+ - `PatternMatch` now extends `Aggregation` and provides richer info to handlers.
+ - **BREAKING**: The `PatternMatch` type returned to handlers registered via `on_pattern_match`
+ has been updated to subclass from the new `Aggregation` type, which means that `content`
+ has been replaced with `text` and `pattern_id` has been replaced with `type`:
+ ```
+ async dev on_match_tag(match: PatternMatch):
+ pattern = match.type # instead of match.pattern_id
+ text = match.text # instead of match.content
+ ```
### Changed
@@ -148,6 +141,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
behavior, but if you want to override the aggregation behavior, you should use the new
processor.
+- Deprecated `add_pattern_pair` in the `PatternPairAggregator` which takes a `pattern_id`
+ and `remove_match` field in favor of the new `add_pattern` method which takes a `type` and an
+ `action`
+
### Fixed
- Fixed subtle issue of assistant context messages ending up with double spaces
diff --git a/examples/foundational/35-pattern-pair-voice-switching.py b/examples/foundational/35-pattern-pair-voice-switching.py
index ebcae8a19..3a102acfd 100644
--- a/examples/foundational/35-pattern-pair-voice-switching.py
+++ b/examples/foundational/35-pattern-pair-voice-switching.py
@@ -110,7 +110,7 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
pattern_aggregator = PatternPairAggregator()
# Add pattern for voice switching
- pattern_aggregator.add_pattern_pair(
+ pattern_aggregator.add_pattern(
type="voice",
start_pattern="",
end_pattern="",
diff --git a/src/pipecat/extensions/ivr/ivr_navigator.py b/src/pipecat/extensions/ivr/ivr_navigator.py
index 7c0cb87aa..d06e76f25 100644
--- a/src/pipecat/extensions/ivr/ivr_navigator.py
+++ b/src/pipecat/extensions/ivr/ivr_navigator.py
@@ -118,15 +118,15 @@ class IVRProcessor(FrameProcessor):
def _setup_xml_patterns(self):
"""Set up XML pattern detection and handlers."""
# Register DTMF pattern
- self._aggregator.add_pattern_pair("dtmf", "", "", action=MatchAction.REMOVE)
+ self._aggregator.add_pattern("dtmf", "", "", action=MatchAction.REMOVE)
self._aggregator.on_pattern_match("dtmf", self._handle_dtmf_action)
# Register mode pattern
- self._aggregator.add_pattern_pair("mode", "", "", action=MatchAction.REMOVE)
+ self._aggregator.add_pattern("mode", "", "", action=MatchAction.REMOVE)
self._aggregator.on_pattern_match("mode", self._handle_mode_action)
# Register IVR pattern
- self._aggregator.add_pattern_pair("ivr", "", "", action=MatchAction.REMOVE)
+ self._aggregator.add_pattern("ivr", "", "", action=MatchAction.REMOVE)
self._aggregator.on_pattern_match("ivr", self._handle_ivr_action)
async def process_frame(self, frame: Frame, direction: FrameDirection):
diff --git a/src/pipecat/utils/text/pattern_pair_aggregator.py b/src/pipecat/utils/text/pattern_pair_aggregator.py
index d734ee163..094a6c910 100644
--- a/src/pipecat/utils/text/pattern_pair_aggregator.py
+++ b/src/pipecat/utils/text/pattern_pair_aggregator.py
@@ -25,9 +25,16 @@ class MatchAction(Enum):
"""Actions to take when a pattern pair is matched.
Parameters:
- REMOVE: Remove the matched pattern from the text.
- KEEP: Keep the matched pattern in the text as normal text.
- AGGREGATE: Return the matched pattern as a separate aggregation object.
+ REMOVE: The text along with its delimiters will be removed from the streaming text.
+ Sentence aggregation will continue on as if this text did not exist.
+ KEEP: The delimiters will be removed, but the content between them will be kept.
+ Sentence aggregation will continue on with the internal text included.
+ AGGREGATE: The delimiters will be removed and the content between will be treated
+ as a separate aggregation. Any text before the start of the pattern will be
+ returned early, whether or not a complete sentence was found. Then the pattern
+ will be returned. Then the aggregation will continue on sentence matching after
+ the closing delimiter is found. The content between the delimiters is not
+ aggregated by sentence. It is aggregated as one single block of text.
"""
REMOVE = "remove"
@@ -106,7 +113,7 @@ class PatternPairAggregator(BaseTextAggregator):
return Aggregation(self._text, pattern_start[1].get("type", "sentence"))
return Aggregation(self._text, "sentence")
- def add_pattern_pair(
+ def add_pattern(
self,
type: str,
start_pattern: str,
@@ -148,6 +155,46 @@ class PatternPairAggregator(BaseTextAggregator):
}
return self
+ def add_pattern_pair(
+ self, pattern_id: str, start_pattern: str, end_pattern: str, remove_match: bool = True
+ ):
+ """Add a pattern pair to detect in the text.
+
+ .. deprecated:: 0.0.95
+ This function is deprecated and will be removed in a future version.
+ Use `add_pattern` with a type and MatchAction instead.
+
+ This method calls `add_pattern` setting type with the provided pattern_id and action
+ to either MatchAction.REMOVE or MatchAction.KEEP based on `remove_match`.
+
+ Args:
+ pattern_id: Identifier for this pattern pair. Should be unique and ideally descriptive.
+ (e.g., 'code', 'speaker', 'custom'). pattern_id can not be 'sentence' as that is
+ reserved for the default behavior.
+ start_pattern: Pattern that marks the beginning of content.
+ end_pattern: Pattern that marks the end of content.
+ remove_match: If True, the matched pattern will be removed from the text. (Same as MatchAction.REMOVE)
+ If False, it will be kept and treated as normal text. (Same as MatchAction.KEEP)
+ """
+ import warnings
+
+ with warnings.catch_warnings():
+ warnings.simplefilter("once")
+ warnings.warn(
+ "add_pattern_pair with a pattern_id or remove_match is deprecated and will be"
+ " removed in a future version. Use add_pattern with a type and MatchAction instead",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+
+ action = MatchAction.REMOVE if remove_match else MatchAction.KEEP
+ return self.add_pattern(
+ type=pattern_id,
+ start_pattern=start_pattern,
+ end_pattern=end_pattern,
+ action=action,
+ )
+
def on_pattern_match(
self, type: str, handler: Callable[[PatternMatch], Awaitable[None]]
) -> "PatternPairAggregator":
diff --git a/tests/test_pattern_pair_aggregator.py b/tests/test_pattern_pair_aggregator.py
index ffbff4fbb..e4d72f3a5 100644
--- a/tests/test_pattern_pair_aggregator.py
+++ b/tests/test_pattern_pair_aggregator.py
@@ -22,12 +22,11 @@ class TestPatternPairAggregator(unittest.IsolatedAsyncioTestCase):
# Add a test pattern
self.aggregator.add_pattern_pair(
- type="test_pattern",
+ pattern_id="test_pattern",
start_pattern="",
end_pattern="",
- action=MatchAction.REMOVE,
)
- self.aggregator.add_pattern_pair(
+ self.aggregator.add_pattern(
type="code_pattern",
start_pattern="",
end_pattern="",
@@ -119,14 +118,14 @@ class TestPatternPairAggregator(unittest.IsolatedAsyncioTestCase):
voice_handler = AsyncMock()
emphasis_handler = AsyncMock()
- self.aggregator.add_pattern_pair(
+ self.aggregator.add_pattern(
type="voice",
start_pattern="",
end_pattern="",
action=MatchAction.REMOVE,
)
- self.aggregator.add_pattern_pair(
+ self.aggregator.add_pattern(
type="emphasis",
start_pattern="",
end_pattern="",