rairo commited on
Commit
8f77489
·
verified ·
1 Parent(s): 0911208

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +101 -233
main.py CHANGED
@@ -952,12 +952,6 @@ def delete_project(project_id):
952
  # AI phone call ElevenLabs
953
  #-------------------------
954
 
955
- import os
956
- import requests
957
- import time
958
- import logging
959
- from elevenlabs import ElevenLabs
960
-
961
  # Configuration
962
  CREDITS_PER_MIN = 3 # Adjust based on ElevenLabs conversational AI pricing
963
  VOICE_NAME = "Daniel" # British male voice (more reliable than Archer)
@@ -972,279 +966,153 @@ KNOWN_VOICE_IDS = {
972
  "Antoni": "ErXwobaYiN019PkySvjV", # American male (fallback)
973
  }
974
 
 
 
 
 
 
 
 
 
975
  class ConversationalAIHandler:
976
  def __init__(self):
977
- self.client = ElevenLabs(api_key=os.getenv("ELEVENLABS_API_KEY"))
978
- # Fixed: Try the correct endpoint structure
979
- self.base_url = "https://api.elevenlabs.io/v1/convai"
 
980
  self.headers = {
981
- "xi-api-key": os.getenv("ELEVENLABS_API_KEY"),
982
  "Content-Type": "application/json"
983
  }
984
 
985
  def get_british_male_voice_id(self):
986
  """Get British male voice ID"""
 
987
  try:
988
- logger.info(f"[VOICE] Attempting to get voices using SDK method")
989
-
990
- # First try with known voice IDs (fastest approach)
991
  logger.info(f"[VOICE] Using known voice ID for Daniel: {KNOWN_VOICE_IDS['Daniel']}")
992
  return KNOWN_VOICE_IDS["Daniel"]
993
-
994
  except Exception as e:
995
- logger.error(f"[VOICE] Error getting voice: {str(e)}")
996
- logger.error(f"[VOICE] Using fallback Daniel voice ID")
997
- return KNOWN_VOICE_IDS["Daniel"]
998
 
999
  def create_or_get_agent(self, project_id, project_data):
1000
  """Create or retrieve existing agent for the project"""
1001
  try:
1002
  logger.info(f"[AGENT] Starting agent creation/retrieval for project: {project_id}")
1003
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1004
  voice_id = self.get_british_male_voice_id()
1005
- if not voice_id:
1006
- logger.error(f"[AGENT] No voice ID available, using Daniel fallback")
1007
- voice_id = KNOWN_VOICE_IDS["Daniel"]
1008
-
1009
- logger.info(f"[AGENT] Using voice ID: {voice_id}")
1010
 
1011
- # DIY Expert persona context
1012
  diy_expert_prompt = f"""
1013
  You are an experienced DIY expert with years of hands-on experience in Home Appliance Repair, Automotive Maintenance, Gardening & Urban Farming,
1014
- Upcycling & Sustainable Crafts, or DIY Project Creation. You speak in a friendly, knowledgeable British manner and provide
1015
  practical, actionable advice. You're working on this specific project:
1016
- Project: {project_data.get('projectTitle', project_data.get('title', 'DIY Project'))}
1017
  Description: {project_data.get('projectDescription', '')}
1018
  Initial Plan: {project_data.get('initialPlan', '')}
1019
- Provide helpful, step-by-step guidance while being encouraging and safety-conscious.
1020
  Ask clarifying questions when needed and share relevant tips from your experience.
1021
  """
1022
 
1023
- # Fixed: Updated agent structure to match ElevenLabs 2025 API
1024
- agent_data = {
1025
- "name": f"DIY Expert - {project_data.get('projectTitle', project_data.get('title', 'Project'))}",
1026
- "conversation_config": {
1027
- "agent": {
1028
- "prompt": {
1029
- "prompt": diy_expert_prompt
1030
- },
1031
- "first_message": "Hello! I'm your Sozo DIY expert assistant. I'm here to help you with your project. What would you like to work on today?",
1032
- "language": "en"
1033
- },
1034
- "asr": {
1035
- "provider": "deepgram",
1036
- "user_input_audio_format": "pcm_16000"
1037
- },
1038
- "tts": {
1039
- "voice_id": voice_id
1040
- },
1041
- "llm": {
1042
- "provider": "openai",
1043
- "model": "gpt-4"
1044
- }
1045
- },
1046
- "platform_settings": {
1047
- "webhook_url": f"{os.getenv('BASE_URL', 'https://rairo-neofix-api.hf.space')}/api/webhook/agent/{project_id}"
1048
- }
1049
  }
