Paul commited on
Commit
0748ff8
·
1 Parent(s): 136f619
__pycache__/app.cpython-313.pyc CHANGED
Binary files a/__pycache__/app.cpython-313.pyc and b/__pycache__/app.cpython-313.pyc differ
 
app.py CHANGED
@@ -4,7 +4,7 @@ from typing import Dict, Any, Tuple
4
 
5
  from reply_service import get_reply_service
6
  from trigger_move_identifier import get_trigger_move_identifier
7
- from perplexity_service import get_perplexity_service
8
  from gemini_service import get_gemini_service, get_available_gemini_models
9
 
10
 
@@ -77,8 +77,8 @@ def parse_conversation(text: str) -> Tuple[str, str]:
77
  return male, female
78
 
79
 
80
- def run_full_pipeline(conversation: str, wingman_prompt: str = "", gemini_model_name: str = "gemini-2.5-flash") -> Dict[str, Any]:
81
- """Run trigger detector and generate replies from 5 models (3 prompt styles + Perplexity + Gemini)."""
82
  try:
83
  male, female = parse_conversation(conversation)
84
  identifier = get_trigger_move_identifier(
@@ -129,20 +129,20 @@ def run_full_pipeline(conversation: str, wingman_prompt: str = "", gemini_model_
129
  wingman_reply = ""
130
  wingman_error = str(exc)
131
 
132
- # Model 4 – Perplexity API
133
  try:
134
- perplexity_service = get_perplexity_service()
135
- # Format conversation for Perplexity: "Male: ... ||| Female: ..."
136
  formatted_conversation = f"Male: {male} ||| Female: {female}"
137
- perplexity_reply = perplexity_service.generate_reply(
138
  conversation=formatted_conversation,
139
  trigger=trigger,
140
  move=move,
141
  )
142
- perplexity_error = ""
143
  except Exception as exc:
144
- perplexity_reply = ""
145
- perplexity_error = str(exc)
146
 
147
  models_output["llama"] = {
148
  "label": "Model 1 – Prompt style: an toàn / nhẹ nhàng",
@@ -162,10 +162,10 @@ def run_full_pipeline(conversation: str, wingman_prompt: str = "", gemini_model_
162
  "error": wingman_error,
163
  }
164
 
165
- models_output["perplexity"] = {
166
- "label": "Model 4 – Perplexity API",
167
- "reply": perplexity_reply,
168
- "error": perplexity_error,
169
  }
170
 
171
  # Model 5 – Google Gemini API
@@ -265,7 +265,7 @@ with gr.Blocks(title=title) as demo:
265
 
266
  # Main Reply Suggestion Tab
267
  gr.Markdown("### 🎯 Generate AI Reply Suggestions (5 Models)")
268
- gr.Markdown("Nhập hội thoại và hệ thống sẽ chạy pipeline Trigger → Move → 5 models (3 prompt styles + Perplexity API + Gemini API).")
269
 
270
  with gr.Row():
271
  with gr.Column(scale=2):
@@ -298,6 +298,40 @@ with gr.Blocks(title=title) as demo:
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)
@@ -361,11 +395,11 @@ with gr.Blocks(title=title) as demo:
361
  placeholder="Reply từ mô hình Wingman LoRA (hoặc fallback prompt) sẽ xuất hiện tại đây.",
362
  )
363
 
364
- perplexity_box = gr.Textbox(
365
  lines=3,
366
- label="Model 4 – Perplexity API",
367
  interactive=False,
368
- placeholder="Reply từ Perplexity API sẽ xuất hiện tại đây.",
369
  )
370
 
371
  gemini_box = gr.Textbox(
@@ -375,9 +409,9 @@ with gr.Blocks(title=title) as demo:
375
  placeholder="Reply từ Gemini API sẽ xuất hiện tại đây.",
376
  )
377
 
378
- def generate_reply_with_extraction(conversation: str, wingman_prompt: str, gemini_model_name: str) -> Tuple[Dict[str, Any], str, str, str, str, str]:
379
  """Generate replies from five models."""
380
- result = run_full_pipeline(conversation, wingman_prompt, gemini_model_name)
381
  if "error" in result:
382
  error_msg = f"❌ {result['error']}"
383
  return result, error_msg, error_msg, error_msg, error_msg, error_msg
@@ -397,14 +431,14 @@ with gr.Blocks(title=title) as demo:
397
  extract_text("llama"),
398
  extract_text("pho"),
399
  extract_text("wingman"),
400
- extract_text("perplexity"),
401
  extract_text("gemini"),
402
  )
403
 
404
  reply_btn.click(
405
  generate_reply_with_extraction,
406
- inputs=[reply_in, wingman_prompt_in, gemini_model_dropdown],
407
- outputs=[reply_out, llama_box, pho_box, wingman_box, perplexity_box, gemini_box],
408
  api_name="reply"
409
  )
410
 
 
4
 
5
  from reply_service import get_reply_service
6
  from trigger_move_identifier import get_trigger_move_identifier
7
+ from openai_service import get_openai_service, get_available_models
8
  from gemini_service import get_gemini_service, get_available_gemini_models
9
 
10
 
 
77
  return male, female
78
 
79
 
80
+ def run_full_pipeline(conversation: str, wingman_prompt: str = "", gemini_model_name: str = "models/gemini-2.0-flash", openai_model_name: str = "gpt-4o-mini") -> Dict[str, Any]:
81
+ """Run trigger detector and generate replies from 5 models (3 prompt styles + OpenAI + Gemini)."""
82
  try:
83
  male, female = parse_conversation(conversation)
84
  identifier = get_trigger_move_identifier(
 
129
  wingman_reply = ""
130
  wingman_error = str(exc)
131
 
132
+ # Model 4 – OpenAI API
133
  try:
134
+ openai_service = get_openai_service(model_name=openai_model_name)
135
+ # Format conversation for OpenAI: "Male: ... ||| Female: ..."
136
  formatted_conversation = f"Male: {male} ||| Female: {female}"
137
+ openai_reply = openai_service.generate_reply(
138
  conversation=formatted_conversation,
139
  trigger=trigger,
140
  move=move,
141
  )
142
+ openai_error = ""
143
  except Exception as exc:
144
+ openai_reply = ""
145
+ openai_error = str(exc)
146
 
147
  models_output["llama"] = {
148
  "label": "Model 1 – Prompt style: an toàn / nhẹ nhàng",
 
162
  "error": wingman_error,
163
  }
164
 
165
+ models_output["openai"] = {
166
+ "label": f"Model 4 – OpenAI API ({openai_model_name})",
167
+ "reply": openai_reply,
168
+ "error": openai_error,
169
  }
170
 
171
  # Model 5 – Google Gemini API
 
265
 
266
  # Main Reply Suggestion Tab
267
  gr.Markdown("### 🎯 Generate AI Reply Suggestions (5 Models)")
268
+ gr.Markdown("Nhập hội thoại và hệ thống sẽ chạy pipeline Trigger → Move → 5 models (3 prompt styles + OpenAI API + Gemini API).")
269
 
270
  with gr.Row():
271
  with gr.Column(scale=2):
 
298
  )
299
  gr.Markdown("Leave as-is for default behavior. Edits apply to Model 3 when its LoRA is used.")
300
 
301
+ # Model 4 – OpenAI Model Selection (with search)
302
+ try:
303
+ # Load all available models from cache
304
+ all_model_ids = get_available_models(prefix_filters=["gpt-", "o1-", "o3-"])
305
+
306
+ # Create choices with (label, value) format for better display
307
+ openai_dropdown_choices = []
308
+ openai_model_choices = []
309
+
310
+ for model_id in all_model_ids:
311
+ # Use model_id as both label and value, but format nicely
312
+ label = model_id.replace("gpt-", "GPT-").replace("o1-", "O1-").replace("o3-", "O3-")
313
+ openai_dropdown_choices.append((label, model_id))
314
+ openai_model_choices.append(model_id)
315
+
316
+ default_openai_model = "gpt-4o-mini" if "gpt-4o-mini" in openai_model_choices else (openai_model_choices[0] if openai_model_choices else "gpt-4o-mini")
317
+
318
+ print(f"✓ Loaded {len(openai_model_choices)} OpenAI models for dropdown")
319
+ except Exception as e:
320
+ print(f"⚠ Error loading OpenAI models: {e}")
321
+ openai_dropdown_choices = [("gpt-4o-mini (default - API key may be missing)", "gpt-4o-mini")]
322
+ openai_model_choices = ["gpt-4o-mini"]
323
+ default_openai_model = "gpt-4o-mini"
324
+
325
+ openai_model_dropdown = gr.Dropdown(
326
+ choices=openai_dropdown_choices,
327
+ value=default_openai_model,
328
+ label="Model 4 – Select OpenAI Model",
329
+ info=f"Search and choose from {len(openai_model_choices)} available OpenAI models",
330
+ interactive=True,
331
+ filterable=True, # Enable search/filter functionality
332
+ scale=1,
333
+ )
334
+
335
  # Model 5 – Gemini Model Selection (using whitelist - 15 tested models)
336
  try:
337
  gemini_models = get_available_gemini_models(use_whitelist=True)
 
395
  placeholder="Reply từ mô hình Wingman LoRA (hoặc fallback prompt) sẽ xuất hiện tại đây.",
396
  )
397
 
398
+ openai_box = gr.Textbox(
399
  lines=3,
400
+ label="Model 4 – OpenAI API",
401
  interactive=False,
402
+ placeholder="Reply từ OpenAI API sẽ xuất hiện tại đây.",
403
  )
404
 
405
  gemini_box = gr.Textbox(
 
409
  placeholder="Reply từ Gemini API sẽ xuất hiện tại đây.",
410
  )
411
 
412
+ def generate_reply_with_extraction(conversation: str, wingman_prompt: str, openai_model_name: str, gemini_model_name: str) -> Tuple[Dict[str, Any], str, str, str, str, str]:
413
  """Generate replies from five models."""
