Adding queue transportation to services

This commit is contained in:
Moishe Lettvin
2024-01-11 19:14:19 -05:00
parent 7ca7764be3
commit b9b82695c6
18 changed files with 194 additions and 87 deletions

View File

@@ -9,7 +9,7 @@ from queue import Queue, PriorityQueue, Empty
from threading import Event, Semaphore, Thread from threading import Event, Semaphore, Thread
from typing import Any, Generator, Iterator, Optional, Type from typing import Any, Generator, Iterator, Optional, Type
from dailyai.output_queue import OutputQueueFrame, FrameType from dailyai.queue_frame import QueueFrame, FrameType
from dailyai.message_handler.message_handler import MessageHandler from dailyai.message_handler.message_handler import MessageHandler
from dailyai.services.ai_services import AIServiceConfig from dailyai.services.ai_services import AIServiceConfig
@@ -268,10 +268,10 @@ class LLMResponse(OrchestratorResponse):
if out.strip(): if out.strip():
yield out.strip() yield out.strip()
def get_frames_from_tts_response(self, audio_frame) -> list[OutputQueueFrame]: def get_frames_from_tts_response(self, audio_frame) -> list[QueueFrame]:
return [OutputQueueFrame(FrameType.AUDIO_FRAME, audio_frame)] return [QueueFrame(FrameType.AUDIO_FRAME, audio_frame)]
def get_frames_from_chunk(self, chunk) -> Generator[list[OutputQueueFrame], Any, None]: def get_frames_from_chunk(self, chunk) -> Generator[list[QueueFrame], Any, None]:
for audio_frame in self.services.tts.run_tts(chunk): for audio_frame in self.services.tts.run_tts(chunk):
yield self.get_frames_from_tts_response(audio_frame) yield self.get_frames_from_tts_response(audio_frame)
@@ -317,7 +317,7 @@ class LLMResponse(OrchestratorResponse):
break break
if not self.has_sent_first_frame: if not self.has_sent_first_frame:
self.output_queue.put(OutputQueueFrame(FrameType.START_STREAM, None)) self.output_queue.put(QueueFrame(FrameType.START_STREAM, None))
self.has_sent_first_frame = True self.has_sent_first_frame = True
for frame in frames: for frame in frames:

View File

@@ -15,7 +15,7 @@ from dailyai.async_processor.async_processor import (
OrchestratorResponse, OrchestratorResponse,
LLMResponse, LLMResponse,
) )
from dailyai.output_queue import OutputQueueFrame, FrameType from dailyai.queue_frame import QueueFrame, FrameType
from dailyai.services.ai_services import AIServiceConfig from dailyai.services.ai_services import AIServiceConfig
from dailyai.message_handler.message_handler import MessageHandler from dailyai.message_handler.message_handler import MessageHandler
@@ -197,7 +197,7 @@ class Orchestrator(EventHandler):
self.logger.info("Camera thread stopped") self.logger.info("Camera thread stopped")
self.logger.info("Put stop in output queue") self.logger.info("Put stop in output queue")
self.output_queue.put(OutputQueueFrame(FrameType.END_STREAM, None)) self.output_queue.put(QueueFrame(FrameType.END_STREAM, None))
self.frame_consumer_thread.join() self.frame_consumer_thread.join()
self.logger.info("Orchestrator stopped.") self.logger.info("Orchestrator stopped.")
@@ -367,7 +367,7 @@ class Orchestrator(EventHandler):
all_audio_frames = bytearray() all_audio_frames = bytearray()
while True: while True:
try: try:
frame:OutputQueueFrame = self.output_queue.get() frame:QueueFrame = self.output_queue.get()
if frame.frame_type == FrameType.END_STREAM: if frame.frame_type == FrameType.END_STREAM:
self.logger.info("Stopping frame consumer thread") self.logger.info("Stopping frame consumer thread")
return return

View File

