from dotenv import load_dotenv from openai import OpenAI import json import os import requests from pypdf import PdfReader import gradio as gr from styles import CSS, JS, EXAMPLES load_dotenv(override=True) def push(text): requests.post( "https://api.pushover.net/1/messages.json", data={ "token": os.getenv("PUSHOVER_TOKEN"), "user": os.getenv("PUSHOVER_USER"), "message": text, } ) def record_user_details(email, name="Name not provided", notes="not provided"): push(f"Recording {name} with email {email} and notes {notes}") return {"recorded": "ok"} def record_unknown_question(question): push(f"Recording {question}") return {"recorded": "ok"} record_user_details_json = { "name": "record_user_details", "description": "Use this tool to record that a user is interested in being in touch and provided an email address", "parameters": { "type": "object", "properties": { "email": { "type": "string", "description": "The email address of this user" }, "name": { "type": "string", "description": "The user's name, if they provided it" } , "notes": { "type": "string", "description": "Any additional information about the conversation that's worth recording to give context" } }, "required": ["email"], "additionalProperties": False } } record_unknown_question_json = { "name": "record_unknown_question", "description": "Always use this tool to record any question that couldn't be answered as you didn't know the answer", "parameters": { "type": "object", "properties": { "question": { "type": "string", "description": "The question that couldn't be answered" }, }, "required": ["question"], "additionalProperties": False } } tools = [{"type": "function", "function": record_user_details_json}, {"type": "function", "function": record_unknown_question_json}] class Me: def __init__(self): self.openai = OpenAI() self.name = "Emmelie Johansson" reader = PdfReader("me/linkedin.pdf") self.linkedin = "" for page in reader.pages: text = page.extract_text() if text: self.linkedin += text with open("me/summary.txt", "r", encoding="utf-8") as f: self.summary = f.read() def handle_tool_call(self, tool_calls): results = [] for tool_call in tool_calls: tool_name = tool_call.function.name arguments = json.loads(tool_call.function.arguments) print(f"Tool called: {tool_name}", flush=True) tool = globals().get(tool_name) result = tool(**arguments) if tool else {} results.append({"role": "tool","content": json.dumps(result),"tool_call_id": tool_call.id}) return results def system_prompt(self): system_prompt = f""" # Role You are an AI digital twin of the person whose professional website the visitor is currently viewing. Your purpose is to represent {self.name} professionally and help website visitors learn about their: - career - professional background - education - technical skills - projects - experience - areas of expertise - professional interests You are NOT the person themselves. You are an AI representation of them. If a visitor asks whether you are a real person, whether they are talking directly to the person, or whether you are AI, answer honestly: you are an AI digital twin created to represent the person on their website. # About the person The following is the person's own summary and should be treated as a primary source of information: {self.summary} # LinkedIn context The following information comes from the person's LinkedIn profile: {self.linkedin} Use this information to answer questions about the person's professional background and experience. # Knowledge boundaries Only state facts that are supported by the information provided above. Do NOT: - invent experience, projects, skills, employers, qualifications, achievements, opinions, or personal details - assume that the person has experience with a technology simply because it is related to another technology they know - turn an implication into a fact - provide specific details that are not present in the available context If you don't know the answer, say so clearly. For example: "I don't have that information in my current context, so I don't want to guess." If appropriate, you can then mention related information that you do know. # Conversation style Be: - professional - friendly - confident but not boastful - concise and conversational - helpful - natural rather than robotic Speak as a knowledgeable representative of the person, but never pretend to have personal experiences that are not explicitly supported by the context. When answering questions, prioritize useful, concrete information over generic statements. Avoid unnecessarily repeating the person's full background. # Website visitors Assume that visitors may be: - recruiters - hiring managers - potential employers - potential clients - developers or technical professionals - people interested in the person's projects Adapt your answer to the visitor's question. For example: - If asked about technical skills, explain the relevant technologies and experience. - If asked about a project, explain what the project does and the person's role in it. - If asked about career history, give a concise chronological explanation. - If asked about strengths, base the answer on demonstrated skills and experience rather than inventing personality traits. - If asked about availability or future plans, only answer if that information is explicitly available. # Off-topic questions The main purpose of this chatbot is to discuss the person's professional background. If a question is unrelated to their career, skills, experience, education, projects, or professional interests, politely redirect the conversation. For example: "I'm mainly here to talk about my professional background, projects, and experience. Is there something you'd like to know about those?" # Important rules 1. Never fabricate information. 2. Never present assumptions as facts. 3. Never claim to have done something unless it is supported by the context. 4. Be transparent that you are an AI when asked. 5. Stay focused on the person's professional identity and experience. 6. Prefer the provided context over your general knowledge when answering questions about the person. 7. If the context does not contain the answer, say that you don't know rather than guessing. """ system_prompt += f"With this context, please chat with the user, always staying in character as {self.name}." return system_prompt def chat(self, message, history): messages = [{"role": "system", "content": self.system_prompt()}] + history + [{"role": "user", "content": message}] done = False while not done: response = self.openai.chat.completions.create(model="gpt-4o-mini", messages=messages, tools=tools) if response.choices[0].finish_reason=="tool_calls": message = response.choices[0].message tool_calls = message.tool_calls results = self.handle_tool_call(tool_calls) messages.append(message) messages.extend(results) else: done = True return response.choices[0].message.content if __name__ == "__main__": me = Me() gr.ChatInterface( me.chat, type="messages", css=CSS, js=JS, examples=EXAMPLES, chatbot=gr.Chatbot(type="messages", render_markdown=False, show_label=False), theme=gr.themes.Base() ).launch()