j-js commited on
Commit
536e0cf
·
verified ·
1 Parent(s): fc5aa10

Update conversation_logic.py

Browse files
Files changed (1) hide show
  1. conversation_logic.py +256 -76
conversation_logic.py CHANGED
@@ -1,7 +1,7 @@
1
  from __future__ import annotations
2
 
3
  import re
4
- from typing import Any, Dict, List, Optional, Set
5
 
6
  from context_parser import detect_intent, intent_to_help_mode
7
  from formatting import format_reply, format_explainer_response
@@ -85,6 +85,16 @@ def detect_help_mode(text: str) -> str:
85
  return "explain"
86
 
87
 
 
 
 
 
 
 
 
 
 
 
88
  def _extract_text_from_history_item(item: Dict[str, Any]) -> str:
89
  if not isinstance(item, dict):
90
  return ""
@@ -119,6 +129,102 @@ def _is_followup_hint_only(text: str) -> bool:
119
  }
120
 
121
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  def _recover_question_text_from_history(
123
  raw_user_text: str,
124
  question_text: Optional[str],
@@ -129,10 +235,10 @@ def _recover_question_text_from_history(
129
  return explicit
130
 
131
  if not _is_followup_hint_only(raw_user_text):
132
- return (raw_user_text or "").strip()
133
 
134
  if not chat_history:
135
- return (raw_user_text or "").strip()
136
 
137
  for item in reversed(chat_history):
138
  role = str(item.get("role", "")).lower()
@@ -148,9 +254,83 @@ def _recover_question_text_from_history(
148
  if _is_followup_hint_only(low):
149
  continue
150
 
151
- return text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
 
153
- return (raw_user_text or "").strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
 
156
  def _normalize_classified_topic(topic: Optional[str], category: Optional[str], question_text: str) -> str:
@@ -166,6 +346,9 @@ def _normalize_classified_topic(topic: Optional[str], category: Optional[str], q
166
  or bool(re.search(r"\b[a-z]\s*[\+\-\*/=]", q))
167
  )
168
 
 
 
 
169
  if t == "ratio" and not has_ratio_form and has_algebra_form:
170
  t = "algebra"
171
 
@@ -297,10 +480,13 @@ def _build_scaffold_reply(
297
  next_hint = _safe_meta_text(scaffold.get("next_hint")) if scaffold else None
298
  setup_actions = _safe_meta_list(scaffold.get("setup_actions", [])) if scaffold else []
299
  intermediate_steps = _safe_meta_list(scaffold.get("intermediate_steps", [])) if scaffold else []
 
300
 
301
  target_mode = help_mode or intent
302
 
303
  if target_mode == "hint" or intent == "hint":
 
 
304
  return first_move or next_hint or (setup_actions[0] if setup_actions else None) or ask or (teaching_points[0] if teaching_points else None)
305
 
306
  if target_mode == "instruction" or intent == "instruction":
@@ -446,71 +632,15 @@ def _is_progression_request(text: str) -> bool:
446
  "show the steps",
447
  "go on",
448
  "continue",
 
 
 
 
 
449
  ]
450
  )
451
 
452
 
453
- def _history_hint_stage(chat_history: Optional[List[Dict[str, Any]]]) -> int:
454
- if not chat_history:
455
- return 0
456
-
457
- best = 0
458
- for item in chat_history:
459
- if not isinstance(item, dict):
460
- continue
461
-
462
- if "hint_stage" in item:
463
- try:
464
- best = max(best, int(item["hint_stage"]))
465
- except Exception:
466
- pass
467
-
468
- meta = item.get("meta")
469
- if isinstance(meta, dict) and "hint_stage" in meta:
470
- try:
471
- best = max(best, int(meta["hint_stage"]))
472
- except Exception:
473
- pass
474
-
475
- return min(best, 3)
476
-
477
-
478
- def _resolve_hint_stage(
479
- user_text: str,
480
- intent: str,
481
- help_mode: str,
482
- chat_history: Optional[List[Dict[str, Any]]],
483
- ) -> int:
484
- history_stage = _history_hint_stage(chat_history)
485
- low = (user_text or "").strip().lower()
486
-
487
- if help_mode == "definition":
488
- return 0
489
-
490
- if help_mode in {"walkthrough", "step_by_step"}:
491
- return 3
492
-
493
- if _is_direct_solve_request(user_text, intent):
494
- return 3
495
-
496
- if any(p in low for p in ["next hint", "another hint", "next step", "continue", "go on"]):
497
- return min(history_stage + 1, 3)
498
-
499
- if "first step" in low:
500
- return max(history_stage, 1)
501
-
502
- if "hint" in low:
503
- return max(history_stage, 1)
504
-
505
- if intent in {"method", "instruction", "explain", "concept"}:
506
- return max(history_stage, 1)
507
-
508
- if _is_progression_request(low):
509
- return min(max(history_stage, 1), 3)
510
-
511
- return history_stage
512
-
513
-
514
  class ConversationEngine:
515
  def __init__(
516
  self,
@@ -533,15 +663,23 @@ class ConversationEngine:
533
  chat_history: Optional[List[Dict[str, Any]]] = None,
534
  question_text: Optional[str] = None,
535
  options_text: Optional[List[str]] = None,
 
536
  **kwargs,
537
  ) -> SolverResult:
538
- user_text = (raw_user_text or "").strip()
539
- recovered_question_text = _recover_question_text_from_history(
 
 
 
 
540
  raw_user_text=user_text,
541
  question_text=question_text,
 
 
542
  chat_history=chat_history,
543
  )
544
- solver_input = recovered_question_text
 
545
 
546
  category = normalize_category(kwargs.get("category"))
547
  classification = classify_question(question_text=solver_input, category=category)
@@ -554,8 +692,21 @@ class ConversationEngine:
554
  )
555
 
556
  resolved_intent = intent or detect_intent(user_text, help_mode)
 
 
 
 
 
 
 
 
557
  resolved_help_mode = help_mode or intent_to_help_mode(resolved_intent)
558
 
 
 
 
 
 
559
  is_quant = inferred_category == "Quantitative" or is_quant_question(solver_input)
560
 
561
  result = SolverResult(
@@ -590,6 +741,18 @@ class ConversationEngine:
590
  if not result.topic or result.topic in {"general_quant", "general", "unknown"}:
591
  result.topic = getattr(explainer_result, "topic", None) if explainer_understood else question_topic
592
 
 
 
 
 
 
 
 
 
 
 
 
 
593
  result.meta = result.meta or {}
594
 
595
  if explainer_understood:
@@ -600,11 +763,12 @@ class ConversationEngine:
600
  result.meta["explainer_teaching_points"] = explainer_teaching_points
601
  result.meta["scaffold"] = explainer_scaffold
602
 
603
- hint_stage = _resolve_hint_stage(
604
- user_text=user_text or solver_input,
605
- intent=resolved_intent,
606
- help_mode=resolved_help_mode,
607
- chat_history=chat_history,
 
608
  )
609
 
610
  result.meta["hint_stage"] = hint_stage
@@ -612,7 +776,7 @@ class ConversationEngine:
612
  result.meta["can_reveal_answer"] = bool(
613
  result.solved and _is_direct_solve_request(user_text or solver_input, resolved_intent) and hint_stage >= 3
614
  )
615
- result.meta["recovered_question_text"] = recovered_question_text
616
 
617
  if explainer_understood:
618
  reply = format_explainer_response(
@@ -644,12 +808,28 @@ class ConversationEngine:
644
  result.answer_value = None
645
  result.internal_answer = None
646
 
 
 
 
 
 
 
 
 
 
 
 
 
647
  result.reply = reply
648
  result.help_mode = resolved_help_mode
649
  result.meta["intent"] = resolved_intent
650
- result.meta["question_text"] = recovered_question_text or ""
651
  result.meta["options_count"] = len(options_text or [])
652
  result.meta["category"] = inferred_category
653
  result.meta["classified_topic"] = question_topic
654
 
 
 
 
 
655
  return result
 
1
  from __future__ import annotations
2
 
3
  import re
4
+ from typing import Any, Dict, List, Optional, Set, Tuple
5
 
6
  from context_parser import detect_intent, intent_to_help_mode
7
  from formatting import format_reply, format_explainer_response
 
85
  return "explain"
86
 
87
 
88
+ def _clean_text(text: Optional[str]) -> str:
89
+ return (text or "").strip()
90
+
91
+
92
+ def _safe_get_state(session_state: Optional[Dict[str, Any]]) -> Dict[str, Any]:
93
+ if isinstance(session_state, dict):
94
+ return dict(session_state)
95
+ return {}
96
+
97
+
98
  def _extract_text_from_history_item(item: Dict[str, Any]) -> str:
99
  if not isinstance(item, dict):
100
  return ""
 
129
  }
130
 
131
 
132
+ def _extract_question_text(raw_user_text: str) -> str:
133
+ text = _clean_text(raw_user_text)
134
+ lowered = text.lower()
135
+
136
+ prefixes = [
137
+ "solve:",
138
+ "solve ",
139
+ "question:",
140
+ "q:",
141
+ ]
142
+
143
+ for p in prefixes:
144
+ if lowered.startswith(p):
145
+ return text[len(p):].strip()
146
+
147
+ return text
148
+
149
+
150
+ def _classify_input_type(raw_user_text: str) -> str:
151
+ text = _clean_text(raw_user_text)
152
+ t = text.lower()
153
+
154
+ if not t:
155
+ return "empty"
156
+
157
+ if t in {"hint", "a hint", "give me a hint", "can i have a hint"}:
158
+ return "hint"
159
+
160
+ if t in {"next hint", "another hint", "more hint", "more hints", "next step", "continue", "go on"}:
161
+ return "next_hint"
162
+
163
+ if any(x in t for x in [
164
+ "i don't get it",
165
+ "i dont get it",
166
+ "i do not get it",
167
+ "i'm confused",
168
+ "im confused",
169
+ "confused",
170
+ "explain more",
171
+ "more explanation",
172
+ "can you explain that",
173
+ "help me understand",
174
+ ]):
175
+ return "confusion"
176
+
177
+ if t.startswith("solve:") or t.startswith("solve "):
178
+ return "solve"
179
+
180
+ if "=" in text and re.search(r"[A-Za-z]", text):
181
+ return "question"
182
+
183
+ if any(k in t for k in [
184
+ "what is",
185
+ "find",
186
+ "if ",
187
+ "how many",
188
+ "probability",
189
+ "ratio",
190
+ "percent",
191
+ "equation",
192
+ "integer",
193
+ ]):
194
+ return "question"
195
+
196
+ return "other"
197
+
198
+
199
+ def _is_followup_input(input_type: str) -> bool:
200
+ return input_type in {"hint", "next_hint", "confusion"}
201
+
202
+
203
+ def _history_hint_stage(chat_history: Optional[List[Dict[str, Any]]]) -> int:
204
+ if not chat_history:
205
+ return 0
206
+
207
+ best = 0
208
+ for item in chat_history:
209
+ if not isinstance(item, dict):
210
+ continue
211
+
212
+ if "hint_stage" in item:
213
+ try:
214
+ best = max(best, int(item["hint_stage"]))
215
+ except Exception:
216
+ pass
217
+
218
+ meta = item.get("meta")
219
+ if isinstance(meta, dict) and "hint_stage" in meta:
220
+ try:
221
+ best = max(best, int(meta["hint_stage"]))
222
+ except Exception:
223
+ pass
224
+
225
+ return min(best, 3)
226
+
227
+
228
  def _recover_question_text_from_history(
229
  raw_user_text: str,
230
  question_text: Optional[str],
 
235
  return explicit
236
 
237
  if not _is_followup_hint_only(raw_user_text):
238
+ return _extract_question_text(raw_user_text)
239
 
240
  if not chat_history:
241
+ return _extract_question_text(raw_user_text)
242
 
243
  for item in reversed(chat_history):
244
  role = str(item.get("role", "")).lower()
 
254
  if _is_followup_hint_only(low):
255
  continue
256
 
257
+ return _extract_question_text(text)
258
+
259
+ return _extract_question_text(raw_user_text)
260
+
261
+
262
+ def _choose_effective_question_text(
263
+ raw_user_text: str,
264
+ question_text: Optional[str],
265
+ input_type: str,
266
+ state: Dict[str, Any],
267
+ chat_history: Optional[List[Dict[str, Any]]],
268
+ ) -> Tuple[str, bool]:
269
+ explicit_question = _clean_text(question_text)
270
+ stored_question = _clean_text(state.get("question_text"))
271
+
272
+ if _is_followup_input(input_type):
273
+ if stored_question:
274
+ return stored_question, True
275
+
276
+ recovered = _recover_question_text_from_history(
277
+ raw_user_text=raw_user_text,
278
+ question_text=question_text,
279
+ chat_history=chat_history,
280
+ )
281
+ return _clean_text(recovered), bool(_clean_text(recovered))
282
+
283
+ if explicit_question:
284
+ return explicit_question, False
285
+
286
+ return _extract_question_text(raw_user_text), False
287
+
288
+
289
+ def _compute_hint_stage(
290
+ input_type: str,
291
+ prior_hint_stage: int,
292
+ fallback_history_stage: int = 0,
293
+ ) -> int:
294
+ prior = int(prior_hint_stage or 0)
295
+ history = int(fallback_history_stage or 0)
296
+ base = max(prior, history)
297
+
298
+ if input_type in {"solve", "question"}:
299
+ return 0
300
+
301
+ if input_type == "hint":
302
+ return max(1, base)
303
+
304
+ if input_type == "next_hint":
305
+ return base + 1 if base > 0 else 2
306
+
307
+ if input_type == "confusion":
308
+ return min((base if base > 0 else 1) + 1, 3)
309
 
310
+ return base
311
+
312
+
313
+ def _update_session_state(
314
+ state: Dict[str, Any],
315
+ *,
316
+ question_text: str,
317
+ hint_stage: int,
318
+ user_last_input_type: str,
319
+ built_on_previous_turn: bool,
320
+ help_mode: str,
321
+ intent: str,
322
+ topic: Optional[str],
323
+ category: Optional[str],
324
+ ) -> Dict[str, Any]:
325
+ state["question_text"] = question_text
326
+ state["hint_stage"] = int(hint_stage or 0)
327
+ state["user_last_input_type"] = user_last_input_type
328
+ state["built_on_previous_turn"] = bool(built_on_previous_turn)
329
+ state["help_mode"] = help_mode
330
+ state["intent"] = intent
331
+ state["topic"] = topic
332
+ state["category"] = category
333
+ return state
334
 
335
 
336
  def _normalize_classified_topic(topic: Optional[str], category: Optional[str], question_text: str) -> str:
 
346
  or bool(re.search(r"\b[a-z]\s*[\+\-\*/=]", q))
347
  )
348
 
349
+ if has_algebra_form:
350
+ return "algebra"
351
+
352
  if t == "ratio" and not has_ratio_form and has_algebra_form:
353
  t = "algebra"
354
 
 
480
  next_hint = _safe_meta_text(scaffold.get("next_hint")) if scaffold else None
481
  setup_actions = _safe_meta_list(scaffold.get("setup_actions", [])) if scaffold else []
482
  intermediate_steps = _safe_meta_list(scaffold.get("intermediate_steps", [])) if scaffold else []
483
+ hint_ladder = _safe_meta_list(scaffold.get("hint_ladder", [])) if scaffold else []
484
 
485
  target_mode = help_mode or intent
486
 
487
  if target_mode == "hint" or intent == "hint":
488
+ if hint_ladder:
489
+ return hint_ladder[0]
490
  return first_move or next_hint or (setup_actions[0] if setup_actions else None) or ask or (teaching_points[0] if teaching_points else None)
491
 
492
  if target_mode == "instruction" or intent == "instruction":
 
632
  "show the steps",
633
  "go on",
634
  "continue",
635
+ "i don't get it",
636
+ "i dont get it",
637
+ "i do not get it",
638
+ "confused",
639
+ "explain more",
640
  ]
641
  )
