Spaces:
Running
Running
File size: 6,126 Bytes
5cb5f85 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
import logging
import os
from io import BytesIO
from typing import Dict
import httpx
from fastapi import APIRouter, Request, Response
from langchain_core.messages import HumanMessage
from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
from agent import create_agent_graph
from agent.utils.state import AgentState
logger = logging.getLogger(__name__)
# Global module instances
speech_to_text = SpeechToText()
text_to_speech = TextToSpeech()
image_to_text = ImageToText()
# Router for WhatsApp respo
whatsapp_router = APIRouter()
# WhatsApp API credentials
WHATSAPP_TOKEN = os.getenv("WHATSAPP_TOKEN")
WHATSAPP_PHONE_NUMBER_ID = os.getenv("WHATSAPP_PHONE_NUMBER_ID")
@whatsapp_router.api_route("/whatsapp_response", methods=["GET", "POST"])
async def whatsapp_handler(request: Request) -> Response:
"""Handles incoming messages and status updates from the WhatsApp Cloud API."""
if request.method == "GET":
params = request.query_params
if params.get("hub.verify_token") == os.getenv("WHATSAPP_VERIFY_TOKEN"):
return Response(content=params.get("hub.challenge"), status_code=200)
return Response(content="Verification token mismatch", status_code=403)
try:
data = await request.json()
change_value = data["entry"][0]["changes"][0]["value"]
if "messages" in change_value:
message = change_value["messages"][0]
from_number = message["from"]
session_id = from_number
# Get user message (text only for now)
if message["type"] != "text":
await send_response(from_number, "Sorry, I can only process text messages at the moment.")
return Response(content="Non-text message received", status_code=200)
content = message["text"]["body"]
# Process message through the graph agent
async with AsyncSqliteSaver.from_conn_string("data/short_term.db") as short_term_memory:
graph = await create_agent_graph(short_term_memory)
current_state = AgentState(
messages=[HumanMessage(content=content)],
context=[]
)
# Get the response from the graph
output_state = await graph.ainvoke(
current_state,
{"configurable": {"thread_id": session_id}},
)
response_message = output_state["messages"][-1].content
success = await send_response(from_number, response_message)
if not success:
return Response(content="Failed to send message", status_code=500)
return Response(content="Message processed", status_code=200)
elif "statuses" in change_value:
return Response(content="Status update received", status_code=200)
else:
return Response(content="Unknown event type", status_code=400)
except Exception as e:
logger.error(f"Error processing message: {e}", exc_info=True)
return Response(content="Internal server error", status_code=500)
async def download_media(media_id: str) -> bytes:
"""Download media from WhatsApp."""
media_metadata_url = f"https://graph.facebook.com/v21.0/{media_id}"
headers = {"Authorization": f"Bearer {WHATSAPP_TOKEN}"}
async with httpx.AsyncClient() as client:
metadata_response = await client.get(media_metadata_url, headers=headers)
metadata_response.raise_for_status()
metadata = metadata_response.json()
download_url = metadata.get("url")
media_response = await client.get(download_url, headers=headers)
media_response.raise_for_status()
return media_response.content
async def process_audio_message(message: Dict) -> str:
"""Download and transcribe audio message."""
audio_id = message["audio"]["id"]
media_metadata_url = f"https://graph.facebook.com/v21.0/{audio_id}"
headers = {"Authorization": f"Bearer {WHATSAPP_TOKEN}"}
async with httpx.AsyncClient() as client:
metadata_response = await client.get(media_metadata_url, headers=headers)
metadata_response.raise_for_status()
metadata = metadata_response.json()
download_url = metadata.get("url")
# Download the audio file
async with httpx.AsyncClient() as client:
audio_response = await client.get(download_url, headers=headers)
audio_response.raise_for_status()
# Prepare for transcription
audio_buffer = BytesIO(audio_response.content)
audio_buffer.seek(0)
audio_data = audio_buffer.read()
return await speech_to_text.transcribe(audio_data)
async def send_response(from_number: str, response_text: str) -> bool:
"""Send text response to user via WhatsApp API."""
headers = {
"Authorization": f"Bearer {WHATSAPP_TOKEN}",
"Content-Type": "application/json",
}
json_data = {
"messaging_product": "whatsapp",
"to": from_number,
"type": "text",
"text": {"body": response_text},
}
async with httpx.AsyncClient() as client:
response = await client.post(
f"https://graph.facebook.com/v21.0/{WHATSAPP_PHONE_NUMBER_ID}/messages",
headers=headers,
json=json_data,
)
return response.status_code == 200
async def upload_media(media_content: BytesIO, mime_type: str) -> str:
"""Upload media to WhatsApp servers."""
headers = {"Authorization": f"Bearer {WHATSAPP_TOKEN}"}
files = {"file": ("response.mp3", media_content, mime_type)}
data = {"messaging_product": "whatsapp", "type": mime_type}
async with httpx.AsyncClient() as client:
response = await client.post(
f"https://graph.facebook.com/v21.0/{WHATSAPP_PHONE_NUMBER_ID}/media",
headers=headers,
files=files,
data=data,
)
result = response.json()
if "id" not in result:
raise Exception("Failed to upload media")
return result["id"] |