@@ -1,14 +0,0 @@
from enum import Enum
from dataclasses import dataclass
class FrameType(Enum):
AUDIO_FRAME = 1
IMAGE_FRAME = 2
START_STREAM = 3
END_STREAM = 4
@dataclass(frozen=True)
class OutputQueueFrame:
frame_type: FrameType
frame_data: bytes | None

View File

@@ -0,0 +1,18 @@
from enum import Enum
from dataclasses import dataclass
class FrameType(Enum):
START_STREAM = 0
END_STREAM = 1
AUDIO_FRAME = 2
IMAGE_FRAME = 3
SENTENCE_FRAME = 4
TEXT_CHUNK_FRAME = 5
LLM_MESSAGE_FRAME = 6
APP_MESSAGE_FRAME = 7
IMAGE_DESCRIPTION = 8
@dataclass(frozen=True)
class QueueFrame:
frame_type: FrameType
frame_data: str | dict | bytes | list | None

View File

@@ -1,23 +1,56 @@
import asyncio
import logging import logging
import re
from tkinter import END
from dailyai.queue_frame import QueueFrame, FrameType
from asyncio import Queue
from abc import abstractmethod from abc import abstractmethod
from typing import AsyncGenerator from typing import AsyncGenerator
from dataclasses import dataclass from dataclasses import dataclass
class AIService: class AIService:
def __init__(self):
self.logger = logging.getLogger("dailyai")
def close(self): def __init__(
self,
input_queue: asyncio.Queue[QueueFrame] | None = None,
output_queue: asyncio.Queue[QueueFrame] | None = None,
):
self.logger = logging.getLogger("dailyai")
self.input_queue: asyncio.Queue[QueueFrame] | None = input_queue
self.output_queue: asyncio.Queue[QueueFrame] | None = output_queue
def stop(self):
pass pass
async def run(self) -> None:
if self.input_queue is None or self.output_queue is None:
raise Exception("Input and output queues must be set before using the run method.")
while True:
frame = await self.input_queue.get()
print(f"{self.__class__.__name__} got frame:", frame.frame_type)
if frame.frame_type == FrameType.END_STREAM:
self.input_queue.task_done()
await self.output_queue.put(QueueFrame(FrameType.END_STREAM, None))
break
output_frame = await self.process_frame(frame)
if output_frame:
await self.output_queue.put(output_frame)
self.input_queue.task_done()
@abstractmethod
async def process_frame(self, frame) -> QueueFrame | None:
pass
class LLMService(AIService): class LLMService(AIService):
# Generate a set of responses to a prompt. Yields a list of responses. # Generate a set of responses to a prompt. Yields a list of responses.
@abstractmethod @abstractmethod
async def run_llm_async(self, messages) -> AsyncGenerator[str, None]: async def run_llm_async(self, messages) -> AsyncGenerator[str, None]:
pass # Adding a yield here lets the linter know what this method actually does
yield ""
# Generate a responses to a prompt. Returns the response # Generate a responses to a prompt. Returns the response
@abstractmethod @abstractmethod
@@ -26,6 +59,30 @@ class LLMService(AIService):
) -> str or None: ) -> str or None:
pass pass
async def run_llm_async_sentences(self, messages) -> AsyncGenerator[str, None]:
current_text = ""
async for text in self.run_llm_async(messages):
current_text += text
if re.match(r"^.*[.!?]$", text):
yield current_text
current_text = ""
if current_text:
yield current_text
async def process_frame(self, frame:QueueFrame) -> QueueFrame | None:
if not self.output_queue:
raise Exception("Output queue must be set before using the run method.")
if frame.frame_type == FrameType.LLM_MESSAGE_FRAME:
if type(frame.frame_data) != list:
raise Exception("LLM service requires a dict for the data field")
messages: list[dict[str, str]] = frame.frame_data
async for message in self.run_llm_async_sentences(messages):
print("got message", message)
await self.output_queue.put(QueueFrame(FrameType.SENTENCE_FRAME, message))
class TTSService(AIService): class TTSService(AIService):
# Some TTS services require a specific sample rate. We default to 16k # Some TTS services require a specific sample rate. We default to 16k
@@ -36,7 +93,21 @@ class TTSService(AIService):
# be sent to the microphone device # be sent to the microphone device
@abstractmethod @abstractmethod
async def run_tts(self, sentence) -> AsyncGenerator[bytes, None]: async def run_tts(self, sentence) -> AsyncGenerator[bytes, None]:
pass # yield empty bytes here, so linting can infer what this method does
yield bytes()
async def process_frame(self, frame:QueueFrame) -> QueueFrame | None:
if not self.output_queue:
raise Exception("Output queue must be set before using the run method.")
print(frame.frame_type)
if frame.frame_type == FrameType.SENTENCE_FRAME:
if type(frame.frame_data) != str:
raise Exception("TTS service requires a string for the data field")
text = frame.frame_data
async for audio in self.run_tts(text):
await self.output_queue.put(QueueFrame(FrameType.AUDIO_FRAME, audio))
class ImageGenService(AIService): class ImageGenService(AIService):

