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}
|