srilakshu012456 commited on
Commit
5eddee2
·
verified ·
1 Parent(s): b2d44cf

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +68 -20
main.py CHANGED
@@ -13,6 +13,8 @@ from fastapi.middleware.cors import CORSMiddleware
13
  from pydantic import BaseModel
14
  from dotenv import load_dotenv
15
  from datetime import datetime
 
 
16
  from services.kb_creation import ACTION_SYNONYMS, MODULE_VOCAB
17
  from services.kb_creation import (
18
  collection,
@@ -144,6 +146,20 @@ ERROR_FAMILY_SYNS = {
144
  ),
145
  }
146
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
  def _detect_error_families(msg: str) -> list:
149
  """Return matching error family names found in the message (generic across SOPs)."""
@@ -936,23 +952,24 @@ async def chat_with_ai(input_data: ChatInput):
936
  "pre-requirements", "requirements"
937
  )
938
  if detected_intent == "neutral" and any(h in sec_title for h in PREREQ_HEADINGS):
939
- detected_intent = "prereqs"
940
- # Make steps nudges module-aware (appointments, picking, shipping, etc.)
 
941
  STEPS_TERMS = ("how to", "procedure", "perform", "steps", "do", "navigate")
942
- ACTIONS_PRESENT = any(s in msg_low for syns in ACTION_SYNONYMS.values() for s in syns)
943
  mod_tags = ((top_meta or {}).get("module_tags") or "").lower()
944
- sec_title = ((top_meta or {}).get("section") or "").strip().lower()
945
- # Flatten vocabulary terms for module detection (appointments, picking, shipping, inventory...)
946
  MODULE_TOKENS = tuple({term for syns in MODULE_VOCAB.values() for term in syns})
947
  looks_like_module = (
948
- any(t in msg_low for t in MODULE_TOKENS)
949
- or any(m in mod_tags for m in ["appointments", "receiving", "picking", "putaway", "shipping", "inventory", "replenishment"])
950
- or any(m in sec_title for m in ["appointment", "appointments", "schedule", "dock", "door", "picking", "putaway", "shipping", "inventory"])
951
- )
952
  looks_like_steps_query = any(t in msg_low for t in STEPS_TERMS) or ACTIONS_PRESENT
953
 
954
  if detected_intent in ("neutral", "prereqs") and looks_like_steps_query and looks_like_module:
955
- detected_intent = "steps"
 
956
  # --- Meaning-aware SOP gating ---
957
  def _contains_any(s: str, keywords: tuple) -> bool:
958
  low = (s or "").lower()
@@ -984,7 +1001,6 @@ async def chat_with_ai(input_data: ChatInput):
984
  weak_domain_only = (mentions_domain and not has_any_action_or_error)
985
  low_context_hit = (matched_count < 2 and filter_mode in ("concise", "exact"))
986
 
987
- # Bypass gate when strong steps signals are present for Receiving module
988
  strong_steps_bypass = looks_like_steps_query and looks_like_module
989
  strong_error_signal = len(_detect_error_families(msg_low)) > 0
990
  if (weak_domain_only or (low_context_hit and not combined_ok)) \
@@ -1023,6 +1039,26 @@ async def chat_with_ai(input_data: ChatInput):
1023
  next_step_applied = False
1024
  next_step_info: Dict[str, Any] = {}
1025
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1026
  if best_doc:
1027
  if detected_intent == "steps":
1028
  full_steps = get_best_steps_section_text(best_doc)
@@ -1032,6 +1068,10 @@ async def chat_with_ai(input_data: ChatInput):
1032
  full_steps = get_section_text(best_doc, sec)
1033
 
1034
  if full_steps:
 
 
 
 
1035
  # Use numbered form only for matching; keep raw for full output
1036
  numbered_full = _ensure_numbering(full_steps)
1037
  next_only = _resolve_next_steps(input_data.user_message, numbered_full, max_next=6, min_score=0.35)
@@ -1049,8 +1089,7 @@ async def chat_with_ai(input_data: ChatInput):
1049
  next_step_info = {"count": len(next_only)}
1050
  context_preformatted = True
1051
  else:
1052
- # Normal mode: return the full SOP section (raw),
1053
- # and we'll number it below once.
1054
  context = full_steps
1055
  context_preformatted = False
1056
 
@@ -1075,23 +1114,32 @@ async def chat_with_ai(input_data: ChatInput):
1075
  )
1076
  escalation_line = _extract_escalation_line(full_errors)
1077
  else:
1078
- # Fallback when Errors section is missing: show steps
1079
- full_steps = get_best_steps_section_text(best_doc) or get_section_text(best_doc, sec_title or "")
1080
- if full_steps:
 
 
 
 
1081
  context = full_steps
1082
  detected_intent = "steps"
1083
  context_preformatted = False
 
1084
  elif detected_intent == "prereqs":
1085
  full_prereqs = _find_prereq_section_text(best_doc)
1086
  if full_prereqs:
1087
  context = full_prereqs.strip()
1088
  else:
1089
- # Fallback when Prereqs section is missing: show steps
1090
  full_steps = get_best_steps_section_text(best_doc) or get_section_text(best_doc, sec_title or "")
1091
  if full_steps:
1092
- context = full_steps
1093
- detected_intent = "steps"
1094
- context_preformatted = False
 
 
 
 
1095
  language_hint = _detect_language_hint(input_data.user_message)
1096
  lang_line = f"Respond in {language_hint}." if language_hint else "Respond in a clear, polite tone."
1097
  use_gemini = (detected_intent == "errors")
 
13
  from pydantic import BaseModel
14
  from dotenv import load_dotenv
15
  from datetime import datetime
16
+
17
+ # Import shared vocab from KB services
18
  from services.kb_creation import ACTION_SYNONYMS, MODULE_VOCAB
19
  from services.kb_creation import (
20
  collection,
 
146
  ),
147
  }
148
 
149
+ # ----- local extension so runtime filtering is precise even without re-ingest -----
150
+ # (Does NOT override your KB synonyms—just augments them at runtime.)
151
+ ACTION_SYNONYMS_EXT: Dict[str, List[str]] = {}
152
+ for k, v in ACTION_SYNONYMS.items():
153
+ ACTION_SYNONYMS_EXT[k] = list(v) # copy
154
+
155
+ # Extend with SOP phrasing (appointments often say 'updation', 'deletion', 'reschedule')
156
+ ACTION_SYNONYMS_EXT.setdefault("create", []).extend(["appointment creation", "create appointment"])
157
+ ACTION_SYNONYMS_EXT.setdefault("update", []).extend([
158
+ "updation", "reschedule", "change time", "change date", "change slot",
159
+ "update time", "update date", "update slot", "update appointment", "edit appointment"
160
+ ])
161
+ ACTION_SYNONYMS_EXT.setdefault("delete", []).extend(["deletion", "delete appointment", "cancel appointment"])
162
+
163
 
164
  def _detect_error_families(msg: str) -> list:
165
  """Return matching error family names found in the message (generic across SOPs)."""
 
952
  "pre-requirements", "requirements"
953
  )
954
  if detected_intent == "neutral" and any(h in sec_title for h in PREREQ_HEADINGS):
955
+ detected_intent = "prereqs"
956
+
957
+ # --- Module-aware steps nudge (appointments, picking, shipping, etc.) ---
958
  STEPS_TERMS = ("how to", "procedure", "perform", "steps", "do", "navigate")
959
+ ACTIONS_PRESENT = any(s in msg_low for syns in ACTION_SYNONYMS_EXT.values() for s in syns)
960
  mod_tags = ((top_meta or {}).get("module_tags") or "").lower()
961
+
 
962
  MODULE_TOKENS = tuple({term for syns in MODULE_VOCAB.values() for term in syns})
963
  looks_like_module = (
964
+ any(t in msg_low for t in MODULE_TOKENS)
965
+ or any(m in mod_tags for m in ["appointments", "receiving", "picking", "putaway", "shipping", "inventory", "replenishment"])
966
+ or any(m in sec_title for m in ["appointment", "appointments", "schedule", "dock", "door", "picking", "putaway", "shipping", "inventory"])
967
+ )
968
  looks_like_steps_query = any(t in msg_low for t in STEPS_TERMS) or ACTIONS_PRESENT
969
 
970
  if detected_intent in ("neutral", "prereqs") and looks_like_steps_query and looks_like_module:
971
+ detected_intent = "steps"
972
+
973
  # --- Meaning-aware SOP gating ---
974
  def _contains_any(s: str, keywords: tuple) -> bool:
975
  low = (s or "").lower()
 
1001
  weak_domain_only = (mentions_domain and not has_any_action_or_error)
1002
  low_context_hit = (matched_count < 2 and filter_mode in ("concise", "exact"))
1003
 
 
1004
  strong_steps_bypass = looks_like_steps_query and looks_like_module
1005
  strong_error_signal = len(_detect_error_families(msg_low)) > 0
1006
  if (weak_domain_only or (low_context_hit and not combined_ok)) \
 
1039
  next_step_applied = False
1040
  next_step_info: Dict[str, Any] = {}
1041
 
1042
+ # Helper: detect asked action from query using extended synonyms
1043
+ def _detect_action_from_query(q: str) -> Optional[str]:
1044
+ qlow = (q or "").lower()
1045
+ for act, syns in ACTION_SYNONYMS_EXT.items():
1046
+ if any(s in qlow for s in syns):
1047
+ return act
1048
+ return None
1049
+
1050
+ # Helper: filter lines in a steps context to keep only the asked action
1051
+ def _filter_steps_by_action(raw_context: str, asked_act: Optional[str]) -> str:
1052
+ if not asked_act or not raw_context.strip():
1053
+ return raw_context
1054
+ other_terms: List[str] = []
1055
+ for act, syns in ACTION_SYNONYMS_EXT.items():
1056
+ if act != asked_act:
1057
+ other_terms.extend(syns)
1058
+ lines = _normalize_lines(raw_context)
1059
+ kept = [ln for ln in lines if not any(t in ln.lower() for t in other_terms)]
1060
+ return "\n".join(kept).strip() if kept else raw_context
1061
+
1062
  if best_doc:
1063
  if detected_intent == "steps":
1064
  full_steps = get_best_steps_section_text(best_doc)
 
1068
  full_steps = get_section_text(best_doc, sec)
1069
 
1070
  if full_steps:
1071
+ # Apply action-focused filtering FIRST (to avoid mixing create/update/delete)
1072
+ asked_action = _detect_action_from_query(input_data.user_message)
1073
+ full_steps = _filter_steps_by_action(full_steps, asked_action)
1074
+
1075
  # Use numbered form only for matching; keep raw for full output
1076
  numbered_full = _ensure_numbering(full_steps)
1077
  next_only = _resolve_next_steps(input_data.user_message, numbered_full, max_next=6, min_score=0.35)
 
1089
  next_step_info = {"count": len(next_only)}
1090
  context_preformatted = True
1091
  else:
1092
+ # Normal mode: return the full SOP section (raw), and we'll number it below once.
 
1093
  context = full_steps
1094
  context_preformatted = False
1095
 
 
1114
  )
1115
  escalation_line = _extract_escalation_line(full_errors)
1116
  else:
1117
+ # Fallback when Errors section is missing: show steps
1118
+ full_steps = get_best_steps_section_text(best_doc) or get_section_text(best_doc, sec_title or "")
1119
+ if full_steps:
1120
+ # Apply action filter to keep responses tight
1121
+ asked_action = _detect_action_from_query(input_data.user_message)
1122
+ full_steps = _filter_steps_by_action(full_steps, asked_action)
1123
+
1124
  context = full_steps
1125
  detected_intent = "steps"
1126
  context_preformatted = False
1127
+
1128
  elif detected_intent == "prereqs":
1129
  full_prereqs = _find_prereq_section_text(best_doc)
1130
  if full_prereqs:
1131
  context = full_prereqs.strip()
1132
  else:
1133
+ # Fallback when Prereqs section is missing: show steps
1134
  full_steps = get_best_steps_section_text(best_doc) or get_section_text(best_doc, sec_title or "")
1135
  if full_steps:
1136
+ asked_action = _detect_action_from_query(input_data.user_message)
1137
+ full_steps = _filter_steps_by_action(full_steps, asked_action)
1138
+
1139
+ context = full_steps
1140
+ detected_intent = "steps"
1141
+ context_preformatted = False
1142
+
1143
  language_hint = _detect_language_hint(input_data.user_message)
1144
  lang_line = f"Respond in {language_hint}." if language_hint else "Respond in a clear, polite tone."
1145
  use_gemini = (detected_intent == "errors")