Asmit Nayak commited on
Commit
72d1624
Β·
1 Parent(s): 23fbfee

Refactor Gemini analysis to improve CSV response parsing and validation

Browse files
Files changed (1) hide show
  1. py_files/gemini_analysis.py +243 -192
py_files/gemini_analysis.py CHANGED
@@ -15,7 +15,6 @@ import gradio as gr
15
 
16
  try:
17
  from google import genai
18
- from google.genai import types
19
  from google.genai.errors import ServerError
20
  GENAI_AVAILABLE = True
21
  except ImportError:
@@ -51,6 +50,100 @@ def check_csv_format(df: pd.DataFrame) -> str:
51
  # analyze_with_gemini function removed - using few_shots_generator instead
52
 
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  def few_shots_generator(eval_dir='./eval', files=None, api_key=None):
55
  """
56
  Generator version of few_shots that yields notifications in real-time.
@@ -116,25 +209,19 @@ def few_shots_generator(eval_dir='./eval', files=None, api_key=None):
116
  yield ('notification', f"πŸ€– Calling Gemini AI for pattern analysis (attempt {try_cnt})...")
117
  if try_cnt == 1:
118
  gr.Info("πŸ€– Starting Gemini analysis...")
119
- print(f"[CONSOLE] Attempt {try_cnt} - Calling Gemini API...")
120
- response = client.models.generate_content(
121
  model='gemini-3-flash-preview',
122
- contents=data,
123
- config=types.GenerateContentConfig(
124
- system_instruction=textsi_1,
125
- temperature=1,
126
- top_p=0.1,
127
- top_k=1,
128
- max_output_tokens=24*1024,
129
- safety_settings=[
130
- types.SafetySetting(category='HARM_CATEGORY_HARASSMENT', threshold='BLOCK_NONE'),
131
- types.SafetySetting(category='HARM_CATEGORY_HATE_SPEECH', threshold='BLOCK_NONE'),
132
- types.SafetySetting(category='HARM_CATEGORY_SEXUALLY_EXPLICIT', threshold='BLOCK_NONE'),
133
- types.SafetySetting(category='HARM_CATEGORY_DANGEROUS_CONTENT', threshold='BLOCK_NONE'),
134
- types.SafetySetting(category='HARM_CATEGORY_CIVIC_INTEGRITY', threshold='BLOCK_NONE')
135
- ]
136
- )
137
  )
 
138
  yield ('notification', f"βœ… Gemini API call successful! Processing results...")
139
  gr.Info("βœ… Gemini analysis successful!")
140
  print(f"[CONSOLE] Gemini API call successful")
@@ -160,104 +247,84 @@ def few_shots_generator(eval_dir='./eval', files=None, api_key=None):
160
  yield 'notification', error_msg
161
  raise gr.Error(f"Gemini API error: {str(e.message)}")
162
 
163
- try:
164
- # Process the response
165
- _f = os.path.join(f"{eval_dir}", "gemini_fs", os.path.basename(f))
166
- df = pd.read_csv(StringIO(response.text.replace("```csv", '').replace("```", '').strip()), sep='|')
167
- csv_with_yolo = pd.read_csv(f, index_col=0)
168
- gemini_cols = df[["Deceptive Design Category", "Deceptive Design Subtype", "Reasoning"]]
169
- csv_with_yolo.reset_index(inplace=True)
170
- final_df = pd.concat([csv_with_yolo, gemini_cols], axis=1)
171
- final_df.to_csv(_f, index=False, quoting=csv.QUOTE_ALL)
172
- print(f"[CONSOLE] Results saved to: {_f}")
173
-
174
- # Check if thinking is needed (if any deceptive patterns found)
175
- if set(final_df['Deceptive Design Category'].tolist()) != {'non-deceptive'}:
176
- yield ('notification', "🧠 Deceptive patterns detected! Running advanced thinking analysis...")
177
- gr.Info("🧠 Deceptive patterns found! Running advanced analysis...")
178
- print(f"[CONSOLE] Deceptive patterns found, running thinking analysis...")
179
-
180
- # Use generator version of thinking
181
- thinking_result = None
182
- for thinking_status, thinking_data in thinking_generator(eval_dir, files=[_f], api_key=api_key):
183
- if thinking_status == 'notification':
184
- yield ('notification', thinking_data)
185
- elif thinking_status == 'result':
186
- thinking_result = thinking_data
187
- break
188
-
189
- if thinking_result is not None:
190
- yield ('notification', "βœ… Advanced thinking analysis completed successfully!")
191
- gr.Info("βœ… Advanced analysis completed!")
192
- print(f"[CONSOLE] Thinking analysis completed, using refined results")
193
- final_df = thinking_result
194
- else:
195
- yield ('notification', "⚠️ Advanced thinking analysis failed, using original results")
196
- gr.Warning("⚠️ Advanced analysis failed, using basic results")
197
- print(f"[CONSOLE] Thinking analysis failed, using original results")
198
- else:
199
- yield ('notification', "βœ… No deceptive patterns found, analysis complete!")
200
- gr.Info("βœ… No deceptive patterns detected!")
201
- print(f"[CONSOLE] No deceptive patterns found, skipping thinking analysis")
202
-
203
- yield 'result', final_df
204
- return
205
-
206
- except Exception as e:
207
- print(f"[CONSOLE] Error parsing with pipe separator, trying comma: {e}")
208
  try:
209
- df = pd.read_csv(StringIO(response.text.replace("```csv", '').replace("```", '').strip()), sep=',')
210
- csv_with_yolo = pd.read_csv(f, index_col=0)
211
- gemini_cols = df[["Deceptive Design Category", "Deceptive Design Subtype", "Reasoning"]]
212
- csv_with_yolo.reset_index(inplace=True)
213
- final_df = pd.concat([csv_with_yolo, gemini_cols], axis=1)
214
- final_df.to_csv(_f, index=False, quoting=csv.QUOTE_ALL)
215
- print(f"[CONSOLE] Results saved to: {_f} (comma separated)")
216
-
217
- # Check if thinking is needed
218
- if set(final_df['Deceptive Design Category'].tolist()) != {'non-deceptive'}:
219
- yield ('notification', "🧠 Deceptive patterns detected! Running advanced thinking analysis...")
220
- gr.Info("🧠 Deceptive patterns found! Running advanced analysis...")
221
- print(f"[CONSOLE] Deceptive patterns found, running thinking analysis...")
222
-
223
- # Use generator version of thinking
224
- thinking_result = None
225
- for thinking_status, thinking_data in thinking_generator(eval_dir, files=[_f], api_key=api_key):
226
- if thinking_status == 'notification':
227
- yield ('notification', thinking_data)
228
- elif thinking_status == 'result':
229
- thinking_result = thinking_data
230
- break
231
-
232
- if thinking_result is not None:
233
- yield ('notification', "βœ… Advanced thinking analysis completed successfully!")
234
- gr.Info("βœ… Advanced analysis completed!")
235
- print(f"[CONSOLE] Thinking analysis completed, using refined results")
236
- final_df = thinking_result
237
- else:
238
- yield ('notification', "⚠️ Advanced thinking analysis failed, using original results")
239
- gr.Warning("⚠️ Advanced analysis failed, using basic results")
240
- print(f"[CONSOLE] Thinking analysis failed, using original results")
241
- else:
242
- yield ('notification', "βœ… No deceptive patterns found, analysis complete!")
243
- gr.Info("βœ… No deceptive patterns detected!")
244
- print(f"[CONSOLE] No deceptive patterns found, skipping thinking analysis")
245
-
246
- yield ('result', final_df)
247
- return
248
- except Exception as e2:
249
- error_msg = f"❌ Error parsing Gemini response with both separators: {str(e2)}"
250
- yield ('notification', error_msg)
251
- print(f"[CONSOLE] FEW_SHOT Error with both separators: {e2}")
252
  try:
253
- error_file = _f.replace(".csv", "e1.txt")
254
- with open(error_file, 'w') as _fs:
255
- _fs.write(response.text)
256
- print(f"[CONSOLE] Error response saved to: {error_file}")
257
  except Exception as e3:
258
  print(f"[CONSOLE] Failed to save error response: {e3}")
259
- raise gr.Error(f"Failed to parse response: {str(e2)}")
260
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
  yield ('result', None)
262
 
263
 
@@ -309,31 +376,24 @@ def thinking_generator(eval_dir="./eval", files=None, api_key=None):
309
 
310
  # Make API call to Gemini with retry logic for thinking analysis
311
  try_cnt = 0
312
- response = None
313
  while try_cnt < 2:
314
  try:
315
  try_cnt += 1
316
  yield ('notification', f"🧠 Running advanced thinking analysis (attempt {try_cnt})...")
317
- print(f"[CONSOLE] Attempt {try_cnt} - Calling Gemini API for thinking...")
318
-
319
- response = client.models.generate_content(
320
  model='gemini-3-flash-preview',
321
- contents=data,
322
- config=types.GenerateContentConfig(
323
- system_instruction=textsi_1,
324
- temperature=1,
325
- top_p=0.1,
326
- top_k=1,
327
- max_output_tokens=24*1024,
328
- thinking_config=types.ThinkingConfig(include_thoughts=True),
329
- safety_settings=[
330
- types.SafetySetting(category='HARM_CATEGORY_HARASSMENT', threshold='BLOCK_NONE'),
331
- types.SafetySetting(category='HARM_CATEGORY_HATE_SPEECH', threshold='BLOCK_NONE'),
332
- types.SafetySetting(category='HARM_CATEGORY_SEXUALLY_EXPLICIT', threshold='BLOCK_NONE'),
333
- types.SafetySetting(category='HARM_CATEGORY_DANGEROUS_CONTENT', threshold='BLOCK_NONE'),
334
- types.SafetySetting(category='HARM_CATEGORY_CIVIC_INTEGRITY', threshold='BLOCK_NONE')
335
- ]
336
- )
337
  )
338
  yield ('notification', f"βœ… Advanced thinking analysis API call successful!")
339
  print(f"[CONSOLE] Thinking API call successful")
@@ -344,7 +404,7 @@ def thinking_generator(eval_dir="./eval", files=None, api_key=None):
344
  yield ('notification', error_msg)
345
  print(f"[CONSOLE] Failed to get thinking response after {try_cnt} attempts")
346
  raise gr.Error(f"Advanced analysis failed after {try_cnt} attempts")
347
-
348
  wait_msg = f"⚠️ Server error in thinking analysis. Retrying attempt {try_cnt + 1}/2 in 60 seconds..."
349
  yield ('notification', wait_msg)
350
  gr.Warning(f"⚠️ Thinking server error. Retrying in 60s... (attempt {try_cnt + 1}/2)")
@@ -358,16 +418,11 @@ def thinking_generator(eval_dir="./eval", files=None, api_key=None):
358
  yield ('notification', error_msg)
359
  print(f"[CONSOLE] Non-server error in thinking API call: {e}")
360
  raise gr.Error(f"Thinking analysis API error: {str(e)}")
361
-
362
- output_csv = ""
363
- thought_txt = ""
364
- for part in response.candidates[0].content.parts:
365
- if part.thought == True:
366
- thought_txt = part.text
367
- print(f"[CONSOLE] Extracted thought text ({len(thought_txt)} chars)")
368
- else:
369
- output_csv = part.text
370
- print(f"[CONSOLE] Extracted output CSV ({len(output_csv)} chars)")
371
 
372
  _f = os.path.join(f"{eval_dir}", "gemini_fs", os.path.basename(f))
373
  _f_thought = os.path.join(f"{eval_dir}", "gemini_fs", os.path.basename(f).replace(".csv", "_thinking.txt"))
@@ -377,60 +432,56 @@ def thinking_generator(eval_dir="./eval", files=None, api_key=None):
377
  _f_thought_file.write(thought_txt)
378
  print(f"[CONSOLE] Thinking text saved to: {_f_thought}")
379
 
380
- # Parse and save updated CSV with similar process as main analysis
381
- try:
382
- # Parse the thinking response CSV
383
- df_thinking = pd.read_csv(StringIO(output_csv), sep='|')
384
-
385
- # Read the original CSV file to get the base data
386
- csv_with_yolo = pd.read_csv(f, index_col=0).drop(columns=["Deceptive Design Category", "Deceptive Design Subtype", "Reasoning"], errors='ignore')
387
-
388
- # Extract the thinking analysis columns (similar to main process)
389
- thinking_cols = df_thinking[["Deceptive Design Category", "Deceptive Design Subtype", "Reasoning"]]
390
-
391
- # Reset index and concatenate with original data
392
- csv_with_yolo.reset_index(inplace=True)
393
- final_df = pd.concat([csv_with_yolo, thinking_cols], axis=1)
394
-
395
- # Save the updated dataframe
396
- final_df.to_csv(_f, index=False, quoting=csv.QUOTE_ALL)
397
- print(f"[CONSOLE] Thinking results saved to: {_f} (pipe separated)")
398
- yield ('result', final_df) # Return the updated dataframe
399
- return
400
- except Exception as e:
401
- print(f"[CONSOLE] Error with pipe separator, trying comma: {e}")
402
  try:
403
- # Parse the thinking response CSV with comma separator
404
- df_thinking = pd.read_csv(StringIO(output_csv), sep=',')
405
-
406
- # Read the original CSV file to get the base data
407
- csv_with_yolo = pd.read_csv(f, index_col=0).drop(columns=["Deceptive Design Category", "Deceptive Design Subtype", "Reasoning"], errors='ignore')
408
-
409
- # Extract the thinking analysis columns (similar to main process)
410
- thinking_cols = df_thinking[["Deceptive Design Category", "Deceptive Design Subtype", "Reasoning"]]
411
-
412
- # Reset index and concatenate with original data
413
- csv_with_yolo.reset_index(inplace=True)
414
- final_df = pd.concat([csv_with_yolo, thinking_cols], axis=1)
415
-
416
- # Save the updated dataframe
417
- final_df.to_csv(_f, index=False, quoting=csv.QUOTE_ALL)
418
- print(f"[CONSOLE] Thinking results saved to: {_f} (comma separated)")
419
- yield ('result', final_df) # Return the updated dataframe
420
- return
421
- except Exception as e2:
422
- error_msg = f"❌ Error parsing thinking analysis response with both separators: {str(e2)}"
423
- yield ('notification', error_msg)
424
- print(f"[CONSOLE] THINKING ERROR with both separators: {e2}")
 
425
  try:
426
- error_file = _f.replace(".csv", "e2.txt")
427
- with open(error_file, 'w') as _fs:
428
  _fs.write(output_csv)
429
- print(f"[CONSOLE] Thinking error response saved to: {error_file}")
430
  except Exception as e3:
431
  print(f"[CONSOLE] Failed to save thinking error response: {e3}")
432
- raise gr.Error(f"Failed to parse thinking response: {str(e2)}")
433
-
 
 
 
 
 
 
 
 
 
 
434
  except Exception as e:
435
  error_msg = f"❌ Error in thinking analysis: {str(e)}"
436
  yield ('notification', error_msg)
 
15
 
16
  try:
17
  from google import genai
 
18
  from google.genai.errors import ServerError
19
  GENAI_AVAILABLE = True
20
  except ImportError:
 
50
  # analyze_with_gemini function removed - using few_shots_generator instead
51
 
52
 
53
+ REQUIRED_GEMINI_COLS = ["Deceptive Design Category", "Deceptive Design Subtype", "Reasoning"]
54
+
55
+
56
+ def _parse_response_to_df(response_text, original_csv_path, drop_existing=False):
57
+ """
58
+ Parse a Gemini CSV response and validate it can be merged with the source CSV.
59
+ Tries pipe then comma separators. Checks required columns, row-count match, and nulls.
60
+
61
+ Returns:
62
+ (final_df, None) on success, or (None, error_message) on failure.
63
+ The error_message is user-readable and safe to send back to the LLM for correction.
64
+ """
65
+ cleaned = response_text.replace("```csv", '').replace("```", '').strip()
66
+
67
+ parsed = None
68
+ sep_errors = []
69
+ for sep in ['|', ',']:
70
+ try:
71
+ candidate = pd.read_csv(StringIO(cleaned), sep=sep)
72
+ missing = [c for c in REQUIRED_GEMINI_COLS if c not in candidate.columns]
73
+ if missing:
74
+ sep_errors.append(f"separator '{sep}': missing required columns {missing}; got {list(candidate.columns)}")
75
+ continue
76
+ parsed = candidate
77
+ break
78
+ except Exception as e:
79
+ sep_errors.append(f"separator '{sep}': {e}")
80
+
81
+ if parsed is None:
82
+ return None, "Failed to parse CSV. " + " | ".join(sep_errors)
83
+
84
+ try:
85
+ csv_with_yolo = pd.read_csv(original_csv_path, index_col=0)
86
+ if drop_existing:
87
+ csv_with_yolo = csv_with_yolo.drop(columns=REQUIRED_GEMINI_COLS, errors='ignore')
88
+ except Exception as e:
89
+ return None, f"Could not read source CSV {original_csv_path}: {e}"
90
+
91
+ if len(parsed) != len(csv_with_yolo):
92
+ return None, (
93
+ f"Row count mismatch: response has {len(parsed)} rows but input has "
94
+ f"{len(csv_with_yolo)} rows. Output must contain exactly one row per input row."
95
+ )
96
+
97
+ gemini_cols = parsed[REQUIRED_GEMINI_COLS]
98
+ if gemini_cols.isnull().any().any():
99
+ null_rows = gemini_cols[gemini_cols.isnull().any(axis=1)].index.tolist()
100
+ return None, f"Null values found in required columns at row indices: {null_rows[:10]}"
101
+
102
+ csv_with_yolo.reset_index(inplace=True)
103
+ final_df = pd.concat([csv_with_yolo, gemini_cols], axis=1)
104
+ return final_df, None
105
+
106
+
107
+ def _build_correction_request(error_message):
108
+ """
109
+ Build the follow-up instruction asking the model to correct its prior bad response.
110
+ Pair with `previous_interaction_id=<prior interaction id>` so the server provides history.
111
+ """
112
+ return (
113
+ f"Your previous response could not be parsed into a valid DataFrame.\n"
114
+ f"Validation error:\n{error_message}\n\n"
115
+ f"Please regenerate ONLY the corrected pipe-separated CSV output. "
116
+ f"Maintain exactly the same number of rows as the input. Include the required columns "
117
+ f"({', '.join(REQUIRED_GEMINI_COLS)}). "
118
+ f"Output only the CSV β€” no markdown code fences, no explanations."
119
+ )
120
+
121
+
122
+ def _extract_model_text(interaction):
123
+ """Concatenate text from all model_output steps in an Interactions response."""
124
+ chunks = []
125
+ for step in interaction.steps:
126
+ if getattr(step, "type", None) == "model_output":
127
+ for block in getattr(step, "content", []) or []:
128
+ if getattr(block, "type", None) == "text":
129
+ chunks.append(getattr(block, "text", "") or "")
130
+ return "".join(chunks)
131
+
132
+
133
+ def _extract_thought_text(interaction):
134
+ """Concatenate thought summary text from all thought steps in an Interactions response."""
135
+ chunks = []
136
+ for step in interaction.steps:
137
+ if getattr(step, "type", None) == "thought":
138
+ summary = getattr(step, "summary", None)
139
+ if not summary:
140
+ continue
141
+ for block in summary:
142
+ if getattr(block, "type", None) == "text":
143
+ chunks.append(getattr(block, "text", "") or "")
144
+ return "".join(chunks)
145
+
146
+
147
  def few_shots_generator(eval_dir='./eval', files=None, api_key=None):
148
  """