View File

@@ -16,8 +16,8 @@ from PIL import Image
from azure.cognitiveservices.speech import SpeechSynthesizer, SpeechConfig, ResultReason, CancellationReason from azure.cognitiveservices.speech import SpeechSynthesizer, SpeechConfig, ResultReason, CancellationReason
class AzureTTSService(TTSService): class AzureTTSService(TTSService):
def __init__(self, speech_key=None, speech_region=None): def __init__(self, input_queue=None, output_queue=None, speech_key=None, speech_region=None):
super().__init__() super().__init__(input_queue, output_queue)
speech_key = speech_key or os.getenv("AZURE_SPEECH_SERVICE_KEY") speech_key = speech_key or os.getenv("AZURE_SPEECH_SERVICE_KEY")
speech_region = speech_region or os.getenv("AZURE_SPEECH_SERVICE_REGION") speech_region = speech_region or os.getenv("AZURE_SPEECH_SERVICE_REGION")
@@ -48,8 +48,8 @@ class AzureTTSService(TTSService):
self.logger.info("Error details: {}".format(cancellation_details.error_details)) self.logger.info("Error details: {}".format(cancellation_details.error_details))
class AzureLLMService(LLMService): class AzureLLMService(LLMService):
def __init__(self, api_key=None, azure_endpoint=None, api_version=None, model=None): def __init__(self, input_queue=None, output_queue=None, api_key=None, azure_endpoint=None, api_version=None, model=None):
super().__init__() super().__init__(input_queue, output_queue)
api_key = api_key or os.getenv("AZURE_CHATGPT_KEY") api_key = api_key or os.getenv("AZURE_CHATGPT_KEY")
azure_endpoint = azure_endpoint or os.getenv("AZURE_CHATGPT_ENDPOINT") azure_endpoint = azure_endpoint or os.getenv("AZURE_CHATGPT_ENDPOINT")

View File

