Spaces:
Running
Running
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
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
|
386 |
-
"You never
|
387 |
-
"
|
388 |
-
"Do not wrap it in markdown
|
389 |
-
"If
|
390 |
-
"Functions:\n"
|
391 |
-
"
|
392 |
-
"
|
393 |
-
"
|
394 |
-
"
|
395 |
-
"
|
396 |
-
"
|
397 |
-
"
|
398 |
-
"
|
399 |
-
"
|
400 |
-
"
|
401 |
-
"
|
402 |
-
"
|
403 |
-
|
|
|
|
|
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 |
-
#
|
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 |
-
|
|
|
|
|
425 |
except ValidationError:
|
426 |
continue
|
427 |
|
428 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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}
|