Chandima Prabhath commited on
Commit
7ac9a06
·
1 Parent(s): 6818a89

Refactor route_intent function for improved prompt clarity and intent handling; enhance error messaging and logging

Browse files
Files changed (1) hide show
  1. app.py +80 -30
app.py CHANGED
@@ -4,7 +4,7 @@ import requests
4
  import logging
5
  import queue
6
  import json
7
- from typing import List, Optional, Literal
8
  from collections import defaultdict, deque
9
  from concurrent.futures import ThreadPoolExecutor
10
 
@@ -356,6 +356,7 @@ class SendTextIntent(BaseIntent):
356
  action: Literal["send_text"]
357
  message: str
358
 
 
359
  INTENT_MODELS = [
360
  SummarizeIntent, TranslateIntent, JokeIntent, WeatherIntent,
361
  InspireIntent, MemeIntent, PollCreateIntent, PollVoteIntent,
@@ -382,50 +383,98 @@ ACTION_HANDLERS = {
382
  def route_intent(user_input: str, chat_id: str, sender: str):
383
  history_text = get_history_text(chat_id, sender)
384
  sys_prompt = (
385
- "You are Eve, a sweet, innocent, and helpful assistant.\n"
386
- "You never do work yourself—you only invoke functions.\n"
387
- "If the user’s request matches a function, return exactly one JSON object matching that signature—and nothing else.\n"
388
- "Do not wrap it in markdown or show it to the user.\n"
389
- "If no function matches, reply in plain text (no JSON, no internals).\n\n"
390
- "Functions:\n"
391
- "- summarize(text)\n"
392
- "- translate(lang, text)\n"
393
- "- joke()\n"
394
- "- weather(location)\n"
395
- "- inspire()\n"
396
- "- meme(text)\n"
397
- "- poll_create(question, options)\n"
398
- "- poll_vote(voter, choice)\n"
399
- "- poll_results()\n"
400
- "- poll_end()\n"
401
- "- generate_image(prompt, count, width, height)\n"
402
- "- send_text(message)\n\n"
403
- f"Conversation so far:\n{history_text}\n\n"
 
 
404
  f"User: {user_input}"
405
  )
406
 
 
 
407
  try:
408
  raw = generate_llm(sys_prompt)
409
  except LLMBadRequestError:
 
410
  clear_history(chat_id, sender)
411
- return SendTextIntent(action="send_text", message="Oops, let’s start fresh!")
412
 
413
  logger.debug(f"LLM raw response: {raw}")
414
 
415
- # Attempt strict JSON parse
416
  try:
417
  parsed = json.loads(raw)
 
418
  except json.JSONDecodeError:
419
  return SendTextIntent(action="send_text", message=raw)
420
 
421
- # Validate against each intent model
422
  for M in INTENT_MODELS:
423
  try:
424
- return M.model_validate(parsed)
 
 
425
  except ValidationError:
426
  continue
427
 
428
- # Fallback to plain text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
429
  return SendTextIntent(action="send_text", message=raw)
430
 
431
  # --- FastAPI & Webhook ----------------------------------------------------
@@ -442,7 +491,7 @@ help_text = (
442
  "• /meme <text>\n"
443
  "• /poll <Q>|… / /results / /endpoll\n"
444
  "• /gen <prompt>|<count>|<width>|<height>\n"
445
- "Otherwise chat or reply to invoke tools."
446
  )
447
 
448
  @app.post("/whatsapp")
@@ -455,6 +504,7 @@ async def whatsapp_webhook(request: Request):
455
  sender = data["senderData"]["sender"]
456
  mid = data["idMessage"]
457
  set_thread_context(chat_id, sender, mid)
 
458
 
459
  if chat_id != BotConfig.BOT_GROUP_CHAT or data["typeWebhook"] != "incomingMessageReceived":
460
  return {"success": True}
@@ -466,9 +516,10 @@ async def whatsapp_webhook(request: Request):
466
 
467
  body = (tmd.get("textMessage") or tmd.get("text","")).strip()
468
  record_user_message(chat_id, sender, body)
 
469
 
470
  low = body.lower()
471
- # Slash commands....
472
  if low == "/help":
473
  _fn_send_text(mid, chat_id, help_text)
474
  return {"success": True}
@@ -543,14 +594,13 @@ async def whatsapp_webhook(request: Request):
543
  # Route intent & dispatch
544
  intent = route_intent(effective, chat_id, sender)
545
  logger.debug(f"Final intent: {intent}")
546
-
547
- # Normal routing
548
- intent = route_intent(body, chat_id, sender)
549
  handler = ACTION_HANDLERS.get(intent.action)
550
  if handler:
551
  kwargs = intent.model_dump(exclude={"action"})
 
552
  handler(mid, chat_id, **kwargs)
553
  else:
 
554
  _fn_send_text(mid, chat_id, "Sorry, I didn't understand that.")
555
 
556
  return {"success": True}
 
4
  import logging
5
  import queue
6
  import json
7
+ from typing import List, Optional, Union, Literal
8
  from collections import defaultdict, deque
9
  from concurrent.futures import ThreadPoolExecutor
10
 
 
356
  action: Literal["send_text"]
357
  message: str
358
 
359
+ # list of all intent models
360
  INTENT_MODELS = [
361
  SummarizeIntent, TranslateIntent, JokeIntent, WeatherIntent,
362
  InspireIntent, MemeIntent, PollCreateIntent, PollVoteIntent,
 
383
  def route_intent(user_input: str, chat_id: str, sender: str):
384
  history_text = get_history_text(chat_id, sender)
385
  sys_prompt = (
386
+ "You are Eve, a sweet, innocent, and helpful assistant. "
387
+ "You never perform work yourself—you only invoke one of the available functions. "
388
+ "When the user asks for something that matches a function signature, you must return exactly one JSON object matching that function’s parameters—and nothing else. "
389
+ "Do not wrap it in markdown, do not add extra text, and do not show the JSON to the user. "
390
+ "If the user’s request does not match any function, reply in plain text, and never mention JSON or internal logic.\n\n"
391
+ "Functions you can call:\n"
392
+ " summarize(text)\n"
393
+ " translate(lang, text)\n"
394
+ " joke()\n"
395
+ " weather(location)\n"
396
+ " inspire()\n"
397
+ " meme(text)\n"
398
+ " poll_create(question, options)\n"
399
+ " poll_vote(voter, choice)\n"
400
+ " poll_results()\n"
401
+ " poll_end()\n"
402
+ " generate_image(prompt, count, width, height)\n"
403
+ " send_text(message)\n\n"
404
+ "Conversation so far:\n"
405
+ f"{history_text}\n\n"
406
+ "Current user message:\n"
407
  f"User: {user_input}"
408
  )
409
 
410
+ #prompt = f"{sys_prompt}\nConversation so far:\n{history_text}\n\n current message: User: {user_input}"
411
+
412
  try:
413
  raw = generate_llm(sys_prompt)
414
  except LLMBadRequestError:
415
+ # Clear history on HTTP 400 from the LLM
416
  clear_history(chat_id, sender)
417
+ return SendTextIntent(action="send_text", message="Oops, I lost my train of thought—let’s start fresh!")
418
 
419
  logger.debug(f"LLM raw response: {raw}")
420
 
421
+ # 1) Strict: try each Pydantic model
422
  try:
423
  parsed = json.loads(raw)
424
+ logger.debug(f"Parsed JSON: {parsed}")
425
  except json.JSONDecodeError:
426
  return SendTextIntent(action="send_text", message=raw)
427
 
 
428
  for M in INTENT_MODELS:
429
  try:
430
+ intent = M.model_validate(parsed)
431
+ logger.debug(f"Matched intent model: {M.__name__} with data {parsed}")
432
+ return intent
433
  except ValidationError:
434
  continue
435
 
436
+ logger.warning("Strict parse failed for all models, falling back to lenient")
437
+
438
+ # 2) Lenient JSON get
439
+ action = parsed.get("action")
440
+ if action in ACTION_HANDLERS:
441
+ data = parsed
442
+ kwargs = {}
443
+ if action == "generate_image":
444
+ kwargs["prompt"] = data.get("prompt","")
445
+ kwargs["count"] = int(data.get("count", BotConfig.DEFAULT_IMAGE_COUNT))
446
+ kwargs["width"] = data.get("width")
447
+ kwargs["height"] = data.get("height")
448
+ elif action == "send_text":
449
+ kwargs["message"] = data.get("message","")
450
+ elif action == "translate":
451
+ kwargs["lang"] = data.get("lang","")
452
+ kwargs["text"] = data.get("text","")
453
+ elif action == "summarize":
454
+ kwargs["text"] = data.get("text","")
455
+ elif action == "weather":
456
+ kwargs["location"] = data.get("location","")
457
+ elif action == "meme":
458
+ kwargs["text"] = data.get("text","")
459
+ elif action == "poll_create":
460
+ kwargs["question"] = data.get("question","")
461
+ kwargs["options"] = data.get("options",[])
462
+ elif action == "poll_vote":
463
+ kwargs["voter"] = sender
464
+ kwargs["choice"] = int(data.get("choice",0))
465
+ try:
466
+ # coerce into Pydantic for uniform interface
467
+ model = next(
468
+ m for m in INTENT_MODELS
469
+ if getattr(m, "__fields__", {}).get("action").default == action
470
+ )
471
+ intent = model.model_validate({"action":action, **kwargs})
472
+ logger.debug(f"Leniently matched intent model: {model.__name__} with kwargs {kwargs}")
473
+ return intent
474
+ except Exception as e:
475
+ logger.error(f"Lenient parsing into Pydantic failed: {e}")
476
+ return SendTextIntent(action="send_text", message=raw)
477
+
478
  return SendTextIntent(action="send_text", message=raw)
479
 
480
  # --- FastAPI & Webhook ----------------------------------------------------
 
491
  "• /meme <text>\n"
492
  "• /poll <Q>|… / /results / /endpoll\n"
493
  "• /gen <prompt>|<count>|<width>|<height>\n"
494
+ "Otherwise chat or reply to my message to invoke tools."
495
  )
496
 
497
  @app.post("/whatsapp")
 
504
  sender = data["senderData"]["sender"]
505
  mid = data["idMessage"]
506
  set_thread_context(chat_id, sender, mid)
507
+ logger.debug(f"Received webhook for message {mid} from {sender}")
508
 
509
  if chat_id != BotConfig.BOT_GROUP_CHAT or data["typeWebhook"] != "incomingMessageReceived":
510
  return {"success": True}
 
516
 
517
  body = (tmd.get("textMessage") or tmd.get("text","")).strip()
518
  record_user_message(chat_id, sender, body)
519
+ logger.debug(f"User message: {body}")
520
 
521
  low = body.lower()
522
+ # Slash commands...
523
  if low == "/help":
524
  _fn_send_text(mid, chat_id, help_text)
525
  return {"success": True}
 
594
  # Route intent & dispatch
595
  intent = route_intent(effective, chat_id, sender)
596
  logger.debug(f"Final intent: {intent}")
 
 
 
597
  handler = ACTION_HANDLERS.get(intent.action)
598
  if handler:
599
  kwargs = intent.model_dump(exclude={"action"})
600
+ logger.debug(f"Dispatching action '{intent.action}' with args {kwargs}")
601
  handler(mid, chat_id, **kwargs)
602
  else:
603
+ logger.warning(f"No handler for action '{intent.action}'")
604
  _fn_send_text(mid, chat_id, "Sorry, I didn't understand that.")
605
 
606
  return {"success": True}