414
+ result = run_full_pipeline(conversation, wingman_prompt, gemini_model_name, openai_model_name)
415
  if "error" in result:
416
  error_msg = f"❌ {result['error']}"
417
  return result, error_msg, error_msg, error_msg, error_msg, error_msg
 
431
  extract_text("llama"),
432
  extract_text("pho"),
433
  extract_text("wingman"),
434
+ extract_text("openai"),
435
  extract_text("gemini"),
436
  )
437
 
438
  reply_btn.click(
439
  generate_reply_with_extraction,
440
+ inputs=[reply_in, wingman_prompt_in, openai_model_dropdown, gemini_model_dropdown],
441
+ outputs=[reply_out, llama_box, pho_box, wingman_box, openai_box, gemini_box],
442
  api_name="reply"
443
  )
444
 
config/ai_models.json ADDED
@@ -0,0 +1,512 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "gpt-4-0613",
4
+ "created": 1686588896,
5
+ "owned_by": "openai"
6
+ },
7
+ {
8
+ "id": "gpt-4",
9
+ "created": 1687882411,
10
+ "owned_by": "openai"
11
+ },
12
+ {
13
+ "id": "gpt-3.5-turbo",
14
+ "created": 1677610602,
15
+ "owned_by": "openai"
16
+ },
17
+ {
18
+ "id": "gpt-5.1-codex-mini",
19
+ "created": 1763007109,
20
+ "owned_by": "system"
21
+ },
22
+ {
23
+ "id": "gpt-5.1-chat-latest",
24
+ "created": 1762547951,
25
+ "owned_by": "system"
26
+ },
27
+ {
28
+ "id": "gpt-5.1-2025-11-13",
29
+ "created": 1762800353,
30
+ "owned_by": "system"
31
+ },
32
+ {
33
+ "id": "gpt-5.1",
34
+ "created": 1762800673,
35
+ "owned_by": "system"
36
+ },
37
+ {
38
+ "id": "gpt-5.1-codex",
39
+ "created": 1762988221,
40
+ "owned_by": "system"
41
+ },
42
+ {
43
+ "id": "davinci-002",
44
+ "created": 1692634301,
45
+ "owned_by": "system"
46
+ },
47
+ {
48
+ "id": "babbage-002",
49
+ "created": 1692634615,
50
+ "owned_by": "system"
51
+ },
52
+ {
53
+ "id": "gpt-3.5-turbo-instruct",
54
+ "created": 1692901427,
55
+ "owned_by": "system"
56
+ },
57
+ {
58
+ "id": "gpt-3.5-turbo-instruct-0914",
59
+ "created": 1694122472,
60
+ "owned_by": "system"
61
+ },
62
+ {
63
+ "id": "dall-e-3",
64
+ "created": 1698785189,
65
+ "owned_by": "system"
66
+ },
67
+ {
68
+ "id": "dall-e-2",
69
+ "created": 1698798177,
70
+ "owned_by": "system"
71
+ },
72
+ {
73
+ "id": "gpt-4-1106-preview",
74
+ "created": 1698957206,
75
+ "owned_by": "system"
76
+ },
77
+ {
78
+ "id": "gpt-3.5-turbo-1106",
79
+ "created": 1698959748,
80
+ "owned_by": "system"
81
+ },
82
+ {
83
+ "id": "tts-1-hd",
84
+ "created": 1699046015,
85
+ "owned_by": "system"
86
+ },
87
+ {
88
+ "id": "tts-1-1106",
89
+ "created": 1699053241,
90
+ "owned_by": "system"
91
+ },
92
+ {
93
+ "id": "tts-1-hd-1106",
94
+ "created": 1699053533,
95
+ "owned_by": "system"
96
+ },
97
+ {
98
+ "id": "text-embedding-3-small",
99
+ "created": 1705948997,
100
+ "owned_by": "system"
101
+ },
102
+ {
103
+ "id": "text-embedding-3-large",
104
+ "created": 1705953180,
105
+ "owned_by": "system"
106
+ },
107
+ {
108
+ "id": "gpt-4-0125-preview",
109
+ "created": 1706037612,
110
+ "owned_by": "system"
111
+ },
112
+ {
113
+ "id": "gpt-4-turbo-preview",
114
+ "created": 1706037777,
115
+ "owned_by": "system"
116
+ },
117
+ {
118
+ "id": "gpt-3.5-turbo-0125",
119
+ "created": 1706048358,
120
+ "owned_by": "system"
121
+ },
122
+ {
123
+ "id": "gpt-4-turbo",
124
+ "created": 1712361441,
125
+ "owned_by": "system"
126
+ },
127
+ {
128
+ "id": "gpt-4-turbo-2024-04-09",
129
+ "created": 1712601677,
130
+ "owned_by": "system"
131
+ },
132
+ {
133
+ "id": "gpt-4o",
134
+ "created": 1715367049,
135
+ "owned_by": "system"
136
+ },
137
+ {
138
+ "id": "gpt-4o-2024-05-13",
139
+ "created": 1715368132,
140
+ "owned_by": "system"
141
+ },
142
+ {
143
+ "id": "gpt-4o-mini-2024-07-18",
144
+ "created": 1721172717,
145
+ "owned_by": "system"
146
+ },
147
+ {
148
+ "id": "gpt-4o-mini",
149
+ "created": 1721172741,
150
+ "owned_by": "system"
151
+ },
152
+ {
153
+ "id": "gpt-4o-2024-08-06",
154
+ "created": 1722814719,
155
+ "owned_by": "system"
156
+ },
157
+ {
158
+ "id": "chatgpt-4o-latest",
159
+ "created": 1723515131,
160
+ "owned_by": "system"
161
+ },
162
+ {
163
+ "id": "gpt-4o-realtime-preview-2024-10-01",
164
+ "created": 1727131766,
165
+ "owned_by": "system"
166
+ },
167
+ {
168
+ "id": "gpt-4o-audio-preview-2024-10-01",
169
+ "created": 1727389042,
170
+ "owned_by": "system"
171
+ },
172
+ {
173
+ "id": "gpt-4o-audio-preview",
174
+ "created": 1727460443,
175
+ "owned_by": "system"
176
+ },
177
+ {
178
+ "id": "gpt-4o-realtime-preview",
179
+ "created": 1727659998,
180
+ "owned_by": "system"
181
+ },
182
+ {
183
+ "id": "omni-moderation-latest",
184
+ "created": 1731689265,
185
+ "owned_by": "system"
186
+ },
187
+ {
188
+ "id": "omni-moderation-2024-09-26",
189
+ "created": 1732734466,
190
+ "owned_by": "system"
191
+ },
192
+ {
193
+ "id": "gpt-4o-realtime-preview-2024-12-17",
194
+ "created": 1733945430,
195
+ "owned_by": "system"
196
+ },
197
+ {
198
+ "id": "gpt-4o-audio-preview-2024-12-17",
199
+ "created": 1734034239,
200
+ "owned_by": "system"
201
+ },
202
+ {
203
+ "id": "gpt-4o-mini-realtime-preview-2024-12-17",
204
+ "created": 1734112601,
205
+ "owned_by": "system"
206
+ },
207
+ {
208
+ "id": "gpt-4o-mini-audio-preview-2024-12-17",
209
+ "created": 1734115920,
210
+ "owned_by": "system"
211
+ },
212
+ {
213
+ "id": "o1-2024-12-17",
214
+ "created": 1734326976,
215
+ "owned_by": "system"
216
+ },
217
+ {
218
+ "id": "o1",
219
+ "created": 1734375816,
220
+ "owned_by": "system"
221
+ },
222
+ {
223
+ "id": "gpt-4o-mini-realtime-preview",
224
+ "created": 1734387380,
225
+ "owned_by": "system"
226
+ },
227
+ {
228
+ "id": "gpt-4o-mini-audio-preview",
229
+ "created": 1734387424,
230
+ "owned_by": "system"
231
+ },
232
+ {
233
+ "id": "o3-mini",
234
+ "created": 1737146383,
235
+ "owned_by": "system"
236
+ },
237
+ {
238
+ "id": "o3-mini-2025-01-31",
239
+ "created": 1738010200,
240
+ "owned_by": "system"
241
+ },
242
+ {
243
+ "id": "gpt-4o-2024-11-20",
244
+ "created": 1739331543,
245
+ "owned_by": "system"
246
+ },
247
+ {
248
+ "id": "gpt-4o-search-preview-2025-03-11",
249
+ "created": 1741388170,
250
+ "owned_by": "system"
251
+ },
252
+ {
253
+ "id": "gpt-4o-search-preview",
254
+ "created": 1741388720,
255
+ "owned_by": "system"
256
+ },
257
+ {
258
+ "id": "gpt-4o-mini-search-preview-2025-03-11",
259
+ "created": 1741390858,
260
+ "owned_by": "system"
261
+ },
262
+ {
263
+ "id": "gpt-4o-mini-search-preview",
264
+ "created": 1741391161,
265
+ "owned_by": "system"
266
+ },
267
+ {
268
+ "id": "gpt-4o-transcribe",
269
+ "created": 1742068463,
270
+ "owned_by": "system"
271
+ },
272
+ {
273
+ "id": "gpt-4o-mini-transcribe",
274
+ "created": 1742068596,
275
+ "owned_by": "system"
276
+ },
277
+ {
278
+ "id": "o1-pro-2025-03-19",
279
+ "created": 1742251504,
280
+ "owned_by": "system"
281
+ },
282
+ {
283
+ "id": "o1-pro",
284
+ "created": 1742251791,
285
+ "owned_by": "system"
286
+ },
287
+ {
288
+ "id": "gpt-4o-mini-tts",
289
+ "created": 1742403959,
290
+ "owned_by": "system"
291
+ },
292
+ {
293
+ "id": "o3-2025-04-16",
294
+ "created": 1744133301,
295
+ "owned_by": "system"
296
+ },
297
+ {
298
+ "id": "o4-mini-2025-04-16",
299
+ "created": 1744133506,
300
+ "owned_by": "system"
301
+ },
302
+ {
303
+ "id": "o3",
304
+ "created": 1744225308,
305
+ "owned_by": "system"
306
+ },
307
+ {
308
+ "id": "o4-mini",
309
+ "created": 1744225351,
310
+ "owned_by": "system"
311
+ },
312
+ {
313
+ "id": "gpt-4.1-2025-04-14",
314
+ "created": 1744315746,
315
+ "owned_by": "system"
316
+ },
317
+ {
318
+ "id": "gpt-4.1",
319
+ "created": 1744316542,
320
+ "owned_by": "system"
321
+ },
322
+ {
323
+ "id": "gpt-4.1-mini-2025-04-14",
324
+ "created": 1744317547,
325
+ "owned_by": "system"
326
+ },
327
+ {
328
+ "id": "gpt-4.1-mini",
329
+ "created": 1744318173,
330
+ "owned_by": "system"
331
+ },
332
+ {
333
+ "id": "gpt-4.1-nano-2025-04-14",
334
+ "created": 1744321025,
335
+ "owned_by": "system"
336
+ },
337
+ {
338
+ "id": "gpt-4.1-nano",
339
+ "created": 1744321707,
340
+ "owned_by": "system"
341
+ },
342
+ {
343
+ "id": "gpt-image-1",
344
+ "created": 1745517030,
345
+ "owned_by": "system"
346
+ },
347
+ {
348
+ "id": "codex-mini-latest",
349
+ "created": 1746673257,
350
+ "owned_by": "system"
351
+ },
352
+ {
353
+ "id": "gpt-4o-realtime-preview-2025-06-03",
354
+ "created": 1748907838,
355
+ "owned_by": "system"
356
+ },
357
+ {
358
+ "id": "gpt-4o-audio-preview-2025-06-03",
359
+ "created": 1748908498,
360
+ "owned_by": "system"
361
+ },
362
+ {
363
+ "id": "o4-mini-deep-research",
364
+ "created": 1749685485,
365
+ "owned_by": "system"
366
+ },
367
+ {
368
+ "id": "gpt-4o-transcribe-diarize",
369
+ "created": 1750798887,
370
+ "owned_by": "system"
371
+ },
372
+ {
373
+ "id": "o4-mini-deep-research-2025-06-26",
374
+ "created": 1750866121,
375
+ "owned_by": "system"
376
+ },
377
+ {
378
+ "id": "gpt-5-chat-latest",
379
+ "created": 1754073306,
380
+ "owned_by": "system"
381
+ },
382
+ {
383
+ "id": "gpt-5-2025-08-07",
384
+ "created": 1754075360,
385
+ "owned_by": "system"
386
+ },
387
+ {
388
+ "id": "gpt-5",
389
+ "created": 1754425777,
390
+ "owned_by": "system"
391
+ },
392
+ {
393
+ "id": "gpt-5-mini-2025-08-07",
394
+ "created": 1754425867,
395
+ "owned_by": "system"
396
+ },
397
+ {
398
+ "id": "gpt-5-mini",
399
+ "created": 1754425928,
400
+ "owned_by": "system"
401
+ },
402
+ {
403
+ "id": "gpt-5-nano-2025-08-07",
404
+ "created": 1754426303,
405
+ "owned_by": "system"
406
+ },
407
+ {
408
+ "id": "gpt-5-nano",
409
+ "created": 1754426384,
410
+ "owned_by": "system"
411
+ },
412
+ {
413
+ "id": "gpt-audio-2025-08-28",
414
+ "created": 1756256146,
415
+ "owned_by": "system"
416
+ },
417
+ {
418
+ "id": "gpt-realtime",
419
+ "created": 1756271701,
420
+ "owned_by": "system"
421
+ },
422
+ {
423
+ "id": "gpt-realtime-2025-08-28",
424
+ "created": 1756271773,
425
+ "owned_by": "system"
426
+ },
427
+ {
428
+ "id": "gpt-audio",
429
+ "created": 1756339249,
430
+ "owned_by": "system"
431
+ },
432
+ {
433
+ "id": "gpt-5-codex",
434
+ "created": 1757527818,
435
+ "owned_by": "system"
436
+ },
437
+ {
438
+ "id": "gpt-image-1-mini",
439
+ "created": 1758845821,
440
+ "owned_by": "system"
441
+ },
442
+ {
443
+ "id": "gpt-5-pro-2025-10-06",
444
+ "created": 1759469707,
445
+ "owned_by": "system"
446
+ },
447
+ {
448
+ "id": "gpt-5-pro",
449
+ "created": 1759469822,
450
+ "owned_by": "system"
451
+ },
452
+ {
453
+ "id": "gpt-audio-mini",
454
+ "created": 1759512027,
455
+ "owned_by": "system"
456
+ },
457
+ {
458
+ "id": "gpt-audio-mini-2025-10-06",
459
+ "created": 1759512137,
460
+ "owned_by": "system"
461
+ },
462
+ {
463
+ "id": "gpt-5-search-api",
464
+ "created": 1759514629,
465
+ "owned_by": "system"
466
+ },
467
+ {
468
+ "id": "gpt-realtime-mini",
469
+ "created": 1759517133,
470
+ "owned_by": "system"
471
+ },
472
+ {
473
+ "id": "gpt-realtime-mini-2025-10-06",
474
+ "created": 1759517175,
475
+ "owned_by": "system"
476
+ },
477
+ {
478
+ "id": "sora-2",
479
+ "created": 1759708615,
480
+ "owned_by": "system"
481
+ },
482
+ {
483
+ "id": "sora-2-pro",
484
+ "created": 1759708663,
485
+ "owned_by": "system"
486
+ },
487
+ {
488
+ "id": "gpt-5-search-api-2025-10-14",
489
+ "created": 1760043960,
490
+ "owned_by": "system"
491
+ },
492
+ {
493
+ "id": "gpt-3.5-turbo-16k",
494
+ "created": 1683758102,
495
+ "owned_by": "openai-internal"
496
+ },
497
+ {
498
+ "id": "tts-1",
499
+ "created": 1681940951,
500
+ "owned_by": "openai-internal"
501
+ },
502
+ {
503
+ "id": "whisper-1",
504
+ "created": 1677532384,
505
+ "owned_by": "openai-internal"
506
+ },
507
+ {
508
+ "id": "text-embedding-ada-002",
509
+ "created": 1671217299,
510
+ "owned_by": "openai-internal"
511
+ }
512
+ ]
finetune_model.py CHANGED
@@ -76,7 +76,7 @@ def prepare_training_data(df, use_history=True, persona="default"):
76
  """
77
  training_data = []
78
  conversation_history = []
79
-
80
  has_clean_reply = {"conversation", "trigger", "move", "male_reply"}.issubset(set(df.columns))
81
 
82
  if has_clean_reply:
@@ -102,20 +102,20 @@ def prepare_training_data(df, use_history=True, persona="default"):
102
  # Fallback: dùng dữ liệu gốc (kém lý tưởng hơn)
103
  trigger_cols = [col for col in df.columns if col.startswith("trigger_")]
104
  move_cols = [col for col in df.columns if col.startswith("move_")]
105
-
106
  for _, row in df.iterrows():
107
  user_text = str(row["user_text"]) if pd.notna(row.get("user_text")) else ""
108
  partner_text = str(row["partner_text"]) if pd.notna(row.get("partner_text")) else ""
109
-
110
  if not partner_text or partner_text.strip() == "_":
111
  continue
112
-
113
  active_triggers = get_active_labels(row, trigger_cols)
114
  active_moves = get_active_labels(row, move_cols)
115
-
116
  trigger = active_triggers[0] if active_triggers[0] != "none" else "neutral"
117
  move = active_moves[0] if active_moves[0] != "none" else "neutral"
118
-
119
  if use_history and conversation_history:
120
  history_str = "\n".join(conversation_history)
121
  if user_text and user_text.strip() != "_":
@@ -128,27 +128,27 @@ def prepare_training_data(df, use_history=True, persona="default"):
128
  conversation = f"Male: {user_text} ||| Female: {partner_text}"
129
  else:
130
  conversation = f"Female: {partner_text}"
131
-
132
  prompt = build_instruction(conversation, trigger, move, persona)
133
  response = partner_text.strip()
134
-
135
  training_data.append(
136
  {
137
- "instruction": prompt,
138
- "input": "",
139
  "output": response,
140
  }
141
  )
142
-
143
  if user_text and user_text.strip() != "_":
144
  conversation_history.append(f"Male: {user_text}")
145
  if partner_text and partner_text.strip() != "_":
146
  conversation_history.append(f"Female: {partner_text}")
147
-
148
  max_history = 4
149
  if len(conversation_history) > max_history:
150
  conversation_history = conversation_history[-max_history:]
151
-
152
  return training_data
153
 
154
 
@@ -268,18 +268,18 @@ def main():
268
  use_quantization = False
269
  quant_config = None
270
  if args.model_arch == "causal":
271
- try:
272
- import bitsandbytes as bnb
273
  quant_config = BitsAndBytesConfig(
274
- load_in_4bit=True,
275
- bnb_4bit_quant_type="nf4",
276
- bnb_4bit_compute_dtype=torch.float16,
277
- bnb_4bit_use_double_quant=True,
278
- )
279
- use_quantization = True
280
- print("4-bit quantization enabled")
281
- except (ImportError, ModuleNotFoundError) as e:
282
- print(f"Warning: BitsAndBytesConfig not available ({e}), loading model without quantization...")
283
 
284
  model = None
285
  last_error = None
@@ -313,15 +313,15 @@ def main():
313
  if use_quantization:
314
  try:
315
  model = load_base_model(use_quant=True)
316
- print("Model loaded with 4-bit quantization")
317
- except Exception as e:
318
- last_error = e
319
- print(f"Failed to load with quantization: {e}")
320
  model = None
321
- if model is None:
322
- try:
323
  model = load_base_model(use_quant=False)
324
- print("Model loaded without quantization (may use more memory)")
325
  except Exception as e:
326
  if last_error:
327
  print(f"Original error: {last_error}")
@@ -356,14 +356,14 @@ def main():
356
 
357
  # Configure LoRA
358
  if args.model_arch == "causal":
359
- lora_config = LoraConfig(
360
- r=16,
361
- lora_alpha=32,
362
- target_modules=["q_proj", "v_proj", "k_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
363
- lora_dropout=0.05,
364
- bias="none",
365
- task_type="CAUSAL_LM",
366
- )
367
  else:
368
  lora_config = LoraConfig(
369
  r=16,
@@ -416,7 +416,7 @@ def main():
416
  training_args = TrainingArguments(
417
  eval_strategy="steps",
418
  **training_common_kwargs,
419
- )
420
 
421
  # Create Trainer
422
  trainer = Trainer(
 
76
  """
