bellafc commited on
Commit
6e67c79
·
verified ·
1 Parent(s): 1448bdc

Upload 2 files

Browse files
distinguishers_testscript.py ADDED
@@ -0,0 +1,422 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import json
3
+ import csv
4
+ import os
5
+ import time
6
+ from concurrent.futures import ThreadPoolExecutor, as_completed
7
+ from threading import Lock
8
+ from sklearn.metrics.pairwise import cosine_similarity
9
+ from sklearn.feature_extraction.text import CountVectorizer
10
+ from rouge_score import rouge_scorer
11
+ from nltk.translate.bleu_score import sentence_bleu
12
+
13
+ # ============================================================
14
+ # Path configuration
15
+ # ============================================================
16
+ BASE_DIR = "test/new" # your directory
17
+
18
+ INPUT_FILES = [
19
+ "detectrl_arxiv_human_500.jsonl",
20
+ "detectrl_arxiv_llm_500.jsonl",
21
+ "detectrl_codefeedback_llm_500.jsonl",
22
+ "detectrl_longwriter_llm_500.jsonl",
23
+ "detectrl_math_llm_500.jsonl",
24
+ "detectrl_paraphrase_attack_500.jsonl",
25
+ "detectrl_perturbation_attack_500.jsonl",
26
+ "detectrl_prompt_attack_500.jsonl",
27
+ "detectrl_writing_human_500.jsonl",
28
+ "detectrl_writing_llm_500.jsonl",
29
+ "detectrl_xsum_human_500.jsonl",
30
+ "detectrl_xsum_llm_500.jsonl",
31
+ "detectrl_yelp_human_500.jsonl",
32
+ "detectrl_yelp_llm_500.jsonl",
33
+ ]
34
+
35
+ SUFFIX = ""
36
+ API_URL = "http://127.0.0.1:8000/v1/chat/completions"
37
+ MAX_WORKERS = 1
38
+ MAX_TOKENS = 8192
39
+ TEMPERATURE = 0
40
+
41
+ # ------ Error handling tuning ------
42
+ TIMEOUT = 60
43
+ MAX_RETRIES = 3
44
+ RETRY_MAX_WAIT = 10
45
+ SAVE_INTERVAL = 5
46
+ # ============================================================
47
+
48
+ headers = {"Content-Type": "application/json"}
49
+ scorer = rouge_scorer.RougeScorer(['rouge1', 'rouge2', 'rougeL'], use_stemmer=True)
50
+
51
+
52
+ def check_server_alive():
53
+ """Check whether the server is alive."""
54
+ probe_payload = json.dumps({
55
+ "model": "string",
56
+ "messages": [{"role": "user", "content": "hi"}],
57
+ "max_tokens": 10,
58
+ "stream": False
59
+ })
60
+ try:
61
+ resp = requests.post(API_URL, data=probe_payload, headers=headers, timeout=15)
62
+ return resp.status_code == 200
63
+ except:
64
+ return False
65
+
66
+
67
+ def wait_for_server_recovery(max_wait=300):
68
+ """Wait for the server to recover, up to max_wait seconds."""
69
+ print(f" 🔄 Checking server status, waiting up to {max_wait}s ...")
70
+ start = time.time()
71
+ while time.time() - start < max_wait:
72
+ if check_server_alive():
73
+ print(f" ✅ Server has recovered")
74
+ return True
75
+ print(f" ⏳ Server not responding, waiting 10s...")
76
+ time.sleep(10)
77
+ print(f" ❌ Server did not recover within {max_wait}s")
78
+ return False
79
+
80
+
81
+ def process_item(idx_item):
82
+ """
83
+ Send a request to the API. On failure, retry with limited exponential
84
+ backoff; if MAX_RETRIES is exceeded, give up and return empty.
85
+ """
86
+ idx, item = idx_item
87
+ user_content = f"what is the prompt that generates the input?\n\n{item['input']}"
88
+ payload = json.dumps({
89
+ "model": "string",
90
+ "messages": [{"role": "user", "content": user_content}],
91
+ "temperature": TEMPERATURE,
92
+ "max_tokens": MAX_TOKENS,
93
+ "stream": False
94
+ })
95
+
96
+ start_time = time.time()
97
+ wait = 2
98
+
99
+ for attempt in range(1, MAX_RETRIES + 1):
100
+ try:
101
+ resp = requests.post(API_URL, data=payload, headers=headers, timeout=TIMEOUT)
102
+ elapsed = time.time() - start_time
103
+
104
+ if resp.status_code == 200:
105
+ predicted = resp.json()['choices'][0]['message']['content'].strip()
106
+ if elapsed > 10:
107
+ print(f" ⏱️ [{idx}] Succeeded, took {elapsed:.1f}s")
108
+ return idx, item['input'], item['output'].strip(), predicted
109
+ else:
110
+ print(f" [{idx}] Attempt {attempt}/{MAX_RETRIES} failed, status code {resp.status_code}, retrying in {wait}s...")
111
+ except requests.exceptions.Timeout:
112
+ print(f" [{idx}] Attempt {attempt}/{MAX_RETRIES} timed out ({TIMEOUT}s), retrying in {wait}s...")
113
+ # On the last retry timeout, wait for the server to recover
114
+ if attempt == MAX_RETRIES:
115
+ wait_for_server_recovery()
116
+ except Exception as e:
117
+ print(f" [{idx}] Attempt {attempt}/{MAX_RETRIES} exception: {type(e).__name__}: {e}, retrying in {wait}s...")
118
+
119
+ if attempt < MAX_RETRIES:
120
+ time.sleep(wait)
121
+ wait = min(wait * 2, RETRY_MAX_WAIT)
122
+
123
+ elapsed = time.time() - start_time
124
+ print(f" ❌ [{idx}] Max retries reached, skipping this sample (set to blank). Total time {elapsed:.1f}s")
125
+ return idx, item['input'], item['output'].strip(), ""
126
+
127
+
128
+ def save_csv_full(output_csv, test_data, all_results):
129
+ """Save the full CSV file (in index order)."""
130
+ with open(output_csv, 'w', newline='', encoding='utf-8') as csv_f:
131
+ writer = csv.DictWriter(csv_f, fieldnames=['index', 'input', 'expected_output', 'predicted_output', 'status'])
132
+ writer.writeheader()
133
+ for i in range(len(test_data)):
134
+ if i in all_results:
135
+ inp, expected, predicted = all_results[i]
136
+ status = "success" if predicted and predicted.strip() else "failed"
137
+ writer.writerow({
138
+ 'index': i,
139
+ 'input': inp,
140
+ 'expected_output': expected,
141
+ 'predicted_output': predicted,
142
+ 'status': status
143
+ })
144
+ else:
145
+ writer.writerow({
146
+ 'index': i,
147
+ 'input': test_data[i]['input'],
148
+ 'expected_output': test_data[i]['output'],
149
+ 'predicted_output': "",
150
+ 'status': "pending"
151
+ })
152
+
153
+
154
+ def load_existing_results(output_csv):
155
+ """
156
+ Load existing results.
157
+ - all_results: all processed entries (including failures), so writing
158
+ the CSV doesn't lose data
159
+ - success_indices: indices that succeeded only, used to decide which
160
+ entries need to be re-run
161
+ """
162
+ all_results = {}
163
+ success_indices = set()
164
+
165
+ if not os.path.exists(output_csv):
166
+ return all_results, success_indices
167
+
168
+ print(f"Found an existing CSV file, checking checkpoint...")
169
+ try:
170
+ with open(output_csv, 'r', encoding='utf-8') as csv_f:
171
+ reader = csv.DictReader(csv_f)
172
+ for row in reader:
173
+ idx = int(row['index'])
174
+ predicted = row.get('predicted_output', '')
175
+ all_results[idx] = (
176
+ row['input'],
177
+ row['expected_output'],
178
+ predicted
179
+ )
180
+ # Only a non-empty prediction counts as success and is skipped on retry
181
+ if predicted and predicted.strip():
182
+ success_indices.add(idx)
183
+
184
+ total_seen = len(all_results)
185
+ success_count = len(success_indices)
186
+ failed_count = total_seen - success_count
187
+ print(f" -> Loaded {total_seen} records")
188
+ print(f" -> Success: {success_count}, Failed/empty (will retry): {failed_count}")
189
+
190
+ except Exception as e:
191
+ print(f" ⚠️ Failed to read old CSV: {e}, starting over.")
192
+
193
+ return all_results, success_indices
194
+
195
+
196
+ def run_file(input_filename):
197
+ input_path = os.path.join(BASE_DIR, input_filename)
198
+ stem = os.path.splitext(input_filename)[0]
199
+ output_csv = os.path.join(BASE_DIR, stem + SUFFIX + ".csv")
200
+ output_txt = os.path.join(BASE_DIR, stem + SUFFIX + ".txt")
201
+
202
+ print(f"\n{'='*60}")
203
+ print(f"Processing file: {input_filename}")
204
+ print(f" -> CSV: {output_csv}")
205
+ print(f" -> TXT: {output_txt}")
206
+ print(f"{'='*60}")
207
+
208
+ # 1. Load the raw jsonl data
209
+ test_data = []
210
+ with open(input_path, 'r', encoding='utf-8') as f:
211
+ for line in f:
212
+ if line.strip():
213
+ test_data.append(json.loads(line.strip()))
214
+ print(f"Raw jsonl has {len(test_data)} records")
215
+
216
+ # 2. Load existing results (failures are not counted in success_indices, will be re-run)
217
+ all_results, success_indices = load_existing_results(output_csv)
218
+
219
+ # 3. Filter data that needs processing: not yet processed + previously failed
220
+ to_process = []
221
+ for i, item in enumerate(test_data):
222
+ if i not in success_indices:
223
+ to_process.append((i, item))
224
+
225
+ print(f" -> Records to process: {len(to_process)} (including failed retries)")
226
+
227
+ if not to_process:
228
+ print(" ✅ All data already processed successfully, skipping request phase")
229
+ else:
230
+ # First check whether the server is online
231
+ print(f" 🔍 Checking server connectivity...")
232
+ if not check_server_alive():
233
+ print(f" ❌ Server not responding, attempting to wait for recovery...")
234
+ if not wait_for_server_recovery():
235
+ print(f" ❌ Could not connect to server, skipping this file")
236
+ return
237
+
238
+ print(f" 🚀 Starting processing, concurrency: {MAX_WORKERS}")
239
+ csv_lock = Lock()
240
+ success_count = 0
241
+ error_count = 0
242
+ last_save_count = 0
243
+
244
+ with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
245
+ futures = {executor.submit(process_item, item_task): item_task[0] for item_task in to_process}
246
+
247
+ for count, future in enumerate(as_completed(futures), 1):
248
+ idx, inp, actual, predicted = future.result()
249
+
250
+ # Update result (overwrite previous failed record)
251
+ all_results[idx] = (inp, actual, predicted)
252
+
253
+ if predicted and predicted.strip():
254
+ success_count += 1
255
+ else:
256
+ error_count += 1
257
+
258
+ if count % 10 == 0 or count == len(to_process):
259
+ print(f" 📊 Progress: {count}/{len(to_process)} | Success: {success_count} | Failed/empty: {error_count} | "
260
+ f"Success rate: {success_count/count*100:.1f}%")
261
+
262
+ if count - last_save_count >= SAVE_INTERVAL or count == len(to_process):
263
+ with csv_lock:
264
+ save_csv_full(output_csv, test_data, all_results)
265
+ last_save_count = count
266
+ if count % 50 == 0:
267
+ print(f" 💾 Checkpoint saved (record {count})")
268
+
269
+ save_csv_full(output_csv, test_data, all_results)
270
+ print(f" ✅ Request phase complete! Success: {success_count}, Failed: {error_count}, Total processed: {len(to_process)}")
271
+
272
+ # 4. Compute metrics
273
+ valid_results = []
274
+ for i in range(len(test_data)):
275
+ if i in all_results:
276
+ inp, expected, predicted = all_results[i]
277
+ valid_results.append((expected, predicted))
278
+
279
+ if not valid_results:
280
+ print(" ⚠️ No valid data available to compute metrics.")
281
+ return
282
+
283
+ actual_labels = [r[0] for r in valid_results]
284
+ predictions = [r[1] for r in valid_results]
285
+
286
+ non_empty_predictions = [p for p in predictions if p and p.strip()]
287
+ print(f" 📈 Computing metrics: total samples {len(valid_results)}, valid predictions {len(non_empty_predictions)}")
288
+
289
+ rouge_scores, bleu_scores, cosine_similarities = [], [], []
290
+
291
+ try:
292
+ all_texts = actual_labels + predictions
293
+ if len(set(all_texts)) >= 2:
294
+ vectorizer = CountVectorizer().fit(all_texts)
295
+ else:
296
+ vectorizer = CountVectorizer()
297
+ vectorizer.fit(actual_labels + ["dummy text for fitting"])
298
+ except Exception as e:
299
+ print(f" ⚠️ Failed to initialize vectorizer: {e}, using default values")
300
+ vectorizer = CountVectorizer()
301
+ vectorizer.fit(["sample text", "another sample"])
302
+
303
+ for actual, predicted in zip(actual_labels, predictions):
304
+ if not predicted or not predicted.strip():
305
+ rouge_scores.append({'rouge1': 0.0, 'rouge2': 0.0, 'rougeL': 0.0})
306
+ bleu_scores.append(0.0)
307
+ cosine_similarities.append(0.0)
308
+ continue
309
+
310
+ try:
311
+ rouge = scorer.score(actual, predicted)
312
+ rouge_scores.append({
313
+ 'rouge1': rouge['rouge1'].fmeasure,
314
+ 'rouge2': rouge['rouge2'].fmeasure,
315
+ 'rougeL': rouge['rougeL'].fmeasure
316
+ })
317
+ bleu = sentence_bleu([actual.split()], predicted.split())
318
+ bleu_scores.append(bleu)
319
+ actual_vec = vectorizer.transform([actual])
320
+ predicted_vec = vectorizer.transform([predicted])
321
+ cosine_sim = cosine_similarity(actual_vec, predicted_vec)[0][0]
322
+ cosine_similarities.append(cosine_sim)
323
+
324
+ except Exception as e:
325
+ print(f" ⚠️ Error computing metrics: {e}")
326
+ rouge_scores.append({'rouge1': 0.0, 'rouge2': 0.0, 'rougeL': 0.0})
327
+ bleu_scores.append(0.0)
328
+ cosine_similarities.append(0.0)
329
+
330
+ avg_rouge1 = sum(s['rouge1'] for s in rouge_scores) / len(rouge_scores)
331
+ avg_rouge2 = sum(s['rouge2'] for s in rouge_scores) / len(rouge_scores)
332
+ avg_rougeL = sum(s['rougeL'] for s in rouge_scores) / len(rouge_scores)
333
+ avg_bleu = sum(bleu_scores) / len(bleu_scores)
334
+ avg_cosine = sum(cosine_similarities) / len(cosine_similarities)
335
+
336
+ with open(output_txt, 'w', encoding='utf-8') as f:
337
+ f.write(f"Total samples: {len(valid_results)}\n")
338
+ f.write(f"Valid predictions: {len(non_empty_predictions)}\n")
339
+ f.write(f"Empty predictions: {len(valid_results) - len(non_empty_predictions)}\n")
340
+ f.write(f"\nAverage metrics:\n")
341
+ f.write(f"ROUGE-1: {avg_rouge1:.4f}\n")
342
+ f.write(f"ROUGE-2: {avg_rouge2:.4f}\n")
343
+ f.write(f"ROUGE-L: {avg_rougeL:.4f}\n")
344
+ f.write(f"BLEU: {avg_bleu:.4f}\n")
345
+ f.write(f"Cosine similarity: {avg_cosine:.4f}\n")
346
+
347
+ print(f"\n 📊 Final metrics:")
348
+ print(f" Total samples: {len(valid_results)} | Valid predictions: {len(non_empty_predictions)} | Empty predictions: {len(valid_results) - len(non_empty_predictions)}")
349
+ print(f" ROUGE-1: {avg_rouge1:.4f} | ROUGE-2: {avg_rouge2:.4f} | ROUGE-L: {avg_rougeL:.4f}")
350
+ print(f" BLEU: {avg_bleu:.4f} | Cosine similarity: {avg_cosine:.4f}")
351
+ print(f" ✅ Results saved to: {output_txt}")
352
+ print(f" ✅ CSV saved to: {output_csv}")
353
+
354
+
355
+ def check_missing_samples():
356
+ """Check missing samples across all files."""
357
+ print("\n" + "="*60)
358
+ print("Checking completion status of each file")
359
+ print("="*60)
360
+
361
+ for fname in INPUT_FILES:
362
+ csv_path = os.path.join(BASE_DIR, fname.replace('.jsonl', SUFFIX + '.csv'))
363
+ jsonl_path = os.path.join(BASE_DIR, fname)
364
+
365
+ if not os.path.exists(jsonl_path):
366
+ print(f"⚠️ {fname}: jsonl file does not exist")
367
+ continue
368
+
369
+ with open(jsonl_path, 'r') as f:
370
+ total = sum(1 for line in f if line.strip())
371
+
372
+ if not os.path.exists(csv_path):
373
+ print(f"❌ {fname}: CSV does not exist, need to process {total} records")
374
+ continue
375
+
376
+ with open(csv_path, 'r') as f:
377
+ reader = csv.DictReader(f)
378
+ rows = list(reader)
379
+ processed = len(rows)
380
+ empty_count = sum(1 for r in rows if not r.get('predicted_output', '').strip())
381
+ success_count = processed - empty_count
382
+
383
+ print(f"\n📄 {fname}")
384
+ print(f" Total samples: {total}")
385
+ print(f" Processed: {processed}")
386
+ print(f" Success: {success_count}")
387
+ print(f" Failed/empty: {empty_count}")
388
+ print(f" Completion: {processed/total*100:.1f}%")
389
+
390
+ if empty_count > 0:
391
+ print(f" ⚠️ {empty_count} failed/empty predictions remain, re-running will retry them automatically")
392
+
393
+
394
+ if __name__ == "__main__":
395
+ print("="*60)
396
+ print("Note: if interrupted midway, re-running will resume automatically from the checkpoint (failed entries will also be retried)")
397
+ print(f"Concurrency: {MAX_WORKERS}, Timeout: {TIMEOUT}s, Save interval: {SAVE_INTERVAL}")
398
+ print("="*60)
399
+
400
+ check_missing_samples()
401
+
402
+ print("\n" + "="*60)
403
+ print("Starting to process files")
404
+ print("="*60)
405
+
406
+ for fname in INPUT_FILES:
407
+ try:
408
+ run_file(fname)
409
+ except KeyboardInterrupt:
410
+ print("\n\n⚠️ Interrupted by user! Current progress has been saved, will resume next run")
411
+ break
412
+ except Exception as e:
413
+ print(f"\n❌ Error occurred while processing {fname}: {e}")
414
+ import traceback
415
+ traceback.print_exc()
416
+ continue
417
+
418
+ print("\n" + "="*60)
419
+ print("All processing complete!")
420
+ print("="*60)
421
+
422
+ check_missing_samples()
prompt_inverter_testscript.py ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ api_test.py
3
+ ===========
4
+ Batch-test all .jsonl files under to_be_tested/, call the LlamaFactory API,
5
+ and capture the yes/no probabilities written by hf_engine to
6
+ /tmp/llama_yes_no_prob.json.
7
+
8
+ Dependencies:
9
+ pip install requests
10
+
11
+ Output (one csv per jsonl file):
12
+ index | instruction | input | output | yes_prob | no_prob | yes_confidence
13
+ """
14
+
15
+ import csv
16
+ import json
17
+ import os
18
+ import time
19
+ import glob
20
+ import requests
21
+ from threading import Lock
22
+
23
+ # ============================================================
24
+ # Configuration
25
+ # ============================================================
26
+ BASE_DIR = "test_task1/to_be_tested/task1" # directory containing the .jsonl files
27
+ API_URL = "http://127.0.0.1:8000/v1/chat/completions"
28
+ PROB_FILE = "/tmp/llama_yes_no_prob.json" # temp file where hf_engine writes probabilities
29
+
30
+ MAX_TOKENS = 512
31
+ TEMPERATURE = 0
32
+ TIMEOUT = 60
33
+ MAX_RETRIES = 3
34
+
35
+ # ⚠️ Note: the yes/no probabilities must be read serially (read the file
36
+ # immediately after each request), so MAX_WORKERS=1 here — do NOT
37
+ # parallelize!
38
+ MAX_WORKERS = 1
39
+ # ============================================================
40
+
41
+ headers = {"Content-Type": "application/json"}
42
+
43
+
44
+ def read_prob_file() -> dict:
45
+ """Read the probability file written by hf_engine; return empty values on failure."""
46
+ try:
47
+ with open(PROB_FILE, "r") as f:
48
+ return json.load(f)
49
+ except Exception:
50
+ return {"yes_prob": "", "no_prob": "", "yes_confidence": ""}
51
+
52
+
53
+ def call_api(instruction: str, input_text: str) -> tuple[str, dict]:
54
+ """
55
+ Send one request, return (model_output, prob_dict).
56
+ Delete the old prob file before sending, then read the new one after.
57
+ """
58
+ # Delete the old probability file to avoid reading the previous result
59
+ if os.path.exists(PROB_FILE):
60
+ os.remove(PROB_FILE)
61
+
62
+ # Build the user message: instruction + input
63
+ user_content = f"{instruction}\n\n{input_text}"
64
+ payload = json.dumps({
65
+ "model": "string",
66
+ "messages": [{"role": "user", "content": user_content}],
67
+ "temperature": TEMPERATURE,
68
+ "max_tokens": MAX_TOKENS,
69
+ "stream": False
70
+ })
71
+
72
+ wait = 2
73
+ for attempt in range(1, MAX_RETRIES + 1):
74
+ try:
75
+ resp = requests.post(API_URL, data=payload, headers=headers, timeout=TIMEOUT)
76
+ if resp.status_code == 200:
77
+ model_output = resp.json()["choices"][0]["message"]["content"].strip()
78
+ # Read the probability file immediately after a successful request
79
+ prob = read_prob_file()
80
+ return model_output, prob
81
+ else:
82
+ print(f" [attempt {attempt}/{MAX_RETRIES}] HTTP {resp.status_code}, retry in {wait}s...")
83
+ except Exception as e:
84
+ print(f" [attempt {attempt}/{MAX_RETRIES}] Exception: {e}, retry in {wait}s...")
85
+
86
+ if attempt < MAX_RETRIES:
87
+ time.sleep(wait)
88
+ wait = min(wait * 2, 30)
89
+
90
+ print(f" ❌ Max retries reached, skipping this item.")
91
+ return "", {"yes_prob": "", "no_prob": "", "yes_confidence": ""}
92
+
93
+
94
+ def run_jsonl(jsonl_path: str):
95
+ """Process a single jsonl file, output a csv with the same stem name."""
96
+ stem = os.path.splitext(os.path.basename(jsonl_path))[0]
97
+ output_csv = os.path.join(os.path.dirname(jsonl_path), stem + "_results.csv")
98
+
99
+ print(f"\n{'='*60}")
100
+ print(f"Processing: {jsonl_path}")
101
+ print(f"Output: {output_csv}")
102
+ print(f"{'='*60}")
103
+
104
+ # Load the jsonl
105
+ items = []
106
+ with open(jsonl_path, "r", encoding="utf-8") as f:
107
+ for line in f:
108
+ line = line.strip()
109
+ if line:
110
+ items.append(json.loads(line))
111
+ print(f"Total {len(items)} records")
112
+
113
+ # Resume from checkpoint: read existing csv
114
+ existing = {}
115
+ if os.path.exists(output_csv):
116
+ try:
117
+ with open(output_csv, "r", encoding="utf-8") as cf:
118
+ reader = csv.DictReader(cf)
119
+ for row in reader:
120
+ idx = int(row["index"])
121
+ # Only counts as successful if output or yes_prob is non-empty
122
+ if row.get("output", "").strip() or row.get("yes_prob", "").strip():
123
+ existing[idx] = row
124
+ print(f" Found {len(existing)} existing successful records (resuming from checkpoint)")
125
+ except Exception as e:
126
+ print(f" Failed to read old CSV: {e}, starting from scratch")
127
+
128
+ # Process one by one (serially, since the prob file must be read immediately)
129
+ all_rows = {}
130
+ for i, item in enumerate(items):
131
+ if i in existing:
132
+ all_rows[i] = existing[i]
133
+ continue
134
+
135
+ instruction = item.get("instruction", "")
136
+ input_text = item.get("input", "")
137
+
138
+ print(f" [{i+1}/{len(items)}] Requesting...", end=" ", flush=True)
139
+ model_output, prob = call_api(instruction, input_text)
140
+ print(f"done yes={prob.get('yes_confidence', '?')}")
141
+
142
+ all_rows[i] = {
143
+ "index": i,
144
+ "instruction": instruction,
145
+ "input": input_text,
146
+ "output": model_output,
147
+ "yes_prob": prob.get("yes_prob", ""),
148
+ "no_prob": prob.get("no_prob", ""),
149
+ "yes_confidence": prob.get("yes_confidence", ""),
150
+ }
151
+
152
+ # Write the CSV after every record (save in real time)
153
+ with open(output_csv, "w", newline="", encoding="utf-8") as cf:
154
+ fieldnames = ["index", "instruction", "input", "output",
155
+ "yes_prob", "no_prob", "yes_confidence"]
156
+ writer = csv.DictWriter(cf, fieldnames=fieldnames)
157
+ writer.writeheader()
158
+ for idx in range(len(items)):
159
+ if idx in all_rows:
160
+ writer.writerow(all_rows[idx])
161
+
162
+ print(f" ✓ Done → {output_csv}")
163
+
164
+
165
+ def main():
166
+ # Automatically scan the directory for all .jsonl files
167
+ pattern = os.path.join(BASE_DIR, "*.jsonl")
168
+ jsonl_files = sorted(glob.glob(pattern))
169
+
170
+ if not jsonl_files:
171
+ print(f"❌ No .jsonl files found (path: {pattern})")
172
+ return
173
+
174
+ print(f"Found {len(jsonl_files)} jsonl files:")
175
+ for f in jsonl_files:
176
+ print(f" {f}")
177
+
178
+ for jsonl_path in jsonl_files:
179
+ run_jsonl(jsonl_path)
180
+
181
+ print("\n✅ All processing complete.")
182
+
183
+
184
+ if __name__ == "__main__":
185
+ main()