Paul commited on
Commit
136f619
·
1 Parent(s): dad418e
app.py CHANGED
@@ -169,6 +169,8 @@ def run_full_pipeline(conversation: str, wingman_prompt: str = "", gemini_model_
169
  }
170
 
171
  # Model 5 – Google Gemini API
 
 
172
  try:
173
  gemini_service = get_gemini_service(model_name=gemini_model_name)
174
  # Format conversation for Gemini: "Male: ... ||| Female: ..."
@@ -180,8 +182,25 @@ def run_full_pipeline(conversation: str, wingman_prompt: str = "", gemini_model_
180
  )
181
  gemini_error = ""
182
  except Exception as exc:
183
- gemini_reply = ""
184
- gemini_error = str(exc)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
 
186
  models_output["gemini"] = {
187
  "label": f"Model 5 – Gemini API ({gemini_model_name})",
@@ -279,22 +298,39 @@ with gr.Blocks(title=title) as demo:
279
  )
280
  gr.Markdown("Leave as-is for default behavior. Edits apply to Model 3 when its LoRA is used.")
281
 
282
- # Model 5 – Gemini Model Selection
283
  try:
284
- gemini_models = get_available_gemini_models()
285
- gemini_model_choices = [model["name"] for model in gemini_models]
286
- gemini_model_display = [f"{model['displayName']} ({model['name']})" for model in gemini_models]
287
- default_gemini_model = gemini_model_choices[0] if gemini_model_choices else "gemini-2.5-flash"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  except Exception as e:
289
- gemini_model_choices = ["gemini-2.5-flash"]
290
- gemini_model_display = ["Gemini 2.5 Flash (default - API key may be missing)"]
291
- default_gemini_model = "gemini-2.5-flash"
 
292
 
293
  gemini_model_dropdown = gr.Dropdown(
294
- choices=gemini_model_choices,
295
  value=default_gemini_model,
296
  label="Model 5 – Select Gemini Model",
297
- info="Choose which Gemini model to use for reply generation",
 
298
  )
299
 
300
  reply_btn = gr.Button("Generate Reply Suggestion", variant="primary", size="lg")
 
169
  }
170
 
171
  # Model 5 – Google Gemini API
172
+ gemini_reply = ""
173
+ gemini_error = ""
174
  try:
175
  gemini_service = get_gemini_service(model_name=gemini_model_name)
176
  # Format conversation for Gemini: "Male: ... ||| Female: ..."
 
182
  )
183
  gemini_error = ""
184
  except Exception as exc:
185
+ error_msg = str(exc)
186
+ # Try fallback to gemini-2.0-flash if current model fails (especially for MAX_TOKENS with 0 parts)
187
+ fallback_model = "gemini-2.0-flash"
188
+ if gemini_model_name != fallback_model and ("MAX_TOKENS" in error_msg or "quota" in error_msg.lower() or "429" in error_msg or "no text" in error_msg.lower()):
189
+ try:
190
+ gemini_service = get_gemini_service(model_name=fallback_model)
191
+ formatted_conversation = f"Male: {male} ||| Female: {female}"
192
+ gemini_reply = gemini_service.generate_reply(
193
+ conversation=formatted_conversation,
194
+ trigger=trigger,
195
+ move=move,
196
+ )
197
+ gemini_error = f"⚠️ Original model ({gemini_model_name}) failed. Used fallback: {fallback_model}"
198
+ except Exception as fallback_exc:
199
+ gemini_reply = ""
200
+ gemini_error = f"Model {gemini_model_name} failed: {error_msg[:150]}. Fallback ({fallback_model}) also failed: {str(fallback_exc)[:150]}"
201
+ else:
202
+ gemini_reply = ""
203
+ gemini_error = error_msg
204
 
205
  models_output["gemini"] = {
206
  "label": f"Model 5 – Gemini API ({gemini_model_name})",
 
298
  )
299
  gr.Markdown("Leave as-is for default behavior. Edits apply to Model 3 when its LoRA is used.")
300
 
301
+ # Model 5 – Gemini Model Selection (using whitelist - 15 tested models)
302
  try:
303
+ gemini_models = get_available_gemini_models(use_whitelist=True)
304
+ # Create choices with (label, value) format for better display
305
+ gemini_dropdown_choices = []
306
+ gemini_model_choices = [] # Keep for value matching
307
+
308
+ for model in gemini_models:
309
+ model_name = model["name"]
310
+ display_name = model.get("displayName", model_name)
311
+ # Remove "models/" prefix for cleaner display
312
+ clean_name = model_name.replace("models/", "")
313
+ # Format: (display_label, actual_value)
314
+ label = f"{display_name} ({clean_name})"
315
+ gemini_dropdown_choices.append((label, model_name))
316
+ gemini_model_choices.append(model_name)
317
+
318
+ # Default to gemini-2.0-flash (first in whitelist) or first available
319
+ default_gemini_model = "models/gemini-2.0-flash" if "models/gemini-2.0-flash" in gemini_model_choices else (gemini_model_choices[0] if gemini_model_choices else "models/gemini-2.0-flash")
320
+
321
+ print(f"✓ Loaded {len(gemini_model_choices)} whitelisted Gemini models for dropdown")
322
  except Exception as e:
323
+ print(f"⚠ Error loading Gemini whitelist: {e}")
324
+ gemini_dropdown_choices = [("Gemini 2.0 Flash (default - API key may be missing)", "models/gemini-2.0-flash")]
325
+ gemini_model_choices = ["models/gemini-2.0-flash"]
326
+ default_gemini_model = "models/gemini-2.0-flash"
327
 
328
  gemini_model_dropdown = gr.Dropdown(
329
+ choices=gemini_dropdown_choices,
330
  value=default_gemini_model,
331
  label="Model 5 – Select Gemini Model",
332
+ info=f"Choose from {len(gemini_model_choices)} tested and working Gemini models (whitelist)",
333
+ interactive=True,
334
  )
335
 
336
  reply_btn = gr.Button("Generate Reply Suggestion", variant="primary", size="lg")
gemini_models_whitelist.json ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "whitelist": [
3
+ {
4
+ "name": "models/gemini-2.0-flash",
5
+ "displayName": "Gemini 2.0 Flash",
6
+ "description": "Gemini 2.0 Flash",
7
+ "version": "2.0",
8
+ "test_reply": "Mai anh rảnh đó, hay mình đổi gió đi đâu vui vui em nhỉ."
9
+ },
10
+ {
11
+ "name": "models/gemini-2.0-flash-001",
12
+ "displayName": "Gemini 2.0 Flash 001",
13
+ "description": "Stable version of Gemini 2.0 Flash, our fast and versatile multimodal model for scaling across diverse tasks, released in January of 2025.",
14
+ "version": "2.0",
15
+ "test_reply": "Mai anh rảnh đó, hay mình đổi gió đi đâu vui vui em nhỉ."
16
+ },
17
+ {
18
+ "name": "models/gemini-2.0-flash-lite-001",
19
+ "displayName": "Gemini 2.0 Flash-Lite 001",
20
+ "description": "Stable version of Gemini 2.0 Flash-Lite",
21
+ "version": "2.0",
22
+ "test_reply": "Mai anh rảnh, mình đi xem phim được không em."
23
+ },
24
+ {
25
+ "name": "models/gemini-2.0-flash-lite",
26
+ "displayName": "Gemini 2.0 Flash-Lite",
27
+ "description": "Gemini 2.0 Flash-Lite",
28
+ "version": "2.0",
29
+ "test_reply": "Mai anh rảnh, mình đi xem phim được không em."
30
+ },
31
+ {
32
+ "name": "models/gemini-2.0-flash-lite-preview-02-05",
33
+ "displayName": "Gemini 2.0 Flash-Lite Preview 02-05",
34
+ "description": "Preview release (February 5th, 2025) of Gemini 2.0 Flash-Lite",
35
+ "version": "preview-02-05",
36
+ "test_reply": "Mai anh rảnh, mình đi xem phim được không em."
37
+ },
38
+ {
39
+ "name": "models/gemini-2.0-flash-lite-preview",
40
+ "displayName": "Gemini 2.0 Flash-Lite Preview",
41
+ "description": "Preview release (February 5th, 2025) of Gemini 2.0 Flash-Lite",
42
+ "version": "preview-02-05",
43
+ "test_reply": "Mai anh rảnh, mình đi xem phim được không em."
44
+ },
45
+ {
46
+ "name": "models/gemma-3-1b-it",
47
+ "displayName": "Gemma 3 1B",
48
+ "description": "",
49
+ "version": "001",
50
+ "test_reply": "Anh cũng vậy, mong được gặp mặt em nhé."
51
+ },
52
+ {
53
+ "name": "models/gemma-3-4b-it",
54
+ "displayName": "Gemma 3 4B",
55
+ "description": "",
56
+ "version": "001",
57
+ "test_reply": "Mai thì em cứ hẹn anh nha, anh rảnh lắm đó ạ."
58
+ },
59
+ {
60
+ "name": "models/gemma-3-12b-it",
61
+ "displayName": "Gemma 3 12B",
62
+ "description": "",
63
+ "version": "001",
64
+ "test_reply": "Mai anh rảnh, em bảo anh đưa em đi đâu chơi nhé."
65
+ },
66
+ {
67
+ "name": "models/gemma-3-27b-it",
68
+ "displayName": "Gemma 3 27B",
69
+ "description": "",
70
+ "version": "001",
71
+ "test_reply": "Mai em rảnh thật ạ."
72
+ },
73
+ {
74
+ "name": "models/gemma-3n-e4b-it",
75
+ "displayName": "Gemma 3n E4B",
76
+ "description": "",
77
+ "version": "001",
78
+ "test_reply": "Dạ được em, mai anh rảnh nhé."
79
+ },
80
+ {
81
+ "name": "models/gemma-3n-e2b-it",
82
+ "displayName": "Gemma 3n E2B",
83
+ "description": "",
84
+ "version": "001",
85
+ "test_reply": "Mai em nhé, anh sẽ cố gắng sắp xếp ạ."
86
+ },
87
+ {
88
+ "name": "models/gemini-flash-lite-latest",
89
+ "displayName": "Gemini Flash-Lite Latest",
90
+ "description": "Latest release of Gemini Flash-Lite",
91
+ "version": "Gemini Flash-Lite Latest",
92
+ "test_reply": "Mai anh rảnh, mình đi đâu em thích nhé."
93
+ },
94
+ {
95
+ "name": "models/gemini-2.5-flash-lite",
96
+ "displayName": "Gemini 2.5 Flash-Lite",
97
+ "description": "Stable version of Gemini 2.5 Flash-Lite, released in July of 2025",
98
+ "version": "001",
99
+ "test_reply": "Mai anh rảnh, mình đi cà phê nha em."
100
+ },
101
+ {
102
+ "name": "models/gemini-2.5-flash-lite-preview-09-2025",
103
+ "displayName": "Gemini 2.5 Flash-Lite Preview Sep 2025",
104
+ "description": "Preview release (Septempber 25th, 2025) of Gemini 2.5 Flash-Lite",
105
+ "version": "2.5-preview-09-25",
106
+ "test_reply": "Mai anh rảnh, mình đi cà phê nhé em."
107
+ }
108
+ ],
109
+ "failed": 26,
110
+ "test_data": {
111
+ "conversation": "Male: Tối nay anh có lịch đột xuất. ||| Female: Thế mai được không?",
112
+ "trigger": "neutral",
113
+ "move": "escalate"
114
+ },
115
+ "total_tested": 41,
116
+ "passed": 15
117
+ }
gemini_service.py CHANGED
@@ -2,7 +2,9 @@
2
  Service for generating replies using Google Gemini API.