1050
 
1051
- logger.info(f"[AGENT] Agent data prepared, checking for existing agent")
 
 
1052
 
1053
- # Check if agent already exists for this project
1054
- existing_agent_id = db_ref.child(f'projects/{project_id}/agent_id').get()
1055
-
1056
- if existing_agent_id:
1057
- logger.info(f"[AGENT] Found existing agent ID: {existing_agent_id}")
1058
- # Verify agent still exists
1059
- try:
1060
- response = requests.get(
1061
- f"{self.base_url}/agents/{existing_agent_id}",
1062
- headers=self.headers,
1063
- timeout=10
1064
- )
1065
- if response.status_code == 200:
1066
- logger.info(f"[AGENT] Existing agent verified and active")
1067
- return existing_agent_id
1068
- else:
1069
- logger.warning(f"[AGENT] Existing agent not found (status {response.status_code}), creating new one")
1070
- except Exception as e:
1071
- logger.warning(f"[AGENT] Error verifying existing agent: {str(e)}, creating new one")
1072
 
1073
- # Create new agent - Fixed: Try correct endpoints in order
1074
- logger.info(f"[AGENT] Creating new agent")
1075
-
1076
- # Endpoint 1: Try the newer conversational-ai endpoint first
1077
- try:
1078
- endpoint = "https://api.elevenlabs.io/v1/conversational-ai/agents"
1079
- logger.info(f"[AGENT] Trying primary endpoint: {endpoint}")
1080
-
1081
- response = requests.post(
1082
- endpoint,
1083
- headers=self.headers,
1084
- json=agent_data,
1085
- timeout=30
1086
- )
1087
-
1088
- logger.info(f"[AGENT] Primary endpoint response status: {response.status_code}")
1089
-
1090
- if response.status_code == 201:
1091
- agent_info = response.json()
1092
- agent_id = agent_info.get("agent_id")
1093
- logger.info(f"[AGENT] New agent created successfully with primary endpoint: {agent_id}")
1094
-
1095
- # Store agent ID in database
1096
- db_ref.child(f'projects/{project_id}').update({
1097
- 'agent_id': agent_id,
1098
- 'agent_created_at': int(time.time())
1099
- })
1100
- logger.info(f"[AGENT] Agent ID stored in database")
1101
-
1102
- return agent_id
1103
-
1104
- except Exception as e:
1105
- logger.warning(f"[AGENT] Primary endpoint failed: {str(e)}")
1106
-
1107
- # Endpoint 2: Try the original convai endpoint
1108
- try:
1109
- endpoint = f"{self.base_url}/agents"
1110
- logger.info(f"[AGENT] Trying fallback endpoint: {endpoint}")
1111
-
1112
- response = requests.post(
1113
- endpoint,
1114
- headers=self.headers,
1115
- json=agent_data,
1116
- timeout=30
1117
- )
1118
-
1119
- logger.info(f"[AGENT] Fallback endpoint response status: {response.status_code}")
1120
- logger.info(f"[AGENT] Response headers: {dict(response.headers)}")
1121
-
1122
- if response.status_code == 201:
1123
- agent_info = response.json()
1124
- agent_id = agent_info.get("agent_id")
1125
- logger.info(f"[AGENT] New agent created successfully with fallback endpoint: {agent_id}")
1126
-
1127
- # Store agent ID in database
1128
- db_ref.child(f'projects/{project_id}').update({
1129
- 'agent_id': agent_id,
1130
- 'agent_created_at': int(time.time())
1131
- })
1132
- logger.info(f"[AGENT] Agent ID stored in database")
1133
-
1134
- return agent_id
1135
- else:
1136
- logger.error(f"[AGENT] Failed to create agent: Status {response.status_code}")
1137
- logger.error(f"[AGENT] Response text: {response.text}")
1138
-
1139
- # Try to parse error response
1140
- try:
1141
- error_data = response.json()
1142
- logger.error(f"[AGENT] Error details: {error_data}")
1143
- except:
1144
- logger.error(f"[AGENT] Could not parse error response as JSON")
1145
-
1146
- raise Exception(f"Failed to create agent: HTTP {response.status_code} - {response.text}")
1147
-
1148
- except Exception as e:
1149
- logger.error(f"[AGENT] All endpoints failed: {str(e)}")
1150
- raise
1151
-
1152
  except Exception as e:
1153
- logger.error(f"[AGENT] Error creating/getting agent: {str(e)}")
1154
- logger.error(f"[AGENT] Exception type: {type(e).__name__}")
1155
  raise