149
  Generator version of few_shots that yields notifications in real-time.
 
209
  yield ('notification', f"πŸ€– Calling Gemini AI for pattern analysis (attempt {try_cnt})...")
210
  if try_cnt == 1:
211
  gr.Info("πŸ€– Starting Gemini analysis...")
212
+ print(f"[CONSOLE] Attempt {try_cnt} - Calling Gemini Interactions API...")
213
+ interaction = client.interactions.create(
214
  model='gemini-3-flash-preview',
215
+ input=data,
216
+ system_instruction=textsi_1,
217
+ generation_config={
218
+ 'temperature': 1,
219
+ 'top_p': 0.1,
220
+ 'max_output_tokens': 45 * 1024,
221
+ 'thinking_level': 'high',
222
+ },
 
 
 
 
 
 
 
223
  )
224
+ response_text = _extract_model_text(interaction)
225
  yield ('notification', f"βœ… Gemini API call successful! Processing results...")
226
  gr.Info("βœ… Gemini analysis successful!")
227
  print(f"[CONSOLE] Gemini API call successful")
 
247
  yield 'notification', error_msg
248
  raise gr.Error(f"Gemini API error: {str(e.message)}")
249
 
250
+ _f = os.path.join(f"{eval_dir}", "gemini_fs", os.path.basename(f))
251
+
252
+ # Parse and validate; on failure, ask the model to self-correct once.
253
+ final_df, parse_error = _parse_response_to_df(response_text, f, drop_existing=False)
254
+
255
+ if final_df is None:
256
+ yield ('notification', f"⚠️ Output validation failed: {parse_error[:200]}. Asking Gemini to correct (1 retry)...")
257
+ gr.Info("⚠️ Output invalid β€” asking Gemini to correct")
258
+ print(f"[CONSOLE] FEW_SHOT parse/validation failed: {parse_error}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
  try:
260
+ correction_interaction = client.interactions.create(
261
+ model='gemini-3-flash-preview',
262
+ input=_build_correction_request(parse_error),
263
+ previous_interaction_id=interaction.id,
264
+ system_instruction=textsi_1,
265
+ generation_config={
266
+ 'temperature': 1,
267
+ 'top_p': 0.1,
268
+ 'max_output_tokens': 45 * 1024,
269
+ 'thinking_level': 'high',
270
+ },
271
+ )
272
+ interaction = correction_interaction
273
+ response_text = _extract_model_text(interaction)
274
+ final_df, parse_error = _parse_response_to_df(response_text, f, drop_existing=False)
275
+ except Exception as e_corr:
276
+ print(f"[CONSOLE] FEW_SHOT correction call failed: {e_corr}")
277
+ parse_error = f"Correction API call failed: {e_corr}"
278
+
279
+ if final_df is None:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  try:
281
+ error_file = _f.replace(".csv", "_parse_error.txt")
282
+ with open(error_file, 'w', encoding='utf-8') as _fs:
283
+ _fs.write(response_text)
284
+ print(f"[CONSOLE] Failed response saved to: {error_file}")
285
  except Exception as e3:
286
  print(f"[CONSOLE] Failed to save error response: {e3}")
287
+ error_msg = f"❌ Gemini output could not be parsed even after correction: {parse_error}"
288
+ yield ('notification', error_msg)
289
+ raise gr.Error(f"Failed to parse response after correction: {parse_error}")
290
+ else:
291
+ yield ('notification', "βœ… Gemini correction succeeded β€” output parsed successfully")
292
+ gr.Info("βœ… Corrected output parsed successfully!")
293
+
294
+ final_df.to_csv(_f, index=False, quoting=csv.QUOTE_ALL)
295
+ print(f"[CONSOLE] Results saved to: {_f}")
296
+
297
+ # Check if thinking is needed (if any deceptive patterns found)
298
+ if set(final_df['Deceptive Design Category'].tolist()) != {'non-deceptive'}:
299
+ yield ('notification', "🧠 Deceptive patterns detected! Running advanced thinking analysis...")
300
+ gr.Info("🧠 Deceptive patterns found! Running advanced analysis...")
301
+ print(f"[CONSOLE] Deceptive patterns found, running thinking analysis...")
302
+
303
+ thinking_result = None
304
+ for thinking_status, thinking_data in thinking_generator(eval_dir, files=[_f], api_key=api_key):
305
+ if thinking_status == 'notification':
306
+ yield ('notification', thinking_data)
307
+ elif thinking_status == 'result':
308
+ thinking_result = thinking_data
309
+ break
310
+
311
+ if thinking_result is not None:
312
+ yield ('notification', "βœ… Advanced thinking analysis completed successfully!")
313
+ gr.Info("βœ… Advanced analysis completed!")
314
+ print(f"[CONSOLE] Thinking analysis completed, using refined results")
315
+ final_df = thinking_result
316
+ else:
317
+ yield ('notification', "⚠️ Advanced thinking analysis failed, using original results")
318
+ gr.Warning("⚠️ Advanced analysis failed, using basic results")
319
+ print(f"[CONSOLE] Thinking analysis failed, using original results")
320
+ else:
321
+ yield ('notification', "βœ… No deceptive patterns found, analysis complete!")
322
+ gr.Info("βœ… No deceptive patterns detected!")
323
+ print(f"[CONSOLE] No deceptive patterns found, skipping thinking analysis")
324
+
325
+ yield ('result', final_df)
326
+ return
327
+
328
  yield ('result', None)
329
 
330
 
 
376
 
377
  # Make API call to Gemini with retry logic for thinking analysis
378
  try_cnt = 0
379
+ interaction = None
380
  while try_cnt < 2:
381
  try:
382
  try_cnt += 1
383
  yield ('notification', f"🧠 Running advanced thinking analysis (attempt {try_cnt})...")
384
+ print(f"[CONSOLE] Attempt {try_cnt} - Calling Gemini Interactions API for thinking...")
385
+
386
+ interaction = client.interactions.create(
387
  model='gemini-3-flash-preview',
388
+ input=data,
389
+ system_instruction=textsi_1,
390
+ generation_config={
391
+ 'temperature': 1,
392
+ 'top_p': 0.1,
393
+ 'max_output_tokens': 45 * 1024,
394
+ 'thinking_level': 'high',
395
+ 'thinking_summaries': 'auto',
396
+ },
 
 
 
 
 
 
 
397
  )
398
  yield ('notification', f"βœ… Advanced thinking analysis API call successful!")
399
  print(f"[CONSOLE] Thinking API call successful")
 
404
  yield ('notification', error_msg)
405
  print(f"[CONSOLE] Failed to get thinking response after {try_cnt} attempts")
406
  raise gr.Error(f"Advanced analysis failed after {try_cnt} attempts")
407
+
408
  wait_msg = f"⚠️ Server error in thinking analysis. Retrying attempt {try_cnt + 1}/2 in 60 seconds..."
409
  yield ('notification', wait_msg)
410
  gr.Warning(f"⚠️ Thinking server error. Retrying in 60s... (attempt {try_cnt + 1}/2)")
 
418
  yield ('notification', error_msg)
419
  print(f"[CONSOLE] Non-server error in thinking API call: {e}")
420
  raise gr.Error(f"Thinking analysis API error: {str(e)}")
421
+
422
+ output_csv = _extract_model_text(interaction)
423
+ thought_txt = _extract_thought_text(interaction)
424
+ print(f"[CONSOLE] Extracted output CSV ({len(output_csv)} chars)")
425
+ print(f"[CONSOLE] Extracted thought text ({len(thought_txt)} chars)")
 
 
 
 
 
426
 
427
  _f = os.path.join(f"{eval_dir}", "gemini_fs", os.path.basename(f))
428
  _f_thought = os.path.join(f"{eval_dir}", "gemini_fs", os.path.basename(f).replace(".csv", "_thinking.txt"))
 
432
  _f_thought_file.write(thought_txt)
433
  print(f"[CONSOLE] Thinking text saved to: {_f_thought}")
434
 
435
+ # Parse and validate; on failure, ask the model to self-correct once.
436
+ final_df, parse_error = _parse_response_to_df(output_csv, f, drop_existing=True)
437
+
438
+ if final_df is None:
439
+ yield ('notification', f"⚠️ Thinking output validation failed: {parse_error[:200]}. Asking Gemini to correct (1 retry)...")
440
+ gr.Info("⚠️ Thinking output invalid β€” asking Gemini to correct")
441
+ print(f"[CONSOLE] THINKING parse/validation failed: {parse_error}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
442
  try:
443
+ correction_interaction = client.interactions.create(
444
+ model='gemini-3-flash-preview',
445
+ input=_build_correction_request(parse_error),
446
+ previous_interaction_id=interaction.id,
447
+ system_instruction=textsi_1,
448
+ generation_config={
449
+ 'temperature': 1,
450
+ 'top_p': 0.1,
451
+ 'max_output_tokens': 45 * 1024,
452
+ 'thinking_level': 'high',
453
+ 'thinking_summaries': 'auto',
454
+ },
455
+ )
456
+ corrected_csv = _extract_model_text(correction_interaction)
457
+ final_df, parse_error = _parse_response_to_df(corrected_csv, f, drop_existing=True)
458
+ if final_df is not None:
459
+ output_csv = corrected_csv
460
+ interaction = correction_interaction
461
+ except Exception as e_corr:
462
+ print(f"[CONSOLE] THINKING correction call failed: {e_corr}")
463
+ parse_error = f"Correction API call failed: {e_corr}"
464
+
465
+ if final_df is None:
466
  try:
467
+ error_file = _f.replace(".csv", "_thinking_parse_error.txt")
468
+ with open(error_file, 'w', encoding='utf-8') as _fs:
469
  _fs.write(output_csv)
470
+ print(f"[CONSOLE] Thinking failed response saved to: {error_file}")
471
  except Exception as e3:
472
  print(f"[CONSOLE] Failed to save thinking error response: {e3}")
473
+ error_msg = f"❌ Thinking output could not be parsed even after correction: {parse_error}"
474
+ yield ('notification', error_msg)
475
+ raise gr.Error(f"Failed to parse thinking response after correction: {parse_error}")
476
+ else:
477
+ yield ('notification', "βœ… Gemini thinking correction succeeded β€” output parsed successfully")
478
+ gr.Info("βœ… Corrected thinking output parsed successfully!")
479
+
480
+ final_df.to_csv(_f, index=False, quoting=csv.QUOTE_ALL)
481
+ print(f"[CONSOLE] Thinking results saved to: {_f}")
482
+ yield ('result', final_df)
483
+ return
484
+
485
  except Exception as e:
486
  error_msg = f"❌ Error in thinking analysis: {str(e)}"
487
  yield ('notification', error_msg)