SkipTagsAggregator and PatternPairAggregator now subclass SimpleTextAggregator
This commit is contained in:
@@ -17,8 +17,8 @@ from typing import Awaitable, Callable, List, Optional, Tuple
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from pipecat.utils.string import match_endofsentence
|
||||
from pipecat.utils.text.base_text_aggregator import Aggregation, AggregationType, BaseTextAggregator
|
||||
from pipecat.utils.text.base_text_aggregator import Aggregation, AggregationType
|
||||
from pipecat.utils.text.simple_text_aggregator import SimpleTextAggregator
|
||||
|
||||
|
||||
class MatchAction(Enum):
|
||||
@@ -72,7 +72,7 @@ class PatternMatch(Aggregation):
|
||||
return f"PatternMatch(type={self.type}, text={self.text}, full_match={self.full_match})"
|
||||
|
||||
|
||||
class PatternPairAggregator(BaseTextAggregator):
|
||||
class PatternPairAggregator(SimpleTextAggregator):
|
||||
"""Aggregator that identifies and processes content between pattern pairs.
|
||||
|
||||
This aggregator buffers text until it can identify complete pattern pairs
|
||||
@@ -97,7 +97,7 @@ class PatternPairAggregator(BaseTextAggregator):
|
||||
Creates an empty aggregator with no patterns or handlers registered.
|
||||
Text buffering and pattern detection will begin when text is aggregated.
|
||||
"""
|
||||
self._text = ""
|
||||
super().__init__()
|
||||
self._patterns = {}
|
||||
self._handlers = {}
|
||||
|
||||
@@ -309,9 +309,8 @@ class PatternPairAggregator(BaseTextAggregator):
|
||||
"""Aggregate text and process pattern pairs.
|
||||
|
||||
This method adds the new text to the buffer, processes any complete pattern
|
||||
pairs, and returns processed text up to sentence boundaries if possible.
|
||||
If there are incomplete patterns (start without matching end), it will
|
||||
continue buffering text.
|
||||
pairs, and uses the parent's lookahead logic for sentence detection when
|
||||
no patterns are active.
|
||||
|
||||
Args:
|
||||
text: New text to add to the buffer.
|
||||
@@ -355,38 +354,25 @@ class PatternPairAggregator(BaseTextAggregator):
|
||||
content=result.strip(), type=AggregationType.SENTENCE, full_match=result
|
||||
)
|
||||
|
||||
# Find sentence boundary if no incomplete patterns
|
||||
eos_marker = match_endofsentence(self._text)
|
||||
if eos_marker:
|
||||
# Extract text up to the sentence boundary
|
||||
result = self._text[:eos_marker]
|
||||
self._text = self._text[eos_marker:]
|
||||
# Use parent's lookahead logic for sentence detection
|
||||
aggregation = await super()._check_sentence_with_lookahead(text)
|
||||
if aggregation:
|
||||
# Convert to PatternMatch for consistency with return type
|
||||
return PatternMatch(
|
||||
content=result.strip(), type=AggregationType.SENTENCE, full_match=result
|
||||
content=aggregation.text, type=aggregation.type, full_match=aggregation.text
|
||||
)
|
||||
|
||||
# No complete sentence found yet
|
||||
return None
|
||||
|
||||
async def flush(self) -> Optional[Aggregation]:
|
||||
"""Flush any pending aggregation.
|
||||
|
||||
For PatternPairAggregator, there is no lookahead buffering, so this
|
||||
simply returns None. Any remaining text can be retrieved via the
|
||||
.text property.
|
||||
|
||||
Returns:
|
||||
None, as this aggregator does not buffer pending sentences.
|
||||
"""
|
||||
return None
|
||||
|
||||
async def handle_interruption(self):
|
||||
"""Handle interruptions by clearing the buffer.
|
||||
"""Handle interruptions by clearing the buffer and pattern state.
|
||||
|
||||
Called when an interruption occurs in the processing pipeline,
|
||||
to reset the state and discard any partially aggregated text.
|
||||
"""
|
||||
self._text = ""
|
||||
await super().handle_interruption()
|
||||
# Pattern and handler state persists across interruptions
|
||||
|
||||
async def reset(self):
|
||||
"""Clear the internally aggregated text.
|
||||
@@ -394,4 +380,5 @@ class PatternPairAggregator(BaseTextAggregator):
|
||||
Resets the aggregator to its initial state, discarding any
|
||||
buffered text and clearing pattern tracking state.
|
||||
"""
|
||||
self._text = ""
|
||||
await super().reset()
|
||||
# Pattern and handler state persists across resets
|
||||
|
||||
@@ -60,6 +60,22 @@ class SimpleTextAggregator(BaseTextAggregator):
|
||||
# Add new text to buffer
|
||||
self._text += text
|
||||
|
||||
# Delegate to sentence detection logic
|
||||
return await self._check_sentence_with_lookahead(text)
|
||||
|
||||
async def _check_sentence_with_lookahead(self, text: str) -> Optional[Aggregation]:
|
||||
"""Check for sentence boundaries using lookahead logic.
|
||||
|
||||
This method implements the core sentence detection logic with lookahead.
|
||||
Subclasses can call this via super() to reuse the lookahead behavior
|
||||
while adding their own logic (e.g., tag handling, pattern matching).
|
||||
|
||||
Args:
|
||||
text: The most recently added text (used for lookahead check).
|
||||
|
||||
Returns:
|
||||
Aggregation if sentence found, None otherwise.
|
||||
"""
|
||||
# If we need lookahead, check if we now have non-whitespace
|
||||
if self._needs_lookahead:
|
||||
# Check if the new character is non-whitespace
|
||||
|
||||
@@ -13,11 +13,12 @@ as a unit regardless of internal punctuation.
|
||||
|
||||
from typing import Optional, Sequence
|
||||
|
||||
from pipecat.utils.string import StartEndTags, match_endofsentence, parse_start_end_tags
|
||||
from pipecat.utils.text.base_text_aggregator import Aggregation, AggregationType, BaseTextAggregator
|
||||
from pipecat.utils.string import StartEndTags, parse_start_end_tags
|
||||
from pipecat.utils.text.base_text_aggregator import Aggregation, AggregationType
|
||||
from pipecat.utils.text.simple_text_aggregator import SimpleTextAggregator
|
||||
|
||||
|
||||
class SkipTagsAggregator(BaseTextAggregator):
|
||||
class SkipTagsAggregator(SimpleTextAggregator):
|
||||
"""Aggregator that prevents end of sentence matching between start/end tags.
|
||||
|
||||
This aggregator buffers text until it finds an end of sentence or a start
|
||||
@@ -37,27 +38,17 @@ class SkipTagsAggregator(BaseTextAggregator):
|
||||
tags: Sequence of StartEndTags objects defining the tag pairs
|
||||
that should prevent sentence boundary detection.
|
||||
"""
|
||||
self._text = ""
|
||||
super().__init__()
|
||||
self._tags = tags
|
||||
self._current_tag: Optional[StartEndTags] = None
|
||||
self._current_tag_index: int = 0
|
||||
|
||||
@property
|
||||
def text(self) -> Aggregation:
|
||||
"""Get the currently buffered text.
|
||||
|
||||
Returns:
|
||||
The current text buffer content that hasn't been processed yet.
|
||||
"""
|
||||
return Aggregation(text=self._text.strip(), type=AggregationType.SENTENCE)
|
||||
|
||||
async def aggregate(self, text: str) -> Optional[Aggregation]:
|
||||
"""Aggregate text while respecting tag boundaries.
|
||||
|
||||
This method adds the new text to the buffer, processes any complete
|
||||
pattern pairs, and returns processed text up to sentence boundaries if
|
||||
possible. If there are incomplete patterns (start without matching
|
||||
end), it will continue buffering text.
|
||||
This method adds the new text to the buffer, updates tag state,
|
||||
and uses the parent's lookahead logic for sentence detection when
|
||||
not inside tags.
|
||||
|
||||
Args:
|
||||
text: New text to add to the buffer.
|
||||
@@ -70,46 +61,34 @@ class SkipTagsAggregator(BaseTextAggregator):
|
||||
# Add new text to buffer
|
||||
self._text += text
|
||||
|
||||
# Update tag state
|
||||
(self._current_tag, self._current_tag_index) = parse_start_end_tags(
|
||||
self._text, self._tags, self._current_tag, self._current_tag_index
|
||||
)
|
||||
|
||||
# Find sentence boundary if no incomplete patterns
|
||||
if not self._current_tag:
|
||||
eos_marker = match_endofsentence(self._text)
|
||||
if eos_marker:
|
||||
# Extract text up to the sentence boundary
|
||||
result = self._text[:eos_marker]
|
||||
self._text = self._text[eos_marker:]
|
||||
return Aggregation(text=result.strip(), type=AggregationType.SENTENCE)
|
||||
# If inside tags, don't check for sentences
|
||||
if self._current_tag:
|
||||
return None
|
||||
|
||||
# No complete sentence found yet
|
||||
return None
|
||||
|
||||
async def flush(self) -> Optional[Aggregation]:
|
||||
"""Flush any pending aggregation.
|
||||
|
||||
For SkipTagsAggregator, there is no lookahead buffering, so this
|
||||
simply returns None. Any remaining text can be retrieved via the
|
||||
.text property.
|
||||
|
||||
Returns:
|
||||
None, as this aggregator does not buffer pending sentences.
|
||||
"""
|
||||
return None
|
||||
# Otherwise, use parent's lookahead logic for sentence detection
|
||||
return await super()._check_sentence_with_lookahead(text)
|
||||
|
||||
async def handle_interruption(self):
|
||||
"""Handle interruptions by clearing the buffer.
|
||||
"""Handle interruptions by clearing the buffer and tag state.
|
||||
|
||||
Called when an interruption occurs in the processing pipeline,
|
||||
to reset the state and discard any partially aggregated text.
|
||||
"""
|
||||
self._text = ""
|
||||
await super().handle_interruption()
|
||||
self._current_tag = None
|
||||
self._current_tag_index = 0
|
||||
|
||||
async def reset(self):
|
||||
"""Clear the internally aggregated text.
|
||||
"""Clear the internally aggregated text and tag state.
|
||||
|
||||
Resets the aggregator to its initial state, discarding any
|
||||
buffered text.
|
||||
"""
|
||||
self._text = ""
|
||||
await super().reset()
|
||||
self._current_tag = None
|
||||
self._current_tag_index = 0
|
||||
|
||||
Reference in New Issue
Block a user