Merge pull request #2 from daily-co/queueing
Adding queue transportation to services
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
18
src/dailyai/queue_frame.py
Normal file
18
src/dailyai/queue_frame.py
Normal 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
|
||||||
@@ -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()
|
||||||
|
self.logger.debug(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,29 @@ 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):
|
||||||
|
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 +92,20 @@ 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.")
|
||||||
|
|
||||||
|
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):
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -45,9 +45,29 @@ class DailyTransportService(EventHandler):
|
|||||||
self.camera_height = 768
|
self.camera_height = 768
|
||||||
self.camera_enabled = False
|
self.camera_enabled = False
|
||||||
|
|
||||||
|
self.other_participant_has_joined = False
|
||||||
|
|
||||||
self.camera_thread = None
|
self.camera_thread = None
|
||||||
self.frame_consumer_thread = None
|
self.frame_consumer_thread = None
|
||||||
|
|
||||||
|
self.transcription_settings = {
|
||||||
|
"language": "en",
|
||||||
|
"tier": "nova",
|
||||||
|
"model": "2-conversationalai",
|
||||||
|
"profanity_filter": True,
|
||||||
|
"redact": False,
|
||||||
|
"extra": {
|
||||||
|
"endpointing": True,
|
||||||
|
"punctuate": False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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,27 +182,34 @@ class DailyTransportService(EventHandler):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if self.token:
|
if self.token:
|
||||||
self.client.start_transcription(
|
self.transcription_queue = asyncio.Queue()
|
||||||
{
|
self.client.start_transcription(self.transcription_settings)
|
||||||
"language": "en",
|
|
||||||
"tier": "nova",
|
|
||||||
"model": "2-conversationalai",
|
|
||||||
"profanity_filter": True,
|
|
||||||
"redact": False,
|
|
||||||
"extra": {
|
|
||||||
"endpointing": True,
|
|
||||||
"punctuate": False,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
self.my_participant_id = self.client.participants()["local"]["id"]
|
self.my_participant_id = self.client.participants()["local"]["id"]
|
||||||
|
|
||||||
|
async def get_transcriptions(self):
|
||||||
|
while True:
|
||||||
|
transcript = await 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,15 +221,21 @@ 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):
|
||||||
self.stop_threads.set()
|
self.stop_threads.set()
|
||||||
|
|
||||||
|
def on_first_other_participant_joined(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def call_joined(self, join_data, client_error):
|
def call_joined(self, join_data, client_error):
|
||||||
self.logger.info(f"Call_joined: {join_data}, {client_error}")
|
self.logger.info(f"Call_joined: {join_data}, {client_error}")
|
||||||
|
|
||||||
@@ -213,7 +246,9 @@ class DailyTransportService(EventHandler):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def on_participant_joined(self, participant):
|
def on_participant_joined(self, participant):
|
||||||
pass
|
if not self.other_participant_has_joined and participant["id"] != self.my_participant_id:
|
||||||
|
self.other_participant_has_joined = True
|
||||||
|
self.on_first_other_participant_joined()
|
||||||
|
|
||||||
def on_participant_left(self, participant, reason):
|
def on_participant_left(self, participant, reason):
|
||||||
if len(self.client.participants()) < 2:
|
if len(self.client.participants()) < 2:
|
||||||
@@ -224,7 +259,10 @@ class DailyTransportService(EventHandler):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def on_transcription_message(self, message):
|
def on_transcription_message(self, message):
|
||||||
pass
|
print("got transcription", message)
|
||||||
|
if self.loop:
|
||||||
|
asyncio.run_coroutine_threadsafe(self.transcription_queue.put(message["text"]), self.loop)
|
||||||
|
print("put transcription in queue", message)
|
||||||
|
|
||||||
def on_transcription_stopped(self, stopped_by, stopped_by_error):
|
def on_transcription_stopped(self, stopped_by, stopped_by_error):
|
||||||
pass
|
pass
|
||||||
@@ -255,11 +293,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")
|
||||||
|
|
||||||
|
|||||||
@@ -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=None, output_queue=None, 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:
|
||||||
|
|||||||
@@ -66,9 +66,14 @@ class OpenAIImageGenService(ImageGenService):
|
|||||||
size=size
|
size=size
|
||||||
)
|
)
|
||||||
image_url = image.data[0].url
|
image_url = image.data[0].url
|
||||||
response = requests.get(image_url)
|
if not image_url:
|
||||||
|
raise Exception("No image provided in response", image)
|
||||||
|
|
||||||
dalle_stream = io.BytesIO(response.content)
|
# Load the image from the url
|
||||||
dalle_im = Image.open(dalle_stream)
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(image_url) as response:
|
||||||
|
image_stream = io.BytesIO(await response.content.read())
|
||||||
|
image = Image.open(image_stream)
|
||||||
|
return (image_url, image.tobytes())
|
||||||
|
|
||||||
return (image_url, dalle_im.tobytes())
|
return (image_url, dalle_im.tobytes())
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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))
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
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
|
||||||
|
|
||||||
local_joined = False
|
|
||||||
participant_joined = False
|
|
||||||
|
|
||||||
async def main(room_url):
|
async def main(room_url):
|
||||||
meeting_duration_minutes = 1
|
meeting_duration_minutes = 1
|
||||||
transport = DailyTransportService(
|
transport = DailyTransportService(
|
||||||
@@ -21,27 +17,26 @@ 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))
|
||||||
|
|
||||||
@transport.event_handler("on_participant_joined")
|
llm_task = asyncio.create_task(llm.run())
|
||||||
async def on_participant_joined(transport, participant):
|
|
||||||
if participant["id"] == transport.my_participant_id:
|
|
||||||
return
|
|
||||||
|
|
||||||
current_text = ""
|
@transport.event_handler("on_first_other_participant_joined")
|
||||||
async for text in llm_generator:
|
async def on_first_other_participant_joined(transport):
|
||||||
current_text += text
|
await asyncio.gather(llm_task, tts.run())
|
||||||
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 +51,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))
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -32,16 +32,20 @@ async def main(room_url:str):
|
|||||||
# queue two pieces of speech: one specified as a text literal,
|
# queue two pieces of speech: one specified as a text literal,
|
||||||
# and one generated by an llm. We'll kick off the llm first, and let
|
# and one generated by an llm. We'll kick off the llm first, and let
|
||||||
# it generate a response while we're speaking the literal string.
|
# it generate a response while we're speaking the literal string.
|
||||||
|
#
|
||||||
|
# Note that in this case, we don't use `run_llm_async` because we're
|
||||||
|
# taking advantage of the time spent speaking the first phrase to generate
|
||||||
|
# the entire LLM response, and this happens asynchronously in a task.
|
||||||
llm_response_task = asyncio.create_task(llm.run_llm(
|
llm_response_task = asyncio.create_task(llm.run_llm(
|
||||||
[{"role": "system", "content": "tell the user a joke about llamas"}]
|
[{"role": "system", "content": "tell the user 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."):
|
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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -25,7 +25,7 @@ async def main(room_url):
|
|||||||
transport.camera_height = 1024
|
transport.camera_height = 1024
|
||||||
|
|
||||||
llm = AzureLLMService()
|
llm = AzureLLMService()
|
||||||
tts = ElevenLabsTTSService()
|
tts = ElevenLabsTTSService(voice_id="ErXwobaYiN019PkySvjV")
|
||||||
dalle = OpenAIImageGenService()
|
dalle = OpenAIImageGenService()
|
||||||
|
|
||||||
# Get a complete audio chunk from the given text. Splitting this into its own
|
# Get a complete audio chunk from the given text. Splitting this into its own
|
||||||
@@ -39,9 +39,9 @@ async def main(room_url):
|
|||||||
|
|
||||||
async def get_month_data(month):
|
async def get_month_data(month):
|
||||||
image_text = ""
|
image_text = ""
|
||||||
current_clause = ""
|
|
||||||
tts_tasks = []
|
tts_tasks = []
|
||||||
async for text in llm.run_llm_async(
|
first_sentence = True
|
||||||
|
async for sentence in llm.run_llm_async_sentences(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"role": "system",
|
"role": "system",
|
||||||
@@ -49,18 +49,24 @@ async def main(room_url):
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
):
|
):
|
||||||
image_text += text
|
image_text += sentence
|
||||||
current_clause += text
|
|
||||||
if re.match(r"^.*[.!?]$", text):
|
if first_sentence:
|
||||||
tts_tasks.append(get_all_audio(current_clause))
|
sentence = f"{month}: {sentence}"
|
||||||
current_clause = ""
|
else:
|
||||||
|
first_sentence = False
|
||||||
|
|
||||||
|
tts_tasks.append(get_all_audio(sentence))
|
||||||
|
|
||||||
tts_tasks.insert(0, dalle.run_image_gen(image_text, "1024x1024"))
|
tts_tasks.insert(0, dalle.run_image_gen(image_text, "1024x1024"))
|
||||||
|
|
||||||
|
print(f"waiting for tasks to finish for {month}")
|
||||||
data = await asyncio.gather(
|
data = await asyncio.gather(
|
||||||
*tts_tasks
|
*tts_tasks
|
||||||
)
|
)
|
||||||
|
|
||||||
|
print(f"done gathering tts tasks for {month}")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"month": month,
|
"month": month,
|
||||||
"text": image_text,
|
"text": image_text,
|
||||||
@@ -83,11 +89,8 @@ async def main(room_url):
|
|||||||
"December",
|
"December",
|
||||||
]
|
]
|
||||||
|
|
||||||
@transport.event_handler("on_participant_joined")
|
@transport.event_handler("on_first_other_participant_joined")
|
||||||
async def on_participant_joined(transport, participant):
|
async def on_first_other_participant_joined(transport):
|
||||||
if participant["id"] == transport.my_participant_id:
|
|
||||||
return
|
|
||||||
|
|
||||||
# This will play the months in the order they're completed. The benefit
|
# This will play the months in the order they're completed. The benefit
|
||||||
# is we'll have as little delay as possible before the first month, and
|
# is we'll have as little delay as possible before the first month, and
|
||||||
# likely no delay between months, but the months won't display in order.
|
# likely no delay between months, but the months won't display in order.
|
||||||
@@ -95,12 +98,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()
|
||||||
@@ -116,6 +119,6 @@ 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))
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -26,28 +26,28 @@ async def main(room_url:str, token):
|
|||||||
llm = AzureLLMService()
|
llm = AzureLLMService()
|
||||||
tts = AzureTTSService()
|
tts = AzureTTSService()
|
||||||
|
|
||||||
transcribed_message = ""
|
async def handle_transcriptions():
|
||||||
transcription_timeout = None
|
messages = [
|
||||||
|
{"role": "system", "content": "You are a helpful LLM in a WebRTC call. Your goal is to demonstrate your capabilities in a succinct way. Your output will be converted to audio. Respond to what the user said in a creative and helpful way."},
|
||||||
|
]
|
||||||
|
|
||||||
@transport.event_handler("on_participant_joined")
|
sentence = ""
|
||||||
async def on_joined(transport, participant):
|
async for message in transport.get_transcriptions():
|
||||||
if participant["id"] == transport.my_participant_id:
|
sentence += message
|
||||||
return
|
if sentence.endswith((".", "?", "!")):
|
||||||
|
messages.append({"role": "user", "content": sentence})
|
||||||
|
sentence = ''
|
||||||
|
|
||||||
async for audio_chunk in tts.run_tts("If you say something, I will respond."):
|
full_response = ""
|
||||||
transport.output_queue.put(OutputQueueFrame(FrameType.AUDIO_FRAME, audio_chunk))
|
async for response in llm.run_llm_async_sentences(messages):
|
||||||
|
full_response += response
|
||||||
|
async for audio in tts.run_tts(response):
|
||||||
|
transport.output_queue.put(QueueFrame(FrameType.AUDIO_FRAME, audio))
|
||||||
|
|
||||||
@transport.event_handler("on_transcription_message")
|
messages.append({"role": "assistant", "content": full_response})
|
||||||
async def on_transcription_message(transport, message) -> None:
|
|
||||||
nonlocal transcribed_message
|
|
||||||
nonlocal transcription_timeout
|
|
||||||
print(message)
|
|
||||||
if message["session_id"] != transport.my_participant_id:
|
|
||||||
transcribed_message += message['text']
|
|
||||||
|
|
||||||
print("message received", transcribed_message)
|
transport.transcription_settings["extra"]["punctuate"] = True
|
||||||
|
await asyncio.gather(transport.run(), handle_transcriptions())
|
||||||
await transport.run()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user