Update conversation_logic.py
Browse files- conversation_logic.py +281 -100
conversation_logic.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
# conversation_logic.py
|
| 2 |
from __future__ import annotations
|
| 3 |
|
| 4 |
import re
|
|
@@ -152,6 +151,203 @@ def _safe_steps(steps: List[str]) -> List[str]:
|
|
| 152 |
return cleaned
|
| 153 |
|
| 154 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
def _compose_reply(
|
| 156 |
result: SolverResult,
|
| 157 |
intent: str,
|
|
@@ -160,6 +356,17 @@ def _compose_reply(
|
|
| 160 |
) -> str:
|
| 161 |
steps = _safe_steps(result.steps or [])
|
| 162 |
topic = (result.topic or "").lower().strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
|
| 164 |
def topic_hint_fallback() -> str:
|
| 165 |
if topic == "algebra":
|
|
@@ -179,6 +386,9 @@ def _compose_reply(
|
|
| 179 |
return "Focus on the main relationship first."
|
| 180 |
|
| 181 |
def topic_method_fallback() -> str:
|
|
|
|
|
|
|
|
|
|
| 182 |
if topic == "algebra":
|
| 183 |
return "\n".join([
|
| 184 |
"- Treat it as an equation.",
|
|
@@ -217,6 +427,9 @@ def _compose_reply(
|
|
| 217 |
return "I can explain the method, but I do not have enough structured steps yet."
|
| 218 |
|
| 219 |
if intent == "hint":
|
|
|
|
|
|
|
|
|
|
| 220 |
if steps:
|
| 221 |
first = steps[0].lower()
|
| 222 |
|
|
@@ -234,11 +447,15 @@ def _compose_reply(
|
|
| 234 |
return topic_hint_fallback()
|
| 235 |
|
| 236 |
if intent == "instruction":
|
|
|
|
|
|
|
| 237 |
if steps:
|
| 238 |
return f"First step: {steps[0]}"
|
| 239 |
return "First, identify the key relationship or comparison in the question."
|
| 240 |
|
| 241 |
if intent == "definition":
|
|
|
|
|
|
|
| 242 |
if steps:
|
| 243 |
return f"Here is the idea in context:\n- {steps[0]}"
|
| 244 |
return "This is asking for the meaning of the term or idea in the question."
|
|
@@ -288,6 +505,9 @@ def _compose_reply(
|
|
| 288 |
|
| 289 |
return "\n".join(f"- {s}" for s in shown_steps)
|
| 290 |
|
|
|
|
|
|
|
|
|
|
| 291 |
if normalize_category(category) == "Verbal":
|
| 292 |
return "I can help analyse the wording or logic, but I need the full question text to guide you properly."
|
| 293 |
|
|
@@ -328,19 +548,6 @@ def is_explainer_request(text: str) -> bool:
|
|
| 328 |
return any(p in t for p in explainer_signals)
|
| 329 |
|
| 330 |
|
| 331 |
-
def _normalize_text(text: str) -> str:
|
| 332 |
-
return re.sub(r"\s+", " ", (text or "").strip().lower())
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
def _extract_keywords(text: str) -> Set[str]:
|
| 336 |
-
raw = re.findall(r"[a-zA-Z][a-zA-Z0-9_+-]*", (text or "").lower())
|
| 337 |
-
stop = {
|
| 338 |
-
"the", "a", "an", "is", "are", "to", "of", "for", "and", "or", "in", "on", "at", "by", "this", "that",
|
| 339 |
-
"it", "be", "do", "i", "me", "my", "you", "how", "what", "why", "give", "show", "please", "can",
|
| 340 |
-
}
|
| 341 |
-
return {w for w in raw if len(w) > 2 and w not in stop}
|
| 342 |
-
|
| 343 |
-
|
| 344 |
def _infer_structure_terms(question_text: str, topic: Optional[str], question_type: Optional[str]) -> List[str]:
|
| 345 |
terms: List[str] = []
|
| 346 |
|
|
@@ -822,48 +1029,6 @@ def _pick_teaching_line(
|
|
| 822 |
return best_line
|
| 823 |
|
| 824 |
|
| 825 |
-
def _safe_meta_list(items: Any) -> List[str]:
|
| 826 |
-
if not items:
|
| 827 |
-
return []
|
| 828 |
-
if isinstance(items, list):
|
| 829 |
-
return [str(x).strip() for x in items if str(x).strip()]
|
| 830 |
-
if isinstance(items, tuple):
|
| 831 |
-
return [str(x).strip() for x in items if str(x).strip()]
|
| 832 |
-
if isinstance(items, str):
|
| 833 |
-
text = items.strip()
|
| 834 |
-
return [text] if text else []
|
| 835 |
-
return []
|
| 836 |
-
|
| 837 |
-
|
| 838 |
-
def _safe_meta_text(value: Any) -> Optional[str]:
|
| 839 |
-
if value is None:
|
| 840 |
-
return None
|
| 841 |
-
text = str(value).strip()
|
| 842 |
-
return text or None
|
| 843 |
-
|
| 844 |
-
|
| 845 |
-
def _extract_explainer_scaffold(explainer_result: Any) -> Dict[str, Any]:
|
| 846 |
-
scaffold = getattr(explainer_result, "scaffold", None)
|
| 847 |
-
|
| 848 |
-
if scaffold is None:
|
| 849 |
-
return {}
|
| 850 |
-
|
| 851 |
-
return {
|
| 852 |
-
"concept": _safe_meta_text(getattr(scaffold, "concept", None)),
|
| 853 |
-
"ask": _safe_meta_text(getattr(scaffold, "ask", None)),
|
| 854 |
-
"givens": _safe_meta_list(getattr(scaffold, "givens", [])),
|
| 855 |
-
"target": _safe_meta_text(getattr(scaffold, "target", None)),
|
| 856 |
-
"setup_actions": _safe_meta_list(getattr(scaffold, "setup_actions", [])),
|
| 857 |
-
"intermediate_steps": _safe_meta_list(getattr(scaffold, "intermediate_steps", [])),
|
| 858 |
-
"first_move": _safe_meta_text(getattr(scaffold, "first_move", None)),
|
| 859 |
-
"next_hint": _safe_meta_text(getattr(scaffold, "next_hint", None)),
|
| 860 |
-
"common_traps": _safe_meta_list(getattr(scaffold, "common_traps", [])),
|
| 861 |
-
"variables_to_define": _safe_meta_list(getattr(scaffold, "variables_to_define", [])),
|
| 862 |
-
"equations_to_form": _safe_meta_list(getattr(scaffold, "equations_to_form", [])),
|
| 863 |
-
"answer_hidden": bool(getattr(scaffold, "answer_hidden", True)),
|
| 864 |
-
}
|
| 865 |
-
|
| 866 |
-
|
| 867 |
class ConversationEngine:
|
| 868 |
def __init__(
|
| 869 |
self,
|
|
@@ -925,47 +1090,49 @@ class ConversationEngine:
|
|
| 925 |
meta={},
|
| 926 |
)
|
| 927 |
|
| 928 |
-
# 1. explainer
|
| 929 |
-
|
| 930 |
-
|
| 931 |
-
|
| 932 |
-
|
| 933 |
-
|
| 934 |
-
|
| 935 |
-
|
| 936 |
-
|
| 937 |
-
|
| 938 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 939 |
|
| 940 |
-
|
| 941 |
-
|
| 942 |
-
|
| 943 |
-
|
| 944 |
-
|
| 945 |
-
|
| 946 |
-
|
| 947 |
-
|
| 948 |
-
|
| 949 |
-
|
| 950 |
-
|
| 951 |
-
|
| 952 |
-
|
| 953 |
-
|
| 954 |
-
|
| 955 |
-
|
| 956 |
-
|
| 957 |
-
|
| 958 |
-
|
| 959 |
-
|
| 960 |
-
|
| 961 |
-
|
| 962 |
-
|
| 963 |
-
|
| 964 |
-
|
| 965 |
-
),
|
| 966 |
-
"scaffold": scaffold_meta,
|
| 967 |
-
}
|
| 968 |
-
return result
|
| 969 |
|
| 970 |
# 2. normal solver path
|
| 971 |
if is_quant:
|
|
@@ -977,10 +1144,22 @@ class ConversationEngine:
|
|
| 977 |
result.help_mode = resolved_help_mode
|
| 978 |
|
| 979 |
if not result.topic or result.topic in {"general_quant", "general", "unknown"}:
|
| 980 |
-
result.topic = question_topic
|
| 981 |
|
| 982 |
result.domain = "quant"
|
| 983 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 984 |
# 3. compose base reply
|
| 985 |
reply = _compose_reply(
|
| 986 |
result=result,
|
|
@@ -1110,14 +1289,16 @@ class ConversationEngine:
|
|
| 1110 |
result.internal_answer = None
|
| 1111 |
result.reply = reply
|
| 1112 |
result.help_mode = resolved_help_mode
|
| 1113 |
-
|
|
|
|
|
|
|
| 1114 |
"intent": resolved_intent,
|
| 1115 |
"question_text": question_text or "",
|
| 1116 |
"options_count": len(options_text or []),
|
| 1117 |
"category": inferred_category,
|
| 1118 |
"question_type": question_type,
|
| 1119 |
"classified_topic": question_topic,
|
| 1120 |
-
|
| 1121 |
-
|
| 1122 |
|
| 1123 |
return result
|
|
|
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
import re
|
|
|
|
| 151 |
return cleaned
|
| 152 |
|
| 153 |
|
| 154 |
+
def _normalize_text(text: str) -> str:
|
| 155 |
+
return re.sub(r"\s+", " ", (text or "").strip().lower())
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def _extract_keywords(text: str) -> Set[str]:
|
| 159 |
+
raw = re.findall(r"[a-zA-Z][a-zA-Z0-9_+-]*", (text or "").lower())
|
| 160 |
+
stop = {
|
| 161 |
+
"the", "a", "an", "is", "are", "to", "of", "for", "and", "or", "in", "on", "at", "by", "this", "that",
|
| 162 |
+
"it", "be", "do", "i", "me", "my", "you", "how", "what", "why", "give", "show", "please", "can",
|
| 163 |
+
}
|
| 164 |
+
return {w for w in raw if len(w) > 2 and w not in stop}
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def _safe_meta_list(items: Any) -> List[str]:
|
| 168 |
+
if not items:
|
| 169 |
+
return []
|
| 170 |
+
if isinstance(items, list):
|
| 171 |
+
return [str(x).strip() for x in items if str(x).strip()]
|
| 172 |
+
if isinstance(items, tuple):
|
| 173 |
+
return [str(x).strip() for x in items if str(x).strip()]
|
| 174 |
+
if isinstance(items, str):
|
| 175 |
+
text = items.strip()
|
| 176 |
+
return [text] if text else []
|
| 177 |
+
return []
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
def _safe_meta_text(value: Any) -> Optional[str]:
|
| 181 |
+
if value is None:
|
| 182 |
+
return None
|
| 183 |
+
text = str(value).strip()
|
| 184 |
+
return text or None
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def _extract_explainer_scaffold(explainer_result: Any) -> Dict[str, Any]:
|
| 188 |
+
scaffold = getattr(explainer_result, "scaffold", None)
|
| 189 |
+
|
| 190 |
+
if scaffold is None:
|
| 191 |
+
return {}
|
| 192 |
+
|
| 193 |
+
return {
|
| 194 |
+
"concept": _safe_meta_text(getattr(scaffold, "concept", None)),
|
| 195 |
+
"ask": _safe_meta_text(getattr(scaffold, "ask", None)),
|
| 196 |
+
"givens": _safe_meta_list(getattr(scaffold, "givens", [])),
|
| 197 |
+
"target": _safe_meta_text(getattr(scaffold, "target", None)),
|
| 198 |
+
"setup_actions": _safe_meta_list(getattr(scaffold, "setup_actions", [])),
|
| 199 |
+
"intermediate_steps": _safe_meta_list(getattr(scaffold, "intermediate_steps", [])),
|
| 200 |
+
"first_move": _safe_meta_text(getattr(scaffold, "first_move", None)),
|
| 201 |
+
"next_hint": _safe_meta_text(getattr(scaffold, "next_hint", None)),
|
| 202 |
+
"common_traps": _safe_meta_list(getattr(scaffold, "common_traps", [])),
|
| 203 |
+
"variables_to_define": _safe_meta_list(getattr(scaffold, "variables_to_define", [])),
|
| 204 |
+
"equations_to_form": _safe_meta_list(getattr(scaffold, "equations_to_form", [])),
|
| 205 |
+
"answer_hidden": bool(getattr(scaffold, "answer_hidden", True)),
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def _build_scaffold_reply(
|
| 210 |
+
intent: str,
|
| 211 |
+
help_mode: str,
|
| 212 |
+
scaffold: Dict[str, Any],
|
| 213 |
+
summary: Optional[str],
|
| 214 |
+
teaching_points: List[str],
|
| 215 |
+
verbosity: float,
|
| 216 |
+
transparency: float,
|
| 217 |
+
) -> Optional[str]:
|
| 218 |
+
if not scaffold and not summary and not teaching_points:
|
| 219 |
+
return None
|
| 220 |
+
|
| 221 |
+
ask = _safe_meta_text(scaffold.get("ask")) if scaffold else None
|
| 222 |
+
first_move = _safe_meta_text(scaffold.get("first_move")) if scaffold else None
|
| 223 |
+
next_hint = _safe_meta_text(scaffold.get("next_hint")) if scaffold else None
|
| 224 |
+
setup_actions = _safe_meta_list(scaffold.get("setup_actions", [])) if scaffold else []
|
| 225 |
+
intermediate_steps = _safe_meta_list(scaffold.get("intermediate_steps", [])) if scaffold else []
|
| 226 |
+
variables_to_define = _safe_meta_list(scaffold.get("variables_to_define", [])) if scaffold else []
|
| 227 |
+
equations_to_form = _safe_meta_list(scaffold.get("equations_to_form", [])) if scaffold else []
|
| 228 |
+
common_traps = _safe_meta_list(scaffold.get("common_traps", [])) if scaffold else []
|
| 229 |
+
|
| 230 |
+
target_mode = help_mode or intent
|
| 231 |
+
|
| 232 |
+
if target_mode == "hint" or intent == "hint":
|
| 233 |
+
if first_move:
|
| 234 |
+
return first_move
|
| 235 |
+
if next_hint:
|
| 236 |
+
return next_hint
|
| 237 |
+
if setup_actions:
|
| 238 |
+
return setup_actions[0]
|
| 239 |
+
if ask:
|
| 240 |
+
return ask
|
| 241 |
+
if teaching_points:
|
| 242 |
+
return teaching_points[0]
|
| 243 |
+
return None
|
| 244 |
+
|
| 245 |
+
if target_mode == "instruction" or intent == "instruction":
|
| 246 |
+
if first_move:
|
| 247 |
+
return f"First step: {first_move}"
|
| 248 |
+
if setup_actions:
|
| 249 |
+
return f"First step: {setup_actions[0]}"
|
| 250 |
+
if ask:
|
| 251 |
+
return f"First, identify this: {ask}"
|
| 252 |
+
return None
|
| 253 |
+
|
| 254 |
+
if target_mode == "definition" or intent == "definition":
|
| 255 |
+
if summary:
|
| 256 |
+
return summary
|
| 257 |
+
if teaching_points:
|
| 258 |
+
return f"Here is the idea in context:\n- {teaching_points[0]}"
|
| 259 |
+
if ask:
|
| 260 |
+
return ask
|
| 261 |
+
return None
|
| 262 |
+
|
| 263 |
+
if target_mode in {"walkthrough", "step_by_step"} or intent in {"walkthrough", "step_by_step"}:
|
| 264 |
+
lines: List[str] = []
|
| 265 |
+
|
| 266 |
+
sequence: List[str] = []
|
| 267 |
+
if ask:
|
| 268 |
+
sequence.append(f"Identify this first: {ask}")
|
| 269 |
+
sequence.extend(setup_actions)
|
| 270 |
+
sequence.extend(intermediate_steps)
|
| 271 |
+
|
| 272 |
+
if first_move and first_move not in sequence:
|
| 273 |
+
sequence.insert(0, first_move)
|
| 274 |
+
|
| 275 |
+
if next_hint and next_hint not in sequence:
|
| 276 |
+
sequence.append(next_hint)
|
| 277 |
+
|
| 278 |
+
if not sequence and summary:
|
| 279 |
+
sequence.append(summary)
|
| 280 |
+
|
| 281 |
+
if not sequence and teaching_points:
|
| 282 |
+
sequence.extend(teaching_points[:3])
|
| 283 |
+
|
| 284 |
+
if not sequence:
|
| 285 |
+
return None
|
| 286 |
+
|
| 287 |
+
if verbosity < 0.25:
|
| 288 |
+
shown = sequence[:1]
|
| 289 |
+
elif verbosity < 0.6:
|
| 290 |
+
shown = sequence[:2]
|
| 291 |
+
elif verbosity < 0.85:
|
| 292 |
+
shown = sequence[:4]
|
| 293 |
+
else:
|
| 294 |
+
shown = sequence[:6]
|
| 295 |
+
|
| 296 |
+
return "\n".join(f"- {s}" for s in shown)
|
| 297 |
+
|
| 298 |
+
if target_mode in {"method", "concept", "explain"} or intent in {"method", "concept", "explain"}:
|
| 299 |
+
lines: List[str] = []
|
| 300 |
+
|
| 301 |
+
if summary:
|
| 302 |
+
lines.append(summary)
|
| 303 |
+
|
| 304 |
+
if ask:
|
| 305 |
+
lines.append(f"Start by identifying: {ask}")
|
| 306 |
+
|
| 307 |
+
core_steps: List[str] = []
|
| 308 |
+
if first_move:
|
| 309 |
+
core_steps.append(first_move)
|
| 310 |
+
core_steps.extend(setup_actions[:3])
|
| 311 |
+
|
| 312 |
+
if transparency >= 0.45:
|
| 313 |
+
core_steps.extend(intermediate_steps[:2])
|
| 314 |
+
|
| 315 |
+
if core_steps:
|
| 316 |
+
lines.extend(core_steps[:1] if verbosity < 0.35 else core_steps[:3] if verbosity < 0.75 else core_steps[:5])
|
| 317 |
+
|
| 318 |
+
if transparency >= 0.55 and variables_to_define:
|
| 319 |
+
lines.append(f"Useful variable setup: {variables_to_define[0]}")
|
| 320 |
+
|
| 321 |
+
if transparency >= 0.6 and equations_to_form:
|
| 322 |
+
lines.append(f"Key equation: {equations_to_form[0]}")
|
| 323 |
+
|
| 324 |
+
if transparency >= 0.65 and next_hint:
|
| 325 |
+
lines.append(f"Next idea: {next_hint}")
|
| 326 |
+
|
| 327 |
+
if (transparency >= 0.75 or verbosity >= 0.75) and common_traps:
|
| 328 |
+
lines.append(f"Watch out for: {common_traps[0]}")
|
| 329 |
+
|
| 330 |
+
if not lines and teaching_points:
|
| 331 |
+
lines.extend(teaching_points[:2])
|
| 332 |
+
|
| 333 |
+
if not lines:
|
| 334 |
+
return None
|
| 335 |
+
|
| 336 |
+
return "\n".join(f"- {s}" if not s.startswith("- ") and len(lines) > 1 else s for s in lines)
|
| 337 |
+
|
| 338 |
+
# generic fallback
|
| 339 |
+
if first_move:
|
| 340 |
+
return first_move
|
| 341 |
+
if setup_actions:
|
| 342 |
+
return setup_actions[0]
|
| 343 |
+
if summary:
|
| 344 |
+
return summary
|
| 345 |
+
if teaching_points:
|
| 346 |
+
return teaching_points[0]
|
| 347 |
+
|
| 348 |
+
return None
|
| 349 |
+
|
| 350 |
+
|
| 351 |
def _compose_reply(
|
| 352 |
result: SolverResult,
|
| 353 |
intent: str,
|
|
|
|
| 356 |
) -> str:
|
| 357 |
steps = _safe_steps(result.steps or [])
|
| 358 |
topic = (result.topic or "").lower().strip()
|
| 359 |
+
meta = result.meta or {}
|
| 360 |
+
|
| 361 |
+
scaffold_reply = _build_scaffold_reply(
|
| 362 |
+
intent=intent,
|
| 363 |
+
help_mode=result.help_mode,
|
| 364 |
+
scaffold=meta.get("scaffold", {}) if isinstance(meta, dict) else {},
|
| 365 |
+
summary=_safe_meta_text(meta.get("explainer_summary")) if isinstance(meta, dict) else None,
|
| 366 |
+
teaching_points=_safe_meta_list(meta.get("explainer_teaching_points", [])) if isinstance(meta, dict) else [],
|
| 367 |
+
verbosity=verbosity,
|
| 368 |
+
transparency=0.5,
|
| 369 |
+
)
|
| 370 |
|
| 371 |
def topic_hint_fallback() -> str:
|
| 372 |
if topic == "algebra":
|
|
|
|
| 386 |
return "Focus on the main relationship first."
|
| 387 |
|
| 388 |
def topic_method_fallback() -> str:
|
| 389 |
+
if scaffold_reply:
|
| 390 |
+
return scaffold_reply
|
| 391 |
+
|
| 392 |
if topic == "algebra":
|
| 393 |
return "\n".join([
|
| 394 |
"- Treat it as an equation.",
|
|
|
|
| 427 |
return "I can explain the method, but I do not have enough structured steps yet."
|
| 428 |
|
| 429 |
if intent == "hint":
|
| 430 |
+
if scaffold_reply:
|
| 431 |
+
return scaffold_reply
|
| 432 |
+
|
| 433 |
if steps:
|
| 434 |
first = steps[0].lower()
|
| 435 |
|
|
|
|
| 447 |
return topic_hint_fallback()
|
| 448 |
|
| 449 |
if intent == "instruction":
|
| 450 |
+
if scaffold_reply:
|
| 451 |
+
return scaffold_reply
|
| 452 |
if steps:
|
| 453 |
return f"First step: {steps[0]}"
|
| 454 |
return "First, identify the key relationship or comparison in the question."
|
| 455 |
|
| 456 |
if intent == "definition":
|
| 457 |
+
if scaffold_reply:
|
| 458 |
+
return scaffold_reply
|
| 459 |
if steps:
|
| 460 |
return f"Here is the idea in context:\n- {steps[0]}"
|
| 461 |
return "This is asking for the meaning of the term or idea in the question."
|
|
|
|
| 505 |
|
| 506 |
return "\n".join(f"- {s}" for s in shown_steps)
|
| 507 |
|
| 508 |
+
if scaffold_reply:
|
| 509 |
+
return scaffold_reply
|
| 510 |
+
|
| 511 |
if normalize_category(category) == "Verbal":
|
| 512 |
return "I can help analyse the wording or logic, but I need the full question text to guide you properly."
|
| 513 |
|
|
|
|
| 548 |
return any(p in t for p in explainer_signals)
|
| 549 |
|
| 550 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 551 |
def _infer_structure_terms(question_text: str, topic: Optional[str], question_type: Optional[str]) -> List[str]:
|
| 552 |
terms: List[str] = []
|
| 553 |
|
|
|
|
| 1029 |
return best_line
|
| 1030 |
|
| 1031 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1032 |
class ConversationEngine:
|
| 1033 |
def __init__(
|
| 1034 |
self,
|
|
|
|
| 1090 |
meta={},
|
| 1091 |
)
|
| 1092 |
|
| 1093 |
+
# 1. Try explainer early so scaffold is available even when solver is weak
|
| 1094 |
+
explainer_result = route_explainer(solver_input)
|
| 1095 |
+
explainer_understood = bool(explainer_result is not None and getattr(explainer_result, "understood", False))
|
| 1096 |
+
explainer_scaffold = _extract_explainer_scaffold(explainer_result) if explainer_understood else {}
|
| 1097 |
+
explainer_summary = getattr(explainer_result, "summary", None) if explainer_understood else None
|
| 1098 |
+
explainer_teaching_points = _safe_meta_list(
|
| 1099 |
+
getattr(explainer_result, "teaching_points", [])
|
| 1100 |
+
) if explainer_understood else []
|
| 1101 |
+
|
| 1102 |
+
# 1a. Explicit explainer request returns scaffold-rich explainer response
|
| 1103 |
+
if is_explainer_request(user_text or solver_input) and explainer_understood:
|
| 1104 |
+
reply = format_explainer_response(
|
| 1105 |
+
result=explainer_result,
|
| 1106 |
+
tone=tone,
|
| 1107 |
+
verbosity=verbosity,
|
| 1108 |
+
transparency=transparency,
|
| 1109 |
+
)
|
| 1110 |
|
| 1111 |
+
result.domain = "quant" if is_quant else "general"
|
| 1112 |
+
result.solved = False
|
| 1113 |
+
result.help_mode = "explain"
|
| 1114 |
+
result.topic = getattr(explainer_result, "topic", None) or question_topic
|
| 1115 |
+
result.answer_letter = None
|
| 1116 |
+
result.answer_value = None
|
| 1117 |
+
result.internal_answer = None
|
| 1118 |
+
result.used_retrieval = False
|
| 1119 |
+
result.used_generator = False
|
| 1120 |
+
result.reply = reply
|
| 1121 |
+
result.meta = {
|
| 1122 |
+
"intent": "explain_question",
|
| 1123 |
+
"question_text": question_text or "",
|
| 1124 |
+
"options_count": len(options_text or []),
|
| 1125 |
+
"category": inferred_category,
|
| 1126 |
+
"question_type": question_type,
|
| 1127 |
+
"classified_topic": question_topic,
|
| 1128 |
+
"explainer_used": True,
|
| 1129 |
+
"bridge_ready": bool(getattr(explainer_result, "meta", {}).get("bridge_ready", False)),
|
| 1130 |
+
"hint_style": getattr(explainer_result, "meta", {}).get("hint_style"),
|
| 1131 |
+
"explainer_summary": explainer_summary,
|
| 1132 |
+
"explainer_teaching_points": explainer_teaching_points,
|
| 1133 |
+
"scaffold": explainer_scaffold,
|
| 1134 |
+
}
|
| 1135 |
+
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1136 |
|
| 1137 |
# 2. normal solver path
|
| 1138 |
if is_quant:
|
|
|
|
| 1144 |
result.help_mode = resolved_help_mode
|
| 1145 |
|
| 1146 |
if not result.topic or result.topic in {"general_quant", "general", "unknown"}:
|
| 1147 |
+
result.topic = getattr(explainer_result, "topic", None) if explainer_understood else question_topic
|
| 1148 |
|
| 1149 |
result.domain = "quant"
|
| 1150 |
|
| 1151 |
+
# 2a. Attach explainer scaffold into result meta so generic paths can use it
|
| 1152 |
+
if result.meta is None:
|
| 1153 |
+
result.meta = {}
|
| 1154 |
+
|
| 1155 |
+
if explainer_understood:
|
| 1156 |
+
result.meta["explainer_used"] = True
|
| 1157 |
+
result.meta["bridge_ready"] = bool(getattr(explainer_result, "meta", {}).get("bridge_ready", False))
|
| 1158 |
+
result.meta["hint_style"] = getattr(explainer_result, "meta", {}).get("hint_style")
|
| 1159 |
+
result.meta["explainer_summary"] = explainer_summary
|
| 1160 |
+
result.meta["explainer_teaching_points"] = explainer_teaching_points
|
| 1161 |
+
result.meta["scaffold"] = explainer_scaffold
|
| 1162 |
+
|
| 1163 |
# 3. compose base reply
|
| 1164 |
reply = _compose_reply(
|
| 1165 |
result=result,
|
|
|
|
| 1289 |
result.internal_answer = None
|
| 1290 |
result.reply = reply
|
| 1291 |
result.help_mode = resolved_help_mode
|
| 1292 |
+
|
| 1293 |
+
final_meta = dict(result.meta or {})
|
| 1294 |
+
final_meta.update({
|
| 1295 |
"intent": resolved_intent,
|
| 1296 |
"question_text": question_text or "",
|
| 1297 |
"options_count": len(options_text or []),
|
| 1298 |
"category": inferred_category,
|
| 1299 |
"question_type": question_type,
|
| 1300 |
"classified_topic": question_topic,
|
| 1301 |
+
})
|
| 1302 |
+
result.meta = final_meta
|
| 1303 |
|
| 1304 |
return result
|