77
  training_data = []
78
  conversation_history = []
79
+
80
  has_clean_reply = {"conversation", "trigger", "move", "male_reply"}.issubset(set(df.columns))
81
 
82
  if has_clean_reply:
 
102
  # Fallback: dùng dữ liệu gốc (kém lý tưởng hơn)
103
  trigger_cols = [col for col in df.columns if col.startswith("trigger_")]
104
  move_cols = [col for col in df.columns if col.startswith("move_")]
105
+
106
  for _, row in df.iterrows():
107
  user_text = str(row["user_text"]) if pd.notna(row.get("user_text")) else ""
108
  partner_text = str(row["partner_text"]) if pd.notna(row.get("partner_text")) else ""
109
+
110
  if not partner_text or partner_text.strip() == "_":
111
  continue
112
+
113
  active_triggers = get_active_labels(row, trigger_cols)
114
  active_moves = get_active_labels(row, move_cols)
115
+
116
  trigger = active_triggers[0] if active_triggers[0] != "none" else "neutral"
117
  move = active_moves[0] if active_moves[0] != "none" else "neutral"
118
+
119
  if use_history and conversation_history:
120
  history_str = "\n".join(conversation_history)
121
  if user_text and user_text.strip() != "_":
 
128
  conversation = f"Male: {user_text} ||| Female: {partner_text}"
