async def _process_audio_buffer(self) -> AsyncGenerator[Frame, None]:
        """Process collected audio frames with Ultravox.

        This method concatenates audio frames, processes them with the model,
        and yields the resulting text frames.

        Yields:
            Frame: TextFrame containing the transcribed text
        """
        try:
            self._buffer.is_processing = True

            # Check if we have valid frames before processing
            if not self._buffer.frames:
                logger.warning("No audio frames to process")
                yield ErrorFrame("No audio frames to process")
                return

            # Process audio frames
            audio_arrays = []
            for f in self._buffer.frames:
                if hasattr(f, "audio") and f.audio:
                    # Handle bytes data - these are int16 PCM samples
                    if isinstance(f.audio, bytes):
                        try:
                            # Convert bytes to int16 array
                            arr = np.frombuffer(f.audio, dtype=np.int16)
                            if arr.size > 0:  # Check if array is not empty
                                audio_arrays.append(arr)
                        except Exception as e:
                            logger.error(f"Error processing bytes audio frame: {e}")
                    # Handle numpy array data
                    elif isinstance(f.audio, np.ndarray):
                        if f.audio.size > 0:  # Check if array is not empty
                            # Ensure it's int16 data
                            if f.audio.dtype != np.int16:
                                logger.info(f"Converting array from {f.audio.dtype} to int16")
                                audio_arrays.append(f.audio.astype(np.int16))
                            else:
                                audio_arrays.append(f.audio)

            # Only proceed if we have valid audio arrays
            if not audio_arrays:
                logger.warning("No valid audio data found in frames")
                yield ErrorFrame("No valid audio data found in frames")
                return

            # Concatenate audio frames - all should be int16 now
            audio_data = np.concatenate(audio_arrays)

            audio_int16 = audio_data  # Already in int16 format
            # Save int16 audio

            # Convert int16 to float32 and normalize for model input
            audio_float32 = audio_int16.astype(np.float32) / 32768.0

            # Generate text using the model
            if self._model:
                try:
                    logger.info("Generating text from audio using model...")

                    # Start metrics tracking
                    await self.start_ttfb_metrics()
                    await self.start_processing_metrics()

                    yield LLMFullResponseStartFrame()

                    async for response in self._model.generate(
                        messages=[{"role": "user", "content": "<|audio|>\n"}],
                        temperature=self._temperature,
                        max_tokens=self._max_tokens,
                        audio=audio_float32,
                    ):
                        # Stop TTFB metrics after first response
                        await self.stop_ttfb_metrics()

                        chunk = json.loads(response)
                        if "choices" in chunk and len(chunk["choices"]) > 0:
                            delta = chunk["choices"][0]["delta"]
                            if "content" in delta:
                                new_text = delta["content"]
                                if new_text:
                                    yield LLMTextFrame(text=new_text)

                    # Stop processing metrics after completion
                    await self.stop_processing_metrics()

                    yield LLMFullResponseEndFrame()

                except Exception as e:
                    logger.error(f"Error generating text from model: {e}")
                    yield ErrorFrame(f"Error generating text: {str(e)}")
            else:
                logger.warning("No model available for text generation")
                yield ErrorFrame("No model available for text generation")

        except Exception as e:
            logger.error(f"Error processing audio buffer: {e}")
            import traceback

            logger.error(traceback.format_exc())
            yield ErrorFrame(f"Error processing audio: {str(e)}")
        finally:
            self._buffer.is_processing = False
            self._buffer.frames = []
            self._buffer.started_at = None
Process collected audio frames with Ultravox.

        This method concatenates audio frames, processes them with the model,
        and yields the resulting text frames.

        Yields:
            Frame: TextFrame containing the transcribed text
        
src/pipecat/services/ultravox/stt.py