j-js commited on
Commit
04b7754
·
verified ·
1 Parent(s): 979bab5

Update conversation_logic.py

Browse files
Files changed (1) hide show
  1. conversation_logic.py +96 -53
conversation_logic.py CHANGED
@@ -24,19 +24,28 @@ DIRECT_SOLVE_PATTERNS = [
24
  r"\bcalculate\b",
25
  ]
26
 
27
-
28
  CONTROL_PREFIX_PATTERNS = [
29
  r"^\s*solve\s*:\s*",
30
  r"^\s*solve\s+",
31
  r"^\s*question\s*:\s*",
32
  r"^\s*q\s*:\s*",
33
  r"^\s*hint\s*:\s*",
 
34
  r"^\s*next hint\s*:\s*",
 
35
  r"^\s*another hint\s*:\s*",
 
36
  r"^\s*walkthrough\s*:\s*",
 
37
  r"^\s*step by step\s*:\s*",
 
38
  r"^\s*explain\s*:\s*",
 
39
  r"^\s*method\s*:\s*",
 
 
 
 
40
  ]
41
 
42
 
@@ -80,6 +89,7 @@ def _is_followup_hint_only(text: str) -> bool:
80
  "go on",
81
  "walk me through it",
82
  "step by step",
 
83
  }
84
 
85
 
@@ -104,23 +114,22 @@ def _sanitize_question_text(text: str) -> str:
104
 
105
  lines = [line.strip() for line in raw.splitlines() if line.strip()]
106
 
107
- # Unity payload safety: take the actual question line
108
  if len(lines) >= 9 and re.fullmatch(r"-?\d+", lines[2]) and lines[4].lower() in {"true", "false"}:
109
  return _strip_control_prefix(lines[1])
110
 
111
- # If the first line is a command and second line looks like the question, use the second line
112
  if len(lines) >= 2 and lines[0].lower() in {
113
- "hint", "next hint", "another hint", "continue", "go on", "walkthrough", "step by step", "solve"
 
114
  }:
115
  return _strip_control_prefix(lines[1])
116
 
117
- # If clearly corrupted multiline payload leaked in, use first substantive line that looks like a question/math line
118
  for line in lines:
119
  candidate = _strip_control_prefix(line)
 
 
