dv-pipecat-ai 0.0.74.dev770__py3-none-any.whl → 0.0.82.dev776__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dv-pipecat-ai might be problematic. Click here for more details.
- {dv_pipecat_ai-0.0.74.dev770.dist-info → dv_pipecat_ai-0.0.82.dev776.dist-info}/METADATA +137 -93
- dv_pipecat_ai-0.0.82.dev776.dist-info/RECORD +340 -0
- pipecat/__init__.py +17 -0
- pipecat/adapters/base_llm_adapter.py +36 -1
- pipecat/adapters/schemas/direct_function.py +296 -0
- pipecat/adapters/schemas/function_schema.py +15 -6
- pipecat/adapters/schemas/tools_schema.py +55 -7
- pipecat/adapters/services/anthropic_adapter.py +22 -3
- pipecat/adapters/services/aws_nova_sonic_adapter.py +23 -3
- pipecat/adapters/services/bedrock_adapter.py +22 -3
- pipecat/adapters/services/gemini_adapter.py +16 -3
- pipecat/adapters/services/open_ai_adapter.py +17 -2
- pipecat/adapters/services/open_ai_realtime_adapter.py +23 -3
- pipecat/audio/filters/base_audio_filter.py +30 -6
- pipecat/audio/filters/koala_filter.py +37 -2
- pipecat/audio/filters/krisp_filter.py +59 -6
- pipecat/audio/filters/noisereduce_filter.py +37 -0
- pipecat/audio/interruptions/base_interruption_strategy.py +25 -5
- pipecat/audio/interruptions/min_words_interruption_strategy.py +21 -4
- pipecat/audio/mixers/base_audio_mixer.py +30 -7
- pipecat/audio/mixers/soundfile_mixer.py +53 -6
- pipecat/audio/resamplers/base_audio_resampler.py +17 -9
- pipecat/audio/resamplers/resampy_resampler.py +26 -1
- pipecat/audio/resamplers/soxr_resampler.py +32 -1
- pipecat/audio/resamplers/soxr_stream_resampler.py +101 -0
- pipecat/audio/utils.py +194 -1
- pipecat/audio/vad/silero.py +60 -3
- pipecat/audio/vad/vad_analyzer.py +114 -30
- pipecat/clocks/base_clock.py +19 -0
- pipecat/clocks/system_clock.py +25 -0
- pipecat/extensions/voicemail/__init__.py +0 -0
- pipecat/extensions/voicemail/voicemail_detector.py +707 -0
- pipecat/frames/frames.py +590 -156
- pipecat/metrics/metrics.py +64 -1
- pipecat/observers/base_observer.py +58 -19
- pipecat/observers/loggers/debug_log_observer.py +56 -64
- pipecat/observers/loggers/llm_log_observer.py +8 -1
- pipecat/observers/loggers/transcription_log_observer.py +19 -7
- pipecat/observers/loggers/user_bot_latency_log_observer.py +32 -5
- pipecat/observers/turn_tracking_observer.py +26 -1
- pipecat/pipeline/base_pipeline.py +5 -7
- pipecat/pipeline/base_task.py +52 -9
- pipecat/pipeline/parallel_pipeline.py +121 -177
- pipecat/pipeline/pipeline.py +129 -20
- pipecat/pipeline/runner.py +50 -1
- pipecat/pipeline/sync_parallel_pipeline.py +132 -32
- pipecat/pipeline/task.py +263 -280
- pipecat/pipeline/task_observer.py +85 -34
- pipecat/pipeline/to_be_updated/merge_pipeline.py +32 -2
- pipecat/processors/aggregators/dtmf_aggregator.py +29 -22
- pipecat/processors/aggregators/gated.py +25 -24
- pipecat/processors/aggregators/gated_openai_llm_context.py +22 -2
- pipecat/processors/aggregators/llm_response.py +398 -89
- pipecat/processors/aggregators/openai_llm_context.py +161 -13
- pipecat/processors/aggregators/sentence.py +25 -14
- pipecat/processors/aggregators/user_response.py +28 -3
- pipecat/processors/aggregators/vision_image_frame.py +24 -14
- pipecat/processors/async_generator.py +28 -0
- pipecat/processors/audio/audio_buffer_processor.py +78 -37
- pipecat/processors/consumer_processor.py +25 -6
- pipecat/processors/filters/frame_filter.py +23 -0
- pipecat/processors/filters/function_filter.py +30 -0
- pipecat/processors/filters/identity_filter.py +17 -2
- pipecat/processors/filters/null_filter.py +24 -1
- pipecat/processors/filters/stt_mute_filter.py +56 -21
- pipecat/processors/filters/wake_check_filter.py +46 -3
- pipecat/processors/filters/wake_notifier_filter.py +21 -3
- pipecat/processors/frame_processor.py +488 -131
- pipecat/processors/frameworks/langchain.py +38 -3
- pipecat/processors/frameworks/rtvi.py +719 -34
- pipecat/processors/gstreamer/pipeline_source.py +41 -0
- pipecat/processors/idle_frame_processor.py +26 -3
- pipecat/processors/logger.py +23 -0
- pipecat/processors/metrics/frame_processor_metrics.py +77 -4
- pipecat/processors/metrics/sentry.py +42 -4
- pipecat/processors/producer_processor.py +34 -14
- pipecat/processors/text_transformer.py +22 -10
- pipecat/processors/transcript_processor.py +48 -29
- pipecat/processors/user_idle_processor.py +31 -21
- pipecat/runner/__init__.py +1 -0
- pipecat/runner/daily.py +132 -0
- pipecat/runner/livekit.py +148 -0
- pipecat/runner/run.py +543 -0
- pipecat/runner/types.py +67 -0
- pipecat/runner/utils.py +515 -0
- pipecat/serializers/base_serializer.py +42 -0
- pipecat/serializers/exotel.py +17 -6
- pipecat/serializers/genesys.py +95 -0
- pipecat/serializers/livekit.py +33 -0
- pipecat/serializers/plivo.py +16 -15
- pipecat/serializers/protobuf.py +37 -1
- pipecat/serializers/telnyx.py +18 -17
- pipecat/serializers/twilio.py +32 -16
- pipecat/services/ai_service.py +5 -3
- pipecat/services/anthropic/llm.py +113 -43
- pipecat/services/assemblyai/models.py +63 -5
- pipecat/services/assemblyai/stt.py +64 -11
- pipecat/services/asyncai/__init__.py +0 -0
- pipecat/services/asyncai/tts.py +501 -0
- pipecat/services/aws/llm.py +185 -111
- pipecat/services/aws/stt.py +217 -23
- pipecat/services/aws/tts.py +118 -52
- pipecat/services/aws/utils.py +101 -5
- pipecat/services/aws_nova_sonic/aws.py +82 -64
- pipecat/services/aws_nova_sonic/context.py +15 -6
- pipecat/services/azure/common.py +10 -2
- pipecat/services/azure/image.py +32 -0
- pipecat/services/azure/llm.py +9 -7
- pipecat/services/azure/stt.py +65 -2
- pipecat/services/azure/tts.py +154 -23
- pipecat/services/cartesia/stt.py +125 -8
- pipecat/services/cartesia/tts.py +102 -38
- pipecat/services/cerebras/llm.py +15 -23
- pipecat/services/deepgram/stt.py +19 -11
- pipecat/services/deepgram/tts.py +36 -0
- pipecat/services/deepseek/llm.py +14 -23
- pipecat/services/elevenlabs/tts.py +330 -64
- pipecat/services/fal/image.py +43 -0
- pipecat/services/fal/stt.py +48 -10
- pipecat/services/fireworks/llm.py +14 -21
- pipecat/services/fish/tts.py +109 -9
- pipecat/services/gemini_multimodal_live/__init__.py +1 -0
- pipecat/services/gemini_multimodal_live/events.py +83 -2
- pipecat/services/gemini_multimodal_live/file_api.py +189 -0
- pipecat/services/gemini_multimodal_live/gemini.py +218 -21
- pipecat/services/gladia/config.py +17 -10
- pipecat/services/gladia/stt.py +82 -36
- pipecat/services/google/frames.py +40 -0
- pipecat/services/google/google.py +2 -0
- pipecat/services/google/image.py +39 -2
- pipecat/services/google/llm.py +176 -58
- pipecat/services/google/llm_openai.py +26 -4
- pipecat/services/google/llm_vertex.py +37 -15
- pipecat/services/google/rtvi.py +41 -0
- pipecat/services/google/stt.py +65 -17
- pipecat/services/google/test-google-chirp.py +45 -0
- pipecat/services/google/tts.py +390 -19
- pipecat/services/grok/llm.py +8 -6
- pipecat/services/groq/llm.py +8 -6
- pipecat/services/groq/stt.py +13 -9
- pipecat/services/groq/tts.py +40 -0
- pipecat/services/hamsa/__init__.py +9 -0
- pipecat/services/hamsa/stt.py +241 -0
- pipecat/services/heygen/__init__.py +5 -0
- pipecat/services/heygen/api.py +281 -0
- pipecat/services/heygen/client.py +620 -0
- pipecat/services/heygen/video.py +338 -0
- pipecat/services/image_service.py +5 -3
- pipecat/services/inworld/__init__.py +1 -0
- pipecat/services/inworld/tts.py +592 -0
- pipecat/services/llm_service.py +127 -45
- pipecat/services/lmnt/tts.py +80 -7
- pipecat/services/mcp_service.py +85 -44
- pipecat/services/mem0/memory.py +42 -13
- pipecat/services/minimax/tts.py +74 -15
- pipecat/services/mistral/__init__.py +0 -0
- pipecat/services/mistral/llm.py +185 -0
- pipecat/services/moondream/vision.py +55 -10
- pipecat/services/neuphonic/tts.py +275 -48
- pipecat/services/nim/llm.py +8 -6
- pipecat/services/ollama/llm.py +27 -7
- pipecat/services/openai/base_llm.py +54 -16
- pipecat/services/openai/image.py +30 -0
- pipecat/services/openai/llm.py +7 -5
- pipecat/services/openai/stt.py +13 -9
- pipecat/services/openai/tts.py +42 -10
- pipecat/services/openai_realtime_beta/azure.py +11 -9
- pipecat/services/openai_realtime_beta/context.py +7 -5
- pipecat/services/openai_realtime_beta/events.py +10 -7
- pipecat/services/openai_realtime_beta/openai.py +37 -18
- pipecat/services/openpipe/llm.py +30 -24
- pipecat/services/openrouter/llm.py +9 -7
- pipecat/services/perplexity/llm.py +15 -19
- pipecat/services/piper/tts.py +26 -12
- pipecat/services/playht/tts.py +227 -65
- pipecat/services/qwen/llm.py +8 -6
- pipecat/services/rime/tts.py +128 -17
- pipecat/services/riva/stt.py +160 -22
- pipecat/services/riva/tts.py +67 -2
- pipecat/services/sambanova/llm.py +19 -17
- pipecat/services/sambanova/stt.py +14 -8
- pipecat/services/sarvam/tts.py +60 -13
- pipecat/services/simli/video.py +82 -21
- pipecat/services/soniox/__init__.py +0 -0
- pipecat/services/soniox/stt.py +398 -0
- pipecat/services/speechmatics/stt.py +29 -17
- pipecat/services/stt_service.py +47 -11
- pipecat/services/tavus/video.py +94 -25
- pipecat/services/together/llm.py +8 -6
- pipecat/services/tts_service.py +77 -53
- pipecat/services/ultravox/stt.py +46 -43
- pipecat/services/vision_service.py +5 -3
- pipecat/services/websocket_service.py +12 -11
- pipecat/services/whisper/base_stt.py +58 -12
- pipecat/services/whisper/stt.py +69 -58
- pipecat/services/xtts/tts.py +59 -2
- pipecat/sync/base_notifier.py +19 -0
- pipecat/sync/event_notifier.py +24 -0
- pipecat/tests/utils.py +73 -5
- pipecat/transcriptions/language.py +24 -0
- pipecat/transports/base_input.py +112 -8
- pipecat/transports/base_output.py +235 -13
- pipecat/transports/base_transport.py +119 -0
- pipecat/transports/local/audio.py +76 -0
- pipecat/transports/local/tk.py +84 -0
- pipecat/transports/network/fastapi_websocket.py +174 -15
- pipecat/transports/network/small_webrtc.py +383 -39
- pipecat/transports/network/webrtc_connection.py +214 -8
- pipecat/transports/network/websocket_client.py +171 -1
- pipecat/transports/network/websocket_server.py +147 -9
- pipecat/transports/services/daily.py +792 -70
- pipecat/transports/services/helpers/daily_rest.py +122 -129
- pipecat/transports/services/livekit.py +339 -4
- pipecat/transports/services/tavus.py +273 -38
- pipecat/utils/asyncio/task_manager.py +92 -186
- pipecat/utils/base_object.py +83 -1
- pipecat/utils/network.py +2 -0
- pipecat/utils/string.py +114 -58
- pipecat/utils/text/base_text_aggregator.py +44 -13
- pipecat/utils/text/base_text_filter.py +46 -0
- pipecat/utils/text/markdown_text_filter.py +70 -14
- pipecat/utils/text/pattern_pair_aggregator.py +18 -14
- pipecat/utils/text/simple_text_aggregator.py +43 -2
- pipecat/utils/text/skip_tags_aggregator.py +21 -13
- pipecat/utils/time.py +36 -0
- pipecat/utils/tracing/class_decorators.py +32 -7
- pipecat/utils/tracing/conversation_context_provider.py +12 -2
- pipecat/utils/tracing/service_attributes.py +80 -64
- pipecat/utils/tracing/service_decorators.py +48 -21
- pipecat/utils/tracing/setup.py +13 -7
- pipecat/utils/tracing/turn_context_provider.py +12 -2
- pipecat/utils/tracing/turn_trace_observer.py +27 -0
- pipecat/utils/utils.py +14 -14
- dv_pipecat_ai-0.0.74.dev770.dist-info/RECORD +0 -319
- pipecat/examples/daily_runner.py +0 -64
- pipecat/examples/run.py +0 -265
- pipecat/utils/asyncio/watchdog_async_iterator.py +0 -72
- pipecat/utils/asyncio/watchdog_event.py +0 -42
- pipecat/utils/asyncio/watchdog_priority_queue.py +0 -48
- pipecat/utils/asyncio/watchdog_queue.py +0 -48
- {dv_pipecat_ai-0.0.74.dev770.dist-info → dv_pipecat_ai-0.0.82.dev776.dist-info}/WHEEL +0 -0
- {dv_pipecat_ai-0.0.74.dev770.dist-info → dv_pipecat_ai-0.0.82.dev776.dist-info}/licenses/LICENSE +0 -0
- {dv_pipecat_ai-0.0.74.dev770.dist-info → dv_pipecat_ai-0.0.82.dev776.dist-info}/top_level.txt +0 -0
- /pipecat/{examples → extensions}/__init__.py +0 -0
pipecat/utils/string.py
CHANGED
|
@@ -4,44 +4,92 @@
|
|
|
4
4
|
# SPDX-License-Identifier: BSD 2-Clause License
|
|
5
5
|
#
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
from typing import Optional, Sequence, Tuple
|
|
9
|
-
|
|
10
|
-
ENDOFSENTENCE_PATTERN_STR = r"""
|
|
11
|
-
(?<![A-Z]) # Negative lookbehind: not preceded by an uppercase letter (e.g., "U.S.A.")
|
|
12
|
-
(?<!\d\.\d) # Not preceded by a decimal number (e.g., "3.14159")
|
|
13
|
-
(?<!^\d\.) # Not preceded by a numbered list item (e.g., "1. Let's start")
|
|
14
|
-
(?<!\d\s[ap]) # Negative lookbehind: not preceded by time (e.g., "3:00 a.m.")
|
|
15
|
-
(?<!Mr|Ms|Dr) # Negative lookbehind: not preceded by Mr, Ms, Dr (combined bc. length is the same)
|
|
16
|
-
(?<!Mrs) # Negative lookbehind: not preceded by "Mrs"
|
|
17
|
-
(?<!Prof) # Negative lookbehind: not preceded by "Prof"
|
|
18
|
-
(\.\s*\.\s*\.|[\.\?\!;])| # Match a period, question mark, exclamation point, or semicolon
|
|
19
|
-
(\。\s*\。\s*\。|[。?!;।]) # the full-width version (mainly used in East Asian languages such as Chinese, Hindi)
|
|
20
|
-
$ # End of string
|
|
21
|
-
"""
|
|
7
|
+
"""Text processing utilities for sentence boundary detection and tag parsing.
|
|
22
8
|
|
|
23
|
-
|
|
9
|
+
This module provides utilities for natural language text processing including
|
|
10
|
+
sentence boundary detection, email and number pattern handling, and XML-style
|
|
11
|
+
tag parsing for structured text content.
|
|
24
12
|
|
|
25
|
-
|
|
13
|
+
Dependencies:
|
|
14
|
+
This module uses NLTK (Natural Language Toolkit) for robust sentence
|
|
15
|
+
tokenization. NLTK is licensed under the Apache License 2.0.
|
|
16
|
+
See: https://www.nltk.org/
|
|
17
|
+
Source: https://www.nltk.org/api/nltk.tokenize.punkt.html
|
|
18
|
+
"""
|
|
26
19
|
|
|
27
|
-
|
|
20
|
+
import re
|
|
21
|
+
from typing import FrozenSet, Optional, Sequence, Tuple
|
|
22
|
+
|
|
23
|
+
import nltk
|
|
24
|
+
from nltk.tokenize import sent_tokenize
|
|
25
|
+
|
|
26
|
+
# Ensure punkt_tab tokenizer data is available
|
|
27
|
+
try:
|
|
28
|
+
nltk.data.find("tokenizers/punkt_tab")
|
|
29
|
+
except LookupError:
|
|
30
|
+
nltk.download("punkt_tab", quiet=True)
|
|
31
|
+
|
|
32
|
+
SENTENCE_ENDING_PUNCTUATION: FrozenSet[str] = frozenset(
|
|
33
|
+
{
|
|
34
|
+
# Latin script punctuation (most European languages, Filipino, etc.)
|
|
35
|
+
".",
|
|
36
|
+
"!",
|
|
37
|
+
"?",
|
|
38
|
+
";",
|
|
39
|
+
# East Asian punctuation (Chinese (Traditional & Simplified), Japanese, Korean)
|
|
40
|
+
"。", # Ideographic full stop
|
|
41
|
+
"?", # Full-width question mark
|
|
42
|
+
"!", # Full-width exclamation mark
|
|
43
|
+
";", # Full-width semicolon
|
|
44
|
+
".", # Full-width period
|
|
45
|
+
"。", # Halfwidth ideographic period
|
|
46
|
+
# Indic scripts punctuation (Hindi, Sanskrit, Marathi, Nepali, Bengali, Tamil, Telugu, Kannada, Malayalam, Gujarati, Punjabi, Oriya, Assamese)
|
|
47
|
+
"।", # Devanagari danda (single vertical bar)
|
|
48
|
+
"॥", # Devanagari double danda (double vertical bar)
|
|
49
|
+
# Arabic script punctuation (Arabic, Persian, Urdu, Pashto)
|
|
50
|
+
"؟", # Arabic question mark
|
|
51
|
+
"؛", # Arabic semicolon
|
|
52
|
+
"۔", # Urdu full stop
|
|
53
|
+
"؏", # Arabic sign misra (classical texts)
|
|
54
|
+
# Thai
|
|
55
|
+
"।", # Thai uses Devanagari-style punctuation in some contexts
|
|
56
|
+
# Myanmar/Burmese
|
|
57
|
+
"၊", # Myanmar sign little section
|
|
58
|
+
"။", # Myanmar sign section
|
|
59
|
+
# Khmer
|
|
60
|
+
"។", # Khmer sign khan
|
|
61
|
+
"៕", # Khmer sign bariyoosan
|
|
62
|
+
# Lao
|
|
63
|
+
"໌", # Lao cancellation mark (used as period)
|
|
64
|
+
"༎", # Tibetan mark delimiter tsheg bstar (also used in Lao contexts)
|
|
65
|
+
# Tibetan
|
|
66
|
+
"།", # Tibetan mark intersyllabic tsheg
|
|
67
|
+
"༎", # Tibetan mark delimiter tsheg bstar
|
|
68
|
+
# Armenian
|
|
69
|
+
"։", # Armenian full stop
|
|
70
|
+
"՜", # Armenian exclamation mark
|
|
71
|
+
"՞", # Armenian question mark
|
|
72
|
+
# Ethiopic script (Amharic)
|
|
73
|
+
"።", # Ethiopic full stop
|
|
74
|
+
"፧", # Ethiopic question mark
|
|
75
|
+
"፨", # Ethiopic paragraph separator
|
|
76
|
+
}
|
|
77
|
+
)
|
|
28
78
|
|
|
29
79
|
StartEndTags = Tuple[str, str]
|
|
30
80
|
|
|
31
81
|
|
|
32
82
|
def replace_match(text: str, match: re.Match, old: str, new: str) -> str:
|
|
33
|
-
"""Replace occurrences of a substring within a matched section of
|
|
34
|
-
text.
|
|
83
|
+
"""Replace occurrences of a substring within a matched section of text.
|
|
35
84
|
|
|
36
85
|
Args:
|
|
37
|
-
text
|
|
38
|
-
match
|
|
39
|
-
old
|
|
40
|
-
new
|
|
86
|
+
text: The input text in which replacements will be made.
|
|
87
|
+
match: A regex match object representing the section of text to modify.
|
|
88
|
+
old: The substring to be replaced.
|
|
89
|
+
new: The substring to replace `old` with.
|
|
41
90
|
|
|
42
91
|
Returns:
|
|
43
|
-
|
|
44
|
-
|
|
92
|
+
The modified text with the specified replacements made within the matched section.
|
|
45
93
|
"""
|
|
46
94
|
start = match.start()
|
|
47
95
|
end = match.end()
|
|
@@ -51,37 +99,47 @@ def replace_match(text: str, match: re.Match, old: str, new: str) -> str:
|
|
|
51
99
|
|
|
52
100
|
|
|
53
101
|
def match_endofsentence(text: str) -> int:
|
|
54
|
-
"""
|
|
102
|
+
"""Find the position of the end of a sentence in the provided text.
|
|
55
103
|
|
|
56
|
-
This function
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
sentence using a specified regex pattern.
|
|
104
|
+
This function uses NLTK's sentence tokenizer to detect sentence boundaries
|
|
105
|
+
in the input text, combined with punctuation verification to ensure that
|
|
106
|
+
single tokens without proper sentence endings aren't considered complete sentences.
|
|
60
107
|
|
|
61
108
|
Args:
|
|
62
|
-
text
|
|
109
|
+
text: The input text in which to find the end of the sentence.
|
|
63
110
|
|
|
64
111
|
Returns:
|
|
65
|
-
|
|
66
|
-
|
|
112
|
+
The position of the end of the sentence if found, otherwise 0.
|
|
67
113
|
"""
|
|
68
114
|
text = text.rstrip()
|
|
69
115
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
emails = list(EMAIL_PATTERN.finditer(text))
|
|
73
|
-
for email_match in emails:
|
|
74
|
-
text = replace_match(text, email_match, ".", "&")
|
|
116
|
+
if not text:
|
|
117
|
+
return 0
|
|
75
118
|
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
-
for number_match in numbers:
|
|
79
|
-
text = replace_match(text, number_match, ".", "&")
|
|
119
|
+
# Use NLTK's sentence tokenizer to find sentence boundaries
|
|
120
|
+
sentences = sent_tokenize(text)
|
|
80
121
|
|
|
81
|
-
|
|
82
|
-
|
|
122
|
+
if not sentences:
|
|
123
|
+
return 0
|
|
83
124
|
|
|
84
|
-
|
|
125
|
+
first_sentence = sentences[0]
|
|
126
|
+
|
|
127
|
+
# If there's only one sentence that equals the entire text,
|
|
128
|
+
# verify it actually ends with sentence-ending punctuation.
|
|
129
|
+
# This is required as NLTK may return a single sentence for
|
|
130
|
+
# text that's a single word. In the case of LLM tokens, it's
|
|
131
|
+
# common for text to be single words, so we need to ensure
|
|
132
|
+
# sentence-ending punctuation is present.
|
|
133
|
+
if len(sentences) == 1 and first_sentence == text:
|
|
134
|
+
return len(text) if text and text[-1] in SENTENCE_ENDING_PUNCTUATION else 0
|
|
135
|
+
|
|
136
|
+
# If there are multiple sentences, the first one is complete by definition
|
|
137
|
+
# (NLTK found a boundary, so there must be proper punctuation)
|
|
138
|
+
if len(sentences) > 1:
|
|
139
|
+
return len(first_sentence)
|
|
140
|
+
|
|
141
|
+
# Single sentence that doesn't equal the full text means incomplete
|
|
142
|
+
return 0
|
|
85
143
|
|
|
86
144
|
|
|
87
145
|
def parse_start_end_tags(
|
|
@@ -90,24 +148,22 @@ def parse_start_end_tags(
|
|
|
90
148
|
current_tag: Optional[StartEndTags],
|
|
91
149
|
current_tag_index: int,
|
|
92
150
|
) -> Tuple[Optional[StartEndTags], int]:
|
|
93
|
-
"""
|
|
151
|
+
"""Parse text to identify start and end tag pairs.
|
|
94
152
|
|
|
95
|
-
If a start tag was previously found (i.e
|
|
96
|
-
the corresponding end tag.
|
|
153
|
+
If a start tag was previously found (i.e., current_tag is valid), wait for
|
|
154
|
+
the corresponding end tag. Otherwise, wait for a start tag.
|
|
97
155
|
|
|
98
|
-
This function
|
|
156
|
+
This function returns the index in the text where parsing should continue
|
|
99
157
|
in the next call and the current or new tags.
|
|
100
158
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
159
|
+
Args:
|
|
160
|
+
text: The text to be parsed.
|
|
161
|
+
tags: List of tuples containing start and end tags.
|
|
162
|
+
current_tag: The currently active tags, if any.
|
|
163
|
+
current_tag_index: The current index in the text.
|
|
106
164
|
|
|
107
165
|
Returns:
|
|
108
|
-
|
|
109
|
-
tag and the index of the text.
|
|
110
|
-
|
|
166
|
+
A tuple containing None or the current tag and the index of the text.
|
|
111
167
|
"""
|
|
112
168
|
# If we are already inside a tag, check if the end tag is in the text.
|
|
113
169
|
if current_tag:
|
|
@@ -4,54 +4,85 @@
|
|
|
4
4
|
# SPDX-License-Identifier: BSD 2-Clause License
|
|
5
5
|
#
|
|
6
6
|
|
|
7
|
+
"""Base text aggregator interface for Pipecat text processing.
|
|
8
|
+
|
|
9
|
+
This module defines the abstract base class for text aggregators that accumulate
|
|
10
|
+
and process text tokens, typically used by TTS services to determine when
|
|
11
|
+
aggregated text should be sent for speech synthesis.
|
|
12
|
+
"""
|
|
13
|
+
|
|
7
14
|
from abc import ABC, abstractmethod
|
|
8
15
|
from typing import Optional
|
|
9
16
|
|
|
10
17
|
|
|
11
18
|
class BaseTextAggregator(ABC):
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
"""Base class for text aggregators in the Pipecat framework.
|
|
20
|
+
|
|
21
|
+
Text aggregators are usually used by the TTS service to aggregate LLM tokens
|
|
22
|
+
and decide when the aggregated text should be pushed to the TTS service.
|
|
15
23
|
|
|
16
24
|
Text aggregators can also be used to manipulate text while it's being
|
|
17
25
|
aggregated (e.g. reasoning blocks can be removed).
|
|
18
26
|
|
|
27
|
+
Subclasses must implement all abstract methods to define specific aggregation
|
|
28
|
+
logic, text manipulation behavior, and state management for interruptions.
|
|
19
29
|
"""
|
|
20
30
|
|
|
21
31
|
@property
|
|
22
32
|
@abstractmethod
|
|
23
33
|
def text(self) -> str:
|
|
24
|
-
"""
|
|
34
|
+
"""Get the currently aggregated text.
|
|
35
|
+
|
|
36
|
+
Subclasses must implement this property to return the text that has
|
|
37
|
+
been accumulated so far in their internal buffer or storage.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
The text that has been accumulated so far.
|
|
41
|
+
"""
|
|
25
42
|
pass
|
|
26
43
|
|
|
27
44
|
@abstractmethod
|
|
28
45
|
async def aggregate(self, text: str) -> Optional[str]:
|
|
29
|
-
"""
|
|
46
|
+
"""Aggregate the specified text with the currently accumulated text.
|
|
30
47
|
|
|
31
48
|
This method should be implemented to define how the new text contributes
|
|
32
49
|
to the aggregation process. It returns the updated aggregated text if
|
|
33
50
|
it's ready to be processed, or None otherwise.
|
|
34
51
|
|
|
52
|
+
Subclasses should implement their specific logic for:
|
|
53
|
+
|
|
54
|
+
- How to combine new text with existing accumulated text
|
|
55
|
+
- When to consider the aggregated text ready for processing
|
|
56
|
+
- What criteria determine text completion (e.g., sentence boundaries)
|
|
57
|
+
|
|
35
58
|
Args:
|
|
36
|
-
text
|
|
59
|
+
text: The text to be aggregated.
|
|
37
60
|
|
|
38
61
|
Returns:
|
|
39
|
-
|
|
40
|
-
text is
|
|
41
|
-
|
|
62
|
+
The updated aggregated text if ready for processing, or None if more
|
|
63
|
+
text is needed before the aggregated content is ready.
|
|
42
64
|
"""
|
|
43
65
|
pass
|
|
44
66
|
|
|
45
67
|
@abstractmethod
|
|
46
68
|
async def handle_interruption(self):
|
|
47
|
-
"""
|
|
48
|
-
that we might want to discard the aggregated text or do some internal
|
|
49
|
-
modifications to the aggregated text.
|
|
69
|
+
"""Handle interruptions in the text aggregation process.
|
|
50
70
|
|
|
71
|
+
When an interruption occurs it is possible that we might want to discard
|
|
72
|
+
the aggregated text or do some internal modifications to the aggregated text.
|
|
73
|
+
|
|
74
|
+
Subclasses should implement this method to define how they respond to
|
|
75
|
+
interruptions, such as clearing buffers, resetting state, or preserving
|
|
76
|
+
partial content.
|
|
51
77
|
"""
|
|
52
78
|
pass
|
|
53
79
|
|
|
54
80
|
@abstractmethod
|
|
55
81
|
async def reset(self):
|
|
56
|
-
"""
|
|
82
|
+
"""Clear the internally aggregated text and reset to initial state.
|
|
83
|
+
|
|
84
|
+
Subclasses should implement this method to return the aggregator to its
|
|
85
|
+
initial state, discarding any previously accumulated text content and
|
|
86
|
+
resetting any internal tracking variables.
|
|
87
|
+
"""
|
|
57
88
|
pass
|
|
@@ -4,23 +4,69 @@
|
|
|
4
4
|
# SPDX-License-Identifier: BSD 2-Clause License
|
|
5
5
|
#
|
|
6
6
|
|
|
7
|
+
"""Base text filter interface for Pipecat text processing.
|
|
8
|
+
|
|
9
|
+
This module defines the abstract base class for text filters that can modify
|
|
10
|
+
text content in the processing pipeline, including support for settings updates
|
|
11
|
+
and interruption handling.
|
|
12
|
+
"""
|
|
13
|
+
|
|
7
14
|
from abc import ABC, abstractmethod
|
|
8
15
|
from typing import Any, Mapping
|
|
9
16
|
|
|
10
17
|
|
|
11
18
|
class BaseTextFilter(ABC):
|
|
19
|
+
"""Abstract base class for text filters in the Pipecat framework.
|
|
20
|
+
|
|
21
|
+
Text filters are responsible for modifying text content as it flows through
|
|
22
|
+
the processing pipeline. They support dynamic settings updates and can handle
|
|
23
|
+
interruptions to reset their internal state.
|
|
24
|
+
|
|
25
|
+
Subclasses must implement all abstract methods to define specific filtering
|
|
26
|
+
behavior, settings management, and interruption handling logic.
|
|
27
|
+
"""
|
|
28
|
+
|
|
12
29
|
@abstractmethod
|
|
13
30
|
async def update_settings(self, settings: Mapping[str, Any]):
|
|
31
|
+
"""Update the filter's configuration settings.
|
|
32
|
+
|
|
33
|
+
Subclasses should implement this method to handle dynamic configuration
|
|
34
|
+
updates during runtime, updating internal state as needed.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
settings: Dictionary of setting names to values for configuration.
|
|
38
|
+
"""
|
|
14
39
|
pass
|
|
15
40
|
|
|
16
41
|
@abstractmethod
|
|
17
42
|
async def filter(self, text: str) -> str:
|
|
43
|
+
"""Apply filtering transformations to the input text.
|
|
44
|
+
|
|
45
|
+
Subclasses must implement this method to define the specific text
|
|
46
|
+
transformations that should be applied to the input.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
text: The input text to be filtered.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
The filtered text after applying transformations.
|
|
53
|
+
"""
|
|
18
54
|
pass
|
|
19
55
|
|
|
20
56
|
@abstractmethod
|
|
21
57
|
async def handle_interruption(self):
|
|
58
|
+
"""Handle interruption events in the processing pipeline.
|
|
59
|
+
|
|
60
|
+
Subclasses should implement this method to reset internal state,
|
|
61
|
+
clear buffers, or perform other cleanup when an interruption occurs.
|
|
62
|
+
"""
|
|
22
63
|
pass
|
|
23
64
|
|
|
24
65
|
@abstractmethod
|
|
25
66
|
async def reset_interruption(self):
|
|
67
|
+
"""Reset the filter state after an interruption has been handled.
|
|
68
|
+
|
|
69
|
+
Subclasses should implement this method to restore the filter to normal
|
|
70
|
+
operation after an interruption has been processed and resolved.
|
|
71
|
+
"""
|
|
26
72
|
pass
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
# SPDX-License-Identifier: BSD 2-Clause License
|
|
5
5
|
#
|
|
6
6
|
|
|
7
|
+
"""Markdown text filter for removing Markdown formatting from text.
|
|
8
|
+
|
|
9
|
+
This module provides a text filter that converts Markdown content to plain text
|
|
10
|
+
while preserving structure and handling special cases like code blocks and tables.
|
|
11
|
+
"""
|
|
12
|
+
|
|
7
13
|
import re
|
|
8
14
|
from typing import Any, Mapping, Optional
|
|
9
15
|
|
|
@@ -14,19 +20,34 @@ from pipecat.utils.text.base_text_filter import BaseTextFilter
|
|
|
14
20
|
|
|
15
21
|
|
|
16
22
|
class MarkdownTextFilter(BaseTextFilter):
|
|
17
|
-
"""
|
|
23
|
+
"""Text filter that removes Markdown formatting from text content.
|
|
18
24
|
|
|
19
25
|
Converts Markdown to plain text while preserving the overall structure,
|
|
20
26
|
including leading and trailing spaces. Handles special cases like
|
|
21
|
-
asterisks and table formatting.
|
|
27
|
+
asterisks and table formatting. Supports selective filtering of code
|
|
28
|
+
blocks and tables based on configuration.
|
|
22
29
|
"""
|
|
23
30
|
|
|
24
31
|
class InputParams(BaseModel):
|
|
32
|
+
"""Configuration parameters for Markdown text filtering.
|
|
33
|
+
|
|
34
|
+
Parameters:
|
|
35
|
+
enable_text_filter: Whether to apply Markdown filtering. Defaults to True.
|
|
36
|
+
filter_code: Whether to remove code blocks from the text. Defaults to False.
|
|
37
|
+
filter_tables: Whether to remove table content from the text. Defaults to False.
|
|
38
|
+
"""
|
|
39
|
+
|
|
25
40
|
enable_text_filter: Optional[bool] = True
|
|
26
41
|
filter_code: Optional[bool] = False
|
|
27
42
|
filter_tables: Optional[bool] = False
|
|
28
43
|
|
|
29
44
|
def __init__(self, params: Optional[InputParams] = None, **kwargs):
|
|
45
|
+
"""Initialize the Markdown text filter.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
params: Configuration parameters for filtering behavior.
|
|
49
|
+
**kwargs: Additional keyword arguments passed to parent class.
|
|
50
|
+
"""
|
|
30
51
|
super().__init__(**kwargs)
|
|
31
52
|
self._settings = params or MarkdownTextFilter.InputParams()
|
|
32
53
|
self._in_code_block = False
|
|
@@ -34,11 +55,24 @@ class MarkdownTextFilter(BaseTextFilter):
|
|
|
34
55
|
self._interrupted = False
|
|
35
56
|
|
|
36
57
|
async def update_settings(self, settings: Mapping[str, Any]):
|
|
58
|
+
"""Update the filter's configuration settings.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
settings: Dictionary of setting names to values for configuration.
|
|
62
|
+
"""
|
|
37
63
|
for key, value in settings.items():
|
|
38
64
|
if hasattr(self._settings, key):
|
|
39
65
|
setattr(self._settings, key, value)
|
|
40
66
|
|
|
41
67
|
async def filter(self, text: str) -> str:
|
|
68
|
+
"""Apply Markdown filtering transformations to the input text.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
text: The input text containing Markdown formatting to be filtered.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
The filtered text with Markdown formatting removed or converted.
|
|
75
|
+
"""
|
|
42
76
|
if self._settings.enable_text_filter:
|
|
43
77
|
# Remove newlines and replace with a space only when there's no text before or after
|
|
44
78
|
filtered_text = re.sub(r"^\s*\n", " ", text, flags=re.MULTILINE)
|
|
@@ -108,11 +142,20 @@ class MarkdownTextFilter(BaseTextFilter):
|
|
|
108
142
|
return text
|
|
109
143
|
|
|
110
144
|
async def handle_interruption(self):
|
|
145
|
+
"""Handle interruption events in the processing pipeline.
|
|
146
|
+
|
|
147
|
+
Resets the filter state and clears any tracking variables for
|
|
148
|
+
code blocks and tables.
|
|
149
|
+
"""
|
|
111
150
|
self._interrupted = True
|
|
112
151
|
self._in_code_block = False
|
|
113
152
|
self._in_table = False
|
|
114
153
|
|
|
115
154
|
async def reset_interruption(self):
|
|
155
|
+
"""Reset the filter state after an interruption has been handled.
|
|
156
|
+
|
|
157
|
+
Clears the interrupted flag to restore normal operation.
|
|
158
|
+
"""
|
|
116
159
|
self._interrupted = False
|
|
117
160
|
|
|
118
161
|
#
|
|
@@ -120,8 +163,10 @@ class MarkdownTextFilter(BaseTextFilter):
|
|
|
120
163
|
#
|
|
121
164
|
|
|
122
165
|
def _remove_code_blocks(self, text: str) -> str:
|
|
123
|
-
"""
|
|
124
|
-
|
|
166
|
+
"""Remove code blocks from the input text.
|
|
167
|
+
|
|
168
|
+
Handles interruptions and delegates to specific methods based on the
|
|
169
|
+
current state.
|
|
125
170
|
"""
|
|
126
171
|
if self._interrupted:
|
|
127
172
|
self._in_code_block = False
|
|
@@ -137,8 +182,10 @@ class MarkdownTextFilter(BaseTextFilter):
|
|
|
137
182
|
return self._handle_not_in_code_block(match, text, code_block_pattern)
|
|
138
183
|
|
|
139
184
|
def _handle_in_code_block(self, match, text):
|
|
140
|
-
"""Handle text when
|
|
141
|
-
|
|
185
|
+
"""Handle text when not currently inside a code block.
|
|
186
|
+
|
|
187
|
+
If we find the end of the block, return text after it. Otherwise, skip
|
|
188
|
+
the content.
|
|
142
189
|
"""
|
|
143
190
|
if match:
|
|
144
191
|
self._in_code_block = False
|
|
@@ -147,9 +194,7 @@ class MarkdownTextFilter(BaseTextFilter):
|
|
|
147
194
|
return "" # Skip content inside code block
|
|
148
195
|
|
|
149
196
|
def _handle_not_in_code_block(self, match, text, code_block_pattern):
|
|
150
|
-
"""Handle text when
|
|
151
|
-
Delegate to specific methods based on whether we find a code block delimiter.
|
|
152
|
-
"""
|
|
197
|
+
"""Handle text when not currently inside a code block."""
|
|
153
198
|
if not match:
|
|
154
199
|
return text # No code block found, return original text
|
|
155
200
|
|
|
@@ -159,14 +204,17 @@ class MarkdownTextFilter(BaseTextFilter):
|
|
|
159
204
|
return self._handle_code_block_within_text(text, code_block_pattern)
|
|
160
205
|
|
|
161
206
|
def _handle_start_of_code_block(self, text, start_index):
|
|
162
|
-
"""Handle the case where
|
|
163
|
-
|
|
207
|
+
"""Handle the case where a code block starts.
|
|
208
|
+
|
|
209
|
+
Return any text before the code block and set the state to inside a
|
|
210
|
+
code block.
|
|
164
211
|
"""
|
|
165
212
|
self._in_code_block = True
|
|
166
213
|
return text[:start_index].strip()
|
|
167
214
|
|
|
168
215
|
def _handle_code_block_within_text(self, text, code_block_pattern):
|
|
169
|
-
"""Handle
|
|
216
|
+
"""Handle code blocks found within text content.
|
|
217
|
+
|
|
170
218
|
If it's a complete code block, remove it and return surrounding text.
|
|
171
219
|
If it's the start of a code block, return text before it and set state.
|
|
172
220
|
"""
|
|
@@ -180,8 +228,16 @@ class MarkdownTextFilter(BaseTextFilter):
|
|
|
180
228
|
# Filter tables
|
|
181
229
|
#
|
|
182
230
|
def remove_tables(self, text: str) -> str:
|
|
183
|
-
"""Remove tables from the input text
|
|
184
|
-
|
|
231
|
+
"""Remove HTML tables from the input text.
|
|
232
|
+
|
|
233
|
+
Handles cases where both start and end tags are in the same input,
|
|
234
|
+
as well as tables that span multiple text chunks.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
text: The text containing HTML tables to remove.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
The text with tables removed.
|
|
185
241
|
"""
|
|
186
242
|
if self._interrupted:
|
|
187
243
|
self._in_table = False
|
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
# SPDX-License-Identifier: BSD 2-Clause License
|
|
5
5
|
#
|
|
6
6
|
|
|
7
|
+
"""Pattern pair aggregator for processing structured content in streaming text.
|
|
8
|
+
|
|
9
|
+
This module provides an aggregator that identifies and processes content between
|
|
10
|
+
pattern pairs (like XML tags or custom delimiters) in streaming text, with
|
|
11
|
+
support for custom handlers and configurable pattern removal.
|
|
12
|
+
"""
|
|
13
|
+
|
|
7
14
|
import re
|
|
8
15
|
from typing import Awaitable, Callable, Optional, Tuple
|
|
9
16
|
|
|
@@ -20,20 +27,15 @@ class PatternMatch:
|
|
|
20
27
|
in the text. It contains information about which pattern was matched,
|
|
21
28
|
the full matched text (including start and end patterns), and the
|
|
22
29
|
content between the patterns.
|
|
23
|
-
|
|
24
|
-
Attributes:
|
|
25
|
-
pattern_id: The identifier of the matched pattern pair.
|
|
26
|
-
full_match: The complete text including start and end patterns.
|
|
27
|
-
content: The text content between the start and end patterns.
|
|
28
30
|
"""
|
|
29
31
|
|
|
30
32
|
def __init__(self, pattern_id: str, full_match: str, content: str):
|
|
31
33
|
"""Initialize a pattern match.
|
|
32
34
|
|
|
33
35
|
Args:
|
|
34
|
-
pattern_id:
|
|
35
|
-
full_match:
|
|
36
|
-
content:
|
|
36
|
+
pattern_id: The identifier of the matched pattern pair.
|
|
37
|
+
full_match: The complete text including start and end patterns.
|
|
38
|
+
content: The text content between the start and end patterns.
|
|
37
39
|
"""
|
|
38
40
|
self.pattern_id = pattern_id
|
|
39
41
|
self.full_match = full_match
|
|
@@ -43,7 +45,7 @@ class PatternMatch:
|
|
|
43
45
|
"""Return a string representation of the pattern match.
|
|
44
46
|
|
|
45
47
|
Returns:
|
|
46
|
-
A string
|
|
48
|
+
A descriptive string showing the pattern ID and content.
|
|
47
49
|
"""
|
|
48
50
|
return f"PatternMatch(id={self.pattern_id}, content={self.content})"
|
|
49
51
|
|
|
@@ -66,6 +68,7 @@ class PatternPairAggregator(BaseTextAggregator):
|
|
|
66
68
|
"""Initialize the pattern pair aggregator.
|
|
67
69
|
|
|
68
70
|
Creates an empty aggregator with no patterns or handlers registered.
|
|
71
|
+
Text buffering and pattern detection will begin when text is aggregated.
|
|
69
72
|
"""
|
|
70
73
|
self._text = ""
|
|
71
74
|
self._patterns = {}
|
|
@@ -76,7 +79,7 @@ class PatternPairAggregator(BaseTextAggregator):
|
|
|
76
79
|
"""Get the currently buffered text.
|
|
77
80
|
|
|
78
81
|
Returns:
|
|
79
|
-
The current text buffer content.
|
|
82
|
+
The current text buffer content that hasn't been processed yet.
|
|
80
83
|
"""
|
|
81
84
|
return self._text
|
|
82
85
|
|
|
@@ -115,7 +118,7 @@ class PatternPairAggregator(BaseTextAggregator):
|
|
|
115
118
|
|
|
116
119
|
Args:
|
|
117
120
|
pattern_id: ID of the pattern pair to match.
|
|
118
|
-
handler:
|
|
121
|
+
handler: Async function to call when pattern is matched.
|
|
119
122
|
The function should accept a PatternMatch object.
|
|
120
123
|
|
|
121
124
|
Returns:
|
|
@@ -131,10 +134,11 @@ class PatternPairAggregator(BaseTextAggregator):
|
|
|
131
134
|
appropriate handlers, and optionally removes the matches.
|
|
132
135
|
|
|
133
136
|
Args:
|
|
134
|
-
text: The text to process.
|
|
137
|
+
text: The text to process for pattern matches.
|
|
135
138
|
|
|
136
139
|
Returns:
|
|
137
140
|
Tuple of (processed_text, was_modified) where:
|
|
141
|
+
|
|
138
142
|
- processed_text is the text after processing patterns
|
|
139
143
|
- was_modified indicates whether any changes were made
|
|
140
144
|
"""
|
|
@@ -185,7 +189,7 @@ class PatternPairAggregator(BaseTextAggregator):
|
|
|
185
189
|
matching end patterns, which would indicate incomplete content.
|
|
186
190
|
|
|
187
191
|
Args:
|
|
188
|
-
text: The text to check.
|
|
192
|
+
text: The text to check for incomplete patterns.
|
|
189
193
|
|
|
190
194
|
Returns:
|
|
191
195
|
True if there are incomplete patterns, False otherwise.
|
|
@@ -257,6 +261,6 @@ class PatternPairAggregator(BaseTextAggregator):
|
|
|
257
261
|
"""Clear the internally aggregated text.
|
|
258
262
|
|
|
259
263
|
Resets the aggregator to its initial state, discarding any
|
|
260
|
-
buffered text.
|
|
264
|
+
buffered text and clearing pattern tracking state.
|
|
261
265
|
"""
|
|
262
266
|
self._text = ""
|