Commit ·
dec867e
1
Parent(s): 22b3bd0
cleaned app
Browse files
app.py
CHANGED
|
@@ -195,216 +195,6 @@ tools = [{"type": "function", "function": record_user_details_json},
|
|
| 195 |
{"type": "function", "function": list_github_repos_json}]
|
| 196 |
|
| 197 |
|
| 198 |
-
class Me:
|
| 199 |
-
|
| 200 |
-
def __init__(self):
|
| 201 |
-
self.openai_api_key = os.getenv('OPENAI_API_KEY')
|
| 202 |
-
self.name = "Gabriel Olatunji"
|
| 203 |
-
self.profiles = {'linkedin': '', 'resume': ''}
|
| 204 |
-
self.summary = ""
|
| 205 |
-
|
| 206 |
-
DATASET_ID = "O-G-O/about_me_bot_docs"
|
| 207 |
-
|
| 208 |
-
# Download all dataset files from HF
|
| 209 |
-
files = list_repo_files(DATASET_ID, repo_type="dataset")
|
| 210 |
-
hf_paths = {}from dotenv import load_dotenv
|
| 211 |
-
from openai import OpenAI
|
| 212 |
-
import json
|
| 213 |
-
import os
|
| 214 |
-
import requests
|
| 215 |
-
import re
|
| 216 |
-
from pypdf import PdfReader
|
| 217 |
-
|
| 218 |
-
from huggingface_hub import hf_hub_download, list_repo_files
|
| 219 |
-
|
| 220 |
-
import gradio as gr
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
load_dotenv(override=True)
|
| 224 |
-
|
| 225 |
-
def push(text):
|
| 226 |
-
payload = {"token": os.getenv("PUSHOVER_TOKEN"),
|
| 227 |
-
"user": os.getenv("PUSHOVER_USER"),
|
| 228 |
-
"message": text
|
| 229 |
-
}
|
| 230 |
-
pushover_url = "https://api.pushover.net/1/messages.json"
|
| 231 |
-
requests.post(pushover_url, data=payload)
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
def record_user_details(email, name="Name not provided", notes="not provided"):
|
| 235 |
-
"""
|
| 236 |
-
Records user details and sends a push notification about the new entry.
|
| 237 |
-
|
| 238 |
-
This function takes a user's email, optional name, and optional notes.
|
| 239 |
-
It then sends a push notification (to my window chrome) summarizing
|
| 240 |
-
the recorded interest. It's intended to be used in conjunction with information or a structure
|
| 241 |
-
defined by `record_user_details_json`.
|
| 242 |
-
|
| 243 |
-
Args:
|
| 244 |
-
email (str): The email address of the user. This is a mandatory field.
|
| 245 |
-
name (str, optional): The name of the user. Defaults to "Name not provided".
|
| 246 |
-
notes (str, optional): Any additional notes or information about the user's interest.
|
| 247 |
-
Defaults to "not provided".
|
| 248 |
-
|
| 249 |
-
Returns:
|
| 250 |
-
dict: A dictionary indicating the status of the recording.
|
| 251 |
-
Currently returns `{"recorded": "ok"}` regardless of actual storage.
|
| 252 |
-
"""
|
| 253 |
-
# use information of record_user_details_json
|
| 254 |
-
push(f"Recording interest from {name} with email {email} and notes {notes}")
|
| 255 |
-
return {"recorded": "ok"}
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
def record_unknown_question(question):
|
| 259 |
-
"""
|
| 260 |
-
Records a user's question that the system was unable to answer and sends a push notification.
|
| 261 |
-
|
| 262 |
-
This function takes a question string, sends a push notification
|
| 263 |
-
to alert about the unanswerable question, and indicates that the question has been
|
| 264 |
-
"recorded". It is intended to integrate the structure from `record_unknown_question_json`
|
| 265 |
-
for storage and logging.
|
| 266 |
-
|
| 267 |
-
Args:
|
| 268 |
-
question (str): The specific question that the system could not answer.
|
| 269 |
-
|
| 270 |
-
Returns:
|
| 271 |
-
dict: A dictionary indicating the status of the recording.
|
| 272 |
-
Currently returns `{"recorded": "ok"}` regardless of actual storage.
|
| 273 |
-
"""
|
| 274 |
-
# use information of record_unknown_question_json
|
| 275 |
-
push(f"Recording {question} asked that I couldn't answer")
|
| 276 |
-
return {"recorded": "ok"}
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
def list_github_repos(profile_url: str):
|
| 280 |
-
"""
|
| 281 |
-
Lists all public repositories name, url and description
|
| 282 |
-
|
| 283 |
-
Args:
|
| 284 |
-
profile_url (str): GitHub profile URL.
|
| 285 |
-
|
| 286 |
-
Returns:
|
| 287 |
-
dict: A dictionary containing a list of public repository details if successful.
|
| 288 |
-
Returns a dictionary with an "error" key if the username cannot be extracted,
|
| 289 |
-
or the API request fails.
|
| 290 |
-
"""
|
| 291 |
-
# 1. Extract username from the URL
|
| 292 |
-
match = re.search(r'github\.com/([^/]+)', profile_url)
|
| 293 |
-
username = match.group(1)
|
| 294 |
-
|
| 295 |
-
base_api_url = f"https://api.github.com/users/{username}/repos"
|
| 296 |
-
|
| 297 |
-
headers = {
|
| 298 |
-
"Accept": "application/vnd.github.v3+json"
|
| 299 |
-
}
|
| 300 |
-
|
| 301 |
-
all_repos = []
|
| 302 |
-
page = 1
|
| 303 |
-
per_page = 100
|
| 304 |
-
|
| 305 |
-
print(f"Attempting to list PUBLIC repositories for {username}...", flush=True)
|
| 306 |
-
|
| 307 |
-
try:
|
| 308 |
-
while True:
|
| 309 |
-
params = {"page": page, "per_page": per_page, "type": "public"}
|
| 310 |
-
response = requests.get(base_api_url, headers=headers, params=params)
|
| 311 |
-
response.raise_for_status()
|
| 312 |
-
|
| 313 |
-
repos_on_page = response.json()
|
| 314 |
-
if not repos_on_page:
|
| 315 |
-
break
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
for repo in repos_on_page:
|
| 319 |
-
if not repo.get("private"):
|
| 320 |
-
all_repos.append({
|
| 321 |
-
"name": repo.get("name"),
|
| 322 |
-
"html_url": repo.get("html_url"),
|
| 323 |
-
"description": repo.get("description"),
|
| 324 |
-
})
|
| 325 |
-
|
| 326 |
-
# Check for 'Link' header to determine if there are more pages
|
| 327 |
-
if 'link' not in response.headers or 'rel="next"' not in response.headers['link']:
|
| 328 |
-
break # No more pages
|
| 329 |
-
|
| 330 |
-
page += 1
|
| 331 |
-
|
| 332 |
-
return {"repositories": all_repos, "count": len(all_repos)}
|
| 333 |
-
|
| 334 |
-
except requests.exceptions.ConnectionError as errc:
|
| 335 |
-
return {"error": f"Connection error fetching public repositories: {errc}. Check internet connection."}
|
| 336 |
-
|
| 337 |
-
except requests.exceptions.Timeout as errt:
|
| 338 |
-
return {"error": f"Timeout error fetching public repositories: {errt}. GitHub API took too long to respond."}
|
| 339 |
-
|
| 340 |
-
except requests.exceptions.RequestException as err:
|
| 341 |
-
return {"error": f"An unexpected error occurred during GitHub API request: {err}"}
|
| 342 |
-
|
| 343 |
-
except json.JSONDecodeError:
|
| 344 |
-
return {"error": "Failed to decode JSON response from GitHub API. Invalid API response."}
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
record_user_details_json = {
|
| 348 |
-
"name": "record_user_details",
|
| 349 |
-
"description": "Use this tool to record that a user is interested in being in touch and provided an email address",
|
| 350 |
-
"parameters": {
|
| 351 |
-
"type": "object",
|
| 352 |
-
"properties": {
|
| 353 |
-
"email": {
|
| 354 |
-
"type": "string",
|
| 355 |
-
"description": "The email address of this user"
|
| 356 |
-
},
|
| 357 |
-
"name": {
|
| 358 |
-
"type": "string",
|
| 359 |
-
"description": "The user's name, if they provided it"
|
| 360 |
-
}
|
| 361 |
-
,
|
| 362 |
-
"notes": {
|
| 363 |
-
"type": "string",
|
| 364 |
-
"description": "Any additional information about the conversation that's worth recording to give context"
|
| 365 |
-
}
|
| 366 |
-
},
|
| 367 |
-
"required": ["email"],
|
| 368 |
-
"additionalProperties": False
|
| 369 |
-
}
|
| 370 |
-
}
|
| 371 |
-
|
| 372 |
-
record_unknown_question_json = {
|
| 373 |
-
"name": "record_unknown_question",
|
| 374 |
-
"description": "Always use this tool to record any question that couldn't be answered as you didn't know the answer",
|
| 375 |
-
"parameters": {
|
| 376 |
-
"type": "object",
|
| 377 |
-
"properties": {
|
| 378 |
-
"question": {
|
| 379 |
-
"type": "string",
|
| 380 |
-
"description": "The question that couldn't be answered"
|
| 381 |
-
},
|
| 382 |
-
},
|
| 383 |
-
"required": ["question"]
|
| 384 |
-
}
|
| 385 |
-
}
|
| 386 |
-
|
| 387 |
-
list_github_repos_json = {
|
| 388 |
-
"name": "list_github_repos",
|
| 389 |
-
"description": "Always use this tool to list all public repository names, URLs, and descriptions.",
|
| 390 |
-
"parameters": {
|
| 391 |
-
"type": "object",
|
| 392 |
-
"properties": {
|
| 393 |
-
"profile_url": {
|
| 394 |
-
"type": "string",
|
| 395 |
-
"description": "The full GitHub profile URL (e.g. https://github.com/omogbolahan94)"
|
| 396 |
-
}
|
| 397 |
-
},
|
| 398 |
-
"required": ["profile_url"],
|
| 399 |
-
"additionalProperties": False
|
| 400 |
-
}
|
| 401 |
-
}
|
| 402 |
-
|
| 403 |
-
tools = [{"type": "function", "function": record_user_details_json},
|
| 404 |
-
{"type": "function", "function": record_unknown_question_json},
|
| 405 |
-
{"type": "function", "function": list_github_repos_json}]
|
| 406 |
-
|
| 407 |
-
|
| 408 |
class Me:
|
| 409 |
|
| 410 |
def __init__(self):
|
|
@@ -502,100 +292,6 @@ class Me:
|
|
| 502 |
return response.choices[0].message.content
|
| 503 |
|
| 504 |
|
| 505 |
-
if __name__ == "__main__":
|
| 506 |
-
me = Me()
|
| 507 |
-
gr.ChatInterface(me.chat, type="messages").launch()
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
for f in files:
|
| 513 |
-
if f.endswith((".pdf", ".txt")):
|
| 514 |
-
hf_paths[f] = hf_hub_download(
|
| 515 |
-
repo_id=DATASET_ID,
|
| 516 |
-
filename=f,
|
| 517 |
-
repo_type="dataset"
|
| 518 |
-
)
|
| 519 |
-
|
| 520 |
-
# Load PDF files for profiles
|
| 521 |
-
for profile in self.profiles:
|
| 522 |
-
pdf_filename = f"{profile}.pdf"
|
| 523 |
-
if pdf_filename in hf_paths:
|
| 524 |
-
reader = PdfReader(hf_paths[pdf_filename])
|
| 525 |
-
result_str = ""
|
| 526 |
-
for page in reader.pages:
|
| 527 |
-
text = page.extract_text()
|
| 528 |
-
if text:
|
| 529 |
-
result_str += text
|
| 530 |
-
self.profiles[profile] += result_str
|
| 531 |
-
|
| 532 |
-
self.linkedin = self.profiles['linkedin']
|
| 533 |
-
self.resume = self.profiles['resume']
|
| 534 |
-
|
| 535 |
-
# Load summary.txt from HF
|
| 536 |
-
summary_filename = "summary.txt"
|
| 537 |
-
if summary_filename in hf_paths:
|
| 538 |
-
with open(hf_paths[summary_filename], "r", encoding="utf-8") as f:
|
| 539 |
-
self.summary = f.read()
|
| 540 |
-
|
| 541 |
-
def handle_tool_calls(self, tool_calls):
|
| 542 |
-
results = []
|
| 543 |
-
for tool_call in tool_calls:
|
| 544 |
-
tool_name = tool_call.function.name
|
| 545 |
-
arguments = json.loads(tool_call.function.arguments)
|
| 546 |
-
print(f"Tool called: {tool_name}", flush=True)
|
| 547 |
-
tool = globals().get(tool_name)
|
| 548 |
-
result = tool(**arguments) if tool else {}
|
| 549 |
-
results.append({"role": "tool","content": json.dumps(result),"tool_call_id": tool_call.id})
|
| 550 |
-
return results
|
| 551 |
-
|
| 552 |
-
|
| 553 |
-
def system_prompt(self):
|
| 554 |
-
system_prompt = f"You are acting as {self.name}. You are answering questions on {self.name}'s website, \
|
| 555 |
-
particularly questions related to {self.name}'s career, background, skills and experience. \
|
| 556 |
-
Your responsibility is to represent {self.name} for interactions on the website as faithfully as possible. \
|
| 557 |
-
You are given a summary of {self.name}'s background and LinkedIn profile which you can use to answer questions. \
|
| 558 |
-
Be professional and engaging, as if talking to a potential client or future employer who came across the website. \
|
| 559 |
-
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. \
|
| 560 |
-
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.\
|
| 561 |
-
If the user requested to know about any projects you have worked on, use your list_github_repos tool to access your github repository and list repos with their names, url and description and then describe all relevant project in a well structured format even if it has to be in a bullet point format."
|
| 562 |
-
|
| 563 |
-
system_prompt += f"\n\n## Summary:\n{self.summary}\n\n## LinkedIn Profile:\n{self.linkedin}\n\n"
|
| 564 |
-
system_prompt += f"With this context, please chat with the user, always staying in character as {self.name}."
|
| 565 |
-
return system_prompt
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
def chat(self, message, history):
|
| 569 |
-
messages = [{"role": "system", "content": self.system_prompt()}] + history + [{"role": "user", "content": message}]
|
| 570 |
-
|
| 571 |
-
done = False
|
| 572 |
-
while not done:
|
| 573 |
-
# gemini = OpenAI(api_key=self.google_api_key, base_url=self.google_gai_url)
|
| 574 |
-
# model_name = "gemini-2.0-flash"
|
| 575 |
-
# response = gemini.chat.completions.create(model=model_name, messages=messages, tools=tools)
|
| 576 |
-
# finish_reason = response.choices[0].finish_reason
|
| 577 |
-
openai_client = OpenAI(api_key=self.openai_api_key)
|
| 578 |
-
|
| 579 |
-
model_name = "gpt-3.5-turbo"
|
| 580 |
-
|
| 581 |
-
response = openai_client.chat.completions.create(
|
| 582 |
-
model=model_name,
|
| 583 |
-
messages=messages,
|
| 584 |
-
tools=tools
|
| 585 |
-
)
|
| 586 |
-
finish_reason = response.choices[0].finish_reason
|
| 587 |
-
|
| 588 |
-
if finish_reason=="tool_calls":
|
| 589 |
-
message = response.choices[0].message
|
| 590 |
-
tool_calls = message.tool_calls
|
| 591 |
-
results = self.handle_tool_calls(tool_calls)
|
| 592 |
-
messages.append(message)
|
| 593 |
-
messages.extend(results)
|
| 594 |
-
else:
|
| 595 |
-
done = True
|
| 596 |
-
return response.choices[0].message.content
|
| 597 |
-
|
| 598 |
-
|
| 599 |
if __name__ == "__main__":
|
| 600 |
me = Me()
|
| 601 |
gr.ChatInterface(me.chat, type="messages").launch()
|
|
|
|
| 195 |
{"type": "function", "function": list_github_repos_json}]
|
| 196 |
|
| 197 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
class Me:
|
| 199 |
|
| 200 |
def __init__(self):
|
|
|
|
| 292 |
return response.choices[0].message.content
|
| 293 |
|
| 294 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
if __name__ == "__main__":
|
| 296 |
me = Me()
|
| 297 |
gr.ChatInterface(me.chat, type="messages").launch()
|