1156
 
1157
  def start_conversation(self, agent_id):
1158
  """Start a conversation with the agent"""
 
 
 
1159
  try:
1160
- logger.info(f"[CONVERSATION] Starting conversation with agent: {agent_id}")
1161
-
1162
- # Fixed: Try the correct conversation endpoints
1163
- endpoints = [
1164
- f"https://api.elevenlabs.io/v1/conversational-ai/agents/{agent_id}/conversations",
1165
- f"{self.base_url}/agents/{agent_id}/conversations"
1166
- ]
1167
-
1168
- for endpoint in endpoints:
1169
- try:
1170
- logger.info(f"[CONVERSATION] Trying endpoint: {endpoint}")
1171
-
1172
- response = requests.post(
1173
- endpoint,
1174
- headers=self.headers,
1175
- json={},
1176
- timeout=30
1177
- )
1178
-
1179
- logger.info(f"[CONVERSATION] Response status: {response.status_code}")
1180
-
1181
- if response.status_code == 201:
1182
- conversation_info = response.json()
1183
- conversation_id = conversation_info.get("conversation_id")
1184
- logger.info(f"[CONVERSATION] Conversation started successfully: {conversation_id}")
1185
- return conversation_id
1186
- else:
1187
- logger.warning(f"[CONVERSATION] Endpoint failed with status {response.status_code}: {response.text}")
1188
- if endpoint == endpoints[-1]: # Last endpoint
1189
- raise Exception(f"Failed to start conversation: {response.text}")
1190
-
1191
- except requests.exceptions.RequestException as req_e:
1192
- logger.warning(f"[CONVERSATION] Request failed for {endpoint}: {str(req_e)}")
1193
- if endpoint == endpoints[-1]: # Last endpoint
1194
- raise Exception(f"All conversation endpoints failed: {str(req_e)}")
1195
-
1196
  except Exception as e:
1197
- logger.error(f"[CONVERSATION] Error starting conversation: {str(e)}")
1198
- logger.error(f"[CONVERSATION] Exception type: {type(e).__name__}")
1199
  raise
1200
 
1201
  def send_message(self, agent_id, conversation_id, message):
1202
  """Send a message to the conversation"""
 
 
 
 
 
 
 
 
 
 
1203
  try:
1204
- logger.info(f"[MESSAGE] Sending message to agent: {agent_id}, conversation: {conversation_id}")
1205
- logger.info(f"[MESSAGE] Message: {message[:100]}{'...' if len(message) > 100 else ''}")
1206
-
1207
- # Fixed: Try the correct message endpoints
1208
- endpoints = [
1209
- f"https://api.elevenlabs.io/v1/conversational-ai/agents/{agent_id}/conversations/{conversation_id}/messages",
1210
- f"{self.base_url}/agents/{agent_id}/conversations/{conversation_id}/messages"
1211
- ]
1212
-
1213
- for endpoint in endpoints:
1214
- try:
1215
- logger.info(f"[MESSAGE] Trying endpoint: {endpoint}")
1216
-
1217
- response = requests.post(
1218
- endpoint,
1219
- headers=self.headers,
1220
- json={
1221
- "message": message,
1222
- "message_type": "text"
1223
- },
1224
- timeout=30
1225
- )
1226
-
1227
- logger.info(f"[MESSAGE] Response status: {response.status_code}")
1228
-
1229
- if response.status_code == 200:
1230
- message_response = response.json()
1231
- logger.info(f"[MESSAGE] Message sent successfully")
1232
- return message_response
1233
- else:
1234
- logger.warning(f"[MESSAGE] Endpoint failed with status {response.status_code}: {response.text}")
1235
- if endpoint == endpoints[-1]: # Last endpoint
1236
- raise Exception(f"Failed to send message: {response.text}")
1237
-
1238
- except requests.exceptions.RequestException as req_e:
1239
- logger.warning(f"[MESSAGE] Request failed for {endpoint}: {str(req_e)}")
1240
- if endpoint == endpoints[-1]: # Last endpoint
1241
- raise Exception(f"All message endpoints failed: {str(req_e)}")
1242
-
1243
  except Exception as e:
1244
- logger.error(f"[MESSAGE] Error sending message: {str(e)}")
1245
- logger.error(f"[MESSAGE] Exception type: {type(e).__name__}")
1246
  raise
1247
 
 
1248
  def calculate_cost(duration_seconds):
1249
  """Calculate cost based on conversation duration"""
1250
  minutes = (duration_seconds + 59) // 60
 
952
  # AI phone call ElevenLabs
