Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| import re | |
| from fractions import Fraction | |
| from typing import Any, Dict, List, Optional | |
| from context_parser import mentions_choice_letter | |
| from formatting import format_reply | |
| from models import SolverResult | |
| from quant_solver import solve_quant, is_quant_question, extract_choices | |
| class ResponseContext: | |
| def __init__( | |
| self, | |
| visible_user_text: str, | |
| question_text: str, | |
| options_text: str, | |
| question_category: str, | |
| question_difficulty: str, | |
| chat_history: Optional[List[Dict[str, Any]]] = None, | |
| ): | |
| self.visible_user_text = (visible_user_text or "").strip() | |
| self.question_text = (question_text or "").strip() | |
| self.options_text = (options_text or "").strip() | |
| self.question_category = (question_category or "").strip() | |
| self.question_difficulty = (question_difficulty or "").strip() | |
| self.chat_history = chat_history or [] | |
| def combined_question_block(self) -> str: | |
| parts: List[str] = [] | |
| if self.question_text: | |
| parts.append(self.question_text) | |
| if self.options_text: | |
| parts.append(self.options_text) | |
| return "\n".join(parts).strip() | |
| def normalize_text(s: str) -> str: | |
| return (s or "").strip().lower() | |
| def get_last_assistant_message(chat_history: List[Dict[str, Any]]) -> str: | |
| for item in reversed(chat_history or []): | |
| if str(item.get("role", "")).strip().lower() == "assistant": | |
| return str(item.get("text", "")).strip() | |
| return "" | |
| def build_choice_explanation( | |
| chosen_letter: str, | |
| result: SolverResult, | |
| choices: dict[str, str], | |
| help_mode: str, | |
| ) -> str: | |
| choice_text = choices.get(chosen_letter, "").strip() | |
| value_text = result.answer_value.strip() if result.answer_value else "" | |
| if help_mode == "hint": | |
| if value_text and choice_text: | |
| return f"Work out the value first, then compare it with choice {chosen_letter} ({choice_text})." | |
| if choice_text: | |
| return f"Focus on why choice {chosen_letter} fits better than the others." | |
| return f"Focus on whether choice {chosen_letter} matches the result." | |
| if help_mode == "walkthrough": | |
| if value_text and choice_text: | |
| return ( | |
| f"The calculation gives {value_text}.\n" | |
| f"Choice {chosen_letter} is {choice_text}, so it matches.\n" | |
| f"That is why {chosen_letter} is the correct answer." | |
| ) | |
| if choice_text: | |
| return f"Choice {chosen_letter} matches the result, so that is why it is correct." | |
| return f"The solved result matches choice {chosen_letter}, so that is why it is correct." | |
| if value_text and choice_text: | |
| return f"Yes — it’s {chosen_letter}, because the calculation gives {value_text}, and choice {chosen_letter} is {choice_text}." | |
| if choice_text: | |
| return f"Yes — it’s {chosen_letter}, because that option matches the result." | |
| return f"Yes — it’s {chosen_letter}." | |
| def parse_percent_value(text: str) -> Optional[int]: | |
| m = re.search(r"(\d+(?:\.\d+)?)\s*%", text or "") | |
| if not m: | |
| return None | |
| try: | |
| value = float(m.group(1)) | |
| if value.is_integer(): | |
| return int(value) | |
| return None | |
| except Exception: | |
| return None | |
| def percent_to_fraction_text(percent_value: int) -> str: | |
| frac = Fraction(percent_value, 100) | |
| return f"{frac.numerator}/{frac.denominator}" | |
| def find_matching_choice_for_value(options_text: str, target_value: str) -> Optional[str]: | |
| for line in (options_text or "").splitlines(): | |
| m = re.match(r"\s*([A-E])\)\s*(.+?)\s*$", line.strip(), re.IGNORECASE) | |
| if not m: | |
| continue | |
| letter = m.group(1).upper() | |
| value = m.group(2).strip() | |
| if value == target_value: | |
| return letter | |
| return None | |
| def handle_percent_fraction_question(ctx: ResponseContext, help_mode: str) -> Optional[SolverResult]: | |
| q = ctx.question_text | |
| u = normalize_text(ctx.visible_user_text) | |
| combined = f"{q}\n{ctx.options_text}".lower() | |
| mentions_fraction_task = ( | |
| "this equals" in combined | |
| or "as a fraction" in combined | |
| or "fraction" in combined | |
| or "pie chart" in combined | |
| or "%" in combined | |
| ) | |
| if not mentions_fraction_task: | |
| return None | |
| percent_value = parse_percent_value(q) or parse_percent_value(ctx.visible_user_text) | |
| if percent_value is None: | |
| return None | |
| fraction_text = percent_to_fraction_text(percent_value) | |
| answer_letter = find_matching_choice_for_value(ctx.options_text, fraction_text) | |
| asking_task = ( | |
| "what is the question asking" in u | |
| or "what is this asking" in u | |
| or "what do i need to do" in u | |
| ) | |
| asking_hint = ( | |
| "hint" in u | |
| or "how do i start" in u | |
| or "first step" in u | |
| or "what do i do first" in u | |
| or "can you help" in u | |
| or u in {"help", "help me", "i dont get it", "i don't get it"} | |
| ) | |
| asking_answer = ( | |
| "answer" in u | |
| or "which one" in u | |
| or "what answer" in u | |
| or "what is 25% as a fraction" in u | |
| or "are you sure" in u | |
| ) | |
| if asking_task: | |
| reply = "It is asking you to convert 25% into an equivalent fraction and then match it to the correct option." | |
| return SolverResult( | |
| reply=reply, | |
| domain="quant", | |
| solved=False, | |
| help_mode="hint", | |
| answer_letter=answer_letter, | |
| answer_value=fraction_text, | |
| ) | |
| if asking_hint or help_mode == "hint": | |
| reply = ( | |
| "Think of percent as 'out of 100'.\n" | |
| f"So {percent_value}% means {percent_value}/100.\n" | |
| "Then simplify that fraction and compare it with the options." | |
| ) | |
| return SolverResult( | |
| reply=reply, | |
| domain="quant", | |
| solved=True, | |
| help_mode="hint", | |
| answer_letter=answer_letter, | |
| answer_value=fraction_text, | |
| ) | |
| if asking_answer or help_mode == "answer": | |
| if answer_letter: | |
| reply = f"{percent_value}% = {percent_value}/100 = {fraction_text}, so the correct answer is {answer_letter}." | |
| else: | |
| reply = f"{percent_value}% = {percent_value}/100 = {fraction_text}." | |
| return SolverResult( | |
| reply=reply, | |
| domain="quant", | |
| solved=True, | |
| help_mode="answer", | |
| answer_letter=answer_letter, | |
| answer_value=fraction_text, | |
| ) | |
| if help_mode == "walkthrough": | |
| if answer_letter: | |
| reply = ( | |
| f"Percent means 'per 100', so {percent_value}% = {percent_value}/100.\n" | |
| f"Simplify {percent_value}/100 to {fraction_text}.\n" | |
| f"Now compare that with the options: {fraction_text} matches choice {answer_letter}." | |
| ) | |
| else: | |
| reply = ( | |
| f"Percent means 'per 100', so {percent_value}% = {percent_value}/100.\n" | |
| f"Simplify {percent_value}/100 to {fraction_text}." | |
| ) | |
| return SolverResult( | |
| reply=reply, | |
| domain="quant", | |
| solved=True, | |
| help_mode="walkthrough", | |
| answer_letter=answer_letter, | |
| answer_value=fraction_text, | |
| ) | |
| return None | |
| def handle_choice_letter_followup(ctx: ResponseContext, help_mode: str) -> Optional[SolverResult]: | |
| question_block = ctx.combined_question_block | |
| if not question_block: | |
| return None | |
| user_text = ctx.visible_user_text | |
| asked_letter = mentions_choice_letter(user_text) | |
| if not asked_letter: | |
| return None | |
| solved = solve_quant(question_block, "answer") | |
| choices = extract_choices(question_block) | |
| if asked_letter and solved.answer_letter and asked_letter == solved.answer_letter: | |
| reply = build_choice_explanation(asked_letter, solved, choices, help_mode) | |
| return SolverResult( | |
| reply=reply, | |
| domain="quant", | |
| solved=solved.solved, | |
| help_mode=help_mode, | |
| answer_letter=solved.answer_letter, | |
| answer_value=solved.answer_value, | |
| ) | |
| if asked_letter and solved.answer_letter and asked_letter != solved.answer_letter: | |
| correct_choice_text = choices.get(solved.answer_letter, "").strip() | |
| if help_mode == "hint": | |
| reply = f"Check the calculation again and compare your result with choice {solved.answer_letter}, not {asked_letter}." | |
| elif help_mode == "walkthrough": | |
| reply = ( | |
| f"It is not {asked_letter}.\n" | |
| f"The calculation leads to choice {solved.answer_letter}" | |
| + (f", which is {correct_choice_text}." if correct_choice_text else ".") | |
| ) | |
| else: | |
| reply = f"It is not {asked_letter} — the correct choice is {solved.answer_letter}." | |
| return SolverResult( | |
| reply=reply, | |
| domain="quant", | |
| solved=solved.solved, | |
| help_mode=help_mode, | |
| answer_letter=solved.answer_letter, | |
| answer_value=solved.answer_value, | |
| ) | |
| return None | |
| def handle_conversational_followup(ctx: ResponseContext, help_mode: str) -> Optional[SolverResult]: | |
| user_text = ctx.visible_user_text | |
| lower = normalize_text(user_text) | |
| question_block = ctx.combined_question_block | |
| if not question_block: | |
| return None | |
| percent_fraction = handle_percent_fraction_question(ctx, help_mode) | |
| if percent_fraction is not None: | |
| return percent_fraction | |
| choice_followup = handle_choice_letter_followup(ctx, help_mode) | |
| if choice_followup is not None: | |
| return choice_followup | |
| if "what is the question asking" in lower or "what is this asking" in lower: | |
| if "variability" in ctx.question_text.lower() or "spread" in ctx.question_text.lower(): | |
| reply = "It is asking you to compare how spread out the answer choices are and identify which dataset varies the most." | |
| else: | |
| reply = "It is asking you to identify the mathematical task and then match the result to one of the options." | |
| return SolverResult(reply=reply, domain="quant", solved=False, help_mode="hint") | |
| if lower in {"help", "can you help", "help me", "i dont get it", "i don't get it"}: | |
| if help_mode == "hint": | |
| return solve_quant(question_block, "hint") | |
| if help_mode == "walkthrough": | |
| return solve_quant(question_block, "walkthrough") | |
| return solve_quant(question_block, "answer") | |
| if "first step" in lower or "how do i start" in lower or "what do i do first" in lower: | |
| return solve_quant(question_block, "hint") | |
| if "hint" in lower: | |
| return solve_quant(question_block, "hint") | |
| if "why" in lower or "explain" in lower: | |
| walkthrough_result = solve_quant(question_block, "walkthrough") | |
| return walkthrough_result | |
| if "answer" in lower or "which one" in lower or "what answer" in lower or "are you sure" in lower: | |
| return solve_quant(question_block, "answer") | |
| last_ai = get_last_assistant_message(ctx.chat_history) | |
| if last_ai and ("that" in lower or "it" in lower): | |
| return solve_quant(question_block, "walkthrough") | |
| return None | |
| def solve_verbal_or_general(user_text: str, help_mode: str) -> SolverResult: | |
| lower = normalize_text(user_text) | |
| if any( | |
| k in lower | |
| for k in [ | |
| "sentence correction", | |
| "grammar", | |
| "verbal", | |
| "critical reasoning", | |
| "reading comprehension", | |
| ] | |
| ): | |
| if help_mode == "hint": | |
| reply = ( | |
| "First identify the task:\n" | |
| "- Sentence Correction: grammar + meaning\n" | |
| "- Critical Reasoning: conclusion, evidence, assumption\n" | |
| "- Reading Comprehension: passage role, inference, detail" | |
| ) | |
| elif help_mode == "walkthrough": | |
| reply = ( | |
| "I can help verbally too, but this backend is strongest on quant-style items. " | |
| "For verbal, I’d use elimination based on grammar, logic, scope, or passage support." | |
| ) | |
| else: | |
| reply = "I can help with verbal strategy, but this version is strongest on quant-style questions right now." | |
| return SolverResult(reply=reply, domain="verbal", solved=False, help_mode=help_mode) | |
| if help_mode == "hint": | |
| reply = "I can help. Ask for a hint, an explanation, or the answer." | |
| elif help_mode == "walkthrough": | |
| reply = "I can talk it through step by step." | |
| else: | |
| reply = "Ask naturally and I’ll help from the current question context when available." | |
| return SolverResult(reply=reply, domain="fallback", solved=False, help_mode=help_mode) | |
| def generate_response( | |
| raw_user_text: str, | |
| tone: float, | |
| verbosity: float, | |
| transparency: float, | |
| help_mode: str, | |
| hidden_context: str = "", | |
| chat_history: Optional[List[Dict[str, Any]]] = None, | |
| question_text: str = "", | |
| options_text: str = "", | |
| question_category: str = "", | |
| question_difficulty: str = "", | |
| retrieval_context: str = "", | |
| ) -> SolverResult: | |
| ctx = ResponseContext( | |
| visible_user_text=raw_user_text, | |
| question_text=question_text, | |
| options_text=options_text, | |
| question_category=question_category, | |
| question_difficulty=question_difficulty, | |
| chat_history=chat_history, | |
| ) | |
| ctx.retrieval_context = retrieval_context | |
| solver_input = question_text.strip() if question_text.strip() else raw_user_text.strip() | |
| if is_quant_question(solver_input): | |
| return solve_quant(solver_input, help_mode) | |
| visible_user_text = ctx.visible_user_text | |
| question_block = ctx.combined_question_block | |
| if not visible_user_text: | |
| result = SolverResult( | |
| reply="Ask a question and I’ll help using the current in-game context.", | |
| domain="fallback", | |
| solved=False, | |
| help_mode=help_mode, | |
| ) | |
| result.reply = format_reply(result.reply, tone, verbosity, transparency, help_mode) | |
| return result | |
| followup = handle_conversational_followup(ctx, help_mode) | |
| if followup is not None: | |
| followup.reply = format_reply(followup.reply, tone, verbosity, transparency, followup.help_mode) | |
| return followup | |
| percent_fraction = handle_percent_fraction_question(ctx, help_mode) | |
| if percent_fraction is not None: | |
| percent_fraction.reply = format_reply(percent_fraction.reply, tone, verbosity, transparency, percent_fraction.help_mode) | |
| return percent_fraction | |
| if question_block: | |
| result = solve_quant(question_block, help_mode) | |
| if result.solved or result.answer_value or result.answer_letter: | |
| result.reply = format_reply(result.reply, tone, verbosity, transparency, help_mode) | |
| return result | |
| if ctx.question_text: | |
| result = solve_quant(ctx.question_text, help_mode) | |
| if result.solved or result.answer_value or result.answer_letter: | |
| result.reply = format_reply(result.reply, tone, verbosity, transparency, help_mode) | |
| return result | |
| result = solve_verbal_or_general(visible_user_text, help_mode) | |
| result.reply = format_reply(result.reply, tone, verbosity, transparency, help_mode) | |
| return result |