Hammad712 commited on
Commit
f1fa185
·
verified ·
1 Parent(s): 7d839c4

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile +25 -0
  2. main.py +272 -0
  3. requirements.txt +9 -0
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Base image using Python 3.9
2
+ FROM python:3.9
3
+
4
+ # Create a new user to run the app
5
+ RUN useradd -m -u 1000 user
6
+ USER user
7
+
8
+ # Set environment variables
9
+ ENV PATH="/home/user/.local/bin:$PATH"
10
+
11
+ # Set the working directory
12
+ WORKDIR /app
13
+
14
+ # Copy the requirements and install dependencies
15
+ COPY --chown=user ./requirements.txt requirements.txt
16
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
17
+
18
+ # Copy the rest of the application
19
+ COPY --chown=user . /app
20
+
21
+ # Expose port 7860 for the application
22
+ EXPOSE 7860
23
+
24
+ # Command to run the FastAPI app using uvicorn
25
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
main.py ADDED
@@ -0,0 +1,272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import base64
4
+ import asyncio
5
+ from fastapi import FastAPI, UploadFile, File, Body, Form, HTTPException
6
+ from fastapi.responses import JSONResponse, Response
7
+ from pymongo import MongoClient
8
+ from cartesia import Cartesia
9
+ from groq import Groq
10
+ from dotenv import load_dotenv
11
+
12
+ # Load environment variables from a .env file
13
+ load_dotenv()
14
+
15
+ # ---------------------------
16
+ # API Client Setup for Groq and Cartesia
17
+ # ---------------------------
18
+ GROQ_API_KEY = os.getenv("GROQ_API_KEY")
19
+ groq_client = Groq(api_key=GROQ_API_KEY)
20
+
21
+ CARTESIA_API_KEY = os.getenv("CARTESIA_API_KEY")
22
+ cartesia_client = Cartesia(api_key=CARTESIA_API_KEY)
23
+
24
+ # ---------------------------
25
+ # OpenAI Chat Client Setup (using langchain_openai)
26
+ # ---------------------------
27
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
28
+ from langchain_openai import ChatOpenAI
29
+
30
+ llm = ChatOpenAI(
31
+ model="gpt-4o",
32
+ temperature=1,
33
+ max_tokens=1024,
34
+ api_key=OPENAI_API_KEY
35
+ )
36
+
37
+ # ---------------------------
38
+ # FastAPI and MongoDB Setup
39
+ # ---------------------------
40
+ app = FastAPI()
41
+
42
+ MONGO_DETAILS = "mongodb://localhost:27017"
43
+ mongo_client = MongoClient(MONGO_DETAILS)
44
+ database = mongo_client["contacts_db"]
45
+ contacts_collection = database["contacts"]
46
+ chat_history_collection = database["chat_history"]
47
+
48
+ # ---------------------------
49
+ # Pydantic Models
50
+ # ---------------------------
51
+ from pydantic import BaseModel, EmailStr, Field
52
+ from typing import List, Optional
53
+
54
+ class Contact(BaseModel):
55
+ name: str = Field(..., example="John Doe")
56
+ phone: str = Field(..., example="+1234567890")
57
+ email: Optional[EmailStr] = Field(None, example="john.doe@example.com")
58
+
59
+ class IncomingMessage(BaseModel):
60
+ phone: str = Field(..., example="+1234567890")
61
+ message: str = Field(..., example="Hello, can we chat?")
62
+
63
+ # ---------------------------
64
+ # Chat History Helper Functions
65
+ # ---------------------------
66
+ def get_chat_history(caller_number: str) -> List[dict]:
67
+ """
68
+ Retrieve the conversation history for a given caller from MongoDB.
69
+ Each entry is a dict with 'role' and 'content' keys.
70
+ """
71
+ doc = chat_history_collection.find_one({"caller_number": caller_number})
72
+ if doc and "messages" in doc:
73
+ return doc["messages"]
74
+ return []
75
+
76
+ def update_chat_history(caller_number: str, role: str, content: str):
77
+ """
78
+ Append a new message (with role and content) to the chat history for the given caller.
79
+ If no document exists, one is created (upsert).
80
+ """
81
+ chat_history_collection.update_one(
82
+ {"caller_number": caller_number},
83
+ {"$push": {"messages": {"role": role, "content": content}}},
84
+ upsert=True
85
+ )
86
+
87
+ # ---------------------------
88
+ # Conversation Simulation Functions
89
+ # ---------------------------
90
+ def simulate_text_conversation(caller_number: str, initial_message: str) -> str:
91
+ """
92
+ For incoming text messages from unknown numbers, use the LLM with a humorous detective prompt.
93
+ The LLM wastes the spammer's time with a witty, drawn-out conversation.
94
+ """
95
+ chat_history = get_chat_history(caller_number)
96
+ messages = [
97
+ {
98
+ "role": "system",
99
+ "content": (
100
+ "You are Detective Quip, a witty, sarcastic detective with a flair for humor. "
101
+ "Your mission is to waste the time of potential spammers by engaging them in an overly elaborate, "
102
+ "investigative conversation. Ask funny and overly detailed questions, and use your detective skills to slowly uncover "
103
+ "their dubious intentions—all while making humorous remarks. Your goal is to keep the spammer engaged for at least 30 seconds with "
104
+ "a minimum of three back-and-forth exchanges. Answers shouls be concise, humorous, and investigative."
105
+ )
106
+ }
107
+ ]
108
+ # Append previous conversation history if available
109
+ for entry in chat_history:
110
+ messages.append({"role": entry["role"], "content": entry["content"]})
111
+ messages.append({"role": "user", "content": initial_message})
112
+
113
+ # Call the LLM for a response and convert the response to string
114
+ assistant_response = llm.invoke(messages)
115
+ if hasattr(assistant_response, "content"):
116
+ assistant_response = assistant_response.content
117
+ else:
118
+ assistant_response = str(assistant_response)
119
+
120
+ # Log the conversation
121
+ update_chat_history(caller_number, "user", initial_message)
122
+ update_chat_history(caller_number, "assistant", assistant_response)
123
+
124
+ return assistant_response
125
+
126
+ def simulate_call_conversation(caller_number: str, initial_message: str) -> str:
127
+ """
128
+ For voice calls from unknown numbers, use the LLM with a detective-on-the-phone prompt.
129
+ The LLM must waste the spammer's time by engaging in a drawn-out, humorous, detective-style conversation.
130
+ """
131
+ chat_history = get_chat_history(caller_number)
132
+ messages = [
133
+ {
134
+ "role": "system",
135
+ "content": (
136
+ "You are Detective Quip on a phone call. Your tone is a mix of gritty detective seriousness and playful humor. "
137
+ "A spammer has just called and said something suspicious. Engage them in a lengthy, multi-turn conversation filled with sarcastic remarks, "
138
+ "overly detailed questions, and witty banter designed to waste their time for at least 30 seconds and at least three conversational turns. "
139
+ "Make sure your responses are concise, humorous and investigative."
140
+ )
141
+ }
142
+ ]
143
+ for entry in chat_history:
144
+ messages.append({"role": entry["role"], "content": entry["content"]})
145
+ messages.append({"role": "user", "content": initial_message})
146
+
147
+ assistant_response = llm.invoke(messages)
148
+ if hasattr(assistant_response, "content"):
149
+ assistant_response = assistant_response.content
150
+ else:
151
+ assistant_response = str(assistant_response)
152
+
153
+ update_chat_history(caller_number, "user", initial_message)
154
+ update_chat_history(caller_number, "assistant", assistant_response)
155
+
156
+ return assistant_response
157
+
158
+ # ---------------------------
159
+ # Endpoints for Contacts, Texts, and Call Forwarding Setup
160
+ # ---------------------------
161
+ @app.post("/contacts", response_model=List[Contact])
162
+ def create_contacts(contacts: List[Contact]):
163
+ """
164
+ Save a list of contacts into MongoDB.
165
+ """
166
+ contacts_to_insert = [contact.dict() for contact in contacts]
167
+ result = contacts_collection.insert_many(contacts_to_insert)
168
+ if not result.inserted_ids:
169
+ raise HTTPException(status_code=500, detail="Error inserting contacts")
170
+ return contacts
171
+
172
+ @app.post("/incoming-message")
173
+ def process_incoming_message(incoming: IncomingMessage):
174
+ """
175
+ Process an incoming text message:
176
+ - If the sender's number is in contacts, forward it normally.
177
+ - Otherwise, simulate a multi-turn conversation using the AI bot.
178
+ """
179
+ contact = contacts_collection.find_one({"phone": incoming.phone})
180
+ if contact:
181
+ return {
182
+ "status": "contact_found",
183
+ "detail": f"Primary: {incoming.phone} – '{incoming.message}'"
184
+ }
185
+ else:
186
+ conversation_result = simulate_text_conversation(incoming.phone, incoming.message)
187
+ return {
188
+ "status": "not_in_contacts",
189
+ "conversation_result": conversation_result
190
+ }
191
+
192
+ @app.post("/setup-call-forwarding")
193
+ def setup_call_forwarding():
194
+ """
195
+ Simulate call forwarding setup.
196
+ """
197
+ forwarding_number = "+1-555-123-4567"
198
+ return {"status": "success", "message": f"Setup done! Calls forwarded to {forwarding_number}"}
199
+
200
+ # ---------------------------
201
+ # STT and TTS Functions for Voice Calls
202
+ # ---------------------------
203
+ def transcribe_audio(audio_file: bytes) -> str:
204
+ """
205
+ Convert incoming audio to text using Groq Whisper v3 (STT).
206
+ """
207
+ response = groq_client.audio.transcriptions.create(
208
+ file=("audio.m4a", audio_file),
209
+ model="whisper-large-v3",
210
+ response_format="verbose_json"
211
+ )
212
+ return response.text
213
+
214
+ def text_to_speech(text: str) -> bytes:
215
+ """
216
+ Convert text to speech using Cartesia TTS.
217
+ """
218
+ audio_bytes = cartesia_client.tts.bytes(
219
+ model_id="sonic",
220
+ transcript=text,
221
+ voice_id="694f9389-aac1-45b6-b726-9d9369183238", # Example voice
222
+ output_format={
223
+ "container": "wav",
224
+ "encoding": "pcm_f32le",
225
+ "sample_rate": 44100,
226
+ },
227
+ )
228
+ return audio_bytes
229
+
230
+ # ---------------------------
231
+ # Endpoint for Processing Voice Calls
232
+ # ---------------------------
233
+ @app.post("/process-call")
234
+ async def process_call(caller_number: str = Form(...), audio: UploadFile = File(...)):
235
+ """
236
+ Process an incoming voice call:
237
+ - If the caller is in contacts, return a normal "ringing" message.
238
+ - Otherwise, transcribe the audio (STT), simulate a multi-turn conversation using the AI bot (with detective humor),
239
+ log the conversation, and return a TTS audio response.
240
+ """
241
+ # Check if caller is in contacts
242
+ contact = contacts_collection.find_one({"phone": caller_number})
243
+ if contact:
244
+ ringing_text = f"Call from {caller_number} – Ringing"
245
+ response_audio = text_to_speech(ringing_text)
246
+ return Response(content=response_audio, media_type="audio/wav")
247
+
248
+ try:
249
+ audio_bytes = await audio.read()
250
+ except Exception as e:
251
+ raise HTTPException(status_code=400, detail=f"Error reading audio file: {str(e)}")
252
+
253
+ try:
254
+ transcription = transcribe_audio(audio_bytes)
255
+ except Exception as e:
256
+ raise HTTPException(status_code=500, detail=f"Error during transcription: {str(e)}")
257
+
258
+ try:
259
+ conversation_result = simulate_call_conversation(caller_number, transcription)
260
+ except Exception as e:
261
+ raise HTTPException(status_code=500, detail=f"Error during AI conversation: {str(e)}")
262
+
263
+ try:
264
+ response_audio = text_to_speech(conversation_result)
265
+ except Exception as e:
266
+ raise HTTPException(status_code=500, detail=f"Error during TTS conversion: {str(e)}")
267
+
268
+ return Response(content=response_audio, media_type="audio/wav")
269
+
270
+ if __name__ == "__main__":
271
+ import uvicorn
272
+ uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ pymongo
4
+ python-dotenv
5
+ python-multipart
6
+ twilio
7
+ websockets
8
+ openai
9
+ langchain_openai