120
  if (
121
  "=" in candidate
122
  or "%" in candidate
123
- or ":" in candidate
124
  or re.search(r"\b(probability|ratio|percent|integer|triangle|circle|mean|median)\b", candidate.lower())
125
  or re.search(r"[a-zA-Z]\s*[\+\-\*/=]", candidate)
126
  or re.search(r"[\+\-\*/=]\s*[a-zA-Z0-9]", candidate)
@@ -195,15 +204,8 @@ def _classify_input_type(raw_user_text: str) -> str:
195
  return "question"
196
 
197
  if any(k in stripped.lower() for k in [
198
- "what is",
199
- "find",
200
- "if ",
201
- "how many",
202
- "probability",
203
- "ratio",
204
- "percent",
205
- "equation",
206
- "integer",
207
  ]):
208
  return "question"
209
 
@@ -252,7 +254,7 @@ def _recover_question_text_from_history(
252
  return _sanitize_question_text(_extract_question_text(raw_user_text))
253
 
254
  if not chat_history:
255
- return _sanitize_question_text(_extract_question_text(raw_user_text))
256
 
257
  for item in reversed(chat_history):
258
  role = str(item.get("role", "")).lower()
@@ -265,9 +267,11 @@ def _recover_question_text_from_history(
265
  if _is_followup_hint_only(low):
266
  continue
267
 
268
- return _sanitize_question_text(_extract_question_text(text))
 
 
269
 
270
- return _sanitize_question_text(_extract_question_text(raw_user_text))
271
 
272
 
273
  def _choose_effective_question_text(
@@ -289,8 +293,10 @@ def _choose_effective_question_text(
289
  question_text=question_text,
290
  chat_history=chat_history,
291
  )
292
- cleaned = _sanitize_question_text(recovered)
293
- return cleaned, bool(cleaned)
 
 
294
 
295
  if explicit_question:
296
  return explicit_question, False
@@ -334,7 +340,8 @@ def _update_session_state(
334
  topic: Optional[str],
335
  category: Optional[str],
336
  ) -> Dict[str, Any]:
337
- state["question_text"] = question_text
 
338
  state["hint_stage"] = int(hint_stage or 0)
339
  state["user_last_input_type"] = user_last_input_type
340
  state["built_on_previous_turn"] = bool(built_on_previous_turn)
@@ -501,18 +508,16 @@ def _build_hint_from_steps(
501
  fallback: Optional[List[str]] = None,
502
  ) -> str:
503
  safe_steps = _safe_steps(steps)
 
 
504
  if safe_steps:
505
- stage = max(1, min(int(hint_stage or 1), 3))
506
- if stage == 1:
507
- return f"- {safe_steps[0]}"
508
- if stage == 2:
509
- return "\n".join(f"- {s}" for s in safe_steps[:2])
510
- return "\n".join(f"- {s}" for s in safe_steps[:3])
511
 
512
  fallback = _safe_steps(fallback or [])
513
  if fallback:
514
- stage = max(1, min(int(hint_stage or 1), 3))
515
- return "\n".join(f"- {s}" for s in fallback[:stage])
516
 
517
  return "- Start by identifying the main relationship in the problem."
518
 
@@ -531,6 +536,19 @@ def _walkthrough_from_steps(steps: List[str], verbosity: float) -> str:
531
  return "\n".join(f"- {s}" for s in shown_steps)
532
 
533
 
 
 
 
 
 
 
 
 
 
 
 
 
 
534
  def _compose_reply(
535
  result: SolverResult,
536
  intent: str,
@@ -602,14 +620,9 @@ def _compose_reply(
602
  ])
603
 
604
  if intent in {"explain", "method", "concept", "answer"}:
605
- if steps:
606
- shown_steps = (
607
- steps[:1] if verbosity < 0.25 else
608
- steps[:2] if verbosity < 0.6 else
609
- steps[:3] if verbosity < 0.85 else
610
- steps
611
- )
612
- return "\n".join(f"- {s}" for s in shown_steps)
613
 
614
  if topic == "algebra":
615
  return "\n".join([
@@ -655,6 +668,29 @@ def _is_direct_solve_request(text: str, intent: str) -> bool:
655
  return False
656
 
657
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
658
  class ConversationEngine:
659
  def __init__(
660
  self,
@@ -732,7 +768,9 @@ class ConversationEngine:
732
  fallback_history_stage=history_hint_stage,
733
  )
734
 
735
- is_quant = inferred_category == "Quantitative" or is_quant_question(solver_input)
 
 
736
 
737
  result = SolverResult(
738
  domain="quant" if is_quant else "general",
@@ -740,7 +778,7 @@ class ConversationEngine:
740
  help_mode=resolved_help_mode,
741
  answer_letter=None,
742
  answer_value=None,
743
- topic=question_topic,
744
  used_retrieval=False,
745
  used_generator=False,
746
  internal_answer=None,
@@ -762,7 +800,7 @@ class ConversationEngine:
762
  if not result.topic or result.topic in {"general_quant", "general", "unknown"}:
763
  result.topic = question_topic
764
 
765
- explainer_result = route_explainer(solver_input)
766
  explainer_understood = bool(
767
  explainer_result is not None and getattr(explainer_result, "understood", False)
768
  )
@@ -774,7 +812,6 @@ class ConversationEngine:
774
  if explainer_understood else []
775
  )
776
 
777
- # force the externally resolved mode after solver routing
778
  result.help_mode = resolved_help_mode
779
 
780
  result.meta = result.meta or {}
@@ -790,7 +827,9 @@ class ConversationEngine:
790
  result.meta["explainer_teaching_points"] = explainer_teaching_points
791
  result.meta["scaffold"] = explainer_scaffold
792
 
793
- use_solver_steps_for_hint = resolved_help_mode == "hint" and _solver_has_useful_steps(result)
 
 
794
 
795
  if resolved_help_mode == "explain" and explainer_understood:
796
  reply = format_explainer_response(
@@ -823,15 +862,19 @@ class ConversationEngine:
823
  hint_stage=hint_stage,
824
  explainer_scaffold=explainer_scaffold,
825
  )
826
- reply = format_reply(
827
- reply_core,
828
- tone=tone,
829
- verbosity=verbosity,
830
- transparency=transparency,
831
- help_mode=resolved_help_mode,
832
- hint_stage=hint_stage,
833
- topic=result.topic,
834
- )
 
 
 
 
835
 
836
  if resolved_help_mode != "answer":
837
  result.solved = False
@@ -866,8 +909,8 @@ class ConversationEngine:
866
  result.meta["intent"] = resolved_intent
867
  result.meta["question_text"] = solver_input or ""
868
  result.meta["options_count"] = len(options_text or [])
869
- result.meta["category"] = inferred_category
870
- result.meta["classified_topic"] = question_topic
871
  result.meta["user_last_input_type"] = input_type
872
  result.meta["built_on_previous_turn"] = built_on_previous_turn
873
  result.meta["session_state"] = state
 
24
  r"\bcalculate\b",
25
  ]
26
 
 
27
  CONTROL_PREFIX_PATTERNS = [
28
  r"^\s*solve\s*:\s*",
29
  r"^\s*solve\s+",
30
  r"^\s*question\s*:\s*",
31
  r"^\s*q\s*:\s*",
32
  r"^\s*hint\s*:\s*",
33
+ r"^\s*hint\s*$",
34
  r"^\s*next hint\s*:\s*",
35
+ r"^\s*next hint\s*$",
36
  r"^\s*another hint\s*:\s*",
37
+ r"^\s*another hint\s*$",
38
  r"^\s*walkthrough\s*:\s*",
39
+ r"^\s*walkthrough\s*$",
40
  r"^\s*step by step\s*:\s*",
41
+ r"^\s*step by step\s*$",
42
  r"^\s*explain\s*:\s*",
43
+ r"^\s*explain\s*$",
44
  r"^\s*method\s*:\s*",
45
+ r"^\s*method\s*$",
46
+ r"^\s*continue\s*$",
47
+ r"^\s*go on\s*$",
48
+ r"^\s*next step\s*$",
49
  ]
50
 
51
 
 
89
  "go on",
90
  "walk me through it",
91
  "step by step",
92
+ "walkthrough",
93
  }
94
 
95
 
 
114
 
115
  lines = [line.strip() for line in raw.splitlines() if line.strip()]
116
 
 
117
  if len(lines) >= 9 and re.fullmatch(r"-?\d+", lines[2]) and lines[4].lower() in {"true", "false"}:
118
  return _strip_control_prefix(lines[1])
119
 
 
120
  if len(lines) >= 2 and lines[0].lower() in {
121
+ "hint", "next hint", "another hint", "continue", "go on",
122
+ "walkthrough", "step by step", "solve", "explain", "method"
123
  }:
124
  return _strip_control_prefix(lines[1])
125
 
 
126
  for line in lines:
127
  candidate = _strip_control_prefix(line)
128
+ if not candidate:
129
+ continue
130
  if (
131
  "=" in candidate
132
  or "%" in candidate
 
133
  or re.search(r"\b(probability|ratio|percent|integer|triangle|circle|mean|median)\b", candidate.lower())
134
  or re.search(r"[a-zA-Z]\s*[\+\-\*/=]", candidate)
135
  or re.search(r"[\+\-\*/=]\s*[a-zA-Z0-9]", candidate)
 
204
  return "question"
205
 
206
  if any(k in stripped.lower() for k in [
207
+ "what is", "find", "if ", "how many", "probability", "ratio",
208
+ "percent", "equation", "integer"
 
 
 
 
 
 
 
209
  ]):
210
  return "question"
211
 
 
254
  return _sanitize_question_text(_extract_question_text(raw_user_text))
255
 
256
  if not chat_history:
257
+ return ""
258
 
259
  for item in reversed(chat_history):
260
  role = str(item.get("role", "")).lower()
 
267
  if _is_followup_hint_only(low):
268
  continue
269
 
270
+ recovered = _sanitize_question_text(_extract_question_text(text))
271
+ if recovered:
272
+ return recovered
273
 
274
+ return ""
275
 
276
 
277
  def _choose_effective_question_text(
 
293
  question_text=question_text,
294
  chat_history=chat_history,
295
  )
296
+ if recovered:
297
+ return recovered, True
298
+
299
+ return "", True
300
 
301
  if explicit_question:
302
  return explicit_question, False
 
340
  topic: Optional[str],
341
  category: Optional[str],
342
  ) -> Dict[str, Any]:
343
+ if question_text:
344
+ state["question_text"] = question_text
345
  state["hint_stage"] = int(hint_stage or 0)
346
  state["user_last_input_type"] = user_last_input_type
347
  state["built_on_previous_turn"] = bool(built_on_previous_turn)
 
508
  fallback: Optional[List[str]] = None,
509
  ) -> str:
510
  safe_steps = _safe_steps(steps)
511
+ stage = max(1, min(int(hint_stage or 1), 3))
512
+
513
  if safe_steps:
514
+ index = min(stage - 1, len(safe_steps) - 1)
515
+ return f"- {safe_steps[index]}"
 
 
 
 
516
 
517
  fallback = _safe_steps(fallback or [])
518
  if fallback:
519
+ index = min(stage - 1, len(fallback) - 1)
520
+ return f"- {fallback[index]}"
521
 
522
  return "- Start by identifying the main relationship in the problem."
523
 
 
536
  return "\n".join(f"- {s}" for s in shown_steps)
537
 
538
 
539
+ def _answer_path_from_steps(steps: List[str], verbosity: float) -> str:
540
+ safe_steps = _safe_steps(steps)
541
+ if not safe_steps:
542
+ return ""
543
+
544
+ shown_steps = (
545
+ safe_steps[:2] if verbosity < 0.35 else
546
+ safe_steps[:3] if verbosity < 0.8 else
547
+ safe_steps
548
+ )
549
+ return "\n".join(f"- {s}" for s in shown_steps)
550
+
551
+
552
  def _compose_reply(
553
  result: SolverResult,
554
  intent: str,
 
620
  ])
621
 
622
  if intent in {"explain", "method", "concept", "answer"}:
623
+ step_reply = _answer_path_from_steps(steps, verbosity=verbosity)
624
+ if step_reply:
625
+ return step_reply
 
 
 
 
 
626
 
627
  if topic == "algebra":
628
  return "\n".join([
 
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,
 
768
  fallback_history_stage=history_hint_stage,
769
  )
770
 
771
+ is_quant = bool(solver_input) and (
772
+ inferred_category == "Quantitative" or is_quant_question(solver_input)
773
+ )
774
 
775
  result = SolverResult(
776
  domain="quant" if is_quant else "general",
 
778
  help_mode=resolved_help_mode,
779
  answer_letter=None,
780
  answer_value=None,
781
+ topic=question_topic if is_quant else "general",
782
  used_retrieval=False,
783
  used_generator=False,
784
  internal_answer=None,
 
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)
806
  )
 
812
  if explainer_understood else []
813
  )
814
 
 
815
  result.help_mode = resolved_help_mode
816
 
817
  result.meta = result.meta or {}
 
827
  result.meta["explainer_teaching_points"] = explainer_teaching_points
828
  result.meta["scaffold"] = explainer_scaffold
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(
 
862
  hint_stage=hint_stage,
863
  explainer_scaffold=explainer_scaffold,
864
  )
865
+
866
+ if use_local_render:
867
+ reply = _render_local_response(resolved_help_mode, reply_core)
868
+ else:
869
+ reply = format_reply(
870
+ reply_core,
871
+ tone=tone,
872
+ verbosity=verbosity,
873
+ transparency=transparency,
874
+ help_mode=resolved_help_mode,
875
+ hint_stage=hint_stage,
876
+ topic=result.topic,
877
+ )
878
 
879
  if resolved_help_mode != "answer":
880
  result.solved = False
 
909
  result.meta["intent"] = resolved_intent
910
  result.meta["question_text"] = solver_input or ""
911
  result.meta["options_count"] = len(options_text or [])
912
+ result.meta["category"] = inferred_category if inferred_category else "General"
913
+ result.meta["classified_topic"] = question_topic if question_topic else "general"
914
  result.meta["user_last_input_type"] = input_type
915
  result.meta["built_on_previous_turn"] = built_on_previous_turn
916
  result.meta["session_state"] = state