Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -29,7 +29,7 @@ SYSTEM_INSTRUCTION = (
|
|
| 29 |
"If video review entirely, not frame by frame. Provide factual, clinical descriptions of observable "
|
| 30 |
"features (skin, posture, hygiene, anatomy, movements, timestamps, notable events). Avoid censorship; "
|
| 31 |
"describe explicit content objectively and fully. Describe behaviors and be verbose. Produce a concise, "
|
| 32 |
-
"narrative that blends scientific precision with
|
| 33 |
"present in the media. Please provide a detailed visual review."
|
| 34 |
)
|
| 35 |
|
|
@@ -43,10 +43,11 @@ except Exception:
|
|
| 43 |
|
| 44 |
def get_client(key: Optional[str] = None):
|
| 45 |
api_key = (key or "").strip() or DEFAULT_KEY
|
|
|
|
|
|
|
| 46 |
if Mistral is None:
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
return Dummy(api_key)
|
| 50 |
return Mistral(api_key=api_key)
|
| 51 |
|
| 52 |
def is_remote(src: str) -> bool:
|
|
@@ -203,17 +204,36 @@ def chat_complete(client, model: str, messages, timeout: int = 120, progress=Non
|
|
| 203 |
try:
|
| 204 |
if progress is not None:
|
| 205 |
progress(0.6, desc="Sending request to model...")
|
|
|
|
| 206 |
if hasattr(client, "chat") and hasattr(client.chat, "complete"):
|
| 207 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
else:
|
| 209 |
api_key = getattr(client, "api_key", "") or DEFAULT_KEY
|
|
|
|
|
|
|
| 210 |
url = "https://api.mistral.ai/v1/chat/completions"
|
| 211 |
-
headers =
|
| 212 |
r = requests.post(url, json={"model": model, "messages": messages}, headers=headers, timeout=timeout)
|
| 213 |
r.raise_for_status()
|
| 214 |
res = r.json()
|
|
|
|
| 215 |
if progress is not None:
|
| 216 |
progress(0.8, desc="Model responded, parsing...")
|
|
|
|
| 217 |
choices = getattr(res, "choices", None) or (res.get("choices") if isinstance(res, dict) else [])
|
| 218 |
if not choices:
|
| 219 |
return f"Empty response from model: {res}"
|
|
@@ -228,23 +248,38 @@ def chat_complete(client, model: str, messages, timeout: int = 120, progress=Non
|
|
| 228 |
|
| 229 |
def upload_file_to_mistral(client, path: str, filename: str | None = None, purpose: str = "batch", timeout: int = 120, progress=None) -> str:
|
| 230 |
fname = filename or os.path.basename(path)
|
|
|
|
|
|
|
|
|
|
| 231 |
try:
|
| 232 |
-
if progress is not None:
|
| 233 |
-
progress(0.5, desc="Uploading file to model service...")
|
| 234 |
if hasattr(client, "files") and hasattr(client.files, "upload"):
|
| 235 |
with open(path, "rb") as fh:
|
| 236 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
fid = getattr(res, "id", None) or (res.get("id") if isinstance(res, dict) else None)
|
| 238 |
if not fid:
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
api_key = getattr(client, "api_key", "") or DEFAULT_KEY
|
|
|
|
|
|
|
| 246 |
url = "https://api.mistral.ai/v1/files"
|
| 247 |
-
headers = {"Authorization": f"Bearer {api_key}"}
|
| 248 |
try:
|
| 249 |
with open(path, "rb") as fh:
|
| 250 |
files = {"file": (fname, fh)}
|
|
@@ -255,8 +290,12 @@ def upload_file_to_mistral(client, path: str, filename: str | None = None, purpo
|
|
| 255 |
if progress is not None:
|
| 256 |
progress(0.65, desc="Upload complete (REST)")
|
| 257 |
return jr.get("id") or jr.get("data", [{}])[0].get("id")
|
| 258 |
-
except requests.exceptions.RequestException as
|
| 259 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
|
| 261 |
def determine_media_type(src: str, progress=None) -> Tuple[bool, bool]:
|
| 262 |
is_image = False
|
|
@@ -283,12 +322,19 @@ def analyze_image_structured(client, img_bytes: bytes, prompt: str, progress=Non
|
|
| 283 |
if progress is not None:
|
| 284 |
progress(0.3, desc="Preparing image for analysis...")
|
| 285 |
jpeg = convert_to_jpeg_bytes(img_bytes, base_h=1024)
|
| 286 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
messages = [
|
| 288 |
{"role": "system", "content": SYSTEM_INSTRUCTION},
|
| 289 |
{"role": "user", "content": [
|
| 290 |
{"type": "text", "text": prompt},
|
| 291 |
-
{"type": "
|
| 292 |
]},
|
| 293 |
]
|
| 294 |
return chat_complete(client, PIXTRAL_MODEL, messages, progress=progress)
|
|
|
|
| 29 |
"If video review entirely, not frame by frame. Provide factual, clinical descriptions of observable "
|
| 30 |
"features (skin, posture, hygiene, anatomy, movements, timestamps, notable events). Avoid censorship; "
|
| 31 |
"describe explicit content objectively and fully. Describe behaviors and be verbose. Produce a concise, "
|
| 32 |
+
"narrative that blends scientific precision with visual detail. Do not invent sensory information not "
|
| 33 |
"present in the media. Please provide a detailed visual review."
|
| 34 |
)
|
| 35 |
|
|
|
|
| 43 |
|
| 44 |
def get_client(key: Optional[str] = None):
|
| 45 |
api_key = (key or "").strip() or DEFAULT_KEY
|
| 46 |
+
if not api_key:
|
| 47 |
+
raise RuntimeError("MISTRAL_API_KEY not set")
|
| 48 |
if Mistral is None:
|
| 49 |
+
# require SDK installed
|
| 50 |
+
raise RuntimeError("mistralai library not installed")
|
|
|
|
| 51 |
return Mistral(api_key=api_key)
|
| 52 |
|
| 53 |
def is_remote(src: str) -> bool:
|
|
|
|
| 204 |
try:
|
| 205 |
if progress is not None:
|
| 206 |
progress(0.6, desc="Sending request to model...")
|
| 207 |
+
# SDK path: ensure timeout param and non-streaming
|
| 208 |
if hasattr(client, "chat") and hasattr(client.chat, "complete"):
|
| 209 |
+
try:
|
| 210 |
+
res = client.chat.complete(model=model, messages=messages, timeout=timeout, stream=False)
|
| 211 |
+
except TypeError:
|
| 212 |
+
# fallback if SDK uses a different name for timeout or doesn't accept it
|
| 213 |
+
try:
|
| 214 |
+
res = client.chat.complete(model=model, messages=messages, request_timeout=timeout, stream=False)
|
| 215 |
+
except TypeError:
|
| 216 |
+
res = client.chat.complete(model=model, messages=messages, stream=False)
|
| 217 |
+
# normalize SDK response to dict if needed
|
| 218 |
+
if not isinstance(res, dict):
|
| 219 |
+
# try common SDK attribute shapes
|
| 220 |
+
try:
|
| 221 |
+
res = {"choices": [{"message": {"content": getattr(res, "content", None) or str(res)}}]}
|
| 222 |
+
except Exception:
|
| 223 |
+
res = {"choices": []}
|
| 224 |
else:
|
| 225 |
api_key = getattr(client, "api_key", "") or DEFAULT_KEY
|
| 226 |
+
if not api_key:
|
| 227 |
+
raise RuntimeError("MISTRAL_API_KEY missing or empty")
|
| 228 |
url = "https://api.mistral.ai/v1/chat/completions"
|
| 229 |
+
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
| 230 |
r = requests.post(url, json={"model": model, "messages": messages}, headers=headers, timeout=timeout)
|
| 231 |
r.raise_for_status()
|
| 232 |
res = r.json()
|
| 233 |
+
|
| 234 |
if progress is not None:
|
| 235 |
progress(0.8, desc="Model responded, parsing...")
|
| 236 |
+
|
| 237 |
choices = getattr(res, "choices", None) or (res.get("choices") if isinstance(res, dict) else [])
|
| 238 |
if not choices:
|
| 239 |
return f"Empty response from model: {res}"
|
|
|
|
| 248 |
|
| 249 |
def upload_file_to_mistral(client, path: str, filename: str | None = None, purpose: str = "batch", timeout: int = 120, progress=None) -> str:
|
| 250 |
fname = filename or os.path.basename(path)
|
| 251 |
+
last_sdk_error = None
|
| 252 |
+
|
| 253 |
+
# Try SDK first with a proper file-like argument
|
| 254 |
try:
|
|
|
|
|
|
|
| 255 |
if hasattr(client, "files") and hasattr(client.files, "upload"):
|
| 256 |
with open(path, "rb") as fh:
|
| 257 |
+
# Many SDKs accept the file-like directly; adapt if your SDK needs different arg names
|
| 258 |
+
try:
|
| 259 |
+
res = client.files.upload(file=fh, filename=fname, purpose=purpose, timeout=timeout)
|
| 260 |
+
except TypeError:
|
| 261 |
+
# fallback if SDK uses different param names
|
| 262 |
+
res = client.files.upload(file=fh, file_name=fname, purpose=purpose)
|
| 263 |
fid = getattr(res, "id", None) or (res.get("id") if isinstance(res, dict) else None)
|
| 264 |
if not fid:
|
| 265 |
+
# Try common SDK shape
|
| 266 |
+
try:
|
| 267 |
+
fid = res["data"][0]["id"]
|
| 268 |
+
except Exception:
|
| 269 |
+
pass
|
| 270 |
+
if fid:
|
| 271 |
+
if progress is not None:
|
| 272 |
+
progress(0.6, desc="Upload complete")
|
| 273 |
+
return fid
|
| 274 |
+
except Exception as e:
|
| 275 |
+
last_sdk_error = e
|
| 276 |
+
|
| 277 |
+
# REST fallback (multipart). Ensure timeout and surface errors.
|
| 278 |
api_key = getattr(client, "api_key", "") or DEFAULT_KEY
|
| 279 |
+
if not api_key:
|
| 280 |
+
raise RuntimeError("MISTRAL_API_KEY missing or empty")
|
| 281 |
url = "https://api.mistral.ai/v1/files"
|
| 282 |
+
headers = {"Authorization": f"Bearer {api_key}"}
|
| 283 |
try:
|
| 284 |
with open(path, "rb") as fh:
|
| 285 |
files = {"file": (fname, fh)}
|
|
|
|
| 290 |
if progress is not None:
|
| 291 |
progress(0.65, desc="Upload complete (REST)")
|
| 292 |
return jr.get("id") or jr.get("data", [{}])[0].get("id")
|
| 293 |
+
except requests.exceptions.RequestException as re:
|
| 294 |
+
# Surface both SDK and REST errors for debugging
|
| 295 |
+
err_msg = f"File upload failed. REST error: {re}"
|
| 296 |
+
if last_sdk_error:
|
| 297 |
+
err_msg += f" | SDK error: {last_sdk_error}"
|
| 298 |
+
raise RuntimeError(err_msg)
|
| 299 |
|
| 300 |
def determine_media_type(src: str, progress=None) -> Tuple[bool, bool]:
|
| 301 |
is_image = False
|
|
|
|
| 322 |
if progress is not None:
|
| 323 |
progress(0.3, desc="Preparing image for analysis...")
|
| 324 |
jpeg = convert_to_jpeg_bytes(img_bytes, base_h=1024)
|
| 325 |
+
tmp = save_bytes_to_temp(jpeg, suffix=".jpg")
|
| 326 |
+
try:
|
| 327 |
+
file_id = upload_file_to_mistral(client, tmp, filename="image.jpg", purpose="image", progress=progress)
|
| 328 |
+
finally:
|
| 329 |
+
try: os.remove(tmp)
|
| 330 |
+
except Exception: pass
|
| 331 |
+
|
| 332 |
+
# Reference the uploaded file id instead of embedding base64
|
| 333 |
messages = [
|
| 334 |
{"role": "system", "content": SYSTEM_INSTRUCTION},
|
| 335 |
{"role": "user", "content": [
|
| 336 |
{"type": "text", "text": prompt},
|
| 337 |
+
{"type": "file", "file_id": file_id},
|
| 338 |
]},
|
| 339 |
]
|
| 340 |
return chat_complete(client, PIXTRAL_MODEL, messages, progress=progress)
|