Spaces:
Sleeping
Sleeping
Update main.py
Browse files
main.py
CHANGED
|
@@ -97,7 +97,10 @@ def _normalize_lines(text: str) -> List[str]:
|
|
| 97 |
return [ln.strip() for ln in (text or "").splitlines() if ln.strip()]
|
| 98 |
|
| 99 |
def _ensure_numbering(text: str) -> str:
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
| 101 |
if not lines:
|
| 102 |
return text or ""
|
| 103 |
normalized = []
|
|
@@ -108,7 +111,7 @@ def _ensure_numbering(text: str) -> str:
|
|
| 108 |
parts = [p.strip() for p in re.split(r"\.\s+(?=[A-Z0-9])", normalized[0]) if p.strip()]
|
| 109 |
if len(parts) > 1:
|
| 110 |
normalized = parts
|
| 111 |
-
return "\n".join([f"{i+1}
|
| 112 |
|
| 113 |
def _friendly_permission_reply(raw: str) -> str:
|
| 114 |
line = (raw or "").strip()
|
|
@@ -123,10 +126,9 @@ def _friendly_permission_reply(raw: str) -> str:
|
|
| 123 |
|
| 124 |
# ---------------- Language detection (Tamil/Hindi mirror) ----------------
|
| 125 |
def _detect_language_hint(msg: str) -> Optional[str]:
|
| 126 |
-
|
| 127 |
-
if re.search(r"[\u0B80-\u0BFF]", msg or ""):
|
| 128 |
return "Tamil"
|
| 129 |
-
if re.search(r"[\u0900-\u097F]", msg or ""):
|
| 130 |
return "Hindi"
|
| 131 |
return None
|
| 132 |
|
|
@@ -474,10 +476,8 @@ async def chat_with_ai(input_data: ChatInput):
|
|
| 474 |
score = distances[i] if i < len(distances) else None
|
| 475 |
comb = combined[i] if i < len(combined) else None
|
| 476 |
m = dict(meta)
|
| 477 |
-
if score is not None:
|
| 478 |
-
|
| 479 |
-
if comb is not None:
|
| 480 |
-
m["combined"] = comb
|
| 481 |
items.append({"text": text, "meta": m})
|
| 482 |
selected = items[:max(1, 2)]
|
| 483 |
context_raw = "\n\n---\n\n".join([s["text"] for s in selected]) if selected else ""
|
|
@@ -487,7 +487,6 @@ async def chat_with_ai(input_data: ChatInput):
|
|
| 487 |
best_distance = min([d for d in distances if d is not None], default=None) if distances else None
|
| 488 |
best_combined = max([c for c in combined if c is not None], default=None) if combined else None
|
| 489 |
detected_intent = kb_results.get("user_intent", "neutral")
|
| 490 |
-
actions = kb_results.get("actions", [])
|
| 491 |
best_doc = kb_results.get("best_doc")
|
| 492 |
top_meta = (metadatas or [{}])[0] if metadatas else {}
|
| 493 |
|
|
@@ -502,15 +501,13 @@ async def chat_with_ai(input_data: ChatInput):
|
|
| 502 |
full_steps = get_best_steps_section_text(best_doc)
|
| 503 |
if not full_steps:
|
| 504 |
sec = (top_meta or {}).get("section")
|
| 505 |
-
if sec:
|
| 506 |
-
full_steps = get_section_text(best_doc, sec)
|
| 507 |
if full_steps:
|
| 508 |
context = _ensure_numbering(full_steps)
|
| 509 |
elif detected_intent == "errors":
|
| 510 |
full_errors = get_best_errors_section_text(best_doc)
|
| 511 |
if full_errors:
|
| 512 |
context = _extract_errors_only(full_errors, max_lines=12)
|
| 513 |
-
# user-friendly sentence if it's only one line
|
| 514 |
lines = _normalize_lines(context)
|
| 515 |
if len(lines) == 1:
|
| 516 |
context = _friendly_permission_reply(lines[0])
|
|
@@ -536,7 +533,7 @@ async def chat_with_ai(input_data: ChatInput):
|
|
| 536 |
lang_line = f"Respond in {language_hint}." if language_hint else "Respond in a clear, polite tone."
|
| 537 |
enhanced_prompt = f"""You are a helpful support assistant. Rewrite the provided context ONLY into clear, user-friendly guidance.
|
| 538 |
- Do not add any information that is not present in the context.
|
| 539 |
-
- If the content describes a procedure or steps, format as a strictly numbered list: 1
|
| 540 |
- If the content is an error/access/permission note, paraphrase it into a helpful sentence users can understand.
|
| 541 |
- {lang_line}
|
| 542 |
|
|
@@ -565,8 +562,8 @@ Return ONLY the rewritten guidance."""
|
|
| 565 |
bot_text = ""
|
| 566 |
http_code = getattr(resp, "status_code", 0)
|
| 567 |
|
|
|
|
| 568 |
if not bot_text.strip() or http_code == 429:
|
| 569 |
-
# Fallback to local formatting
|
| 570 |
if detected_intent == "steps":
|
| 571 |
bot_text = _ensure_numbering(context)
|
| 572 |
elif detected_intent == "errors":
|
|
@@ -575,11 +572,11 @@ Return ONLY the rewritten guidance."""
|
|
| 575 |
else:
|
| 576 |
bot_text = context
|
| 577 |
|
| 578 |
-
#
|
| 579 |
if detected_intent == "steps":
|
| 580 |
bot_text = _ensure_numbering(bot_text)
|
| 581 |
|
| 582 |
-
#
|
| 583 |
short_query = len((input_data.user_message or "").split()) <= 4
|
| 584 |
gate_combined_ok = 0.60 if short_query else 0.55
|
| 585 |
status = "OK" if (best_combined is not None and best_combined >= gate_combined_ok) else "PARTIAL"
|
|
@@ -588,7 +585,6 @@ Return ONLY the rewritten guidance."""
|
|
| 588 |
if ("partial" in lower) or ("may be partial" in lower) or ("closest" in lower) or ("may not fully" in lower):
|
| 589 |
status = "PARTIAL"
|
| 590 |
|
| 591 |
-
# For PARTIAL: show Yes/No card
|
| 592 |
options = [{"type":"yesno","title":"Share details or raise a ticket?"}] if status == "PARTIAL" else []
|
| 593 |
|
| 594 |
return {
|
|
@@ -644,8 +640,7 @@ def _set_incident_resolved(sys_id: str) -> bool:
|
|
| 644 |
except Exception as e:
|
| 645 |
print(f"[SN PATCH progress] exception={safe_str(e)}")
|
| 646 |
|
| 647 |
-
def clean(d: dict) -> dict:
|
| 648 |
-
return {k: v for k, v in d.items() if v is not None}
|
| 649 |
|
| 650 |
payload_A = clean({
|
| 651 |
"state": "6",
|
|
@@ -658,8 +653,7 @@ def _set_incident_resolved(sys_id: str) -> bool:
|
|
| 658 |
"assignment_group": assign_group,
|
| 659 |
})
|
| 660 |
respA = requests.patch(url, headers=headers, json=payload_A, verify=VERIFY_SSL, timeout=25)
|
| 661 |
-
if respA.status_code in (200, 204):
|
| 662 |
-
return True
|
| 663 |
print(f"[SN PATCH resolve A] status={respA.status_code} body={respA.text[:500]}")
|
| 664 |
|
| 665 |
payload_B = clean({
|
|
@@ -673,8 +667,7 @@ def _set_incident_resolved(sys_id: str) -> bool:
|
|
| 673 |
"assignment_group": assign_group,
|
| 674 |
})
|
| 675 |
respB = requests.patch(url, headers=headers, json=payload_B, verify=VERIFY_SSL, timeout=25)
|
| 676 |
-
if respB.status_code in (200, 204):
|
| 677 |
-
return True
|
| 678 |
print(f"[SN PATCH resolve B] status={respB.status_code} body={respB.text[:500]}")
|
| 679 |
|
| 680 |
code_field = os.getenv("SERVICENOW_RESOLUTION_CODE_FIELD", "close_code")
|
|
@@ -690,8 +683,7 @@ def _set_incident_resolved(sys_id: str) -> bool:
|
|
| 690 |
"assignment_group": assign_group,
|
| 691 |
})
|
| 692 |
respC = requests.patch(url, headers=headers, json=payload_C, verify=VERIFY_SSL, timeout=25)
|
| 693 |
-
if respC.status_code in (200, 204):
|
| 694 |
-
return True
|
| 695 |
print(f"[SN PATCH resolve C] status={respC.status_code} body={respC.text[:500]}")
|
| 696 |
return False
|
| 697 |
except Exception as e:
|
|
|
|
| 97 |
return [ln.strip() for ln in (text or "").splitlines() if ln.strip()]
|
| 98 |
|
| 99 |
def _ensure_numbering(text: str) -> str:
|
| 100 |
+
"""
|
| 101 |
+
Force plain-text numbering: 1) 2) 3) so it renders in any bubble.
|
| 102 |
+
"""
|
| 103 |
+
lines = [ln.strip() for ln in (text or "").splitlines() if ln.strip()]
|
| 104 |
if not lines:
|
| 105 |
return text or ""
|
| 106 |
normalized = []
|
|
|
|
| 111 |
parts = [p.strip() for p in re.split(r"\.\s+(?=[A-Z0-9])", normalized[0]) if p.strip()]
|
| 112 |
if len(parts) > 1:
|
| 113 |
normalized = parts
|
| 114 |
+
return "\n".join([f"{i+1}) {ln}" for i, ln in enumerate(normalized)])
|
| 115 |
|
| 116 |
def _friendly_permission_reply(raw: str) -> str:
|
| 117 |
line = (raw or "").strip()
|
|
|
|
| 126 |
|
| 127 |
# ---------------- Language detection (Tamil/Hindi mirror) ----------------
|
| 128 |
def _detect_language_hint(msg: str) -> Optional[str]:
|
| 129 |
+
if re.search(r"[\u0B80-\u0BFF]", msg or ""): # Tamil
|
|
|
|
| 130 |
return "Tamil"
|
| 131 |
+
if re.search(r"[\u0900-\u097F]", msg or ""): # Hindi
|
| 132 |
return "Hindi"
|
| 133 |
return None
|
| 134 |
|
|
|
|
| 476 |
score = distances[i] if i < len(distances) else None
|
| 477 |
comb = combined[i] if i < len(combined) else None
|
| 478 |
m = dict(meta)
|
| 479 |
+
if score is not None: m["distance"] = score
|
| 480 |
+
if comb is not None: m["combined"] = comb
|
|
|
|
|
|
|
| 481 |
items.append({"text": text, "meta": m})
|
| 482 |
selected = items[:max(1, 2)]
|
| 483 |
context_raw = "\n\n---\n\n".join([s["text"] for s in selected]) if selected else ""
|
|
|
|
| 487 |
best_distance = min([d for d in distances if d is not None], default=None) if distances else None
|
| 488 |
best_combined = max([c for c in combined if c is not None], default=None) if combined else None
|
| 489 |
detected_intent = kb_results.get("user_intent", "neutral")
|
|
|
|
| 490 |
best_doc = kb_results.get("best_doc")
|
| 491 |
top_meta = (metadatas or [{}])[0] if metadatas else {}
|
| 492 |
|
|
|
|
| 501 |
full_steps = get_best_steps_section_text(best_doc)
|
| 502 |
if not full_steps:
|
| 503 |
sec = (top_meta or {}).get("section")
|
| 504 |
+
if sec: full_steps = get_section_text(best_doc, sec)
|
|
|
|
| 505 |
if full_steps:
|
| 506 |
context = _ensure_numbering(full_steps)
|
| 507 |
elif detected_intent == "errors":
|
| 508 |
full_errors = get_best_errors_section_text(best_doc)
|
| 509 |
if full_errors:
|
| 510 |
context = _extract_errors_only(full_errors, max_lines=12)
|
|
|
|
| 511 |
lines = _normalize_lines(context)
|
| 512 |
if len(lines) == 1:
|
| 513 |
context = _friendly_permission_reply(lines[0])
|
|
|
|
| 533 |
lang_line = f"Respond in {language_hint}." if language_hint else "Respond in a clear, polite tone."
|
| 534 |
enhanced_prompt = f"""You are a helpful support assistant. Rewrite the provided context ONLY into clear, user-friendly guidance.
|
| 535 |
- Do not add any information that is not present in the context.
|
| 536 |
+
- If the content describes a procedure or steps, format as a strictly numbered list using plain text: 1), 2), 3), ...
|
| 537 |
- If the content is an error/access/permission note, paraphrase it into a helpful sentence users can understand.
|
| 538 |
- {lang_line}
|
| 539 |
|
|
|
|
| 562 |
bot_text = ""
|
| 563 |
http_code = getattr(resp, "status_code", 0)
|
| 564 |
|
| 565 |
+
# Fallback to local formatting
|
| 566 |
if not bot_text.strip() or http_code == 429:
|
|
|
|
| 567 |
if detected_intent == "steps":
|
| 568 |
bot_text = _ensure_numbering(context)
|
| 569 |
elif detected_intent == "errors":
|
|
|
|
| 572 |
else:
|
| 573 |
bot_text = context
|
| 574 |
|
| 575 |
+
# Force numbering for steps
|
| 576 |
if detected_intent == "steps":
|
| 577 |
bot_text = _ensure_numbering(bot_text)
|
| 578 |
|
| 579 |
+
# Status compute
|
| 580 |
short_query = len((input_data.user_message or "").split()) <= 4
|
| 581 |
gate_combined_ok = 0.60 if short_query else 0.55
|
| 582 |
status = "OK" if (best_combined is not None and best_combined >= gate_combined_ok) else "PARTIAL"
|
|
|
|
| 585 |
if ("partial" in lower) or ("may be partial" in lower) or ("closest" in lower) or ("may not fully" in lower):
|
| 586 |
status = "PARTIAL"
|
| 587 |
|
|
|
|
| 588 |
options = [{"type":"yesno","title":"Share details or raise a ticket?"}] if status == "PARTIAL" else []
|
| 589 |
|
| 590 |
return {
|
|
|
|
| 640 |
except Exception as e:
|
| 641 |
print(f"[SN PATCH progress] exception={safe_str(e)}")
|
| 642 |
|
| 643 |
+
def clean(d: dict) -> dict: return {k: v for k, v in d.items() if v is not None}
|
|
|
|
| 644 |
|
| 645 |
payload_A = clean({
|
| 646 |
"state": "6",
|
|
|
|
| 653 |
"assignment_group": assign_group,
|
| 654 |
})
|
| 655 |
respA = requests.patch(url, headers=headers, json=payload_A, verify=VERIFY_SSL, timeout=25)
|
| 656 |
+
if respA.status_code in (200, 204): return True
|
|
|
|
| 657 |
print(f"[SN PATCH resolve A] status={respA.status_code} body={respA.text[:500]}")
|
| 658 |
|
| 659 |
payload_B = clean({
|
|
|
|
| 667 |
"assignment_group": assign_group,
|
| 668 |
})
|
| 669 |
respB = requests.patch(url, headers=headers, json=payload_B, verify=VERIFY_SSL, timeout=25)
|
| 670 |
+
if respB.status_code in (200, 204): return True
|
|
|
|
| 671 |
print(f"[SN PATCH resolve B] status={respB.status_code} body={respB.text[:500]}")
|
| 672 |
|
| 673 |
code_field = os.getenv("SERVICENOW_RESOLUTION_CODE_FIELD", "close_code")
|
|
|
|
| 683 |
"assignment_group": assign_group,
|
| 684 |
})
|
| 685 |
respC = requests.patch(url, headers=headers, json=payload_C, verify=VERIFY_SSL, timeout=25)
|
| 686 |
+
if respC.status_code in (200, 204): return True
|
|
|
|
| 687 |
print(f"[SN PATCH resolve C] status={respC.status_code} body={respC.text[:500]}")
|
| 688 |
return False
|
| 689 |
except Exception as e:
|