sankarlabs commited on
Commit
73a80eb
·
verified ·
1 Parent(s): d7b03b1

upload app.py

Browse files
Files changed (1) hide show
  1. app.py +285 -0
app.py ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Personal Website Chatbot Application
3
+
4
+ This application creates an AI-powered chatbot that impersonates the owner using their
5
+ LinkedIn profile and personal summary. It uses OpenAI's GPT model with function calling
6
+ to handle conversations and capture leads through Pushover notifications.
7
+ """
8
+
9
+ # Import required libraries
10
+ from dotenv import load_dotenv # For loading environment variables
11
+ from openai import OpenAI # OpenAI API client
12
+ import json # JSON processing for function calls
13
+ import os # Operating system interface
14
+ import requests # HTTP requests for Pushover notifications
15
+ from pypdf import PdfReader # PDF text extraction
16
+ import gradio as gr # Web interface framework
17
+
18
+ # Load environment variables from .env file
19
+ load_dotenv(override=True)
20
+
21
+ # ================================
22
+ # NOTIFICATION FUNCTIONS
23
+ # ================================
24
+
25
+ def push(text):
26
+ """
27
+ Send a notification via Pushover API.
28
+
29
+ Args:
30
+ text (str): The message text to send
31
+ """
32
+ requests.post(
33
+ "https://api.pushover.net/1/messages.json",
34
+ data={
35
+ "token": os.getenv("PUSHOVER_TOKEN"),
36
+ "user": os.getenv("PUSHOVER_USER"),
37
+ "message": text,
38
+ }
39
+ )
40
+
41
+
42
+ # ================================
43
+ # TOOL FUNCTIONS FOR OPENAI
44
+ # ================================
45
+
46
+ def record_user_details(email, name="Name not provided", notes="not provided"):
47
+ """
48
+ Record user contact details and send notification.
49
+
50
+ This function is called by OpenAI when a user provides their contact information.
51
+
52
+ Args:
53
+ email (str): User's email address (required)
54
+ name (str): User's name (optional)
55
+ notes (str): Additional conversation context (optional)
56
+
57
+ Returns:
58
+ dict: Success confirmation for OpenAI
59
+ """
60
+ push(f"Recording {name} with email {email} and notes {notes}")
61
+ return {"recorded": "ok"}
62
+
63
+ def record_unknown_question(question):
64
+ """
65
+ Record questions the chatbot couldn't answer.
66
+
67
+ This function is called by OpenAI when encountering unknown questions
68
+ to help improve the chatbot's knowledge base.
69
+
70
+ Args:
71
+ question (str): The question that couldn't be answered
72
+
73
+ Returns:
74
+ dict: Success confirmation for OpenAI
75
+ """
76
+ push(f"Recording {question}")
77
+ return {"recorded": "ok"}
78
+
79
+ # ================================
80
+ # OPENAI FUNCTION SCHEMAS
81
+ # ================================
82
+
83
+ # Schema definition for the user details recording function
84
+ record_user_details_json = {
85
+ "name": "record_user_details",
86
+ "description": "Use this tool to record that a user is interested in being in touch and provided an email address",
87
+ "parameters": {
88
+ "type": "object",
89
+ "properties": {
90
+ "email": {
91
+ "type": "string",
92
+ "description": "The email address of this user"
93
+ },
94
+ "name": {
95
+ "type": "string",
96
+ "description": "The user's name, if they provided it"
97
+ },
98
+ "notes": {
99
+ "type": "string",
100
+ "description": "Any additional information about the conversation that's worth recording to give context"
101
+ }
102
+ },
103
+ "required": ["email"],
104
+ "additionalProperties": False
105
+ }
106
+ }
107
+
108
+ # Schema definition for the unknown question recording function
109
+ record_unknown_question_json = {
110
+ "name": "record_unknown_question",
111
+ "description": "Always use this tool to record any question that couldn't be answered as you didn't know the answer",
112
+ "parameters": {
113
+ "type": "object",
114
+ "properties": {
115
+ "question": {
116
+ "type": "string",
117
+ "description": "The question that couldn't be answered"
118
+ },
119
+ },
120
+ "required": ["question"],
121
+ "additionalProperties": False
122
+ }
123
+ }
124
+
125
+ # Combined tools list for OpenAI function calling
126
+ tools = [
127
+ {"type": "function", "function": record_user_details_json},
128
+ {"type": "function", "function": record_unknown_question_json}
129
+ ]
130
+
131
+
132
+ # ================================
133
+ # MAIN CHATBOT CLASS
134
+ # ================================
135
+
136
+ class Me:
137
+ """
138
+ Main chatbot class that handles conversations and impersonates the owner.
139
+
140
+ This class loads profile data, manages OpenAI interactions, and handles
141
+ function calling for lead capture and question tracking.
142
+ """
143
+
144
+ def __init__(self):
145
+ """
146
+ Initialize the chatbot with profile data and OpenAI client.
147
+
148
+ Loads LinkedIn PDF and summary text file to create the chatbot's
149
+ knowledge base about the owner.
150
+ """
151
+ # Initialize OpenAI client
152
+ self.openai = OpenAI()
153
+
154
+ # Set the owner's name (hardcoded for now)
155
+ self.name = "Gowrisankar"
156
+
157
+ # Load LinkedIn profile from PDF
158
+ reader = PdfReader("documents/linkedin.pdf")
159
+ self.linkedin = ""
160
+ for page in reader.pages:
161
+ text = page.extract_text()
162
+ if text:
163
+ self.linkedin += text
164
+
165
+ # Load personal summary from text file
166
+ with open("documents/summary.txt", "r", encoding="utf-8") as f:
167
+ self.summary = f.read()
168
+
169
+
170
+ def handle_tool_call(self, tool_calls):
171
+ """
172
+ Execute OpenAI function calls and return results.
173
+
174
+ This method processes tool calls from OpenAI, executes the corresponding
175
+ Python functions, and formats the results for the conversation.
176
+
177
+ Args:
178
+ tool_calls: List of tool calls from OpenAI response
179
+
180
+ Returns:
181
+ list: Formatted tool results for OpenAI conversation
182
+ """
183
+ results = []
184
+ for tool_call in tool_calls:
185
+ # Extract tool name and arguments
186
+ tool_name = tool_call.function.name
187
+ arguments = json.loads(tool_call.function.arguments)
188
+ print(f"Tool called: {tool_name}", flush=True)
189
+
190
+ # Get the corresponding Python function and execute it
191
+ tool = globals().get(tool_name)
192
+ result = tool(**arguments) if tool else {}
193
+
194
+ # Format result for OpenAI
195
+ results.append({
196
+ "role": "tool",
197
+ "content": json.dumps(result),
198
+ "tool_call_id": tool_call.id
199
+ })
200
+ return results
201
+
202
+ def system_prompt(self):
203
+ """
204
+ Generate the system prompt with owner's profile data.
205
+
206
+ Creates a comprehensive system prompt that instructs the AI to impersonate
207
+ the owner using their LinkedIn profile and summary information.
208
+
209
+ Returns:
210
+ str: Complete system prompt for OpenAI
211
+ """
212
+ system_prompt = f"""You are acting as {self.name}. You are answering questions on {self.name}'s website, \
213
+ particularly questions related to {self.name}'s career, background, skills and experience. \
214
+ Your responsibility is to represent {self.name} for interactions on the website as faithfully as possible. \
215
+ You are given a summary of {self.name}'s background and LinkedIn profile which you can use to answer questions. \
216
+ Be professional and engaging, as if talking to a potential client or future employer who came across the website. \
217
+ If you don't know the answer to any question, use your record_unknown_question tool to record the question that you couldn't answer, even if it's about something trivial or unrelated to career. \
218
+ If the user is engaging in discussion, try to steer them towards getting in touch via email; ask for their email and record it using your record_user_details tool."""
219
+
220
+ # Add profile data to the prompt
221
+ system_prompt += f"\n\n## Summary:\n{self.summary}\n\n## LinkedIn Profile:\n{self.linkedin}\n\n"
222
+ system_prompt += f"With this context, please chat with the user, always staying in character as {self.name}."
223
+
224
+ return system_prompt
225
+
226
+ def chat(self, message, history):
227
+ """
228
+ Handle a chat message and return the AI response.
229
+
230
+ This method implements the conversation loop with OpenAI, handling
231
+ function calls and continuing the conversation until a final response.
232
+
233
+ Args:
234
+ message (str): User's input message
235
+ history (list): Previous conversation history
236
+
237
+ Returns:
238
+ str: AI's response message
239
+ """
240
+ # Build complete message history including system prompt
241
+ messages = [
242
+ {"role": "system", "content": self.system_prompt()}
243
+ ] + history + [
244
+ {"role": "user", "content": message}
245
+ ]
246
+
247
+ # Continue conversation until no more function calls
248
+ done = False
249
+ while not done:
250
+ # Get response from OpenAI
251
+ response = self.openai.chat.completions.create(
252
+ model="gpt-4o-mini",
253
+ messages=messages,
254
+ tools=tools
255
+ )
256
+
257
+ # Check if OpenAI wants to call functions
258
+ if response.choices[0].finish_reason == "tool_calls":
259
+ # Extract and execute function calls
260
+ message_with_tools = response.choices[0].message
261
+ tool_calls = message_with_tools.tool_calls
262
+ tool_results = self.handle_tool_call(tool_calls)
263
+
264
+ # Add function call and results to conversation
265
+ messages.append(message_with_tools)
266
+ messages.extend(tool_results)
267
+ else:
268
+ # No more function calls, conversation is complete
269
+ done = True
270
+
271
+ return response.choices[0].message.content
272
+
273
+
274
+ # ================================
275
+ # APPLICATION ENTRY POINT
276
+ # ================================
277
+
278
+ if __name__ == "__main__":
279
+ # Initialize the chatbot
280
+ me = Me()
281
+
282
+ # Create and launch the Gradio web interface
283
+ # type="messages" enables proper conversation history handling
284
+ gr.ChatInterface(me.chat, type="messages", cache_examples=False).launch()
285
+