| """String operation solver: reverse, case conversion, length, counting, |
| substring extraction, and letter sorting on a quoted or clearly-delimited |
| target string. |
| |
| Only fires when the operand string can be unambiguously extracted (quoted, |
| or following a clear "the string X" / "of X" marker) and the operation is one |
| of the supported shapes; otherwise falls through with confident=False. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import re |
|
|
| from routing_agent.classifier import TaskType |
| from routing_agent.models import Task |
| from routing_agent.solvers import SolverResult |
|
|
| _QUOTED_RE = re.compile(r"[\"'ββ](?P<value>[^\"'ββ]*)[\"'ββ]") |
|
|
|
|
| def _all_quoted_spans(prompt: str) -> list[str]: |
| return [m.group("value") for m in _QUOTED_RE.finditer(prompt)] |
|
|
|
|
| def _extract_target(prompt: str) -> str | None: |
| """Extract the operand string: the *last* quoted span. |
| |
| Most operations have exactly one quoted span (the operand itself). The |
| count-char-occurrences shape has two ("l" ... "hello") β the character |
| to search for, then the source string to search in β so the last quoted |
| span is always the operand being operated on. |
| """ |
| spans = _all_quoted_spans(prompt) |
| if not spans: |
| return None |
| return spans[-1] |
|
|
|
|
| |
| |
| |
| _REVERSE_WORD_ORDER_RE = re.compile( |
| r"\breverse\b.*\b(order of the words|word order)\b", re.IGNORECASE |
| ) |
| _REVERSE_RE = re.compile(r"\breverse\b", re.IGNORECASE) |
| _UPPER_RE = re.compile(r"\buppercase\b|\bupper[- ]?case\b", re.IGNORECASE) |
| _LOWER_RE = re.compile(r"\blowercase\b|\blower[- ]?case\b", re.IGNORECASE) |
| _LENGTH_RE = re.compile(r"how many (?P<unit>letters|characters)\b|\blength of\b", re.IGNORECASE) |
| _COUNT_CHAR_RE = re.compile( |
| r"how many (?:times does |occurrences of )?.*(?:occur|appear)", re.IGNORECASE |
| ) |
| _COUNT_WORD_RE = re.compile(r"how many words\b", re.IGNORECASE) |
| _FIRST_N_RE = re.compile(r"first (?P<n>\d+) characters", re.IGNORECASE) |
| _LAST_N_RE = re.compile(r"last (?P<n>\d+) characters", re.IGNORECASE) |
| _SORT_RE = re.compile(r"sort the letters", re.IGNORECASE) |
|
|
|
|
| def try_solve(task: Task, task_type: TaskType) -> SolverResult: |
| """Attempt to solve a string-operation task on a quoted operand. |
| |
| Requires the target string to be explicitly quoted in the prompt (no |
| heuristic "the rest of the sentence is the string" guessing) so the |
| solver never mis-segments prose from the operand. |
| """ |
| if task_type != TaskType.STRING_OP: |
| return SolverResult(answer=None, confident=False) |
|
|
| prompt = task.prompt.strip() |
|
|
| |
| |
| if _COUNT_CHAR_RE.search(prompt): |
| spans = _all_quoted_spans(prompt) |
| if len(spans) == 2 and len(spans[0]) == 1: |
| char, source = spans |
| return SolverResult(answer=str(source.count(char)), confident=True) |
| return SolverResult(answer=None, confident=False) |
|
|
| target = _extract_target(prompt) |
| if target is None: |
| return SolverResult(answer=None, confident=False) |
|
|
| if _REVERSE_WORD_ORDER_RE.search(prompt): |
| return SolverResult(answer=" ".join(reversed(target.split())), confident=True) |
|
|
| if _REVERSE_RE.search(prompt): |
| return SolverResult(answer=target[::-1], confident=True) |
|
|
| if _UPPER_RE.search(prompt): |
| return SolverResult(answer=target.upper(), confident=True) |
|
|
| if _LOWER_RE.search(prompt): |
| return SolverResult(answer=target.lower(), confident=True) |
|
|
| first_n_match = _FIRST_N_RE.search(prompt) |
| if first_n_match: |
| n = int(first_n_match.group("n")) |
| return SolverResult(answer=target[:n], confident=True) |
|
|
| last_n_match = _LAST_N_RE.search(prompt) |
| if last_n_match: |
| n = int(last_n_match.group("n")) |
| return SolverResult(answer=target[-n:] if n > 0 else "", confident=True) |
|
|
| if _COUNT_WORD_RE.search(prompt): |
| word_count = len(target.split()) |
| return SolverResult(answer=str(word_count), confident=True) |
|
|
| if _LENGTH_RE.search(prompt): |
| return SolverResult(answer=str(len(target)), confident=True) |
|
|
| if _SORT_RE.search(prompt): |
| return SolverResult(answer="".join(sorted(target)), confident=True) |
|
|
| return SolverResult(answer=None, confident=False) |
|
|