Spaces:
Sleeping
Sleeping
Update main.py
Browse files
main.py
CHANGED
|
@@ -43,6 +43,12 @@ GEMINI_URL = (
|
|
| 43 |
)
|
| 44 |
os.environ["POSTHOG_DISABLED"] = "true"
|
| 45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
def safe_str(e: Any) -> str:
|
| 48 |
try:
|
|
@@ -535,9 +541,6 @@ def _build_clarifying_message() -> str:
|
|
| 535 |
"or should I raise a ServiceNow ticket for you?")
|
| 536 |
|
| 537 |
|
| 538 |
-
|
| 539 |
-
|
| 540 |
-
|
| 541 |
def _build_tracking_descriptions(issue_text: str, resolved_text: str) -> Tuple[str, str]:
|
| 542 |
"""
|
| 543 |
ShortDescription must always be the original user query / process heading / error text.
|
|
@@ -547,7 +550,7 @@ def _build_tracking_descriptions(issue_text: str, resolved_text: str) -> Tuple[s
|
|
| 547 |
resolved = (resolved_text or "").strip()
|
| 548 |
|
| 549 |
# Strict: only the original issue/query goes into ShortDescription
|
| 550 |
-
short_desc = issue[:100] # If empty, stays empty—
|
| 551 |
|
| 552 |
# DetailedDescription documents both the original issue and the user's resolution confirmation
|
| 553 |
long_desc = (
|
|
@@ -559,7 +562,6 @@ def _build_tracking_descriptions(issue_text: str, resolved_text: str) -> Tuple[s
|
|
| 559 |
return short_desc, long_desc
|
| 560 |
|
| 561 |
|
| 562 |
-
|
| 563 |
def _is_incident_intent(msg_norm: str) -> bool:
|
| 564 |
intent_phrases = [
|
| 565 |
"create ticket", "create a ticket", "raise ticket", "raise a ticket", "open ticket", "open a ticket",
|
|
@@ -662,7 +664,15 @@ async def chat_with_ai(input_data: ChatInput):
|
|
| 662 |
is_llm_resolved = False
|
| 663 |
if (not _has_negation_resolved(msg_norm)) and (_is_resolution_ack_heuristic(msg_norm) or is_llm_resolved):
|
| 664 |
try:
|
| 665 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 666 |
result = create_incident(short_desc, long_desc)
|
| 667 |
if isinstance(result, dict) and not result.get("error"):
|
| 668 |
inc_number = result.get("number", "<unknown>")
|
|
@@ -902,6 +912,7 @@ async def chat_with_ai(input_data: ChatInput):
|
|
| 902 |
next_step_applied = False
|
| 903 |
next_step_info: Dict[str, Any] = {}
|
| 904 |
context_preformatted = False
|
|
|
|
| 905 |
|
| 906 |
if best_doc and detected_intent == "steps":
|
| 907 |
# prefer exact section of the top hit; fallback to all steps
|
|
@@ -976,10 +987,8 @@ async def chat_with_ai(input_data: ChatInput):
|
|
| 976 |
filt_info = {'mode': None, 'matched_count': None, 'all_sentences': None}
|
| 977 |
context_found = True
|
| 978 |
|
| 979 |
-
|
| 980 |
-
|
| 981 |
elif best_doc and detected_intent == "errors":
|
| 982 |
-
# ---
|
| 983 |
said_not_resolved = (
|
| 984 |
_has_negation_resolved(msg_norm) or
|
| 985 |
bool(re.search(
|
|
@@ -990,9 +999,9 @@ async def chat_with_ai(input_data: ChatInput):
|
|
| 990 |
)
|
| 991 |
|
| 992 |
if said_not_resolved:
|
| 993 |
-
#
|
| 994 |
return {
|
| 995 |
-
"bot_response": "
|
| 996 |
"status": "OK",
|
| 997 |
"context_found": False,
|
| 998 |
"ask_resolved": False,
|
|
@@ -1005,19 +1014,17 @@ async def chat_with_ai(input_data: ChatInput):
|
|
| 1005 |
"debug": {
|
| 1006 |
"intent": "errors_not_resolved",
|
| 1007 |
"best_doc": best_doc,
|
| 1008 |
-
|
| 1009 |
}
|
| 1010 |
|
| 1011 |
# Build errors context
|
| 1012 |
full_errors = get_best_errors_section_text(best_doc)
|
| 1013 |
-
steps_override_applied = False
|
| 1014 |
if full_errors:
|
| 1015 |
ctx_err = _extract_errors_only(full_errors, max_lines=30)
|
| 1016 |
|
| 1017 |
# If it's a permissions query, trim to permission lines
|
| 1018 |
if is_perm_query:
|
| 1019 |
context = _filter_permission_lines(ctx_err, max_lines=6)
|
| 1020 |
-
|
| 1021 |
else:
|
| 1022 |
# Treat domain-only messages (e.g., "putaway error") as specific queries
|
| 1023 |
# so we filter errors to the most relevant lines instead of dumping entire heading.
|
|
@@ -1075,7 +1082,6 @@ async def chat_with_ai(input_data: ChatInput):
|
|
| 1075 |
# Non-fatal; keep errors context
|
| 1076 |
pass
|
| 1077 |
|
| 1078 |
-
|
| 1079 |
elif best_doc and detected_intent == "prereqs":
|
| 1080 |
full_prereqs = _find_prereq_section_text(best_doc)
|
| 1081 |
if full_prereqs:
|
|
@@ -1159,6 +1165,28 @@ Return ONLY the rewritten guidance."""
|
|
| 1159 |
|
| 1160 |
options = [{"type": "yesno", "title": "Share details or raise a ticket?"}] if status == "PARTIAL" else []
|
| 1161 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1162 |
return {
|
| 1163 |
"bot_response": bot_text,
|
| 1164 |
"status": status,
|
|
@@ -1282,7 +1310,7 @@ def _classify_resolution_llm(user_message: str) -> bool:
|
|
| 1282 |
Return only 'true' or 'false'.
|
| 1283 |
Message: {user_message}"""
|
| 1284 |
headers = {"Content-Type": "application/json"}
|
| 1285 |
-
payload = {"contents": [{"parts": [{"text": prompt}]}]}
|
| 1286 |
try:
|
| 1287 |
resp = requests.post(GEMINI_URL, headers=headers, json=payload, timeout=12, verify=GEMINI_SSL_VERIFY)
|
| 1288 |
data = resp.json()
|
|
|
|
| 43 |
)
|
| 44 |
os.environ["POSTHOG_DISABLED"] = "true"
|
| 45 |
|
| 46 |
+
# ---------------------------------------------------------------------
|
| 47 |
+
# Minimal server-side cache to remember last issue text shown to user
|
| 48 |
+
# (Used when user says "issue resolved" but frontend doesn't send last_issue)
|
| 49 |
+
# ---------------------------------------------------------------------
|
| 50 |
+
LAST_ISSUE_HINT: str = ""
|
| 51 |
+
|
| 52 |
|
| 53 |
def safe_str(e: Any) -> str:
|
| 54 |
try:
|
|
|
|
| 541 |
"or should I raise a ServiceNow ticket for you?")
|
| 542 |
|
| 543 |
|
|
|
|
|
|
|
|
|
|
| 544 |
def _build_tracking_descriptions(issue_text: str, resolved_text: str) -> Tuple[str, str]:
|
| 545 |
"""
|
| 546 |
ShortDescription must always be the original user query / process heading / error text.
|
|
|
|
| 550 |
resolved = (resolved_text or "").strip()
|
| 551 |
|
| 552 |
# Strict: only the original issue/query goes into ShortDescription
|
| 553 |
+
short_desc = issue[:100] # If empty, stays empty—backend now tries to fill from cache
|
| 554 |
|
| 555 |
# DetailedDescription documents both the original issue and the user's resolution confirmation
|
| 556 |
long_desc = (
|
|
|
|
| 562 |
return short_desc, long_desc
|
| 563 |
|
| 564 |
|
|
|
|
| 565 |
def _is_incident_intent(msg_norm: str) -> bool:
|
| 566 |
intent_phrases = [
|
| 567 |
"create ticket", "create a ticket", "raise ticket", "raise a ticket", "open ticket", "open a ticket",
|
|
|
|
| 664 |
is_llm_resolved = False
|
| 665 |
if (not _has_negation_resolved(msg_norm)) and (_is_resolution_ack_heuristic(msg_norm) or is_llm_resolved):
|
| 666 |
try:
|
| 667 |
+
# Prefer server-side cached hint if frontend didn't pass last_issue
|
| 668 |
+
issue_hint = (input_data.last_issue or "").strip()
|
| 669 |
+
if not issue_hint:
|
| 670 |
+
try:
|
| 671 |
+
issue_hint = LAST_ISSUE_HINT.strip()
|
| 672 |
+
except Exception:
|
| 673 |
+
issue_hint = ""
|
| 674 |
+
|
| 675 |
+
short_desc, long_desc = _build_tracking_descriptions(issue_hint, input_data.user_message)
|
| 676 |
result = create_incident(short_desc, long_desc)
|
| 677 |
if isinstance(result, dict) and not result.get("error"):
|
| 678 |
inc_number = result.get("number", "<unknown>")
|
|
|
|
| 912 |
next_step_applied = False
|
| 913 |
next_step_info: Dict[str, Any] = {}
|
| 914 |
context_preformatted = False
|
| 915 |
+
steps_override_applied = False # for Gemini paraphrase gating
|
| 916 |
|
| 917 |
if best_doc and detected_intent == "steps":
|
| 918 |
# prefer exact section of the top hit; fallback to all steps
|
|
|
|
| 987 |
filt_info = {'mode': None, 'matched_count': None, 'all_sentences': None}
|
| 988 |
context_found = True
|
| 989 |
|
|
|
|
|
|
|
| 990 |
elif best_doc and detected_intent == "errors":
|
| 991 |
+
# --- Detect explicit "not resolved" phrases ---
|
| 992 |
said_not_resolved = (
|
| 993 |
_has_negation_resolved(msg_norm) or
|
| 994 |
bool(re.search(
|
|
|
|
| 999 |
)
|
| 1000 |
|
| 1001 |
if said_not_resolved:
|
| 1002 |
+
# Keep only the card; avoid duplicate text or dots in bubble.
|
| 1003 |
return {
|
| 1004 |
+
"bot_response": "Select an option below.", # short, non-duplicated helper line
|
| 1005 |
"status": "OK",
|
| 1006 |
"context_found": False,
|
| 1007 |
"ask_resolved": False,
|
|
|
|
| 1014 |
"debug": {
|
| 1015 |
"intent": "errors_not_resolved",
|
| 1016 |
"best_doc": best_doc,
|
| 1017 |
+
},
|
| 1018 |
}
|
| 1019 |
|
| 1020 |
# Build errors context
|
| 1021 |
full_errors = get_best_errors_section_text(best_doc)
|
|
|
|
| 1022 |
if full_errors:
|
| 1023 |
ctx_err = _extract_errors_only(full_errors, max_lines=30)
|
| 1024 |
|
| 1025 |
# If it's a permissions query, trim to permission lines
|
| 1026 |
if is_perm_query:
|
| 1027 |
context = _filter_permission_lines(ctx_err, max_lines=6)
|
|
|
|
| 1028 |
else:
|
| 1029 |
# Treat domain-only messages (e.g., "putaway error") as specific queries
|
| 1030 |
# so we filter errors to the most relevant lines instead of dumping entire heading.
|
|
|
|
| 1082 |
# Non-fatal; keep errors context
|
| 1083 |
pass
|
| 1084 |
|
|
|
|
| 1085 |
elif best_doc and detected_intent == "prereqs":
|
| 1086 |
full_prereqs = _find_prereq_section_text(best_doc)
|
| 1087 |
if full_prereqs:
|
|
|
|
| 1165 |
|
| 1166 |
options = [{"type": "yesno", "title": "Share details or raise a ticket?"}] if status == "PARTIAL" else []
|
| 1167 |
|
| 1168 |
+
# ----- Cache last issue hint (used if user later says "issue resolved thanks")
|
| 1169 |
+
try:
|
| 1170 |
+
global LAST_ISSUE_HINT
|
| 1171 |
+
if detected_intent == "steps":
|
| 1172 |
+
# Prefer section heading if present; else user's query
|
| 1173 |
+
section_heading = ((top_meta or {}).get("section") or "").strip()
|
| 1174 |
+
LAST_ISSUE_HINT = (section_heading or input_data.user_message or "").strip()[:100]
|
| 1175 |
+
elif detected_intent == "errors":
|
| 1176 |
+
shown_lines = [ln.strip() for ln in (bot_text or "").splitlines() if ln.strip()]
|
| 1177 |
+
top_error_line = ""
|
| 1178 |
+
for ln in shown_lines:
|
| 1179 |
+
if re.match(r"^\s*[\-\*\u2022]\s*", ln) or ":" in ln:
|
| 1180 |
+
top_error_line = ln
|
| 1181 |
+
break
|
| 1182 |
+
base = (input_data.user_message or "").strip()
|
| 1183 |
+
if top_error_line:
|
| 1184 |
+
LAST_ISSUE_HINT = f"{base} — {top_error_line}".strip()[:100]
|
| 1185 |
+
else:
|
| 1186 |
+
LAST_ISSUE_HINT = base[:100]
|
| 1187 |
+
except Exception:
|
| 1188 |
+
pass
|
| 1189 |
+
|
| 1190 |
return {
|
| 1191 |
"bot_response": bot_text,
|
| 1192 |
"status": status,
|
|
|
|
| 1310 |
Return only 'true' or 'false'.
|
| 1311 |
Message: {user_message}"""
|
| 1312 |
headers = {"Content-Type": "application/json"}
|
| 1313 |
+
payload = {"contents": [{"parts": [{"text": prompt}]}]} # noqa: E501
|
| 1314 |
try:
|
| 1315 |
resp = requests.post(GEMINI_URL, headers=headers, json=payload, timeout=12, verify=GEMINI_SSL_VERIFY)
|
| 1316 |
data = resp.json()
|