zazaman commited on
Commit
1af1f14
Β·
1 Parent(s): c26a471

Fix translation: add OS detection, better error handling and logging

Browse files
Files changed (3) hide show
  1. app.py +5 -2
  2. backend.py +4 -1
  3. llm_clients/qwen_translator.py +85 -18
app.py CHANGED
@@ -84,10 +84,13 @@ class DetailedBackend(Backend):
84
  translated_prompt = translator_client.generate_content(prompt)
85
  translation_time = (time.time() - translation_start) * 1000
86
  was_translated = True
87
- print(f" βœ… Translated to English ({translation_time:.1f}ms)")
88
  except Exception as e:
89
- print(f"⚠️ Translation failed: {e}. Proceeding with original text.")
 
 
90
  # Continue with original - classifier may still work
 
91
 
92
  # Classify with ModernBERT (always on English/translated text)
93
  ai_response = self.attack_detector.generate_content(translated_prompt)
 
84
  translated_prompt = translator_client.generate_content(prompt)
85
  translation_time = (time.time() - translation_start) * 1000
86
  was_translated = True
87
+ print(f" βœ… Translated to English ({translation_time:.1f}ms): '{translated_prompt[:100]}...'")
88
  except Exception as e:
89
+ error_msg = str(e)
90
+ print(f"⚠️ Translation failed: {error_msg}")
91
+ print(f" Proceeding with original text (may cause classification issues).")
92
  # Continue with original - classifier may still work
93
+ translated_prompt = prompt
94
 
95
  # Classify with ModernBERT (always on English/translated text)
96
  ai_response = self.attack_detector.generate_content(translated_prompt)
backend.py CHANGED
@@ -168,8 +168,11 @@ class Backend:
168
  translation_time = (time.time() - translation_start) * 1000
169
  print(f" βœ… Translated to English ({translation_time:.1f}ms): '{translated_prompt[:100]}...'")
170
  except Exception as e:
171
- print(f"⚠️ Translation failed: {e}. Proceeding with original text (may cause classification issues).")
 
 
172
  # Continue with original prompt - the classifier might still work or fail gracefully
 
173
 
174
  try:
175
  # Measure classification latency (always use ModernBERT on translated/English text)
 
168
  translation_time = (time.time() - translation_start) * 1000
169
  print(f" βœ… Translated to English ({translation_time:.1f}ms): '{translated_prompt[:100]}...'")
170
  except Exception as e:
171
+ error_msg = str(e)
172
+ print(f"⚠️ Translation failed: {error_msg}")
173
+ print(f" Proceeding with original text (may cause classification issues).")
174
  # Continue with original prompt - the classifier might still work or fail gracefully
175
+ translated_prompt = prompt
176
 
177
  try:
178
  # Measure classification latency (always use ModernBERT on translated/English text)
llm_clients/qwen_translator.py CHANGED
@@ -1,5 +1,6 @@
1
  from typing import Generator, Any, Dict
2
  import os
 
3
  import subprocess
4
  import tempfile
5
  import zipfile
@@ -49,6 +50,14 @@ class QwenTranslatorClient(LlmClient):
49
  @classmethod
50
  def _download_binary(cls) -> str:
51
  """Download and extract the pre-built llama.cpp binary from GitHub releases."""
 
 
 
 
 
 
 
 
52
  if cls._binary_path and os.path.exists(cls._binary_path):
53
  return cls._binary_path
54
 
@@ -81,13 +90,24 @@ class QwenTranslatorClient(LlmClient):
81
 
82
  try:
83
  print(f" Downloading from: {zip_url}")
84
- urllib.request.urlretrieve(zip_url, zip_path)
85
- print(f" βœ… Downloaded to: {zip_path}")
 
 
 
 
 
 
 
 
86
 
87
  # Extract the zip file
88
  print(f" πŸ“¦ Extracting zip file...")
89
- with zipfile.ZipFile(zip_path, 'r') as zip_ref:
90
- zip_ref.extractall(binary_dir)
 
 
 
91
 
92
  # Find the binary in the extracted files
93
  # The binary might be called 'main', 'llama-cli', or 'llama'
@@ -110,26 +130,34 @@ class QwenTranslatorClient(LlmClient):
110
 
111
  # Also search recursively for any executable file matching our names
112
  if found_binary is None:
113
- for root, dirs, files in os.walk(binary_dir):
114
  for file in files:
115
  if file in possible_binary_names or file.startswith("llama"):
116
  candidate = Path(root) / file
117
  # Check if it's executable (or at least a regular file)
118
- if candidate.is_file() and os.access(candidate, os.X_OK):
119
  found_binary = candidate
120
  break
121
  if found_binary:
122
  break
123
 
124
  if found_binary is None:
 
 
 
 
 