953
  #-------------------------
954
 
 
 
 
 
 
 
955
  # Configuration
956
  CREDITS_PER_MIN = 3 # Adjust based on ElevenLabs conversational AI pricing
957
  VOICE_NAME = "Daniel" # British male voice (more reliable than Archer)
 
966
  "Antoni": "ErXwobaYiN019PkySvjV", # American male (fallback)
967
  }
968
 
969
+ import os
970
+ import time
971
+ import requests
972
+ import logging
973
+
974
+ # Assumes 'db_ref', 'logger', and 'KNOWN_VOICE_IDS' are initialized elsewhere in your application.
975
+ # from elevenlabs import ElevenLabs # This is no longer needed for the handler's logic.
976
+
977
  class ConversationalAIHandler:
978
  def __init__(self):
979
+ # CORRECTED: The base_url should be the root of the v1 API.
980
+ # Specific paths like '/agents' or '/conversations' will be appended later.
981
+ self.base_url = "https://api.elevenlabs.io/v1"
982
+ self.api_key = os.getenv("ELEVENLABS_API_KEY")
983
  self.headers = {
984
+ "xi-api-key": self.api_key,
985
  "Content-Type": "application/json"
986
  }
987
 
988
  def get_british_male_voice_id(self):
989
  """Get British male voice ID"""
990
+ # This method's logic is sound and remains unchanged.
991
  try:
 
 
 
992
  logger.info(f"[VOICE] Using known voice ID for Daniel: {KNOWN_VOICE_IDS['Daniel']}")
993
  return KNOWN_VOICE_IDS["Daniel"]
 
994
  except Exception as e:
995
+ logger.error(f"[VOICE] Error getting voice ID: {str(e)}. Using fallback.")
996
+ return KNOWN_VOICE_IDS.get("Daniel", "onwK4e9ZLuTAKqWW03F9") # Hardcoded fallback
 
997
 
998
  def create_or_get_agent(self, project_id, project_data):
999
  """Create or retrieve existing agent for the project"""
1000
  try:
1001
  logger.info(f"[AGENT] Starting agent creation/retrieval for project: {project_id}")
1002
+
1003
+ # Step 1: Check for and verify an existing agent.
1004
+ existing_agent_id = db_ref.child(f'projects/{project_id}/agent_id').get()
1005
+ if existing_agent_id:
1006
+ logger.info(f"[AGENT] Found existing agent ID: {existing_agent_id}. Verifying...")
1007
+ # CORRECTED: Use the correct verification endpoint.
1008
+ verify_url = f"{self.base_url}/agents/{existing_agent_id}"
1009
+ try:
1010
+ response = requests.get(verify_url, headers=self.headers, timeout=15)
1011
+ if response.status_code == 200:
1012
+ logger.info(f"[AGENT] Existing agent {existing_agent_id} verified and active.")
1013
+ return existing_agent_id
1014
+ else:
1015
+ logger.warning(f"[AGENT] Verification failed for agent {existing_agent_id} (Status: {response.status_code}). Creating a new one.")
1016
+ except Exception as e:
1017
+ logger.warning(f"[AGENT] Error verifying existing agent: {str(e)}. Creating a new one.")
1018
+
1019
+ # Step 2: If no valid agent exists, create a new one.
1020
+ logger.info(f"[AGENT] Creating new agent.")
1021
  voice_id = self.get_british_male_voice_id()
 
 
 
 
 
1022
 
 
1023
  diy_expert_prompt = f"""
1024
  You are an experienced DIY expert with years of hands-on experience in Home Appliance Repair, Automotive Maintenance, Gardening & Urban Farming,
1025
+ Upcycling & Sustainable Crafts, or DIY Project Creation. You speak in a friendly, knowledgeable British manner and provide
1026
  practical, actionable advice. You're working on this specific project:
1027
+ Project: {project_data.get('projectTitle', 'DIY Project')}
1028
  Description: {project_data.get('projectDescription', '')}
1029
  Initial Plan: {project_data.get('initialPlan', '')}
1030
+ Provide helpful, step-by-step guidance while being encouraging and safety-conscious.
1031
  Ask clarifying questions when needed and share relevant tips from your experience.
1032
  """
1033
 
