Spaces:
Running
Running
| 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() | |