Yassine Mhirsi commited on
Commit
f101875
·
2 Parent(s): 2380f6f 76c719b

Merge branch 'main' of https://huggingface.co/spaces/NLP-Debater-Project/FastAPI-Backend-Models

Browse files
models/__init__.py CHANGED
@@ -48,7 +48,6 @@ from .mcp_models import (
48
  MatchKeypointResponse,
49
  TranscribeAudioResponse,
50
  GenerateSpeechResponse,
51
- GenerateArgumentResponse,
52
  )
53
 
54
  __all__ = [
@@ -84,5 +83,4 @@ __all__ = [
84
  "MatchKeypointResponse",
85
  "TranscribeAudioResponse",
86
  "GenerateSpeechResponse",
87
- "GenerateArgumentResponse",
88
  ]
 
48
  MatchKeypointResponse,
49
  TranscribeAudioResponse,
50
  GenerateSpeechResponse,
 
51
  )
52
 
53
  __all__ = [
 
83
  "MatchKeypointResponse",
84
  "TranscribeAudioResponse",
85
  "GenerateSpeechResponse",
 
86
  ]
models/mcp_models.py CHANGED
@@ -3,15 +3,43 @@ from typing import Any, Dict, List, Optional
3
 
4
  class ToolCallRequest(BaseModel):
5
  """Request for calling an MCP tool"""
6
- tool_name: str
7
- arguments: Dict[str, Any] = {}
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  class ToolCallResponse(BaseModel):
10
  """Response from MCP tool call"""
11
- success: bool
12
- result: Optional[Dict[str, Any]] = None
13
- error: Optional[str] = None
14
- tool_name: str
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  # Response models for individual MCP tools
17
  class DetectStanceResponse(BaseModel):
@@ -77,18 +105,6 @@ class GenerateSpeechResponse(BaseModel):
77
 
78
  audio_path: str = Field(..., description="Path to generated audio file")
79
 
80
- class GenerateArgumentResponse(BaseModel):
81
- """Response model for argument generation"""
82
- model_config = ConfigDict(
83
- json_schema_extra={
84
- "example": {
85
- "argument": "Climate change is a pressing issue that requires immediate action..."
86
- }
87
- }
88
- )
89
-
90
- argument: str = Field(..., description="Generated debate argument")
91
-
92
  class ResourceInfo(BaseModel):
93
  """Information about an MCP resource"""
94
  uri: str
 
3
 
4
  class ToolCallRequest(BaseModel):
5
  """Request for calling an MCP tool"""
6
+ model_config = ConfigDict(
7
+ json_schema_extra={
8
+ "example": {
9
+ "tool_name": "detect_stance",
10
+ "arguments": {
11
+ "topic": "Climate change is real",
12
+ "argument": "Rising global temperatures prove it"
13
+ }
14
+ }
15
+ }
16
+ )
17
+
18
+ tool_name: str = Field(..., description="Name of the MCP tool to call (e.g., 'detect_stance', 'match_keypoint_argument', 'transcribe_audio', 'generate_speech', 'generate_argument')")
19
+ arguments: Dict[str, Any] = Field(default_factory=dict, description="Arguments for the tool (varies by tool)")
20
 
21
  class ToolCallResponse(BaseModel):
22
  """Response from MCP tool call"""
23
+ model_config = ConfigDict(
24
+ json_schema_extra={
25
+ "example": {
26
+ "success": True,
27
+ "result": {
28
+ "predicted_stance": "PRO",
29
+ "confidence": 0.9598,
30
+ "probability_con": 0.0402,
31
+ "probability_pro": 0.9598
32
+ },
33
+ "error": None,
34
+ "tool_name": "detect_stance"
35
+ }
36
+ }
37
+ )
38
+
39
+ success: bool = Field(..., description="Whether the tool call was successful")
40
+ result: Optional[Dict[str, Any]] = Field(None, description="Result from the tool call")
41
+ error: Optional[str] = Field(None, description="Error message if the call failed")
42
+ tool_name: str = Field(..., description="Name of the tool that was called")
43
 
44
  # Response models for individual MCP tools
45
  class DetectStanceResponse(BaseModel):
 
105
 
106
  audio_path: str = Field(..., description="Path to generated audio file")