@@ -7,7 +7,7 @@ import types
from functools import partial from functools import partial
from queue import Queue, Empty from queue import Queue, Empty
from dailyai.output_queue import OutputQueueFrame, FrameType from dailyai.queue_frame import QueueFrame, FrameType
from threading import Thread, Event, Timer from threading import Thread, Event, Timer
@@ -48,6 +48,12 @@ class DailyTransportService(EventHandler):
self.camera_thread = None self.camera_thread = None
self.frame_consumer_thread = None self.frame_consumer_thread = None
# This queue is used to marshal frames from the async output queue to the sync output queue
# We need this to maintain the asynchronous behavior of asyncio queues -- to give async functions
# a chance to run while waiting for queue items -- but also to maintain thread safety for the
# primary output queue.
self.async_output_queue = asyncio.Queue()
self.logger: logging.Logger = logging.getLogger("dailyai") self.logger: logging.Logger = logging.getLogger("dailyai")
self.event_handlers = {} self.event_handlers = {}
@@ -162,6 +168,7 @@ class DailyTransportService(EventHandler):
) )
if self.token: if self.token:
self.transcription_queue = Queue()
self.client.start_transcription( self.client.start_transcription(
{ {
"language": "en", "language": "en",
@@ -178,11 +185,29 @@ class DailyTransportService(EventHandler):
self.my_participant_id = self.client.participants()["local"]["id"] self.my_participant_id = self.client.participants()["local"]["id"]
def get_transcriptions(self):
while True:
transcript = self.transcription_queue.get()
yield transcript
def get_async_output_queue(self):
return self.async_output_queue
async def marshal_frames(self):
while True:
frame = await self.async_output_queue.get()
self.output_queue.put(frame)
self.async_output_queue.task_done()
if frame.frame_type == FrameType.END_STREAM:
break
async def run(self) -> None: async def run(self) -> None:
self.configure_daily() self.configure_daily()
self.participant_left = False self.participant_left = False
async_output_queue_marshal_task = asyncio.create_task(self.marshal_frames())
try: try:
participant_count: int = len(self.client.participants()) participant_count: int = len(self.client.participants())
self.logger.info(f"{participant_count} participants in room") self.logger.info(f"{participant_count} participants in room")
@@ -194,10 +219,13 @@ class DailyTransportService(EventHandler):
self.client.leave() self.client.leave()
self.stop_threads.set() self.stop_threads.set()
await self.async_output_queue.put(QueueFrame(FrameType.END_STREAM, None))
await async_output_queue_marshal_task
if self.camera_thread and self.camera_thread.is_alive(): if self.camera_thread and self.camera_thread.is_alive():
self.camera_thread.join() self.camera_thread.join()
if self.frame_consumer_thread and self.frame_consumer_thread.is_alive(): if self.frame_consumer_thread and self.frame_consumer_thread.is_alive():
self.output_queue.put(OutputQueueFrame(FrameType.END_STREAM, None))
self.frame_consumer_thread.join() self.frame_consumer_thread.join()
def stop(self): def stop(self):
@@ -224,6 +252,7 @@ class DailyTransportService(EventHandler):
pass pass
def on_transcription_message(self, message): def on_transcription_message(self, message):
self.transcription_queue.put(message["text"])
pass pass
def on_transcription_stopped(self, stopped_by, stopped_by_error): def on_transcription_stopped(self, stopped_by, stopped_by_error):
@@ -255,11 +284,11 @@ class DailyTransportService(EventHandler):
all_audio_frames = bytearray() all_audio_frames = bytearray()
while True: while True:
try: try:
frames_or_frame: OutputQueueFrame | list[OutputQueueFrame] = self.output_queue.get() frames_or_frame: QueueFrame | list[QueueFrame] = self.output_queue.get()
if type(frames_or_frame) == OutputQueueFrame: if type(frames_or_frame) == QueueFrame:
frames: list[OutputQueueFrame] = [frames_or_frame] frames: list[QueueFrame] = [frames_or_frame]
elif type(frames_or_frame) == list: elif type(frames_or_frame) == list:
frames: list[OutputQueueFrame] = frames_or_frame frames: list[QueueFrame] = frames_or_frame
else: else:
raise Exception("Unknown type in output queue") raise Exception("Unknown type in output queue")

View File

@@ -9,11 +9,11 @@ from dailyai.services.ai_services import TTSService
class ElevenLabsTTSService(TTSService): class ElevenLabsTTSService(TTSService):
def __init__(self): def __init__(self, input_queue, output_queue, api_key=None, voice_id=None):
super().__init__() super().__init__(input_queue, output_queue)
self.api_key = os.getenv("ELEVENLABS_API_KEY") self.api_key = api_key or os.getenv("ELEVENLABS_API_KEY")
self.voice_id = os.getenv("ELEVENLABS_VOICE_ID") self.voice_id = voice_id or os.getenv("ELEVENLABS_VOICE_ID")
async def run_tts(self, sentence) -> AsyncGenerator[bytes, None]: async def run_tts(self, sentence) -> AsyncGenerator[bytes, None]:
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:

View File

@@ -11,7 +11,7 @@ from dailyai.async_processor.async_processor import (
LLMResponse, LLMResponse,
) )
from dailyai.message_handler.message_handler import MessageHandler from dailyai.message_handler.message_handler import MessageHandler
from dailyai.output_queue import OutputQueueFrame, FrameType from dailyai.queue_frame import QueueFrame, FrameType
from dailyai.services.ai_services import ( from dailyai.services.ai_services import (
AIServiceConfig, AIServiceConfig,
ImageGenService, ImageGenService,
@@ -71,7 +71,7 @@ class TestResponse(unittest.TestCase):
output_queue.task_done() output_queue.task_done()
while expected_words: while expected_words:
actual_word:OutputQueueFrame = output_queue.get() actual_word:QueueFrame = output_queue.get()
word = expected_words.pop(0) word = expected_words.pop(0)
self.assertEqual(actual_word.frame_type, FrameType.AUDIO_FRAME) self.assertEqual(actual_word.frame_type, FrameType.AUDIO_FRAME)
self.assertEqual(actual_word.frame_data, bytes(word, "utf-8")) self.assertEqual(actual_word.frame_data, bytes(word, "utf-8"))
@@ -127,7 +127,7 @@ class TestResponse(unittest.TestCase):
expected_words = ["Hello", "there.", "How", "are", "you?", "I", "hope", "you", "are", "well."] expected_words = ["Hello", "there.", "How", "are", "you?", "I", "hope", "you", "are", "well."]
while expected_words and not stop_processing_output_queue.is_set(): while expected_words and not stop_processing_output_queue.is_set():
try: try:
actual_word:OutputQueueFrame = output_queue.get_nowait() actual_word:QueueFrame = output_queue.get_nowait()
if actual_word.frame_type == FrameType.AUDIO_FRAME: if actual_word.frame_type == FrameType.AUDIO_FRAME:
time.sleep(0.1) time.sleep(0.1)
word = expected_words.pop(0) word = expected_words.pop(0)

View File

@@ -15,7 +15,7 @@ from dailyai.async_processor.async_processor import (
OrchestratorResponse OrchestratorResponse
) )
from dailyai.orchestrator import OrchestratorConfig, Orchestrator from dailyai.orchestrator import OrchestratorConfig, Orchestrator
from dailyai.output_queue import OutputQueueFrame, FrameType from dailyai.queue_frame import QueueFrame, FrameType
from dailyai.message_handler.message_handler import MessageHandler from dailyai.message_handler.message_handler import MessageHandler
from dailyai.services.ai_services import AIServiceConfig from dailyai.services.ai_services import AIServiceConfig
from dailyai.services.azure_ai_services import AzureImageGenService, AzureTTSService, AzureLLMService from dailyai.services.azure_ai_services import AzureImageGenService, AzureTTSService, AzureLLMService
@@ -40,7 +40,7 @@ class StaticSpriteResponse(OrchestratorResponse):
self.image_bytes = img.tobytes() self.image_bytes = img.tobytes()
def do_play(self) -> None: def do_play(self) -> None:
self.output_queue.put(OutputQueueFrame(FrameType.IMAGE_FRAME, self.image_bytes)) self.output_queue.put(QueueFrame(FrameType.IMAGE_FRAME, self.image_bytes))
class IntroSpriteResponse(StaticSpriteResponse): class IntroSpriteResponse(StaticSpriteResponse):
@@ -71,10 +71,10 @@ class AnimatedSpriteLLMResponse(LLMResponse):
with Image.open(full_path) as img: with Image.open(full_path) as img:
self.image_bytes.append(img.tobytes()) self.image_bytes.append(img.tobytes())
def get_frames_from_tts_response(self, audio_frame) -> list[OutputQueueFrame]: def get_frames_from_tts_response(self, audio_frame) -> list[QueueFrame]:
return [ return [
OutputQueueFrame(FrameType.AUDIO_FRAME, audio_frame), QueueFrame(FrameType.AUDIO_FRAME, audio_frame),
OutputQueueFrame(FrameType.IMAGE_FRAME, random.choice(self.image_bytes)) QueueFrame(FrameType.IMAGE_FRAME, random.choice(self.image_bytes))
] ]

View File

@@ -2,7 +2,7 @@ import argparse
import asyncio import asyncio
from typing import AsyncGenerator from typing import AsyncGenerator
from dailyai.output_queue import OutputQueueFrame, FrameType from dailyai.queue_frame import QueueFrame, FrameType
from dailyai.services.daily_transport_service import DailyTransportService from dailyai.services.daily_transport_service import DailyTransportService
from dailyai.services.azure_ai_services import AzureTTSService from dailyai.services.azure_ai_services import AzureTTSService
@@ -37,7 +37,7 @@ async def main(room_url):
if participant["info"]["isLocal"]: if participant["info"]["isLocal"]:
return return
async for audio in audio_generator: async for audio in audio_generator:
transport.output_queue.put(OutputQueueFrame(FrameType.AUDIO_FRAME, audio)) transport.output_queue.put(QueueFrame(FrameType.AUDIO_FRAME, audio))
# wait for the output queue to be empty, then leave the meeting # wait for the output queue to be empty, then leave the meeting
transport.output_queue.join() transport.output_queue.join()

View File

@@ -2,7 +2,7 @@ import asyncio
import time import time
from typing import AsyncGenerator from typing import AsyncGenerator
from dailyai.output_queue import OutputQueueFrame, FrameType from dailyai.queue_frame import QueueFrame, FrameType
from dailyai.services.daily_transport_service import DailyTransportService from dailyai.services.daily_transport_service import DailyTransportService
from dailyai.services.azure_ai_services import AzureTTSService from dailyai.services.azure_ai_services import AzureTTSService
from dailyai.services.deepgram_ai_services import DeepgramTTSService from dailyai.services.deepgram_ai_services import DeepgramTTSService
@@ -41,13 +41,13 @@ async def main(room_url):
audio_generator: AsyncGenerator[bytes, None] = tts.run_tts(f"Hello there, {participant['info']['userName']}!") audio_generator: AsyncGenerator[bytes, None] = tts.run_tts(f"Hello there, {participant['info']['userName']}!")
async for audio in audio_generator: async for audio in audio_generator:
transport.output_queue.put(OutputQueueFrame(FrameType.AUDIO_FRAME, audio)) transport.output_queue.put(QueueFrame(FrameType.AUDIO_FRAME, audio))
print("setting up call state handler") print("setting up call state handler")
@transport.event_handler("on_call_state_updated") @transport.event_handler("on_call_state_updated")
async def on_call_joined(transport, state): async def on_call_joined(transport, state):
print(f"call state callback: {state}") print(f"call state callback: {state}")
await transport.run() await transport.run()

View File

@@ -1,9 +1,8 @@
import argparse import argparse
import asyncio import asyncio
import re
from typing import AsyncGenerator from typing import AsyncGenerator
from dailyai.output_queue import OutputQueueFrame, FrameType from dailyai.queue_frame import QueueFrame, FrameType
from dailyai.services.daily_transport_service import DailyTransportService from dailyai.services.daily_transport_service import DailyTransportService
from dailyai.services.azure_ai_services import AzureLLMService from dailyai.services.azure_ai_services import AzureLLMService
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
@@ -21,27 +20,32 @@ async def main(room_url):
) )
transport.mic_enabled = True transport.mic_enabled = True
tts = ElevenLabsTTSService() text_to_llm_queue = asyncio.Queue()
llm = AzureLLMService() llm_to_tts_queue = asyncio.Queue()
tts = ElevenLabsTTSService(
llm_to_tts_queue, transport.get_async_output_queue(), voice_id="29vD33N1CtxCmqQRPOHJ"
)
llm = AzureLLMService(text_to_llm_queue, llm_to_tts_queue)
messages = [{ messages = [{
"role": "system", "role": "system",
"content": "You are an LLM in a WebRTC session, and your text will be converted to audio. Introduce yourself." "content": "You are an LLM in a WebRTC session, and this is a 'hello world' demo. Say hello to the world."
}] }]
llm_generator: AsyncGenerator[str, None] = llm.run_llm_async(messages) await text_to_llm_queue.put(QueueFrame(FrameType.LLM_MESSAGE_FRAME, messages))
await text_to_llm_queue.put(QueueFrame(FrameType.END_STREAM, None))
llm_task = asyncio.create_task(llm.run())
has_joined = False
@transport.event_handler("on_participant_joined") @transport.event_handler("on_participant_joined")
async def on_participant_joined(transport, participant): async def on_participant_joined(transport, participant):
if participant["id"] == transport.my_participant_id: nonlocal has_joined
if participant["id"] == transport.my_participant_id or has_joined:
return return
current_text = "" has_joined = True
async for text in llm_generator: await asyncio.gather(llm_task, tts.run())
current_text += text
if re.match(r"^.*[.!?]$", text):
async for audio in tts.run_tts(current_text):
transport.output_queue.put(OutputQueueFrame(FrameType.AUDIO_FRAME, audio))
current_text = ""
# wait for the output queue to be empty, then leave the meeting # wait for the output queue to be empty, then leave the meeting
transport.output_queue.join() transport.output_queue.join()
@@ -56,6 +60,5 @@ if __name__ == "__main__":
"-u", "--url", type=str, required=True, help="URL of the Daily room to join" "-u", "--url", type=str, required=True, help="URL of the Daily room to join"
) )
args: argparse.Namespace = parser.parse_args() args, unknown = parser.parse_known_args()
asyncio.run(main(args.url)) asyncio.run(main(args.url))

