gateway: auto-handle unresolved user questions in chat context
This commit is contained in:
@@ -171,23 +171,59 @@ def _extract_unanswered_user_messages(
|
||||
if not isinstance(events, list) or not current_user_id:
|
||||
return []
|
||||
|
||||
pending: List[str] = []
|
||||
def _normalize_tokens(raw: str) -> set:
|
||||
toks = re.findall(r"[a-zA-Zа-яА-ЯіїєґІЇЄҐ0-9]{3,}", (raw or "").lower())
|
||||
stop = {
|
||||
"що", "як", "коли", "де", "хто", "чому", "який", "яка", "яке", "скільки", "чи",
|
||||
"what", "how", "when", "where", "who", "why", "which",
|
||||
"and", "for", "the", "this", "that", "with", "from",
|
||||
}
|
||||
return {t for t in toks if t not in stop}
|
||||
|
||||
def _looks_like_ack_or_generic(raw: str) -> bool:
|
||||
t = (raw or "").strip().lower()
|
||||
if not t:
|
||||
return True
|
||||
markers = [
|
||||
"привіт", "вітаю", "чим можу допомогти", "ок", "добре", "дякую", "готово",
|
||||
"hello", "hi", "how can i help", "thanks", "okay", "done",
|
||||
]
|
||||
return any(m in t for m in markers) and len(t) < 180
|
||||
|
||||
def _assistant_resolves_question(question_text: str, assistant_text: str) -> bool:
|
||||
if _looks_like_ack_or_generic(assistant_text):
|
||||
return False
|
||||
q_tokens = _normalize_tokens(question_text)
|
||||
a_tokens = _normalize_tokens(assistant_text)
|
||||
if not q_tokens or not a_tokens:
|
||||
return False
|
||||
overlap = len(q_tokens.intersection(a_tokens))
|
||||
# Require at least partial semantic overlap, otherwise do not auto-close.
|
||||
return overlap >= 2 or (overlap >= 1 and len(q_tokens) <= 3)
|
||||
|
||||
pending: List[Dict[str, str]] = []
|
||||
for ev in events:
|
||||
role = str(ev.get("role") or ev.get("type") or "").lower()
|
||||
text = str(ev.get("body_text") or "").strip()
|
||||
if not text:
|
||||
continue
|
||||
if role == "user" and str(ev.get("user_id") or "") == current_user_id:
|
||||
pending.append(text)
|
||||
if role == "user" and str(ev.get("user_id") or "") == current_user_id and _is_question_like(text):
|
||||
pending.append({"text": text})
|
||||
continue
|
||||
if role in ("assistant", "agent") and pending:
|
||||
# Assume latest agent reply resolved the oldest pending user question.
|
||||
pending.pop(0)
|
||||
# Resolve only matching question; do not auto-close all pending items.
|
||||
resolved_idx = None
|
||||
for idx, item in enumerate(pending):
|
||||
if _assistant_resolves_question(item["text"], text):
|
||||
resolved_idx = idx
|
||||
break
|
||||
if resolved_idx is not None:
|
||||
pending.pop(resolved_idx)
|
||||
|
||||
# Keep the latest unresolved items only.
|
||||
if len(pending) > max_items:
|
||||
pending = pending[-max_items:]
|
||||
return pending
|
||||
return [p["text"] for p in pending]
|
||||
|
||||
|
||||
def _is_question_like(text: str) -> bool:
|
||||
@@ -2831,14 +2867,20 @@ async def handle_telegram_webhook(
|
||||
current_user_id=f"tg:{user_id}",
|
||||
max_items=3,
|
||||
)
|
||||
unresolved_non_current: List[str] = []
|
||||
unresolved_block = ""
|
||||
if unresolved_questions:
|
||||
# Do not duplicate current prompt if it matches one pending message.
|
||||
filtered = [q for q in unresolved_questions if q.strip() != (text or "").strip()]
|
||||
if filtered:
|
||||
unresolved_block = "[Невідповідані питання цього користувача]\n" + "\n".join(
|
||||
f"- {q}" for q in filtered
|
||||
) + "\n\n"
|
||||
unresolved_non_current = [q for q in unresolved_questions if q.strip() != (text or "").strip()]
|
||||
if unresolved_non_current:
|
||||
unresolved_block = (
|
||||
"[КРИТИЧНО: є невідповідані питання цього користувача. "
|
||||
"Спочатку коротко відповідай на них, потім на поточне повідомлення. "
|
||||
"Не змінюй тему і не ігноруй pending-питання.]\n"
|
||||
"[Невідповідані питання цього користувача]\n"
|
||||
+ "\n".join(f"- {q}" for q in unresolved_non_current)
|
||||
+ "\n\n"
|
||||
)
|
||||
|
||||
if local_history:
|
||||
# Add conversation history to message for better context understanding
|
||||
@@ -2897,6 +2939,8 @@ async def handle_telegram_webhook(
|
||||
"preferred_response_language": preferred_lang,
|
||||
"preferred_response_language_label": preferred_lang_label,
|
||||
"response_style_preference": response_style_pref,
|
||||
"has_unresolved_questions": bool(unresolved_non_current),
|
||||
"unresolved_questions_count": len(unresolved_non_current),
|
||||
},
|
||||
"context": {
|
||||
"agent_name": agent_config.name,
|
||||
@@ -2937,6 +2981,13 @@ async def handle_telegram_webhook(
|
||||
+ f"\n\n(Мова відповіді: {preferred_lang_label}.)"
|
||||
+ "\n(Не потрібно щоразу представлятися по імені або писати шаблонне: 'чим можу допомогти'.)"
|
||||
)
|
||||
|
||||
if unresolved_non_current:
|
||||
router_request["message"] = (
|
||||
router_request["message"]
|
||||
+ "\n\n(Пріоритет відповіді: 1) закрий невідповідані питання користувача; "
|
||||
"2) дай відповідь на поточне повідомлення. Якщо питання пов'язані, дай одну узгоджену відповідь.)"
|
||||
)
|
||||
|
||||
# Send to Router
|
||||
logger.info(f"Sending to Router: agent={agent_config.agent_id}, dao={dao_id}, user=tg:{user_id}")
|
||||
|
||||
Reference in New Issue
Block a user