129
  else:
130
  conversation = f"Female: {partner_text}"
131
+
132
  prompt = build_instruction(conversation, trigger, move, persona)
133
  response = partner_text.strip()
134
+
135
  training_data.append(
136
  {
137
+ "instruction": prompt,
138
+ "input": "",
139
  "output": response,
140
  }
141
  )
142
+
143
  if user_text and user_text.strip() != "_":
144
  conversation_history.append(f"Male: {user_text}")
145
  if partner_text and partner_text.strip() != "_":
146
  conversation_history.append(f"Female: {partner_text}")
147
+
148
  max_history = 4
149
  if len(conversation_history) > max_history:
150
  conversation_history = conversation_history[-max_history:]
151
+
152
  return training_data
153
 
154
 
 
268
  use_quantization = False
269
  quant_config = None
270
  if args.model_arch == "causal":
271
+ try:
272
+ import bitsandbytes as bnb
273
  quant_config = BitsAndBytesConfig(
274
+ load_in_4bit=True,
275
+ bnb_4bit_quant_type="nf4",
276
+ bnb_4bit_compute_dtype=torch.float16,
277
+ bnb_4bit_use_double_quant=True,
278
+ )
279
+ use_quantization = True
280
+ print("4-bit quantization enabled")
281
+ except (ImportError, ModuleNotFoundError) as e:
282
+ print(f"Warning: BitsAndBytesConfig not available ({e}), loading model without quantization...")
283
 
284
  model = None
285
  last_error = None
 
313
  if use_quantization:
314
  try:
315
  model = load_base_model(use_quant=True)
316
+ print("Model loaded with 4-bit quantization")
317
+ except Exception as e:
318
+ last_error = e
319
+ print(f"Failed to load with quantization: {e}")
320
  model = None
321
+ if model is None:
322
+ try:
323
  model = load_base_model(use_quant=False)
324
+ print("Model loaded without quantization (may use more memory)")
325
  except Exception as e:
326
  if last_error:
327
  print(f"Original error: {last_error}")
 
356
 
357
  # Configure LoRA
358
  if args.model_arch == "causal":
