From 8cb0d8a7202646494b5dc4447e0e7add02e597e0 Mon Sep 17 00:00:00 2001 From: Nour Shaker Date: Fri, 11 Apr 2025 13:10:51 +0100 Subject: [PATCH 1/2] Moving to native OpenAI SDK, no need for additional client SDK Refactored the code and renamed functions to reflect their functionality packaging the azureOpenAIService as a class to improve reusablilty with constructors and destructors to help maintain healthy connections lifecycle --- .../aoai-whl/rtclient-0.5.1-py3-none-any.whl | Bin 19694 -> 0 bytes .../azureOpenAIService.py | 283 +++++++++++------- callautomation-azure-openai-voice/main.py | 15 +- .../mediaStreamingHandler.py | 12 - callautomation-azure-openai-voice/readme.md | 5 +- .../requirements.txt | 3 +- 6 files changed, 184 insertions(+), 134 deletions(-) delete mode 100644 callautomation-azure-openai-voice/aoai-whl/rtclient-0.5.1-py3-none-any.whl delete mode 100644 callautomation-azure-openai-voice/mediaStreamingHandler.py diff --git a/callautomation-azure-openai-voice/aoai-whl/rtclient-0.5.1-py3-none-any.whl b/callautomation-azure-openai-voice/aoai-whl/rtclient-0.5.1-py3-none-any.whl deleted file mode 100644 index 8dfcd955f5be73255d9f35f2707db1cca5b2b66d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19694 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 None: + print("Hello World") + self.client = AsyncAzureOpenAI( + azure_endpoint=AZURE_OPENAI_API_ENDPOINT, + azure_deployment=AZURE_OPENAI_DEPLOYMENT_NAME, + api_key=AZURE_OPENAI_API_KEY, + api_version=AZURE_OPENAI_API_VERSION, + ) + self.connection_manager = self.client.beta.realtime.connect( + model="gpt-4o-realtime-preview" # Replace with your deployed realtime model id on Azure OpenAI. + ) + + def __exit__(self, exc_type, exc_value, traceback): + self.connection.close() + self.incoming_websocket.close() + +#start_conversation > start_client + async def start_client(self): + self.connection = await self.connection_manager.enter() + await self.connection.session.update(session=session_config()) + await self.connection.response.create() + ### running an async task to listen and recieve oai messages + asyncio.create_task(self.receive_oai_messages()) + +#send_audio_to_external_ai > audio_to_oai + async def audio_to_oai(self, audioData: str): + await self.connection.input_audio_buffer.append(audio=audioData) + +#receive_messages > receive_oai_messages + async def receive_oai_messages(self): + #while not self.connection._connection.close_code: + async for event in self.connection: + #print(event) + if event is None: + continue + match event.type: + case "session.created": + print("Session Created Message") + print(f" Session Id: {event.session.id}") + pass + case "error": + print(f" Error: {event.error}") + pass + case "input_audio_buffer.cleared": + print("Input Audio Buffer Cleared Message") + pass + case "input_audio_buffer.speech_started": + print(f"Voice activity detection started at {event.audio_start_ms} [ms]") + await self.stop_audio() + pass + case "input_audio_buffer.speech_stopped": + pass + case "conversation.item.input_audio_transcription.completed": + print(f" User:-- {event.transcript}") + case "conversation.item.input_audio_transcription.failed": + print(f" Error: {event.error}") + case "response.done": + print("Response Done Message") + print(f" Response Id: {event.response.id}") + if event.response.status_details: + print(f" Status Details: {event.response.status_details.model_dump_json()}") + case "response.audio_transcript.done": + print(f" AI:-- {event.transcript}") + case "response.audio.delta": + await self.oai_to_acs(event.delta) + pass + case _: + pass + +#init_websocket -> init_incoming_websocket (incoming) + async def init_incoming_websocket(self, socket): + # print("--inbound socket set") + self.incoming_websocket = socket + +#receive_audio_for_outbound > oai_to_acs + async def oai_to_acs(self, data): + try: + data = { + "Kind": "AudioData", + "AudioData": { + "Data": data + }, + "StopAudio": None + } + + # Serialize the server streaming data + serialized_data = json.dumps(data) + await self.send_message(serialized_data) + + except Exception as e: + print(e) + +# stop oai talking when detecting the user talking + async def stop_audio(self): + stop_audio_data = { + "Kind": "StopAudio", + "AudioData": None, + "StopAudio": {} + } + + json_data = json.dumps(stop_audio_data) + await self.send_message(json_data) + +# send_message > send_message + async def send_message(self, message: str): + try: + await self.incoming_websocket.send(message) + except Exception as e: + print(f"Failed to send message: {e}") + + async def send_welcome(self): + if not self.welcomed: + await self.connection.conversation.item.create( + item={ + "type": "message", + "role": "user", + "content": [{"type": "input_text", "text": "Hi! What's your name and who do you work for?"}], + } ) - ) - - asyncio.create_task(receive_messages(client)) - -async def send_audio_to_external_ai(audioData: str): - await client.send(message=InputAudioBufferAppendMessage(type="input_audio_buffer.append", audio=audioData, _is_azure=True)) - -async def receive_messages(client: RTLowLevelClient): - while not client.closed: - message = await client.recv() - if message is None: - continue - match message.type: - case "session.created": - print("Session Created Message") - print(f" Session Id: {message.session.id}") - pass - case "error": - print(f" Error: {message.error}") - pass - case "input_audio_buffer.cleared": - print("Input Audio Buffer Cleared Message") - pass - case "input_audio_buffer.speech_started": - print(f"Voice activity detection started at {message.audio_start_ms} [ms]") - await stop_audio() - pass - case "input_audio_buffer.speech_stopped": - pass - case "conversation.item.input_audio_transcription.completed": - print(f" User:-- {message.transcript}") - case "conversation.item.input_audio_transcription.failed": - print(f" Error: {message.error}") - case "response.done": - print("Response Done Message") - print(f" Response Id: {message.response.id}") - if message.response.status_details: - print(f" Status Details: {message.response.status_details.model_dump_json()}") - case "response.audio_transcript.done": - print(f" AI:-- {message.transcript}") - case "response.audio.delta": - await receive_audio_for_outbound(message.delta) - pass - case _: - pass - -async def init_websocket(socket): - global active_websocket - active_websocket = socket - -async def receive_audio_for_outbound(data): - try: - data = { - "Kind": "AudioData", - "AudioData": { - "Data": data - }, - "StopAudio": None - } - - # Serialize the server streaming data - serialized_data = json.dumps(data) - await send_message(serialized_data) - - except Exception as e: - print(e) - -async def stop_audio(): - stop_audio_data = { - "Kind": "StopAudio", - "AudioData": None, - "StopAudio": {} - } - - json_data = json.dumps(stop_audio_data) - await send_message(json_data) - -async def send_message(message: str): - global active_websocket - try: - await active_websocket.send(message) - except Exception as e: - print(f"Failed to send message: {e}") + await self.connection.response.create() + self.welcomed = True +#mediaStreamingHandler.process_websocket_message_async -> acs_to_oai + async def acs_to_oai(self, stream_data): + try: + data = json.loads(stream_data) + kind = data['kind'] + if kind == "AudioData": + audio_data = data["audioData"]["data"] + await self.audio_to_oai(audio_data) + except Exception as e: + print(f'Error processing WebSocket message: {e}') \ No newline at end of file diff --git a/callautomation-azure-openai-voice/main.py b/callautomation-azure-openai-voice/main.py index 3e2ca4c..ded61f0 100644 --- a/callautomation-azure-openai-voice/main.py +++ b/callautomation-azure-openai-voice/main.py @@ -15,8 +15,7 @@ import uuid from azure.core.messaging import CloudEvent -from azureOpenAIService import init_websocket, start_conversation -from mediaStreamingHandler import process_websocket_message_async +from azureOpenAIService import OpenAIRTHandler from threading import Thread # Your ACS resource connection string @@ -110,14 +109,16 @@ async def callbacks(contextId): # WebSocket. @app.websocket('/ws') async def ws(): + handler = OpenAIRTHandler() print("Client connected to WebSocket") - await init_websocket(websocket) - await start_conversation() - while True: + await handler.init_incoming_websocket(websocket) + await handler.start_client() + while websocket: try: # Receive data from the client data = await websocket.receive() - await process_websocket_message_async(data) + await handler.acs_to_oai(data) + await handler.send_welcome() except Exception as e: print(f"WebSocket connection closed: {e}") break @@ -128,7 +129,7 @@ def home(): if __name__ == '__main__': app.logger.setLevel(INFO) - app.run(port=8080) + app.run(port=8000) diff --git a/callautomation-azure-openai-voice/mediaStreamingHandler.py b/callautomation-azure-openai-voice/mediaStreamingHandler.py deleted file mode 100644 index eaa2373..0000000 --- a/callautomation-azure-openai-voice/mediaStreamingHandler.py +++ /dev/null @@ -1,12 +0,0 @@ -import json -from azureOpenAIService import send_audio_to_external_ai - -async def process_websocket_message_async(stream_data): - try: - data = json.loads(stream_data) - kind = data['kind'] - if kind == "AudioData": - audio_data = data["audioData"]["data"] - await send_audio_to_external_ai(audio_data) - except Exception as e: - print(f'Error processing WebSocket message: {e}') \ No newline at end of file diff --git a/callautomation-azure-openai-voice/readme.md b/callautomation-azure-openai-voice/readme.md index c149a53..0df4c77 100644 --- a/callautomation-azure-openai-voice/readme.md +++ b/callautomation-azure-openai-voice/readme.md @@ -25,7 +25,6 @@ This is a sample application demonstrated during Microsoft Ignite 2024. It highl Create and activate python virtual environment and install required packages using following command ``` pip install -r requirements.txt -pip install -r ./aoai-whl/rtclient-0.5.1-py3-none-any.whl ``` ### Setup and host your Azure DevTunnel @@ -34,7 +33,7 @@ pip install -r ./aoai-whl/rtclient-0.5.1-py3-none-any.whl ```bash devtunnel create --allow-anonymous -devtunnel port create -p 8080 +devtunnel port create -p 8000 devtunnel host ``` @@ -54,7 +53,7 @@ Open `azureOpenAIService.py` file to configure the following settings ## Run app locally 1. Navigate to `callautomation-azure-openai-voice` folder and run `main.py` in debug mode or use command `python ./main.py` to run it from PowerShell, Command Prompt or Unix Terminal -2. Browser should pop up with the below page. If not navigate it to `http://localhost:8080/`or your dev tunnel url. +2. Browser should pop up with the below page. If not navigate it to `http://localhost:8000/`or your dev tunnel url. 3. Register an EventGrid Webhook for the IncomingCall(`https:///api/incomingCall`) event that points to your devtunnel URI. Instructions [here](https://learn.microsoft.com/en-us/azure/communication-services/concepts/call-automation/incoming-call-notification). Once that's completed you should have a running application. The best way to test this is to place a call to your ACS phone number and talk to your intelligent agent. diff --git a/callautomation-azure-openai-voice/requirements.txt b/callautomation-azure-openai-voice/requirements.txt index ba72a08..be5c10d 100644 --- a/callautomation-azure-openai-voice/requirements.txt +++ b/callautomation-azure-openai-voice/requirements.txt @@ -1,4 +1,5 @@ Quart>=0.19.6 azure-eventgrid==4.11.0 aiohttp>= 3.11.9 -azure-communication-callautomation==1.4.0b1 \ No newline at end of file +azure-communication-callautomation==1.4.0b1 +openai \ No newline at end of file From 57e1996b27b7c5ca7d8b3a9432153e99f2f054f2 Mon Sep 17 00:00:00 2001 From: Nour Shaker Date: Fri, 11 Apr 2025 14:18:06 +0100 Subject: [PATCH 2/2] missing requirement - openai[realtime] --- callautomation-azure-openai-voice/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/callautomation-azure-openai-voice/requirements.txt b/callautomation-azure-openai-voice/requirements.txt index be5c10d..ef5c9b3 100644 --- a/callautomation-azure-openai-voice/requirements.txt +++ b/callautomation-azure-openai-voice/requirements.txt @@ -2,4 +2,5 @@ Quart>=0.19.6 azure-eventgrid==4.11.0 aiohttp>= 3.11.9 azure-communication-callautomation==1.4.0b1 -openai \ No newline at end of file +openai +openai[realtime] \ No newline at end of file