Update formatting.py
Browse files- formatting.py +165 -484
formatting.py
CHANGED
|
@@ -14,15 +14,6 @@ def style_prefix(tone: float) -> str:
|
|
| 14 |
return "You’ve got this — let’s solve it cleanly."
|
| 15 |
|
| 16 |
|
| 17 |
-
def _clean_lines(core: str) -> list[str]:
|
| 18 |
-
lines = []
|
| 19 |
-
for line in (core or "").splitlines():
|
| 20 |
-
cleaned = line.strip()
|
| 21 |
-
if cleaned:
|
| 22 |
-
lines.append(cleaned)
|
| 23 |
-
return lines
|
| 24 |
-
|
| 25 |
-
|
| 26 |
def _normalize_key(text: str) -> str:
|
| 27 |
text = (text or "").strip().lower()
|
| 28 |
text = text.replace("’", "'")
|
|
@@ -30,56 +21,13 @@ def _normalize_key(text: str) -> str:
|
|
| 30 |
return text
|
| 31 |
|
| 32 |
|
| 33 |
-
def
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
"you've got this — let's solve it cleanly.",
|
| 41 |
-
"youve got this — lets solve it cleanly.",
|
| 42 |
-
"you’ve got this — let’s solve it cleanly.",
|
| 43 |
-
"walkthrough",
|
| 44 |
-
"hint",
|
| 45 |
-
"steps",
|
| 46 |
-
"method",
|
| 47 |
-
"explanation",
|
| 48 |
-
"key idea",
|
| 49 |
-
"question breakdown",
|
| 50 |
-
"here’s what the question is really asking",
|
| 51 |
-
"here's what the question is really asking",
|
| 52 |
-
"let’s break down what the question is asking",
|
| 53 |
-
"lets break down what the question is asking",
|
| 54 |
-
"you’ve got this — here’s what the question is really asking",
|
| 55 |
-
"you've got this — here's what the question is really asking",
|
| 56 |
-
"what to identify first",
|
| 57 |
-
"what to identify",
|
| 58 |
-
"set-up path",
|
| 59 |
-
"setup path",
|
| 60 |
-
"first move",
|
| 61 |
-
"next hint",
|
| 62 |
-
"next steps",
|
| 63 |
-
"watch out for",
|
| 64 |
-
"variables to define",
|
| 65 |
-
"equations to form",
|
| 66 |
-
"key teaching points",
|
| 67 |
-
"how to build it",
|
| 68 |
-
"what the question gives you",
|
| 69 |
-
"constraints or conditions",
|
| 70 |
-
"key relationship",
|
| 71 |
-
"concepts you will probably need",
|
| 72 |
-
"best starting move",
|
| 73 |
-
"final answer",
|
| 74 |
-
}
|
| 75 |
-
return key in wrapper_lines
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
def _strip_leading_wrapper_lines(lines: list[str]) -> list[str]:
|
| 79 |
-
cleaned = list(lines)
|
| 80 |
-
while cleaned and _is_wrapper_line(cleaned[0]):
|
| 81 |
-
cleaned.pop(0)
|
| 82 |
-
return cleaned
|
| 83 |
|
| 84 |
|
| 85 |
def _dedupe_lines(lines: list[str]) -> list[str]:
|
|
@@ -93,95 +41,6 @@ def _dedupe_lines(lines: list[str]) -> list[str]:
|
|
| 93 |
return output
|
| 94 |
|
| 95 |
|
| 96 |
-
def _limit_lines_for_verbosity(lines: list[str], verbosity: float, help_mode: str) -> list[str]:
|
| 97 |
-
if not lines:
|
| 98 |
-
return lines
|
| 99 |
-
|
| 100 |
-
if help_mode == "hint":
|
| 101 |
-
return lines[:1]
|
| 102 |
-
|
| 103 |
-
if help_mode == "answer":
|
| 104 |
-
if verbosity < 0.2:
|
| 105 |
-
return lines[:2]
|
| 106 |
-
if verbosity < 0.45:
|
| 107 |
-
return lines[:3]
|
| 108 |
-
if verbosity < 0.75:
|
| 109 |
-
return lines[:4]
|
| 110 |
-
return lines[:5]
|
| 111 |
-
|
| 112 |
-
if verbosity < 0.2:
|
| 113 |
-
return lines[:1]
|
| 114 |
-
if verbosity < 0.45:
|
| 115 |
-
return lines[:2]
|
| 116 |
-
if verbosity < 0.75:
|
| 117 |
-
return lines[:3]
|
| 118 |
-
return lines
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
def _extract_topic_from_lines(lines: list[str]) -> str:
|
| 122 |
-
joined = " ".join(lines).lower()
|
| 123 |
-
|
| 124 |
-
if any(word in joined for word in ["equation", "variable", "isolate", "algebra"]):
|
| 125 |
-
return "algebra"
|
| 126 |
-
if any(word in joined for word in ["percent", "percentage"]):
|
| 127 |
-
return "percent"
|
| 128 |
-
if any(word in joined for word in ["ratio", "proportion"]):
|
| 129 |
-
return "ratio"
|
| 130 |
-
if any(word in joined for word in ["probability", "outcome"]):
|
| 131 |
-
return "probability"
|
| 132 |
-
if any(word in joined for word in ["mean", "median", "average"]):
|
| 133 |
-
return "statistics"
|
| 134 |
-
if any(word in joined for word in ["triangle", "circle", "angle", "area", "perimeter"]):
|
| 135 |
-
return "geometry"
|
| 136 |
-
if any(word in joined for word in ["integer", "factor", "multiple", "prime", "remainder"]):
|
| 137 |
-
return "number_theory"
|
| 138 |
-
|
| 139 |
-
return "general"
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
def _why_line(topic: str, lines: list[str]) -> str:
|
| 143 |
-
joined = " ".join(lines).lower()
|
| 144 |
-
|
| 145 |
-
if topic == "algebra":
|
| 146 |
-
if "equation" in joined or "isolate" in joined or "variable" in joined:
|
| 147 |
-
return "Why: this works because inverse operations undo what is attached to the variable while keeping the equation balanced."
|
| 148 |
-
return "Why: the goal is to isolate the variable without changing the balance of the relationship."
|
| 149 |
-
|
| 150 |
-
if topic == "percent":
|
| 151 |
-
return "Why: percent relationships depend on identifying the correct base value before calculating the change or part."
|
| 152 |
-
|
| 153 |
-
if topic == "ratio":
|
| 154 |
-
return "Why: ratios compare parts consistently, so the relationship must stay proportional."
|
| 155 |
-
|
| 156 |
-
if topic == "probability":
|
| 157 |
-
return "Why: probability compares favorable outcomes to the total number of possible outcomes."
|
| 158 |
-
|
| 159 |
-
if topic == "statistics":
|
| 160 |
-
return "Why: statistical measures describe the distribution, so the method depends on what feature the question asks for."
|
| 161 |
-
|
| 162 |
-
if topic == "geometry":
|
| 163 |
-
return "Why: geometry problems depend on the properties of the figure and the relationships between its parts."
|
| 164 |
-
|
| 165 |
-
if topic == "number_theory":
|
| 166 |
-
return "Why: number properties such as divisibility, factors, and remainders follow fixed rules that guide the method."
|
| 167 |
-
|
| 168 |
-
return "Why: focus on the structure of the problem before doing any calculations."
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
def _tone_adjust_line(line: str, tone: float) -> str:
|
| 172 |
-
line = (line or "").strip()
|
| 173 |
-
if not line:
|
| 174 |
-
return line
|
| 175 |
-
|
| 176 |
-
if tone < 0.25:
|
| 177 |
-
line = re.sub(r"^let’s\s+", "", line, flags=re.IGNORECASE)
|
| 178 |
-
line = re.sub(r"^let's\s+", "", line, flags=re.IGNORECASE)
|
| 179 |
-
line = re.sub(r"^here is the idea in context:\s*", "", line, flags=re.IGNORECASE)
|
| 180 |
-
return line.strip()
|
| 181 |
-
|
| 182 |
-
return line
|
| 183 |
-
|
| 184 |
-
|
| 185 |
def _coerce_string(value: Any) -> str:
|
| 186 |
return (value or "").strip() if isinstance(value, str) else ""
|
| 187 |
|
|
@@ -199,85 +58,59 @@ def _coerce_list(value: Any) -> List[str]:
|
|
| 199 |
return []
|
| 200 |
|
| 201 |
|
| 202 |
-
def
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
continue
|
| 215 |
-
seen.add(key)
|
| 216 |
-
cleaned.append(text)
|
| 217 |
-
|
| 218 |
-
return cleaned
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
def _append_section(lines: List[str], title: str, items: List[str], limit: int) -> None:
|
| 222 |
-
cleaned = _clean_section_items(items)
|
| 223 |
-
if not cleaned:
|
| 224 |
-
return
|
| 225 |
-
|
| 226 |
-
lines.append("")
|
| 227 |
-
lines.append(title)
|
| 228 |
-
for item in cleaned[:limit]:
|
| 229 |
-
lines.append(f"- {item}")
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
def _get_scaffold(result: Any):
|
| 233 |
-
return getattr(result, "scaffold", None)
|
| 234 |
|
| 235 |
|
| 236 |
-
def
|
| 237 |
-
topic =
|
| 238 |
-
if topic:
|
| 239 |
-
return topic
|
| 240 |
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
|
| 248 |
|
| 249 |
-
def
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
continue
|
| 269 |
-
cleaned.append(s)
|
| 270 |
-
return _dedupe_lines(cleaned)
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
def _explainer_header(tone: float) -> str:
|
| 274 |
-
if tone < 0.2:
|
| 275 |
-
return "Question breakdown:"
|
| 276 |
-
if tone < 0.45:
|
| 277 |
-
return "Here’s what the question is really asking:"
|
| 278 |
-
if tone < 0.75:
|
| 279 |
-
return "Let’s break down what the question is asking:"
|
| 280 |
-
return "You’ve got this — here’s what the question is really asking:"
|
| 281 |
|
| 282 |
|
| 283 |
def _format_answer_mode(
|
|
@@ -288,18 +121,14 @@ def _format_answer_mode(
|
|
| 288 |
transparency: float,
|
| 289 |
) -> str:
|
| 290 |
output: List[str] = []
|
| 291 |
-
|
| 292 |
prefix = style_prefix(tone)
|
| 293 |
if prefix:
|
| 294 |
output.append(prefix)
|
| 295 |
output.append("")
|
| 296 |
|
| 297 |
-
|
| 298 |
-
output.append("")
|
| 299 |
-
|
| 300 |
-
limited = _limit_lines_for_verbosity(lines, verbosity, "answer")
|
| 301 |
-
|
| 302 |
if limited:
|
|
|
|
| 303 |
if len(limited) >= 1:
|
| 304 |
output.append(f"- What to identify: {limited[0]}")
|
| 305 |
if len(limited) >= 2:
|
|
@@ -311,37 +140,37 @@ def _format_answer_mode(
|
|
| 311 |
|
| 312 |
if transparency >= 0.8:
|
| 313 |
output.append("")
|
| 314 |
-
output.append(_why_line(topic
|
| 315 |
|
| 316 |
return "\n".join(output).strip()
|
| 317 |
|
| 318 |
|
| 319 |
-
def format_reply(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
prefix = style_prefix(tone)
|
| 321 |
core = (core or "").strip()
|
| 322 |
|
| 323 |
if not core:
|
| 324 |
return prefix or "Start with the structure of the problem."
|
| 325 |
|
| 326 |
-
lines = _clean_lines(core)
|
| 327 |
-
lines = _strip_leading_wrapper_lines(lines)
|
| 328 |
-
lines = [_tone_adjust_line(line, tone) for line in lines]
|
| 329 |
-
lines = [line for line in lines if line]
|
| 330 |
-
lines = _dedupe_lines(lines)
|
| 331 |
-
|
| 332 |
if not lines:
|
| 333 |
return prefix or "Start with the structure of the problem."
|
| 334 |
|
| 335 |
-
|
| 336 |
|
| 337 |
if help_mode == "answer":
|
| 338 |
-
return _format_answer_mode(lines,
|
| 339 |
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
return prefix or "Start with the structure of the problem."
|
| 343 |
-
|
| 344 |
-
output: list[str] = []
|
| 345 |
|
| 346 |
if prefix:
|
| 347 |
output.append(prefix)
|
|
@@ -349,228 +178,171 @@ def format_reply(core: str, tone: float, verbosity: float, transparency: float,
|
|
| 349 |
|
| 350 |
if help_mode == "hint":
|
| 351 |
output.append("Hint:")
|
| 352 |
-
output.
|
| 353 |
-
|
| 354 |
if transparency >= 0.8:
|
| 355 |
output.append("")
|
| 356 |
-
output.append(_why_line(
|
| 357 |
-
|
| 358 |
return "\n".join(output).strip()
|
| 359 |
|
| 360 |
-
if help_mode
|
| 361 |
-
|
| 362 |
-
output.
|
| 363 |
-
|
|
|
|
| 364 |
if transparency >= 0.8:
|
| 365 |
output.append("")
|
| 366 |
-
output.append(_why_line(
|
| 367 |
-
|
| 368 |
return "\n".join(output).strip()
|
| 369 |
|
| 370 |
-
if help_mode in {"
|
| 371 |
label = {
|
| 372 |
-
"step_by_step": "Steps:",
|
| 373 |
"method": "Method:",
|
| 374 |
"explain": "Explanation:",
|
| 375 |
"concept": "Key idea:",
|
| 376 |
"definition": "Key idea:",
|
| 377 |
-
}
|
| 378 |
output.append(label)
|
| 379 |
-
|
| 380 |
-
|
| 381 |
if transparency >= 0.75:
|
| 382 |
output.append("")
|
| 383 |
-
output.append(_why_line(
|
| 384 |
-
|
| 385 |
return "\n".join(output).strip()
|
| 386 |
|
| 387 |
-
|
|
|
|
| 388 |
|
| 389 |
-
if transparency >= 0.85
|
| 390 |
output.append("")
|
| 391 |
-
output.append(_why_line(
|
| 392 |
|
| 393 |
return "\n".join(output).strip()
|
| 394 |
|
| 395 |
|
| 396 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
| 397 |
result: Any,
|
| 398 |
-
help_mode: str,
|
| 399 |
hint_stage: int,
|
| 400 |
verbosity: float,
|
| 401 |
transparency: float,
|
| 402 |
-
solver_steps: Optional[List[str]] = None,
|
| 403 |
-
can_reveal_answer: bool = False,
|
| 404 |
-
final_answer: Optional[str] = None,
|
| 405 |
) -> List[str]:
|
| 406 |
output: List[str] = []
|
| 407 |
scaffold = _get_scaffold(result)
|
| 408 |
if scaffold is None:
|
| 409 |
return output
|
| 410 |
|
| 411 |
-
|
|
|
|
| 412 |
concept = _coerce_string(getattr(scaffold, "concept", ""))
|
| 413 |
-
|
| 414 |
-
setup_actions = _coerce_list(getattr(scaffold, "setup_actions", []))
|
| 415 |
-
intermediate_steps = _coerce_list(getattr(scaffold, "intermediate_steps", []))
|
| 416 |
first_move = _coerce_string(getattr(scaffold, "first_move", ""))
|
| 417 |
next_hint = _coerce_string(getattr(scaffold, "next_hint", ""))
|
|
|
|
|
|
|
| 418 |
variables_to_define = _coerce_list(getattr(scaffold, "variables_to_define", []))
|
| 419 |
equations_to_form = _coerce_list(getattr(scaffold, "equations_to_form", []))
|
| 420 |
common_traps = _coerce_list(getattr(scaffold, "common_traps", []))
|
| 421 |
hint_ladder = _coerce_list(getattr(scaffold, "hint_ladder", []))
|
| 422 |
key_operations = _coerce_list(getattr(scaffold, "key_operations", []))
|
| 423 |
|
| 424 |
-
|
|
|
|
|
|
|
|
|
|
| 425 |
|
| 426 |
-
if
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
stage = 3
|
| 430 |
-
elif help_mode == "answer" and can_reveal_answer:
|
| 431 |
-
stage = 3
|
| 432 |
-
elif help_mode == "answer":
|
| 433 |
-
stage = max(2, hint_stage)
|
| 434 |
-
else:
|
| 435 |
-
stage = min(max(hint_stage, 0), 3)
|
| 436 |
|
| 437 |
if stage == 0:
|
| 438 |
-
if concept:
|
| 439 |
-
output.append(concept)
|
| 440 |
-
if ask:
|
| 441 |
-
output.append("")
|
| 442 |
-
output.append("What to identify:")
|
| 443 |
-
output.append(f"- {ask}")
|
| 444 |
if first_move:
|
| 445 |
output.append("")
|
| 446 |
output.append("First move:")
|
| 447 |
output.append(f"- {first_move}")
|
| 448 |
-
elif
|
| 449 |
output.append("")
|
| 450 |
output.append("First move:")
|
| 451 |
-
output.append(f"- {
|
| 452 |
return output
|
| 453 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 454 |
if stage == 1:
|
| 455 |
-
if ask:
|
| 456 |
-
output.append("What to identify:")
|
| 457 |
-
output.append(f"- {ask}")
|
| 458 |
-
if setup_actions:
|
| 459 |
-
output.append("")
|
| 460 |
-
output.append("Set-up path:")
|
| 461 |
-
for item in setup_actions[:2]:
|
| 462 |
-
output.append(f"- {item}")
|
| 463 |
-
if first_move:
|
| 464 |
-
output.append("")
|
| 465 |
-
output.append("First move:")
|
| 466 |
-
output.append(f"- {first_move}")
|
| 467 |
if next_hint:
|
| 468 |
output.append("")
|
| 469 |
output.append("Next hint:")
|
| 470 |
output.append(f"- {next_hint}")
|
| 471 |
-
elif
|
| 472 |
output.append("")
|
| 473 |
output.append("Next hint:")
|
| 474 |
-
output.append(f"- {
|
| 475 |
return output
|
| 476 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 477 |
if stage == 2:
|
| 478 |
-
if
|
| 479 |
-
output.append("What to identify:")
|
| 480 |
-
output.append(f"- {ask}")
|
| 481 |
-
if target:
|
| 482 |
-
output.append("")
|
| 483 |
-
output.append(f"Target: {target}")
|
| 484 |
-
if setup_actions:
|
| 485 |
-
output.append("")
|
| 486 |
-
output.append("Set-up path:")
|
| 487 |
-
for item in setup_actions[: min(3, len(setup_actions))]:
|
| 488 |
-
output.append(f"- {item}")
|
| 489 |
-
progression_steps = intermediate_steps[:2] or safe_steps[:2]
|
| 490 |
-
if progression_steps:
|
| 491 |
-
output.append("")
|
| 492 |
-
output.append("Next steps:")
|
| 493 |
-
for item in progression_steps:
|
| 494 |
-
output.append(f"- {item}")
|
| 495 |
-
if next_hint:
|
| 496 |
-
output.append("")
|
| 497 |
-
output.append("Next hint:")
|
| 498 |
-
output.append(f"- {next_hint}")
|
| 499 |
-
if variables_to_define and transparency >= 0.45:
|
| 500 |
output.append("")
|
| 501 |
output.append("Variables to define:")
|
| 502 |
for item in variables_to_define[:2]:
|
| 503 |
output.append(f"- {item}")
|
| 504 |
-
if equations_to_form
|
| 505 |
output.append("")
|
| 506 |
output.append("Equations to form:")
|
| 507 |
for item in equations_to_form[:2]:
|
| 508 |
output.append(f"- {item}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 509 |
return output
|
| 510 |
|
| 511 |
-
if
|
| 512 |
-
output.append("What to identify first:")
|
| 513 |
-
output.append(f"- {ask}")
|
| 514 |
-
|
| 515 |
-
if setup_actions:
|
| 516 |
-
output.append("")
|
| 517 |
-
output.append("Set-up path:")
|
| 518 |
-
limit = 2 if verbosity < 0.4 else 3 if verbosity < 0.75 else 5
|
| 519 |
-
for item in setup_actions[:limit]:
|
| 520 |
-
output.append(f"- {item}")
|
| 521 |
-
|
| 522 |
-
deep_steps = intermediate_steps or safe_steps
|
| 523 |
-
if deep_steps:
|
| 524 |
-
output.append("")
|
| 525 |
-
output.append("How to build it:")
|
| 526 |
-
limit = 2 if verbosity < 0.75 else 4
|
| 527 |
-
for item in deep_steps[:limit]:
|
| 528 |
-
output.append(f"- {item}")
|
| 529 |
-
|
| 530 |
-
if first_move:
|
| 531 |
-
output.append("")
|
| 532 |
-
output.append("First move:")
|
| 533 |
-
output.append(f"- {first_move}")
|
| 534 |
-
|
| 535 |
-
if next_hint and (transparency >= 0.35 or verbosity >= 0.45):
|
| 536 |
-
output.append("")
|
| 537 |
-
output.append("Next hint:")
|
| 538 |
-
output.append(f"- {next_hint}")
|
| 539 |
-
|
| 540 |
-
if hint_ladder and verbosity >= 0.65:
|
| 541 |
-
output.append("")
|
| 542 |
-
output.append("Hint ladder:")
|
| 543 |
-
for item in hint_ladder[:3]:
|
| 544 |
-
output.append(f"- {item}")
|
| 545 |
-
|
| 546 |
-
if variables_to_define and verbosity >= 0.65:
|
| 547 |
output.append("")
|
| 548 |
output.append("Variables to define:")
|
| 549 |
for item in variables_to_define[:3]:
|
| 550 |
output.append(f"- {item}")
|
| 551 |
|
| 552 |
-
if equations_to_form
|
| 553 |
output.append("")
|
| 554 |
output.append("Equations to form:")
|
| 555 |
for item in equations_to_form[:3]:
|
| 556 |
output.append(f"- {item}")
|
| 557 |
|
| 558 |
-
if key_operations
|
| 559 |
output.append("")
|
| 560 |
output.append("Key operations:")
|
| 561 |
for item in key_operations[:3]:
|
| 562 |
output.append(f"- {item}")
|
| 563 |
|
| 564 |
-
if
|
| 565 |
output.append("")
|
| 566 |
output.append("Watch out for:")
|
| 567 |
for item in common_traps[:4]:
|
| 568 |
output.append(f"- {item}")
|
| 569 |
|
| 570 |
-
if help_mode == "answer" and can_reveal_answer and final_answer:
|
| 571 |
-
output.append("")
|
| 572 |
-
output.append(f"Final answer: {final_answer}")
|
| 573 |
-
|
| 574 |
return output
|
| 575 |
|
| 576 |
|
|
@@ -579,140 +351,49 @@ def format_explainer_response(
|
|
| 579 |
tone: float,
|
| 580 |
verbosity: float,
|
| 581 |
transparency: float,
|
| 582 |
-
help_mode: str = "explain",
|
| 583 |
hint_stage: int = 0,
|
| 584 |
-
solver_steps: Optional[List[str]] = None,
|
| 585 |
-
can_reveal_answer: bool = False,
|
| 586 |
-
final_answer: Optional[str] = None,
|
| 587 |
) -> str:
|
| 588 |
if not result or not getattr(result, "understood", False):
|
| 589 |
return "I can help explain what the question is asking, but I need the full wording of the question."
|
| 590 |
|
| 591 |
output: List[str] = []
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
output.append(header)
|
| 596 |
output.append("")
|
| 597 |
|
| 598 |
-
|
| 599 |
-
|
| 600 |
|
| 601 |
-
|
|
|
|
| 602 |
output.append(summary)
|
| 603 |
|
| 604 |
-
|
| 605 |
result=result,
|
| 606 |
-
help_mode=help_mode,
|
| 607 |
hint_stage=hint_stage,
|
| 608 |
verbosity=verbosity,
|
| 609 |
transparency=transparency,
|
| 610 |
-
solver_steps=solver_steps,
|
| 611 |
-
can_reveal_answer=can_reveal_answer,
|
| 612 |
-
final_answer=final_answer,
|
| 613 |
)
|
| 614 |
-
if
|
| 615 |
-
|
|
|
|
|
|
|
| 616 |
|
| 617 |
-
|
|
|
|
| 618 |
output.append("")
|
| 619 |
output.append("Key teaching points:")
|
| 620 |
-
|
| 621 |
-
for item in teaching_points[:limit]:
|
| 622 |
output.append(f"- {item}")
|
| 623 |
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
asks_for = _coerce_string(getattr(result, "asks_for", ""))
|
| 629 |
-
if asks_for and transparency >= 0.35 and hint_stage >= 2:
|
| 630 |
-
output.append("")
|
| 631 |
-
output.append(f"The question is asking for: {asks_for}")
|
| 632 |
-
|
| 633 |
-
if verbosity >= 0.45 and hint_stage >= 2:
|
| 634 |
-
_append_section(
|
| 635 |
-
output,
|
| 636 |
-
"What the question gives you:",
|
| 637 |
-
getattr(result, "givens", []) or [],
|
| 638 |
-
5 if verbosity >= 0.7 else 3,
|
| 639 |
-
)
|
| 640 |
-
|
| 641 |
-
if verbosity >= 0.7 and hint_stage >= 3:
|
| 642 |
-
_append_section(
|
| 643 |
-
output,
|
| 644 |
-
"Constraints or conditions:",
|
| 645 |
-
getattr(result, "constraints", []) or [],
|
| 646 |
-
5,
|
| 647 |
-
)
|
| 648 |
-
|
| 649 |
-
if transparency >= 0.5 and hint_stage >= 2:
|
| 650 |
-
_append_section(
|
| 651 |
-
output,
|
| 652 |
-
"Key relationship:",
|
| 653 |
-
getattr(result, "relationships", []) or [],
|
| 654 |
-
4,
|
| 655 |
-
)
|
| 656 |
-
|
| 657 |
-
if transparency >= 0.55 and hint_stage >= 2:
|
| 658 |
-
_append_section(
|
| 659 |
-
output,
|
| 660 |
-
"Concepts you will probably need:",
|
| 661 |
-
getattr(result, "needed_concepts", []) or [],
|
| 662 |
-
4,
|
| 663 |
-
)
|
| 664 |
-
|
| 665 |
-
if (transparency >= 0.6 or verbosity >= 0.75) and hint_stage >= 3:
|
| 666 |
-
_append_section(
|
| 667 |
-
output,
|
| 668 |
-
"Watch out for:",
|
| 669 |
-
getattr(result, "trap_notes", []) or [],
|
| 670 |
-
4,
|
| 671 |
-
)
|
| 672 |
-
|
| 673 |
-
strategy_hint = _coerce_string(getattr(result, "strategy_hint", ""))
|
| 674 |
-
if strategy_hint and verbosity >= 0.35 and not _is_wrapper_line(strategy_hint) and hint_stage >= 1:
|
| 675 |
-
output.append("")
|
| 676 |
-
output.append(f"Best starting move: {strategy_hint}")
|
| 677 |
|
| 678 |
if transparency >= 0.8:
|
| 679 |
-
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
if summary:
|
| 683 |
-
why_seed_lines.append(summary)
|
| 684 |
-
|
| 685 |
-
scaffold = _get_scaffold(result)
|
| 686 |
-
if scaffold is not None:
|
| 687 |
-
ask = _coerce_string(getattr(scaffold, "ask", ""))
|
| 688 |
-
first_move = _coerce_string(getattr(scaffold, "first_move", ""))
|
| 689 |
-
if ask:
|
| 690 |
-
why_seed_lines.append(ask)
|
| 691 |
-
if first_move:
|
| 692 |
-
why_seed_lines.append(first_move)
|
| 693 |
-
|
| 694 |
-
if not why_seed_lines:
|
| 695 |
-
why_seed_lines.extend(teaching_points[:2])
|
| 696 |
-
|
| 697 |
-
if why_seed_lines:
|
| 698 |
-
output.append("")
|
| 699 |
-
output.append(_why_line(topic, why_seed_lines))
|
| 700 |
-
|
| 701 |
-
final_lines = []
|
| 702 |
-
previous_key = None
|
| 703 |
-
for line in output:
|
| 704 |
-
if line is None:
|
| 705 |
-
continue
|
| 706 |
-
text = str(line).rstrip()
|
| 707 |
-
key = _normalize_key(text)
|
| 708 |
-
if key == previous_key and key:
|
| 709 |
-
continue
|
| 710 |
-
final_lines.append(text)
|
| 711 |
-
previous_key = key
|
| 712 |
-
|
| 713 |
-
text = "\n".join(final_lines).strip()
|
| 714 |
-
|
| 715 |
-
if not text:
|
| 716 |
-
return "I can help explain what the question is asking, but I need the full wording of the question."
|
| 717 |
|
| 718 |
-
return
|
|
|
|
| 14 |
return "You’ve got this — let’s solve it cleanly."
|
| 15 |
|
| 16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
def _normalize_key(text: str) -> str:
|
| 18 |
text = (text or "").strip().lower()
|
| 19 |
text = text.replace("’", "'")
|
|
|
|
| 21 |
return text
|
| 22 |
|
| 23 |
|
| 24 |
+
def _clean_lines(core: str) -> list[str]:
|
| 25 |
+
lines = []
|
| 26 |
+
for line in (core or "").splitlines():
|
| 27 |
+
cleaned = line.strip()
|
| 28 |
+
if cleaned:
|
| 29 |
+
lines.append(cleaned)
|
| 30 |
+
return lines
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
|
| 33 |
def _dedupe_lines(lines: list[str]) -> list[str]:
|
|
|
|
| 41 |
return output
|
| 42 |
|
| 43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
def _coerce_string(value: Any) -> str:
|
| 45 |
return (value or "").strip() if isinstance(value, str) else ""
|
| 46 |
|
|
|
|
| 58 |
return []
|
| 59 |
|
| 60 |
|
| 61 |
+
def _limit_steps(steps: List[str], verbosity: float, minimum: int = 1) -> List[str]:
|
| 62 |
+
if not steps:
|
| 63 |
+
return []
|
| 64 |
+
if verbosity < 0.25:
|
| 65 |
+
limit = minimum
|
| 66 |
+
elif verbosity < 0.5:
|
| 67 |
+
limit = max(minimum, 2)
|
| 68 |
+
elif verbosity < 0.75:
|
| 69 |
+
limit = max(minimum, 3)
|
| 70 |
+
else:
|
| 71 |
+
limit = max(minimum, 5)
|
| 72 |
+
return steps[:limit]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
|
| 75 |
+
def _why_line(topic: str) -> str:
|
| 76 |
+
topic = (topic or "").lower()
|
|
|
|
|
|
|
| 77 |
|
| 78 |
+
if topic == "algebra":
|
| 79 |
+
return "Why: algebra works by keeping the relationship balanced while undoing the operations attached to the variable."
|
| 80 |
+
if topic == "percent":
|
| 81 |
+
return "Why: percent questions depend on choosing the correct base before doing any calculation."
|
| 82 |
+
if topic == "ratio":
|
| 83 |
+
return "Why: ratio questions depend on preserving the comparison and using one shared scale factor."
|
| 84 |
+
if topic == "probability":
|
| 85 |
+
return "Why: probability compares successful outcomes to all possible outcomes."
|
| 86 |
+
if topic == "statistics":
|
| 87 |
+
return "Why: the right method depends on which summary measure the question actually asks for."
|
| 88 |
+
if topic == "geometry":
|
| 89 |
+
return "Why: geometry depends on the relationships between the parts of the figure."
|
| 90 |
+
if topic == "number_theory":
|
| 91 |
+
return "Why: number properties follow fixed rules about divisibility, factors, and remainders."
|
| 92 |
+
return "Why: start with the structure of the problem before calculating."
|
| 93 |
|
| 94 |
|
| 95 |
+
def _extract_topic_from_text(text: str, fallback: Optional[str] = None) -> str:
|
| 96 |
+
low = (text or "").lower()
|
| 97 |
+
if fallback:
|
| 98 |
+
return fallback
|
| 99 |
+
if any(word in low for word in ["equation", "variable", "isolate", "algebra"]):
|
| 100 |
+
return "algebra"
|
| 101 |
+
if any(word in low for word in ["percent", "percentage", "%"]):
|
| 102 |
+
return "percent"
|
| 103 |
+
if any(word in low for word in ["ratio", "proportion"]):
|
| 104 |
+
return "ratio"
|
| 105 |
+
if any(word in low for word in ["probability", "outcome", "chance", "odds"]):
|
| 106 |
+
return "probability"
|
| 107 |
+
if any(word in low for word in ["mean", "median", "average"]):
|
| 108 |
+
return "statistics"
|
| 109 |
+
if any(word in low for word in ["triangle", "circle", "angle", "area", "perimeter"]):
|
| 110 |
+
return "geometry"
|
| 111 |
+
if any(word in low for word in ["integer", "factor", "multiple", "prime", "remainder"]):
|
| 112 |
+
return "number_theory"
|
| 113 |
+
return "general"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
|
| 115 |
|
| 116 |
def _format_answer_mode(
|
|
|
|
| 121 |
transparency: float,
|
| 122 |
) -> str:
|
| 123 |
output: List[str] = []
|
|
|
|
| 124 |
prefix = style_prefix(tone)
|
| 125 |
if prefix:
|
| 126 |
output.append(prefix)
|
| 127 |
output.append("")
|
| 128 |
|
| 129 |
+
limited = _limit_steps(lines, verbosity, minimum=2)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
if limited:
|
| 131 |
+
output.append("Answer path:")
|
| 132 |
if len(limited) >= 1:
|
| 133 |
output.append(f"- What to identify: {limited[0]}")
|
| 134 |
if len(limited) >= 2:
|
|
|
|
| 140 |
|
| 141 |
if transparency >= 0.8:
|
| 142 |
output.append("")
|
| 143 |
+
output.append(_why_line(topic))
|
| 144 |
|
| 145 |
return "\n".join(output).strip()
|
| 146 |
|
| 147 |
|
| 148 |
+
def format_reply(
|
| 149 |
+
core: str,
|
| 150 |
+
tone: float,
|
| 151 |
+
verbosity: float,
|
| 152 |
+
transparency: float,
|
| 153 |
+
help_mode: str,
|
| 154 |
+
hint_stage: int = 0,
|
| 155 |
+
topic: Optional[str] = None,
|
| 156 |
+
) -> str:
|
| 157 |
prefix = style_prefix(tone)
|
| 158 |
core = (core or "").strip()
|
| 159 |
|
| 160 |
if not core:
|
| 161 |
return prefix or "Start with the structure of the problem."
|
| 162 |
|
| 163 |
+
lines = _dedupe_lines(_clean_lines(core))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
if not lines:
|
| 165 |
return prefix or "Start with the structure of the problem."
|
| 166 |
|
| 167 |
+
resolved_topic = _extract_topic_from_text(core, topic)
|
| 168 |
|
| 169 |
if help_mode == "answer":
|
| 170 |
+
return _format_answer_mode(lines, resolved_topic, tone, verbosity, transparency)
|
| 171 |
|
| 172 |
+
shown = _limit_steps(lines, verbosity, minimum=1)
|
| 173 |
+
output: List[str] = []
|
|
|
|
|
|
|
|
|
|
| 174 |
|
| 175 |
if prefix:
|
| 176 |
output.append(prefix)
|
|
|
|
| 178 |
|
| 179 |
if help_mode == "hint":
|
| 180 |
output.append("Hint:")
|
| 181 |
+
output.append(f"- {shown[0]}")
|
|
|
|
| 182 |
if transparency >= 0.8:
|
| 183 |
output.append("")
|
| 184 |
+
output.append(_why_line(resolved_topic))
|
|
|
|
| 185 |
return "\n".join(output).strip()
|
| 186 |
|
| 187 |
+
if help_mode in {"instruction", "step_by_step", "walkthrough"}:
|
| 188 |
+
label = "First step:" if help_mode == "instruction" else "Walkthrough:"
|
| 189 |
+
output.append(label)
|
| 190 |
+
for line in shown:
|
| 191 |
+
output.append(f"- {line}")
|
| 192 |
if transparency >= 0.8:
|
| 193 |
output.append("")
|
| 194 |
+
output.append(_why_line(resolved_topic))
|
|
|
|
| 195 |
return "\n".join(output).strip()
|
| 196 |
|
| 197 |
+
if help_mode in {"method", "explain", "concept", "definition"}:
|
| 198 |
label = {
|
|
|
|
| 199 |
"method": "Method:",
|
| 200 |
"explain": "Explanation:",
|
| 201 |
"concept": "Key idea:",
|
| 202 |
"definition": "Key idea:",
|
| 203 |
+
}[help_mode]
|
| 204 |
output.append(label)
|
| 205 |
+
for line in shown:
|
| 206 |
+
output.append(f"- {line}")
|
| 207 |
if transparency >= 0.75:
|
| 208 |
output.append("")
|
| 209 |
+
output.append(_why_line(resolved_topic))
|
|
|
|
| 210 |
return "\n".join(output).strip()
|
| 211 |
|
| 212 |
+
for line in shown:
|
| 213 |
+
output.append(f"- {line}")
|
| 214 |
|
| 215 |
+
if transparency >= 0.85:
|
| 216 |
output.append("")
|
| 217 |
+
output.append(_why_line(resolved_topic))
|
| 218 |
|
| 219 |
return "\n".join(output).strip()
|
| 220 |
|
| 221 |
|
| 222 |
+
def _get_scaffold(result: Any):
|
| 223 |
+
return getattr(result, "scaffold", None)
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def _staged_scaffold_lines(
|
| 227 |
result: Any,
|
|
|
|
| 228 |
hint_stage: int,
|
| 229 |
verbosity: float,
|
| 230 |
transparency: float,
|
|
|
|
|
|
|
|
|
|
| 231 |
) -> List[str]:
|
| 232 |
output: List[str] = []
|
| 233 |
scaffold = _get_scaffold(result)
|
| 234 |
if scaffold is None:
|
| 235 |
return output
|
| 236 |
|
| 237 |
+
stage = max(0, min(int(hint_stage), 3))
|
| 238 |
+
|
| 239 |
concept = _coerce_string(getattr(scaffold, "concept", ""))
|
| 240 |
+
ask = _coerce_string(getattr(scaffold, "ask", ""))
|
|
|
|
|
|
|
| 241 |
first_move = _coerce_string(getattr(scaffold, "first_move", ""))
|
| 242 |
next_hint = _coerce_string(getattr(scaffold, "next_hint", ""))
|
| 243 |
+
setup_actions = _coerce_list(getattr(scaffold, "setup_actions", []))
|
| 244 |
+
intermediate_steps = _coerce_list(getattr(scaffold, "intermediate_steps", []))
|
| 245 |
variables_to_define = _coerce_list(getattr(scaffold, "variables_to_define", []))
|
| 246 |
equations_to_form = _coerce_list(getattr(scaffold, "equations_to_form", []))
|
| 247 |
common_traps = _coerce_list(getattr(scaffold, "common_traps", []))
|
| 248 |
hint_ladder = _coerce_list(getattr(scaffold, "hint_ladder", []))
|
| 249 |
key_operations = _coerce_list(getattr(scaffold, "key_operations", []))
|
| 250 |
|
| 251 |
+
if concept and stage == 0 and transparency >= 0.75:
|
| 252 |
+
output.append("Core idea:")
|
| 253 |
+
output.append(f"- {concept}")
|
| 254 |
+
output.append("")
|
| 255 |
|
| 256 |
+
if ask:
|
| 257 |
+
output.append("What to identify first:")
|
| 258 |
+
output.append(f"- {ask}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
|
| 260 |
if stage == 0:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
if first_move:
|
| 262 |
output.append("")
|
| 263 |
output.append("First move:")
|
| 264 |
output.append(f"- {first_move}")
|
| 265 |
+
elif hint_ladder:
|
| 266 |
output.append("")
|
| 267 |
output.append("First move:")
|
| 268 |
+
output.append(f"- {hint_ladder[0]}")
|
| 269 |
return output
|
| 270 |
|
| 271 |
+
if setup_actions:
|
| 272 |
+
output.append("")
|
| 273 |
+
output.append("Set-up path:")
|
| 274 |
+
for item in _limit_steps(setup_actions, verbosity, minimum=2 if stage >= 1 else 1):
|
| 275 |
+
output.append(f"- {item}")
|
| 276 |
+
|
| 277 |
+
if first_move:
|
| 278 |
+
output.append("")
|
| 279 |
+
output.append("First move:")
|
| 280 |
+
output.append(f"- {first_move}")
|
| 281 |
+
|
| 282 |
if stage == 1:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
if next_hint:
|
| 284 |
output.append("")
|
| 285 |
output.append("Next hint:")
|
| 286 |
output.append(f"- {next_hint}")
|
| 287 |
+
elif len(hint_ladder) >= 2:
|
| 288 |
output.append("")
|
| 289 |
output.append("Next hint:")
|
| 290 |
+
output.append(f"- {hint_ladder[1]}")
|
| 291 |
return output
|
| 292 |
|
| 293 |
+
if intermediate_steps:
|
| 294 |
+
output.append("")
|
| 295 |
+
output.append("How to build it:")
|
| 296 |
+
for item in _limit_steps(intermediate_steps, verbosity, minimum=2):
|
| 297 |
+
output.append(f"- {item}")
|
| 298 |
+
|
| 299 |
+
if next_hint:
|
| 300 |
+
output.append("")
|
| 301 |
+
output.append("Next hint:")
|
| 302 |
+
output.append(f"- {next_hint}")
|
| 303 |
+
|
| 304 |
if stage == 2:
|
| 305 |
+
if variables_to_define:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
output.append("")
|
| 307 |
output.append("Variables to define:")
|
| 308 |
for item in variables_to_define[:2]:
|
| 309 |
output.append(f"- {item}")
|
| 310 |
+
if equations_to_form:
|
| 311 |
output.append("")
|
| 312 |
output.append("Equations to form:")
|
| 313 |
for item in equations_to_form[:2]:
|
| 314 |
output.append(f"- {item}")
|
| 315 |
+
if key_operations:
|
| 316 |
+
output.append("")
|
| 317 |
+
output.append("Key operations:")
|
| 318 |
+
for item in key_operations[:3]:
|
| 319 |
+
output.append(f"- {item}")
|
| 320 |
return output
|
| 321 |
|
| 322 |
+
if variables_to_define:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 323 |
output.append("")
|
| 324 |
output.append("Variables to define:")
|
| 325 |
for item in variables_to_define[:3]:
|
| 326 |
output.append(f"- {item}")
|
| 327 |
|
| 328 |
+
if equations_to_form:
|
| 329 |
output.append("")
|
| 330 |
output.append("Equations to form:")
|
| 331 |
for item in equations_to_form[:3]:
|
| 332 |
output.append(f"- {item}")
|
| 333 |
|
| 334 |
+
if key_operations:
|
| 335 |
output.append("")
|
| 336 |
output.append("Key operations:")
|
| 337 |
for item in key_operations[:3]:
|
| 338 |
output.append(f"- {item}")
|
| 339 |
|
| 340 |
+
if common_traps:
|
| 341 |
output.append("")
|
| 342 |
output.append("Watch out for:")
|
| 343 |
for item in common_traps[:4]:
|
| 344 |
output.append(f"- {item}")
|
| 345 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 346 |
return output
|
| 347 |
|
| 348 |
|
|
|
|
| 351 |
tone: float,
|
| 352 |
verbosity: float,
|
| 353 |
transparency: float,
|
|
|
|
| 354 |
hint_stage: int = 0,
|
|
|
|
|
|
|
|
|
|
| 355 |
) -> str:
|
| 356 |
if not result or not getattr(result, "understood", False):
|
| 357 |
return "I can help explain what the question is asking, but I need the full wording of the question."
|
| 358 |
|
| 359 |
output: List[str] = []
|
| 360 |
+
prefix = style_prefix(tone)
|
| 361 |
+
if prefix:
|
| 362 |
+
output.append(prefix)
|
|
|
|
| 363 |
output.append("")
|
| 364 |
|
| 365 |
+
output.append("Question breakdown:")
|
| 366 |
+
output.append("")
|
| 367 |
|
| 368 |
+
summary = _coerce_string(getattr(result, "summary", ""))
|
| 369 |
+
if summary:
|
| 370 |
output.append(summary)
|
| 371 |
|
| 372 |
+
scaffold_lines = _staged_scaffold_lines(
|
| 373 |
result=result,
|
|
|
|
| 374 |
hint_stage=hint_stage,
|
| 375 |
verbosity=verbosity,
|
| 376 |
transparency=transparency,
|
|
|
|
|
|
|
|
|
|
| 377 |
)
|
| 378 |
+
if scaffold_lines:
|
| 379 |
+
if summary:
|
| 380 |
+
output.append("")
|
| 381 |
+
output.extend(scaffold_lines)
|
| 382 |
|
| 383 |
+
teaching_points = _coerce_list(getattr(result, "teaching_points", []))
|
| 384 |
+
if teaching_points and (verbosity >= 0.55 or hint_stage >= 2):
|
| 385 |
output.append("")
|
| 386 |
output.append("Key teaching points:")
|
| 387 |
+
for item in _limit_steps(teaching_points, verbosity, minimum=2):
|
|
|
|
| 388 |
output.append(f"- {item}")
|
| 389 |
|
| 390 |
+
topic = _extract_topic_from_text(
|
| 391 |
+
f"{summary} {' '.join(teaching_points)}",
|
| 392 |
+
getattr(result, "topic", None),
|
| 393 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 394 |
|
| 395 |
if transparency >= 0.8:
|
| 396 |
+
output.append("")
|
| 397 |
+
output.append(_why_line(topic))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 398 |
|
| 399 |
+
return "\n".join(_dedupe_lines(output)).strip()
|