Hug0endob commited on
Commit
a999681
·
verified ·
1 Parent(s): 60c3cca

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +74 -36
app.py CHANGED
@@ -11,7 +11,7 @@ from PIL import Image, ImageFile, UnidentifiedImageError
11
  import gradio as gr
12
  import time
13
  import atexit
14
- from requests.exceptions import RequestException
15
 
16
  # --- Mistral Client Import & Placeholder for graceful degradation ---
17
  _MISTRAL_CLIENT_INSTALLED = False
@@ -22,8 +22,9 @@ try:
22
  except ImportError:
23
  print(
24
  "Warning: Mistral AI client library ('mistralai') not found. "
25
- "Please install it with 'pip install mistralai' to enable AI analysis features. "
26
- "The application will launch, but API calls will fail."
 
27
  )
28
  # Define placeholder classes to prevent NameErrors and provide clear messages
29
  class MistralAPIException(Exception):
@@ -38,6 +39,8 @@ except ImportError:
38
  class _DummyMistralChatClient:
39
  """Placeholder for Mistral client's chat interface."""
40
  def complete(self, *args, **kwargs):
 
 
41
  raise MistralAPIException(
42
  "Mistral AI chat client is unavailable. "
43
  "Please install 'mistralai' with 'pip install mistralai'.",
@@ -46,6 +49,7 @@ except ImportError:
46
  class _DummyMistralFilesClient:
47
  """Placeholder for Mistral client's files interface."""
48
  def upload(self, *args, **kwargs):
 
49
  raise MistralAPIException(
50
  "Mistral AI files client is unavailable. "
51
  "Please install 'mistralai' with 'pip install mistralai'.",
@@ -53,8 +57,8 @@ except ImportError:
53
  )
54
  class Mistral:
55
  """A placeholder for the Mistral client if the library is not installed."""
56
- def __init__(self, *args, **kwargs):
57
- pass # Constructor doesn't need to raise here, methods will.
58
  @property
59
  def chat(self):
60
  return _DummyMistralChatClient()
@@ -105,15 +109,10 @@ atexit.register(_cleanup_all_temp_files)
105
  # --- Mistral Client and API Helpers ---
106
  def get_client(api_key: Optional[str] = None):
107
  """
108
- Returns a Mistral client instance. If the API key is missing or the client library
109
- is not installed, a MistralAPIException is raised.
 
110
  """
111
- if not _MISTRAL_CLIENT_INSTALLED:
112
- raise MistralAPIException(
113
- "Mistral AI client library is not installed. Please install it with 'pip install mistralai'.",
114
- status_code=500 # Internal Server Error, as it's a server-side dependency issue
115
- )
116
-
117
  key_to_use = (api_key or "").strip() or DEFAULT_MISTRAL_KEY
118
  if not key_to_use:
119
  raise MistralAPIException(
@@ -121,8 +120,9 @@ def get_client(api_key: Optional[str] = None):
121
  status_code=401 # Unauthorized
122
  )
123
 
 
124
  # If _MISTRAL_CLIENT_INSTALLED is True, this will be the real Mistral client.
125
- # Otherwise, it's the placeholder that will raise on method call.
126
  return Mistral(api_key=key_to_use)
127
 
128
  def is_remote(src: str) -> bool:
@@ -370,29 +370,48 @@ def chat_complete(client, model: str, messages, timeout: int = 120, progress=Non
370
  if progress is not None:
371
  progress(0.6 + 0.01 * attempt, desc=f"Sending request to model (attempt {attempt+1}/{max_retries})...")
372
 
373
- res = client.chat.complete(model=model, messages=messages, stream=False, timeout_ms=timeout * 1000)
374
- choices = getattr(res, "choices", None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
375
 
376
  if progress is not None:
377
  progress(0.8, desc="Model responded, parsing...")
378
 
 
 
 
379
  if not choices:
380
  return f"Empty response from model: {res}"
381
 
382
  first = choices[0]
383
- # Handle both object-style and dict-style responses
384
  msg = getattr(first, "message", None) or (first.get("message") if isinstance(first, dict) else first)
385
  content = getattr(msg, "content", None) or (msg.get("content") if isinstance(msg, dict) else None)
386
  return content.strip() if isinstance(content, str) else str(content)
387
 
388
- except MistralAPIException as e:
389
- if e.status_code == 429 and attempt < max_retries - 1:
 
 
 
390
  delay = initial_delay * (2 ** attempt)
391
- print(f"MistralAPIException: Rate limit exceeded (429). Retrying in {delay:.2f}s...")
392
  time.sleep(delay)
393
  else:
394
- return f"Error: Mistral API error occurred ({e.status_code if e.status_code else 'unknown'}): {e.message}"
395
- except RequestException as e:
396
  if attempt < max_retries - 1:
397
  delay = initial_delay * (2 ** attempt)
398
  print(f"Network/API request failed: {e}. Retrying in {delay:.2f}s...")
@@ -414,26 +433,45 @@ def upload_file_to_mistral(client, path: str, filename: str | None = None, purpo
414
  if progress is not None:
415
  progress(0.5 + 0.01 * attempt, desc=f"Uploading file to model service (attempt {attempt+1}/{max_retries})...")
416
 
417
- with open(path, "rb") as fh:
418
- # The Mistral client's file upload typically expects a dict with 'file_name' and 'content'
419
- # or a file-like object directly. Ensure compatibility here.
420
- # Assuming the client expects 'file' parameter to be a dict as shown in placeholder
421
- res = client.files.upload(file={"file_name": fname, "content": fh}, purpose=purpose)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
 
423
- fid = getattr(res, "id", None) or (res.get("id") if isinstance(res, dict) else None)
424
- if not fid:
425
- raise RuntimeError(f"Mistral API upload response missing file ID: {res}")
426
  if progress is not None:
427
  progress(0.6, desc="Upload complete")
428
  return fid
429
 
430
- except MistralAPIException as e:
431
- if e.status_code == 429 and attempt < max_retries - 1:
 
 
432
  delay = initial_delay * (2 ** attempt)
433
- print(f"MistralAPIException: Upload rate limit exceeded (429). Retrying in {delay:.2f}s...")
434
  time.sleep(delay)
435
  else:
436
- raise RuntimeError(f"Mistral API file upload failed with status {e.status_code}: {e.message}") from e
437
  except RequestException as e:
438
  if attempt < max_retries - 1:
439
  delay = initial_delay * (2 ** attempt)
@@ -650,7 +688,7 @@ def create_demo():
650
 
651
  mistral_client_status_message = ""
652
  if not _MISTRAL_CLIENT_INSTALLED:
653
- mistral_client_status_message = "🔴 Mistral AI client ('mistralai') not installed. AI analysis features will fail. Run `pip install mistralai`."
654
  else:
655
  mistral_client_status_message = "🟢 Mistral AI client found."
656
 
@@ -883,7 +921,7 @@ def create_demo():
883
  if ext_from_src(raw_media_path) in VIDEO_EXTENSIONS:
884
  is_actually_video_for_analysis = True
885
 
886
- client = get_client(key) # This will raise MistralAPIException if library not installed or key missing
887
 
888
  if is_actually_video_for_analysis:
889
  progress(0.25, desc="Running full-video analysis")
 
11
  import gradio as gr
12
  import time
13
  import atexit
14
+ from requests.exceptions import RequestException, HTTPError # Import HTTPError for requests fallback
15
 
16
  # --- Mistral Client Import & Placeholder for graceful degradation ---
17
  _MISTRAL_CLIENT_INSTALLED = False
 
22
  except ImportError:
23
  print(
24
  "Warning: Mistral AI client library ('mistralai') not found. "
25
+ "Please install it with 'pip install mistralai' to enable full AI analysis features. " # Updated message
26
+ "The application will launch, but API calls will fall back to direct HTTP requests "
27
+ "if an API key is provided." # Updated message to reflect fallback
28
  )
29
  # Define placeholder classes to prevent NameErrors and provide clear messages
30
  class MistralAPIException(Exception):
 
39
  class _DummyMistralChatClient:
40
  """Placeholder for Mistral client's chat interface."""
41
  def complete(self, *args, **kwargs):
42
+ # This method will typically not be called if _MISTRAL_CLIENT_INSTALLED is False,
43
+ # as the `chat_complete` function will use the requests fallback instead.
44
  raise MistralAPIException(
45
  "Mistral AI chat client is unavailable. "
46
  "Please install 'mistralai' with 'pip install mistralai'.",
 
49
  class _DummyMistralFilesClient:
50
  """Placeholder for Mistral client's files interface."""
51
  def upload(self, *args, **kwargs):
52
+ # This method will typically not be called if _MISTRAL_CLIENT_INSTALLED is False.
53
  raise MistralAPIException(
54
  "Mistral AI files client is unavailable. "
55
  "Please install 'mistralai' with 'pip install mistralai'.",
 
57
  )
58
  class Mistral:
59
  """A placeholder for the Mistral client if the library is not installed."""
60
+ def __init__(self, api_key: str = "", *args, **kwargs): # Added api_key to store it for fallback
61
+ self.api_key = api_key # Store the API key
62
  @property
63
  def chat(self):
64
  return _DummyMistralChatClient()
 
109
  # --- Mistral Client and API Helpers ---
110
  def get_client(api_key: Optional[str] = None):
111
  """
112
+ Returns a Mistral client instance. If the API key is missing, a MistralAPIException is raised.
113
+ If the client library is not installed, a placeholder client is returned, and API calls
114
+ will fall back to direct HTTP requests.
115
  """
 
 
 
 
 
 
116
  key_to_use = (api_key or "").strip() or DEFAULT_MISTRAL_KEY
117
  if not key_to_use:
118
  raise MistralAPIException(
 
120
  status_code=401 # Unauthorized
121
  )
122
 
123
+ # Always return a Mistral client instance.
124
  # If _MISTRAL_CLIENT_INSTALLED is True, this will be the real Mistral client.
125
+ # Otherwise, it's the placeholder that has the api_key stored.
126
  return Mistral(api_key=key_to_use)
127
 
128
  def is_remote(src: str) -> bool:
 
370
  if progress is not None:
371
  progress(0.6 + 0.01 * attempt, desc=f"Sending request to model (attempt {attempt+1}/{max_retries})...")
372
 
373
+ res = None
374
+ if _MISTRAL_CLIENT_INSTALLED:
375
+ # Use the real Mistral client's chat.complete method
376
+ res = client.chat.complete(model=model, messages=messages, stream=False, timeout_ms=timeout * 1000)
377
+ else:
378
+ # Fallback to direct HTTP request if client library not installed
379
+ api_key = getattr(client, "api_key", "") # Get key from client, should always be present now
380
+ if not api_key: # Double check, though get_client already ensures this
381
+ return "Error: Mistral API key is not set for fallback."
382
+
383
+ url = "https://api.mistral.ai/v1/chat/completions"
384
+ headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
385
+ payload = {"model": model, "messages": messages, "stream": False} # Removed timeout_ms for requests
386
+ r = requests.post(url, json=payload, headers=headers, timeout=timeout) # requests timeout in seconds
387
+ r.raise_for_status() # Raises HTTPError for bad responses (4xx or 5xx)
388
+ res = r.json()
389
 
390
  if progress is not None:
391
  progress(0.8, desc="Model responded, parsing...")
392
 
393
+ # Handle both object-style (from mistralai client) and dict-style (from requests.json()) responses
394
+ choices = getattr(res, "choices", None) or (res.get("choices") if isinstance(res, dict) else [])
395
+
396
  if not choices:
397
  return f"Empty response from model: {res}"
398
 
399
  first = choices[0]
 
400
  msg = getattr(first, "message", None) or (first.get("message") if isinstance(first, dict) else first)
401
  content = getattr(msg, "content", None) or (msg.get("content") if isinstance(msg, dict) else None)
402
  return content.strip() if isinstance(content, str) else str(content)
403
 
404
+ except (MistralAPIException, HTTPError) as e: # Catch both client lib and requests HTTP errors
405
+ status_code = getattr(e, "status_code", None) or (e.response.status_code if isinstance(e, HTTPError) else None)
406
+ message = getattr(e, "message", str(e))
407
+
408
+ if status_code == 429 and attempt < max_retries - 1:
409
  delay = initial_delay * (2 ** attempt)
410
+ print(f"Mistral API: Rate limit exceeded (429). Retrying in {delay:.2f}s...")
411
  time.sleep(delay)
412
  else:
413
+ return f"Error: Mistral API error occurred ({status_code if status_code else 'unknown'}): {message}"
414
+ except RequestException as e: # Catch other requests errors (e.g., connection issues, timeout)
415
  if attempt < max_retries - 1:
416
  delay = initial_delay * (2 ** attempt)
417
  print(f"Network/API request failed: {e}. Retrying in {delay:.2f}s...")
 
433
  if progress is not None:
434
  progress(0.5 + 0.01 * attempt, desc=f"Uploading file to model service (attempt {attempt+1}/{max_retries})...")
435
 
436
+ fid = None
437
+ if _MISTRAL_CLIENT_INSTALLED:
438
+ with open(path, "rb") as fh:
439
+ # Mistral client's file upload expects (filename, file_like_object) for 'file' param
440
+ res = client.files.upload(file=(fname, fh), purpose=purpose)
441
+ fid = getattr(res, "id", None) or (res.get("id") if isinstance(res, dict) else None)
442
+ if not fid:
443
+ raise RuntimeError(f"Mistral API upload response missing file ID from client: {res}")
444
+ else:
445
+ # Fallback to direct HTTP request
446
+ api_key = getattr(client, "api_key", "")
447
+ if not api_key:
448
+ raise RuntimeError("Mistral API key is not set for file upload fallback.")
449
+
450
+ url = "https://api.mistral.ai/v1/files"
451
+ headers = {"Authorization": f"Bearer {api_key}"}
452
+ with open(path, "rb") as fh:
453
+ files = {"file": (fname, fh)}
454
+ data = {"purpose": purpose}
455
+ r = requests.post(url, headers=headers, files=files, data=data, timeout=timeout)
456
+ r.raise_for_status()
457
+ jr = r.json()
458
+ fid = jr.get("id") or jr.get("data", [{}])[0].get("id") # Handle potential nested 'data' from older API
459
+ if not fid:
460
+ raise RuntimeError(f"Mistral API upload response missing file ID from direct request: {jr}")
461
 
 
 
 
462
  if progress is not None:
463
  progress(0.6, desc="Upload complete")
464
  return fid
465
 
466
+ except (MistralAPIException, HTTPError) as e:
467
+ status_code = getattr(e, "status_code", None) or (e.response.status_code if isinstance(e, HTTPError) else None)
468
+ message = getattr(e, "message", str(e))
469
+ if status_code == 429 and attempt < max_retries - 1:
470
  delay = initial_delay * (2 ** attempt)
471
+ print(f"Mistral API: Upload rate limit exceeded (429). Retrying in {delay:.2f}s...")
472
  time.sleep(delay)
473
  else:
474
+ raise RuntimeError(f"Mistral API file upload failed with status {status_code}: {message}") from e
475
  except RequestException as e:
476
  if attempt < max_retries - 1:
477
  delay = initial_delay * (2 ** attempt)
 
688
 
689
  mistral_client_status_message = ""
690
  if not _MISTRAL_CLIENT_INSTALLED:
691
+ mistral_client_status_message = "🟡 Mistral AI client ('mistralai') not installed. AI analysis will fall back to direct HTTP requests. Run `pip install mistralai` for full features." # Updated message
692
  else:
693
  mistral_client_status_message = "🟢 Mistral AI client found."
694
 
 
921
  if ext_from_src(raw_media_path) in VIDEO_EXTENSIONS:
922
  is_actually_video_for_analysis = True
923
 
924
+ client = get_client(key) # This will raise MistralAPIException if key missing, but NOT if lib not installed
925
 
926
  if is_actually_video_for_analysis:
927
  progress(0.25, desc="Running full-video analysis")