malek-messaoudii commited on
Commit
c059296
·
1 Parent(s): 1d46e48

Update chat endpoints

Browse files
Files changed (2) hide show
  1. routes/voice_chat_routes.py +128 -34
  2. services/chat_service.py +87 -49
routes/voice_chat_routes.py CHANGED
@@ -1,15 +1,17 @@
1
- from fastapi import APIRouter, UploadFile, File, HTTPException
2
- from fastapi.responses import StreamingResponse
3
  from pydantic import BaseModel
4
  from typing import Optional
5
  import tempfile
6
  import os
7
  from pathlib import Path
 
8
  import io
9
 
10
  from services.stt_service import speech_to_text
11
  from services.tts_service import text_to_speech
12
- from services.chat_service import generate_chat_response # Votre service de chatbot existant
 
13
 
14
  router = APIRouter(prefix="/voice-chat", tags=["Voice Chat"])
15
 
@@ -22,10 +24,13 @@ class VoiceChatResponse(BaseModel):
22
  audio_url: Optional[str] = None
23
  conversation_id: str
24
 
 
 
 
25
  @router.post("/voice", response_model=VoiceChatResponse)
26
  async def voice_chat_endpoint(
27
  file: UploadFile = File(...),
28
- conversation_id: Optional[str] = None
29
  ):
30
  """
31
  Point d'entrée unique pour le chat vocal:
@@ -34,61 +39,88 @@ async def voice_chat_endpoint(
34
  3. TTS: Réponse → Audio
35
  """
36
  # 1. Vérifier le fichier audio
37
- if not file.content_type.startswith('audio/'):
38
- raise HTTPException(400, "Le fichier doit être un fichier audio")
 
 
 
39
 
40
- # Créer un ID de conversation si non fourni
41
  if not conversation_id:
42
- import uuid
43
  conversation_id = str(uuid.uuid4())
44
 
45
- # 2. Sauvegarder temporairement l'audio
46
  with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as temp_file:
47
  temp_path = temp_file.name
48
  content = await file.read()
 
 
 
 
 
 
49
  temp_file.write(content)
50
 
51
  try:
52
- # 3. STT: Audio → Texte
53
  user_text = speech_to_text(temp_path)
54
 
55
  if not user_text or user_text.strip() == "":
56
- raise HTTPException(400, "Aucune parole détectée dans l'audio")
 
 
 
57
 
58
- # 4. Générer la réponse du chatbot (utilise votre logique existante)
 
59
  chatbot_response = generate_chat_response(
60
  user_input=user_text,
61
  conversation_id=conversation_id
62
  )
63
 
64
- # 5. TTS: Réponse texte → Audio
65
  audio_path = text_to_speech(
66
  text=chatbot_response,
67
- voice="Aaliyah-PlayAI", # ou une voix configurable
68
  fmt="wav"
69
  )
70
 
71
- # 6. Lire le fichier audio
72
  with open(audio_path, "rb") as audio_file:
73
  audio_data = audio_file.read()
74
 
 
 
 
75
  # Nettoyer les fichiers temporaires
76
  os.unlink(temp_path)
77
  if Path(audio_path).exists():
78
  os.unlink(audio_path)
79
 
80
- # 7. Retourner réponse
81
  return VoiceChatResponse(
82
  text_response=chatbot_response,
83
- audio_url=f"/voice-chat/audio/{conversation_id}", # ou stream direct
84
  conversation_id=conversation_id
85
  )
86
 
 
 
 
87
  except Exception as e:
88
  # Nettoyer en cas d'erreur
89
  if os.path.exists(temp_path):
90
  os.unlink(temp_path)
91
- raise HTTPException(500, f"Erreur lors du traitement vocal: {str(e)}")
 
 
 
 
 
 
 
 
 
92
 
93
  @router.post("/text", response_model=VoiceChatResponse)
94
  async def text_chat_endpoint(request: TextChatRequest):
@@ -96,50 +128,112 @@ async def text_chat_endpoint(request: TextChatRequest):
96
  Alternative: Chat texte avec réponse audio
97
  Pour les utilisateurs qui préfèrent taper mais écouter la réponse