3
  """
4
  import os
 
5
  import requests
 
6
  from typing import Optional, List, Dict, Any
7
  import google.generativeai as genai
8
 
@@ -111,7 +113,7 @@ def fetch_gemini_models(api_key: Optional[str] = None) -> List[Dict[str, Any]]:
111
  class GeminiReplyService:
112
  """Service for generating replies using Google Gemini API."""
113
 
114
- def __init__(self, api_key: Optional[str] = None, model_name: str = "gemini-2.5-flash"):
115
  """
116
  Initialize Gemini service.
117
 
@@ -145,7 +147,7 @@ class GeminiReplyService:
145
  trigger: str,
146
  move: str,
147
  temperature: float = 0.2,
148
- max_output_tokens: int = 80,
149
  ) -> str:
150
  """
151
  Generate reply using Google Gemini API.
@@ -177,9 +179,65 @@ MOVE: "{move}"
177
  temperature=temperature,
178
  max_output_tokens=max_output_tokens,
179
  ),
 
 
 
 
 
 
180
  )
181
 
182
- raw = response.text.strip() if response.text else ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
 
184
  # Hậu xử lý: lấy câu đầu, giới hạn 25 từ
185
  import re
@@ -198,7 +256,14 @@ MOVE: "{move}"
198
  return limited
199
 
200
  except Exception as e:
201
- raise Exception(f"Gemini API error: {str(e)}")
 
 
 
 
 
 
 
202
 
203
 
204
  # Global singleton instance
@@ -208,7 +273,7 @@ _cached_models = None
208
 
209
  def get_gemini_service(
210
  api_key: Optional[str] = None,
211
- model_name: str = "gemini-2.5-flash",
212
  ) -> GeminiReplyService:
213
  """Get or create the global Gemini service instance."""
214
  global _gemini_service
@@ -219,18 +284,41 @@ def get_gemini_service(
219
  return _gemini_service
220
 
221
 
222
- def get_available_gemini_models(api_key: Optional[str] = None, use_cache: bool = True) -> List[Dict[str, Any]]:
223
  """
224
  Get list of available Gemini models that support generateContent.
 
225
 
226
  Args:
227
  api_key: Google API key. If None, will try to get from GOOGLE_API_KEY env var.
228
  use_cache: Whether to use cached models list (default: True)
 
229
 
230
  Returns:
231
- List of model dictionaries
232
  """
233
  global _cached_models
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  if use_cache and _cached_models is not None:
235
  return _cached_models
236
 
 
2
  Service for generating replies using Google Gemini API.
3
  """
4
  import os
5
+ import json
6
  import requests
7
+ from pathlib import Path
8
  from typing import Optional, List, Dict, Any
9
  import google.generativeai as genai
10
 
 
113
  class GeminiReplyService:
114
  """Service for generating replies using Google Gemini API."""
115
 
116
+ def __init__(self, api_key: Optional[str] = None, model_name: str = "gemini-2.0-flash"):
117
  """
118
  Initialize Gemini service.
119
 
 
147
  trigger: str,
148
  move: str,
149
  temperature: float = 0.2,
150
+ max_output_tokens: int = 500, # Increased significantly to avoid truncation issues
151
  ) -> str:
152
  """
153
  Generate reply using Google Gemini API.
 
179
  temperature=temperature,
180
  max_output_tokens=max_output_tokens,
181
  ),
182
+ safety_settings=[
183
+ {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
184
+ {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"},
185
+ {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_NONE"},
186
+ {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"},
187
+ ],
188
  )
189
 
190
+ # Check if response has text
191
+ try:
192
+ raw = response.text.strip()
193
+ except Exception as text_error:
194
+ # Check finish reason if text access fails
195
+ if response.candidates and len(response.candidates) > 0:
196
+ candidate = response.candidates[0]
197
+ finish_reason = candidate.finish_reason
198
+
199
+ # Map finish_reason codes to names
200
+ finish_reason_map = {
201
+ 0: "FINISH_REASON_UNSPECIFIED",
202
+ 1: "STOP",
203
+ 2: "MAX_TOKENS",
204
+ 3: "SAFETY",
205
+ 4: "RECITATION",
206
+ 5: "OTHER"
207
+ }
208
+ finish_reason_name = finish_reason_map.get(finish_reason, f"UNKNOWN({finish_reason})")
209
+
210
+ if finish_reason == 2: # MAX_TOKENS - response was truncated
211
+ # Try to get partial text if available
212
+ try:
213
+ if hasattr(candidate, 'content') and candidate.content:
214
+ if hasattr(candidate.content, 'parts') and candidate.content.parts:
215
+ parts_text = []
216
+ for part in candidate.content.parts:
217
+ if hasattr(part, 'text') and part.text:
218
+ parts_text.append(part.text)
219
+ if parts_text:
220
+ raw = " ".join(parts_text).strip()
221
+ # Continue with processing below
222
+ else:
223
+ raise Exception(f"Response truncated due to max_tokens limit (finish_reason: {finish_reason_name}). No text parts found. Try increasing max_output_tokens.")
224
+ else:
225
+ raise Exception(f"Response truncated due to max_tokens limit (finish_reason: {finish_reason_name}). No content parts. Try increasing max_output_tokens.")
226
+ else:
227
+ raise Exception(f"Response truncated due to max_tokens limit (finish_reason: {finish_reason_name}). No candidate content. Try increasing max_output_tokens.")
228
+ except Exception as parts_error:
229
+ # If we couldn't extract partial text, raise the original error
230
+ raise Exception(f"Response truncated due to max_tokens limit (finish_reason: {finish_reason_name}). Could not extract partial text: {str(parts_error)}. Try increasing max_output_tokens.")
231
+ else:
232
+ raise Exception(f"Response truncated due to max_tokens limit (finish_reason: {finish_reason_name}). Try increasing max_output_tokens.")
233
+ elif finish_reason == 3: # SAFETY
234
+ raise Exception(f"Response blocked by safety filter (finish_reason: {finish_reason_name}). The content may have triggered safety filters. Try adjusting the prompt or using a different model.")
235
+ elif finish_reason == 4: # RECITATION
236
+ raise Exception(f"Response blocked due to recitation policy (finish_reason: {finish_reason_name}).")
237
+ else:
238
+ raise Exception(f"Response has no text. Finish reason: {finish_reason_name} ({finish_reason}). Error: {str(text_error)}")
239
+ else:
240
+ raise Exception(f"Response has no candidates. Error: {str(text_error)}")
241
 
242
  # Hậu xử lý: lấy câu đầu, giới hạn 25 từ
243
  import re
 
256
  return limited
257
 
258
  except Exception as e:
259
+ error_msg = str(e)
260
+ # Check for quota errors and provide clearer message
261
+ if "429" in error_msg or "quota" in error_msg.lower() or "Quota exceeded" in error_msg:
262
+ raise Exception(
263
+ f"Gemini API quota exceeded. Please check your billing/plan at https://ai.google.dev/gemini-api/docs/rate-limits. "
264
+ f"Original error: {error_msg[:200]}"
265
+ )
266
+ raise Exception(f"Gemini API error: {error_msg}")
267
 
268
 
269
  # Global singleton instance
 
273
 
274
  def get_gemini_service(
275
  api_key: Optional[str] = None,
276
+ model_name: str = "gemini-2.0-flash",
277
  ) -> GeminiReplyService:
278
  """Get or create the global Gemini service instance."""
279
  global _gemini_service
 
284
  return _gemini_service
285
 
286
 
287
+ def get_available_gemini_models(api_key: Optional[str] = None, use_cache: bool = True, use_whitelist: bool = True) -> List[Dict[str, Any]]:
288
  """
289
  Get list of available Gemini models that support generateContent.
290
+ By default, only returns models from whitelist (tested and working models).
291
 
292
  Args:
293
  api_key: Google API key. If None, will try to get from GOOGLE_API_KEY env var.
294
  use_cache: Whether to use cached models list (default: True)
295
+ use_whitelist: Whether to filter by whitelist (default: True)
296
 
297
  Returns:
298
+ List of model dictionaries (whitelisted models only if use_whitelist=True)
299
  """
300
  global _cached_models
301
+
302
+ # Load whitelist if requested
303
+ if use_whitelist:
304
+ whitelist_path = Path(__file__).parent / "gemini_models_whitelist.json"
305
+ if whitelist_path.exists():
306
+ try:
307
+ import json
308
+ with open(whitelist_path, "r", encoding="utf-8") as f:
309
+ whitelist_data = json.load(f)
310
+ whitelist = whitelist_data.get("whitelist", [])
311
+ if whitelist:
312
+ if use_cache and _cached_models is not None:
313
+ return _cached_models
314
+ _cached_models = whitelist
315
+ return whitelist
316
+ except Exception as e:
317
+ print(f"Warning: Could not load whitelist: {e}. Falling back to fetching all models.")
318
+ else:
319
+ print(f"Warning: Whitelist file not found at {whitelist_path}. Falling back to fetching all models.")
320
+
321
+ # Fallback to fetching all models
322
  if use_cache and _cached_models is not None:
323
  return _cached_models
324
 
perplexity_service.py CHANGED
@@ -62,13 +62,16 @@ Nếu vì bất kỳ lý do gì bạn không thể tuân thủ tất cả quy t
62
  class PerplexityReplyService:
63
  """Service for generating replies using Perplexity API."""
64
 
65
- def __init__(self, api_key: Optional[str] = None, model: str = "mistral-7b-instruct"):
66
  """
67
  Initialize Perplexity service.
68
 
69
  Args:
70
  api_key: Perplexity API key. If None, will try to get from PERPLEXITY_API_KEY env var.
71
- model: Model name to use (default: "mistral-7b-instruct")
 
 
 
72
  """
73
  self.api_key = api_key or os.getenv("PERPLEXITY_API_KEY")
74
  if not self.api_key:
@@ -157,7 +160,7 @@ _perplexity_service = None
157
 
158
  def get_perplexity_service(
159
  api_key: Optional[str] = None,
160
- model: str = "mistral-7b-instruct",
161
  ) -> PerplexityReplyService:
162
  """Get or create the global Perplexity service instance."""
163
  global _perplexity_service
 
62
  class PerplexityReplyService:
63
  """Service for generating replies using Perplexity API."""
64
 
65
+ def __init__(self, api_key: Optional[str] = None, model: str = "sonar"):
66
  """
67
  Initialize Perplexity service.
68
 
69
  Args:
70
  api_key: Perplexity API key. If None, will try to get from PERPLEXITY_API_KEY env var.
71
+ model: Model name to use (default: "sonar")
72
+ Valid models: sonar, sonar-pro, llama-3.1-sonar-small-32k-online, etc.
73
+ See: https://docs.perplexity.ai/getting-started/models
74
+ Note: Model names may vary. Check documentation for current valid models.
75
  """
76
  self.api_key = api_key or os.getenv("PERPLEXITY_API_KEY")
77
  if not self.api_key:
 
160
 
161
  def get_perplexity_service(
162
  api_key: Optional[str] = None,
163
+ model: str = "sonar",
164
  ) -> PerplexityReplyService:
165
  """Get or create the global Perplexity service instance."""
166
  global _perplexity_service
test_all_gemini_models.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test all Gemini models and create whitelist of working models.
3
+ """
4
+ import os
5
+ import json
6
+ from pathlib import Path
7
+ from dotenv import load_dotenv
8
+ from gemini_service import fetch_gemini_models, get_gemini_service
9
+
10
+ # Load .env file
11
+ env_path = Path(__file__).parent / '.env'
12
+ if env_path.exists():
13
+ load_dotenv(env_path)
14
+
15
+ # Test data
16
+ TEST_CONVERSATION = "Male: Tối nay anh có lịch đột xuất. ||| Female: Thế mai được không?"
17
+ TEST_TRIGGER = "neutral"
18
+ TEST_MOVE = "escalate"
19
+
20
+ SYSTEM_PROMPT = """
21
+ Bạn là một wingman AI tinh tế, chuyên giúp Nam soạn 1 tin nhắn trả lời duy nhất trong hội thoại hẹn hò tiếng Việt. Bạn luôn nhìn từ góc nhìn của Nam, xưng "anh" và gọi đối phương là "em".
22
+
23
+ Bạn được cung cấp:
24
+
25
+ - HỘI THOẠI: đoạn hội thoại gần nhất giữa Nam (Male) và Nữ (Female), phân tách các tin bằng ký hiệu "|||".
26
+
27
+ - TRIGGER: intent hiện tại (ví dụ: neutral, positive, negative, confused...).
28
+
29
+ - MOVE: chiến lược hiện tại (ví dụ: escalate, hold, de-escalate, tease, comfort...).
30
+
31
+ Nhiệm vụ của bạn:
32
+
33
+ - Dựa trên HỘI THOẠI + TRIGGER + MOVE, hãy chọn một hướng phản hồi tự nhiên, duyên dáng, đúng chiến lược (không quá đẩy hay quá lùi so với MOVE).
34
+
35
+ - Ưu tiên giữ mạch cảm xúc nhất quán với hội thoại, tránh tạo thông tin fact mới về thế giới bên ngoài hoặc về hai người.
36
+
37
+ QUY TẮC CỨNG:
38
+
39
+ - Chỉ trả về đúng 1 câu duy nhất.
40
+
41
+ - Tối đa 25 từ tiếng Việt.
42
+
43
+ - Lịch sự, ấm áp, thân thiện; không phán xét, không thô lỗ.
44
+
45
+ - Không giải thích meta (không nói về "prompt", "AI", "chiến lược", "MOVE", "TRIGGER"...).
46
+
47
+ - Không lặp lại nguyên văn câu của đối phương.
48
+
49
+ - Không thêm fact mới (chỉ dựa trên những gì có trong hội thoại, hoặc các câu nói chung chung, không cụ thể hóa thông tin chưa có).
50
+
51
+ Khi TRIGGER hoặc MOVE có vẻ mâu thuẫn với HỘI THOẠI:
52
+
53
+ - Hãy ưu tiên sự an toàn và mềm mại.
54
+
55
+ - Có thể hỏi lại nhẹ nhàng để làm rõ, nhưng vẫn giữ frame chủ động, tự tin của Nam.
56
+
57
+ PHONG CÁCH:
58
+
59
+ - Ấm áp, tự tin nhưng không tự cao.
60
+
61
+ - Có thể dùng từ đệm tự nhiên (nha, nhé, ạ, dạ) khi phù hợp với ngữ cảnh.
62
+
63
+ - Phản chiếu cảm xúc của đối phương.
64
+
65
+ - Giữ mạch trò chuyện mở để còn đất tăng tương tác về sau.
66
+
67
+ Nếu vì bất kỳ lý do gì bạn không thể tuân thủ tất cả quy tắc trên:
68
+
69
+ - Hãy ưu tiên vẫn trả về đúng 1 câu, ≤25 từ, không chứa meta, không chứa thông tin fact mới.
70
+ """.strip()
71
+
72
+
73
+ def test_model(model_name: str, max_retries: int = 2) -> tuple[bool, str, str]:
74
+ """
75
+ Test a single Gemini model.
76
+
77
+ Returns:
78
+ (success: bool, reply: str, error: str)
79
+ """
80
+ for attempt in range(max_retries):
81
+ try:
82
+ service = get_gemini_service(model_name=model_name)
83
+ formatted_conversation = f"Male: {TEST_CONVERSATION.split('|||')[0].strip()} ||| Female: {TEST_CONVERSATION.split('|||')[1].strip()}"
84
+
85
+ reply = service.generate_reply(
86
+ conversation=formatted_conversation,
87
+ trigger=TEST_TRIGGER,
88
+ move=TEST_MOVE,
89
+ max_output_tokens=200,
90
+ )
91
+
92
+ if reply and len(reply.strip()) > 0:
93
+ return True, reply, ""
94
+ else:
95
+ return False, "", "Empty reply"
96
+
97
+ except Exception as e:
98
+ error_msg = str(e)
99
+ if attempt < max_retries - 1:
100
+ continue # Retry
101
+ return False, "", error_msg
102
+
103
+ return False, "", "Max retries exceeded"
104
+
105
+
106
+ def main():
107
+ """Test all Gemini models and create whitelist."""
108
+ print("=" * 60)
109
+ print("Testing All Gemini Models for Whitelist")
110
+ print("=" * 60)
111
+
112
+ # Fetch all available models
113
+ try:
114
+ print("\nFetching available Gemini models...")
115
+ all_models = fetch_gemini_models()
116
+ print(f"✓ Found {len(all_models)} models with generateContent support")
117
+ except Exception as e:
118
+ print(f"✗ Error fetching models: {str(e)}")
119
+ return 1
120
+
121
+ # Test each model
122
+ whitelist = []
123
+ failed_models = []
124
+
125
+ print(f"\nTesting {len(all_models)} models...")
126
+ print("=" * 60)
127
+
128
+ for idx, model_info in enumerate(all_models, 1):
129
+ model_name = model_info["name"]
130
+ display_name = model_info.get("displayName", model_name)
131
+
132
+ print(f"\n[{idx}/{len(all_models)}] Testing: {display_name}")
133
+ print(f" Model name: {model_name}")
134
+
135
+ success, reply, error = test_model(model_name)
136
+
137
+ if success:
138
+ print(f" ✓ PASSED - Reply: {reply[:60]}...")
139
+ whitelist.append({
140
+ "name": model_name,
141
+ "displayName": display_name,
142
+ "description": model_info.get("description", ""),
143
+ "version": model_info.get("version", ""),
144
+ "test_reply": reply[:100], # Store sample reply
145
+ })
146
+ else:
147
+ print(f" ✗ FAILED - {error[:100]}")
148
+ failed_models.append({
149
+ "name": model_name,
150
+ "displayName": display_name,
151
+ "error": error[:200],
152
+ })
153
+
154
+ # Save whitelist
155
+ whitelist_file = Path(__file__).parent / "gemini_models_whitelist.json"
156
+ with open(whitelist_file, "w", encoding="utf-8") as f:
157
+ json.dump({
158
+ "whitelist": whitelist,
159
+ "failed": failed_models,
160
+ "test_data": {
161
+ "conversation": TEST_CONVERSATION,
162
+ "trigger": TEST_TRIGGER,
163
+ "move": TEST_MOVE,
164
+ },
165
+ "total_tested": len(all_models),
166
+ "passed": len(whitelist),
167
+ "failed": len(failed_models),
168
+ }, f, indent=2, ensure_ascii=False)
169
+
170
+ # Print summary
171
+ print("\n" + "=" * 60)
172
+ print("Test Summary")
173
+ print("=" * 60)
174
+ print(f"Total models tested: {len(all_models)}")
175
+ print(f"✓ Passed (whitelist): {len(whitelist)}")
176
+ print(f"✗ Failed: {len(failed_models)}")
177
+ print(f"\nWhitelist saved to: {whitelist_file}")
178
+
179
+ if whitelist:
180
+ print("\n✓ Working models (whitelist):")
181
+ for model in whitelist:
182
+ print(f" - {model['displayName']} ({model['name']})")
183
+
184
+ if failed_models:
185
+ print("\n✗ Failed models:")
186
+ for model in failed_models[:10]: # Show first 10
187
+ print(f" - {model['displayName']} ({model['name']}): {model['error'][:50]}...")
188
+ if len(failed_models) > 10:
189
+ print(f" ... and {len(failed_models) - 10} more")
190
+
191
+ return 0 if whitelist else 1
192
+
193
+
194
+ if __name__ == "__main__":
195
+ exit(main())
196
+
test_api_models.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test script for Model 4 (Perplexity) and Model 5 (Gemini) APIs.
3
+ """
4
+ import os
5
+ from pathlib import Path
6
+ from dotenv import load_dotenv
7
+ from perplexity_service import get_perplexity_service
8
+ from gemini_service import get_gemini_service
9
+
10
+ # Load .env file
11
+ env_path = Path(__file__).parent / '.env'
12
+ if env_path.exists():
13
+ load_dotenv(env_path)
14
+ print(f"✓ Loaded .env file from {env_path}")
15
+ else:
16
+ print(f"⚠ .env file not found at {env_path}, using environment variables only")
17
+
18
+ # Test data
19
+ TEST_CONVERSATION = "Tối nay anh có lịch đột xuất. ||| Thế mai được không?"
20
+ TEST_TRIGGER = "neutral"
21
+ TEST_MOVE = "escalate"
22
+
23
+ def test_perplexity():
24
+ """Test Perplexity API (Model 4)."""
25
+ print("=" * 60)
26
+ print("Testing Model 4 - Perplexity API")
27
+ print("=" * 60)
28
+
29
+ try:
30
+ service = get_perplexity_service()
31
+ print(f"✓ Perplexity service initialized")
32
+ print(f" Model: {service.model}")
33
+ print(f" API Key: {'Set' if service.api_key else 'Not set'}")
34
+
35
+ print("\nGenerating reply...")
36
+ reply = service.generate_reply(
37
+ conversation=TEST_CONVERSATION,
38
+ trigger=TEST_TRIGGER,
39
+ move=TEST_MOVE,
40
+ )
41
+
42
+ print(f"\n✓ Success!")
43
+ print(f" Input conversation: {TEST_CONVERSATION}")
44
+ print(f" Trigger: {TEST_TRIGGER}")
45
+ print(f" Move: {TEST_MOVE}")
46
+ print(f" Generated reply: {reply}")
47
+ print(f" Reply length: {len(reply.split())} words")
48
+
49
+ return True, reply
50
+
51
+ except Exception as e:
52
+ print(f"\n✗ Error: {str(e)}")
53
+ return False, str(e)
54
+
55
+
56
+ def test_gemini():
57
+ """Test Gemini API (Model 5)."""
58
+ print("\n" + "=" * 60)
59
+ print("Testing Model 5 - Gemini API")
60
+ print("=" * 60)
61
+
62
+ try:
63
+ # Try default model first
64
+ model_name = "gemini-2.0-flash"
65
+ service = get_gemini_service(model_name=model_name)
66
+ print(f"✓ Gemini service initialized")
67
+ print(f" Model: {service.model_name}")
68
+ print(f" API Key: {'Set' if service.api_key else 'Not set'}")
69
+
70
+ print("\nGenerating reply...")
71
+ formatted_conversation = f"Male: {TEST_CONVERSATION.split('|||')[0].strip()} ||| Female: {TEST_CONVERSATION.split('|||')[1].strip()}"
72
+ reply = service.generate_reply(
73
+ conversation=formatted_conversation,
74
+ trigger=TEST_TRIGGER,
75
+ move=TEST_MOVE,
76
+ max_output_tokens=500, # Use higher limit for testing
77
+ )
78
+
79
+ print(f"\n✓ Success!")
80
+ print(f" Input conversation: {formatted_conversation}")
81
+ print(f" Trigger: {TEST_TRIGGER}")
82
+ print(f" Move: {TEST_MOVE}")
83
+ print(f" Generated reply: {reply}")
84
+ print(f" Reply length: {len(reply.split())} words")
85
+
86
+ return True, reply
87
+
88
+ except Exception as e:
89
+ print(f"\n✗ Error: {str(e)}")
90
+ return False, str(e)
91
+
92
+
93
+ def main():
94
+ """Run all tests."""
95
+ print("\n" + "=" * 60)
96
+ print("API Endpoint Test Script")
97
+ print("=" * 60)
98
+ print(f"\nTest Data:")
99
+ print(f" Conversation: {TEST_CONVERSATION}")
100
+ print(f" Trigger: {TEST_TRIGGER}")
101
+ print(f" Move: {TEST_MOVE}")
102
+ print()
103
+
104
+ # Check environment variables
105
+ print("Environment Variables:")
106
+ perplexity_key = os.getenv("PERPLEXITY_API_KEY")
107
+ google_key = os.getenv("GOOGLE_API_KEY")
108
+ print(f" PERPLEXITY_API_KEY: {'✓ Set' if perplexity_key else '✗ Not set'}")
109
+ print(f" GOOGLE_API_KEY: {'✓ Set' if google_key else '✗ Not set'}")
110
+ print()
111
+
112
+ # Test Perplexity
113
+ perplexity_success, perplexity_result = test_perplexity()
114
+
115
+ # Test Gemini
116
+ gemini_success, gemini_result = test_gemini()
117
+
118
+ # Summary
119
+ print("\n" + "=" * 60)
120
+ print("Test Summary")
121
+ print("=" * 60)
122
+ print(f" Model 4 (Perplexity): {'✓ PASSED' if perplexity_success else '✗ FAILED'}")
123
+ print(f" Model 5 (Gemini): {'✓ PASSED' if gemini_success else '✗ FAILED'}")
124
+ print()
125
+
126
+ if perplexity_success and gemini_success:
127
+ print("✓ All tests passed!")
128
+ return 0
129
+ else:
130
+ print("✗ Some tests failed. Check errors above.")
131
+ return 1
132
+
133
+
134
+ if __name__ == "__main__":
135
+ exit(main())
136
+