359
+ lora_config = LoraConfig(
360
+ r=16,
361
+ lora_alpha=32,
362
+ target_modules=["q_proj", "v_proj", "k_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
363
+ lora_dropout=0.05,
364
+ bias="none",
365
+ task_type="CAUSAL_LM",
366
+ )
367
  else:
368
  lora_config = LoraConfig(
369
  r=16,
 
416
  training_args = TrainingArguments(
417
  eval_strategy="steps",
418
  **training_common_kwargs,
419
+ )
420
 
421
  # Create Trainer
422
  trainer = Trainer(
finetuned_reply_service.py CHANGED
@@ -86,12 +86,12 @@ class FinetunedReplyService:
86
 
87
  if self.model_arch == "causal" and torch.cuda.is_available():
88
  try:
89
- bnb_config = BitsAndBytesConfig(
90
- load_in_4bit=True,
91
- bnb_4bit_quant_type="nf4",
92
- bnb_4bit_compute_dtype=torch.float16,
93
- bnb_4bit_use_double_quant=True,
94
- )
95
  quant_kwargs["quantization_config"] = bnb_config
96
  except Exception as exc:
97
  print(f"Warning: Could not enable 4-bit quantization ({exc}). Falling back to full precision.")
@@ -100,7 +100,7 @@ class FinetunedReplyService:
100
  if self.model_arch == "encoder_decoder":
101
  model = EncoderDecoderModel.from_encoder_decoder_pretrained(
102
  self.base_model_name,
103
- self.base_model_name,
104
  tie_encoder_decoder=True,
105
  )
106
  model.config.decoder_start_token_id = getattr(self.tokenizer, "bos_token_id", self.tokenizer.cls_token_id)
@@ -108,7 +108,7 @@ class FinetunedReplyService:
108
  model.config.vocab_size = model.config.encoder.vocab_size
109
  return model.to(self.device)
110
  kwargs = dict(
111
- trust_remote_code=True,
112
  torch_dtype=dtype,
113
  device_map=device_map,
114
  token=self.api_token,
@@ -130,8 +130,8 @@ class FinetunedReplyService:
130
  has_lora = os.path.exists(adapter_config)
131
  if has_lora:
132
  try:
133
- print(f"Loading fine-tuned weights from {self.finetuned_model_path}")
134
- self.model = PeftModel.from_pretrained(base_model, self.finetuned_model_path)
135
  except FileNotFoundError as exc:
136
  print(f"Adapter files incomplete ({exc}). Falling back to base model.")
137
  self.model = base_model
@@ -185,7 +185,7 @@ class FinetunedReplyService:
185
  attention_mask=inputs.get("attention_mask"),
186
  )
187
  else:
188
- inputs = self.tokenizer(prompt, return_tensors="pt").to(self.device)
189
  generate_kwargs = dict(**inputs)
190
 
191
  with torch.no_grad():
 
86
 
87
  if self.model_arch == "causal" and torch.cuda.is_available():
88
  try:
89
+ bnb_config = BitsAndBytesConfig(
90
+ load_in_4bit=True,
91
+ bnb_4bit_quant_type="nf4",
92
+ bnb_4bit_compute_dtype=torch.float16,
93
+ bnb_4bit_use_double_quant=True,
94
+ )
95
  quant_kwargs["quantization_config"] = bnb_config
96
  except Exception as exc:
97
  print(f"Warning: Could not enable 4-bit quantization ({exc}). Falling back to full precision.")
 
100
  if self.model_arch == "encoder_decoder":
101
  model = EncoderDecoderModel.from_encoder_decoder_pretrained(
102
  self.base_model_name,
103
+ self.base_model_name,
104
  tie_encoder_decoder=True,
105
  )
106
  model.config.decoder_start_token_id = getattr(self.tokenizer, "bos_token_id", self.tokenizer.cls_token_id)
 
108
  model.config.vocab_size = model.config.encoder.vocab_size
109
  return model.to(self.device)
110
  kwargs = dict(
111
+ trust_remote_code=True,
112
  torch_dtype=dtype,
113
  device_map=device_map,
114
  token=self.api_token,
 
130
  has_lora = os.path.exists(adapter_config)
131
  if has_lora:
132
  try:
133
+ print(f"Loading fine-tuned weights from {self.finetuned_model_path}")
134
+ self.model = PeftModel.from_pretrained(base_model, self.finetuned_model_path)
135
  except FileNotFoundError as exc:
136
  print(f"Adapter files incomplete ({exc}). Falling back to base model.")
137
  self.model = base_model
 
185
  attention_mask=inputs.get("attention_mask"),
186
  )
187
  else:
188
+ inputs = self.tokenizer(prompt, return_tensors="pt").to(self.device)
189
  generate_kwargs = dict(**inputs)
190
 
191
  with torch.no_grad():
openai_models_cache.json ADDED
@@ -0,0 +1,512 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "gpt-4-0613",
4
+ "created": 1686588896,
5
+ "owned_by": "openai"
6
+ },
7
+ {
8
+ "id": "gpt-4",
9
+ "created": 1687882411,
10
+ "owned_by": "openai"
11
+ },
12
+ {
13
+ "id": "gpt-3.5-turbo",
14
+ "created": 1677610602,
15
+ "owned_by": "openai"
16
+ },
17
+ {
18
+ "id": "gpt-5.1-codex-mini",
19
+ "created": 1763007109,
20
+ "owned_by": "system"
21
+ },
22
+ {
23
+ "id": "gpt-5.1-chat-latest",
24
+ "created": 1762547951,
25
+ "owned_by": "system"
26
+ },
27
+ {
28
+ "id": "gpt-5.1-2025-11-13",
29
+ "created": 1762800353,
30
+ "owned_by": "system"
31
+ },
32
+ {
33
+ "id": "gpt-5.1",
34
+ "created": 1762800673,
35
+ "owned_by": "system"
36
+ },
37
+ {
38
+ "id": "gpt-5.1-codex",
39
+ "created": 1762988221,
40
+ "owned_by": "system"
41
+ },
42
+ {
43
+ "id": "davinci-002",
44
+ "created": 1692634301,
45
+ "owned_by": "system"
46
+ },
47
+ {
48
+ "id": "babbage-002",
49
+ "created": 1692634615,
50
+ "owned_by": "system"
51
+ },
52
+ {
53
+ "id": "gpt-3.5-turbo-instruct",
54
+ "created": 1692901427,
55
+ "owned_by": "system"
56
+ },
57
+ {
58
+ "id": "gpt-3.5-turbo-instruct-0914",
59
+ "created": 1694122472,
60
+ "owned_by": "system"
61
+ },
62
+ {
63
+ "id": "dall-e-3",
64
+ "created": 1698785189,
65
+ "owned_by": "system"
66
+ },
67
+ {
68
+ "id": "dall-e-2",
69
+ "created": 1698798177,
70
+ "owned_by": "system"
71
+ },
72
+ {
73
+ "id": "gpt-4-1106-preview",
74
+ "created": 1698957206,
75
+ "owned_by": "system"
76
+ },
77
+ {
78
+ "id": "gpt-3.5-turbo-1106",
79
+ "created": 1698959748,
80
+ "owned_by": "system"
81
+ },
82
+ {
83
+ "id": "tts-1-hd",
84
+ "created": 1699046015,
85
+ "owned_by": "system"
86
+ },
87
+ {
88
+ "id": "tts-1-1106",
89
+ "created": 1699053241,
90
+ "owned_by": "system"
91
+ },
92
+ {
93
+ "id": "tts-1-hd-1106",
94
+ "created": 1699053533,
95
+ "owned_by": "system"
96
+ },
97
+ {
98
+ "id": "text-embedding-3-small",
99
+ "created": 1705948997,
100
+ "owned_by": "system"
101
+ },
102
+ {
103
+ "id": "text-embedding-3-large",
104
+ "created": 1705953180,
105
+ "owned_by": "system"
106
+ },
107
+ {
108
+ "id": "gpt-4-0125-preview",
109
+ "created": 1706037612,
110
+ "owned_by": "system"
111
+ },
112
+ {
113
+ "id": "gpt-4-turbo-preview",
114
+ "created": 1706037777,
115
+ "owned_by": "system"
116
+ },
117
+ {
118
+ "id": "gpt-3.5-turbo-0125",
119
+ "created": 1706048358,
120
+ "owned_by": "system"
121
+ },
122
+ {
123
+ "id": "gpt-4-turbo",
124
+ "created": 1712361441,
125
+ "owned_by": "system"
126
+ },
127
+ {
128
+ "id": "gpt-4-turbo-2024-04-09",
129
+ "created": 1712601677,
130
+ "owned_by": "system"
131
+ },
132
+ {
133
+ "id": "gpt-4o",
134
+ "created": 1715367049,
135
+ "owned_by": "system"
136
+ },
137
+ {
138
+ "id": "gpt-4o-2024-05-13",
139
+ "created": 1715368132,
140
+ "owned_by": "system"
141
+ },
142
+ {
143
+ "id": "gpt-4o-mini-2024-07-18",
144
+ "created": 1721172717,
145
+ "owned_by": "system"
146
+ },
147
+ {
148
+ "id": "gpt-4o-mini",
149
+ "created": 1721172741,
150
+ "owned_by": "system"
151
+ },
152
+ {
153
+ "id": "gpt-4o-2024-08-06",
154
+ "created": 1722814719,
155
+ "owned_by": "system"
156
+ },
157
+ {
158
+ "id": "chatgpt-4o-latest",
159
+ "created": 1723515131,
160
+ "owned_by": "system"
161
+ },
162
+ {
163
+ "id": "gpt-4o-realtime-preview-2024-10-01",
164
+ "created": 1727131766,
165
+ "owned_by": "system"
166
+ },
167
+ {
168
+ "id": "gpt-4o-audio-preview-2024-10-01",
169
+ "created": 1727389042,
170
+ "owned_by": "system"
171
+ },
172
+ {
173
+ "id": "gpt-4o-audio-preview",
174
+ "created": 1727460443,
175
+ "owned_by": "system"
176
+ },
177
+ {
178
+ "id": "gpt-4o-realtime-preview",
179
+ "created": 1727659998,
180
+ "owned_by": "system"
181
+ },
182
+ {
183
+ "id": "omni-moderation-latest",
184
+ "created": 1731689265,
185
+ "owned_by": "system"
186
+ },
187
+ {
188
+ "id": "omni-moderation-2024-09-26",
189
+ "created": 1732734466,
190
+ "owned_by": "system"
191
+ },
192
+ {
193
+ "id": "gpt-4o-realtime-preview-2024-12-17",
194
+ "created": 1733945430,
195
+ "owned_by": "system"
196
+ },
197
+ {
198
+ "id": "gpt-4o-audio-preview-2024-12-17",
199
+ "created": 1734034239,
200
+ "owned_by": "system"
201
+ },
202
+ {
203
+ "id": "gpt-4o-mini-realtime-preview-2024-12-17",
204
+ "created": 1734112601,
205
+ "owned_by": "system"
206
+ },
207
+ {
208
+ "id": "gpt-4o-mini-audio-preview-2024-12-17",
209
+ "created": 1734115920,
210
+ "owned_by": "system"
211
+ },
212
+ {
213
+ "id": "o1-2024-12-17",
214
+ "created": 1734326976,
215
+ "owned_by": "system"
216
+ },
217
+ {
218
+ "id": "o1",
219
+ "created": 1734375816,
220
+ "owned_by": "system"
221
+ },
222
+ {
223
+ "id": "gpt-4o-mini-realtime-preview",
224
+ "created": 1734387380,
225
+ "owned_by": "system"
226
+ },
227
+ {
228
+ "id": "gpt-4o-mini-audio-preview",
229
+ "created": 1734387424,
230
+ "owned_by": "system"
231
+ },
232
+ {
233
+ "id": "o3-mini",
234
+ "created": 1737146383,
235
+ "owned_by": "system"
236
+ },
237
+ {
238
+ "id": "o3-mini-2025-01-31",
239
+ "created": 1738010200,
240
+ "owned_by": "system"
241
+ },
242
+ {
243
+ "id": "gpt-4o-2024-11-20",
244
+ "created": 1739331543,
245
+ "owned_by": "system"
246
+ },
247
+ {
248
+ "id": "gpt-4o-search-preview-2025-03-11",
249
+ "created": 1741388170,
250
+ "owned_by": "system"
251
+ },
252
+ {
253
+ "id": "gpt-4o-search-preview",
254
+ "created": 1741388720,
255
+ "owned_by": "system"
256
+ },
257
+ {
258
+ "id": "gpt-4o-mini-search-preview-2025-03-11",
259
+ "created": 1741390858,
260
+ "owned_by": "system"
261
+ },
262
+ {
263
+ "id": "gpt-4o-mini-search-preview",
264
+ "created": 1741391161,
265
+ "owned_by": "system"
266
+ },
267
+ {
268
+ "id": "gpt-4o-transcribe",
269
+ "created": 1742068463,
270
+ "owned_by": "system"
271
+ },
272
+ {
273
+ "id": "gpt-4o-mini-transcribe",
274
+ "created": 1742068596,
275
+ "owned_by": "system"
276
+ },
277
+ {
278
+ "id": "o1-pro-2025-03-19",
279
+ "created": 1742251504,
280
+ "owned_by": "system"
281
+ },
282
+ {
283
+ "id": "o1-pro",
284
+ "created": 1742251791,
285
+ "owned_by": "system"
286
+ },
287
+ {
288
+ "id": "gpt-4o-mini-tts",
289
+ "created": 1742403959,
290
+ "owned_by": "system"
291
+ },
292
+ {
293
+ "id": "o3-2025-04-16",
294
+ "created": 1744133301,
295
+ "owned_by": "system"
296
+ },
297
+ {
298
+ "id": "o4-mini-2025-04-16",
299
+ "created": 1744133506,
300
+ "owned_by": "system"
301
+ },
302
+ {
303
+ "id": "o3",
304
+ "created": 1744225308,
305
+ "owned_by": "system"
306
+ },
307
+ {
308
+ "id": "o4-mini",
309
+ "created": 1744225351,
310
+ "owned_by": "system"
311
+ },
312
+ {
313
+ "id": "gpt-4.1-2025-04-14",
314
+ "created": 1744315746,
315
+ "owned_by": "system"
316
+ },
317
+ {
318
+ "id": "gpt-4.1",
319
+ "created": 1744316542,
320
+ "owned_by": "system"
321
+ },
322
+ {
323
+ "id": "gpt-4.1-mini-2025-04-14",
324
+ "created": 1744317547,
325
+ "owned_by": "system"
326
+ },
327
+ {
328
+ "id": "gpt-4.1-mini",
329
+ "created": 1744318173,
330
+ "owned_by": "system"
331
+ },
332
+ {
333
+ "id": "gpt-4.1-nano-2025-04-14",
334
+ "created": 1744321025,
335
+ "owned_by": "system"
336
+ },
337
+ {
338
+ "id": "gpt-4.1-nano",
339
+ "created": 1744321707,
340
+ "owned_by": "system"
341
+ },
342
+ {
343
+ "id": "gpt-image-1",
344
+ "created": 1745517030,
345
+ "owned_by": "system"
346
+ },
347
+ {
348
+ "id": "codex-mini-latest",
349
+ "created": 1746673257,
350
+ "owned_by": "system"
351
+ },
352
+ {
353
+ "id": "gpt-4o-realtime-preview-2025-06-03",
354
+ "created": 1748907838,
355
+ "owned_by": "system"
356
+ },
357
+ {
358
+ "id": "gpt-4o-audio-preview-2025-06-03",
359
+ "created": 1748908498,
360
+ "owned_by": "system"
361
+ },
362
+ {
363
+ "id": "o4-mini-deep-research",
364
+ "created": 1749685485,
365
+ "owned_by": "system"
366
+ },
367
+ {
368
+ "id": "gpt-4o-transcribe-diarize",
369
+ "created": 1750798887,
370
+ "owned_by": "system"
371
+ },
372
+ {
373
+ "id": "o4-mini-deep-research-2025-06-26",
374
+ "created": 1750866121,
375
+ "owned_by": "system"
376
+ },
377
+ {
378
+ "id": "gpt-5-chat-latest",
379
+ "created": 1754073306,
380
+ "owned_by": "system"
381
+ },
382
+ {
383
+ "id": "gpt-5-2025-08-07",
384
+ "created": 1754075360,
385
+ "owned_by": "system"
386
+ },
387
+ {
388
+ "id": "gpt-5",
389
+ "created": 1754425777,
390
+ "owned_by": "system"
391
+ },
392
+ {
393
+ "id": "gpt-5-mini-2025-08-07",
394
+ "created": 1754425867,
395
+ "owned_by": "system"
396
+ },
397
+ {
398
+ "id": "gpt-5-mini",
399
+ "created": 1754425928,
400
+ "owned_by": "system"
401
+ },
402
+ {
403
+ "id": "gpt-5-nano-2025-08-07",
404
+ "created": 1754426303,
405
+ "owned_by": "system"
406
+ },
407
+ {
408
+ "id": "gpt-5-nano",
409
+ "created": 1754426384,
410
+ "owned_by": "system"
411
+ },
412
+ {
413
+ "id": "gpt-audio-2025-08-28",
414
+ "created": 1756256146,
415
+ "owned_by": "system"
416
+ },
417
+ {
418
+ "id": "gpt-realtime",
419
+ "created": 1756271701,
420
+ "owned_by": "system"
421
+ },
422
+ {
423
+ "id": "gpt-realtime-2025-08-28",
424
+ "created": 1756271773,
425
+ "owned_by": "system"
426
+ },
427
+ {
428
+ "id": "gpt-audio",
429
+ "created": 1756339249,
430
+ "owned_by": "system"
431
+ },
432
+ {
433
+ "id": "gpt-5-codex",
434
+ "created": 1757527818,
435
+ "owned_by": "system"
436
+ },
437
+ {
438
+ "id": "gpt-image-1-mini",
439
+ "created": 1758845821,
440
+ "owned_by": "system"
441
+ },
442
+ {
443
+ "id": "gpt-5-pro-2025-10-06",
444
+ "created": 1759469707,
445
+ "owned_by": "system"
446
+ },
447
+ {
448
+ "id": "gpt-5-pro",
449
+ "created": 1759469822,
450
+ "owned_by": "system"
451
+ },
452
+ {
453
+ "id": "gpt-audio-mini",
454
+ "created": 1759512027,
455
+ "owned_by": "system"
456
+ },
457
+ {
458
+ "id": "gpt-audio-mini-2025-10-06",
459
+ "created": 1759512137,
460
+ "owned_by": "system"
461
+ },
462
+ {
463
+ "id": "gpt-5-search-api",
464
+ "created": 1759514629,
465
+ "owned_by": "system"
466
+ },
467
+ {
468
+ "id": "gpt-realtime-mini",
469
+ "created": 1759517133,
470
+ "owned_by": "system"
471
+ },
472
+ {
473
+ "id": "gpt-realtime-mini-2025-10-06",
474
+ "created": 1759517175,
475
+ "owned_by": "system"
476
+ },
477
+ {
478
+ "id": "sora-2",
479
+ "created": 1759708615,
480
+ "owned_by": "system"
481
+ },
482
+ {
483
+ "id": "sora-2-pro",
484
+ "created": 1759708663,
485
+ "owned_by": "system"
486
+ },
487
+ {
488
+ "id": "gpt-5-search-api-2025-10-14",
489
+ "created": 1760043960,
490
+ "owned_by": "system"
491
+ },
492
+ {
493
+ "id": "gpt-3.5-turbo-16k",
494
+ "created": 1683758102,
495
+ "owned_by": "openai-internal"
496
+ },
497
+ {
498
+ "id": "tts-1",
499
+ "created": 1681940951,
500
+ "owned_by": "openai-internal"
501
+ },
502
+ {
503
+ "id": "whisper-1",
504
+ "created": 1677532384,
505
+ "owned_by": "openai-internal"
506
+ },
507
+ {
508
+ "id": "text-embedding-ada-002",
509
+ "created": 1671217299,
510
+ "owned_by": "openai-internal"
511
+ }
512
+ ]
openai_service.py ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Service for generating replies using OpenAI API.
3
+ """
4
+ import os
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Optional, List, Dict, Any
8
+ from openai import OpenAI
9
+
10
+ # Reuse the same system prompt from Perplexity service
11
+ SYSTEM_PROMPT = """
12
+ 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".
13
+
14
+ Bạn được cung cấp:
15
+
16
+ - 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 "|||".
17
+
18
+ - TRIGGER: intent hiện tại (ví dụ: neutral, positive, negative, confused...).
19
+
20
+ - MOVE: chiến lược hiện tại (ví dụ: escalate, hold, de-escalate, tease, comfort...).
21
+
22
+ Nhiệm vụ của bạn:
23
+
24
+ - 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).
25
+
26
+ - Ư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.
27
+
28
+ QUY TẮC CỨNG:
29
+
30
+ - Chỉ trả về đúng 1 câu duy nhất.
31
+
32
+ - Tối đa 25 từ tiếng Việt.
33
+
34
+ - Lịch sự, ấm áp, thân thiện; không phán xét, không thô lỗ.
35
+
36
+ - Không giải thích meta (không nói về "prompt", "AI", "chiến lược", "MOVE", "TRIGGER"...).
37
+
38
+ - Không lặp lại nguyên văn câu của đối phương.
39
+
40
+ - 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ó).
41
+
42
+ Khi TRIGGER hoặc MOVE có vẻ mâu thuẫn với HỘI THOẠI:
43
+
44
+ - Hãy ưu tiên sự an toàn và mềm mại.
45
+
46
+ - 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.
47
+
48
+ PHONG CÁCH:
49
+
50
+ - Ấm áp, tự tin nhưng không tự cao.
51
+
52
+ - Có thể dùng từ đệm tự nhiên (nha, nhé, ạ, dạ) khi phù hợp với ngữ cảnh.
53
+
54
+ - Phản chiếu cảm xúc của đối phương.
55
+
56
+ - Giữ mạch trò chuyện mở để còn đất tăng tương tác về sau.
57
+
58
+ 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:
59
+
60
+ - 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.
61
+ """.strip()
62
+
63
+ MODELS_CACHE_PATH = Path(__file__).resolve().parent / "config" / "ai_models.json"
64
+ ENV = os.getenv("APP_ENV", "development").lower()
65
+
66
+
67
+ def get_openai_client() -> OpenAI:
68
+ """Get OpenAI client with API key from environment."""
69
+ api_key = os.getenv("OPENAI_API_KEY")
70
+ if not api_key:
71
+ raise ValueError(
72
+ "OpenAI API key is required.\n\n"
73
+ "Set environment variable:\n"
74
+ " export OPENAI_API_KEY=sk-...\n\n"
75
+ "Or add to .env file."
76
+ )
77
+ return OpenAI(api_key=api_key)
78
+
79
+
80
+ def fetch_and_cache_models_if_needed() -> List[Dict[str, Any]]:
81
+ """
82
+ Local/dev:
83
+ - If cache file does not exist -> call OpenAI models.list() and save to JSON
84
+ - If cache file exists -> just read
85
+ Production:
86
+ - Never call OpenAI models.list(), only read from JSON
87
+ """
88
+ # If cache exists, always use it
89
+ if MODELS_CACHE_PATH.exists():
90
+ with MODELS_CACHE_PATH.open("r", encoding="utf-8") as f:
91
+ return json.load(f)
92
+
93
+ # If production and cache missing: fail fast
94
+ if ENV in ("production", "prod"):
95
+ raise RuntimeError(
96
+ f"Models cache not found at {MODELS_CACHE_PATH}. "
97
+ f"Generate it in development before deploying."
98
+ )
99
+
100
+ # Dev/test: call OpenAI and write cache
101
+ client = get_openai_client()
102
+ models = client.models.list() # returns a ModelList object
103
+ data = []
104
+ for m in models.data:
105
+ data.append(
106
+ {
107
+ "id": m.id,
108
+ "created": getattr(m, "created", None),
109
+ "owned_by": getattr(m, "owned_by", None),
110
+ }
111
+ )
112
+ MODELS_CACHE_PATH.parent.mkdir(parents=True, exist_ok=True)
113
+ with MODELS_CACHE_PATH.open("w", encoding="utf-8") as f:
114
+ json.dump(data, f, ensure_ascii=False, indent=2)
115
+ return data
116
+
117
+
118
+ def get_available_models(prefix_filters: Optional[List[str]] = None) -> List[str]:
119
+ """
120
+ Return list of model ids from cache (generate cache in dev if needed).
121
+ Optional: filter by prefix, e.g. ["gpt-4", "gpt-3.5"].
122
+ """
123
+ models = fetch_and_cache_models_if_needed()
124
+ ids = [m["id"] for m in models]
125
+ if prefix_filters:
126
+ filtered = []
127
+ for mid in ids:
128
+ if any(mid.startswith(p) for p in prefix_filters):
129
+ filtered.append(mid)
130
+ return filtered
131
+ return ids
132
+
133
+
134
+ def create_chat_response(
135
+ model: str,
136
+ user_message: str,
137
+ ) -> str:
138
+ """
139
+ Use chosen model to create a simple text response via Responses API.
140
+ """
141
+ client = get_openai_client()
142
+ resp = client.responses.create(
143
+ model=model,
144
+ input=user_message,
145
+ )
146
+ # Responses API trả về output dạng structured
147
+ # resp.output[0].content[0].text là string trực tiếp
148
+ if resp.output and len(resp.output) > 0:
149
+ first_output = resp.output[0]
150
+ if first_output.content and len(first_output.content) > 0:
151
+ first_content = first_output.content[0]
152
+ if hasattr(first_content, 'text'):
153
+ text = first_content.text
154
+ # text là string trực tiếp
155
+ return str(text) if text else ""
156
+
157
+ # Fallback: use output_text if available
158
+ if hasattr(resp, 'output_text') and resp.output_text:
159
+ return str(resp.output_text)
160
+
161
+ raise ValueError("No text found in response")
162
+
163
+
164
+ class OpenAIReplyService:
165
+ """Service for generating replies using OpenAI API."""
166
+
167
+ def __init__(self, api_key: Optional[str] = None, model_name: str = "gpt-4o-mini"):
168
+ """
169
+ Initialize OpenAI service.
170
+
171
+ Args:
172
+ api_key: OpenAI API key. If None, will try to get from OPENAI_API_KEY env var.
173
+ model_name: Model name to use (default: "gpt-4o-mini")
174
+ """
175
+ if api_key:
176
+ os.environ["OPENAI_API_KEY"] = api_key
177
+
178
+ self.model_name = model_name
179
+ self.client = get_openai_client()
180
+
181
+ def generate_reply(
182
+ self,
183
+ conversation: str,
184
+ trigger: str,
185
+ move: str,
186
+ ) -> str:
187
+ """
188
+ Generate reply using OpenAI API.
189
+
190
+ Args:
191
+ conversation: Conversation text in format "Male: ... ||| Female: ..."
192
+ trigger: Trigger label (e.g., "rapport_bid", "flirt_charm")
193
+ move: Move label (e.g., "charm", "invite", "validate")
194
+
195
+ Returns:
196
+ Generated reply text (1 sentence, ≤25 words)
197
+ """
198
+ user_content = f"""
199
+ HỘI THOẠI: "{conversation}"
200
+ TRIGGER: "{trigger}"
201
+ MOVE: "{move}"
202
+ """.strip()
203
+
204
+ # Combine system prompt and user content
205
+ full_prompt = f"{SYSTEM_PROMPT}\n\n{user_content}"
206
+
207
+ try:
208
+ # Use Responses API
209
+ response = create_chat_response(
210
+ model=self.model_name,
211
+ user_message=full_prompt,
212
+ )
213
+
214
+ raw = response.strip() if response else ""
215
+
216
+ # Hậu xử lý: lấy câu đầu, giới hạn 25 từ
217
+ import re
218
+ # Tách theo dấu câu, lấy câu đầu
219
+ sentences = re.split(r'[.!?]', raw)
220
+ one_sentence = sentences[0].strip() if sentences else raw.strip()
221
+
222
+ # Giới hạn 25 từ
223
+ words = one_sentence.split()
224
+ limited = " ".join(words[:25])
225
+
226
+ # Đảm bảo kết thúc bằng dấu câu nếu cần
227
+ if limited and not limited[-1] in ".!?":
228
+ limited = limited.rstrip(",;:") + "."
229
+
230
+ return limited
231
+
232
+ except Exception as e:
233
+ error_msg = str(e)
234
+ raise Exception(f"OpenAI API error: {error_msg}")
235
+
236
+
237
+ # Global singleton instance
238
+ _openai_service = None
239
+
240
+
241
+ def get_openai_service(
242
+ api_key: Optional[str] = None,
243
+ model_name: str = "gpt-4o-mini",
244
+ ) -> OpenAIReplyService:
245
+ """Get or create the global OpenAI service instance."""
246
+ global _openai_service
247
+ if _openai_service is None or _openai_service.model_name != model_name:
248
+ _openai_service = OpenAIReplyService(api_key=api_key, model_name=model_name)
249
+ return _openai_service
250
+
setup_and_finetune.py CHANGED
@@ -166,17 +166,17 @@ def run_training_plan(tasks):
166
  continue
167
  print(f"[START] Training {task['name']} ...")
168
  try:
169
- result = subprocess.run(
170
  task["command"],
171
- capture_output=True,
172
- text=True,
173
- check=False
174
- )
175
- if result.returncode == 0:
176
  print(f"[DONE] {task['name']} completed successfully.")
177
- else:
178
  print(f"[FAIL] {task['name']} exited with error code {result.returncode}")
179
- print(result.stderr)
180
  except Exception as exc:
181
  print(f"[ERROR] {task['name']} -> {exc}")
182
  print("All auto-training tasks finished.")
 
166
  continue
167
  print(f"[START] Training {task['name']} ...")
168
  try:
169
+ result = subprocess.run(
170
  task["command"],
171
+ capture_output=True,
172
+ text=True,
173
+ check=False
174
+ )
175
+ if result.returncode == 0:
176
  print(f"[DONE] {task['name']} completed successfully.")
177
+ else:
178
  print(f"[FAIL] {task['name']} exited with error code {result.returncode}")
179
+ print(result.stderr)
180
  except Exception as exc:
181
  print(f"[ERROR] {task['name']} -> {exc}")
182
  print("All auto-training tasks finished.")
test_all_openai_models.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test all OpenAI 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 openai_service import get_available_models, get_openai_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
+
21
+ def test_model(model_name: str, max_retries: int = 2) -> tuple[bool, str, str]:
22
+ """
23
+ Test a single OpenAI model.
24
+
25
+ Returns:
26
+ (success: bool, reply: str, error: str)
27
+ """
28
+ for attempt in range(max_retries):
29
+ try:
30
+ service = get_openai_service(model_name=model_name)
31
+ formatted_conversation = f"Male: {TEST_CONVERSATION.split('|||')[0].strip()} ||| Female: {TEST_CONVERSATION.split('|||')[1].strip()}"
32
+
33
+ reply = service.generate_reply(
34
+ conversation=formatted_conversation,
35
+ trigger=TEST_TRIGGER,
36
+ move=TEST_MOVE,
37
+ )
38
+
39
+ if reply and len(reply.strip()) > 0:
40
+ return True, reply, ""
41
+ else:
42
+ return False, "", "Empty reply"
43
+
44
+ except Exception as e:
45
+ error_msg = str(e)
46
+ if attempt < max_retries - 1:
47
+ continue # Retry
48
+ return False, "", error_msg
49
+
50
+ return False, "", "Max retries exceeded"
51
+
52
+
53
+ def main():
54
+ """Test all OpenAI models and create whitelist."""
55
+ print("=" * 60)
56
+ print("Testing All OpenAI Models for Whitelist")
57
+ print("=" * 60)
58
+
59
+ # Fetch all available models
60
+ try:
61
+ print("\nFetching available OpenAI models...")
62
+ # Filter for common chat models
63
+ all_model_ids = get_available_models(prefix_filters=["gpt-", "o1-", "o3-"])
64
+ print(f"✓ Found {len(all_model_ids)} models with prefixes: gpt-, o1-, o3-")
65
+ except Exception as e:
66
+ print(f"✗ Error fetching models: {str(e)}")
67
+ return 1
68
+
69
+ # Test each model
70
+ whitelist = []
71
+ failed_models = []
72
+
73
+ print(f"\nTesting {len(all_model_ids)} models...")
74
+ print("=" * 60)
75
+
76
+ for idx, model_id in enumerate(all_model_ids, 1):
77
+ print(f"\n[{idx}/{len(all_model_ids)}] Testing: {model_id}")
78
+
79
+ success, reply, error = test_model(model_id)
80
+
81
+ if success:
82
+ print(f" ✓ PASSED - Reply: {reply[:60]}...")
83
+ whitelist.append({
84
+ "id": model_id,
85
+ "name": model_id,
86
+ "displayName": model_id,
87
+ "test_reply": reply[:100], # Store sample reply
88
+ })
89
+ else:
90
+ print(f" ✗ FAILED - {error[:100]}")
91
+ failed_models.append({
92
+ "id": model_id,
93
+ "name": model_id,
94
+ "error": error[:200],
95
+ })
96
+
97
+ # Save whitelist
98
+ whitelist_file = Path(__file__).parent / "openai_models_whitelist.json"
99
+ with open(whitelist_file, "w", encoding="utf-8") as f:
100
+ json.dump({
101
+ "whitelist": whitelist,
102
+ "failed": failed_models,
103
+ "test_data": {
104
+ "conversation": TEST_CONVERSATION,
105
+ "trigger": TEST_TRIGGER,
106
+ "move": TEST_MOVE,
107
+ },
108
+ "total_tested": len(all_model_ids),
109
+ "passed": len(whitelist),
110
+ "failed": len(failed_models),
111
+ }, f, indent=2, ensure_ascii=False)
112
+
113
+ # Print summary
114
+ print("\n" + "=" * 60)
115
+ print("Test Summary")
116
+ print("=" * 60)
117
+ print(f"Total models tested: {len(all_model_ids)}")
118
+ print(f"✓ Passed (whitelist): {len(whitelist)}")
119
+ print(f"✗ Failed: {len(failed_models)}")
120
+ print(f"\nWhitelist saved to: {whitelist_file}")
121
+
122
+ if whitelist:
123
+ print("\n✓ Working models (whitelist):")
124
+ for model in whitelist:
125
+ print(f" - {model['displayName']} ({model['id']})")
126
+
127
+ if failed_models:
128
+ print("\n✗ Failed models:")
129
+ for model in failed_models[:10]: # Show first 10
130
+ print(f" - {model['name']}: {model['error'][:50]}...")
131
+ if len(failed_models) > 10:
132
+ print(f" ... and {len(failed_models) - 10} more")
133
+
134
+ return 0 if whitelist else 1
135
+
136
+
137
+ if __name__ == "__main__":
138
+ exit(main())
139
+
test_openai_list_models.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test script to fetch OpenAI models list.
3
+ """
4
+ import os
5
+ from pathlib import Path
6
+ from dotenv import load_dotenv
7
+ from openai import OpenAI
8
+
9
+ # Load .env file
10
+ env_path = Path(__file__).parent / '.env'
11
+ if env_path.exists():
12
+ load_dotenv(env_path)
13
+ print(f"✓ Loaded .env file from {env_path}")
14
+ else:
15
+ print(f"⚠ .env file not found at {env_path}, using environment variables only")
16
+
17
+ # Get API key
18
+ api_key = os.getenv("OPENAI_API_KEY")
19
+ if not api_key:
20
+ print("❌ OPENAI_API_KEY not found in environment variables")
21
+ print("\nPlease set it:")
22
+ print(" export OPENAI_API_KEY=sk-...")
23
+ exit(1)
24
+
25
+ print(f"✓ OpenAI API Key found: {api_key[:10]}...{api_key[-4:]}")
26
+
27
+ # Create client
28
+ try:
29
+ client = OpenAI(api_key=api_key)
30
+ print("✓ OpenAI client created successfully")
31
+ except Exception as e:
32
+ print(f"✗ Error creating OpenAI client: {e}")
33
+ exit(1)
34
+
35
+ # Fetch models
36
+ print("\n" + "=" * 60)
37
+ print("Fetching OpenAI models list...")
38
+ print("=" * 60)
39
+
40
+ try:
41
+ models = client.models.list()
42
+ print(f"✓ Successfully fetched models list")
43
+ print(f" Total models: {len(models.data)}")
44
+
45
+ # Filter for common chat models
46
+ chat_models = []
47
+ prefixes = ["gpt-", "o1-", "o3-"]
48
+
49
+ for model in models.data:
50
+ model_id = model.id
51
+ if any(model_id.startswith(prefix) for prefix in prefixes):
52
+ chat_models.append({
53
+ "id": model_id,
54
+ "created": getattr(model, "created", None),
55
+ "owned_by": getattr(model, "owned_by", None),
56
+ })
57
+
58
+ print(f"\n✓ Found {len(chat_models)} chat models (filtered by prefixes: {prefixes})")
59
+
60
+ print("\n" + "=" * 60)
61
+ print("Chat Models List:")
62
+ print("=" * 60)
63
+ for idx, model in enumerate(chat_models[:20], 1): # Show first 20
64
+ created = model.get("created")
65
+ owned_by = model.get("owned_by", "unknown")
66
+ print(f"{idx}. {model['id']} (owned_by: {owned_by}, created: {created})")
67
+
68
+ if len(chat_models) > 20:
69
+ print(f"\n... and {len(chat_models) - 20} more models")
70
+
71
+ # Save to cache
72
+ cache_path = Path(__file__).parent / "config" / "ai_models.json"
73
+ cache_path.parent.mkdir(parents=True, exist_ok=True)
74
+
75
+ import json
76
+ all_models_data = []
77
+ for model in models.data:
78
+ all_models_data.append({
79
+ "id": model.id,
80
+ "created": getattr(model, "created", None),
81
+ "owned_by": getattr(model, "owned_by", None),
82
+ })
83
+
84
+ with open(cache_path, "w", encoding="utf-8") as f:
85
+ json.dump(all_models_data, f, ensure_ascii=False, indent=2)
86
+
87
+ print(f"\n✓ Saved all models to cache: {cache_path}")
88
+ print(f" Total models cached: {len(all_models_data)}")
89
+
90
+ print("\n" + "=" * 60)
91
+ print("Test Summary")
92
+ print("=" * 60)
93
+ print(f"✓ API connection: SUCCESS")
94
+ print(f"✓ Models fetched: {len(models.data)}")
95
+ print(f"✓ Chat models (filtered): {len(chat_models)}")
96
+ print(f"✓ Cache saved: {cache_path}")
97
+
98
+ except Exception as e:
99
+ print(f"\n✗ Error fetching models: {str(e)}")
100
+ import traceback
101
+ traceback.print_exc()
102
+ exit(1)
103
+
test_openai_response.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test OpenAI Responses API với một model.
3
+ """
4
+ import os
5
+ from pathlib import Path
6
+ from dotenv import load_dotenv
7
+ from openai import OpenAI
8
+
9
+ # Load .env file
10
+ env_path = Path(__file__).parent / '.env'
11
+ if env_path.exists():
12
+ load_dotenv(env_path)
13
+
14
+ api_key = os.getenv("OPENAI_API_KEY")
15
+ if not api_key:
16
+ print("❌ OPENAI_API_KEY not found")
17
+ exit(1)
18
+
19
+ client = OpenAI(api_key=api_key)
20
+
21
+ # Test data
22
+ SYSTEM_PROMPT = "You are a helpful assistant. Respond briefly in Vietnamese."
23
+ USER_MESSAGE = "Xin chào, bạn khỏe không?"
24
+
25
+ print("=" * 60)
26
+ print("Testing OpenAI Responses API")
27
+ print("=" * 60)
28
+
29
+ # Test với gpt-4o-mini
30
+ model = "gpt-4o-mini"
31
+ print(f"\nTesting model: {model}")
32
+ print(f"System prompt: {SYSTEM_PROMPT}")
33
+ print(f"User message: {USER_MESSAGE}")
34
+
35
+ try:
36
+ full_prompt = f"{SYSTEM_PROMPT}\n\n{USER_MESSAGE}"
37
+
38
+ print("\nCalling client.responses.create()...")
39
+ resp = client.responses.create(
40
+ model=model,
41
+ input=full_prompt,
42
+ )
43
+
44
+ print(f"✓ Response received")
45
+ print(f" Response type: {type(resp)}")
46
+ print(f" Response attributes: {dir(resp)}")
47
+
48
+ # Extract text
49
+ if hasattr(resp, 'output') and resp.output:
50
+ print(f" Output type: {type(resp.output)}")
51
+ print(f" Output length: {len(resp.output) if hasattr(resp.output, '__len__') else 'N/A'}")
52
+
53
+ if len(resp.output) > 0:
54
+ first_output = resp.output[0]
55
+ print(f" First output type: {type(first_output)}")
56
+ print(f" First output attributes: {dir(first_output)}")
57
+
58
+ if hasattr(first_output, 'content') and first_output.content:
59
+ print(f" Content type: {type(first_output.content)}")
60
+ print(f" Content length: {len(first_output.content) if hasattr(first_output.content, '__len__') else 'N/A'}")
61
+
62
+ if len(first_output.content) > 0:
63
+ first_content = first_output.content[0]
64
+ print(f" First content type: {type(first_content)}")
65
+ print(f" First content attributes: {dir(first_content)}")
66
+
67
+ if hasattr(first_content, 'text'):
68
+ text_obj = first_content.text
69
+ print(f" Text object type: {type(text_obj)}")
70
+ print(f" Text object attributes: {dir(text_obj)}")
71
+
72
+ if hasattr(text_obj, 'value'):
73
+ text_value = text_obj.value
74
+ print(f"\n✓ SUCCESS!")
75
+ print(f" Response text: {text_value}")
76
+ else:
77
+ print(f" Text object: {text_obj}")
78
+ else:
79
+ print(f" First content: {first_content}")
80
+ else:
81
+ print(" No content found")
82
+ else:
83
+ print(" No content attribute found")
84
+ else:
85
+ print(" Output is empty")
86
+ else:
87
+ print(f" Response object: {resp}")
88
+ print(f" Full response: {resp}")
89
+
90
+ except Exception as e:
91
+ print(f"\n✗ Error: {str(e)}")
92
+ import traceback
93
+ traceback.print_exc()
94
+
trigger_move_identifier.py CHANGED
@@ -64,7 +64,7 @@ class TriggerMoveIdentifier:
64
  "pivot_disreengage",
65
  "neutral",
66
  ]
67
-
68
  DEFAULT_MOVE_LABELS = [
69
  "spark",
70
  "intrigue",
@@ -92,7 +92,7 @@ class TriggerMoveIdentifier:
92
  "relate",
93
  "neutral",
94
  ]
95
-
96
  def __init__(
97
  self,
98
  model_dir: str = "./models/trigger_detector",
@@ -212,13 +212,13 @@ class TriggerMoveIdentifier:
212
  "Respond using format 'Trigger: <trigger> | Move: <move>'"
213
  )
214
 
215
- try:
216
- response = self.client.text_generation(
217
- prompt,
218
  max_new_tokens=64,
219
- temperature=0.3,
220
  return_full_text=False,
221
- )
222
  return self._parse_response(response)
223
  except Exception as exc:
224
  print(f"Inference API error: {exc}, falling back to heuristics.")
 
64
  "pivot_disreengage",
65
  "neutral",
66
  ]
67
+
68
  DEFAULT_MOVE_LABELS = [
69
  "spark",
70
  "intrigue",
 
92
  "relate",
93
  "neutral",
94
  ]
95
+
96
  def __init__(
97
  self,
98
  model_dir: str = "./models/trigger_detector",
 
212
  "Respond using format 'Trigger: <trigger> | Move: <move>'"
213
  )
214
 
215
+ try:
216
+ response = self.client.text_generation(
217
+ prompt,
218
  max_new_tokens=64,
219
+ temperature=0.3,
220
  return_full_text=False,
221
+ )
222
  return self._parse_response(response)
223
  except Exception as exc:
224
  print(f"Inference API error: {exc}, falling back to heuristics.")