98
  """
99
- # Créer un ID de conversation si non fourni
100
- if not request.conversation_id:
101
- import uuid
102
- conversation_id = str(uuid.uuid4())
103
- else:
104
- conversation_id = request.conversation_id
105
-
106
  try:
107
- # 1. Générer la réponse du chatbot
 
 
 
 
 
 
 
 
 
 
108
  chatbot_response = generate_chat_response(
109
  user_input=request.text,
110
  conversation_id=conversation_id
111
  )
112
 
113
- # 2. TTS: Réponse texte → Audio
114
  audio_path = text_to_speech(
115
  text=chatbot_response,
116
  voice="Aaliyah-PlayAI",
117
  fmt="wav"
118
  )
119
 
 
 
 
 
 
 
 
 
 
 
 
120
  return VoiceChatResponse(
121
  text_response=chatbot_response,
122
  audio_url=f"/voice-chat/audio/{conversation_id}",
123
  conversation_id=conversation_id
124
  )
125
 
 
 
126
  except Exception as e:
127
- raise HTTPException(500, f"Erreur lors du chat: {str(e)}")
 
 
 
 
 
 
 
128
 
129
  @router.get("/audio/{conversation_id}")
130
  async def get_audio_stream(conversation_id: str):
131
  """
132
  Stream l'audio de la dernière réponse
133
- (Cache ou base de données dans une version réelle)
134
  """
135
- # Dans une vraie implémentation, vous stockeriez l'audio
136
- # temporairement ou en cache
137
- return {"message": "Endpoint pour récupérer l'audio"}
 
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
  @router.get("/conversation/{conversation_id}")
140
  async def get_conversation_history(conversation_id: str):
141
  """
142
  Récupérer l'historique d'une conversation
143
  """
144
- # Implémentez selon votre système de stockage
145
- return {"conversation_id": conversation_id, "history": []}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, UploadFile, File, HTTPException, Query
2
+ from fastapi.responses import FileResponse, StreamingResponse
3
  from pydantic import BaseModel
4
  from typing import Optional
5
  import tempfile
6
  import os
7
  from pathlib import Path
8
+ import uuid
9
  import io
10
 
11
  from services.stt_service import speech_to_text
12
  from services.tts_service import text_to_speech
13
+ # Assurez-vous que ce service existe et fonctionne correctement
14
+ from services.chat_service import generate_chat_response
15
 
16
  router = APIRouter(prefix="/voice-chat", tags=["Voice Chat"])
17
 
 
24
  audio_url: Optional[str] = None
25
  conversation_id: str
26
 
27
+ # Stockage temporaire pour l'audio généré
28
+ audio_cache = {}
29
+
30
  @router.post("/voice", response_model=VoiceChatResponse)
31
  async def voice_chat_endpoint(
32
  file: UploadFile = File(...),
33
+ conversation_id: Optional[str] = Query(None)
34
  ):
35
  """
36
  Point d'entrée unique pour le chat vocal:
 
39
  3. TTS: Réponse → Audio
40
  """
41
  # 1. Vérifier le fichier audio
42
+ if not file.content_type or not file.content_type.startswith('audio/'):
43
+ raise HTTPException(
44
+ status_code=400,
45
+ detail=f"Le fichier doit être un fichier audio. Type reçu: {file.content_type}"
46
+ )
47
 
48
+ # 2. Créer un ID de conversation si non fourni
49
  if not conversation_id:
 
50
  conversation_id = str(uuid.uuid4())
51
 
52
+ # 3. Sauvegarder temporairement l'audio
53
  with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as temp_file:
54
  temp_path = temp_file.name
55
  content = await file.read()
56
+
57
+ # Vérifier que le fichier n'est pas vide
58
+ if len(content) == 0:
59
+ os.unlink(temp_path)
60
+ raise HTTPException(status_code=400, detail="Le fichier audio est vide")
61
+
62
  temp_file.write(content)
63
 
64
  try:
65
+ # 4. STT: Audio → Texte
66
  user_text = speech_to_text(temp_path)
67
 
68
  if not user_text or user_text.strip() == "":
69
+ raise HTTPException(
70
+ status_code=400,
71
+ detail="Aucune parole détectée dans l'audio. Essayez de parler plus clairement."
72
+ )
73
 
74
+ # 5. Générer la réponse du chatbot
75
+ # Assurez-vous que generate_chat_response fonctionne correctement
76
  chatbot_response = generate_chat_response(
77
  user_input=user_text,
78
  conversation_id=conversation_id
79
  )
80
 
81
+ # 6. TTS: Réponse texte → Audio
82
  audio_path = text_to_speech(
83
  text=chatbot_response,
84
+ voice="Aaliyah-PlayAI",
85
  fmt="wav"
86
  )
87
 
88
+ # 7. Lire le fichier audio
89
  with open(audio_path, "rb") as audio_file:
90
  audio_data = audio_file.read()
91
 
92
+ # Stocker l'audio dans le cache
93
+ audio_cache[conversation_id] = audio_data
94
+
95
  # Nettoyer les fichiers temporaires
96
  os.unlink(temp_path)
97
  if Path(audio_path).exists():
98
  os.unlink(audio_path)
99
 
100
+ # 8. Retourner réponse avec URL pour récupérer l'audio
101
  return VoiceChatResponse(
102
  text_response=chatbot_response,
103
+ audio_url=f"/voice-chat/audio/{conversation_id}",
104
  conversation_id=conversation_id
105
  )
106
 
107
+ except HTTPException:
108
+ # Relancer les HTTPException
109
+ raise
110
  except Exception as e:
111
  # Nettoyer en cas d'erreur
112
  if os.path.exists(temp_path):
113
  os.unlink(temp_path)
114
+
115
+ # Log l'erreur complète pour le débogage
116
+ import traceback
117
+ error_details = traceback.format_exc()
118
+ print(f"Erreur dans voice_chat_endpoint: {error_details}")
119
+
120
+ raise HTTPException(
121
+ status_code=500,
122
+ detail=f"Erreur lors du traitement vocal: {str(e)}"
123
+ )
124
 
125
  @router.post("/text", response_model=VoiceChatResponse)
126
  async def text_chat_endpoint(request: TextChatRequest):
 
128
  Alternative: Chat texte avec réponse audio
129
  Pour les utilisateurs qui préfèrent taper mais écouter la réponse
130
  """
 
 
 
 
 
 
 