107
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  class ResourceInfo(BaseModel):
109
  """Information about an MCP resource"""
110
  uri: str
routes/mcp_routes.py CHANGED
@@ -1,12 +1,19 @@
1
  """Routes pour exposer MCP via FastAPI pour Swagger UI"""
2
 
3
- from fastapi import APIRouter, HTTPException
 
4
  from typing import Dict, Any, Optional
5
  from pydantic import BaseModel, Field
6
  import logging
7
  import json
 
 
 
8
 
9
  from services.mcp_service import mcp_server
 
 
 
10
  from models.mcp_models import (
11
  ToolListResponse,
12
  ToolInfo,
@@ -15,9 +22,10 @@ from models.mcp_models import (
15
  DetectStanceResponse,
16
  MatchKeypointResponse,
17
  TranscribeAudioResponse,
18
- GenerateSpeechResponse,
19
- GenerateArgumentResponse
20
  )
 
 
21
 
22
  router = APIRouter(prefix="/api/v1/mcp", tags=["MCP"])
23
  logger = logging.getLogger(__name__)
@@ -51,16 +59,6 @@ class MatchKeypointRequest(BaseModel):
51
  }
52
  }
53
 
54
- class TranscribeAudioRequest(BaseModel):
55
- """Request pour transcrire un audio"""
56
- audio_path: str = Field(..., description="Chemin vers le fichier audio")
57
-
58
- class Config:
59
- json_schema_extra = {
60
- "example": {
61
- "audio_path": "/path/to/audio.wav"
62
- }
63
- }
64
 
65
  class GenerateSpeechRequest(BaseModel):
66
  """Request pour générer de la parole"""
@@ -77,18 +75,6 @@ class GenerateSpeechRequest(BaseModel):
77
  }
78
  }
79
 
80
- class GenerateArgumentRequest(BaseModel):
81
- """Request pour générer un argument"""
82
- user_input: str = Field(..., description="Input utilisateur pour générer l'argument")
83
- conversation_id: Optional[str] = Field(default=None, description="ID de conversation (optionnel)")
84
-
85
- class Config:
86
- json_schema_extra = {
87
- "example": {
88
- "user_input": "Generate an argument about climate change",
89
- "conversation_id": "conv_123"
90
- }
91
- }
92
 
93
 
94
  # ===== Routes MCP =====
@@ -171,14 +157,14 @@ async def list_mcp_tools():
171
  ),
172
  ToolInfo(
173
  name="generate_argument",
174
- description="Génère un argument de débat à partir d'un input utilisateur",
175
  input_schema={
176
  "type": "object",
177
  "properties": {
178
- "user_input": {"type": "string", "description": "Input utilisateur pour générer l'argument"},
179
- "conversation_id": {"type": "string", "description": "ID de conversation (optionnel)"}
180
  },
181
- "required": ["user_input"]
182
  }
183
  ),