642
 
643
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
644
  class ConversationEngine:
645
  def __init__(
646
  self,
 
663
  chat_history: Optional[List[Dict[str, Any]]] = None,
664
  question_text: Optional[str] = None,
665
  options_text: Optional[List[str]] = None,
666
+ session_state: Optional[Dict[str, Any]] = None,
667
  **kwargs,
668
  ) -> SolverResult:
669
+ user_text = _clean_text(raw_user_text)
670
+ state = _safe_get_state(session_state)
671
+
672
+ input_type = _classify_input_type(user_text)
673
+
674
+ effective_question_text, built_on_previous_turn = _choose_effective_question_text(
675
  raw_user_text=user_text,
676
  question_text=question_text,
677
+ input_type=input_type,
678
+ state=state,
679
  chat_history=chat_history,
680
  )
681
+
682
+ solver_input = effective_question_text
683
 
684
  category = normalize_category(kwargs.get("category"))
685
  classification = classify_question(question_text=solver_input, category=category)
 
692
  )
693
 
694
  resolved_intent = intent or detect_intent(user_text, help_mode)
695
+
696
+ if input_type in {"hint", "next_hint"}:
697
+ resolved_intent = "hint"
698
+ elif input_type == "confusion":
699
+ resolved_intent = "walkthrough"
700
+ elif input_type in {"solve", "question"} and resolved_intent in {"hint", "walkthrough", "step_by_step"}:
701
+ resolved_intent = "answer"
702
+
703
  resolved_help_mode = help_mode or intent_to_help_mode(resolved_intent)