131
  try:
132
+ # 1. Créer un ID de conversation si non fourni
133
+ if not request.conversation_id:
134
+ conversation_id = str(uuid.uuid4())
135
+ else:
136
+ conversation_id = request.conversation_id
137
+
138
+ # 2. Vérifier que le texte n'est pas vide
139
+ if not request.text or request.text.strip() == "":
140
+ raise HTTPException(status_code=400, detail="Le texte ne peut pas être vide")
141
+
142
+ # 3. Générer la réponse du chatbot
143
  chatbot_response = generate_chat_response(
144
  user_input=request.text,
145
  conversation_id=conversation_id
146
  )
147
 
148
+ # 4. TTS: Réponse texte → Audio
149
  audio_path = text_to_speech(
150
  text=chatbot_response,
151
  voice="Aaliyah-PlayAI",
152
  fmt="wav"
153
  )
154
 
155
+ # 5. Lire et stocker l'audio
156
+ with open(audio_path, "rb") as audio_file:
157
+ audio_data = audio_file.read()
158
+
159
+ audio_cache[conversation_id] = audio_data
160
+
161
+ # 6. Nettoyer le fichier temporaire
162
+ if Path(audio_path).exists():
163
+ os.unlink(audio_path)
164
+
165
+ # 7. Retourner réponse
166
  return VoiceChatResponse(
167
  text_response=chatbot_response,
168
  audio_url=f"/voice-chat/audio/{conversation_id}",
169
  conversation_id=conversation_id
170
  )
171
 
172
+ except HTTPException:
173
+ raise
174
  except Exception as e:
175
+ import traceback
176
+ error_details = traceback.format_exc()
177
+ print(f"Erreur dans text_chat_endpoint: {error_details}")
178
+
179
+ raise HTTPException(
180
+ status_code=500,
181
+ detail=f"Erreur lors du chat: {str(e)}"
182
+ )
183
 
184
  @router.get("/audio/{conversation_id}")
185
  async def get_audio_stream(conversation_id: str):
186
  """
187
  Stream l'audio de la dernière réponse
 
188
  """
189
+ if conversation_id not in audio_cache:
190
+ raise HTTPException(
191
+ status_code=404,
192
+ detail=f"Aucun audio trouvé pour la conversation {conversation_id}"
193
+ )
194
+
195
+ audio_data = audio_cache[conversation_id]
196
+
197
+ # Retourner l'audio en streaming
198
+ return StreamingResponse(
199
+ io.BytesIO(audio_data),
200
+ media_type="audio/wav",
201
+ headers={
202
+ "Content-Disposition": f"attachment; filename=response_{conversation_id}.wav"
203
+ }
204
+ )
205
 
206
  @router.get("/conversation/{conversation_id}")
207
  async def get_conversation_history(conversation_id: str):
208
  """
209
  Récupérer l'historique d'une conversation
210
  """
