Update conversation_logic.py
Browse files- conversation_logic.py +104 -57
conversation_logic.py
CHANGED
|
@@ -38,24 +38,12 @@ STRUCTURE_KEYWORDS = {
|
|
| 38 |
"equation", "solve", "isolate", "variable", "linear", "expression",
|
| 39 |
"unknown", "algebra", "substitute", "rearrange"
|
| 40 |
],
|
| 41 |
-
"percent": [
|
| 42 |
-
|
| 43 |
-
],
|
| 44 |
-
"
|
| 45 |
-
|
| 46 |
-
],
|
| 47 |
-
"statistics": [
|
| 48 |
-
"mean", "median", "mode", "range", "average", "standard deviation"
|
| 49 |
-
],
|
| 50 |
-
"probability": [
|
| 51 |
-
"probability", "chance", "likely", "odds", "event"
|
| 52 |
-
],
|
| 53 |
-
"geometry": [
|
| 54 |
-
"triangle", "circle", "angle", "area", "perimeter", "radius", "diameter"
|
| 55 |
-
],
|
| 56 |
-
"number_properties": [
|
| 57 |
-
"integer", "odd", "even", "prime", "divisible", "factor", "multiple"
|
| 58 |
-
],
|
| 59 |
}
|
| 60 |
|
| 61 |
INTENT_KEYWORDS = {
|
|
@@ -74,24 +62,12 @@ MISMATCH_TERMS = {
|
|
| 74 |
"absolute value", "modulus", "square root", "quadratic", "inequality",
|
| 75 |
"roots", "parabola", "simultaneous equations"
|
| 76 |
],
|
| 77 |
-
"percent": [
|
| 78 |
-
|
| 79 |
-
],
|
| 80 |
-
"
|
| 81 |
-
|
| 82 |
-
],
|
| 83 |
-
"statistics": [
|
| 84 |
-
"absolute value", "prime", "triangle"
|
| 85 |
-
],
|
| 86 |
-
"probability": [
|
| 87 |
-
"absolute value", "circle area", "quadratic"
|
| 88 |
-
],
|
| 89 |
-
"geometry": [
|
| 90 |
-
"absolute value", "prime", "median salary"
|
| 91 |
-
],
|
| 92 |
-
"number_properties": [
|
| 93 |
-
"circle", "triangle", "absolute value"
|
| 94 |
-
],
|
| 95 |
}
|
| 96 |
|
| 97 |
|
|
@@ -255,8 +231,7 @@ def _score_chunk(
|
|
| 255 |
elif topic.lower() in text:
|
| 256 |
score += 2.0
|
| 257 |
|
| 258 |
-
|
| 259 |
-
for term in structure_terms:
|
| 260 |
if term.lower() in text:
|
| 261 |
score += 1.5
|
| 262 |
|
|
@@ -268,8 +243,7 @@ def _score_chunk(
|
|
| 268 |
overlap = sum(1 for kw in q_keywords if kw in text)
|
| 269 |
score += min(overlap * 0.4, 3.0)
|
| 270 |
|
| 271 |
-
|
| 272 |
-
for bad in mismatch_terms:
|
| 273 |
if bad.lower() in text:
|
| 274 |
score -= 2.5
|
| 275 |
|
|
@@ -325,6 +299,78 @@ def _build_retrieval_query(
|
|
| 325 |
return " ".join(parts).strip()
|
| 326 |
|
| 327 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 328 |
class ConversationEngine:
|
| 329 |
def __init__(
|
| 330 |
self,
|
|
@@ -374,7 +420,9 @@ class ConversationEngine:
|
|
| 374 |
selected_chunks: List[RetrievedChunk] = []
|
| 375 |
|
| 376 |
if is_quant_question(solver_input):
|
| 377 |
-
|
|
|
|
|
|
|
| 378 |
result.help_mode = resolved_help_mode
|
| 379 |
|
| 380 |
reply = _compose_quant_reply(
|
|
@@ -391,8 +439,10 @@ class ConversationEngine:
|
|
| 391 |
)
|
| 392 |
|
| 393 |
if allow_retrieval and retrieval_context:
|
|
|
|
|
|
|
| 394 |
filtered = _filter_retrieved_chunks(
|
| 395 |
-
chunks=
|
| 396 |
intent=resolved_intent,
|
| 397 |
topic=result.topic,
|
| 398 |
question_text=solver_input,
|
|
@@ -403,14 +453,13 @@ class ConversationEngine:
|
|
| 403 |
result.teaching_chunks = filtered
|
| 404 |
|
| 405 |
elif allow_retrieval and self.retriever is not None:
|
| 406 |
-
|
| 407 |
raw_user_text=user_text,
|
| 408 |
question_text=solver_input,
|
| 409 |
intent=resolved_intent,
|
| 410 |
topic=result.topic,
|
| 411 |
solved=bool(result.solved),
|
| 412 |
-
)
|
| 413 |
-
retrieved = self.retriever.search(query, top_k=6)
|
| 414 |
filtered = _filter_retrieved_chunks(
|
| 415 |
chunks=retrieved,
|
| 416 |
intent=resolved_intent,
|
|
@@ -426,18 +475,16 @@ class ConversationEngine:
|
|
| 426 |
reply = f"{reply}\n\nRelevant study notes:\n" + "\n".join(_teaching_lines(selected_chunks))
|
| 427 |
|
| 428 |
if not result.solved and self.generator is not None:
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
except Exception:
|
| 440 |
-
pass
|
| 441 |
|
| 442 |
reply = format_reply(
|
| 443 |
text=reply,
|
|
|
|
| 38 |
"equation", "solve", "isolate", "variable", "linear", "expression",
|
| 39 |
"unknown", "algebra", "substitute", "rearrange"
|
| 40 |
],
|
| 41 |
+
"percent": ["percent", "%", "percentage", "increase", "decrease", "of"],
|
| 42 |
+
"ratio": ["ratio", "proportion", "proportional", "part", "share"],
|
| 43 |
+
"statistics": ["mean", "median", "mode", "range", "average", "standard deviation"],
|
| 44 |
+
"probability": ["probability", "chance", "likely", "odds", "event"],
|
| 45 |
+
"geometry": ["triangle", "circle", "angle", "area", "perimeter", "radius", "diameter"],
|
| 46 |
+
"number_properties": ["integer", "odd", "even", "prime", "divisible", "factor", "multiple"],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
}
|
| 48 |
|
| 49 |
INTENT_KEYWORDS = {
|
|
|
|
| 62 |
"absolute value", "modulus", "square root", "quadratic", "inequality",
|
| 63 |
"roots", "parabola", "simultaneous equations"
|
| 64 |
],
|
| 65 |
+
"percent": ["triangle", "circle", "prime", "absolute value"],
|
| 66 |
+
"ratio": ["absolute value", "quadratic", "circle"],
|
| 67 |
+
"statistics": ["absolute value", "prime", "triangle"],
|
| 68 |
+
"probability": ["absolute value", "circle area", "quadratic"],
|
| 69 |
+
"geometry": ["absolute value", "prime", "median salary"],
|
| 70 |
+
"number_properties": ["circle", "triangle", "absolute value"],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
}
|
| 72 |
|
| 73 |
|
|
|
|
| 231 |
elif topic.lower() in text:
|
| 232 |
score += 2.0
|
| 233 |
|
| 234 |
+
for term in _infer_structure_terms(question_text, topic):
|
|
|
|
| 235 |
if term.lower() in text:
|
| 236 |
score += 1.5
|
| 237 |
|
|
|
|
| 243 |
overlap = sum(1 for kw in q_keywords if kw in text)
|
| 244 |
score += min(overlap * 0.4, 3.0)
|
| 245 |
|
| 246 |
+
for bad in _infer_mismatch_terms(topic, question_text):
|
|
|
|
| 247 |
if bad.lower() in text:
|
| 248 |
score -= 2.5
|
| 249 |
|
|
|
|
| 299 |
return " ".join(parts).strip()
|
| 300 |
|
| 301 |
|
| 302 |
+
def _to_chunk(item: Any) -> Optional[RetrievedChunk]:
|
| 303 |
+
if isinstance(item, RetrievedChunk):
|
| 304 |
+
return item
|
| 305 |
+
|
| 306 |
+
if isinstance(item, dict):
|
| 307 |
+
return RetrievedChunk(
|
| 308 |
+
text=str(item.get("text", "")).strip(),
|
| 309 |
+
topic=str(item.get("topic", "general")).strip() or "general",
|
| 310 |
+
source=str(item.get("source", "local")).strip() or "local",
|
| 311 |
+
score=float(item.get("score", 0.0) or 0.0),
|
| 312 |
+
)
|
| 313 |
+
|
| 314 |
+
if isinstance(item, str):
|
| 315 |
+
txt = item.strip()
|
| 316 |
+
if txt:
|
| 317 |
+
return RetrievedChunk(text=txt)
|
| 318 |
+
|
| 319 |
+
return None
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
def _retrieve_chunks(retriever: Any, query: str) -> List[RetrievedChunk]:
|
| 323 |
+
raw = None
|
| 324 |
+
|
| 325 |
+
try:
|
| 326 |
+
raw = retriever.search(query, top_k=6)
|
| 327 |
+
except TypeError:
|
| 328 |
+
try:
|
| 329 |
+
raw = retriever.search(query)
|
| 330 |
+
except Exception:
|
| 331 |
+
raw = None
|
| 332 |
+
except Exception:
|
| 333 |
+
raw = None
|
| 334 |
+
|
| 335 |
+
if raw is None:
|
| 336 |
+
for method_name in ["retrieve", "run", "query"]:
|
| 337 |
+
method = getattr(retriever, method_name, None)
|
| 338 |
+
if callable(method):
|
| 339 |
+
try:
|
| 340 |
+
raw = method(query)
|
| 341 |
+
break
|
| 342 |
+
except Exception:
|
| 343 |
+
raw = None
|
| 344 |
+
|
| 345 |
+
if raw is None:
|
| 346 |
+
return []
|
| 347 |
+
|
| 348 |
+
if not isinstance(raw, list):
|
| 349 |
+
raw = [raw]
|
| 350 |
+
|
| 351 |
+
out: List[RetrievedChunk] = []
|
| 352 |
+
for item in raw:
|
| 353 |
+
chunk = _to_chunk(item)
|
| 354 |
+
if chunk and chunk.text:
|
| 355 |
+
out.append(chunk)
|
| 356 |
+
return out
|
| 357 |
+
|
| 358 |
+
|
| 359 |
+
def _generate_fallback(generator: Any, user_text: str, intent: str, topic: Optional[str], chat_history: List[Dict[str, Any]]) -> Optional[str]:
|
| 360 |
+
for call in [
|
| 361 |
+
lambda: generator.generate(user_text=user_text, intent=intent, topic=topic, chat_history=chat_history),
|
| 362 |
+
lambda: generator.generate(user_text, intent=intent, topic=topic, chat_history=chat_history),
|
| 363 |
+
lambda: generator.generate(user_text),
|
| 364 |
+
]:
|
| 365 |
+
try:
|
| 366 |
+
result = call()
|
| 367 |
+
if isinstance(result, str) and result.strip():
|
| 368 |
+
return result.strip()
|
| 369 |
+
except Exception:
|
| 370 |
+
continue
|
| 371 |
+
return None
|
| 372 |
+
|
| 373 |
+
|
| 374 |
class ConversationEngine:
|
| 375 |
def __init__(
|
| 376 |
self,
|
|
|
|
| 420 |
selected_chunks: List[RetrievedChunk] = []
|
| 421 |
|
| 422 |
if is_quant_question(solver_input):
|
| 423 |
+
solved_result = solve_quant(solver_input)
|
| 424 |
+
if solved_result is not None:
|
| 425 |
+
result = solved_result
|
| 426 |
result.help_mode = resolved_help_mode
|
| 427 |
|
| 428 |
reply = _compose_quant_reply(
|
|
|
|
| 439 |
)
|
| 440 |
|
| 441 |
if allow_retrieval and retrieval_context:
|
| 442 |
+
safe_chunks = [_to_chunk(c) for c in retrieval_context]
|
| 443 |
+
safe_chunks = [c for c in safe_chunks if c and c.text]
|
| 444 |
filtered = _filter_retrieved_chunks(
|
| 445 |
+
chunks=safe_chunks,
|
| 446 |
intent=resolved_intent,
|
| 447 |
topic=result.topic,
|
| 448 |
question_text=solver_input,
|
|
|
|
| 453 |
result.teaching_chunks = filtered
|
| 454 |
|
| 455 |
elif allow_retrieval and self.retriever is not None:
|
| 456 |
+
retrieved = _retrieve_chunks(self.retriever, _build_retrieval_query(
|
| 457 |
raw_user_text=user_text,
|
| 458 |
question_text=solver_input,
|
| 459 |
intent=resolved_intent,
|
| 460 |
topic=result.topic,
|
| 461 |
solved=bool(result.solved),
|
| 462 |
+
))
|
|
|
|
| 463 |
filtered = _filter_retrieved_chunks(
|
| 464 |
chunks=retrieved,
|
| 465 |
intent=resolved_intent,
|
|
|
|
| 475 |
reply = f"{reply}\n\nRelevant study notes:\n" + "\n".join(_teaching_lines(selected_chunks))
|
| 476 |
|
| 477 |
if not result.solved and self.generator is not None:
|
| 478 |
+
generated = _generate_fallback(
|
| 479 |
+
generator=self.generator,
|
| 480 |
+
user_text=user_text or solver_input,
|
| 481 |
+
intent=resolved_intent,
|
| 482 |
+
topic=result.topic,
|
| 483 |
+
chat_history=chat_history or [],
|
| 484 |
+
)
|
| 485 |
+
if generated:
|
| 486 |
+
reply = generated
|
| 487 |
+
result.used_generator = True
|
|
|
|
|
|
|
| 488 |
|
| 489 |
reply = format_reply(
|
| 490 |
text=reply,
|