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:
|
if not isinstance(events, list) or not current_user_id:
|
||||||
return []
|
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:
|
for ev in events:
|
||||||
role = str(ev.get("role") or ev.get("type") or "").lower()
|
role = str(ev.get("role") or ev.get("type") or "").lower()
|
||||||
text = str(ev.get("body_text") or "").strip()
|
text = str(ev.get("body_text") or "").strip()
|
||||||
if not text:
|
if not text:
|
||||||
continue
|
continue
|
||||||
if role == "user" and str(ev.get("user_id") or "") == current_user_id:
|
if role == "user" and str(ev.get("user_id") or "") == current_user_id and _is_question_like(text):
|
||||||
pending.append(text)
|
pending.append({"text": text})
|
||||||
continue
|
continue
|
||||||
if role in ("assistant", "agent") and pending:
|
if role in ("assistant", "agent") and pending:
|
||||||
# Assume latest agent reply resolved the oldest pending user question.
|
# Resolve only matching question; do not auto-close all pending items.
|
||||||
pending.pop(0)
|
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.
|
# Keep the latest unresolved items only.
|
||||||
if len(pending) > max_items:
|
if len(pending) > max_items:
|
||||||
pending = pending[-max_items:]
|
pending = pending[-max_items:]
|
||||||
return pending
|
return [p["text"] for p in pending]
|
||||||
|
|
||||||
|
|
||||||
def _is_question_like(text: str) -> bool:
|
def _is_question_like(text: str) -> bool:
|
||||||
@@ -2831,14 +2867,20 @@ async def handle_telegram_webhook(
|
|||||||
current_user_id=f"tg:{user_id}",
|
current_user_id=f"tg:{user_id}",
|
||||||
max_items=3,
|
max_items=3,
|
||||||
)
|
)
|
||||||
|
unresolved_non_current: List[str] = []
|
||||||
unresolved_block = ""
|
unresolved_block = ""
|
||||||
if unresolved_questions:
|
if unresolved_questions:
|
||||||
# Do not duplicate current prompt if it matches one pending message.
|
# Do not duplicate current prompt if it matches one pending message.
|
||||||
filtered = [q for q in unresolved_questions if q.strip() != (text or "").strip()]
|
unresolved_non_current = [q for q in unresolved_questions if q.strip() != (text or "").strip()]
|
||||||
if filtered:
|
if unresolved_non_current:
|
||||||
unresolved_block = "[Невідповідані питання цього користувача]\n" + "\n".join(
|
unresolved_block = (
|
||||||
f"- {q}" for q in filtered
|
"[КРИТИЧНО: є невідповідані питання цього користувача. "
|
||||||
) + "\n\n"
|
"Спочатку коротко відповідай на них, потім на поточне повідомлення. "
|
||||||
|
"Не змінюй тему і не ігноруй pending-питання.]\n"
|
||||||
|
"[Невідповідані питання цього користувача]\n"
|
||||||
|
+ "\n".join(f"- {q}" for q in unresolved_non_current)
|
||||||
|
+ "\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
if local_history:
|
if local_history:
|
||||||
# Add conversation history to message for better context understanding
|
# 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": preferred_lang,
|
||||||
"preferred_response_language_label": preferred_lang_label,
|
"preferred_response_language_label": preferred_lang_label,
|
||||||
"response_style_preference": response_style_pref,
|
"response_style_preference": response_style_pref,
|
||||||
|
"has_unresolved_questions": bool(unresolved_non_current),
|
||||||
|
"unresolved_questions_count": len(unresolved_non_current),
|
||||||
},
|
},
|
||||||
"context": {
|
"context": {
|
||||||
"agent_name": agent_config.name,
|
"agent_name": agent_config.name,
|
||||||
@@ -2937,6 +2981,13 @@ async def handle_telegram_webhook(
|
|||||||
+ f"\n\n(Мова відповіді: {preferred_lang_label}.)"
|
+ f"\n\n(Мова відповіді: {preferred_lang_label}.)"
|
||||||
+ "\n(Не потрібно щоразу представлятися по імені або писати шаблонне: 'чим можу допомогти'.)"
|
+ "\n(Не потрібно щоразу представлятися по імені або писати шаблонне: 'чим можу допомогти'.)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if unresolved_non_current:
|
||||||
|
router_request["message"] = (
|
||||||
|
router_request["message"]
|
||||||
|
+ "\n\n(Пріоритет відповіді: 1) закрий невідповідані питання користувача; "
|
||||||
|
"2) дай відповідь на поточне повідомлення. Якщо питання пов'язані, дай одну узгоджену відповідь.)"
|
||||||
|
)
|
||||||
|
|
||||||
# Send to Router
|
# Send to Router
|
||||||
logger.info(f"Sending to Router: agent={agent_config.agent_id}, dao={dao_id}, user=tg:{user_id}")
|
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