704
 
705
+ if input_type in {"hint", "next_hint"}:
706
+ resolved_help_mode = "hint"
707
+ elif input_type == "confusion":
708
+ resolved_help_mode = "walkthrough"
709
+
710
  is_quant = inferred_category == "Quantitative" or is_quant_question(solver_input)
711
 
712
  result = SolverResult(
 
741
  if not result.topic or result.topic in {"general_quant", "general", "unknown"}:
742
  result.topic = getattr(explainer_result, "topic", None) if explainer_understood else question_topic
743
 
744
+ if result.topic == "ratio":
745
+ qlow = solver_input.lower()
746
+ has_ratio_form = bool(re.search(r"\b\d+\s*:\s*\d+\b", qlow))
747
+ has_algebra_form = (
748
+ "=" in qlow
749
+ or bool(re.search(r"\b[xyz]\b", qlow))
750
+ or bool(re.search(r"\d+[a-z]\b", qlow))
751
+ or bool(re.search(r"\b[a-z]\s*[\+\-\*/=]", qlow))
752
+ )
753
+ if has_algebra_form and not has_ratio_form:
754
+ result.topic = "algebra"
755
+
756
  result.meta = result.meta or {}
757
 
758
  if explainer_understood:
 
763
  result.meta["explainer_teaching_points"] = explainer_teaching_points
764
  result.meta["scaffold"] = explainer_scaffold
765
 
766
+ prior_hint_stage = int(state.get("hint_stage", 0) or 0)
767
+ history_hint_stage = _history_hint_stage(chat_history)
768
+ hint_stage = _compute_hint_stage(
769
+ input_type=input_type,
770
+ prior_hint_stage=prior_hint_stage,
771
+ fallback_history_stage=history_hint_stage,
772
  )
773
 
774
  result.meta["hint_stage"] = hint_stage
 
776
  result.meta["can_reveal_answer"] = bool(
777
  result.solved and _is_direct_solve_request(user_text or solver_input, resolved_intent) and hint_stage >= 3
778
  )
779
+ result.meta["recovered_question_text"] = effective_question_text
780
 
781
  if explainer_understood:
782
  reply = format_explainer_response(
 
808
  result.answer_value = None
809
  result.internal_answer = None
810
 
811
+ state = _update_session_state(
812
+ state,
813
+ question_text=effective_question_text,
814
+ hint_stage=hint_stage,
815
+ user_last_input_type=input_type,
816
+ built_on_previous_turn=built_on_previous_turn,
817
+ help_mode=resolved_help_mode,
818
+ intent=resolved_intent,
819
+ topic=result.topic,
820
+ category=inferred_category,
821
+ )
822
+
823
  result.reply = reply
824
  result.help_mode = resolved_help_mode
825
  result.meta["intent"] = resolved_intent
826
+ result.meta["question_text"] = effective_question_text or ""
827
  result.meta["options_count"] = len(options_text or [])
828
  result.meta["category"] = inferred_category
829
  result.meta["classified_topic"] = question_topic
830
 
831
+ result.meta["user_last_input_type"] = input_type
832
+ result.meta["built_on_previous_turn"] = built_on_previous_turn
833
+ result.meta["session_state"] = state
834
+
835
  return result