Extract multiple answer letters from the response. Returns sorted letters as a string, e.g. "ABC".
(response: str)
| 49 | |
| 50 | |
| 51 | def _parse_multi_answer(response: str) -> Optional[str]: |
| 52 | """Extract multiple answer letters from the response. |
| 53 | |
| 54 | Returns sorted letters as a string, e.g. "ABC". |
| 55 | """ |
| 56 | valid = {"A", "B", "C", "D"} |
| 57 | |
| 58 | # Try: "The correct answers are (A, B, C)" or "(A, B, C)" |
| 59 | match = re.search(r"answers? (?:are|is) \(([A-D](?:\s*,\s*[A-D])*)\)", response) |
| 60 | if match: |
| 61 | letters = set(re.findall(r"[A-D]", match.group(1))) |
| 62 | if letters and letters <= valid: |
| 63 | return "".join(sorted(letters)) |
| 64 | |
| 65 | # Try: "answers are A, B, C" or "answers are A, B and C" |
| 66 | match = re.search(r"answers? (?:are|is)\s+([A-D](?:\s*[,and]+\s*[A-D])*)", response) |
| 67 | if match: |
| 68 | letters = set(re.findall(r"[A-D]", match.group(1))) |
| 69 | if letters and letters <= valid: |
| 70 | return "".join(sorted(letters)) |
| 71 | |
| 72 | # Try: "(A)(B)(C)" or "(A), (B), (C)" |
| 73 | tail = response[-300:] if len(response) > 300 else response |
| 74 | matches = re.findall(r"\(([A-D])\)", tail) |
| 75 | if len(matches) >= 2: |
| 76 | letters = set(matches) |
| 77 | if letters <= valid: |
| 78 | return "".join(sorted(letters)) |
| 79 | |
| 80 | # Fallback: look for consecutive capital letters like "ABC" near end |
| 81 | match = re.search(r"\b([A-D]{2,4})\b", tail) |
| 82 | if match: |
| 83 | letters = set(match.group(1)) |
| 84 | if letters <= valid: |
| 85 | return "".join(sorted(letters)) |
| 86 | |
| 87 | return None |
| 88 | |
| 89 | |
| 90 | def evaluate( |