Spaces:
Sleeping
Sleeping
Update main.py
Browse files
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 |
-
|
|
|
|
|
|
|
| 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",
|
| 1014 |
"close", "closing", "open", "navigate", "scan", "confirm", "generate", "update",
|
| 1015 |
-
"receive", "receiving",
|
| 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 |
-
# ----------
|
| 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 |
-
|
| 1172 |
-
|
| 1173 |
-
|
| 1174 |
-
|
| 1175 |
-
|
| 1176 |
-
|
| 1177 |
-
|
| 1178 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1179 |
full_steps = get_best_steps_section_text(best_doc)
|
| 1180 |
-
|
| 1181 |
-
|
| 1182 |
-
|
| 1183 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 1189 |
-
|
| 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 |
-
|
| 1316 |
-
|
| 1317 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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": {
|