From 7229fc806e8783ec16e62dba0d2de4a33e4ce0d2 Mon Sep 17 00:00:00 2001 From: Moishe Lettvin Date: Wed, 10 Jan 2024 13:00:36 -0500 Subject: [PATCH] getting started on 06- --- src/dailyai/services/ai_services.py | 8 +- .../services/daily_transport_service.py | 6 +- .../to_be_updated/google_ai_service.py | 1 - .../06-listen-and-respond.py | 89 +++++++++++++++++++ 4 files changed, 95 insertions(+), 9 deletions(-) create mode 100644 src/samples/theoretical-to-real/06-listen-and-respond.py diff --git a/src/dailyai/services/ai_services.py b/src/dailyai/services/ai_services.py index a99a7aba4..60f04cba5 100644 --- a/src/dailyai/services/ai_services.py +++ b/src/dailyai/services/ai_services.py @@ -1,8 +1,8 @@ import logging +from asyncio import Queue from abc import abstractmethod -from collections.abc import AsyncGenerator - +from typing import AsyncGenerator from dataclasses import dataclass @@ -16,9 +16,7 @@ class AIService: class LLMService(AIService): # Generate a set of responses to a prompt. Yields a list of responses. @abstractmethod - async def run_llm_async( - self, messages - ) -> AsyncGenerator[str, None]: + async def run_llm_async(self, messages) -> AsyncGenerator[str, None]: pass # Generate a responses to a prompt. Returns the response diff --git a/src/dailyai/services/daily_transport_service.py b/src/dailyai/services/daily_transport_service.py index e18fbfc41..85fe4a212 100644 --- a/src/dailyai/services/daily_transport_service.py +++ b/src/dailyai/services/daily_transport_service.py @@ -222,13 +222,13 @@ class DailyTransportService(EventHandler): pass def on_transcription_stopped(self, stopped_by, stopped_by_error): - self.logger.info(f"Transcription stopped {stopped_by}, {stopped_by_error}") + pass def on_transcription_error(self, message): - self.logger.error(f"Transcription error {message}") + pass def on_transcription_started(self, status): - self.logger.info(f"Transcription started {status}") + pass def set_image(self, image: bytes): self.image: bytes | None = image diff --git a/src/dailyai/services/to_be_updated/google_ai_service.py b/src/dailyai/services/to_be_updated/google_ai_service.py index c8b0715df..8a742d79f 100644 --- a/src/dailyai/services/to_be_updated/google_ai_service.py +++ b/src/dailyai/services/to_be_updated/google_ai_service.py @@ -20,7 +20,6 @@ class GoogleAIService(AIService): ) def run_tts(self, sentence): - print("running google tts") synthesis_input = texttospeech.SynthesisInput(text = sentence.strip()) result = self.client.synthesize_speech(input=synthesis_input, voice=self.voice, audio_config=self.audio_config) return result diff --git a/src/samples/theoretical-to-real/06-listen-and-respond.py b/src/samples/theoretical-to-real/06-listen-and-respond.py new file mode 100644 index 000000000..149439a65 --- /dev/null +++ b/src/samples/theoretical-to-real/06-listen-and-respond.py @@ -0,0 +1,89 @@ +import argparse +import asyncio +import requests + +from dailyai.services.daily_transport_service import DailyTransportService +from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService +from dailyai.output_queue import OutputQueueFrame, FrameType + +async def main(room_url:str, token): + global transport + global llm + global tts + + transport = DailyTransportService( + room_url, + token, + "Say Two Things Bot", + 1, + ) + transport.mic_enabled = True + transport.mic_sample_rate = 16000 + transport.camera_enabled = False + + llm = AzureLLMService() + tts = AzureTTSService() + + @transport.event_handler("on_participant_joined") + async def on_joined(transport, participant): + if participant["id"] == transport.my_participant_id: + return + + # 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 + # it generate a response while we're speaking the literal string. + llm_response_task = asyncio.create_task(llm.run_llm( + [{"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."): + transport.output_queue.put(OutputQueueFrame(FrameType.AUDIO_FRAME, audio_chunk)) + + llm_response = await llm_response_task + async for audio_chunk in tts.run_tts(llm_response): + transport.output_queue.put(OutputQueueFrame(FrameType.AUDIO_FRAME, audio_chunk)) + + # wait for the output queue to be empty, then leave the meeting + transport.output_queue.join() + transport.stop() + + @transport.event_handler("on_transcription_message") + async def on_transcription_message(transport, message) -> None: + print("message received", message) + + await transport.run() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Simple Daily Bot Sample") + parser.add_argument( + "-u", "--url", type=str, required=True, help="URL of the Daily room to join" + ) + parser.add_argument( + "-k", + "--apikey", + type=str, + required=True, + help="Daily API Key (needed to create token)", + ) + + args: argparse.Namespace = parser.parse_args() + + # Create a meeting token for the given room with an expiration 1 hour in the future. + room_name: str = urllib.parse.urlparse(args.url).path[1:] + expiration: float = time.time() + 60 * 60 + + res: requests.Response = requests.post( + f"https://api.daily.co/v1/meeting-tokens", + headers={"Authorization": f"Bearer {args.apikey}"}, + json={ + "properties": {"room_name": room_name, "is_owner": True, "exp": expiration} + }, + ) + + if res.status_code != 200: + raise Exception(f"Failed to create meeting token: {res.status_code} {res.text}") + + token: str = res.json()["token"] + + asyncio.run(main(args.url, token))