1034
+ # CORRECTED: The payload for agent creation is a flatter structure.
1035
+ # The complex 'conversation_config' is not used for the standard /agents endpoint.
1036
+ agent_payload = {
1037
+ "name": f"DIY Expert - {project_data.get('projectTitle', project_id)}",
1038
+ "prompt": diy_expert_prompt,
1039
+ "voice_id": voice_id,
1040
+ "initial_message": "Hello! I'm your Sozo DIY expert assistant. I'm here to help you with your project. What would you like to work on today?",
1041
+ "webhook_url": f"{os.getenv('BASE_URL', 'https://rairo-neofix-api.hf.space')}/api/webhook/agent/{project_id}",
1042
+ "model": "eleven_turbo_v2" # A default model is recommended
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1043
  }
1044
 
1045
+ # CORRECTED: Use the single, correct endpoint for agent creation.
1046
+ creation_url = f"{self.base_url}/agents"
1047
+ logger.info(f"[AGENT] Posting to agent creation endpoint: {creation_url}")
1048
 
1049
+ response = requests.post(creation_url, headers=self.headers, json=agent_payload, timeout=30)
1050
+
1051
+ if response.status_code == 200: # A successful creation returns status 200
1052
+ agent_info = response.json()
1053
+ agent_id = agent_info.get("agent_id")
1054
+ logger.info(f"[AGENT] New agent created successfully: {agent_id}")
1055
+
1056
+ db_ref.child(f'projects/{project_id}').update({
1057
+ 'agent_id': agent_id,
1058
+ 'agent_created_at': int(time.time())
1059
+ })
1060
+ logger.info(f"[AGENT] Agent ID stored in database.")
1061
+ return agent_id
1062
+ else:
1063
+ # This will now give a much more informative error if the payload is wrong.
1064
+ logger.error(f"[AGENT] Failed to create agent. Status: {response.status_code}, Response: {response.text}")
1065
+ raise Exception(f"Failed to create agent: HTTP {response.status_code} - {response.text}")
 
 
1066
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1067
  except Exception as e:
1068
+ logger.error(f"[AGENT] An exception occurred in create_or_get_agent: {e}")
 
1069
  raise
1070
 
1071
  def start_conversation(self, agent_id):
1072
  """Start a conversation with the agent"""
1073
+ logger.info(f"[CONVERSATION] Starting conversation with agent: {agent_id}")
1074
+ # CORRECTED: Use the single, correct endpoint for starting a conversation with an agent.
1075
+ conversation_url = f"{self.base_url}/agents/{agent_id}/conversations"
1076
  try:
1077
+ response = requests.post(conversation_url, headers=self.headers, json={}, timeout=30)
1078
+ if response.status_code == 200: # Successful start returns 200
1079
+ conversation_info = response.json()
1080
+ conversation_id = conversation_info.get("conversation_id")
1081
+ logger.info(f"[CONVERSATION] Conversation started successfully: {conversation_id}")
1082
+ return conversation_id
1083
+ else:
1084
+ logger.error(f"[CONVERSATION] Failed to start conversation. Status: {response.status_code}, Response: {response.text}")
1085
+ raise Exception(f"Failed to start conversation: {response.text}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1086
  except Exception as e:
1087
+ logger.error(f"[CONVERSATION] An exception occurred in start_conversation: {e}")
 
1088
  raise
1089
 
1090
  def send_message(self, agent_id, conversation_id, message):
1091
  """Send a message to the conversation"""
1092
+ # The 'agent_id' is not required for this endpoint, but we'll keep the signature for compatibility with your calling code.
1093
+ logger.info(f"[MESSAGE] Sending message to conversation: {conversation_id}")
1094
+ # CORRECTED: Use the single, correct endpoint for sending a message.
1095
+ message_url = f"{self.base_url}/conversations/{conversation_id}/messages"
1096
+
1097
+ # CORRECTED: The payload for sending a message is simpler.
1098
+ message_payload = {
1099
+ "text": message
1100
+ }
1101
+
1102
  try:
1103
+ response = requests.post(message_url, headers=self.headers, json=message_payload, timeout=30)
1104
+ if response.status_code == 200:
1105
+ logger.info(f"[MESSAGE] Message sent successfully to {conversation_id}.")
1106
+ # The response is streaming audio, but we can return the successful JSON confirmation.
1107
+ return response.json()
1108
+ else:
1109
+ logger.error(f"[MESSAGE] Failed to send message. Status: {response.status_code}, Response: {response.text}")
1110
+ raise Exception(f"Failed to send message: {response.text}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1111
  except Exception as e:
1112
+ logger.error(f"[MESSAGE] An exception occurred in send_message: {e}")
 
1113
  raise
1114
 
1115
+
1116
  def calculate_cost(duration_seconds):
1117
  """Calculate cost based on conversation duration"""
1118
  minutes = (duration_seconds + 59) // 60