JuliaKreutzerCohere commited on
Commit
dd7abf5
·
verified ·
1 Parent(s): 7b2aae3

Upload script.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. script.py +39 -84
script.py CHANGED
@@ -85,13 +85,11 @@ SYSTEM = (
85
  "Then write a draft of the final answer. "
86
  "Subsequently, compare it with the format requirements again, "
87
  "and verify it's compliant with the deduced rules, and it is complete, i.e. has an answer for each element in QUERY. "
88
- "If necessary, correct and refine.\n"
89
- "Structure your response in exactly two sections with these headers:\n\n"
90
- "**Reasoning:**\n"
91
- "(your step-by-step analysis of linguistic rules, how to apply them, and answer format)\n\n"
92
- "**Final Answers:**\n"
93
- "(one answer per line, in query order -- bare answers only, no numbering, no quotes, "
94
- "no extra text, according to the TASK TYPE)"
95
  )
96
 
97
  tok = load_tokenizer(MODEL_ID)
@@ -138,55 +136,6 @@ for r in test_rows:
138
  print(f"{len(outputs_queries_types)}/{len(test_rows)} done", flush=True)
139
 
140
  # Postprocess and store the answers.
141
- def _normalize_header(line: str) -> str:
142
- header = line.strip().lower()
143
- header = re.sub(r"^#{1,3}\s*", "", header)
144
- header = re.sub(r"\*+", "", header)
145
- return header.rstrip(":").strip()
146
-
147
-
148
- def _is_reasoning_header(line: str) -> bool:
149
- header = _normalize_header(line)
150
- return header == "reasoning" or header.endswith(" reasoning")
151
-
152
-
153
- def _is_final_answers_header(line: str) -> bool:
154
- header = _normalize_header(line)
155
- return header in ("final answer", "final answers")
156
-
157
-
158
- def split_response(text: str) -> tuple[str, str]:
159
- """Return (explanation, raw final-answers section text)."""
160
- lines = text.splitlines(keepends=True)
161
- offset = 0
162
- reasoning_content_start = None
163
- explanation_end = None
164
- last_final_content_start = None
165
-
166
- reasoning_header_seen = False
167
- final_header_indices: list[int] = []
168
-
169
- for line in lines:
170
- if _is_reasoning_header(line) and not reasoning_header_seen:
171
- reasoning_header_seen = True
172
- reasoning_content_start = offset + len(line)
173
- elif _is_final_answers_header(line):
174
- final_header_indices.append(offset)
175
- if reasoning_content_start is not None and explanation_end is None:
176
- explanation_end = offset
177
- last_final_content_start = offset + len(line)
178
- offset += len(line)
179
-
180
- raw_final = text[last_final_content_start:].strip() if last_final_content_start is not None else ""
181
-
182
- explanation = ""
183
- if reasoning_content_start is not None and explanation_end is not None:
184
- explanation = text[reasoning_content_start:explanation_end].strip()
185
- if not explanation:
186
- explanation = raw_final
187
- return explanation, raw_final
188
-
189
-
190
  def expected_answer_count(query: str, task_type: str) -> int:
191
  if task_type == "match_letters":
192
  numbered = re.findall(r"^\s*\d+\.", query, re.MULTILINE)
@@ -232,33 +181,46 @@ def split_single_line_answer(text: str, expected: int, task_type: str) -> list[s
232
  return [text]
233
 
234
 
235
- def parse_answer_lines(text_after_marker: str, query: str, task_type: str) -> list[str]:
236
- """Parse cleaned answer lines from the raw final-answers section."""
 
 
 
 
 
 
 
 
 
 
237
  answers = []
238
- for line in text_after_marker.splitlines():
239
- stripped_line = line.strip("`").strip()
 
240
 
241
- if stripped_line == "":
242
- continue
 
243
 
 
 
244
  match_numbered_prefix = re.match(r"^\s*\d+[.)]\s+(.*)", stripped_line)
245
  if match_numbered_prefix:
246
  cleaned_line = match_numbered_prefix.group(1).strip()
247
  else:
248
  cleaned_line = stripped_line
249
 
 
250
  cleaned_line = re.sub(r"\*\*", "", cleaned_line).strip()
251
 
252
- if task_type == "match_letters":
 
253
  parts = [
254
  part.strip("().[]")
255
  for part in re.split(r"[\s,;]+", cleaned_line)
256
  if part.strip()
257
  ]
258
- if not (
259
- len(parts) > 1
260
- and all(re.fullmatch(r"[A-Za-z]", part) for part in parts)
261
- ):
262
  match_letter_word = re.match(
263
  r"^\s*(?:\(([A-Za-z])\)|\[([A-Za-z])\]|([A-Za-z]))\.?:?\s*(.*)$",
264
  cleaned_line,
@@ -271,34 +233,27 @@ def parse_answer_lines(text_after_marker: str, query: str, task_type: str) -> li
271
  )
272
  cleaned_line = letter.upper()
273
 
 
274
  if cleaned_line:
275
  answers.append(cleaned_line)
276
 
 
 
 
 
277
  expected = expected_answer_count(query, task_type)
 
 
278
  if len(answers) == 1 and expected > 1:
279
  answers = split_single_line_answer(answers[0], expected, task_type)
280
  return answers
281
 
282
-
283
- def postprocess_answer(text, query, task_type):
284
- """Extract explanation and parsed final answers from model output."""
285
- explanation, raw_final = split_response(text)
286
- if not raw_final:
287
- return [], explanation
288
- return parse_answer_lines(raw_final, query, task_type), explanation
289
-
290
  rows = []
291
  for answer, row_id, query, task_type in outputs_queries_types:
292
- answers, explanation = postprocess_answer(answer, query, task_type)
293
- rows.append(
294
- {
295
- "id": row_id,
296
- "pred": json.dumps(answers, ensure_ascii=False),
297
- "explanation": explanation,
298
- }
299
- )
300
  with open("submission.csv", "w", encoding="utf-8", newline="") as f:
301
- writer = csv.DictWriter(f, fieldnames=["id", "pred", "explanation"])
302
  writer.writeheader()
303
  writer.writerows(rows)
304
  print("wrote submission.csv", flush=True)
 
85
  "Then write a draft of the final answer. "
86
  "Subsequently, compare it with the format requirements again, "
87
  "and verify it's compliant with the deduced rules, and it is complete, i.e. has an answer for each element in QUERY. "
88
+ "If necessary, correct and refine."
89
+ "Finally, write a line that says exactly `FINAL ANSWERS:` "
90
+ "and, below it, write the answers to the items requested in QUERY (not those in CONTEXT),"
91
+ "one answer per line (separated by \n) in the order the items are asked for in the QUERY -- the "
92
+ "bare answer only, no numbering, no quotes, no extra text, according to the given TASK TYPE."
 
 
93
  )