View File

@@ -1,7 +1,7 @@
import argparse import argparse
import asyncio import asyncio
from dailyai.output_queue import OutputQueueFrame, FrameType from dailyai.queue_frame import QueueFrame, FrameType
from dailyai.services.daily_transport_service import DailyTransportService from dailyai.services.daily_transport_service import DailyTransportService
from dailyai.services.open_ai_services import OpenAIImageGenService from dailyai.services.open_ai_services import OpenAIImageGenService
@@ -27,7 +27,7 @@ async def main(room_url):
@transport.event_handler("on_participant_joined") @transport.event_handler("on_participant_joined")
async def on_participant_joined(transport, participant): async def on_participant_joined(transport, participant):
(_, image_bytes) = await image_task (_, image_bytes) = await image_task
transport.output_queue.put(OutputQueueFrame(FrameType.IMAGE_FRAME, image_bytes)) transport.output_queue.put(QueueFrame(FrameType.IMAGE_FRAME, image_bytes))
await transport.run() await transport.run()

View File

@@ -4,7 +4,7 @@ import re
from dailyai.services.daily_transport_service import DailyTransportService from dailyai.services.daily_transport_service import DailyTransportService
from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService
from dailyai.output_queue import OutputQueueFrame, FrameType from dailyai.queue_frame import QueueFrame, FrameType
async def main(room_url:str): async def main(room_url:str):
global transport global transport
@@ -37,11 +37,11 @@ async def main(room_url:str):
)) ))
async for audio_chunk in tts.run_tts("My friend the LLM is now going to tell a joke about llamas."): async for audio_chunk in tts.run_tts("My friend the LLM is now going to tell a joke about llamas."):
transport.output_queue.put(OutputQueueFrame(FrameType.AUDIO_FRAME, audio_chunk)) transport.output_queue.put(QueueFrame(FrameType.AUDIO_FRAME, audio_chunk))
llm_response = await llm_response_task llm_response = await llm_response_task
async for audio_chunk in tts.run_tts(llm_response): async for audio_chunk in tts.run_tts(llm_response):
transport.output_queue.put(OutputQueueFrame(FrameType.AUDIO_FRAME, audio_chunk)) transport.output_queue.put(QueueFrame(FrameType.AUDIO_FRAME, audio_chunk))
# wait for the output queue to be empty, then leave the meeting # wait for the output queue to be empty, then leave the meeting

