Update conversation_logic.py
Browse files- conversation_logic.py +170 -64
conversation_logic.py
CHANGED
|
@@ -63,20 +63,76 @@ def _extract_text_from_history_item(item: Dict[str, Any]) -> str:
|
|
| 63 |
if not isinstance(item, dict):
|
| 64 |
return ""
|
| 65 |
|
| 66 |
-
|
|
|
|
| 67 |
value = item.get(key)
|
| 68 |
if isinstance(value, str) and value.strip():
|
| 69 |
return value.strip()
|
| 70 |
|
| 71 |
meta = item.get("meta")
|
| 72 |
if isinstance(meta, dict):
|
| 73 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
if isinstance(value, str) and value.strip():
|
| 75 |
return value.strip()
|
| 76 |
|
| 77 |
return ""
|
| 78 |
|
| 79 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
def _is_followup_hint_only(text: str) -> bool:
|
| 81 |
low = (text or "").strip().lower()
|
| 82 |
return low in {
|
|
@@ -90,6 +146,13 @@ def _is_followup_hint_only(text: str) -> bool:
|
|
| 90 |
"walk me through it",
|
| 91 |
"step by step",
|
| 92 |
"walkthrough",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
}
|
| 94 |
|
| 95 |
|
|
@@ -144,6 +207,33 @@ def _extract_question_text(raw_user_text: str) -> str:
|
|
| 144 |
return _strip_control_prefix(text)
|
| 145 |
|
| 146 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
def _classify_input_type(raw_user_text: str) -> str:
|
| 148 |
text = _clean_text(raw_user_text)
|
| 149 |
if not text:
|
|
@@ -238,38 +328,51 @@ def _history_hint_stage(chat_history: Optional[List[Dict[str, Any]]]) -> int:
|
|
| 238 |
except Exception:
|
| 239 |
pass
|
| 240 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
return min(best, 3)
|
| 242 |
|
| 243 |
|
| 244 |
-
def
|
| 245 |
raw_user_text: str,
|
| 246 |
question_text: Optional[str],
|
| 247 |
chat_history: Optional[List[Dict[str, Any]]],
|
|
|
|
| 248 |
) -> str:
|
| 249 |
explicit = _sanitize_question_text(question_text or "")
|
| 250 |
if explicit:
|
| 251 |
return explicit
|
| 252 |
|
| 253 |
-
|
| 254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
|
| 256 |
if not chat_history:
|
| 257 |
return ""
|
| 258 |
|
| 259 |
for item in reversed(chat_history):
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
|
| 274 |
return ""
|
| 275 |
|
|
@@ -285,13 +388,21 @@ def _choose_effective_question_text(
|
|
| 285 |
stored_question = _sanitize_question_text(state.get("question_text", ""))
|
| 286 |
|
| 287 |
if _is_followup_input(input_type):
|
| 288 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 289 |
return stored_question, True
|
| 290 |
|
| 291 |
-
recovered =
|
| 292 |
raw_user_text=raw_user_text,
|
| 293 |
question_text=question_text,
|
| 294 |
chat_history=chat_history,
|
|
|
|
| 295 |
)
|
| 296 |
if recovered:
|
| 297 |
return recovered, True
|
|
@@ -411,6 +522,7 @@ def _safe_steps(steps: List[str]) -> List[str]:
|
|
| 411 |
r"\bthat gives\b",
|
| 412 |
r"\bthis gives\b",
|
| 413 |
r"\btherefore\b",
|
|
|
|
| 414 |
r"\bso x\s*=",
|
| 415 |
r"\bso y\s*=",
|
| 416 |
r"\bx\s*=",
|
|
@@ -422,12 +534,20 @@ def _safe_steps(steps: List[str]) -> List[str]:
|
|
| 422 |
for step in steps:
|
| 423 |
s = _strip_bullet_prefix(step)
|
| 424 |
lowered = s.lower()
|
| 425 |
-
if any(re.search(
|
| 426 |
continue
|
| 427 |
if s:
|
| 428 |
cleaned.append(s)
|
| 429 |
|
| 430 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
|
| 432 |
|
| 433 |
def _safe_meta_list(items: Any) -> List[str]:
|
|
@@ -496,6 +616,17 @@ def _get_result_steps(result: SolverResult) -> List[str]:
|
|
| 496 |
return []
|
| 497 |
|
| 498 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 499 |
def _solver_has_useful_steps(result: Optional[SolverResult]) -> bool:
|
| 500 |
if result is None:
|
| 501 |
return False
|
|
@@ -533,7 +664,7 @@ def _walkthrough_from_steps(steps: List[str], verbosity: float) -> str:
|
|
| 533 |
safe_steps[:4] if verbosity < 0.85 else
|
| 534 |
safe_steps
|
| 535 |
)
|
| 536 |
-
return "\n".join(f"- {
|
| 537 |
|
| 538 |
|
| 539 |
def _answer_path_from_steps(steps: List[str], verbosity: float) -> str:
|
|
@@ -546,7 +677,7 @@ def _answer_path_from_steps(steps: List[str], verbosity: float) -> str:
|
|
| 546 |
safe_steps[:3] if verbosity < 0.8 else
|
| 547 |
safe_steps
|
| 548 |
)
|
| 549 |
-
return "\n".join(f"- {
|
| 550 |
|
| 551 |
|
| 552 |
def _compose_reply(
|
|
@@ -567,13 +698,13 @@ def _compose_reply(
|
|
| 567 |
|
| 568 |
if intent == "instruction":
|
| 569 |
if steps:
|
| 570 |
-
return f"
|
| 571 |
-
return "First, identify the key relationship or comparison in the question."
|
| 572 |
|
| 573 |
if intent == "definition":
|
| 574 |
if steps:
|
| 575 |
-
return f"
|
| 576 |
-
return "This is asking for the meaning of the term or idea in the question."
|
| 577 |
|
| 578 |
if intent in {"walkthrough", "step_by_step"}:
|
| 579 |
step_reply = _walkthrough_from_steps(steps, verbosity=verbosity)
|
|
@@ -668,29 +799,6 @@ def _is_direct_solve_request(text: str, intent: str) -> bool:
|
|
| 668 |
return False
|
| 669 |
|
| 670 |
|
| 671 |
-
def _render_local_response(help_mode: str, body: str) -> str:
|
| 672 |
-
body = (body or "").strip()
|
| 673 |
-
if not body:
|
| 674 |
-
return "Let’s work through it."
|
| 675 |
-
|
| 676 |
-
if help_mode == "hint":
|
| 677 |
-
return f"Let’s work through it.\n\nHint:\n{body}"
|
| 678 |
-
|
| 679 |
-
if help_mode == "walkthrough":
|
| 680 |
-
return f"Let’s work through it.\n\nWalkthrough:\n{body}"
|
| 681 |
-
|
| 682 |
-
if help_mode == "answer":
|
| 683 |
-
return f"Let’s work through it.\n\nAnswer path:\n{body}"
|
| 684 |
-
|
| 685 |
-
if help_mode == "method":
|
| 686 |
-
return f"Let’s work through it.\n\nMethod:\n{body}"
|
| 687 |
-
|
| 688 |
-
if help_mode == "explain":
|
| 689 |
-
return f"Let’s work through it.\n\nExplanation:\n{body}"
|
| 690 |
-
|
| 691 |
-
return f"Let’s work through it.\n\n{body}"
|
| 692 |
-
|
| 693 |
-
|
| 694 |
class ConversationEngine:
|
| 695 |
def __init__(
|
| 696 |
self,
|
|
@@ -800,6 +908,8 @@ class ConversationEngine:
|
|
| 800 |
if not result.topic or result.topic in {"general_quant", "general", "unknown"}:
|
| 801 |
result.topic = question_topic
|
| 802 |
|
|
|
|
|
|
|
| 803 |
explainer_result = route_explainer(solver_input) if solver_input else None
|
| 804 |
explainer_understood = bool(
|
| 805 |
explainer_result is not None and getattr(explainer_result, "understood", False)
|
|
@@ -829,7 +939,6 @@ class ConversationEngine:
|
|
| 829 |
|
| 830 |
use_solver_steps = _solver_has_useful_steps(result)
|
| 831 |
use_solver_steps_for_hint = resolved_help_mode == "hint" and use_solver_steps
|
| 832 |
-
use_local_render = use_solver_steps and resolved_help_mode in {"answer", "walkthrough", "hint", "method"}
|
| 833 |
|
| 834 |
if resolved_help_mode == "explain" and explainer_understood:
|
| 835 |
reply = format_explainer_response(
|
|
@@ -863,18 +972,15 @@ class ConversationEngine:
|
|
| 863 |
explainer_scaffold=explainer_scaffold,
|
| 864 |
)
|
| 865 |
|
| 866 |
-
|
| 867 |
-
|
| 868 |
-
|
| 869 |
-
|
| 870 |
-
|
| 871 |
-
|
| 872 |
-
|
| 873 |
-
|
| 874 |
-
|
| 875 |
-
hint_stage=hint_stage,
|
| 876 |
-
topic=result.topic,
|
| 877 |
-
)
|
| 878 |
|
| 879 |
if resolved_help_mode != "answer":
|
| 880 |
result.solved = False
|
|
|
|
| 63 |
if not isinstance(item, dict):
|
| 64 |
return ""
|
| 65 |
|
| 66 |
+
direct_keys = ("content", "text", "message", "question_text", "raw_user_text")
|
| 67 |
+
for key in direct_keys:
|
| 68 |
value = item.get(key)
|
| 69 |
if isinstance(value, str) and value.strip():
|
| 70 |
return value.strip()
|
| 71 |
|
| 72 |
meta = item.get("meta")
|
| 73 |
if isinstance(meta, dict):
|
| 74 |
+
for key in ("question_text", "recovered_question_text"):
|
| 75 |
+
value = meta.get(key)
|
| 76 |
+
if isinstance(value, str) and value.strip():
|
| 77 |
+
return value.strip()
|
| 78 |
+
|
| 79 |
+
nested_state = meta.get("session_state")
|
| 80 |
+
if isinstance(nested_state, dict):
|
| 81 |
+
value = nested_state.get("question_text")
|
| 82 |
+
if isinstance(value, str) and value.strip():
|
| 83 |
+
return value.strip()
|
| 84 |
+
|
| 85 |
+
nested_state = item.get("session_state")
|
| 86 |
+
if isinstance(nested_state, dict):
|
| 87 |
+
value = nested_state.get("question_text")
|
| 88 |
if isinstance(value, str) and value.strip():
|
| 89 |
return value.strip()
|
| 90 |
|
| 91 |
return ""
|
| 92 |
|
| 93 |
|
| 94 |
+
def _extract_question_candidates_from_history_item(item: Dict[str, Any]) -> List[str]:
|
| 95 |
+
if not isinstance(item, dict):
|
| 96 |
+
return []
|
| 97 |
+
|
| 98 |
+
candidates: List[str] = []
|
| 99 |
+
|
| 100 |
+
direct_keys = ("question_text", "raw_user_text", "content", "text", "message")
|
| 101 |
+
for key in direct_keys:
|
| 102 |
+
value = item.get(key)
|
| 103 |
+
if isinstance(value, str) and value.strip():
|
| 104 |
+
candidates.append(value.strip())
|
| 105 |
+
|
| 106 |
+
meta = item.get("meta")
|
| 107 |
+
if isinstance(meta, dict):
|
| 108 |
+
for key in ("question_text", "recovered_question_text"):
|
| 109 |
+
value = meta.get(key)
|
| 110 |
+
if isinstance(value, str) and value.strip():
|
| 111 |
+
candidates.append(value.strip())
|
| 112 |
+
|
| 113 |
+
nested_state = meta.get("session_state")
|
| 114 |
+
if isinstance(nested_state, dict):
|
| 115 |
+
value = nested_state.get("question_text")
|
| 116 |
+
if isinstance(value, str) and value.strip():
|
| 117 |
+
candidates.append(value.strip())
|
| 118 |
+
|
| 119 |
+
nested_state = item.get("session_state")
|
| 120 |
+
if isinstance(nested_state, dict):
|
| 121 |
+
value = nested_state.get("question_text")
|
| 122 |
+
if isinstance(value, str) and value.strip():
|
| 123 |
+
candidates.append(value.strip())
|
| 124 |
+
|
| 125 |
+
deduped: List[str] = []
|
| 126 |
+
seen = set()
|
| 127 |
+
for candidate in candidates:
|
| 128 |
+
key = candidate.strip().lower()
|
| 129 |
+
if key and key not in seen:
|
| 130 |
+
seen.add(key)
|
| 131 |
+
deduped.append(candidate.strip())
|
| 132 |
+
|
| 133 |
+
return deduped
|
| 134 |
+
|
| 135 |
+
|
| 136 |
def _is_followup_hint_only(text: str) -> bool:
|
| 137 |
low = (text or "").strip().lower()
|
| 138 |
return low in {
|
|
|
|
| 146 |
"walk me through it",
|
| 147 |
"step by step",
|
| 148 |
"walkthrough",
|
| 149 |
+
"i'm confused",
|
| 150 |
+
"im confused",
|
| 151 |
+
"confused",
|
| 152 |
+
"explain more",
|
| 153 |
+
"more explanation",
|
| 154 |
+
"can you explain that",
|
| 155 |
+
"help me understand",
|
| 156 |
}
|
| 157 |
|
| 158 |
|
|
|
|
| 207 |
return _strip_control_prefix(text)
|
| 208 |
|
| 209 |
|
| 210 |
+
def _looks_like_question_text(text: str) -> bool:
|
| 211 |
+
t = (text or "").strip()
|
| 212 |
+
if not t:
|
| 213 |
+
return False
|
| 214 |
+
|
| 215 |
+
low = t.lower()
|
| 216 |
+
|
| 217 |
+
if "=" in t:
|
| 218 |
+
return True
|
| 219 |
+
if "%" in t:
|
| 220 |
+
return True
|
| 221 |
+
if re.search(r"\b\d+\s*:\s*\d+\b", t):
|
| 222 |
+
return True
|
| 223 |
+
if re.search(r"[a-zA-Z]\s*[\+\-\*/=]", t):
|
| 224 |
+
return True
|
| 225 |
+
if re.search(r"[\+\-\*/=]\s*[a-zA-Z0-9]", t):
|
| 226 |
+
return True
|
| 227 |
+
if any(k in low for k in [
|
| 228 |
+
"what is", "find", "if ", "how many", "probability", "ratio",
|
| 229 |
+
"percent", "equation", "integer", "triangle", "circle",
|
| 230 |
+
"mean", "median", "average", "remainder", "prime", "factor",
|
| 231 |
+
]):
|
| 232 |
+
return True
|
| 233 |
+
|
| 234 |
+
return False
|
| 235 |
+
|
| 236 |
+
|
| 237 |
def _classify_input_type(raw_user_text: str) -> str:
|
| 238 |
text = _clean_text(raw_user_text)
|
| 239 |
if not text:
|
|
|
|
| 328 |
except Exception:
|
| 329 |
pass
|
| 330 |
|
| 331 |
+
if isinstance(meta, dict):
|
| 332 |
+
nested_state = meta.get("session_state")
|
| 333 |
+
if isinstance(nested_state, dict) and "hint_stage" in nested_state:
|
| 334 |
+
try:
|
| 335 |
+
best = max(best, int(nested_state["hint_stage"]))
|
| 336 |
+
except Exception:
|
| 337 |
+
pass
|
| 338 |
+
|
| 339 |
return min(best, 3)
|
| 340 |
|
| 341 |
|
| 342 |
+
def _recover_question_text(
|
| 343 |
raw_user_text: str,
|
| 344 |
question_text: Optional[str],
|
| 345 |
chat_history: Optional[List[Dict[str, Any]]],
|
| 346 |
+
input_type: str,
|
| 347 |
) -> str:
|
| 348 |
explicit = _sanitize_question_text(question_text or "")
|
| 349 |
if explicit:
|
| 350 |
return explicit
|
| 351 |
|
| 352 |
+
direct_candidate = _sanitize_question_text(_extract_question_text(raw_user_text))
|
| 353 |
+
if direct_candidate and _looks_like_question_text(direct_candidate):
|
| 354 |
+
return direct_candidate
|
| 355 |
+
|
| 356 |
+
if not _is_followup_input(input_type):
|
| 357 |
+
return direct_candidate
|
| 358 |
|
| 359 |
if not chat_history:
|
| 360 |
return ""
|
| 361 |
|
| 362 |
for item in reversed(chat_history):
|
| 363 |
+
candidates = _extract_question_candidates_from_history_item(item)
|
| 364 |
+
for candidate in candidates:
|
| 365 |
+
recovered = _sanitize_question_text(candidate)
|
| 366 |
+
if not recovered:
|
| 367 |
+
continue
|
| 368 |
+
if _is_followup_hint_only(recovered):
|
| 369 |
+
continue
|
| 370 |
+
if _looks_like_question_text(recovered):
|
| 371 |
+
return recovered
|
| 372 |
+
|
| 373 |
+
extracted = _sanitize_question_text(_extract_question_text(recovered))
|
| 374 |
+
if extracted and not _is_followup_hint_only(extracted) and _looks_like_question_text(extracted):
|
| 375 |
+
return extracted
|
| 376 |
|
| 377 |
return ""
|
| 378 |
|
|
|
|
| 388 |
stored_question = _sanitize_question_text(state.get("question_text", ""))
|
| 389 |
|
| 390 |
if _is_followup_input(input_type):
|
| 391 |
+
if explicit_question and _looks_like_question_text(explicit_question):
|
| 392 |
+
return explicit_question, False
|
| 393 |
+
|
| 394 |
+
direct_candidate = _sanitize_question_text(_extract_question_text(raw_user_text))
|
| 395 |
+
if direct_candidate and _looks_like_question_text(direct_candidate):
|
| 396 |
+
return direct_candidate, False
|
| 397 |
+
|
| 398 |
+
if stored_question and _looks_like_question_text(stored_question):
|
| 399 |
return stored_question, True
|
| 400 |
|
| 401 |
+
recovered = _recover_question_text(
|
| 402 |
raw_user_text=raw_user_text,
|
| 403 |
question_text=question_text,
|
| 404 |
chat_history=chat_history,
|
| 405 |
+
input_type=input_type,
|
| 406 |
)
|
| 407 |
if recovered:
|
| 408 |
return recovered, True
|
|
|
|
| 522 |
r"\bthat gives\b",
|
| 523 |
r"\bthis gives\b",
|
| 524 |
r"\btherefore\b",
|
| 525 |
+
r"\bthus\b",
|
| 526 |
r"\bso x\s*=",
|
| 527 |
r"\bso y\s*=",
|
| 528 |
r"\bx\s*=",
|
|
|
|
| 534 |
for step in steps:
|
| 535 |
s = _strip_bullet_prefix(step)
|
| 536 |
lowered = s.lower()
|
| 537 |
+
if any(re.search(pattern, lowered) for pattern in banned_patterns):
|
| 538 |
continue
|
| 539 |
if s:
|
| 540 |
cleaned.append(s)
|
| 541 |
|
| 542 |
+
deduped: List[str] = []
|
| 543 |
+
seen = set()
|
| 544 |
+
for step in cleaned:
|
| 545 |
+
key = step.strip().lower()
|
| 546 |
+
if key and key not in seen:
|
| 547 |
+
seen.add(key)
|
| 548 |
+
deduped.append(step.strip())
|
| 549 |
+
|
| 550 |
+
return deduped
|
| 551 |
|
| 552 |
|
| 553 |
def _safe_meta_list(items: Any) -> List[str]:
|
|
|
|
| 616 |
return []
|
| 617 |
|
| 618 |
|
| 619 |
+
def _apply_safe_step_sanitization(result: SolverResult) -> None:
|
| 620 |
+
safe_steps = _get_result_steps(result)
|
| 621 |
+
|
| 622 |
+
result.steps = list(safe_steps)
|
| 623 |
+
setattr(result, "display_steps", list(safe_steps))
|
| 624 |
+
|
| 625 |
+
result.meta = result.meta or {}
|
| 626 |
+
result.meta["steps"] = list(safe_steps)
|
| 627 |
+
result.meta["display_steps"] = list(safe_steps)
|
| 628 |
+
|
| 629 |
+
|
| 630 |
def _solver_has_useful_steps(result: Optional[SolverResult]) -> bool:
|
| 631 |
if result is None:
|
| 632 |
return False
|
|
|
|
| 664 |
safe_steps[:4] if verbosity < 0.85 else
|
| 665 |
safe_steps
|
| 666 |
)
|
| 667 |
+
return "\n".join(f"- {step}" for step in shown_steps)
|
| 668 |
|
| 669 |
|
| 670 |
def _answer_path_from_steps(steps: List[str], verbosity: float) -> str:
|
|
|
|
| 677 |
safe_steps[:3] if verbosity < 0.8 else
|
| 678 |
safe_steps
|
| 679 |
)
|
| 680 |
+
return "\n".join(f"- {step}" for step in shown_steps)
|
| 681 |
|
| 682 |
|
| 683 |
def _compose_reply(
|
|
|
|
| 698 |
|
| 699 |
if intent == "instruction":
|
| 700 |
if steps:
|
| 701 |
+
return f"- {steps[0]}"
|
| 702 |
+
return "- First, identify the key relationship or comparison in the question."
|
| 703 |
|
| 704 |
if intent == "definition":
|
| 705 |
if steps:
|
| 706 |
+
return f"- {steps[0]}"
|
| 707 |
+
return "- This is asking for the meaning of the term or idea in the question."
|
| 708 |
|
| 709 |
if intent in {"walkthrough", "step_by_step"}:
|
| 710 |
step_reply = _walkthrough_from_steps(steps, verbosity=verbosity)
|
|
|
|
| 799 |
return False
|
| 800 |
|
| 801 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 802 |
class ConversationEngine:
|
| 803 |
def __init__(
|
| 804 |
self,
|
|
|
|
| 908 |
if not result.topic or result.topic in {"general_quant", "general", "unknown"}:
|
| 909 |
result.topic = question_topic
|
| 910 |
|
| 911 |
+
_apply_safe_step_sanitization(result)
|
| 912 |
+
|
| 913 |
explainer_result = route_explainer(solver_input) if solver_input else None
|
| 914 |
explainer_understood = bool(
|
| 915 |
explainer_result is not None and getattr(explainer_result, "understood", False)
|
|
|
|
| 939 |
|
| 940 |
use_solver_steps = _solver_has_useful_steps(result)
|
| 941 |
use_solver_steps_for_hint = resolved_help_mode == "hint" and use_solver_steps
|
|
|
|
| 942 |
|
| 943 |
if resolved_help_mode == "explain" and explainer_understood:
|
| 944 |
reply = format_explainer_response(
|
|
|
|
| 972 |
explainer_scaffold=explainer_scaffold,
|
| 973 |
)
|
| 974 |
|
| 975 |
+
reply = format_reply(
|
| 976 |
+
reply_core,
|
| 977 |
+
tone=tone,
|
| 978 |
+
verbosity=verbosity,
|
| 979 |
+
transparency=transparency,
|
| 980 |
+
help_mode=resolved_help_mode,
|
| 981 |
+
hint_stage=hint_stage,
|
| 982 |
+
topic=result.topic,
|
| 983 |
+
)
|
|
|
|
|
|
|
|
|
|
| 984 |
|
| 985 |
if resolved_help_mode != "answer":
|
| 986 |
result.solved = False
|