Spaces:
Running
Running
Chandima Prabhath
commited on
Commit
·
6818a89
1
Parent(s):
b4241b8
Refactor route_intent function for improved clarity and efficiency in prompt handling and intent parsing
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,
|
8 |
from collections import defaultdict, deque
|
9 |
from concurrent.futures import ThreadPoolExecutor
|
10 |
|
@@ -356,7 +356,6 @@ class SendTextIntent(BaseIntent):
|
|
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,98 +382,50 @@ ACTION_HANDLERS = {
|
|
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
|
388 |
-
"
|
389 |
-
"Do not wrap it in markdown
|
390 |
-
"If
|
391 |
-
"Functions
|
392 |
-
"
|
393 |
-
"
|
394 |
-
"
|
395 |
-
"
|
396 |
-
"
|
397 |
-
"
|
398 |
-
"
|
399 |
-
"
|
400 |
-
"
|
401 |
-
"
|
402 |
-
"
|
403 |
-
"
|
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,
|
418 |
|
419 |
logger.debug(f"LLM raw response: {raw}")
|
420 |
|
421 |
-
#
|
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 |
-
|
431 |
-
logger.debug(f"Matched intent model: {M.__name__} with data {parsed}")
|
432 |
-
return intent
|
433 |
except ValidationError:
|
434 |
continue
|
435 |
|
436 |
-
|
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,7 +442,7 @@ help_text = (
|
|
491 |
"• /meme <text>\n"
|
492 |
"• /poll <Q>|… / /results / /endpoll\n"
|
493 |
"• /gen <prompt>|<count>|<width>|<height>\n"
|
494 |
-
"Otherwise chat or reply to
|
495 |
)
|
496 |
|
497 |
@app.post("/whatsapp")
|
@@ -504,7 +455,6 @@ async def whatsapp_webhook(request: Request):
|
|
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,10 +466,9 @@ async def whatsapp_webhook(request: Request):
|
|
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,13 +543,14 @@ async def whatsapp_webhook(request: Request):
|
|
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}
|
|
|
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 |
action: Literal["send_text"]
|
357 |
message: str
|
358 |
|
|
|
359 |
INTENT_MODELS = [
|
360 |
SummarizeIntent, TranslateIntent, JokeIntent, WeatherIntent,
|
361 |
InspireIntent, MemeIntent, PollCreateIntent, PollVoteIntent,
|
|
|
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 |
"• /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 |
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 |
|
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 |
# 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}
|