srilakshu012456 commited on
Commit
2e68e1d
·
verified ·
1 Parent(s): 1af13e2

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +161 -90
main.py CHANGED
@@ -1,6 +1,4 @@
1
 
2
- # main_hugging_phase_recent.py
3
-
4
  import os
5
  import json
6
  import re
@@ -201,6 +199,52 @@ def _get_steps_for_action(best_doc: str, actions: list) -> Optional[str]:
201
  return txt.strip()
202
  return None
203
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  def _is_domain_status_context(msg_norm: str) -> bool:
205
  if "status locked" in msg_norm or "locked status" in msg_norm:
206
  return True
@@ -943,8 +987,12 @@ async def chat_with_ai(input_data: ChatInput):
943
 
944
  selected = items[:max(1, 2)]
945
  context_raw = "\n\n---\n\n".join([s["text"] for s in selected]) if selected else ""
 
 
946
  filtered_text, filt_info = _filter_context_for_query(context_raw, input_data.user_message)
947
- context = filtered_text
 
 
948
  context_found = bool(context.strip())
949
  best_distance = min([d for d in distances if d is not None], default=None) if distances else None
950
  best_combined = max([c for c in combined if c is not None], default=None) if combined else None
@@ -998,7 +1046,7 @@ async def chat_with_ai(input_data: ChatInput):
998
  if detected_intent in ("neutral", "prereqs") and looks_like_steps_query and looks_like_module:
999
  detected_intent = "steps"
1000
 
1001
- # --- Meaning-aware SOP gating ---
1002
  def _contains_any(s: str, keywords: tuple) -> bool:
1003
  low = (s or "").lower()
1004
  return any(k in low for k in keywords)
@@ -1010,9 +1058,9 @@ async def chat_with_ai(input_data: ChatInput):
1010
  "asn", "grn", "pick", "picking"
1011
  )
1012
  ACTION_OR_ERROR_TERMS = (
1013
- "how to", "procedure", "perform", # added
1014
  "close", "closing", "open", "navigate", "scan", "confirm", "generate", "update",
1015
- "receive", "receiving", # added
1016
  "error", "issue", "fail", "failed", "not working", "locked", "mismatch",
1017
  "access", "permission", "status"
1018
  )
@@ -1058,10 +1106,7 @@ async def chat_with_ai(input_data: ChatInput):
1058
  },
1059
  }
1060
 
1061
- # ---------- Action-focused extraction + boilerplate cleanup ----------
1062
- MONTH_TERMS = ("january", "february", "march", "april", "may", "june",
1063
- "july", "august", "september", "october", "november", "december")
1064
-
1065
  def _detect_action_from_query(q: str) -> Optional[str]:
1066
  qlow = (q or "").lower()
1067
  for act, syns in ACTION_SYNONYMS_EXT.items():
@@ -1071,6 +1116,8 @@ async def chat_with_ai(input_data: ChatInput):
1071
 
1072
  def _strip_boilerplate(raw_context: str) -> str:
1073
  """Remove document title/date/author/change-history noise from steps."""
 
 
1074
  lines = _normalize_lines(raw_context)
1075
  cleaned: List[str] = []
1076
  for ln in lines:
@@ -1158,91 +1205,110 @@ async def chat_with_ai(input_data: ChatInput):
1158
  block = "\n".join(lines).strip() if lines else block
1159
  return block
1160
 
1161
- # Build SOP context if allowed
1162
- if is_perm_query:
1163
- detected_intent = "errors"
1164
-
1165
  escalation_line = None
1166
  full_errors = None
1167
  next_step_applied = False
1168
  next_step_info: Dict[str, Any] = {}
1169
 
1170
- if best_doc:
1171
- if detected_intent == "steps":
1172
- action_steps = _get_steps_for_action(best_doc, kb_results.get("actions", []))
1173
- if action_steps:
1174
- full_steps = action_steps
1175
- context_preformatted = False # we will number it below
1176
-
1177
- else:
1178
- # existing fallback to the best steps bundle or the top section
 
 
 
 
 
 
1179
  full_steps = get_best_steps_section_text(best_doc)
1180
- if not full_steps:
1181
- sec = (top_meta or {}).get("section")
1182
- if sec:
1183
- full_steps = get_section_text(best_doc, sec)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1184
  if full_steps:
1185
  asked_action = _detect_action_from_query(input_data.user_message)
1186
  full_steps = _filter_steps_by_action(full_steps, asked_action)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1187
 
1188
- numbered_full = _ensure_numbering(full_steps)
1189
- next_only = _resolve_next_steps(input_data.user_message, numbered_full, max_next=6, min_score=0.35)
1190
-
1191
- if next_only is not None:
1192
- if len(next_only) == 0:
1193
- context = "You are at the final step of this SOP. No further steps."
1194
- next_step_applied = True
1195
- next_step_info = {"count": 0}
1196
- context_preformatted = True
1197
- else:
1198
- context = _format_steps_as_numbered(next_only)
1199
- next_step_applied = True
1200
- next_step_info = {"count": len(next_only)}
1201
- context_preformatted = True
1202
- else:
1203
- context = full_steps
1204
- context_preformatted = False
1205
-
1206
- elif detected_intent == "errors":
1207
- full_errors = get_best_errors_section_text(best_doc)
1208
- if full_errors:
1209
- ctx_err = _extract_errors_only(full_errors, max_lines=30)
1210
- if is_perm_query:
1211
- context = _filter_permission_lines(ctx_err, max_lines=6)
1212
- else:
1213
- is_specific_error = len(_detect_error_families(msg_low)) > 0
1214
- if is_specific_error:
1215
- context = _filter_error_lines_by_query(ctx_err, input_data.user_message, max_lines=1)
1216
- else:
1217
- all_lines: List[str] = _normalize_lines(ctx_err)
1218
- error_bullets = [ln for ln in all_lines if re.match(r"^\s*[-*\u2022]\s*", ln) or (":" in ln)]
1219
- context = "\n".join(error_bullets[:8]).strip()
1220
- assist_followup = (
1221
- "Please tell me which error above matches your screen (paste the exact text), "
1222
- "or share a screenshot. I can guide you further or raise a ServiceNow ticket."
1223
- )
1224
- escalation_line = _extract_escalation_line(full_errors)
1225
- else:
1226
- full_steps = get_best_steps_section_text(best_doc) or get_section_text(best_doc, sec_title or "")
1227
- if full_steps:
1228
- asked_action = _detect_action_from_query(input_data.user_message)
1229
- full_steps = _filter_steps_by_action(full_steps, asked_action)
1230
- context = full_steps
1231
- detected_intent = "steps"
1232
- context_preformatted = False
1233
-
1234
- elif detected_intent == "prereqs":
1235
- full_prereqs = _find_prereq_section_text(best_doc)
1236
- if full_prereqs:
1237
- context = full_prereqs.strip()
1238
- else:
1239
- full_steps = get_best_steps_section_text(best_doc) or get_section_text(best_doc, sec_title or "")
1240
- if full_steps:
1241
- asked_action = _detect_action_from_query(input_data.user_message)
1242
- full_steps = _filter_steps_by_action(full_steps, asked_action)
1243
- context = full_steps
1244
- detected_intent = "steps"
1245
- context_preformatted = False
1246
 
1247
  language_hint = _detect_language_hint(input_data.user_message)
1248
  lang_line = f"Respond in {language_hint}." if language_hint else "Respond in a clear, polite tone."
@@ -1312,9 +1378,14 @@ Return ONLY the rewritten guidance."""
1312
  "Share a bit more detail (module/screen/error), or say ‘create ticket’."
1313
  )
1314
 
1315
- short_query = len((input_data.user_message or "").split()) <= 4
1316
- gate_combined_ok = 0.60 if short_query else 0.55
1317
- status = "OK" if (best_combined is not None and best_combined >= gate_combined_ok) else "PARTIAL"
 
 
 
 
 
1318
  lower = (bot_text or "").lower()
1319
  if ("partial" in lower) or ("may be partial" in lower) or ("closest" in lower) or ("may not fully" in lower):
1320
  status = "PARTIAL"
@@ -1335,8 +1406,8 @@ Return ONLY the rewritten guidance."""
1335
  "best_distance": best_distance,
1336
  "best_combined": best_combined,
1337
  "http_status": http_code,
1338
- "filter_mode": filt_info.get("mode"),
1339
- "matched_count": filt_info.get("matched_count"),
1340
  "user_intent": detected_intent,
1341
  "best_doc": best_doc,
1342
  "next_step": {
 
1
 
 
 
2
  import os
3
  import json
4
  import re
 
199
  return txt.strip()
200
  return None
201
 
202
+
203
+ # --- Default section picker when query doesn't reveal action ---
204
+ def _pick_default_action_section(best_doc: str) -> Optional[str]:
205
+ """
206
+ If user actions are empty, prefer '...Creation' section,
207
+ else prefer '...Updation'/'...Update', else '...Deletion'/'...Cancel'.
208
+ Works generically for SOPs that use common headings.
209
+ """
210
+ order = ("creation", "updation", "update", "deletion", "delete", "cancel")
211
+ sections = []
212
+ for d in bm25_docs:
213
+ m = d.get("meta", {})
214
+ if m.get("filename") == best_doc and m.get("intent_tag") == "steps":
215
+ title = (m.get("section") or "").strip().lower()
216
+ if title:
217
+ sections.append(title)
218
+ for key in order:
219
+ for t in sections:
220
+ if key in t:
221
+ return t
222
+ return sections[0] if sections else None
223
+
224
+
225
+ # --- Harvest 'Save' lines from ALL steps chunks in the doc (generic across SOPs) ---
226
+ SAVE_SYNS = ("save", "saving", "save changes", "click save", "press save", "select save")
227
+
228
+ def _find_save_lines_in_doc(best_doc: str, max_lines: int = 2) -> str:
229
+ """
230
+ Pulls up to max_lines lines that mention 'save' from any steps chunk in best_doc.
231
+ Returns a \n-joined string or empty if none found.
232
+ """
233
+ lines: List[str] = []
234
+ for d in bm25_docs:
235
+ m = d.get("meta", {})
236
+ if m.get("filename") != best_doc or m.get("intent_tag") != "steps":
237
+ continue
238
+ t = (d.get("text") or "").strip()
239
+ for ln in [x.strip() for x in t.splitlines() if x.strip()]:
240
+ low = ln.lower()
241
+ if any(s in low for s in SAVE_SYNS):
242
+ lines.append(ln)
243
+ if len(lines) >= max_lines:
244
+ return "\n".join(lines)
245
+ return "\n".join(lines)
246
+
247
+
248
  def _is_domain_status_context(msg_norm: str) -> bool:
249
  if "status locked" in msg_norm or "locked status" in msg_norm:
250
  return True
 
987
 
988
  selected = items[:max(1, 2)]
989
  context_raw = "\n\n---\n\n".join([s["text"] for s in selected]) if selected else ""
990
+
991
+ # Compute filter info for gating only; do NOT use the filtered text for steps
992
  filtered_text, filt_info = _filter_context_for_query(context_raw, input_data.user_message)
993
+ filtered_context = filtered_text
994
+
995
+ context = context_raw # keep raw; we'll decide below
996
  context_found = bool(context.strip())
997
  best_distance = min([d for d in distances if d is not None], default=None) if distances else None
998
  best_combined = max([c for c in combined if c is not None], default=None) if combined else None
 
1046
  if detected_intent in ("neutral", "prereqs") and looks_like_steps_query and looks_like_module:
1047
  detected_intent = "steps"
1048
 
1049
+ # --- Meaning-aware SOP gating (uses filter info) ---
1050
  def _contains_any(s: str, keywords: tuple) -> bool:
1051
  low = (s or "").lower()
1052
  return any(k in low for k in keywords)
 
1058
  "asn", "grn", "pick", "picking"
1059
  )
1060
  ACTION_OR_ERROR_TERMS = (
1061
+ "how to", "procedure", "perform",
1062
  "close", "closing", "open", "navigate", "scan", "confirm", "generate", "update",
1063
+ "receive", "receiving",
1064
  "error", "issue", "fail", "failed", "not working", "locked", "mismatch",
1065
  "access", "permission", "status"
1066
  )
 
1106
  },
1107
  }
1108
 
1109
+ # ---------- Build SOP context ----------
 
 
 
1110
  def _detect_action_from_query(q: str) -> Optional[str]:
1111
  qlow = (q or "").lower()
1112
  for act, syns in ACTION_SYNONYMS_EXT.items():
 
1116
 
1117
  def _strip_boilerplate(raw_context: str) -> str:
1118
  """Remove document title/date/author/change-history noise from steps."""
1119
+ MONTH_TERMS = ("january", "february", "march", "april", "may", "june",
1120
+ "july", "august", "september", "october", "november", "december")
1121
  lines = _normalize_lines(raw_context)
1122
  cleaned: List[str] = []
1123
  for ln in lines:
 
1205
  block = "\n".join(lines).strip() if lines else block
1206
  return block
1207
 
 
 
 
 
1208
  escalation_line = None
1209
  full_errors = None
1210
  next_step_applied = False
1211
  next_step_info: Dict[str, Any] = {}
1212
 
1213
+ if best_doc and detected_intent == "steps":
1214
+ context_preformatted = False
1215
+ full_steps = None
1216
+
1217
+ # 1) Try by KB action tags
1218
+ action_steps = _get_steps_for_action(best_doc, kb_results.get("actions", []))
1219
+ if action_steps:
1220
+ full_steps = action_steps
1221
+ else:
1222
+ # 2) If no user action, pick a sensible default section (creation > update > deletion)
1223
+ default_sec = _pick_default_action_section(best_doc)
1224
+ if default_sec:
1225
+ full_steps = get_section_text(best_doc, default_sec)
1226
+ # 3) Last resort: aggregated steps for the doc
1227
+ if not full_steps:
1228
  full_steps = get_best_steps_section_text(best_doc)
1229
+ # 4) Final fallback: top hit section
1230
+ if not full_steps:
1231
+ sec = (top_meta or {}).get("section")
1232
+ if sec:
1233
+ full_steps = get_section_text(best_doc, sec)
1234
+
1235
+ if full_steps:
1236
+ # Always add Save lines if present anywhere in the doc (independent of query wording)
1237
+ save_lines = _find_save_lines_in_doc(best_doc, max_lines=2)
1238
+ if save_lines:
1239
+ low_steps = (full_steps or "").lower()
1240
+ if not any(s in low_steps for s in SAVE_SYNS):
1241
+ full_steps = (full_steps or "").rstrip() + "\n" + save_lines
1242
+
1243
+ asked_action = _detect_action_from_query(input_data.user_message)
1244
+ full_steps = _filter_steps_by_action(full_steps, asked_action)
1245
+
1246
+ numbered_full = _ensure_numbering(full_steps)
1247
+ next_only = _resolve_next_steps(input_data.user_message, numbered_full, max_next=6, min_score=0.35)
1248
+
1249
+ if next_only is not None:
1250
+ if len(next_only) == 0:
1251
+ context = "You are at the final step of this SOP. No further steps."
1252
+ next_step_applied = True
1253
+ next_step_info = {"count": 0}
1254
+ context_preformatted = True
1255
+ else:
1256
+ context = _format_steps_as_numbered(next_only)
1257
+ next_step_applied = True
1258
+ next_step_info = {"count": len(next_only)}
1259
+ context_preformatted = True
1260
+ else:
1261
+ context = full_steps
1262
+ context_preformatted = False
1263
+
1264
+ # Clear filter info for debug clarity
1265
+ filt_info = {'mode': None, 'matched_count': None, 'all_sentences': None}
1266
+ context_found = True
1267
+
1268
+ elif best_doc and detected_intent == "errors":
1269
+ full_errors = get_best_errors_section_text(best_doc)
1270
+ if full_errors:
1271
+ ctx_err = _extract_errors_only(full_errors, max_lines=30)
1272
+ if is_perm_query:
1273
+ context = _filter_permission_lines(ctx_err, max_lines=6)
1274
+ else:
1275
+ is_specific_error = len(_detect_error_families(msg_low)) > 0
1276
+ if is_specific_error:
1277
+ context = _filter_error_lines_by_query(ctx_err, input_data.user_message, max_lines=1)
1278
+ else:
1279
+ all_lines: List[str] = _normalize_lines(ctx_err)
1280
+ error_bullets = [ln for ln in all_lines if re.match(r"^\s*[-*\u2022]\s*", ln) or (":" in ln)]
1281
+ context = "\n".join(error_bullets[:8]).strip()
1282
+ assist_followup = (
1283
+ "Please tell me which error above matches your screen (paste the exact text), "
1284
+ "or share a screenshot. I can guide you further or raise a ServiceNow ticket."
1285
+ )
1286
+ escalation_line = _extract_escalation_line(full_errors)
1287
+ else:
1288
+ full_steps = get_best_steps_section_text(best_doc) or get_section_text(best_doc, sec_title or "")
1289
  if full_steps:
1290
  asked_action = _detect_action_from_query(input_data.user_message)
1291
  full_steps = _filter_steps_by_action(full_steps, asked_action)
1292
+ context = full_steps
1293
+ detected_intent = "steps"
1294
+ context_preformatted = False
1295
+
1296
+ elif best_doc and detected_intent == "prereqs":
1297
+ full_prereqs = _find_prereq_section_text(best_doc)
1298
+ if full_prereqs:
1299
+ context = full_prereqs.strip()
1300
+ else:
1301
+ full_steps = get_best_steps_section_text(best_doc) or get_section_text(best_doc, sec_title or "")
1302
+ if full_steps:
1303
+ asked_action = _detect_action_from_query(input_data.user_message)
1304
+ full_steps = _filter_steps_by_action(full_steps, asked_action)
1305
+ context = full_steps
1306
+ detected_intent = "steps"
1307
+ context_preformatted = False
1308
 
1309
+ else:
1310
+ # Neutral or other intents: use filtered context
1311
+ context = filtered_context
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1312
 
1313
  language_hint = _detect_language_hint(input_data.user_message)
1314
  lang_line = f"Respond in {language_hint}." if language_hint else "Respond in a clear, polite tone."
 
1378
  "Share a bit more detail (module/screen/error), or say ‘create ticket’."
1379
  )
1380
 
1381
+ # Status: mark OK when we served steps successfully
1382
+ if detected_intent == "steps" and bot_text.strip():
1383
+ status = "OK"
1384
+ else:
1385
+ short_query = len((input_data.user_message or "").split()) <= 4
1386
+ gate_combined_ok = 0.60 if short_query else 0.55
1387
+ status = "OK" if (best_combined is not None and best_combined >= gate_combined_ok) else "PARTIAL"
1388
+
1389
  lower = (bot_text or "").lower()
1390
  if ("partial" in lower) or ("may be partial" in lower) or ("closest" in lower) or ("may not fully" in lower):
1391
  status = "PARTIAL"
 
1406
  "best_distance": best_distance,
1407
  "best_combined": best_combined,
1408
  "http_status": http_code,
1409
+ "filter_mode": (filt_info.get("mode") if filt_info else None),
1410
+ "matched_count": (filt_info.get("matched_count") if filt_info else None),
1411
  "user_intent": detected_intent,
1412
  "best_doc": best_doc,
1413
  "next_step": {