View File

@@ -4,7 +4,7 @@ import asyncio
from asyncio.queues import Queue from asyncio.queues import Queue
import re import re
from dailyai.output_queue import OutputQueueFrame, FrameType from dailyai.queue_frame import QueueFrame, FrameType
from dailyai.services.azure_ai_services import AzureLLMService from dailyai.services.azure_ai_services import AzureLLMService
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
from dailyai.services.open_ai_services import OpenAIImageGenService from dailyai.services.open_ai_services import OpenAIImageGenService
@@ -95,12 +95,12 @@ async def main(room_url):
data = await month_data_task data = await month_data_task
transport.output_queue.put( transport.output_queue.put(
[ [
OutputQueueFrame(FrameType.IMAGE_FRAME, data["image"]), QueueFrame(FrameType.IMAGE_FRAME, data["image"]),
OutputQueueFrame(FrameType.AUDIO_FRAME, data["audio"][0]), QueueFrame(FrameType.AUDIO_FRAME, data["audio"][0]),
] ]
) )
for audio in data["audio"][1:]: for audio in data["audio"][1:]:
transport.output_queue.put(OutputQueueFrame(FrameType.AUDIO_FRAME, audio)) transport.output_queue.put(QueueFrame(FrameType.AUDIO_FRAME, audio))
# wait for the output queue to be empty, then leave the meeting # wait for the output queue to be empty, then leave the meeting
transport.output_queue.join() transport.output_queue.join()

