From 859cd7c9205c5675903784af5503fcfe79245dbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleix=20Conchillo=20Flaqu=C3=A9?= Date: Thu, 19 Feb 2026 10:52:24 -0800 Subject: [PATCH 1/3] Refactor STT TTFB metrics to use base class start/stop pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eliminate custom _emit_stt_ttfb_metric and manual timestamp tracking in STTService by reusing FrameProcessor's start_ttfb_metrics/stop_ttfb_metrics with new start_time/end_time parameters. This keeps the chronological start→stop ordering and removes _speech_end_time and _last_transcription_time state from STTService. --- src/pipecat/processors/frame_processor.py | 50 +++++++++++++------ .../metrics/frame_processor_metrics.py | 43 ++++++++++++---- src/pipecat/services/stt_service.py | 48 +++--------------- 3 files changed, 74 insertions(+), 67 deletions(-) diff --git a/src/pipecat/processors/frame_processor.py b/src/pipecat/processors/frame_processor.py index f0c9e7183..7423c4845 100644 --- a/src/pipecat/processors/frame_processor.py +++ b/src/pipecat/processors/frame_processor.py @@ -419,27 +419,49 @@ class FrameProcessor(BaseObject): """ self._metrics.set_core_metrics_data(data) - async def start_ttfb_metrics(self): - """Start time-to-first-byte metrics collection.""" - if self.can_generate_metrics() and self.metrics_enabled: - await self._metrics.start_ttfb_metrics(self._report_only_initial_ttfb) + async def start_ttfb_metrics(self, *, start_time: Optional[float] = None): + """Start time-to-first-byte metrics collection. - async def stop_ttfb_metrics(self): - """Stop time-to-first-byte metrics collection and push results.""" + Args: + start_time: Optional timestamp to use as the start time. If None, + uses the current time. + """ if self.can_generate_metrics() and self.metrics_enabled: - frame = await self._metrics.stop_ttfb_metrics() + await self._metrics.start_ttfb_metrics( + start_time=start_time, report_only_initial_ttfb=self._report_only_initial_ttfb + ) + + async def stop_ttfb_metrics(self, *, end_time: Optional[float] = None): + """Stop time-to-first-byte metrics collection and push results. + + Args: + end_time: Optional timestamp to use as the end time. If None, uses + the current time. + """ + if self.can_generate_metrics() and self.metrics_enabled: + frame = await self._metrics.stop_ttfb_metrics(end_time=end_time) if frame: await self.push_frame(frame) - async def start_processing_metrics(self): - """Start processing metrics collection.""" - if self.can_generate_metrics() and self.metrics_enabled: - await self._metrics.start_processing_metrics() + async def start_processing_metrics(self, *, start_time: Optional[float] = None): + """Start processing metrics collection. - async def stop_processing_metrics(self): - """Stop processing metrics collection and push results.""" + Args: + start_time: Optional timestamp to use as the start time. If None, + uses the current time. + """ if self.can_generate_metrics() and self.metrics_enabled: - frame = await self._metrics.stop_processing_metrics() + await self._metrics.start_processing_metrics(start_time=start_time) + + async def stop_processing_metrics(self, *, end_time: Optional[float] = None): + """Stop processing metrics collection and push results. + + Args: + end_time: Optional timestamp to use as the end time. If None, uses + the current time. + """ + if self.can_generate_metrics() and self.metrics_enabled: + frame = await self._metrics.stop_processing_metrics(end_time=end_time) if frame: await self.push_frame(frame) diff --git a/src/pipecat/processors/metrics/frame_processor_metrics.py b/src/pipecat/processors/metrics/frame_processor_metrics.py index b8beba6e2..c82fd9698 100644 --- a/src/pipecat/processors/metrics/frame_processor_metrics.py +++ b/src/pipecat/processors/metrics/frame_processor_metrics.py @@ -107,49 +107,70 @@ class FrameProcessorMetrics(BaseObject): """ self._core_metrics_data = MetricsData(processor=name) - async def start_ttfb_metrics(self, report_only_initial_ttfb): + async def start_ttfb_metrics( + self, *, start_time: Optional[float] = None, report_only_initial_ttfb: bool + ): """Start measuring time-to-first-byte (TTFB). Args: + start_time: Optional timestamp to use as the start time. If None, + uses the current time. report_only_initial_ttfb: Whether to report only the first TTFB measurement. """ if self._should_report_ttfb: - self._start_ttfb_time = time.time() + self._start_ttfb_time = start_time or time.time() self._last_ttfb_time = 0 self._should_report_ttfb = not report_only_initial_ttfb - async def stop_ttfb_metrics(self): + async def stop_ttfb_metrics(self, *, end_time: Optional[float] = None): """Stop TTFB measurement and generate metrics frame. + Args: + end_time: Optional timestamp to use as the end time. If None, uses + the current time. + Returns: MetricsFrame containing TTFB data, or None if not measuring. """ if self._start_ttfb_time == 0: return None - self._last_ttfb_time = time.time() - self._start_ttfb_time - logger.debug(f"{self._processor_name()} TTFB: {self._last_ttfb_time}") + end_time = end_time or time.time() + + self._last_ttfb_time = end_time - self._start_ttfb_time + logger.debug(f"{self._processor_name()} TTFB: {self._last_ttfb_time:.3f}s") ttfb = TTFBMetricsData( processor=self._processor_name(), value=self._last_ttfb_time, model=self._model_name() ) self._start_ttfb_time = 0 return MetricsFrame(data=[ttfb]) - async def start_processing_metrics(self): - """Start measuring processing time.""" - self._start_processing_time = time.time() + async def start_processing_metrics(self, *, start_time: Optional[float] = None): + """Start measuring processing time. - async def stop_processing_metrics(self): + Args: + start_time: Optional timestamp to use as the start time. If None, + uses the current time. + """ + self._start_processing_time = start_time or time.time() + + async def stop_processing_metrics(self, *, end_time: Optional[float] = None): """Stop processing time measurement and generate metrics frame. + Args: + end_time: Optional timestamp to use as the end time. If None, uses + the current time. + Returns: MetricsFrame containing processing duration data, or None if not measuring. """ if self._start_processing_time == 0: return None - value = time.time() - self._start_processing_time - logger.debug(f"{self._processor_name()} processing time: {value}") + end_time = end_time or time.time() + + value = end_time - self._start_processing_time + logger.debug(f"{self._processor_name()} processing time: {value:.3f}s") processing = ProcessingMetricsData( processor=self._processor_name(), value=value, model=self._model_name() ) diff --git a/src/pipecat/services/stt_service.py b/src/pipecat/services/stt_service.py index 6d431c523..bad8e2e28 100644 --- a/src/pipecat/services/stt_service.py +++ b/src/pipecat/services/stt_service.py @@ -21,7 +21,6 @@ from pipecat.frames.frames import ( ErrorFrame, Frame, InterruptionFrame, - MetricsFrame, ServiceSwitcherRequestMetadataFrame, StartFrame, STTMetadataFrame, @@ -31,7 +30,6 @@ from pipecat.frames.frames import ( VADUserStartedSpeakingFrame, VADUserStoppedSpeakingFrame, ) -from pipecat.metrics.metrics import TTFBMetricsData from pipecat.processors.frame_processor import FrameDirection from pipecat.services.ai_service import AIService from pipecat.services.stt_latency import DEFAULT_TTFS_P99 @@ -121,9 +119,7 @@ class STTService(AIService): # STT TTFB tracking state self._stt_ttfb_timeout = stt_ttfb_timeout self._ttfb_timeout_task: Optional[asyncio.Task] = None - self._speech_end_time: Optional[float] = None self._user_speaking: bool = False - self._last_transcription_time: Optional[float] = None self._finalize_pending: bool = False self._finalize_requested: bool = False @@ -327,23 +323,16 @@ class STTService(AIService): direction: The direction to push the frame. """ if isinstance(frame, TranscriptionFrame): - # Store the transcription time for TTFB calculation - self._last_transcription_time = time.time() - # Set finalized from pending state and auto-reset if self._finalize_pending: frame.finalized = True self._finalize_pending = False # If this is a finalized transcription, report TTFB immediately - if frame.finalized and self._speech_end_time is not None: - ttfb = self._last_transcription_time - self._speech_end_time - await self._emit_stt_ttfb_metric(ttfb) + if frame.finalized: + await self.stop_ttfb_metrics() # Cancel the timeout since we've already reported await self._cancel_ttfb_timeout() - # Clear state - self._speech_end_time = None - self._last_transcription_time = None await super().push_frame(frame, direction) @@ -373,8 +362,6 @@ class STTService(AIService): while user is still speaking. """ await self._cancel_ttfb_timeout() - self._speech_end_time = None - self._last_transcription_time = None async def _handle_vad_user_started_speaking(self, frame: VADUserStartedSpeakingFrame): """Handle VAD user started speaking frame to start tracking transcriptions. @@ -408,7 +395,8 @@ class STTService(AIService): # Calculate the actual speech end time (current time minus VAD stop delay). # This approximates when the last user audio was sent to the STT service, # which we use to measure against the eventual transcription response. - self._speech_end_time = frame.timestamp - frame.stop_secs + speech_end_time = frame.timestamp - frame.stop_secs + await self.start_ttfb_metrics(start_time=speech_end_time) # Start timeout task (any previous timeout was cancelled by VADUserStartedSpeakingFrame # or InterruptionFrame) @@ -417,44 +405,20 @@ class STTService(AIService): ) async def _ttfb_timeout_handler(self): - """Wait for timeout then report TTFB using the last transcription timestamp. + """Wait for timeout then report TTFB. This timeout allows the final transcription to arrive before we calculate and report TTFB. If no transcription arrived, no TTFB is reported. """ try: await asyncio.sleep(self._stt_ttfb_timeout) - - # Report TTFB if we have both speech end time and transcription time - if self._speech_end_time is not None and self._last_transcription_time is not None: - ttfb = self._last_transcription_time - self._speech_end_time - await self._emit_stt_ttfb_metric(ttfb) - - # Clear state after reporting - self._speech_end_time = None - self._last_transcription_time = None + await self.stop_ttfb_metrics() except asyncio.CancelledError: # Task was cancelled (new utterance or interruption), which is expected behavior pass finally: self._ttfb_timeout_task = None - async def _emit_stt_ttfb_metric(self, ttfb: float): - """Emit STT TTFB metric if value is non-negative. - - Args: - ttfb: The TTFB value in seconds. - """ - if ttfb >= 0: - logger.debug(f"{self} TTFB: {ttfb:.3f}s") - if self.metrics_enabled: - ttfb_data = TTFBMetricsData( - processor=self.name, - model=self.model_name, - value=ttfb, - ) - await super().push_frame(MetricsFrame(data=[ttfb_data])) - def _create_keepalive_task(self): """Start the keepalive task if keepalive is enabled.""" if self._keepalive_timeout is not None: From bae4211369e6ab0003a377ac2c0ad3762ca9f7b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleix=20Conchillo=20Flaqu=C3=A9?= Date: Thu, 19 Feb 2026 10:52:28 -0800 Subject: [PATCH 2/3] Update dependency lock file --- uv.lock | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/uv.lock b/uv.lock index 0a3206c0f..512a6a49d 100644 --- a/uv.lock +++ b/uv.lock @@ -2110,7 +2110,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fe/65/5b235b40581ad75ab97dcd8b4218022ae8e3ab77c13c919f1a1dfe9171fd/greenlet-3.3.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:04bee4775f40ecefcdaa9d115ab44736cd4b9c5fba733575bfe9379419582e13", size = 273723, upload-time = "2026-01-23T15:30:37.521Z" }, { url = "https://files.pythonhosted.org/packages/ce/ad/eb4729b85cba2d29499e0a04ca6fbdd8f540afd7be142fd571eea43d712f/greenlet-3.3.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50e1457f4fed12a50e427988a07f0f9df53cf0ee8da23fab16e6732c2ec909d4", size = 574874, upload-time = "2026-01-23T16:00:54.551Z" }, { url = "https://files.pythonhosted.org/packages/87/32/57cad7fe4c8b82fdaa098c89498ef85ad92dfbb09d5eb713adedfc2ae1f5/greenlet-3.3.1-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:070472cd156f0656f86f92e954591644e158fd65aa415ffbe2d44ca77656a8f5", size = 586309, upload-time = "2026-01-23T16:05:25.18Z" }, - { url = "https://files.pythonhosted.org/packages/66/66/f041005cb87055e62b0d68680e88ec1a57f4688523d5e2fb305841bc8307/greenlet-3.3.1-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1108b61b06b5224656121c3c8ee8876161c491cbe74e5c519e0634c837cf93d5", size = 597461, upload-time = "2026-01-23T16:15:51.943Z" }, { url = "https://files.pythonhosted.org/packages/87/eb/8a1ec2da4d55824f160594a75a9d8354a5fe0a300fb1c48e7944265217e1/greenlet-3.3.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a300354f27dd86bae5fbf7002e6dd2b3255cd372e9242c933faf5e859b703fe", size = 586985, upload-time = "2026-01-23T15:32:47.968Z" }, { url = "https://files.pythonhosted.org/packages/15/1c/0621dd4321dd8c351372ee8f9308136acb628600658a49be1b7504208738/greenlet-3.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e84b51cbebf9ae573b5fbd15df88887815e3253fc000a7d0ff95170e8f7e9729", size = 1547271, upload-time = "2026-01-23T16:04:18.977Z" }, { url = "https://files.pythonhosted.org/packages/9d/53/24047f8924c83bea7a59c8678d9571209c6bfe5f4c17c94a78c06024e9f2/greenlet-3.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0093bd1a06d899892427217f0ff2a3c8f306182b8c754336d32e2d587c131b4", size = 1613427, upload-time = "2026-01-23T15:33:44.428Z" }, @@ -2118,7 +2117,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/e8/2e1462c8fdbe0f210feb5ac7ad2d9029af8be3bf45bd9fa39765f821642f/greenlet-3.3.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5fd23b9bc6d37b563211c6abbb1b3cab27db385a4449af5c32e932f93017080c", size = 274974, upload-time = "2026-01-23T15:31:02.891Z" }, { url = "https://files.pythonhosted.org/packages/7e/a8/530a401419a6b302af59f67aaf0b9ba1015855ea7e56c036b5928793c5bd/greenlet-3.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f51496a0bfbaa9d74d36a52d2580d1ef5ed4fdfcff0a73730abfbbbe1403dd", size = 577175, upload-time = "2026-01-23T16:00:56.213Z" }, { url = "https://files.pythonhosted.org/packages/8e/89/7e812bb9c05e1aaef9b597ac1d0962b9021d2c6269354966451e885c4e6b/greenlet-3.3.1-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb0feb07fe6e6a74615ee62a880007d976cf739b6669cce95daa7373d4fc69c5", size = 590401, upload-time = "2026-01-23T16:05:26.365Z" }, - { url = "https://files.pythonhosted.org/packages/70/ae/e2d5f0e59b94a2269b68a629173263fa40b63da32f5c231307c349315871/greenlet-3.3.1-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:67ea3fc73c8cd92f42467a72b75e8f05ed51a0e9b1d15398c913416f2dafd49f", size = 601161, upload-time = "2026-01-23T16:15:53.456Z" }, { url = "https://files.pythonhosted.org/packages/5c/ae/8d472e1f5ac5efe55c563f3eabb38c98a44b832602e12910750a7c025802/greenlet-3.3.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39eda9ba259cc9801da05351eaa8576e9aa83eb9411e8f0c299e05d712a210f2", size = 590272, upload-time = "2026-01-23T15:32:49.411Z" }, { url = "https://files.pythonhosted.org/packages/a8/51/0fde34bebfcadc833550717eade64e35ec8738e6b097d5d248274a01258b/greenlet-3.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e2e7e882f83149f0a71ac822ebf156d902e7a5d22c9045e3e0d1daf59cee2cc9", size = 1550729, upload-time = "2026-01-23T16:04:20.867Z" }, { url = "https://files.pythonhosted.org/packages/16/c9/2fb47bee83b25b119d5a35d580807bb8b92480a54b68fef009a02945629f/greenlet-3.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80aa4d79eb5564f2e0a6144fcc744b5a37c56c4a92d60920720e99210d88db0f", size = 1615552, upload-time = "2026-01-23T15:33:45.743Z" }, @@ -2127,7 +2125,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/c8/9d76a66421d1ae24340dfae7e79c313957f6e3195c144d2c73333b5bfe34/greenlet-3.3.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:7e806ca53acf6d15a888405880766ec84721aa4181261cd11a457dfe9a7a4975", size = 276443, upload-time = "2026-01-23T15:30:10.066Z" }, { url = "https://files.pythonhosted.org/packages/81/99/401ff34bb3c032d1f10477d199724f5e5f6fbfb59816ad1455c79c1eb8e7/greenlet-3.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d842c94b9155f1c9b3058036c24ffb8ff78b428414a19792b2380be9cecf4f36", size = 597359, upload-time = "2026-01-23T16:00:57.394Z" }, { url = "https://files.pythonhosted.org/packages/2b/bc/4dcc0871ed557792d304f50be0f7487a14e017952ec689effe2180a6ff35/greenlet-3.3.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20fedaadd422fa02695f82093f9a98bad3dab5fcda793c658b945fcde2ab27ba", size = 607805, upload-time = "2026-01-23T16:05:28.068Z" }, - { url = "https://files.pythonhosted.org/packages/3b/cd/7a7ca57588dac3389e97f7c9521cb6641fd8b6602faf1eaa4188384757df/greenlet-3.3.1-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c620051669fd04ac6b60ebc70478210119c56e2d5d5df848baec4312e260e4ca", size = 622363, upload-time = "2026-01-23T16:15:54.754Z" }, { url = "https://files.pythonhosted.org/packages/cf/05/821587cf19e2ce1f2b24945d890b164401e5085f9d09cbd969b0c193cd20/greenlet-3.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14194f5f4305800ff329cbf02c5fcc88f01886cadd29941b807668a45f0d2336", size = 609947, upload-time = "2026-01-23T15:32:51.004Z" }, { url = "https://files.pythonhosted.org/packages/a4/52/ee8c46ed9f8babaa93a19e577f26e3d28a519feac6350ed6f25f1afee7e9/greenlet-3.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7b2fe4150a0cf59f847a67db8c155ac36aed89080a6a639e9f16df5d6c6096f1", size = 1567487, upload-time = "2026-01-23T16:04:22.125Z" }, { url = "https://files.pythonhosted.org/packages/8f/7c/456a74f07029597626f3a6db71b273a3632aecb9afafeeca452cfa633197/greenlet-3.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49f4ad195d45f4a66a0eb9c1ba4832bb380570d361912fa3554746830d332149", size = 1636087, upload-time = "2026-01-23T15:33:47.486Z" }, @@ -2136,7 +2133,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/ab/d26750f2b7242c2b90ea2ad71de70cfcd73a948a49513188a0fc0d6fc15a/greenlet-3.3.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:7ab327905cabb0622adca5971e488064e35115430cec2c35a50fd36e72a315b3", size = 275205, upload-time = "2026-01-23T15:30:24.556Z" }, { url = "https://files.pythonhosted.org/packages/10/d3/be7d19e8fad7c5a78eeefb2d896a08cd4643e1e90c605c4be3b46264998f/greenlet-3.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65be2f026ca6a176f88fb935ee23c18333ccea97048076aef4db1ef5bc0713ac", size = 599284, upload-time = "2026-01-23T16:00:58.584Z" }, { url = "https://files.pythonhosted.org/packages/ae/21/fe703aaa056fdb0f17e5afd4b5c80195bbdab701208918938bd15b00d39b/greenlet-3.3.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a3ae05b3d225b4155bda56b072ceb09d05e974bc74be6c3fc15463cf69f33fd", size = 610274, upload-time = "2026-01-23T16:05:29.312Z" }, - { url = "https://files.pythonhosted.org/packages/06/00/95df0b6a935103c0452dad2203f5be8377e551b8466a29650c4c5a5af6cc/greenlet-3.3.1-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:12184c61e5d64268a160226fb4818af4df02cfead8379d7f8b99a56c3a54ff3e", size = 624375, upload-time = "2026-01-23T16:15:55.915Z" }, { url = "https://files.pythonhosted.org/packages/cb/86/5c6ab23bb3c28c21ed6bebad006515cfe08b04613eb105ca0041fecca852/greenlet-3.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6423481193bbbe871313de5fd06a082f2649e7ce6e08015d2a76c1e9186ca5b3", size = 612904, upload-time = "2026-01-23T15:32:52.317Z" }, { url = "https://files.pythonhosted.org/packages/c2/f3/7949994264e22639e40718c2daf6f6df5169bf48fb038c008a489ec53a50/greenlet-3.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:33a956fe78bbbda82bfc95e128d61129b32d66bcf0a20a1f0c08aa4839ffa951", size = 1567316, upload-time = "2026-01-23T16:04:23.316Z" }, { url = "https://files.pythonhosted.org/packages/8d/6e/d73c94d13b6465e9f7cd6231c68abde838bb22408596c05d9059830b7872/greenlet-3.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b065d3284be43728dd280f6f9a13990b56470b81be20375a207cdc814a983f2", size = 1636549, upload-time = "2026-01-23T15:33:48.643Z" }, @@ -2145,7 +2141,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ae/fb/011c7c717213182caf78084a9bea51c8590b0afda98001f69d9f853a495b/greenlet-3.3.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:bd59acd8529b372775cd0fcbc5f420ae20681c5b045ce25bd453ed8455ab99b5", size = 275737, upload-time = "2026-01-23T15:32:16.889Z" }, { url = "https://files.pythonhosted.org/packages/41/2e/a3a417d620363fdbb08a48b1dd582956a46a61bf8fd27ee8164f9dfe87c2/greenlet-3.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b31c05dd84ef6871dd47120386aed35323c944d86c3d91a17c4b8d23df62f15b", size = 646422, upload-time = "2026-01-23T16:01:00.354Z" }, { url = "https://files.pythonhosted.org/packages/b4/09/c6c4a0db47defafd2d6bab8ddfe47ad19963b4e30f5bed84d75328059f8c/greenlet-3.3.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02925a0bfffc41e542c70aa14c7eda3593e4d7e274bfcccca1827e6c0875902e", size = 658219, upload-time = "2026-01-23T16:05:30.956Z" }, - { url = "https://files.pythonhosted.org/packages/e2/89/b95f2ddcc5f3c2bc09c8ee8d77be312df7f9e7175703ab780f2014a0e781/greenlet-3.3.1-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3e0f3878ca3a3ff63ab4ea478585942b53df66ddde327b59ecb191b19dbbd62d", size = 671455, upload-time = "2026-01-23T16:15:57.232Z" }, { url = "https://files.pythonhosted.org/packages/80/38/9d42d60dffb04b45f03dbab9430898352dba277758640751dc5cc316c521/greenlet-3.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34a729e2e4e4ffe9ae2408d5ecaf12f944853f40ad724929b7585bca808a9d6f", size = 660237, upload-time = "2026-01-23T15:32:53.967Z" }, { url = "https://files.pythonhosted.org/packages/96/61/373c30b7197f9e756e4c81ae90a8d55dc3598c17673f91f4d31c3c689c3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aec9ab04e82918e623415947921dea15851b152b822661cce3f8e4393c3df683", size = 1615261, upload-time = "2026-01-23T16:04:25.066Z" }, { url = "https://files.pythonhosted.org/packages/fd/d3/ca534310343f5945316f9451e953dcd89b36fe7a19de652a1dc5a0eeef3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71c767cf281a80d02b6c1bdc41c9468e1f5a494fb11bc8688c360524e273d7b1", size = 1683719, upload-time = "2026-01-23T15:33:50.61Z" }, @@ -2154,7 +2149,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/28/24/cbbec49bacdcc9ec652a81d3efef7b59f326697e7edf6ed775a5e08e54c2/greenlet-3.3.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:3e63252943c921b90abb035ebe9de832c436401d9c45f262d80e2d06cc659242", size = 282706, upload-time = "2026-01-23T15:33:05.525Z" }, { url = "https://files.pythonhosted.org/packages/86/2e/4f2b9323c144c4fe8842a4e0d92121465485c3c2c5b9e9b30a52e80f523f/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76e39058e68eb125de10c92524573924e827927df5d3891fbc97bd55764a8774", size = 651209, upload-time = "2026-01-23T16:01:01.517Z" }, { url = "https://files.pythonhosted.org/packages/d9/87/50ca60e515f5bb55a2fbc5f0c9b5b156de7d2fc51a0a69abc9d23914a237/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9f9d5e7a9310b7a2f416dd13d2e3fd8b42d803968ea580b7c0f322ccb389b97", size = 654300, upload-time = "2026-01-23T16:05:32.199Z" }, - { url = "https://files.pythonhosted.org/packages/7c/25/c51a63f3f463171e09cb586eb64db0861eb06667ab01a7968371a24c4f3b/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b9721549a95db96689458a1e0ae32412ca18776ed004463df3a9299c1b257ab", size = 662574, upload-time = "2026-01-23T16:15:58.364Z" }, { url = "https://files.pythonhosted.org/packages/1d/94/74310866dfa2b73dd08659a3d18762f83985ad3281901ba0ee9a815194fb/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92497c78adf3ac703b57f1e3813c2d874f27f71a178f9ea5887855da413cd6d2", size = 653842, upload-time = "2026-01-23T15:32:55.671Z" }, { url = "https://files.pythonhosted.org/packages/97/43/8bf0ffa3d498eeee4c58c212a3905dd6146c01c8dc0b0a046481ca29b18c/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ed6b402bc74d6557a705e197d47f9063733091ed6357b3de33619d8a8d93ac53", size = 1614917, upload-time = "2026-01-23T16:04:26.276Z" }, { url = "https://files.pythonhosted.org/packages/89/90/a3be7a5f378fc6e84abe4dcfb2ba32b07786861172e502388b4c90000d1b/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:59913f1e5ada20fde795ba906916aea25d442abcc0593fba7e26c92b7ad76249", size = 1676092, upload-time = "2026-01-23T15:33:52.176Z" }, @@ -4751,10 +4745,10 @@ requires-dist = [ { name = "resampy", specifier = "~=0.4.3" }, { name = "sarvamai", marker = "extra == 'sarvam'", specifier = "==0.1.21" }, { name = "sentry-sdk", marker = "extra == 'sentry'", specifier = ">=2.28.0,<3" }, - { name = "simli-ai", marker = "extra == 'simli'", specifier = "~=1.0.3" }, + { name = "simli-ai", marker = "extra == 'simli'", specifier = "~=2.0.1" }, { name = "soundfile", marker = "extra == 'soundfile'", specifier = "~=0.13.1" }, { name = "soxr", specifier = "~=0.5.0" }, - { name = "speechmatics-voice", extras = ["smart"], marker = "extra == 'speechmatics'", specifier = ">=0.2.8" }, + { name = "speechmatics-voice", extras = ["smart"], marker = "extra == 'speechmatics'", specifier = "~=0.2.8" }, { name = "strands-agents", marker = "extra == 'strands'", specifier = ">=1.9.1,<2" }, { name = "tenacity", marker = "extra == 'livekit'", specifier = ">=8.2.3,<10.0.0" }, { name = "timm", marker = "extra == 'moondream'", specifier = "~=1.0.13" }, @@ -6467,19 +6461,18 @@ wheels = [ [[package]] name = "simli-ai" -version = "1.0.3" +version = "2.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiortc" }, { name = "av" }, { name = "httpx" }, - { name = "livekit" }, { name = "numpy" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/03/b0b3e12c68fd3f9c57f6afeee67841349e4866b88760f413357af3043ae4/simli_ai-1.0.3.tar.gz", hash = "sha256:e96b0621a1dbd9582b2ae3d51eefd4995983b49c1f1061eb9239707b15a1ee27", size = 13350, upload-time = "2025-11-13T12:22:32.514Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/b5/6021990871daf9f5b6eb744aff68c83f2c7b257cfd2ee5b9883d0acd9cf4/simli_ai-2.0.1.tar.gz", hash = "sha256:1f63eb76900d4dac0c18406a854219e54ebab51acb0c01e245c7a0738dc72413", size = 16104, upload-time = "2026-02-17T12:46:09.743Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/d1/dc382ba529de0d2d51f35e9bfd20b41d8f5c96404a3aa24bae97a5a5e51f/simli_ai-1.0.3-py3-none-any.whl", hash = "sha256:ffafa7540aa28833e207be8f3b199367c7f500dac1a8ba0108395bfb7d8362bc", size = 13863, upload-time = "2025-11-13T12:22:31.218Z" }, + { url = "https://files.pythonhosted.org/packages/bc/c1/e7aed0f59d04628c0ac738e2bf8cb6bf020870d1909f3ec9fcf265136663/simli_ai-2.0.1-py3-none-any.whl", hash = "sha256:0a48e38fe289568e56236266843484a1f0e28aca694dd8e2b96610fe40d6c687", size = 19456, upload-time = "2026-02-17T12:46:08.727Z" }, ] [[package]] From 8e52df7f034309f372da3e76a9865cc6bd9b0cd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleix=20Conchillo=20Flaqu=C3=A9?= Date: Thu, 19 Feb 2026 10:52:31 -0800 Subject: [PATCH 3/3] Add changelog entries for PR #3776 --- changelog/3776.changed.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/3776.changed.md diff --git a/changelog/3776.changed.md b/changelog/3776.changed.md new file mode 100644 index 000000000..87b5d6128 --- /dev/null +++ b/changelog/3776.changed.md @@ -0,0 +1 @@ +- Added `start_time` and `end_time` parameters to `start_ttfb_metrics()`, `stop_ttfb_metrics()`, `start_processing_metrics()`, and `stop_processing_metrics()` in `FrameProcessor` and `FrameProcessorMetrics`, allowing custom timestamps for metrics measurement. `STTService` now uses these instead of custom TTFB tracking.