chore: address review comments
This commit is contained in:
@@ -9,9 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- **async.ai TTS integration** (https://async.ai/)
|
- Added Async.ai TTS integration (https://async.ai/)
|
||||||
- `AsyncAITTSService` – streaming / interruptible TTS over WebSocket.
|
- `AsyncAITTSService` – WebSocket-based streaming TTS with interruption support
|
||||||
- `AsyncAIHttpTTSService` – streaming TTS over HTTP.
|
- `AsyncAIHttpTTSService` – HTTP-based streaming TTS service
|
||||||
- Example scripts:
|
- Example scripts:
|
||||||
- `examples/foundational/07ac-interruptible-asyncai.py` (WebSocket demo)
|
- `examples/foundational/07ac-interruptible-asyncai.py` (WebSocket demo)
|
||||||
- `examples/foundational/07ac-interruptible-asyncai-http.py` (HTTP demo)
|
- `examples/foundational/07ac-interruptible-asyncai-http.py` (HTTP demo)
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ 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.processors.aggregators.openai_llm_context import OpenAILLMContext
|
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
||||||
from pipecat.services.openai.stt import OpenAISTTService
|
|
||||||
from pipecat.services.asyncai.tts import AsyncAIHttpTTSService
|
from pipecat.services.asyncai.tts import AsyncAIHttpTTSService
|
||||||
|
from pipecat.services.deepgram.stt import DeepgramSTTService
|
||||||
from pipecat.services.openai.llm import OpenAILLMService
|
from pipecat.services.openai.llm import OpenAILLMService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
||||||
@@ -53,11 +53,11 @@ async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_si
|
|||||||
|
|
||||||
# Create an HTTP session
|
# Create an HTTP session
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
stt = OpenAISTTService(api_key=os.getenv("OPENAI_API_KEY"))
|
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
||||||
|
|
||||||
tts = AsyncAIHttpTTSService(
|
tts = AsyncAIHttpTTSService(
|
||||||
api_key=os.getenv("ASYNCAI_API_KEY", ""),
|
api_key=os.getenv("ASYNCAI_API_KEY", ""),
|
||||||
voice_id=os.getenv("ASYNCAI_VOICE_ID", ""),
|
voice_id=os.getenv("ASYNCAI_VOICE_ID", "e0f39dc4-f691-4e78-bba5-5c636692cc04"),
|
||||||
aiohttp_session=session,
|
aiohttp_session=session,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,14 @@ import os
|
|||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from pipecat.audio.vad.silero import SileroVADAnalyzer
|
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.processors.aggregators.openai_llm_context import OpenAILLMContext
|
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
||||||
from pipecat.services.openai.stt import OpenAISTTService
|
|
||||||
from pipecat.services.asyncai.tts import AsyncAITTSService
|
from pipecat.services.asyncai.tts import AsyncAITTSService
|
||||||
|
from pipecat.services.deepgram.stt import DeepgramSTTService
|
||||||
from pipecat.services.openai.llm import OpenAILLMService
|
from pipecat.services.openai.llm import OpenAILLMService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
||||||
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
|
||||||
@@ -49,11 +50,11 @@ transport_params = {
|
|||||||
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
|
||||||
logger.info(f"Starting bot")
|
logger.info(f"Starting bot")
|
||||||
|
|
||||||
stt = OpenAISTTService(api_key=os.getenv("OPENAI_API_KEY"))
|
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
||||||
|
|
||||||
tts = AsyncAITTSService(
|
tts = AsyncAITTSService(
|
||||||
api_key=os.getenv("ASYNCAI_API_KEY", ""),
|
api_key=os.getenv("ASYNCAI_API_KEY", ""),
|
||||||
voice_id=os.getenv("ASYNCAI_VOICE_ID", ""),
|
voice_id=os.getenv("ASYNCAI_VOICE_ID", "e0f39dc4-f691-4e78-bba5-5c636692cc04"),
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"))
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"))
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import sys
|
|
||||||
|
|
||||||
from pipecat.services import DeprecatedModuleProxy
|
|
||||||
|
|
||||||
from .tts import *
|
|
||||||
|
|
||||||
sys.modules[__name__] = DeprecatedModuleProxy(globals(), "asyncai", "asyncai.tts")
|
|
||||||
|
|||||||
@@ -6,15 +6,14 @@
|
|||||||
|
|
||||||
"""Async text-to-speech service implementations."""
|
"""Async text-to-speech service implementations."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
import uuid
|
|
||||||
from typing import AsyncGenerator, Optional
|
from typing import AsyncGenerator, Optional
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
import asyncio
|
|
||||||
import aiohttp
|
|
||||||
|
|
||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
CancelFrame,
|
CancelFrame,
|
||||||
@@ -103,7 +102,8 @@ class AsyncAITTSService(InterruptibleTTSService):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
api_key: Async API key.
|
api_key: Async API key.
|
||||||
voice_id: ID of the voice to use for synthesis.
|
voice_id: UUID of the voice to use for synthesis. See docs for a full list:
|
||||||
|
https://docs.async.ai/list-voices-16699698e0
|
||||||
version: Async API version.
|
version: Async API version.
|
||||||
url: WebSocket URL for Async TTS API.
|
url: WebSocket URL for Async TTS API.
|
||||||
model: TTS model to use (e.g., "asyncflow_v2.0").
|
model: TTS model to use (e.g., "asyncflow_v2.0").
|
||||||
@@ -138,7 +138,7 @@ class AsyncAITTSService(InterruptibleTTSService):
|
|||||||
if params.language
|
if params.language
|
||||||
else "en",
|
else "en",
|
||||||
}
|
}
|
||||||
|
|
||||||
self.set_model_name(model)
|
self.set_model_name(model)
|
||||||
self.set_voice(voice_id)
|
self.set_voice(voice_id)
|
||||||
|
|
||||||
@@ -165,15 +165,10 @@ class AsyncAITTSService(InterruptibleTTSService):
|
|||||||
"""
|
"""
|
||||||
return language_to_async_language(language)
|
return language_to_async_language(language)
|
||||||
|
|
||||||
def _build_msg(
|
def _build_msg(self, text: str = "", force: bool = False) -> str:
|
||||||
self, text: str = "", force: bool = False
|
msg = {"transcript": text, "force": force}
|
||||||
) -> str:
|
|
||||||
msg = {
|
|
||||||
"transcript": text,
|
|
||||||
"force": force
|
|
||||||
}
|
|
||||||
return json.dumps(msg)
|
return json.dumps(msg)
|
||||||
|
|
||||||
async def start(self, frame: StartFrame):
|
async def start(self, frame: StartFrame):
|
||||||
"""Start the Async TTS service.
|
"""Start the Async TTS service.
|
||||||
|
|
||||||
@@ -203,7 +198,7 @@ class AsyncAITTSService(InterruptibleTTSService):
|
|||||||
await self._disconnect()
|
await self._disconnect()
|
||||||
|
|
||||||
async def _connect(self):
|
async def _connect(self):
|
||||||
await self._connect_websocket()
|
await self._connect_websocket()
|
||||||
|
|
||||||
if self._websocket and not self._receive_task:
|
if self._websocket and not self._receive_task:
|
||||||
self._receive_task = self.create_task(self._receive_task_handler(self._report_error))
|
self._receive_task = self.create_task(self._receive_task_handler(self._report_error))
|
||||||
@@ -215,7 +210,7 @@ class AsyncAITTSService(InterruptibleTTSService):
|
|||||||
if self._receive_task:
|
if self._receive_task:
|
||||||
await self.cancel_task(self._receive_task)
|
await self.cancel_task(self._receive_task)
|
||||||
self._receive_task = None
|
self._receive_task = None
|
||||||
|
|
||||||
if self._keepalive_task:
|
if self._keepalive_task:
|
||||||
await self.cancel_task(self._keepalive_task)
|
await self.cancel_task(self._keepalive_task)
|
||||||
self._keepalive_task = None
|
self._keepalive_task = None
|
||||||
@@ -268,7 +263,7 @@ class AsyncAITTSService(InterruptibleTTSService):
|
|||||||
logger.trace(f"{self}: flushing audio")
|
logger.trace(f"{self}: flushing audio")
|
||||||
msg = self._build_msg(text=" ", force=True)
|
msg = self._build_msg(text=" ", force=True)
|
||||||
await self._websocket.send(msg)
|
await self._websocket.send(msg)
|
||||||
|
|
||||||
async def push_frame(self, frame: Frame, direction: FrameDirection = FrameDirection.DOWNSTREAM):
|
async def push_frame(self, frame: Frame, direction: FrameDirection = FrameDirection.DOWNSTREAM):
|
||||||
"""Push a frame downstream with special handling for stop conditions.
|
"""Push a frame downstream with special handling for stop conditions.
|
||||||
|
|
||||||
@@ -339,7 +334,7 @@ class AsyncAITTSService(InterruptibleTTSService):
|
|||||||
await self.start_ttfb_metrics()
|
await self.start_ttfb_metrics()
|
||||||
yield TTSStartedFrame()
|
yield TTSStartedFrame()
|
||||||
self._started = True
|
self._started = True
|
||||||
|
|
||||||
msg = self._build_msg(text=text)
|
msg = self._build_msg(text=text)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -355,6 +350,7 @@ class AsyncAITTSService(InterruptibleTTSService):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} exception: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
|
||||||
|
|
||||||
class AsyncAIHttpTTSService(TTSService):
|
class AsyncAIHttpTTSService(TTSService):
|
||||||
"""HTTP-based Async TTS service.
|
"""HTTP-based Async TTS service.
|
||||||
|
|
||||||
@@ -369,8 +365,9 @@ class AsyncAIHttpTTSService(TTSService):
|
|||||||
Parameters:
|
Parameters:
|
||||||
language: Language to use for synthesis.
|
language: Language to use for synthesis.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
language: Optional[Language] = Language.EN
|
language: Optional[Language] = Language.EN
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -391,6 +388,7 @@ class AsyncAIHttpTTSService(TTSService):
|
|||||||
Args:
|
Args:
|
||||||
api_key: Async API key.
|
api_key: Async API key.
|
||||||
voice_id: ID of the voice to use for synthesis.
|
voice_id: ID of the voice to use for synthesis.
|
||||||
|
aiohttp_session: An aiohttp session for making HTTP requests.
|
||||||
model: TTS model to use (e.g., "asyncflow_v2.0").
|
model: TTS model to use (e.g., "asyncflow_v2.0").
|
||||||
url: Base URL for Async API.
|
url: Base URL for Async API.
|
||||||
version: API version string for Async API.
|
version: API version string for Async API.
|
||||||
|
|||||||
Reference in New Issue
Block a user