184
  ToolInfo(
@@ -199,7 +185,66 @@ async def list_mcp_tools():
199
 
200
  @router.post("/tools/call", response_model=ToolCallResponse, summary="Appeler un outil MCP")
201
  async def call_mcp_tool(request: ToolCallRequest):
202
- """Appelle un outil MCP par son nom avec des arguments"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  try:
204
  result = await mcp_server.call_tool(request.tool_name, request.arguments)
205
  # Gérer différents types de retours MCP
@@ -248,43 +293,19 @@ async def call_mcp_tool(request: ToolCallRequest):
248
  async def mcp_detect_stance(request: DetectStanceRequest):
249
  """Détecte si un argument est PRO ou CON pour un topic donné"""
250
  try:
251
- # Appeler directement via call_tool (async)
252
- result = await mcp_server.call_tool("detect_stance", {
253
- "topic": request.topic,
254
- "argument": request.argument
255
- })
256
-
257
- # Extraire les données du résultat MCP
258
- parsed_result = None
259
- if isinstance(result, dict):
260
- # Si le résultat contient une clé "result" avec une liste de ContentBlock
261
- if "result" in result and isinstance(result["result"], list) and len(result["result"]) > 0:
262
- content_block = result["result"][0]
263
- if hasattr(content_block, 'text') and content_block.text:
264
- try:
265
- parsed_result = json.loads(content_block.text)
266
- except json.JSONDecodeError:
267
- raise HTTPException(status_code=500, detail="Invalid JSON response from MCP tool")
268
- else:
269
- parsed_result = result
270
- elif isinstance(result, (list, tuple)) and len(result) > 0:
271
- if hasattr(result[0], 'text') and result[0].text:
272
- try:
273
- parsed_result = json.loads(result[0].text)
274
- except json.JSONDecodeError:
275
- raise HTTPException(status_code=500, detail="Invalid JSON response from MCP tool")
276
- else:
277
- parsed_result = result
278
 
279
- if not parsed_result:
280
- raise HTTPException(status_code=500, detail="Empty response from MCP tool")
281
 
282
- # Construire la réponse structurée
283
  response = DetectStanceResponse(
284
- predicted_stance=parsed_result["predicted_stance"],
285
- confidence=parsed_result["confidence"],
286
- probability_con=parsed_result["probability_con"],
287
- probability_pro=parsed_result["probability_pro"]
288
  )
289
 
290
  logger.info(f"Stance prediction: {response.predicted_stance} (conf={response.confidence:.4f})")
@@ -292,49 +313,30 @@ async def mcp_detect_stance(request: DetectStanceRequest):
292
 
293
  except HTTPException:
294
  raise
 
 
 
295
  except Exception as e:
296
- logger.error(f"Error in detect_stance: {e}")
297
  raise HTTPException(status_code=500, detail=f"Error executing tool detect_stance: {e}")
298
 
299
  @router.post("/tools/match-keypoint", response_model=MatchKeypointResponse, summary="Matcher un argument avec un keypoint")
300
  async def mcp_match_keypoint(request: MatchKeypointRequest):
301
  """Détermine si un argument correspond à un keypoint"""
302
  try:
303
- result = await mcp_server.call_tool("match_keypoint_argument", {
304
- "argument": request.argument,
305
- "key_point": request.key_point
306
- })
307
 
308
- # Extraire les données du résultat MCP
309
- parsed_result = None
310
- if isinstance(result, dict):
311
- if "result" in result and isinstance(result["result"], list) and len(result["result"]) > 0:
312
- content_block = result["result"][0]
313
- if hasattr(content_block, 'text') and content_block.text:
314
- try:
315
- parsed_result = json.loads(content_block.text)
316
- except json.JSONDecodeError:
317
- raise HTTPException(status_code=500, detail="Invalid JSON response from MCP tool")
318
- else:
319
- parsed_result = result
320
- elif isinstance(result, (list, tuple)) and len(result) > 0:
321
- if hasattr(result[0], 'text') and result[0].text:
322
- try:
323
- parsed_result = json.loads(result[0].text)
324
- except json.JSONDecodeError:
325
- raise HTTPException(status_code=500, detail="Invalid JSON response from MCP tool")
326
- else:
327
- parsed_result = result
328
-
329
- if not parsed_result:
330
- raise HTTPException(status_code=500, detail="Empty response from MCP tool")
331
 
332
- # Construire la réponse structurée
333
  response = MatchKeypointResponse(
334
- prediction=parsed_result["prediction"],
335
- label=parsed_result["label"],
336
- confidence=parsed_result["confidence"],
337
- probabilities=parsed_result["probabilities"]
338
  )
339
 
340
  logger.info(f"Keypoint matching: {response.label} (conf={response.confidence:.4f})")
@@ -342,16 +344,35 @@ async def mcp_match_keypoint(request: MatchKeypointRequest):
342
 
343
  except HTTPException:
344
  raise
 
 
 
345
  except Exception as e:
346
- logger.error(f"Error in match_keypoint_argument: {e}")
347
  raise HTTPException(status_code=500, detail=f"Error executing tool match_keypoint_argument: {e}")
348
 
349
  @router.post("/tools/transcribe-audio", response_model=TranscribeAudioResponse, summary="Transcrire un audio en texte")
350
- async def mcp_transcribe_audio(request: TranscribeAudioRequest):
351
- """Convertit un fichier audio en texte"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
  try:
 
353
  result = await mcp_server.call_tool("transcribe_audio", {
354
- "audio_path": request.audio_path
355
  })
356
 
357
  # Extraire le texte du résultat MCP
@@ -388,10 +409,14 @@ async def mcp_transcribe_audio(request: TranscribeAudioRequest):
388
  except Exception as e:
389
  logger.error(f"Error in transcribe_audio: {e}")
390
  raise HTTPException(status_code=500, detail=f"Error executing tool transcribe_audio: {e}")
 
 
 
 
391
 
392
- @router.post("/tools/generate-speech", response_model=GenerateSpeechResponse, summary="Générer de la parole à partir de texte")
393
  async def mcp_generate_speech(request: GenerateSpeechRequest):
394
- """Convertit du texte en fichier audio"""
395
  try:
396
  result = await mcp_server.call_tool("generate_speech", {
397
  "text": request.text,
@@ -418,12 +443,35 @@ async def mcp_generate_speech(request: GenerateSpeechRequest):
418
  else:
419
  audio_path = str(result)
420
 
 
 
 
 
 
 
 
 
 
421
  if not audio_path:
422
  raise HTTPException(status_code=500, detail="Empty audio path from MCP tool")
423
 
424
- response = GenerateSpeechResponse(audio_path=audio_path)
 
 
 
 
 
 
 
425
  logger.info(f"Speech generated: {audio_path}")
426
- return response
 
 
 
 
 
 
 
427
 
428
  except HTTPException:
429
  raise
@@ -431,45 +479,35 @@ async def mcp_generate_speech(request: GenerateSpeechRequest):
431
  logger.error(f"Error in generate_speech: {e}")
432
  raise HTTPException(status_code=500, detail=f"Error executing tool generate_speech: {e}")
433
 
434
- @router.post("/tools/generate-argument", response_model=GenerateArgumentResponse, summary="Générer un argument de débat")
435
- async def mcp_generate_argument(request: GenerateArgumentRequest):
436
- """Génère un argument de débat à partir d'un input utilisateur"""
437
  try:
438
- result = await mcp_server.call_tool("generate_argument", {
439
- "user_input": request.user_input,
440
- "conversation_id": request.conversation_id
441
- })
442
 
443
- # Extraire l'argument du résultat MCP
444
- generated_argument = None
445
- if isinstance(result, dict):
446
- if "result" in result and isinstance(result["result"], list) and len(result["result"]) > 0:
447
- content_block = result["result"][0]
448
- if hasattr(content_block, 'text'):
449
- generated_argument = content_block.text
450
- elif "argument" in result:
451
- generated_argument = result["argument"]
452
- elif isinstance(result, str):
453
- generated_argument = result
454
- elif isinstance(result, (list, tuple)) and len(result) > 0:
455
- if hasattr(result[0], 'text'):
456
- generated_argument = result[0].text
457
- else:
458
- generated_argument = str(result[0])
459
- else:
460
- generated_argument = str(result)
461
 
462
- if not generated_argument:
463
- raise HTTPException(status_code=500, detail="Empty argument from MCP tool")
 
 
 
 
 
464
 
465
- response = GenerateArgumentResponse(argument=generated_argument)
466
- logger.info(f"Argument generated: {len(generated_argument)} characters")
467
  return response
468
 
469
  except HTTPException:
470
  raise
471
  except Exception as e:
472
- logger.error(f"Error in generate_argument: {e}")
473
  raise HTTPException(status_code=500, detail=f"Error executing tool generate_argument: {e}")
474
 
475
  @router.get("/tools/health-check", summary="Health check MCP (outil)")
 
1
  """Routes pour exposer MCP via FastAPI pour Swagger UI"""
2
 
3
+ from fastapi import APIRouter, HTTPException, UploadFile, File
4
+ from fastapi.responses import FileResponse
5
  from typing import Dict, Any, Optional
6
  from pydantic import BaseModel, Field
7
  import logging
8
  import json
9
+ import tempfile
10
+ import os
11
+ from pathlib import Path
12
 
13
  from services.mcp_service import mcp_server
14
+ from services.stance_model_manager import stance_model_manager
15
+ from services.label_model_manager import kpa_model_manager
16
+ from services.generate_model_manager import generate_model_manager
17
  from models.mcp_models import (
18
  ToolListResponse,
19
  ToolInfo,
 
22
  DetectStanceResponse,
23
  MatchKeypointResponse,
24
  TranscribeAudioResponse,
25
+ GenerateSpeechResponse
 
26
  )
27
+ from models.generate import GenerateRequest, GenerateResponse
28
+ from datetime import datetime
29
 
30
  router = APIRouter(prefix="/api/v1/mcp", tags=["MCP"])
31
  logger = logging.getLogger(__name__)
 
59
  }
60
  }
61
 
 
 
 
 
 
 
 
 
 
 
62
 
63
  class GenerateSpeechRequest(BaseModel):
64
  """Request pour générer de la parole"""
 
75
  }