211
+ try:
212
+ from services.chat_service import get_conversation_history
213
+ history = get_conversation_history(conversation_id)
214
+
215
+ return {
216
+ "conversation_id": conversation_id,
217
+ "history": history,
218
+ "message_count": len(history)
219
+ }
220
+ except Exception as e:
221
+ raise HTTPException(
222
+ status_code=500,
223
+ detail=f"Erreur lors de la récupération de l'historique: {str(e)}"
224
+ )
225
+
226
+ # Endpoint pour nettoyer le cache audio (optionnel)
227
+ @router.delete("/audio/{conversation_id}")
228
+ async def clear_audio_cache(conversation_id: str):
229
+ """
230
+ Supprimer l'audio d'une conversation du cache
231
+ """
232
+ if conversation_id in audio_cache:
233
+ del audio_cache[conversation_id]
234
+ return {"message": f"Audio de la conversation {conversation_id} supprimé"}
235
+ else:
236
+ raise HTTPException(
237
+ status_code=404,
238
+ detail=f"Aucun audio trouvé pour la conversation {conversation_id}"
239
+ )
services/chat_service.py CHANGED
@@ -1,13 +1,12 @@
1
  import uuid
2
  from typing import Dict, List, Optional
3
  from datetime import datetime
 
4
  import json
5
 
6
- # Pour Groq Chat API (si vous utilisez Groq)
7
- import requests
8
  from config import GROQ_API_KEY, GROQ_CHAT_MODEL
9
 
10
- # Stockage en mémoire (remplacez par une base de données en production)
11
  conversation_store: Dict[str, List[Dict]] = {}
12
 
