11
11
####################################################################
12
12
#
13
13
# /// script
14
- # requires-python = ">=3.10 "
14
+ # requires-python = ">=3.12 "
15
15
# dependencies = [
16
16
# "Quart",
17
17
# "azure-eventgrid",
18
18
# "azure-communication-callautomation==1.4.0b1",
19
- # "semantic-kernel[realtime] ",
19
+ # "semantic-kernel",
20
20
# ]
21
21
# ///
22
-
23
22
import asyncio
24
23
import base64
24
+ import logging
25
25
import os
26
26
import uuid
27
27
from datetime import datetime
28
- from logging import INFO
29
28
from random import randint
30
29
from urllib .parse import urlencode , urlparse , urlunparse
31
30
@@ -96,6 +95,7 @@ async def goodbye(self):
96
95
97
96
async def from_realtime_to_acs (audio : ndarray ):
98
97
"""Function that forwards the audio from the model to the websocket of the ACS client."""
98
+ app .logger .debug ("Audio received from the model, sending to ACS client" )
99
99
await websocket .send (
100
100
json .dumps ({"kind" : "AudioData" , "audioData" : {"data" : base64 .b64encode (audio .tobytes ()).decode ("utf-8" )}})
101
101
)
@@ -129,27 +129,28 @@ async def handle_realtime_messages(client: RealtimeClientBase):
129
129
async for event in client .receive (audio_output_callback = from_realtime_to_acs ):
130
130
match event .service_type :
131
131
case ListenEvents .SESSION_CREATED :
132
- print ("Session Created Message" )
133
- print (f" Session Id: { event .service_event .session .id } " )
132
+ app . logger . info ("Session Created Message" )
133
+ app . logger . debug (f" Session Id: { event .service_event .session .id } " )
134
134
case ListenEvents .ERROR :
135
- print (f" Error: { event .service_event .error } " )
135
+ app . logger . error (f" Error: { event .service_event .error } " )
136
136
case ListenEvents .INPUT_AUDIO_BUFFER_CLEARED :
137
- print ("Input Audio Buffer Cleared Message" )
137
+ app . logger . info ("Input Audio Buffer Cleared Message" )
138
138
case ListenEvents .INPUT_AUDIO_BUFFER_SPEECH_STARTED :
139
- print (f"Voice activity detection started at { event .service_event .audio_start_ms } [ms]" )
139
+ app . logger . debug (f"Voice activity detection started at { event .service_event .audio_start_ms } [ms]" )
140
140
await websocket .send (json .dumps ({"Kind" : "StopAudio" , "AudioData" : None , "StopAudio" : {}}))
141
-
142
141
case ListenEvents .CONVERSATION_ITEM_INPUT_AUDIO_TRANSCRIPTION_COMPLETED :
143
- print (f" User:-- { event .service_event .transcript } " )
142
+ app . logger . info (f" User:-- { event .service_event .transcript } " )
144
143
case ListenEvents .CONVERSATION_ITEM_INPUT_AUDIO_TRANSCRIPTION_FAILED :
145
- print (f" Error: { event .service_event .error } " )
144
+ app . logger . error (f" Error: { event .service_event .error } " )
146
145
case ListenEvents .RESPONSE_DONE :
147
- print ("Response Done Message" )
148
- print (f" Response Id: { event .service_event .response .id } " )
146
+ app . logger . info ("Response Done Message" )
147
+ app . logger . debug (f" Response Id: { event .service_event .response .id } " )
149
148
if event .service_event .response .status_details :
150
- print (f" Status Details: { event .service_event .response .status_details .model_dump_json ()} " )
149
+ app .logger .debug (
150
+ f" Status Details: { event .service_event .response .status_details .model_dump_json ()} "
151
+ )
151
152
case ListenEvents .RESPONSE_AUDIO_TRANSCRIPT_DONE :
152
- print (f" AI:-- { event .service_event .transcript } " )
153
+ app . logger . info (f" AI:-- { event .service_event .transcript } " )
153
154
154
155
155
156
# region: Routes
@@ -159,8 +160,6 @@ async def handle_realtime_messages(client: RealtimeClientBase):
159
160
@app .websocket ("/ws" )
160
161
async def ws ():
161
162
app .logger .info ("Client connected to WebSocket" )
162
-
163
- # create the client, using the audio callback
164
163
client = AzureRealtimeWebsocket ()
165
164
settings = AzureRealtimeExecutionSettings (
166
165
instructions = """You are a chat bot. Your name is Mosscap and
@@ -189,52 +188,51 @@ async def ws():
189
188
190
189
@app .route ("/api/incomingCall" , methods = ["POST" ])
191
190
async def incoming_call_handler () -> Response :
192
- app .logger .info ("incoming event data" )
193
191
for event_dict in await request .json :
194
192
event = EventGridEvent .from_dict (event_dict )
195
- app . logger . info ( "incoming event data --> %s" , event . data )
196
-
197
- if event . event_type == SystemEventNames . EventGridSubscriptionValidationEventName :
198
- app . logger . info ( "Validating subscription" )
199
- validation_code = event . data [ "validationCode" ]
200
- validation_response = { "validationResponse" : validation_code }
201
- return Response ( response = json . dumps ( validation_response ), status = 200 )
202
-
203
- if event . event_type == "Microsoft.Communication.IncomingCall" :
204
- app . logger . info ( "Incoming call received: data=%s" , event .data )
205
- caller_id = (
206
- event .data ["from" ]["phoneNumber" ][ "value " ]
207
- if event . data [ "from" ][ "kind" ] == "phoneNumber"
208
- else event . data [ "from" ][ "rawId" ]
209
- )
210
- app . logger . info ( "incoming call handler caller id: %s" , caller_id )
211
- incoming_call_context = event . data [ "incomingCallContext" ]
212
- guid = uuid . uuid4 ()
213
- query_parameters = urlencode ({ "callerId" : caller_id })
214
- callback_uri = f" { CALLBACK_EVENTS_URI } / { guid } ? { query_parameters } "
215
-
216
- parsed_url = urlparse ( CALLBACK_EVENTS_URI )
217
- websocket_url = urlunparse (( "wss" , parsed_url . netloc , "/ws" , "" , "" , "" ) )
218
-
219
- app . logger . info ( "callback url: %s" , callback_uri )
220
- app . logger . info ( "websocket url: %s" , websocket_url )
221
-
222
- media_streaming_options = MediaStreamingOptions (
223
- transport_url = websocket_url ,
224
- transport_type = MediaStreamingTransportType . WEBSOCKET ,
225
- content_type = MediaStreamingContentType . AUDIO ,
226
- audio_channel_type = MediaStreamingAudioChannelType . MIXED ,
227
- start_media_streaming = True ,
228
- enable_bidirectional = True ,
229
- audio_format = AudioFormat . PCM24_K_MONO ,
230
- )
231
- answer_call_result = await acs_client . answer_call (
232
- incoming_call_context = incoming_call_context ,
233
- operation_context = "incomingCall" ,
234
- callback_url = callback_uri ,
235
- media_streaming = media_streaming_options ,
236
- )
237
- app .logger .info ( "Answered call for connection id : %s" , answer_call_result . call_connection_id )
193
+ match event . event_type :
194
+ case SystemEventNames . EventGridSubscriptionValidationEventName :
195
+ app . logger . info ( "Validating subscription" )
196
+ validation_code = event . data [ "validationCode" ]
197
+ validation_response = { "validationResponse" : validation_code }
198
+ return Response ( response = json . dumps ( validation_response ), status = 200 )
199
+ case SystemEventNames . AcsIncomingCallEventName :
200
+ app . logger . debug ( "Incoming call received: data=%s" , event . data )
201
+ caller_id = (
202
+ event .data [ "from" ][ "phoneNumber" ][ "value" ]
203
+ if event . data [ "from" ][ "kind" ] == "phoneNumber"
204
+ else event .data ["from" ]["rawId " ]
205
+ )
206
+ app . logger . info ( "incoming call handler caller id: %s" , caller_id )
207
+ incoming_call_context = event . data [ "incomingCallContext" ]
208
+ guid = uuid . uuid4 ( )
209
+ query_parameters = urlencode ({ "callerId" : caller_id })
210
+ callback_uri = f" { CALLBACK_EVENTS_URI } / { guid } ? { query_parameters } "
211
+
212
+ parsed_url = urlparse ( CALLBACK_EVENTS_URI )
213
+ websocket_url = urlunparse (( "wss" , parsed_url . netloc , "/ws" , "" , "" , "" ))
214
+
215
+ app . logger . debug ( "callback url: %s" , callback_uri )
216
+ app . logger . debug ( "websocket url: %s" , websocket_url )
217
+
218
+ answer_call_result = await acs_client . answer_call (
219
+ incoming_call_context = incoming_call_context ,
220
+ operation_context = "incomingCall" ,
221
+ callback_url = callback_uri ,
222
+ media_streaming = MediaStreamingOptions (
223
+ transport_url = websocket_url ,
224
+ transport_type = MediaStreamingTransportType . WEBSOCKET ,
225
+ content_type = MediaStreamingContentType . AUDIO ,
226
+ audio_channel_type = MediaStreamingAudioChannelType . MIXED ,
227
+ start_media_streaming = True ,
228
+ enable_bidirectional = True ,
229
+ audio_format = AudioFormat . PCM24_K_MONO ,
230
+ ) ,
231
+ )
232
+ app . logger . info ( f"Answered call for connection id: { answer_call_result . call_connection_id } " )
233
+ case _:
234
+ app . logger . debug ( "Event type not handled: %s" , event . event_type )
235
+ app .logger .debug ( "Event data : %s" , event . data )
238
236
return Response (status = 200 )
239
237
return Response (status = 200 )
240
238
@@ -246,7 +244,7 @@ async def callbacks(contextId):
246
244
global call_connection_id
247
245
event_data = event ["data" ]
248
246
call_connection_id = event_data ["callConnectionId" ]
249
- app .logger .info (
247
+ app .logger .debug (
250
248
f"Received Event:-> { event ['type' ]} , Correlation Id:-> { event_data ['correlationId' ]} , CallConnectionId:-> { call_connection_id } " # noqa: E501
251
249
)
252
250
match event ["type" ]:
@@ -257,23 +255,25 @@ async def callbacks(contextId):
257
255
media_streaming_subscription = call_connection_properties .media_streaming_subscription
258
256
app .logger .info (f"MediaStreamingSubscription:--> { media_streaming_subscription } " )
259
257
app .logger .info (f"Received CallConnected event for connection id: { call_connection_id } " )
260
- app .logger .info ("CORRELATION ID:--> %s" , event_data ["correlationId" ])
261
- app .logger .info ("CALL CONNECTION ID:--> %s" , event_data ["callConnectionId" ])
258
+ app .logger .debug ("CORRELATION ID:--> %s" , event_data ["correlationId" ])
259
+ app .logger .debug ("CALL CONNECTION ID:--> %s" , event_data ["callConnectionId" ])
262
260
case "Microsoft.Communication.MediaStreamingStarted" | "Microsoft.Communication.MediaStreamingStopped" :
263
- app .logger .info (f"Media streaming content type:--> { event_data ['mediaStreamingUpdate' ]['contentType' ]} " )
264
- app .logger .info (
261
+ app .logger .debug (
262
+ f"Media streaming content type:--> { event_data ['mediaStreamingUpdate' ]['contentType' ]} "
263
+ )
264
+ app .logger .debug (
265
265
f"Media streaming status:--> { event_data ['mediaStreamingUpdate' ]['mediaStreamingStatus' ]} "
266
266
)
267
- app .logger .info (
267
+ app .logger .debug (
268
268
f"Media streaming status details:--> { event_data ['mediaStreamingUpdate' ]['mediaStreamingStatusDetails' ]} " # noqa: E501
269
269
)
270
270
case "Microsoft.Communication.MediaStreamingFailed" :
271
- app .logger .info (
271
+ app .logger .warning (
272
272
f"Code:->{ event_data ['resultInformation' ]['code' ]} , Subcode:-> { event_data ['resultInformation' ]['subCode' ]} " # noqa: E501
273
273
)
274
- app .logger .info (f"Message:->{ event_data ['resultInformation' ]['message' ]} " )
274
+ app .logger .warning (f"Message:->{ event_data ['resultInformation' ]['message' ]} " )
275
275
case "Microsoft.Communication.CallDisconnected" :
276
- pass
276
+ app . logger . debug ( f"Call disconnected for connection id: { call_connection_id } " )
277
277
return Response (status = 200 )
278
278
279
279
@@ -286,5 +286,5 @@ def home():
286
286
287
287
288
288
if __name__ == "__main__" :
289
- app .logger .setLevel (INFO )
289
+ app .logger .setLevel (logging . INFO )
290
290
app .run (port = 8080 )
0 commit comments