76
  }
77
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
 
80
  # ===== Routes MCP =====
 
157
  ),
158
  ToolInfo(
159
  name="generate_argument",
160
+ description="Génère un argument de débat pour un topic et une position donnés",
161
  input_schema={
162
  "type": "object",
163
  "properties": {
164
+ "topic": {"type": "string", "description": "Le sujet du débat"},
165
+ "position": {"type": "string", "description": "La position à prendre (positive ou negative)"}
166
  },
167
+ "required": ["topic", "position"]
168
  }
169
  ),
170
  ToolInfo(
 
185
 
186
  @router.post("/tools/call", response_model=ToolCallResponse, summary="Appeler un outil MCP")
187
  async def call_mcp_tool(request: ToolCallRequest):
188
+ """
189
+ Appelle un outil MCP par son nom avec des arguments
190
+
191
+ **Exemples d'utilisation:**
192
+
193
+ 1. **detect_stance** - Détecter la stance d'un argument:
194
+ ```json
195
+ {
196
+ "tool_name": "detect_stance",
197
+ "arguments": {
198
+ "topic": "Climate change is real",
199
+ "argument": "Rising global temperatures prove it"
200
+ }
201
+ }
202
+ ```
203
+
204
+ 2. **match_keypoint_argument** - Matcher un argument avec un keypoint:
205
+ ```json
206
+ {
207
+ "tool_name": "match_keypoint_argument",
208
+ "arguments": {
209
+ "argument": "Renewable energy reduces emissions",
210
+ "key_point": "Environmental benefits"
211
+ }
212
+ }
213
+ ```
214
+
215
+ 3. **generate_argument** - Générer un argument:
216
+ ```json
217
+ {
218
+ "tool_name": "generate_argument",
219
+ "arguments": {
220
+ "topic": "Assisted suicide should be legal",
221
+ "position": "positive"
222
+ }
223
+ }
224
+ ```
225
+
226
+ 4. **transcribe_audio** - Transcrire un audio:
227
+ ```json
228
+ {
229
+ "tool_name": "transcribe_audio",
230
+ "arguments": {
231
+ "audio_path": "/path/to/audio.wav"
232
+ }
233
+ }
234
+ ```
235
+
236
+ 5. **generate_speech** - Générer de la parole:
237
+ ```json
238
+ {
239
+ "tool_name": "generate_speech",
240
+ "arguments": {
241
+ "text": "Hello, this is a test",
242
+ "voice": "Aaliyah-PlayAI",
243
+ "format": "wav"
244
+ }
245
+ }
246
+ ```
247
+ """
248
  try:
249
  result = await mcp_server.call_tool(request.tool_name, request.arguments)
250
  # Gérer différents types de retours MCP
 
293
  async def mcp_detect_stance(request: DetectStanceRequest):
294
  """Détecte si un argument est PRO ou CON pour un topic donné"""
295
  try:
296
+ # Vérifier que le modèle est chargé
297
+ if not stance_model_manager.model_loaded:
298
+ raise HTTPException(status_code=503, detail="Stance model not loaded")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
 
300
+ # Appeler directement le modèle (plus fiable que via MCP)
301
+ result = stance_model_manager.predict(request.topic, request.argument)
302
 
303
+ # Construire la réponse structurée directement depuis le résultat du modèle
304
  response = DetectStanceResponse(
305
+ predicted_stance=result["predicted_stance"],
306
+ confidence=result["confidence"],
307
+ probability_con=result["probability_con"],
308
+ probability_pro=result["probability_pro"]
309
  )
310
 
311
  logger.info(f"Stance prediction: {response.predicted_stance} (conf={response.confidence:.4f})")
 
313
 
314
  except HTTPException:
315
  raise
316
+ except KeyError as e:
317
+ logger.error(f"Missing key in detect_stance response: {e}")
318
+ raise HTTPException(status_code=500, detail=f"Invalid response format: missing {e}")
319
  except Exception as e:
320
+ logger.error(f"Error in detect_stance: {e}", exc_info=True)
321
  raise HTTPException(status_code=500, detail=f"Error executing tool detect_stance: {e}")
322
 
323
  @router.post("/tools/match-keypoint", response_model=MatchKeypointResponse, summary="Matcher un argument avec un keypoint")
324
  async def mcp_match_keypoint(request: MatchKeypointRequest):
325
  """Détermine si un argument correspond à un keypoint"""
326
  try:
327
+ # Vérifier que le modèle est chargé
328
+ if not kpa_model_manager.model_loaded:
329
+ raise HTTPException(status_code=503, detail="KPA model not loaded")
 
330
 
331
+ # Appeler directement le modèle (plus fiable que via MCP)
332
+ result = kpa_model_manager.predict(request.argument, request.key_point)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
 
334
+ # Construire la réponse structurée directement depuis le résultat du modèle
335
  response = MatchKeypointResponse(
336
+ prediction=result["prediction"],
337
+ label=result["label"],
338
+ confidence=result["confidence"],
339
+ probabilities=result["probabilities"]
340
  )
341
 
342
  logger.info(f"Keypoint matching: {response.label} (conf={response.confidence:.4f})")
 
344
 
345
  except HTTPException:
346
  raise
347
+ except KeyError as e:
348
+ logger.error(f"Missing key in match_keypoint response: {e}")
349
+ raise HTTPException(status_code=500, detail=f"Invalid response format: missing {e}")
350
  except Exception as e:
351
+ logger.error(f"Error in match_keypoint_argument: {e}", exc_info=True)
352
  raise HTTPException(status_code=500, detail=f"Error executing tool match_keypoint_argument: {e}")
353
 
354
  @router.post("/tools/transcribe-audio", response_model=TranscribeAudioResponse, summary="Transcrire un audio en texte")
355
+ async def mcp_transcribe_audio(file: UploadFile = File(...)):
356
+ """Convertit un fichier audio en texte (upload de fichier)"""
357
+ # Vérifier le type de fichier
358
+ if not file.content_type or not file.content_type.startswith('audio/'):
359
+ raise HTTPException(status_code=400, detail="File must be an audio file")
360
+
361
+ # Créer un fichier temporaire
362
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as temp_file:
363
+ temp_path = temp_file.name
364
+ content = await file.read()
365
+
366
+ if len(content) == 0:
367
+ os.unlink(temp_path)
368
+ raise HTTPException(status_code=400, detail="Audio file is empty")
369
+
370
+ temp_file.write(content)
371
+
372
  try:
373
+ # Appeler le service MCP avec le chemin temporaire
374
  result = await mcp_server.call_tool("transcribe_audio", {
375
+ "audio_path": temp_path
376
  })
377
 
378
  # Extraire le texte du résultat MCP
 
409
  except Exception as e:
410
  logger.error(f"Error in transcribe_audio: {e}")
411
  raise HTTPException(status_code=500, detail=f"Error executing tool transcribe_audio: {e}")
412
+ finally:
413
+ # Nettoyer le fichier temporaire
414
+ if os.path.exists(temp_path):
415
+ os.unlink(temp_path)
416
 
417
+ @router.post("/tools/generate-speech", summary="Générer de la parole à partir de texte")
418
  async def mcp_generate_speech(request: GenerateSpeechRequest):
419
+ """Convertit du texte en fichier audio (téléchargeable)"""
420
  try:
421
  result = await mcp_server.call_tool("generate_speech", {
422
  "text": request.text,
 
443
  else:
444
  audio_path = str(result)
445
 
446
+ # Nettoyer le chemin si c'est une représentation string d'objet
447
+ if audio_path and isinstance(audio_path, str):
448
+ # Si c'est une représentation d'objet TextContent, extraire le chemin
449
+ if "text='" in audio_path and ".wav" in audio_path:
450
+ import re
451
+ match = re.search(r"text='([^']+)'", audio_path)
452
+ if match:
453
+ audio_path = match.group(1)
454
+
455
  if not audio_path:
456
  raise HTTPException(status_code=500, detail="Empty audio path from MCP tool")
457
 
458
+ # Vérifier que le fichier existe
459
+ if not Path(audio_path).exists():
460
+ raise HTTPException(status_code=500, detail=f"Audio file not found: {audio_path}")
461
+
462
+ # Déterminer le type MIME
463
+ media_type = "audio/wav" if request.format == "wav" else "audio/mpeg"
464
+
465
+ # Retourner le fichier pour téléchargement
466
  logger.info(f"Speech generated: {audio_path}")
467
+ return FileResponse(
468
+ path=audio_path,
469
+ filename=f"speech.{request.format}",
470
+ media_type=media_type,
471
+ headers={
472
+ "Content-Disposition": f"attachment; filename=speech.{request.format}"
473
+ }
474
+ )
475
 
476
  except HTTPException:
477
  raise
 
479
  logger.error(f"Error in generate_speech: {e}")
480
  raise HTTPException(status_code=500, detail=f"Error executing tool generate_speech: {e}")
481
 
482
+ @router.post("/tools/generate-argument", response_model=GenerateResponse, summary="Générer un argument de débat")
483
+ async def mcp_generate_argument(request: GenerateRequest):
484
+ """Génère un argument de débat pour un topic et une position donnés"""
485
  try:
486
+ # Vérifier que le modèle est chargé
487
+ if not generate_model_manager.model_loaded:
488
+ raise HTTPException(status_code=503, detail="Generation model not loaded")
 
489
 
490
+ # Appeler directement le modèle (plus fiable que via MCP)
491
+ argument_text = generate_model_manager.generate(
492
+ topic=request.topic,
493
+ position=request.position
494
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
495
 
496
+ # Construire la réponse structurée
497
+ response = GenerateResponse(
498
+ topic=request.topic,
499
+ position=request.position,
500
+ argument=argument_text,
501
+ timestamp=datetime.now().isoformat()
502
+ )
503
 
504
+ logger.info(f"Argument generated for topic '{request.topic}' with position '{request.position}': {len(response.argument)} characters")
 
505
  return response
506
 
507
  except HTTPException:
508
  raise
509
  except Exception as e:
510
+ logger.error(f"Error in generate_argument: {e}", exc_info=True)
511
  raise HTTPException(status_code=500, detail=f"Error executing tool generate_argument: {e}")
512
 
513
  @router.get("/tools/health-check", summary="Health check MCP (outil)")
services/mcp_service.py CHANGED
@@ -10,7 +10,7 @@ from services.stance_model_manager import stance_model_manager
10
  from services.label_model_manager import kpa_model_manager
11
  from services.stt_service import speech_to_text
12
  from services.tts_service import text_to_speech
13
- from services.chat_service import generate_chat_response
14
 
15
  logger = logging.getLogger(__name__)
16
 
@@ -51,8 +51,16 @@ def generate_speech(text: str, voice: str = "Aaliyah-PlayAI", format: str = "wav
51
  return text_to_speech(text, voice, format)
52
 
53
  @mcp_server.tool()
54
- def generate_argument(user_input: str, conversation_id: str = None) -> str:
55
- return generate_chat_response(user_input, conversation_id)
 
 
 
 
 
 
 
 
56
 
57
  @mcp_server.resource("debate://prompt")
58
  def get_debate_prompt() -> str:
 
10
  from services.label_model_manager import kpa_model_manager
11
  from services.stt_service import speech_to_text
12
  from services.tts_service import text_to_speech
13
+ from services.generate_model_manager import generate_model_manager
14
 
15
  logger = logging.getLogger(__name__)
16
 
 
51
  return text_to_speech(text, voice, format)
52
 
53
  @mcp_server.tool()
54
+ def generate_argument(topic: str, position: str) -> Dict[str, Any]:
55
+ """Generate an argument for a given topic and position"""
56
+ if not generate_model_manager.model_loaded:
57
+ raise ValueError("Modèle de génération non chargé")
58
+ argument = generate_model_manager.generate(topic=topic, position=position)
59
+ return {
60
+ "topic": topic,
61
+ "position": position,
62
+ "argument": argument
63
+ }
64
 
65
  @mcp_server.resource("debate://prompt")
66
  def get_debate_prompt() -> str: