zenaight commited on
Commit
851cd13
·
1 Parent(s): 2d92890
Files changed (4) hide show
  1. README.md +124 -11
  2. main.py +130 -7
  3. requirements.txt +2 -1
  4. supabase_setup.sql +33 -0
README.md CHANGED
@@ -1,11 +1,124 @@
1
- ---
2
- title: WhatsappChatBot
3
- emoji: 🌖
4
- colorFrom: yellow
5
- colorTo: red
6
- sdk: docker
7
- pinned: false
8
- short_description: WhatsApp chat bot for Rumas
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # PropAgent - WhatsApp AI Agent
2
+
3
+ A WhatsApp chatbot powered by OpenAI GPT-4 and integrated with Supabase for user management.
4
+
5
+ ## Features
6
+
7
+ - 🤖 AI-powered chat responses using OpenAI GPT-4
8
+ - 📱 WhatsApp Business API integration
9
+ - 👥 User management with Supabase database
10
+ - 🔄 Conversation memory and context
11
+ - 📊 User activity tracking
12
+ - 🚀 RESTful API endpoints for user management
13
+
14
+ ## Setup
15
+
16
+ ### 1. Environment Variables
17
+
18
+ Create a `.env` file with the following variables:
19
+
20
+ ```bash
21
+ # WhatsApp Business API
22
+ VERIFY_TOKEN=your_webhook_verify_token
23
+ PHONE_NUMBER_ID=your_phone_number_id
24
+ WHATSAPP_API_TOKEN=your_whatsapp_api_token
25
+
26
+ # OpenAI
27
+ OPENAI_API_KEY=your_openai_api_key
28
+
29
+ # Supabase
30
+ SUPABASE_URL=your_supabase_project_url
31
+ SUPABASE_KEY=your_supabase_anon_key
32
+ ```
33
+
34
+ ### 2. Supabase Setup
35
+
36
+ 1. Create a new Supabase project at [supabase.com](https://supabase.com)
37
+ 2. Go to the SQL Editor in your Supabase dashboard
38
+ 3. Run the SQL script from `supabase_setup.sql` to create the users table
39
+ 4. Copy your project URL and anon key from Settings > API
40
+
41
+ ### 3. WhatsApp Business API Setup
42
+
43
+ 1. Create a Meta Developer account
44
+ 2. Set up a WhatsApp Business app
45
+ 3. Configure webhook URL: `https://your-domain.com/webhook`
46
+ 4. Set the verify token in your environment variables
47
+
48
+ ### 4. Installation
49
+
50
+ ```bash
51
+ pip install -r requirements.txt
52
+ ```
53
+
54
+ ### 5. Running the Application
55
+
56
+ ```bash
57
+ uvicorn main:app --host 0.0.0.0 --port 8000
58
+ ```
59
+
60
+ ## API Endpoints
61
+
62
+ ### Webhook Endpoints
63
+ - `GET /webhook` - WhatsApp webhook verification
64
+ - `POST /webhook` - Receive WhatsApp messages
65
+
66
+ ### User Management
67
+ - `GET /users/{wa_id}` - Get user by WhatsApp ID
68
+ - `GET /users` - List all users (with pagination)
69
+ - `PUT /users/{wa_id}` - Update user name
70
+
71
+ ### Health & Testing
72
+ - `GET /ping` - Simple health check
73
+ - `GET /health` - Detailed service status
74
+ - `POST /chat` - Direct AI chat endpoint
75
+
76
+ ## Database Schema
77
+
78
+ ### Users Table
79
+ ```sql
80
+ users
81
+ -----
82
+ - wa_id (TEXT, PRIMARY KEY) - WhatsApp user ID
83
+ - name (TEXT) - User's display name
84
+ - created_at (TIMESTAMP) - Account creation time
85
+ - updated_at (TIMESTAMP) - Last activity time
86
+ ```
87
+
88
+ ## Features
89
+
90
+ ### User Management
91
+ - Automatic user creation when first message is received
92
+ - User name updates from WhatsApp profile
93
+ - Activity tracking with automatic timestamp updates
94
+
95
+ ### AI Chat
96
+ - Context-aware conversations using user names
97
+ - Conversation memory (last 6 messages)
98
+ - Error handling and fallback responses
99
+
100
+ ### Security
101
+ - Environment variable configuration
102
+ - Optional Row Level Security (RLS) in Supabase
103
+ - Input validation and error handling
104
+
105
+ ## Development
106
+
107
+ The application uses:
108
+ - **FastAPI** for the web framework
109
+ - **LangGraph** for conversation flow management
110
+ - **OpenAI GPT-4** for AI responses
111
+ - **Supabase** for user data storage
112
+ - **WhatsApp Business API** for messaging
113
+
114
+ ## Deployment
115
+
116
+ The application includes a Dockerfile for containerized deployment. You can deploy it to:
117
+ - Hugging Face Spaces
118
+ - Railway
119
+ - Heroku
120
+ - Any container platform
121
+
122
+ ## License
123
+
124
+ MIT License
main.py CHANGED
@@ -11,6 +11,8 @@ from langchain_core.memory import BaseMemory
11
  from langchain_core.chat_history import BaseChatMessageHistory
12
  from langchain_core.messages import HumanMessage, AIMessage
13
  from langchain_openai import ChatOpenAI
 
 
14
 
15
  # --- App and Config ---
16
  app = FastAPI()
@@ -18,6 +20,57 @@ VERIFY_TOKEN = os.getenv("VERIFY_TOKEN", "")
18
  PHONE_NUMBER_ID = os.getenv("PHONE_NUMBER_ID", "")
19
  WHATSAPP_API_TOKEN = os.getenv("WHATSAPP_API_TOKEN", "")
20
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
  # --- OpenAI Client Setup ---
23
  llm = ChatOpenAI(openai_api_key=OPENAI_API_KEY, model="gpt-4o-mini")
@@ -32,6 +85,7 @@ if not OPENAI_API_KEY:
32
  # --- LangGraph Setup ---
33
  def chat_with_memory(state):
34
  user_message = state["user_message"]
 
35
 
36
  # Add user message to history
37
  chat_history.append({"role": "user", "content": user_message})
@@ -39,8 +93,12 @@ def chat_with_memory(state):
39
  # Keep only last 6 messages for context
40
  recent_messages = chat_history[-6:] if len(chat_history) > 6 else chat_history
41
 
42
- # Add system message at the beginning
43
- messages = [{"role": "system", "content": "You are a helpful and concise assistant."}] + recent_messages
 
 
 
 
44
 
45
  try:
46
  if not OPENAI_API_KEY:
@@ -62,6 +120,7 @@ from typing import TypedDict
62
  class ChatState(TypedDict):
63
  user_message: str
64
  response: str
 
65
 
66
  graph = StateGraph(ChatState)
67
  graph.add_node("chat", RunnableLambda(chat_with_memory))
@@ -94,13 +153,26 @@ async def receive_message(req: Request):
94
  return JSONResponse({"status": "ignored", "reason": "no contacts/messages"})
95
 
96
  wa_id = contacts[0]["wa_id"]
 
97
  msg = messages[0]
98
 
99
  if msg.get("type") == "text":
100
  user_message = msg.get("text", {}).get("body", "")
101
- ai_result = await chat_graph.ainvoke({"user_message": user_message})
 
 
 
 
 
 
 
 
 
 
 
 
102
  await send_whatsapp_message(wa_id, ai_result["response"])
103
- return JSONResponse({"status": "replied"})
104
 
105
  except Exception as e:
106
  print("Error processing message:", e)
@@ -131,7 +203,7 @@ async def send_whatsapp_message(wa_id: str, message: str):
131
  class ChatRequest(BaseModel):
132
  message: str
133
  model: Optional[str] = "gpt-4o-mini"
134
- max_tokens: Optional[int] = 150
135
 
136
  class ChatResponse(BaseModel):
137
  response: str
@@ -153,6 +225,57 @@ def health_check():
153
  status = {
154
  "api": "healthy",
155
  "whatsapp": "configured" if WHATSAPP_API_TOKEN else "not configured",
156
- "openai": "configured" if OPENAI_API_KEY else "not configured"
 
157
  }
158
- return status
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  from langchain_core.chat_history import BaseChatMessageHistory
12
  from langchain_core.messages import HumanMessage, AIMessage
13
  from langchain_openai import ChatOpenAI
14
+ from supabase import create_client, Client
15
+ from datetime import datetime
16
 
17
  # --- App and Config ---
18
  app = FastAPI()
 
20
  PHONE_NUMBER_ID = os.getenv("PHONE_NUMBER_ID", "")
21
  WHATSAPP_API_TOKEN = os.getenv("WHATSAPP_API_TOKEN", "")
22
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
23
+ SUPABASE_URL = os.getenv("SUPABASE_URL", "")
24
+ SUPABASE_KEY = os.getenv("SUPABASE_KEY", "")
25
+
26
+ # --- Supabase Client Setup ---
27
+ supabase: Client = None
28
+ if SUPABASE_URL and SUPABASE_KEY:
29
+ supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
30
+ else:
31
+ print("Warning: Supabase credentials not set. Database functionality will be limited.")
32
+
33
+ # --- User Management Functions ---
34
+ async def get_or_create_user(wa_id: str, name: str = None) -> dict:
35
+ """Get existing user or create new one"""
36
+ if not supabase:
37
+ return {"wa_id": wa_id, "name": name or "Unknown"}
38
+
39
+ try:
40
+ # Try to get existing user
41
+ response = supabase.table("users").select("*").eq("wa_id", wa_id).execute()
42
+
43
+ if response.data:
44
+ # User exists, update if name provided
45
+ user = response.data[0]
46
+ if name and name != user.get("name"):
47
+ supabase.table("users").update({"name": name, "updated_at": datetime.utcnow().isoformat()}).eq("wa_id", wa_id).execute()
48
+ user["name"] = name
49
+ return user
50
+ else:
51
+ # Create new user
52
+ new_user = {
53
+ "wa_id": wa_id,
54
+ "name": name or "Unknown",
55
+ "created_at": datetime.utcnow().isoformat(),
56
+ "updated_at": datetime.utcnow().isoformat()
57
+ }
58
+ response = supabase.table("users").insert(new_user).execute()
59
+ return response.data[0] if response.data else new_user
60
+
61
+ except Exception as e:
62
+ print(f"Error in get_or_create_user: {e}")
63
+ return {"wa_id": wa_id, "name": name or "Unknown"}
64
+
65
+ async def update_user_activity(wa_id: str):
66
+ """Update user's last activity timestamp"""
67
+ if not supabase:
68
+ return
69
+
70
+ try:
71
+ supabase.table("users").update({"updated_at": datetime.utcnow().isoformat()}).eq("wa_id", wa_id).execute()
72
+ except Exception as e:
73
+ print(f"Error updating user activity: {e}")
74
 
75
  # --- OpenAI Client Setup ---
76
  llm = ChatOpenAI(openai_api_key=OPENAI_API_KEY, model="gpt-4o-mini")
 
85
  # --- LangGraph Setup ---
86
  def chat_with_memory(state):
87
  user_message = state["user_message"]
88
+ user_info = state.get("user_info", {})
89
 
90
  # Add user message to history
91
  chat_history.append({"role": "user", "content": user_message})
 
93
  # Keep only last 6 messages for context
94
  recent_messages = chat_history[-6:] if len(chat_history) > 6 else chat_history
95
 
96
+ # Add system message with user context
97
+ system_message = "You are a helpful and concise assistant."
98
+ if user_info.get("name") and user_info["name"] != "Unknown":
99
+ system_message += f" The user's name is {user_info['name']}."
100
+
101
+ messages = [{"role": "system", "content": system_message}] + recent_messages
102
 
103
  try:
104
  if not OPENAI_API_KEY:
 
120
  class ChatState(TypedDict):
121
  user_message: str
122
  response: str
123
+ user_info: dict
124
 
125
  graph = StateGraph(ChatState)
126
  graph.add_node("chat", RunnableLambda(chat_with_memory))
 
153
  return JSONResponse({"status": "ignored", "reason": "no contacts/messages"})
154
 
155
  wa_id = contacts[0]["wa_id"]
156
+ contact_name = contacts[0].get("profile", {}).get("name")
157
  msg = messages[0]
158
 
159
  if msg.get("type") == "text":
160
  user_message = msg.get("text", {}).get("body", "")
161
+
162
+ # Get or create user in database
163
+ user_info = await get_or_create_user(wa_id, contact_name)
164
+
165
+ # Update user activity
166
+ await update_user_activity(wa_id)
167
+
168
+ # Process with AI including user context
169
+ ai_result = await chat_graph.ainvoke({
170
+ "user_message": user_message,
171
+ "user_info": user_info
172
+ })
173
+
174
  await send_whatsapp_message(wa_id, ai_result["response"])
175
+ return JSONResponse({"status": "replied", "user_id": wa_id})
176
 
177
  except Exception as e:
178
  print("Error processing message:", e)
 
203
  class ChatRequest(BaseModel):
204
  message: str
205
  model: Optional[str] = "gpt-4o-mini"
206
+ max_tokens: Optional[int] = 1500
207
 
208
  class ChatResponse(BaseModel):
209
  response: str
 
225
  status = {
226
  "api": "healthy",
227
  "whatsapp": "configured" if WHATSAPP_API_TOKEN else "not configured",
228
+ "openai": "configured" if OPENAI_API_KEY else "not configured",
229
+ "supabase": "configured" if supabase else "not configured"
230
  }
231
+ return status
232
+
233
+ # --- User Management Endpoints ---
234
+ @app.get("/users/{wa_id}")
235
+ async def get_user(wa_id: str):
236
+ """Get user by WhatsApp ID"""
237
+ if not supabase:
238
+ raise HTTPException(status_code=503, detail="Database not configured")
239
+
240
+ try:
241
+ response = supabase.table("users").select("*").eq("wa_id", wa_id).execute()
242
+ if response.data:
243
+ return response.data[0]
244
+ else:
245
+ raise HTTPException(status_code=404, detail="User not found")
246
+ except Exception as e:
247
+ print(f"Error getting user: {e}")
248
+ raise HTTPException(status_code=500, detail="Database error")
249
+
250
+ @app.get("/users")
251
+ async def list_users(limit: int = 50, offset: int = 0):
252
+ """List all users with pagination"""
253
+ if not supabase:
254
+ raise HTTPException(status_code=503, detail="Database not configured")
255
+
256
+ try:
257
+ response = supabase.table("users").select("*").range(offset, offset + limit - 1).order("created_at", desc=True).execute()
258
+ return {"users": response.data, "count": len(response.data)}
259
+ except Exception as e:
260
+ print(f"Error listing users: {e}")
261
+ raise HTTPException(status_code=500, detail="Database error")
262
+
263
+ @app.put("/users/{wa_id}")
264
+ async def update_user(wa_id: str, name: str):
265
+ """Update user name"""
266
+ if not supabase:
267
+ raise HTTPException(status_code=503, detail="Database not configured")
268
+
269
+ try:
270
+ response = supabase.table("users").update({
271
+ "name": name,
272
+ "updated_at": datetime.utcnow().isoformat()
273
+ }).eq("wa_id", wa_id).execute()
274
+
275
+ if response.data:
276
+ return response.data[0]
277
+ else:
278
+ raise HTTPException(status_code=404, detail="User not found")
279
+ except Exception as e:
280
+ print(f"Error updating user: {e}")
281
+ raise HTTPException(status_code=500, detail="Database error")
requirements.txt CHANGED
@@ -6,4 +6,5 @@ openai
6
  langchain
7
  langchain-openai
8
  langgraph
9
- langchain-core
 
 
6
  langchain
7
  langchain-openai
8
  langgraph
9
+ langchain-core
10
+ supabase
supabase_setup.sql ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- Create users table for WhatsApp AI Agent
2
+ CREATE TABLE IF NOT EXISTS users (
3
+ wa_id TEXT PRIMARY KEY,
4
+ name TEXT NOT NULL DEFAULT 'Unknown',
5
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
6
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
7
+ );
8
+
9
+ -- Create index for better query performance
10
+ CREATE INDEX IF NOT EXISTS idx_users_created_at ON users(created_at DESC);
11
+ CREATE INDEX IF NOT EXISTS idx_users_updated_at ON users(updated_at DESC);
12
+
13
+ -- Enable Row Level Security (RLS) - optional but recommended
14
+ ALTER TABLE users ENABLE ROW LEVEL SECURITY;
15
+
16
+ -- Create policy to allow all operations (you can restrict this based on your needs)
17
+ CREATE POLICY "Allow all operations on users" ON users
18
+ FOR ALL USING (true);
19
+
20
+ -- Optional: Create a function to automatically update the updated_at timestamp
21
+ CREATE OR REPLACE FUNCTION update_updated_at_column()
22
+ RETURNS TRIGGER AS $$
23
+ BEGIN
24
+ NEW.updated_at = NOW();
25
+ RETURN NEW;
26
+ END;
27
+ $$ language 'plpgsql';
28
+
29
+ -- Create trigger to automatically update updated_at
30
+ CREATE TRIGGER update_users_updated_at
31
+ BEFORE UPDATE ON users
32
+ FOR EACH ROW
33
+ EXECUTE FUNCTION update_updated_at_column();