View File

@@ -5,7 +5,7 @@ import asyncio
from asyncio.queues import Queue from asyncio.queues import Queue
import re import re
from dailyai.output_queue import OutputQueueFrame, FrameType from dailyai.queue_frame import QueueFrame, FrameType
from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
from dailyai.services.open_ai_services import OpenAILLMService, OpenAIImageGenService from dailyai.services.open_ai_services import OpenAILLMService, OpenAIImageGenService
@@ -97,12 +97,12 @@ async def main(room_url):
data = await month_data_task data = await month_data_task
transport.output_queue.put( transport.output_queue.put(
[ [
OutputQueueFrame(FrameType.IMAGE_FRAME, data["image"]), QueueFrame(FrameType.IMAGE_FRAME, data["image"]),
OutputQueueFrame(FrameType.AUDIO_FRAME, data["audio"][0]), QueueFrame(FrameType.AUDIO_FRAME, data["audio"][0]),
] ]
) )
for audio in data["audio"][1:]: for audio in data["audio"][1:]:
transport.output_queue.put(OutputQueueFrame(FrameType.AUDIO_FRAME, audio)) transport.output_queue.put(QueueFrame(FrameType.AUDIO_FRAME, audio))
# wait for the output queue to be empty, then leave the meeting # wait for the output queue to be empty, then leave the meeting
transport.output_queue.join() transport.output_queue.join()

View File

@@ -6,7 +6,7 @@ import urllib.parse
from dailyai.services.daily_transport_service import DailyTransportService from dailyai.services.daily_transport_service import DailyTransportService
from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService
from dailyai.output_queue import OutputQueueFrame, FrameType from dailyai.queue_frame import QueueFrame, FrameType
async def main(room_url:str, token): async def main(room_url:str, token):
global transport global transport
@@ -35,7 +35,7 @@ async def main(room_url:str, token):
return return
async for audio_chunk in tts.run_tts("If you say something, I will respond."): async for audio_chunk in tts.run_tts("If you say something, I will respond."):
transport.output_queue.put(OutputQueueFrame(FrameType.AUDIO_FRAME, audio_chunk)) transport.output_queue.put(QueueFrame(FrameType.AUDIO_FRAME, audio_chunk))
@transport.event_handler("on_transcription_message") @transport.event_handler("on_transcription_message")
async def on_transcription_message(transport, message) -> None: async def on_transcription_message(transport, message) -> None: