Spaces:
Sleeping
Sleeping
Update main.py
Browse files
main.py
CHANGED
|
@@ -694,7 +694,7 @@ def _set_incident_resolved(sys_id: str) -> bool:
|
|
| 694 |
notes_field = os.getenv("SERVICENOW_RESOLUTION_NOTES_FIELD", "close_notes")
|
| 695 |
payload_C = clean({
|
| 696 |
"state": "6",
|
| 697 |
-
code_field:
|
| 698 |
notes_field: close_notes_val,
|
| 699 |
"caller_id": caller_sysid,
|
| 700 |
"resolved_at": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"),
|
|
@@ -1030,8 +1030,9 @@ async def chat_with_ai(input_data: ChatInput):
|
|
| 1030 |
},
|
| 1031 |
}
|
| 1032 |
|
| 1033 |
-
# ---------- Action-focused extraction + boilerplate cleanup
|
| 1034 |
-
MONTH_TERMS = ("january","february","march","april","may","june",
|
|
|
|
| 1035 |
|
| 1036 |
def _detect_action_from_query(q: str) -> Optional[str]:
|
| 1037 |
qlow = (q or "").lower()
|
|
@@ -1049,7 +1050,6 @@ async def chat_with_ai(input_data: ChatInput):
|
|
| 1049 |
is_change_hist = ("change history" in low) or ("initial draft" in low) or ("review" in low) or ("version" in low)
|
| 1050 |
has_month_year = any(m in low for m in MONTH_TERMS) and bool(re.search(r"\b20\d{2}\b", low))
|
| 1051 |
is_title_line = ("sop document" in low) or ("contents" in low)
|
| 1052 |
-
# Drop lines that look like doc header/footer metadata
|
| 1053 |
if is_change_hist or has_month_year or is_title_line:
|
| 1054 |
continue
|
| 1055 |
cleaned.append(ln)
|
|
@@ -1058,46 +1058,68 @@ async def chat_with_ai(input_data: ChatInput):
|
|
| 1058 |
def _extract_action_block(raw_context: str, target_act: Optional[str]) -> str:
|
| 1059 |
"""
|
| 1060 |
Extract the contiguous block of lines for the target action (create/update/delete).
|
| 1061 |
-
Start when a line mentions the target action OR looks
|
| 1062 |
-
stop when a line
|
|
|
|
|
|
|
| 1063 |
"""
|
| 1064 |
if not raw_context.strip():
|
| 1065 |
return raw_context
|
|
|
|
| 1066 |
lines = _normalize_lines(raw_context)
|
| 1067 |
-
if not target_act:
|
| 1068 |
return raw_context
|
| 1069 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1070 |
other_terms: List[str] = []
|
| 1071 |
for act, syns in ACTION_SYNONYMS_EXT.items():
|
| 1072 |
if act != target_act:
|
| 1073 |
other_terms.extend(syns)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1074 |
|
| 1075 |
started = False
|
| 1076 |
block: List[str] = []
|
|
|
|
| 1077 |
for ln in lines:
|
| 1078 |
low = ln.lower()
|
| 1079 |
-
contains_target = any(s in low for s in ACTION_SYNONYMS_EXT.get(target_act, []))
|
| 1080 |
-
contains_other = any(s in low for s in other_terms)
|
| 1081 |
|
| 1082 |
-
|
| 1083 |
-
|
| 1084 |
-
|
| 1085 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1086 |
|
| 1087 |
-
|
| 1088 |
-
if contains_other:
|
| 1089 |
-
break # end of target block
|
| 1090 |
-
block.append(ln)
|
| 1091 |
|
| 1092 |
-
# Fallback: if we never 'started', return original (minus boilerplate)
|
| 1093 |
return "\n".join(block).strip() if block else raw_context
|
| 1094 |
|
| 1095 |
def _filter_steps_by_action(raw_context: str, asked_act: Optional[str]) -> str:
|
| 1096 |
-
# 1) strip boilerplate (title/date/author/change-history)
|
| 1097 |
cleaned = _strip_boilerplate(raw_context)
|
| 1098 |
-
# 2) extract the contiguous action block
|
| 1099 |
block = _extract_action_block(cleaned, asked_act)
|
| 1100 |
-
# 3) as a final guard, remove any stray lines that mention other actions
|
| 1101 |
if asked_act:
|
| 1102 |
other_terms: List[str] = []
|
| 1103 |
for act, syns in ACTION_SYNONYMS_EXT.items():
|
|
@@ -1112,8 +1134,8 @@ async def chat_with_ai(input_data: ChatInput):
|
|
| 1112 |
if is_perm_query:
|
| 1113 |
detected_intent = "errors"
|
| 1114 |
|
| 1115 |
-
escalation_line = None
|
| 1116 |
-
full_errors = None
|
| 1117 |
next_step_applied = False
|
| 1118 |
next_step_info: Dict[str, Any] = {}
|
| 1119 |
|
|
@@ -1126,16 +1148,13 @@ async def chat_with_ai(input_data: ChatInput):
|
|
| 1126 |
full_steps = get_section_text(best_doc, sec)
|
| 1127 |
|
| 1128 |
if full_steps:
|
| 1129 |
-
# Apply action-focused extraction & cleanup FIRST
|
| 1130 |
asked_action = _detect_action_from_query(input_data.user_message)
|
| 1131 |
full_steps = _filter_steps_by_action(full_steps, asked_action)
|
| 1132 |
|
| 1133 |
-
# Use numbered form only for matching; keep raw for full output
|
| 1134 |
numbered_full = _ensure_numbering(full_steps)
|
| 1135 |
next_only = _resolve_next_steps(input_data.user_message, numbered_full, max_next=6, min_score=0.35)
|
| 1136 |
|
| 1137 |
if next_only is not None:
|
| 1138 |
-
# "what's next" mode
|
| 1139 |
if len(next_only) == 0:
|
| 1140 |
context = "You are at the final step of this SOP. No further steps."
|
| 1141 |
next_step_applied = True
|
|
@@ -1147,8 +1166,6 @@ async def chat_with_ai(input_data: ChatInput):
|
|
| 1147 |
next_step_info = {"count": len(next_only)}
|
| 1148 |
context_preformatted = True
|
| 1149 |
else:
|
| 1150 |
-
# Normal mode: return the full SOP section (raw),
|
| 1151 |
-
# and we'll number it below once.
|
| 1152 |
context = full_steps
|
| 1153 |
context_preformatted = False
|
| 1154 |
|
|
@@ -1159,7 +1176,6 @@ async def chat_with_ai(input_data: ChatInput):
|
|
| 1159 |
if is_perm_query:
|
| 1160 |
context = _filter_permission_lines(ctx_err, max_lines=6)
|
| 1161 |
else:
|
| 1162 |
-
# Decide specific vs generic:
|
| 1163 |
is_specific_error = len(_detect_error_families(msg_low)) > 0
|
| 1164 |
if is_specific_error:
|
| 1165 |
context = _filter_error_lines_by_query(ctx_err, input_data.user_message, max_lines=1)
|
|
@@ -1173,7 +1189,6 @@ async def chat_with_ai(input_data: ChatInput):
|
|
| 1173 |
)
|
| 1174 |
escalation_line = _extract_escalation_line(full_errors)
|
| 1175 |
else:
|
| 1176 |
-
# Fallback when Errors section is missing: show steps
|
| 1177 |
full_steps = get_best_steps_section_text(best_doc) or get_section_text(best_doc, sec_title or "")
|
| 1178 |
if full_steps:
|
| 1179 |
asked_action = _detect_action_from_query(input_data.user_message)
|
|
@@ -1187,7 +1202,6 @@ async def chat_with_ai(input_data: ChatInput):
|
|
| 1187 |
if full_prereqs:
|
| 1188 |
context = full_prereqs.strip()
|
| 1189 |
else:
|
| 1190 |
-
# Fallback when Prereqs section is missing: show steps
|
| 1191 |
full_steps = get_best_steps_section_text(best_doc) or get_section_text(best_doc, sec_title or "")
|
| 1192 |
if full_steps:
|
| 1193 |
asked_action = _detect_action_from_query(input_data.user_message)
|
|
@@ -1230,8 +1244,6 @@ Return ONLY the rewritten guidance."""
|
|
| 1230 |
|
| 1231 |
# Deterministic local formatting
|
| 1232 |
if detected_intent == "steps":
|
| 1233 |
-
# If we trimmed to next steps, 'context' is already formatted (or a sentence).
|
| 1234 |
-
# Only number when returning full SOP raw text.
|
| 1235 |
if ('context_preformatted' in locals()) and context_preformatted:
|
| 1236 |
bot_text = context
|
| 1237 |
else:
|
|
|
|
| 694 |
notes_field = os.getenv("SERVICENOW_RESOLUTION_NOTES_FIELD", "close_notes")
|
| 695 |
payload_C = clean({
|
| 696 |
"state": "6",
|
| 697 |
+
code_field: close_notes_val, # (if you have custom fields, adjust here)
|
| 698 |
notes_field: close_notes_val,
|
| 699 |
"caller_id": caller_sysid,
|
| 700 |
"resolved_at": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"),
|
|
|
|
| 1030 |
},
|
| 1031 |
}
|
| 1032 |
|
| 1033 |
+
# ---------- Action-focused extraction + boilerplate cleanup ----------
|
| 1034 |
+
MONTH_TERMS = ("january", "february", "march", "april", "may", "june",
|
| 1035 |
+
"july", "august", "september", "october", "november", "december")
|
| 1036 |
|
| 1037 |
def _detect_action_from_query(q: str) -> Optional[str]:
|
| 1038 |
qlow = (q or "").lower()
|
|
|
|
| 1050 |
is_change_hist = ("change history" in low) or ("initial draft" in low) or ("review" in low) or ("version" in low)
|
| 1051 |
has_month_year = any(m in low for m in MONTH_TERMS) and bool(re.search(r"\b20\d{2}\b", low))
|
| 1052 |
is_title_line = ("sop document" in low) or ("contents" in low)
|
|
|
|
| 1053 |
if is_change_hist or has_month_year or is_title_line:
|
| 1054 |
continue
|
| 1055 |
cleaned.append(ln)
|
|
|
|
| 1058 |
def _extract_action_block(raw_context: str, target_act: Optional[str]) -> str:
|
| 1059 |
"""
|
| 1060 |
Extract the contiguous block of lines for the target action (create/update/delete).
|
| 1061 |
+
Start when a line mentions the target action OR looks procedural for 'create',
|
| 1062 |
+
and stop ONLY when a line is a clear boundary:
|
| 1063 |
+
- inline heading for another topic (e.g., 'Appointment schedule updation', 'Appointment deletion'), OR
|
| 1064 |
+
- a line that strongly signals a different action (update/delete) via extended synonyms.
|
| 1065 |
"""
|
| 1066 |
if not raw_context.strip():
|
| 1067 |
return raw_context
|
| 1068 |
+
|
| 1069 |
lines = _normalize_lines(raw_context)
|
| 1070 |
+
if not lines or not target_act:
|
| 1071 |
return raw_context
|
| 1072 |
|
| 1073 |
+
INLINE_BOUNDARIES = (
|
| 1074 |
+
"appointment schedule updation",
|
| 1075 |
+
"schedule updation",
|
| 1076 |
+
"appointment deletion",
|
| 1077 |
+
"deletion",
|
| 1078 |
+
)
|
| 1079 |
+
|
| 1080 |
other_terms: List[str] = []
|
| 1081 |
for act, syns in ACTION_SYNONYMS_EXT.items():
|
| 1082 |
if act != target_act:
|
| 1083 |
other_terms.extend(syns)
|
| 1084 |
+
other_terms_low = set(t.lower() for t in other_terms)
|
| 1085 |
+
|
| 1086 |
+
def is_boundary_line(low: str) -> bool:
|
| 1087 |
+
if any(h in low for h in INLINE_BOUNDARIES):
|
| 1088 |
+
return True
|
| 1089 |
+
if any(t in low for t in other_terms_low):
|
| 1090 |
+
return True
|
| 1091 |
+
return False
|
| 1092 |
+
|
| 1093 |
+
PROCEDURAL_VERBS = ("select", "choose", "click", "open", "add", "assign", "save",
|
| 1094 |
+
"navigate", "tag", "displayed", "triggered")
|
| 1095 |
+
def is_procedural(low: str) -> bool:
|
| 1096 |
+
return any(v in low for v in PROCEDURAL_VERBS)
|
| 1097 |
+
|
| 1098 |
+
target_terms_low = set(t.lower() for t in ACTION_SYNONYMS_EXT.get(target_act, []))
|
| 1099 |
|
| 1100 |
started = False
|
| 1101 |
block: List[str] = []
|
| 1102 |
+
|
| 1103 |
for ln in lines:
|
| 1104 |
low = ln.lower()
|
|
|
|
|
|
|
| 1105 |
|
| 1106 |
+
contains_target = any(t in low for t in target_terms_low)
|
| 1107 |
+
if not started:
|
| 1108 |
+
if contains_target or (target_act == "create" and is_procedural(low)):
|
| 1109 |
+
started = True
|
| 1110 |
+
block.append(ln)
|
| 1111 |
+
continue
|
| 1112 |
+
|
| 1113 |
+
if is_boundary_line(low):
|
| 1114 |
+
break
|
| 1115 |
|
| 1116 |
+
block.append(ln)
|
|
|
|
|
|
|
|
|
|
| 1117 |
|
|
|
|
| 1118 |
return "\n".join(block).strip() if block else raw_context
|
| 1119 |
|
| 1120 |
def _filter_steps_by_action(raw_context: str, asked_act: Optional[str]) -> str:
|
|
|
|
| 1121 |
cleaned = _strip_boilerplate(raw_context)
|
|
|
|
| 1122 |
block = _extract_action_block(cleaned, asked_act)
|
|
|
|
| 1123 |
if asked_act:
|
| 1124 |
other_terms: List[str] = []
|
| 1125 |
for act, syns in ACTION_SYNONYMS_EXT.items():
|
|
|
|
| 1134 |
if is_perm_query:
|
| 1135 |
detected_intent = "errors"
|
| 1136 |
|
| 1137 |
+
escalation_line = None
|
| 1138 |
+
full_errors = None
|
| 1139 |
next_step_applied = False
|
| 1140 |
next_step_info: Dict[str, Any] = {}
|
| 1141 |
|
|
|
|
| 1148 |
full_steps = get_section_text(best_doc, sec)
|
| 1149 |
|
| 1150 |
if full_steps:
|
|
|
|
| 1151 |
asked_action = _detect_action_from_query(input_data.user_message)
|
| 1152 |
full_steps = _filter_steps_by_action(full_steps, asked_action)
|
| 1153 |
|
|
|
|
| 1154 |
numbered_full = _ensure_numbering(full_steps)
|
| 1155 |
next_only = _resolve_next_steps(input_data.user_message, numbered_full, max_next=6, min_score=0.35)
|
| 1156 |
|
| 1157 |
if next_only is not None:
|
|
|
|
| 1158 |
if len(next_only) == 0:
|
| 1159 |
context = "You are at the final step of this SOP. No further steps."
|
| 1160 |
next_step_applied = True
|
|
|
|
| 1166 |
next_step_info = {"count": len(next_only)}
|
| 1167 |
context_preformatted = True
|
| 1168 |
else:
|
|
|
|
|
|
|
| 1169 |
context = full_steps
|
| 1170 |
context_preformatted = False
|
| 1171 |
|
|
|
|
| 1176 |
if is_perm_query:
|
| 1177 |
context = _filter_permission_lines(ctx_err, max_lines=6)
|
| 1178 |
else:
|
|
|
|
| 1179 |
is_specific_error = len(_detect_error_families(msg_low)) > 0
|
| 1180 |
if is_specific_error:
|
| 1181 |
context = _filter_error_lines_by_query(ctx_err, input_data.user_message, max_lines=1)
|
|
|
|
| 1189 |
)
|
| 1190 |
escalation_line = _extract_escalation_line(full_errors)
|
| 1191 |
else:
|
|
|
|
| 1192 |
full_steps = get_best_steps_section_text(best_doc) or get_section_text(best_doc, sec_title or "")
|
| 1193 |
if full_steps:
|
| 1194 |
asked_action = _detect_action_from_query(input_data.user_message)
|
|
|
|
| 1202 |
if full_prereqs:
|
| 1203 |
context = full_prereqs.strip()
|
| 1204 |
else:
|
|
|
|
| 1205 |
full_steps = get_best_steps_section_text(best_doc) or get_section_text(best_doc, sec_title or "")
|
| 1206 |
if full_steps:
|
| 1207 |
asked_action = _detect_action_from_query(input_data.user_message)
|
|
|
|
| 1244 |
|
| 1245 |
# Deterministic local formatting
|
| 1246 |
if detected_intent == "steps":
|
|
|
|
|
|
|
| 1247 |
if ('context_preformatted' in locals()) and context_preformatted:
|
| 1248 |
bot_text = context
|
| 1249 |
else:
|