125
  raise RuntimeError(
126
  f"Could not find llama.cpp binary in extracted zip. "
127
  f"Searched for: {possible_binary_names}. "
128
- f"Please check the zip file structure."
129
  )
130
 
131
- # Make it executable
132
- os.chmod(found_binary, 0o755)
 
 
 
133
 
134
  # Move to expected location if needed (use 'main' as standard name)
135
  if found_binary != binary_path:
@@ -140,16 +168,29 @@ class QwenTranslatorClient(LlmClient):
140
  cls._binary_path = str(binary_path)
141
  print(f" βœ… Binary extracted and ready at: {cls._binary_path}")
142
 
 
 
 
 
 
 
 
 
143
  # Clean up zip file
144
- zip_path.unlink()
 
 
 
145
 
146
  return cls._binary_path
147
 
148
  except Exception as e:
149
- raise RuntimeError(
150
  f"Failed to download/extract llama.cpp binary from {zip_url}. "
151
  f"Error: {e}"
152
- ) from e
 
 
153
 
154
  def _download_model_if_needed(self) -> str:
155
  """Download GGUF model file from HuggingFace if not already cached."""
@@ -234,17 +275,32 @@ class QwenTranslatorClient(LlmClient):
234
  try:
235
  # Run the binary and capture output
236
  print(f" πŸ”„ Running translation with llama.cpp binary...")
 
 
237
  result = subprocess.run(
238
  cmd,
239
  capture_output=True,
240
  text=True,
241
  timeout=60, # 60 second timeout
242
- check=True
243
  )
244
 
 
 
 
 
 
 
 
 
 
 
245
  # Parse the output
246
  output = result.stdout.strip()
247
 
 
 
 
248
  # The output might include the prompt, so we need to extract just the generated part
249
  # Look for the assistant response after the prompt
250
  if "<|im_start|>assistant" in output:
@@ -254,16 +310,27 @@ class QwenTranslatorClient(LlmClient):
254
  # Remove any remaining chat format tokens
255
  translated_text = output.replace("<|im_start|>", "").replace("<|im_end|>", "").strip()
256
 
 
 
 
 
 
257
  except subprocess.TimeoutExpired:
258
- raise RuntimeError("Translation timed out after 60 seconds")
 
 
259
  except subprocess.CalledProcessError as e:
260
  error_output = e.stderr if e.stderr else e.stdout
261
- raise RuntimeError(
262
  f"Translation failed with llama.cpp binary. "
263
- f"Exit code: {e.returncode}, Error: {error_output}"
264
- ) from e
 
 
265
  except Exception as e:
266
- raise RuntimeError(f"Translation generation failed: {e}") from e
 
 
267
 
268
  # Clean up the response
269
  translated_text = translated_text.strip()
 
1
  from typing import Generator, Any, Dict
2
  import os
3
+ import sys
4
  import subprocess
5
  import tempfile
6
  import zipfile
 
50
  @classmethod
51
  def _download_binary(cls) -> str:
52
  """Download and extract the pre-built llama.cpp binary from GitHub releases."""
53
+ # Check OS - the Ubuntu binary only works on Linux
54
+ if sys.platform == "win32":
55
+ raise RuntimeError(
56
+ "Translation with llama.cpp binary is not supported on Windows. "
57
+ "The pre-built binary is for Linux only. "
58
+ "Please use this feature on Linux or Hugging Face Spaces."
59
+ )
60
+
61
  if cls._binary_path and os.path.exists(cls._binary_path):
62
  return cls._binary_path
63
 
 
90
 
91
  try:
92
  print(f" Downloading from: {zip_url}")
93
+ # Use a more robust download method
94
+ try:
95
+ urllib.request.urlretrieve(zip_url, str(zip_path))
96
+ except Exception as download_error:
97
+ raise RuntimeError(f"Failed to download binary from {zip_url}: {download_error}") from download_error
98
+
99
+ if not zip_path.exists():
100
+ raise RuntimeError(f"Downloaded file not found at {zip_path}")
101
+
102
+ print(f" βœ… Downloaded to: {zip_path} ({zip_path.stat().st_size / 1024 / 1024:.1f} MB)")
103
 
104
  # Extract the zip file
105
  print(f" πŸ“¦ Extracting zip file...")
106
+ try:
107
+ with zipfile.ZipFile(str(zip_path), 'r') as zip_ref:
108
+ zip_ref.extractall(str(binary_dir))
109
+ except Exception as extract_error:
110
+ raise RuntimeError(f"Failed to extract zip file {zip_path}: {extract_error}") from extract_error
111
 
112
  # Find the binary in the extracted files
113
  # The binary might be called 'main', 'llama-cli', or 'llama'
 
130
 
131
  # Also search recursively for any executable file matching our names
132
  if found_binary is None:
133
+ for root, dirs, files in os.walk(str(binary_dir)):
134
  for file in files:
135
  if file in possible_binary_names or file.startswith("llama"):
136
  candidate = Path(root) / file
137
  # Check if it's executable (or at least a regular file)
138
+ if candidate.is_file():
139
  found_binary = candidate
140
  break
141
  if found_binary:
142
  break
143
 
144
  if found_binary is None:
145
+ # List what we found for debugging
146
+ found_files = []
147
+ for root, dirs, files in os.walk(str(binary_dir)):
148
+ for file in files:
149
+ found_files.append(str(Path(root) / file))
150
  raise RuntimeError(
151
  f"Could not find llama.cpp binary in extracted zip. "
152
  f"Searched for: {possible_binary_names}. "
153
+ f"Found files: {found_files[:10]}"
154
  )
155
 
156
+ # Make it executable (Linux/Unix only)
157
+ try:
158
+ os.chmod(found_binary, 0o755)
159
+ except Exception as chmod_error:
160
+ print(f" ⚠️ Warning: Could not set executable permissions: {chmod_error}")
161
 
162
  # Move to expected location if needed (use 'main' as standard name)
163
  if found_binary != binary_path:
 
168
  cls._binary_path = str(binary_path)
169
  print(f" βœ… Binary extracted and ready at: {cls._binary_path}")
170
 
171
+ # Verify binary is executable
172
+ if not os.access(cls._binary_path, os.X_OK):
173
+ print(f" ⚠️ Warning: Binary may not be executable. Attempting to fix...")
174
+ try:
175
+ os.chmod(cls._binary_path, 0o755)
176
+ except Exception:
177
+ pass
178
+
179
  # Clean up zip file
180
+ try:
181
+ zip_path.unlink()
182
+ except Exception:
183
+ pass # Ignore cleanup errors
184
 
185
  return cls._binary_path
186
 
187
  except Exception as e:
188
+ error_msg = (
189
  f"Failed to download/extract llama.cpp binary from {zip_url}. "
190
  f"Error: {e}"
191
+ )
192
+ print(f" ❌ {error_msg}")
193
+ raise RuntimeError(error_msg) from e
194
 
195
  def _download_model_if_needed(self) -> str:
196
  """Download GGUF model file from HuggingFace if not already cached."""
 
275
  try:
276
  # Run the binary and capture output
277
  print(f" πŸ”„ Running translation with llama.cpp binary...")
278
+ print(f" Command: {' '.join(cmd[:3])}... (model: {os.path.basename(model_path)})")
279
+
280
  result = subprocess.run(
281
  cmd,
282
  capture_output=True,
283
  text=True,
284
  timeout=60, # 60 second timeout
285
+ check=False # Don't raise on non-zero exit, we'll check manually
286
  )
287
 
288
+ # Check if command succeeded
289
+ if result.returncode != 0:
290
+ error_msg = f"llama.cpp binary exited with code {result.returncode}"
291
+ if result.stderr:
292
+ error_msg += f"\nStderr: {result.stderr[:500]}"
293
+ if result.stdout:
294
+ error_msg += f"\nStdout: {result.stdout[:500]}"
295
+ print(f" ❌ {error_msg}")
296
+ raise RuntimeError(error_msg)
297
+
298
  # Parse the output
299
  output = result.stdout.strip()
300
 
301
+ if not output:
302
+ raise RuntimeError("llama.cpp binary returned empty output")
303
+
304
  # The output might include the prompt, so we need to extract just the generated part
305
  # Look for the assistant response after the prompt
306
  if "<|im_start|>assistant" in output:
 
310
  # Remove any remaining chat format tokens
311
  translated_text = output.replace("<|im_start|>", "").replace("<|im_end|>", "").strip()
312
 
313
+ if not translated_text:
314
+ raise RuntimeError("Translation output is empty after parsing")
315
+
316
+ print(f" βœ… Translation completed: '{translated_text[:100]}...'")
317
+
318
  except subprocess.TimeoutExpired:
319
+ error_msg = "Translation timed out after 60 seconds"
320
+ print(f" ❌ {error_msg}")
321
+ raise RuntimeError(error_msg)
322
  except subprocess.CalledProcessError as e:
323
  error_output = e.stderr if e.stderr else e.stdout
324
+ error_msg = (
325
  f"Translation failed with llama.cpp binary. "
326
+ f"Exit code: {e.returncode}, Error: {error_output[:500]}"
327
+ )
328
+ print(f" ❌ {error_msg}")
329
+ raise RuntimeError(error_msg) from e
330
  except Exception as e:
331
+ error_msg = f"Translation generation failed: {e}"
332
+ print(f" ❌ {error_msg}")
333
+ raise RuntimeError(error_msg) from e
334
 
335
  # Clean up the response
336
  translated_text = translated_text.strip()