-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
216 lines (176 loc) · 7.15 KB
/
Copy pathmain.py
File metadata and controls
216 lines (176 loc) · 7.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
import os
from dotenv import load_dotenv
from fastapi import FastAPI, WebSocket
from elevenlabs.client import ElevenLabs
from elevenlabs.conversational_ai.conversation import Conversation, AudioInterface
import asyncio
import base64
import json
import logging
from fastapi.responses import Response
# Set up detailed logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# Load environment variables
load_dotenv()
AGENT_ID = os.getenv("ELEVENLABS_AGENT_ID")
API_KEY = os.getenv("ELEVENLABS_API_KEY")
app = FastAPI()
class TwilioAudioInterface(AudioInterface):
def __init__(self, websocket: WebSocket):
self.websocket = websocket
self.stream_sid = None
self._is_running = False
self._input_callback = None
self.logger = logging.getLogger(__name__)
def start(self, input_callback=None):
"""Start the audio interface."""
self._is_running = True
self._input_callback = input_callback
self.logger.info("Audio interface started with callback")
def stop(self):
"""Stop the audio interface."""
self._is_running = False
self.logger.info("Audio interface stopped")
async def output(self, audio_data: bytes):
"""Output audio data to Twilio."""
try:
if not self.stream_sid:
self.logger.error("No stream_sid available")
return
if not self._is_running:
self.logger.error("Audio interface not running")
return
if not audio_data:
self.logger.error("No audio data received")
return
self.logger.info(f"Sending audio chunk of size {len(audio_data)} bytes to Twilio")
# Create the media message
message = {
"event": "media",
"streamSid": self.stream_sid,
"media": {
"payload": base64.b64encode(audio_data).decode('utf-8')
}
}
# Send audio data to Twilio
await self.websocket.send_text(json.dumps(message))
self.logger.info("Audio chunk sent successfully to Twilio")
except Exception as e:
self.logger.error(f"Error in output: {str(e)}", exc_info=True)
raise
def interrupt(self):
"""Interrupt current audio playback."""
self._is_running = False
self.logger.info("Audio playback interrupted")
async def process_incoming_audio(self, audio_data: bytes):
"""Process incoming audio from Twilio."""
try:
if not self._input_callback:
self.logger.error("No input callback registered")
return
if not self._is_running:
self.logger.error("Audio interface not running")
return
if not audio_data:
self.logger.error("No audio data received")
return
self.logger.debug(f"Processing incoming audio chunk of size {len(audio_data)} bytes")
self._input_callback(audio_data)
self.logger.debug("Audio chunk processed successfully")
except Exception as e:
self.logger.error(f"Error processing incoming audio: {str(e)}", exc_info=True)
raise
@app.post("/incoming-call-eleven")
async def incoming_call():
try:
host = os.getenv('HOST')
if not host:
logger.error("HOST environment variable not set")
raise ValueError("HOST not configured")
twiml = f"""<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Connect>
<Stream url="wss://{host}/media-stream" />
</Connect>
</Response>"""
logger.info(f"Returning TwiML: {twiml}")
return Response(
content=twiml,
media_type="application/xml"
)
except Exception as e:
logger.error(f"Error in incoming call handler: {str(e)}", exc_info=True)
raise
@app.websocket("/media-stream")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
logger.info("Twilio WebSocket connected")
audio_interface = None
conversation = None
try:
# Initialize audio interface
audio_interface = TwilioAudioInterface(websocket)
client = ElevenLabs(api_key=API_KEY)
def on_agent_response(response):
logger.info(f"Agent response: {response}")
def on_transcript(transcript):
logger.info(f"User transcript: {transcript}")
def on_audio_event(audio_data):
logger.info(f"Received audio data of size: {len(audio_data)} bytes")
conversation = Conversation(
client=client,
agent_id=AGENT_ID,
requires_auth=bool(API_KEY),
audio_interface=audio_interface,
callback_agent_response=on_agent_response,
callback_user_transcript=on_transcript,
callback_latency_measurement=lambda latency: logger.info(f"Latency: {latency}ms")
)
# Start conversation
conversation.start_session()
logger.info("Started ElevenLabs conversation session")
# Main WebSocket message handling loop
while True:
try:
message = await websocket.receive_text()
data = json.loads(message)
logger.info(f"Received WebSocket message type: {data.get('event')}")
if data["event"] == "start":
audio_interface.stream_sid = data["start"]["streamSid"]
logger.info(f"Stream started with ID: {audio_interface.stream_sid}")
elif data["event"] == "media":
audio_data = base64.b64decode(data["media"]["payload"])
await audio_interface.process_incoming_audio(audio_data)
elif data["event"] == "stop":
logger.info("Stream stopped")
break
elif data["event"] == "mark":
logger.debug(f"Received mark event: {data}")
except json.JSONDecodeError as e:
logger.error(f"Failed to decode WebSocket message: {e}")
continue
except Exception as e:
logger.error(f"Error processing WebSocket message: {e}", exc_info=True)
break
except Exception as e:
logger.error(f"Error in WebSocket connection: {e}", exc_info=True)
finally:
# Cleanup
if audio_interface:
audio_interface.stop()
logger.info("Audio interface stopped")
if conversation:
conversation.end_session()
logger.info("Ended ElevenLabs conversation session")
await websocket.close()
logger.info("WebSocket connection closed")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)