refine handling of unknown questions by improving user follow-up prompts and notification logic
Browse files- implementation/chat.py +67 -19
implementation/chat.py
CHANGED
|
@@ -119,9 +119,9 @@ Keep replies concise.
|
|
| 119 |
|
| 120 |
UNKNOWN_SYSTEM_PROMPT = """
|
| 121 |
You are acting as Kharisma Rizki Wijanarko (Rizki)'s assistant.
|
| 122 |
-
If you cannot answer confidently, say you don't know
|
| 123 |
-
Then
|
| 124 |
-
|
| 125 |
Keep replies concise.
|
| 126 |
"""
|
| 127 |
|
|
@@ -286,13 +286,47 @@ def record_unknown_question(question: str, user_details: str | None = None) -> s
|
|
| 286 |
if user_details:
|
| 287 |
entry["user_details"] = user_details
|
| 288 |
_append_to_json(UNKNOWN_QUESTIONS_PATH, entry)
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
details_line = f"User Details: {user_details}" if user_details else "User Details: (not provided)"
|
| 292 |
-
send_telegram_notification(f"New Unknown Question: {question}\n{details_line}")
|
| 293 |
return "Question recorded successfully."
|
| 294 |
|
| 295 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 296 |
def _extract_name_email(history: list[dict]) -> tuple[str | None, str | None]:
|
| 297 |
"""
|
| 298 |
Best-effort extraction of user's name/email from conversation text.
|
|
@@ -503,6 +537,31 @@ def answer_question(question: str, history: list[dict] = []) -> tuple[str, list[
|
|
| 503 |
Handles multi-turn tool calls in a loop until the model produces a final text response.
|
| 504 |
"""
|
| 505 |
t0 = time.time()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 506 |
action = route_action(question, history)
|
| 507 |
logger.info(f"[ROUTER] action={action!r}")
|
| 508 |
|
|
@@ -523,18 +582,7 @@ def answer_question(question: str, history: list[dict] = []) -> tuple[str, list[
|
|
| 523 |
return answer, []
|
| 524 |
|
| 525 |
if action == "unknown":
|
| 526 |
-
|
| 527 |
-
user_details_parts = []
|
| 528 |
-
if name:
|
| 529 |
-
user_details_parts.append(f"Name: {name}")
|
| 530 |
-
if email:
|
| 531 |
-
user_details_parts.append(f"Email: {email}")
|
| 532 |
-
user_details = "\n".join(user_details_parts) if user_details_parts else None
|
| 533 |
-
|
| 534 |
-
# Deterministic: always record + notify so Rizki can follow up.
|
| 535 |
-
record_unknown_question(question, user_details=user_details)
|
| 536 |
-
|
| 537 |
-
# Respond to the user (no need for tools here).
|
| 538 |
response = completion(
|
| 539 |
model=MODEL,
|
| 540 |
messages=[{"role": "system", "content": UNKNOWN_SYSTEM_PROMPT}] + history + [
|
|
|
|
| 119 |
|
| 120 |
UNKNOWN_SYSTEM_PROMPT = """
|
| 121 |
You are acting as Kharisma Rizki Wijanarko (Rizki)'s assistant.
|
| 122 |
+
If you cannot answer confidently, say you don't know.
|
| 123 |
+
Then ask the user to share their name and email so Rizki can follow up directly.
|
| 124 |
+
Do not claim you already notified Rizki until the user provides their details.
|
| 125 |
Keep replies concise.
|
| 126 |
"""
|
| 127 |
|
|
|
|
| 286 |
if user_details:
|
| 287 |
entry["user_details"] = user_details
|
| 288 |
_append_to_json(UNKNOWN_QUESTIONS_PATH, entry)
|
| 289 |
+
if user_details:
|
| 290 |
+
send_telegram_notification(f"New Unknown Question: {question}\nUser Details: {user_details}")
|
|
|
|
|
|
|
| 291 |
return "Question recorded successfully."
|
| 292 |
|
| 293 |
|
| 294 |
+
def _looks_like_unknown_followup_request(history: list[dict]) -> bool:
|
| 295 |
+
"""
|
| 296 |
+
Detect whether the assistant previously asked for name+email due to an unknown question.
|
| 297 |
+
We keep this heuristic intentionally simple and robust to minor prompt changes.
|
| 298 |
+
"""
|
| 299 |
+
last_assistant = next((m for m in reversed(history) if m.get("role") == "assistant"), None)
|
| 300 |
+
if not last_assistant:
|
| 301 |
+
return False
|
| 302 |
+
text = str(last_assistant.get("content", "")).lower()
|
| 303 |
+
return (
|
| 304 |
+
("i don't know" in text or "i do not know" in text)
|
| 305 |
+
and ("name" in text)
|
| 306 |
+
and ("email" in text)
|
| 307 |
+
and ("follow up" in text or "follow-up" in text or "follow up directly" in text)
|
| 308 |
+
)
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
def _find_previous_user_question_before_followup(history: list[dict]) -> str | None:
|
| 312 |
+
"""
|
| 313 |
+
When the assistant asks for name/email after an unknown question, the unknown question is
|
| 314 |
+
typically the latest user message before that assistant response.
|
| 315 |
+
"""
|
| 316 |
+
assistant_idx = None
|
| 317 |
+
for i in range(len(history) - 1, -1, -1):
|
| 318 |
+
if history[i].get("role") == "assistant":
|
| 319 |
+
assistant_idx = i
|
| 320 |
+
break
|
| 321 |
+
if assistant_idx is None:
|
| 322 |
+
return None
|
| 323 |
+
for j in range(assistant_idx - 1, -1, -1):
|
| 324 |
+
if history[j].get("role") == "user":
|
| 325 |
+
q = str(history[j].get("content", "")).strip()
|
| 326 |
+
return q or None
|
| 327 |
+
return None
|
| 328 |
+
|
| 329 |
+
|
| 330 |
def _extract_name_email(history: list[dict]) -> tuple[str | None, str | None]:
|
| 331 |
"""
|
| 332 |
Best-effort extraction of user's name/email from conversation text.
|
|
|
|
| 537 |
Handles multi-turn tool calls in a loop until the model produces a final text response.
|
| 538 |
"""
|
| 539 |
t0 = time.time()
|
| 540 |
+
|
| 541 |
+
# If we previously couldn't answer and asked for name/email, treat the next user message
|
| 542 |
+
# as the follow-up details and send Telegram once we have an email.
|
| 543 |
+
if _looks_like_unknown_followup_request(history):
|
| 544 |
+
prev_unknown_q = _find_previous_user_question_before_followup(history)
|
| 545 |
+
name, email = _extract_name_email(history + [{"role": "user", "content": question}])
|
| 546 |
+
if not email:
|
| 547 |
+
return (
|
| 548 |
+
"Thanks — could you share your email address as well? (And your name, if you haven’t already.)",
|
| 549 |
+
[],
|
| 550 |
+
)
|
| 551 |
+
|
| 552 |
+
details_lines = []
|
| 553 |
+
if name:
|
| 554 |
+
details_lines.append(f"Name: {name}")
|
| 555 |
+
details_lines.append(f"Email: {email}")
|
| 556 |
+
if question.strip():
|
| 557 |
+
details_lines.append(f"Follow-up: {question.strip()}")
|
| 558 |
+
|
| 559 |
+
record_unknown_question(prev_unknown_q or "(unknown question not found in history)", user_details="\n".join(details_lines))
|
| 560 |
+
return (
|
| 561 |
+
"Thanks — I’ve shared your details with Rizki so he can follow up.",
|
| 562 |
+
[],
|
| 563 |
+
)
|
| 564 |
+
|
| 565 |
action = route_action(question, history)
|
| 566 |
logger.info(f"[ROUTER] action={action!r}")
|
| 567 |
|
|
|
|
| 582 |
return answer, []
|
| 583 |
|
| 584 |
if action == "unknown":
|
| 585 |
+
# Respond to the user (ask for name + email so Rizki can follow up).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 586 |
response = completion(
|
| 587 |
model=MODEL,
|
| 588 |
messages=[{"role": "system", "content": UNKNOWN_SYSTEM_PROMPT}] + history + [
|