94
 
95
  tok = load_tokenizer(MODEL_ID)
 
136
  print(f"{len(outputs_queries_types)}/{len(test_rows)} done", flush=True)
137
 
138
  # Postprocess and store the answers.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  def expected_answer_count(query: str, task_type: str) -> int:
140
  if task_type == "match_letters":
141
  numbered = re.findall(r"^\s*\d+\.", query, re.MULTILINE)
 
181
  return [text]
182
 
183
 
184
+ def postprocess_answer(text, query, task_type):
185
+ """Keep only the lines after the last 'FINAL ANSWERS:' marker, one answer per line,
186
+ stopping at the first empty line. If eval_type is multiple and only one line as answer, split at whitespace."""
187
+ # Updated regex to be more flexible with surrounding characters
188
+ marker_match = list(re.finditer(r"(?im)^[^\w\n]*final answers?[^\w\n]*:?\s*$", text))
189
+ if marker_match:
190
+ text_after_marker = text[marker_match[-1].end():]
191
+ #print('FOUND FINAL ANSWER', text_after_marker)
192
+ else:
193
+ #print("No 'FINAL ANSWERS:' marker found")
194
+ return []
195
+
196
  answers = []
197
+ answer_lines = text_after_marker.splitlines()
198
+ for i, line in enumerate(answer_lines):
199
+ stripped_line = line.strip('`').strip()
200
 
201
+ # Stop processing if an empty line is encountered (not as first line)
202
+ if stripped_line=='':
203
+ continue
204
 
205
+ # Use a more precise regex to only remove numbering if it's a prefix to other text
206
+ # This ensures that lines which are just numbers (e.g., '1') are not stripped.
207
  match_numbered_prefix = re.match(r"^\s*\d+[.)]\s+(.*)", stripped_line)
208
  if match_numbered_prefix:
209
  cleaned_line = match_numbered_prefix.group(1).strip()
210
  else:
211
  cleaned_line = stripped_line
212
 
213
+ # Remove any bold markdown '**'
214
  cleaned_line = re.sub(r"\*\*", "", cleaned_line).strip()
215
 
216
+ # Specific handling for 'match_letters' task type to strip extra words
217
+ if task_type == 'match_letters':
218
  parts = [
219
  part.strip("().[]")
220
  for part in re.split(r"[\s,;]+", cleaned_line)
221
  if part.strip()
222
  ]
223
+ if not (len(parts) > 1 and all(re.fullmatch(r"[A-Za-z]", part) for part in parts)):
 
 
 
224
  match_letter_word = re.match(
225
  r"^\s*(?:\(([A-Za-z])\)|\[([A-Za-z])\]|([A-Za-z]))\.?:?\s*(.*)$",
226
  cleaned_line,
 
233
  )
234
  cleaned_line = letter.upper()
235
 
236
+ # Append the cleaned, non-empty line
237
  if cleaned_line:
238
  answers.append(cleaned_line)
239
 
240
+ #print('PARSED ANSWERS', answers)
241
+
242
+ # Compare against QUERY length: sometimes model forgets newlines
243
+ #print('QUERY', query)
244
  expected = expected_answer_count(query, task_type)
245
+ query_len = len(query.splitlines()) - 2
246
+ #print(query_len)
247
  if len(answers) == 1 and expected > 1:
248
  answers = split_single_line_answer(answers[0], expected, task_type)
249
  return answers
250
 
 
 
 
 
 
 
 
 
251
  rows = []
252
  for answer, row_id, query, task_type in outputs_queries_types:
253
+ answers = postprocess_answer(answer, query, task_type)
254
+ rows.append({"id": row_id, "pred": json.dumps(answers, ensure_ascii=False)})
 
 
 
 
 
 
255
  with open("submission.csv", "w", encoding="utf-8", newline="") as f:
256
+ writer = csv.DictWriter(f, fieldnames=["id", "pred"])
257
  writer.writeheader()
258
  writer.writerows(rows)
259
  print("wrote submission.csv", flush=True)