13
  def generate_chat_response(
@@ -18,47 +17,73 @@ def generate_chat_response(
18
  """
19
  Génère une réponse de chatbot pour une entrée utilisateur
20
  """
21
- # 1. Gérer la conversation
22
- if not conversation_id:
23
- conversation_id = str(uuid.uuid4())
24
- conversation_store[conversation_id] = []
25
-
26
- # 2. Ajouter le message utilisateur à l'historique
27
- conversation_store[conversation_id].append({
28
- "role": "user",
29
- "content": user_input,
30
- "timestamp": datetime.now().isoformat()
31
- })
32
-
33
- # 3. Préparer le prompt système
34
- if not system_prompt:
35
- system_prompt = """Tu es un assistant vocal amical et utile.
36
- Tes réponses doivent être naturelles à l'oral, concises
37
- (max 2-3 phrases) et adaptées à une synthèse vocale."""
38
-
39
- # 4. Préparer les messages pour l'API Groq
40
- messages = [{"role": "system", "content": system_prompt}]
41
-
42
- # Ajouter l'historique (limité aux derniers messages pour le contexte)
43
- history = conversation_store[conversation_id][-5:] # Derniers 5 échanges
44
- for msg in history:
45
- messages.append({"role": msg["role"], "content": msg["content"]})
46
-
47
- # 5. Appeler l'API Groq Chat
48
  try:
49
- response_text = call_groq_chat_api(messages)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  except Exception as e:
51
- # Fallback simple
52
- response_text = f"Désolé, je ne peux pas répondre pour le moment. Erreur: {str(e)}"
53
-
54
- # 6. Ajouter la réponse à l'historique
55
- conversation_store[conversation_id].append({
56
- "role": "assistant",
57
- "content": response_text,
58
- "timestamp": datetime.now().isoformat()
59
- })
60
-
61
- return response_text
62
 
63
  def call_groq_chat_api(messages: List[Dict]) -> str:
64
  """
@@ -78,15 +103,26 @@ def call_groq_chat_api(messages: List[Dict]) -> str:
78
  "model": GROQ_CHAT_MODEL,
79
  "messages": messages,
80
  "temperature": 0.7,
81
- "max_tokens": 150, # Limité pour les réponses vocales
82
  "top_p": 0.9,
 
83
  }
84
 
85
- response = requests.post(url, headers=headers, json=payload, timeout=30)
86
- response.raise_for_status()
87
-
88
- result = response.json()
89
- return result["choices"][0]["message"]["content"]
 
 
 
 
 
 
 
 
 
 
90
 
91
  def get_conversation_history(conversation_id: str) -> List[Dict]:
92
  """
@@ -99,4 +135,6 @@ def clear_conversation(conversation_id: str):
99
  Efface une conversation
100
  """
101
  if conversation_id in conversation_store:
102
- del conversation_store[conversation_id]
 
 
 
1
  import uuid
2
  from typing import Dict, List, Optional
3
  from datetime import datetime
4
+ import requests
5
  import json
6
 
 
 
7
  from config import GROQ_API_KEY, GROQ_CHAT_MODEL
8
 
9
+ # Stockage en mémoire des conversations
10
  conversation_store: Dict[str, List[Dict]] = {}
11
 
12
  def generate_chat_response(
 
17
  """
18
  Génère une réponse de chatbot pour une entrée utilisateur
19
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  try:
21
+ # 1. Vérifier que l'input n'est pas vide
22
+ if not user_input or not isinstance(user_input, str):
23
+ raise ValueError("L'entrée utilisateur doit être une chaîne de caractères non vide")
24
+
25
+ user_input = user_input.strip()
26
+ if len(user_input) == 0:
27
+ return "Je n'ai pas entendu ce que vous avez dit. Pouvez-vous répéter ?"
28
+
29
+ # 2. Gérer la conversation
30
+ if not conversation_id:
31
+ conversation_id = str(uuid.uuid4())
32
+
33
+ # Initialiser la conversation si elle n'existe pas
34
+ if conversation_id not in conversation_store:
35
+ conversation_store[conversation_id] = []
36
+
37
+ # 3. Ajouter le message utilisateur à l'historique
38
+ conversation_store[conversation_id].append({
39
+ "role": "user",
40
+ "content": user_input,
41
+ "timestamp": datetime.now().isoformat()
42
+ })
43
+
44
+ # 4. Préparer le prompt système
45
+ if not system_prompt:
46
+ system_prompt = """Tu es un assistant vocal amical et utile.
47
+ Tes réponses doivent être naturelles à l'oral, concises
48
+ (max 2-3 phrases) et adaptées à une synthèse vocale.
49
+ Sois courtois et serviable."""
50
+
51
+ # 5. Préparer les messages pour l'API Groq
52
+ messages = [{"role": "system", "content": system_prompt}]
53
+
54
+ # Ajouter l'historique (limité aux derniers messages)
55
+ history = conversation_store[conversation_id][-6:] # Derniers 6 messages
56
+ for msg in history:
57
+ messages.append({"role": msg["role"], "content": msg["content"]})
58
+
59
+ # 6. Appeler l'API Groq Chat
60
+ if not GROQ_API_KEY:
61
+ # Fallback si pas de clé API
62
+ response_text = f"Bonjour ! Vous avez dit : '{user_input}'. Je suis configuré pour répondre, mais l'API Groq n'est pas configurée."
63
+ else:
64
+ try:
65
+ response_text = call_groq_chat_api(messages)
66
+ except Exception as api_error:
67
+ # Fallback en cas d'erreur API
68
+ print(f"Erreur API Groq: {api_error}")
69
+ response_text = f"D'accord, j'ai compris : {user_input}. Je suis un chatbot et je vous réponds."
70
+
71
+ # 7. Ajouter la réponse à l'historique
72
+ conversation_store[conversation_id].append({
73
+ "role": "assistant",
74
+ "content": response_text,
75
+ "timestamp": datetime.now().isoformat()
76
+ })
77
+
78
+ # Limiter la taille de l'historique
79
+ if len(conversation_store[conversation_id]) > 20:
80
+ conversation_store[conversation_id] = conversation_store[conversation_id][-10:]
81
+
82
+ return response_text
83
+
84
  except Exception as e:
85
+ print(f"Erreur dans generate_chat_response: {e}")
86
+ return f"Désolé, une erreur est survenue : {str(e)}"
 
 
 
 
 
 
 
 
 
87
 
88
  def call_groq_chat_api(messages: List[Dict]) -> str:
89
  """
 
103
  "model": GROQ_CHAT_MODEL,
104
  "messages": messages,
105
  "temperature": 0.7,
106
+ "max_tokens": 300, # Augmenté pour des réponses plus complètes
107
  "top_p": 0.9,
108
+ "stream": False
109
  }
110
 
111
+ try:
112
+ response = requests.post(url, headers=headers, json=payload, timeout=30)
113
+ response.raise_for_status()
114
+
115
+ result = response.json()
116
+
117
+ if "choices" not in result or len(result["choices"]) == 0:
118
+ raise ValueError("Réponse invalide de l'API Groq")
119
+
120
+ return result["choices"][0]["message"]["content"]
121
+
122
+ except requests.exceptions.RequestException as e:
123
+ raise Exception(f"Erreur de connexion à l'API Groq: {str(e)}")
124
+ except KeyError as e:
125
+ raise Exception(f"Format de réponse invalide: {str(e)}")
126
 
127
  def get_conversation_history(conversation_id: str) -> List[Dict]:
128
  """
 
135
  Efface une conversation
136
  """
137
  if conversation_id in conversation_store:
138
+ del conversation_store[conversation_id]
139
+ return True
140
+ return False