raghavNCI
commited on
Commit
Β·
68f07e2
1
Parent(s):
89615e3
wa integrating llm
Browse files- routes/question.py +21 -10
- routes/wa_gateway.py +63 -75
routes/question.py
CHANGED
@@ -77,17 +77,28 @@ async def ask_question(input: QuestionInput):
|
|
77 |
"answer": "Cannot answer β no relevant context found.",
|
78 |
"sources": sources
|
79 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
80 |
|
81 |
-
|
82 |
-
answer_prompt = (
|
83 |
-
f"You are a concise news assistant. Answer the user's question clearly using the context below if relevant. "
|
84 |
-
f"Make sure you are precise, accurate and to the point. Do not assume that the reader has any prerequisite understanding of the subject."
|
85 |
-
f"If the context is not helpful, you may rely on your own knowledge, but do not mention the context or question again.\n\n"
|
86 |
-
f"Context:\n{context}\n\n"
|
87 |
-
f"Question: {question}\n\n"
|
88 |
-
f"Answer:"
|
89 |
-
)
|
90 |
-
answer_raw = mistral_generate(answer_prompt, max_new_tokens=256)
|
91 |
|
92 |
if not answer_raw:
|
93 |
final_answer = "Cannot answer β model did not return a valid response."
|
|
|
77 |
"answer": "Cannot answer β no relevant context found.",
|
78 |
"sources": sources
|
79 |
}
|
80 |
+
|
81 |
+
# Step 3: Ask Mistral to answer
|
82 |
+
answer_prompt = (
|
83 |
+
f"You are a concise news assistant. Answer the user's question clearly using the context below if relevant. "
|
84 |
+
f"Make sure you are precise, accurate and to the point. Do not assume that the reader has any prerequisite understanding of the subject."
|
85 |
+
f"If the context is not helpful, you may rely on your own knowledge, but do not mention the context or question again.\n\n"
|
86 |
+
f"Context:\n{context}\n\n"
|
87 |
+
f"Question: {question}\n\n"
|
88 |
+
f"Answer:"
|
89 |
+
)
|
90 |
+
answer_raw = mistral_generate(answer_prompt, max_new_tokens=256)
|
91 |
+
|
92 |
+
else:
|
93 |
+
|
94 |
+
answer_prompt = (
|
95 |
+
f"You are a concise news assistant. Answer the user's question clearly using the context below if relevant. "
|
96 |
+
f"Make sure you are precise, accurate and to the point. Do not assume that the reader has any prerequisite understanding of the subject."
|
97 |
+
f"Question: {question}\n\n"
|
98 |
+
f"Answer:"
|
99 |
+
)
|
100 |
|
101 |
+
answer_raw = mistral_generate(answer_prompt, max_new_tokens=256)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
|
103 |
if not answer_raw:
|
104 |
final_answer = "Cannot answer β model did not return a valid response."
|
routes/wa_gateway.py
CHANGED
@@ -1,120 +1,108 @@
|
|
1 |
# app/routes/wa_gateway.py
|
2 |
from fastapi import APIRouter, Request
|
3 |
-
import os
|
4 |
-
import requests
|
5 |
from dotenv import load_dotenv
|
6 |
from fastapi.responses import PlainTextResponse, JSONResponse
|
7 |
|
|
|
|
|
|
|
8 |
load_dotenv()
|
9 |
|
10 |
wa_router = APIRouter()
|
11 |
|
12 |
-
VERIFY_TOKEN
|
13 |
-
ACCESS_TOKEN
|
14 |
-
PHONE_NUMBER_ID
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
headers = {
|
20 |
"Authorization": f"Bearer {ACCESS_TOKEN}",
|
21 |
-
"Content-Type":
|
22 |
}
|
23 |
payload = {
|
24 |
"messaging_product": "whatsapp",
|
25 |
-
"to":
|
26 |
-
"type": "
|
27 |
-
"
|
28 |
-
"type": "list",
|
29 |
-
"header": { "type": "text", "text": "Select Your Interests" },
|
30 |
-
"body": { "text": f"Hi {name}! Please pick your preferred news topics." },
|
31 |
-
"footer": { "text": "Powered by nuse" },
|
32 |
-
"action": {
|
33 |
-
"button": "Choose Topics",
|
34 |
-
"sections": [{
|
35 |
-
"title": "Categories",
|
36 |
-
"rows": [
|
37 |
-
{ "id": "world", "title": "World" },
|
38 |
-
{ "id": "india", "title": "India" },
|
39 |
-
{ "id": "finance", "title": "Finance" },
|
40 |
-
{ "id": "sports", "title": "Sports" },
|
41 |
-
{ "id": "entertainment", "title": "Entertainment" }
|
42 |
-
]
|
43 |
-
}]
|
44 |
-
}
|
45 |
-
}
|
46 |
}
|
47 |
|
48 |
-
response = requests.post(url, headers=headers, json=payload)
|
49 |
try:
|
50 |
-
|
51 |
-
|
|
|
52 |
except Exception as e:
|
53 |
return {"status": "failed", "error": str(e)}
|
54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
55 |
@wa_router.get("/webhook")
|
56 |
async def verify_webhook(request: Request):
|
57 |
params = request.query_params
|
58 |
if params.get("hub.mode") == "subscribe" and params.get("hub.verify_token") == VERIFY_TOKEN:
|
59 |
-
# Return raw plain text (not JSON)
|
60 |
return PlainTextResponse(content=params.get("hub.challenge"))
|
61 |
return JSONResponse(status_code=403, content={"error": "Verification failed"})
|
62 |
|
|
|
|
|
|
|
63 |
@wa_router.post("/webhook")
|
64 |
async def receive_whatsapp_event(request: Request):
|
65 |
body = await request.json()
|
66 |
-
print("[WEBHOOK] Incoming
|
67 |
|
68 |
-
# Detect message and extract user number + message
|
69 |
try:
|
70 |
-
entry
|
71 |
-
changes
|
72 |
-
value
|
73 |
messages = value.get("messages")
|
74 |
contacts = value.get("contacts")
|
75 |
|
76 |
if messages and contacts:
|
77 |
-
msg
|
78 |
contact = contacts[0]
|
79 |
|
80 |
-
from_number
|
81 |
-
sender_name
|
82 |
-
|
83 |
-
|
84 |
-
print(f"[INFO] Message from {sender_name} ({from_number}): {text}")
|
85 |
-
|
86 |
-
response = send_preferences_flow(from_number, sender_name)
|
87 |
|
88 |
-
|
89 |
-
print("[ERROR] Failed to send interactive message:", response["error"])
|
90 |
|
91 |
-
#
|
|
|
|
|
92 |
|
93 |
except Exception as e:
|
94 |
print("[ERROR] Webhook processing failed:", str(e))
|
95 |
|
96 |
return JSONResponse(content={"status": "received"})
|
97 |
-
|
98 |
-
@wa_router.post("/whatsapp/send")
|
99 |
-
def send_whatsapp_message():
|
100 |
-
url = f"https://graph.facebook.com/v17.0/{PHONE_NUMBER_ID}/messages"
|
101 |
-
headers = {
|
102 |
-
"Authorization": f"Bearer {ACCESS_TOKEN}",
|
103 |
-
"Content-Type": "application/json"
|
104 |
-
}
|
105 |
-
payload = {
|
106 |
-
"messaging_product": "whatsapp",
|
107 |
-
"to": RECIPIENT_NUMBER,
|
108 |
-
"type": "template",
|
109 |
-
"template": {
|
110 |
-
"name": "hello_world",
|
111 |
-
"language": { "code": "en_US" }
|
112 |
-
}
|
113 |
-
}
|
114 |
-
|
115 |
-
response = requests.post(url, headers=headers, json=payload)
|
116 |
-
try:
|
117 |
-
response.raise_for_status()
|
118 |
-
return {"status": "sent", "response": response.json()}
|
119 |
-
except Exception as e:
|
120 |
-
return {"status": "failed", "error": str(e)}
|
|
|
1 |
# app/routes/wa_gateway.py
|
2 |
from fastapi import APIRouter, Request
|
3 |
+
import os, requests, asyncio
|
|
|
4 |
from dotenv import load_dotenv
|
5 |
from fastapi.responses import PlainTextResponse, JSONResponse
|
6 |
|
7 |
+
# ----- import the Q&A logic -----
|
8 |
+
from routes.question import ask_question, QuestionInput # β new
|
9 |
+
|
10 |
load_dotenv()
|
11 |
|
12 |
wa_router = APIRouter()
|
13 |
|
14 |
+
VERIFY_TOKEN = os.getenv("VERIFY_TOKEN")
|
15 |
+
ACCESS_TOKEN = os.getenv("WHATSAPP_ACCESS_TOKEN")
|
16 |
+
PHONE_NUMBER_ID = os.getenv("WHATSAPP_PHONE_NUMBER_ID")
|
17 |
+
|
18 |
+
# ------------------------------------------------------------------
|
19 |
+
# Helper: actually POST a plain-text WhatsApp message via Graph API
|
20 |
+
# ------------------------------------------------------------------
|
21 |
+
def send_whatsapp_text(to: str, text: str) -> dict:
|
22 |
+
"""
|
23 |
+
Send a plain text message (max 4096 chars per WhatsApp limits).
|
24 |
+
Returns {'status': 'sent' | 'failed', ... } for easy logging.
|
25 |
+
"""
|
26 |
+
url = f"https://graph.facebook.com/v17.0/{PHONE_NUMBER_ID}/messages"
|
27 |
headers = {
|
28 |
"Authorization": f"Bearer {ACCESS_TOKEN}",
|
29 |
+
"Content-Type": "application/json",
|
30 |
}
|
31 |
payload = {
|
32 |
"messaging_product": "whatsapp",
|
33 |
+
"to": to,
|
34 |
+
"type": "text",
|
35 |
+
"text": {"body": text[:4096]}, # truncate if necessary
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
}
|
37 |
|
|
|
38 |
try:
|
39 |
+
r = requests.post(url, headers=headers, json=payload)
|
40 |
+
r.raise_for_status()
|
41 |
+
return {"status": "sent", "response": r.json()}
|
42 |
except Exception as e:
|
43 |
return {"status": "failed", "error": str(e)}
|
44 |
|
45 |
+
# ------------------------------------------------------------------
|
46 |
+
# Helper: glue logic β ask Mistral and relay the reply to WhatsApp
|
47 |
+
# ------------------------------------------------------------------
|
48 |
+
async def nuse_interact(to: str, name: str, prompt: str) -> dict:
|
49 |
+
"""
|
50 |
+
1. Call ask_question() directly (no HTTP round-trip);
|
51 |
+
2. Extract the answer text (+sources if you like);
|
52 |
+
3. Push it back to the user via WhatsApp API.
|
53 |
+
"""
|
54 |
+
# 1) Ask the news assistant
|
55 |
+
qa_result = await ask_question(QuestionInput(question=prompt))
|
56 |
+
answer_text = qa_result["answer"]
|
57 |
+
|
58 |
+
# Optional: include sources as bullet list
|
59 |
+
if qa_result.get("sources"):
|
60 |
+
bullet_list = "\n".join(f"β’ {s['title']}" for s in qa_result["sources"])
|
61 |
+
answer_text = f"{answer_text}\n\nSources:\n{bullet_list}"
|
62 |
+
|
63 |
+
# 2) Send to WhatsApp
|
64 |
+
return send_whatsapp_text(to, answer_text)
|
65 |
+
|
66 |
+
# ------------------------------------------------------------------
|
67 |
+
# Webhook verification (unchanged)
|
68 |
+
# ------------------------------------------------------------------
|
69 |
@wa_router.get("/webhook")
|
70 |
async def verify_webhook(request: Request):
|
71 |
params = request.query_params
|
72 |
if params.get("hub.mode") == "subscribe" and params.get("hub.verify_token") == VERIFY_TOKEN:
|
|
|
73 |
return PlainTextResponse(content=params.get("hub.challenge"))
|
74 |
return JSONResponse(status_code=403, content={"error": "Verification failed"})
|
75 |
|
76 |
+
# ------------------------------------------------------------------
|
77 |
+
# Incoming WhatsApp messages
|
78 |
+
# ------------------------------------------------------------------
|
79 |
@wa_router.post("/webhook")
|
80 |
async def receive_whatsapp_event(request: Request):
|
81 |
body = await request.json()
|
82 |
+
print("[WEBHOOK] Incoming:", body)
|
83 |
|
|
|
84 |
try:
|
85 |
+
entry = body["entry"][0]
|
86 |
+
changes = entry["changes"][0]
|
87 |
+
value = changes["value"]
|
88 |
messages = value.get("messages")
|
89 |
contacts = value.get("contacts")
|
90 |
|
91 |
if messages and contacts:
|
92 |
+
msg = messages[0]
|
93 |
contact = contacts[0]
|
94 |
|
95 |
+
from_number = msg["from"]
|
96 |
+
sender_name = contact["profile"]["name"]
|
97 |
+
incoming_txt = msg["text"]["body"]
|
|
|
|
|
|
|
|
|
98 |
|
99 |
+
print(f"[INFO] {sender_name} ({from_number}): {incoming_txt}")
|
|
|
100 |
|
101 |
+
# Hand off to Q&A -> WhatsApp reply
|
102 |
+
result = await nuse_interact(from_number, sender_name, incoming_txt)
|
103 |
+
print("[INFO] Reply status:", result)
|
104 |
|
105 |
except Exception as e:
|
106 |
print("[ERROR] Webhook processing failed:", str(e))
|
107 |
|
108 |
return JSONResponse(content={"status": "received"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|