Merge pull request #1370 from pipecat-ai/aleix/minor-ultravox-updates
services(ultravox): CHANGELOG, formatting and minor changes
This commit is contained in:
@@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- Added new `UltravoxSTTService`.
|
||||||
|
(see https://github.com/fixie-ai/ultravox)
|
||||||
|
|
||||||
- Added `on_frame_reached_upstream` and `on_frame_reached_downstream` event
|
- Added `on_frame_reached_upstream` and `on_frame_reached_downstream` event
|
||||||
handlers to `PipelineTask`. Those events will be called when a frame reaches
|
handlers to `PipelineTask`. Those events will be called when a frame reaches
|
||||||
the beginning or end of the pipeline respectively.
|
the beginning or end of the pipeline respectively.
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ pip install "pipecat-ai[option,...]"
|
|||||||
### Available services
|
### Available services
|
||||||
|
|
||||||
| Category | Services | Install Command Example |
|
| Category | Services | Install Command Example |
|
||||||
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
|
|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------|
|
||||||
| Speech-to-Text | [AssemblyAI](https://docs.pipecat.ai/server/services/stt/assemblyai), [Azure](https://docs.pipecat.ai/server/services/stt/azure), [Deepgram](https://docs.pipecat.ai/server/services/stt/deepgram), [Gladia](https://docs.pipecat.ai/server/services/stt/gladia), [Google](https://docs.pipecat.ai/server/services/stt/google), [Groq (Whisper)](https://docs.pipecat.ai/server/services/stt/groq), [OpenAI (Whisper)](https://docs.pipecat.ai/server/services/stt/openai), [Whisper](https://docs.pipecat.ai/server/services/stt/whisper) | `pip install "pipecat-ai[deepgram]"` |
|
| Speech-to-Text | [AssemblyAI](https://docs.pipecat.ai/server/services/stt/assemblyai), [Azure](https://docs.pipecat.ai/server/services/stt/azure), [Deepgram](https://docs.pipecat.ai/server/services/stt/deepgram), [Gladia](https://docs.pipecat.ai/server/services/stt/gladia), [Google](https://docs.pipecat.ai/server/services/stt/google), [Groq (Whisper)](https://docs.pipecat.ai/server/services/stt/groq), [OpenAI (Whisper)](https://docs.pipecat.ai/server/services/stt/openai), [Ultravox](https://docs.pipecat.ai/server/services/stt/ultravox), [Whisper](https://docs.pipecat.ai/server/services/stt/whisper) | `pip install "pipecat-ai[deepgram]"` |
|
||||||
| LLMs | [Anthropic](https://docs.pipecat.ai/server/services/llm/anthropic), [Azure](https://docs.pipecat.ai/server/services/llm/azure), [Cerebras](https://docs.pipecat.ai/server/services/llm/cerebras), [DeepSeek](https://docs.pipecat.ai/server/services/llm/deepseek), [Fireworks AI](https://docs.pipecat.ai/server/services/llm/fireworks), [Gemini](https://docs.pipecat.ai/server/services/llm/gemini), [Grok](https://docs.pipecat.ai/server/services/llm/grok), [Groq](https://docs.pipecat.ai/server/services/llm/groq), [NVIDIA NIM](https://docs.pipecat.ai/server/services/llm/nim), [Ollama](https://docs.pipecat.ai/server/services/llm/ollama), [OpenAI](https://docs.pipecat.ai/server/services/llm/openai), [OpenRouter](https://docs.pipecat.ai/server/services/llm/openrouter), [Perplexity](https://docs.pipecat.ai/server/services/llm/perplexity), [Together AI](https://docs.pipecat.ai/server/services/llm/together) | `pip install "pipecat-ai[openai]"` |
|
| LLMs | [Anthropic](https://docs.pipecat.ai/server/services/llm/anthropic), [Azure](https://docs.pipecat.ai/server/services/llm/azure), [Cerebras](https://docs.pipecat.ai/server/services/llm/cerebras), [DeepSeek](https://docs.pipecat.ai/server/services/llm/deepseek), [Fireworks AI](https://docs.pipecat.ai/server/services/llm/fireworks), [Gemini](https://docs.pipecat.ai/server/services/llm/gemini), [Grok](https://docs.pipecat.ai/server/services/llm/grok), [Groq](https://docs.pipecat.ai/server/services/llm/groq), [NVIDIA NIM](https://docs.pipecat.ai/server/services/llm/nim), [Ollama](https://docs.pipecat.ai/server/services/llm/ollama), [OpenAI](https://docs.pipecat.ai/server/services/llm/openai), [OpenRouter](https://docs.pipecat.ai/server/services/llm/openrouter), [Perplexity](https://docs.pipecat.ai/server/services/llm/perplexity), [Together AI](https://docs.pipecat.ai/server/services/llm/together) | `pip install "pipecat-ai[openai]"` |
|
||||||
| Text-to-Speech | [AWS](https://docs.pipecat.ai/server/services/tts/aws), [Azure](https://docs.pipecat.ai/server/services/tts/azure), [Cartesia](https://docs.pipecat.ai/server/services/tts/cartesia), [Deepgram](https://docs.pipecat.ai/server/services/tts/deepgram), [ElevenLabs](https://docs.pipecat.ai/server/services/tts/elevenlabs), [Fish](https://docs.pipecat.ai/server/services/tts/fish), [Google](https://docs.pipecat.ai/server/services/tts/google), [LMNT](https://docs.pipecat.ai/server/services/tts/lmnt), [OpenAI](https://docs.pipecat.ai/server/services/tts/openai), [PlayHT](https://docs.pipecat.ai/server/services/tts/playht), [Rime](https://docs.pipecat.ai/server/services/tts/rime), [XTTS](https://docs.pipecat.ai/server/services/tts/xtts) | `pip install "pipecat-ai[cartesia]"` |
|
| Text-to-Speech | [AWS](https://docs.pipecat.ai/server/services/tts/aws), [Azure](https://docs.pipecat.ai/server/services/tts/azure), [Cartesia](https://docs.pipecat.ai/server/services/tts/cartesia), [Deepgram](https://docs.pipecat.ai/server/services/tts/deepgram), [ElevenLabs](https://docs.pipecat.ai/server/services/tts/elevenlabs), [Fish](https://docs.pipecat.ai/server/services/tts/fish), [Google](https://docs.pipecat.ai/server/services/tts/google), [LMNT](https://docs.pipecat.ai/server/services/tts/lmnt), [OpenAI](https://docs.pipecat.ai/server/services/tts/openai), [PlayHT](https://docs.pipecat.ai/server/services/tts/playht), [Rime](https://docs.pipecat.ai/server/services/tts/rime), [XTTS](https://docs.pipecat.ai/server/services/tts/xtts) | `pip install "pipecat-ai[cartesia]"` |
|
||||||
| Speech-to-Speech | [Gemini Multimodal Live](https://docs.pipecat.ai/server/services/s2s/gemini), [OpenAI Realtime](https://docs.pipecat.ai/server/services/s2s/openai) | `pip install "pipecat-ai[google]"` |
|
| Speech-to-Speech | [Gemini Multimodal Live](https://docs.pipecat.ai/server/services/s2s/gemini), [OpenAI Realtime](https://docs.pipecat.ai/server/services/s2s/openai) | `pip install "pipecat-ai[google]"` |
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ from pipecat.audio.vad.silero import SileroVADAnalyzer
|
|||||||
from pipecat.pipeline.pipeline import Pipeline
|
from pipecat.pipeline.pipeline import Pipeline
|
||||||
from pipecat.pipeline.runner import PipelineRunner
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
|
||||||
from pipecat.services.ultravox import UltravoxSTTService
|
|
||||||
from pipecat.services.cartesia import CartesiaTTSService
|
from pipecat.services.cartesia import CartesiaTTSService
|
||||||
|
from pipecat.services.ultravox import UltravoxSTTService
|
||||||
|
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
@@ -30,13 +30,14 @@ load_dotenv(override=True)
|
|||||||
logger.remove(0)
|
logger.remove(0)
|
||||||
logger.add(sys.stderr, level="DEBUG")
|
logger.add(sys.stderr, level="DEBUG")
|
||||||
|
|
||||||
#Want to initialize the ultravox processor since it takes time to load the model and dont
|
# Want to initialize the ultravox processor since it takes time to load the model and dont
|
||||||
#want to load it every time the pipeline is run
|
# want to load it every time the pipeline is run
|
||||||
ultravox_processor = UltravoxSTTService(
|
ultravox_processor = UltravoxSTTService(
|
||||||
model_size="fixie-ai/ultravox-v0_4_1-llama-3_1-8b",
|
model_size="fixie-ai/ultravox-v0_4_1-llama-3_1-8b",
|
||||||
hf_token=os.getenv("HF_TOKEN"),
|
hf_token=os.getenv("HF_TOKEN"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
(room_url, token) = await configure(session)
|
(room_url, token) = await configure(session)
|
||||||
@@ -56,11 +57,9 @@ async def main():
|
|||||||
|
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
api_key=os.environ.get("CARTESIA_API_KEY"),
|
api_key=os.environ.get("CARTESIA_API_KEY"),
|
||||||
voice_id='97f4b8fb-f2fe-444b-bb9a-c109783a857a',
|
voice_id="97f4b8fb-f2fe-444b-bb9a-c109783a857a",
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
pipeline = Pipeline(
|
pipeline = Pipeline(
|
||||||
[
|
[
|
||||||
transport.input(), # Transport user input
|
transport.input(), # Transport user input
|
||||||
@@ -89,5 +88,3 @@ async def main():
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ silero = [ "onnxruntime~=1.20.1" ]
|
|||||||
simli = [ "simli-ai~=0.1.10"]
|
simli = [ "simli-ai~=0.1.10"]
|
||||||
soundfile = [ "soundfile~=0.13.0" ]
|
soundfile = [ "soundfile~=0.13.0" ]
|
||||||
together = []
|
together = []
|
||||||
|
ultravox = [ "transformers~=4.48.0", "vllm~=0.7.3" ]
|
||||||
websocket = [ "websockets~=13.1", "fastapi~=0.115.6" ]
|
websocket = [ "websockets~=13.1", "fastapi~=0.115.6" ]
|
||||||
whisper = [ "faster-whisper~=1.1.1" ]
|
whisper = [ "faster-whisper~=1.1.1" ]
|
||||||
openrouter = []
|
openrouter = []
|
||||||
|
|||||||
@@ -1,40 +1,45 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2024–2025, Daily
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
|
#
|
||||||
|
|
||||||
"""This module implements Ultravox speech-to-text with a locally-loaded model."""
|
"""This module implements Ultravox speech-to-text with a locally-loaded model."""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import time
|
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
|
from typing import AsyncGenerator, List, Optional
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from enum import Enum
|
|
||||||
from typing import AsyncGenerator, Optional, List
|
|
||||||
from loguru import logger
|
|
||||||
from pydantic import BaseModel
|
|
||||||
from huggingface_hub import login
|
from huggingface_hub import login
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
Frame,
|
|
||||||
AudioRawFrame,
|
AudioRawFrame,
|
||||||
TranscriptionFrame,
|
|
||||||
TextFrame,
|
|
||||||
StartFrame,
|
|
||||||
EndFrame,
|
|
||||||
CancelFrame,
|
CancelFrame,
|
||||||
|
EndFrame,
|
||||||
|
ErrorFrame,
|
||||||
|
Frame,
|
||||||
|
StartFrame,
|
||||||
|
TranscriptionFrame,
|
||||||
UserStartedSpeakingFrame,
|
UserStartedSpeakingFrame,
|
||||||
UserStoppedSpeakingFrame,
|
UserStoppedSpeakingFrame,
|
||||||
ErrorFrame
|
|
||||||
)
|
)
|
||||||
from pipecat.services.ai_services import AIService
|
|
||||||
from pipecat.processors.frame_processor import FrameDirection
|
from pipecat.processors.frame_processor import FrameDirection
|
||||||
|
from pipecat.services.ai_services import AIService
|
||||||
from pipecat.utils.time import time_now_iso8601
|
from pipecat.utils.time import time_now_iso8601
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from vllm import SamplingParams, AsyncLLMEngine
|
|
||||||
from vllm.engine.arg_utils import AsyncEngineArgs
|
|
||||||
from transformers import AutoTokenizer
|
from transformers import AutoTokenizer
|
||||||
|
from vllm import AsyncLLMEngine, SamplingParams
|
||||||
|
from vllm.engine.arg_utils import AsyncEngineArgs
|
||||||
except ModuleNotFoundError as e:
|
except ModuleNotFoundError as e:
|
||||||
logger.error(f"Exception: {e}")
|
logger.error(f"Exception: {e}")
|
||||||
logger.error("In order to use Ultravox, you need to `pip install pipecat-ai[ultravox]`.")
|
logger.error("In order to use Ultravox, you need to `pip install pipecat-ai[ultravox]`.")
|
||||||
raise Exception(f"Missing module: {e}")
|
raise Exception(f"Missing module: {e}")
|
||||||
|
|
||||||
|
|
||||||
class AudioBuffer:
|
class AudioBuffer:
|
||||||
"""Buffer to collect audio frames before processing.
|
"""Buffer to collect audio frames before processing.
|
||||||
|
|
||||||
@@ -43,11 +48,13 @@ class AudioBuffer:
|
|||||||
started_at: Timestamp when speech started
|
started_at: Timestamp when speech started
|
||||||
is_processing: Flag to prevent concurrent processing
|
is_processing: Flag to prevent concurrent processing
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.frames: List[AudioRawFrame] = []
|
self.frames: List[AudioRawFrame] = []
|
||||||
self.started_at: Optional[float] = None
|
self.started_at: Optional[float] = None
|
||||||
self.is_processing: bool = False
|
self.is_processing: bool = False
|
||||||
|
|
||||||
|
|
||||||
class UltravoxModel:
|
class UltravoxModel:
|
||||||
"""Model wrapper for the Ultravox multimodal model.
|
"""Model wrapper for the Ultravox multimodal model.
|
||||||
|
|
||||||
@@ -62,6 +69,7 @@ class UltravoxModel:
|
|||||||
tokenizer: The tokenizer for the model
|
tokenizer: The tokenizer for the model
|
||||||
stop_token_ids: Optional token IDs to stop generation
|
stop_token_ids: Optional token IDs to stop generation
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, model_name: str = "fixie-ai/ultravox-v0_4_1-llama-3_1-8b"):
|
def __init__(self, model_name: str = "fixie-ai/ultravox-v0_4_1-llama-3_1-8b"):
|
||||||
self.model_name = model_name
|
self.model_name = model_name
|
||||||
self._initialize_engine()
|
self._initialize_engine()
|
||||||
@@ -74,7 +82,7 @@ class UltravoxModel:
|
|||||||
model=self.model_name,
|
model=self.model_name,
|
||||||
gpu_memory_utilization=0.9,
|
gpu_memory_utilization=0.9,
|
||||||
max_model_len=8192,
|
max_model_len=8192,
|
||||||
trust_remote_code=True
|
trust_remote_code=True,
|
||||||
)
|
)
|
||||||
self.engine = AsyncLLMEngine.from_engine_args(engine_args)
|
self.engine = AsyncLLMEngine.from_engine_args(engine_args)
|
||||||
|
|
||||||
@@ -92,12 +100,16 @@ class UltravoxModel:
|
|||||||
str: Formatted prompt string
|
str: Formatted prompt string
|
||||||
"""
|
"""
|
||||||
return self.tokenizer.apply_chat_template(
|
return self.tokenizer.apply_chat_template(
|
||||||
messages,
|
messages, tokenize=False, add_generation_prompt=True
|
||||||
tokenize=False,
|
|
||||||
add_generation_prompt=True
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def generate(self, messages: list, temperature: float = 0.7, max_tokens: int = 100, audio: np.ndarray = None):
|
async def generate(
|
||||||
|
self,
|
||||||
|
messages: list,
|
||||||
|
temperature: float = 0.7,
|
||||||
|
max_tokens: int = 100,
|
||||||
|
audio: np.ndarray = None,
|
||||||
|
):
|
||||||
"""Generate text from audio input using the model.
|
"""Generate text from audio input using the model.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -110,14 +122,10 @@ class UltravoxModel:
|
|||||||
str: JSON chunks of the generated response
|
str: JSON chunks of the generated response
|
||||||
"""
|
"""
|
||||||
sampling_params = SamplingParams(
|
sampling_params = SamplingParams(
|
||||||
temperature=temperature,
|
temperature=temperature, max_tokens=max_tokens, stop_token_ids=self.stop_token_ids
|
||||||
max_tokens=max_tokens,
|
|
||||||
stop_token_ids=self.stop_token_ids
|
|
||||||
)
|
)
|
||||||
|
|
||||||
mm_data = {
|
mm_data = {"audio": audio}
|
||||||
"audio": audio
|
|
||||||
}
|
|
||||||
inputs = {"prompt": self.format_prompt(messages), "multi_modal_data": mm_data}
|
inputs = {"prompt": self.format_prompt(messages), "multi_modal_data": mm_data}
|
||||||
results_generator = self.engine.generate(inputs, sampling_params, str(time.time()))
|
results_generator = self.engine.generate(inputs, sampling_params, str(time.time()))
|
||||||
|
|
||||||
@@ -126,7 +134,7 @@ class UltravoxModel:
|
|||||||
|
|
||||||
async for output in results_generator:
|
async for output in results_generator:
|
||||||
prompt_output = output.outputs
|
prompt_output = output.outputs
|
||||||
new_text = prompt_output[0].text[len(previous_text):]
|
new_text = prompt_output[0].text[len(previous_text) :]
|
||||||
previous_text = prompt_output[0].text
|
previous_text = prompt_output[0].text
|
||||||
|
|
||||||
# Construct OpenAI-compatible chunk
|
# Construct OpenAI-compatible chunk
|
||||||
@@ -160,6 +168,7 @@ class UltravoxModel:
|
|||||||
|
|
||||||
yield json.dumps(chunk)
|
yield json.dumps(chunk)
|
||||||
|
|
||||||
|
|
||||||
class UltravoxSTTService(AIService):
|
class UltravoxSTTService(AIService):
|
||||||
"""Service to transcribe audio using the Ultravox multimodal model.
|
"""Service to transcribe audio using the Ultravox multimodal model.
|
||||||
|
|
||||||
@@ -180,10 +189,11 @@ class UltravoxSTTService(AIService):
|
|||||||
max_tokens: Maximum tokens to generate
|
max_tokens: Maximum tokens to generate
|
||||||
_connection_active: Flag indicating if service is active
|
_connection_active: Flag indicating if service is active
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
model_size: str = "fixie-ai/ultravox-v0_4_1-llama-3_1-8b",
|
model_size: str = "fixie-ai/ultravox-v0_4_1-llama-3_1-8b",
|
||||||
hf_token: Optional[str] = None,
|
hf_token: Optional[str] = None,
|
||||||
temperature: float = 0.7,
|
temperature: float = 0.7,
|
||||||
max_tokens: int = 100,
|
max_tokens: int = 100,
|
||||||
@@ -201,12 +211,12 @@ class UltravoxSTTService(AIService):
|
|||||||
|
|
||||||
# Initialize model
|
# Initialize model
|
||||||
model_name = model_size if isinstance(model_size, str) else model_size.value
|
model_name = model_size if isinstance(model_size, str) else model_size.value
|
||||||
self.model = UltravoxModel(model_name=model_name)
|
self._model = UltravoxModel(model_name=model_name)
|
||||||
|
|
||||||
# Initialize service state
|
# Initialize service state
|
||||||
self.buffer = AudioBuffer()
|
self._buffer = AudioBuffer()
|
||||||
self.temperature = temperature
|
self._temperature = temperature
|
||||||
self.max_tokens = max_tokens
|
self._max_tokens = max_tokens
|
||||||
self._connection_active = False
|
self._connection_active = False
|
||||||
|
|
||||||
logger.info(f"Initialized UltravoxSTTService with model: {model_name}")
|
logger.info(f"Initialized UltravoxSTTService with model: {model_name}")
|
||||||
@@ -247,7 +257,7 @@ class UltravoxSTTService(AIService):
|
|||||||
"""
|
"""
|
||||||
await super().cancel(frame)
|
await super().cancel(frame)
|
||||||
self._connection_active = False
|
self._connection_active = False
|
||||||
self.buffer = AudioBuffer()
|
self._buffer = AudioBuffer()
|
||||||
logger.info("UltravoxSTTService cancelled")
|
logger.info("UltravoxSTTService cancelled")
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
@@ -263,14 +273,14 @@ class UltravoxSTTService(AIService):
|
|||||||
|
|
||||||
if isinstance(frame, UserStartedSpeakingFrame):
|
if isinstance(frame, UserStartedSpeakingFrame):
|
||||||
logger.info("Speech started")
|
logger.info("Speech started")
|
||||||
self.buffer = AudioBuffer()
|
self._buffer = AudioBuffer()
|
||||||
self.buffer.started_at = time.time()
|
self._buffer.started_at = time.time()
|
||||||
|
|
||||||
elif isinstance(frame, AudioRawFrame) and self.buffer.started_at is not None:
|
elif isinstance(frame, AudioRawFrame) and self._buffer.started_at is not None:
|
||||||
self.buffer.frames.append(frame)
|
self._buffer.frames.append(frame)
|
||||||
|
|
||||||
elif isinstance(frame, UserStoppedSpeakingFrame):
|
elif isinstance(frame, UserStoppedSpeakingFrame):
|
||||||
if self.buffer.frames and not self.buffer.is_processing:
|
if self._buffer.frames and not self._buffer.is_processing:
|
||||||
logger.info("Speech ended, processing buffer...")
|
logger.info("Speech ended, processing buffer...")
|
||||||
await self.process_generator(self._process_audio_buffer())
|
await self.process_generator(self._process_audio_buffer())
|
||||||
return # Return early to avoid pushing None frame
|
return # Return early to avoid pushing None frame
|
||||||
@@ -289,18 +299,18 @@ class UltravoxSTTService(AIService):
|
|||||||
Frame: TextFrame containing the transcribed text
|
Frame: TextFrame containing the transcribed text
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.buffer.is_processing = True
|
self._buffer.is_processing = True
|
||||||
|
|
||||||
# Check if we have valid frames before processing
|
# Check if we have valid frames before processing
|
||||||
if not self.buffer.frames:
|
if not self._buffer.frames:
|
||||||
logger.warning("No audio frames to process")
|
logger.warning("No audio frames to process")
|
||||||
yield ErrorFrame("No audio frames to process")
|
yield ErrorFrame("No audio frames to process")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Process audio frames
|
# Process audio frames
|
||||||
audio_arrays = []
|
audio_arrays = []
|
||||||
for f in self.buffer.frames:
|
for f in self._buffer.frames:
|
||||||
if hasattr(f, 'audio') and f.audio:
|
if hasattr(f, "audio") and f.audio:
|
||||||
# Handle bytes data - these are int16 PCM samples
|
# Handle bytes data - these are int16 PCM samples
|
||||||
if isinstance(f.audio, bytes):
|
if isinstance(f.audio, bytes):
|
||||||
try:
|
try:
|
||||||
@@ -330,7 +340,7 @@ class UltravoxSTTService(AIService):
|
|||||||
audio_data = np.concatenate(audio_arrays)
|
audio_data = np.concatenate(audio_arrays)
|
||||||
|
|
||||||
# Generate text using the model
|
# Generate text using the model
|
||||||
if self.model:
|
if self._model:
|
||||||
try:
|
try:
|
||||||
logger.info("Generating text from audio using model...")
|
logger.info("Generating text from audio using model...")
|
||||||
full_response = ""
|
full_response = ""
|
||||||
@@ -339,14 +349,11 @@ class UltravoxSTTService(AIService):
|
|||||||
await self.start_ttfb_metrics()
|
await self.start_ttfb_metrics()
|
||||||
await self.start_processing_metrics()
|
await self.start_processing_metrics()
|
||||||
|
|
||||||
async for response in self.model.generate(
|
async for response in self._model.generate(
|
||||||
messages=[{
|
messages=[{"role": "user", "content": "<|audio|>\n"}],
|
||||||
'role': 'user',
|
temperature=self._temperature,
|
||||||
'content': "<|audio|>\n"
|
max_tokens=self._max_tokens,
|
||||||
}],
|
audio=audio_data,
|
||||||
temperature=self.temperature,
|
|
||||||
max_tokens=self.max_tokens,
|
|
||||||
audio=audio_data
|
|
||||||
):
|
):
|
||||||
# Stop TTFB metrics after first response
|
# Stop TTFB metrics after first response
|
||||||
await self.stop_ttfb_metrics()
|
await self.stop_ttfb_metrics()
|
||||||
@@ -367,9 +374,9 @@ class UltravoxSTTService(AIService):
|
|||||||
transcription = full_response.strip()
|
transcription = full_response.strip()
|
||||||
if transcription:
|
if transcription:
|
||||||
yield TranscriptionFrame(
|
yield TranscriptionFrame(
|
||||||
|
user_id="",
|
||||||
text=transcription,
|
text=transcription,
|
||||||
interim_text="",
|
timestamp=time_now_iso8601(),
|
||||||
timestamp=time_now_iso8601()
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.warning("Empty transcription result")
|
logger.warning("Empty transcription result")
|
||||||
@@ -385,9 +392,10 @@ class UltravoxSTTService(AIService):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing audio buffer: {e}")
|
logger.error(f"Error processing audio buffer: {e}")
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
yield ErrorFrame(f"Error processing audio: {str(e)}")
|
yield ErrorFrame(f"Error processing audio: {str(e)}")
|
||||||
finally:
|
finally:
|
||||||
self.buffer.is_processing = False
|
self._buffer.is_processing = False
|
||||||
self.buffer.frames = []
|
self._buffer.frames = []
|
||||||
self.buffer.started_at = None
|
self._buffer.started_at = None
|
||||||
|
|||||||
Reference in New Issue
Block a user