from dotenv import load_dotenv from openai import OpenAI import json import os import requests from pypdf import PdfReader import gradio as gr from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import FAISS import numpy as np import time from datetime import datetime # Load environment variables load_dotenv(override=True) # Check if required environment variables are set def check_env_vars(): required_vars = ['OPENAI_API_KEY', 'PUSHOVER_USER', 'PUSHOVER_TOKEN'] missing_vars = [] for var in required_vars: if not os.getenv(var): missing_vars.append(var) if missing_vars: print(f"Warning: Missing environment variables: {missing_vars}") print("Please set these in your .env file or HuggingFace secrets") return False return True def push(text): # Only push if credentials are available if not (os.getenv("PUSHOVER_TOKEN") and os.getenv("PUSHOVER_USER")): print(f"Push notification (credentials not available): {text}") return try: requests.post( "https://api.pushover.net/1/messages.json", data={ "token": os.getenv("PUSHOVER_TOKEN"), "user": os.getenv("PUSHOVER_USER"), "message": text, } ) except Exception as e: print(f"Failed to send push notification: {e}") def record_user_details(email, name="Name not provided", notes="not provided"): push(f"🎉 New connection! {name} ({email}) - {notes}") return {"recorded": "ok", "message": "Thanks for connecting! I'll be in touch soon."} def record_unknown_question(question): push(f"🤔 Interesting question I couldn't answer: {question}") return {"recorded": "ok", "message": "That's a great question! I'll research this and get back to you."} def evaluate_response(question, response): """Evaluate the quality and relevance of the response""" print("\n=== Evaluation Debug ===") print(f"Evaluating response for question: {question}") evaluation_prompt = f""" Question: {question} Response: {response} Please evaluate this response on a scale of 1-10 for: 1. Relevance to the question 2. Professionalism 3. Completeness 4. Clarity Provide a brief explanation for each score. """ client = OpenAI() evaluation = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": evaluation_prompt}] ) print(f"Evaluation result: {evaluation.choices[0].message.content[:200]}...") # Print first 200 chars return evaluation.choices[0].message.content def get_relevant_context(question, vectorstore): """Retrieve relevant context from the vector store""" print("\n=== RAG Debug ===") print(f"Searching for context for question: {question}") docs = vectorstore.similarity_search(question, k=3) context = "\n".join([doc.page_content for doc in docs]) print(f"Found relevant context: {context[:200]}...") # Print first 200 chars return context def create_welcome_message(): """Create a personalized welcome message""" return """👋 **Welcome! I'm Monideep Chakraborti** I'm a Product Manager passionate about building intelligent technology that makes search, communication, and learning more accessible and inclusive. **What I do:** • 🏥 Lead biomedical search modernization at NCBI/NIH (6M+ daily users) • 🎯 Build AI-powered speech accessibility tools • 🧠 Design GenAI applications for learning and decision-making • 💡 Bridge product strategy with technical execution **Ask me about:** • My work at NIH and biomedical search • Speech accessibility and ASR systems • Product management in AI/ML • GenAI applications and prompt engineering • My side projects and research interests Let's connect and explore how we can build something amazing together! 🚀""" 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 } } evaluate_response_json = { "name": "evaluate_response", "description": "Evaluate the quality and relevance of a response to a question", "parameters": { "type": "object", "properties": { "question": { "type": "string", "description": "The original question" }, "response": { "type": "string", "description": "The response to evaluate" } }, "required": ["question", "response"], "additionalProperties": False } } tools = [ {"type": "function", "function": record_user_details_json}, {"type": "function", "function": record_unknown_question_json}, {"type": "function", "function": evaluate_response_json} ] class Monideep: def __init__(self): self.openai = OpenAI() self.name = "Monideep Chakraborti" # Load and process documents with error handling try: reader = PdfReader("me/linkedin.pdf") self.linkedin = "" for page in reader.pages: text = page.extract_text() if text: self.linkedin += text print("✅ LinkedIn PDF loaded successfully") except Exception as e: print(f"⚠️ Could not load LinkedIn PDF: {e}") self.linkedin = "Experienced Product Manager with expertise in AI/ML, biomedical search, and speech accessibility." try: with open("me/summary.txt", "r", encoding="utf-8") as f: self.summary = f.read() print("✅ Summary file loaded successfully") except Exception as e: print(f"⚠️ Could not load summary file: {e}") self.summary = "I'm a Product Manager focused on building technology that makes search, communication, and learning more intelligent and inclusive." # Create vector store for RAG (only if API key is available) self.vectorstore = None if os.getenv("OPENAI_API_KEY"): try: self.text_splitter = RecursiveCharacterTextSplitter( chunk_size=1000, chunk_overlap=200 ) # Combine all text sources all_text = f"{self.summary}\n\n{self.linkedin}" texts = self.text_splitter.split_text(all_text) # Create embeddings and vector store embeddings = OpenAIEmbeddings() self.vectorstore = FAISS.from_texts(texts, embeddings) print("✅ Vector store created successfully") except Exception as e: print(f"⚠️ Failed to create vector store: {e}") print("Continuing without RAG functionality") else: print("⚠️ OpenAI API key not found. Continuing without RAG functionality") 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) if tool_name == "evaluate_response": result = evaluate_response(**arguments) else: 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"""You are acting as {self.name}. You are answering questions on {self.name}'s website, \ particularly questions related to {self.name}'s career, background, skills and experience. \ Your responsibility is to represent {self.name} for interactions on the website as faithfully as possible. \ You are given a summary of {self.name}'s background and LinkedIn profile which you can use to answer questions. \ Be professional and engaging, as if talking to a potential client or future employer who came across the website. \ 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. \ 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. \ After providing a response, use the evaluate_response tool to evaluate the quality of your response.""" system_prompt += f"\n\n## Summary:\n{self.summary}\n\n## LinkedIn Profile:\n{self.linkedin}\n\n" 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): print("\n=== Chat Debug ===") print(f"Processing new message: {message}") # Get relevant context from vector store (if available) if self.vectorstore: relevant_context = get_relevant_context(message, self.vectorstore) enhanced_message = f"""Context from knowledge base: {relevant_context} User question: {message}""" else: # Fallback without RAG enhanced_message = message print("Using fallback mode without RAG") print(f"Enhanced message with context: {enhanced_message[:200]}...") # Print first 200 chars # Convert history to the format expected by OpenAI messages = [{"role": "system", "content": self.system_prompt()}] # Add conversation history for msg in history: if isinstance(msg, list) and len(msg) == 2: # Old format: [user_msg, assistant_msg] if msg[0]: # User message messages.append({"role": "user", "content": msg[0]}) if msg[1]: # Assistant message messages.append({"role": "assistant", "content": msg[1]}) elif isinstance(msg, dict): # New format: {"role": "user", "content": "..."} messages.append(msg) # Add current message messages.append({"role": "user", "content": enhanced_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 print(f"Tool calls requested: {[tool.function.name for tool in tool_calls]}") results = self.handle_tool_call(tool_calls) messages.append(message) messages.extend(results) else: done = True print(f"Final response: {response.choices[0].message.content[:200]}...") # Print first 200 chars return response.choices[0].message.content def create_custom_theme(): """Create a custom theme for the app""" return gr.themes.Soft( primary_hue="blue", secondary_hue="teal", neutral_hue="slate", radius_size="lg", font=["Inter", "ui-sans-serif", "system-ui", "sans-serif"], font_mono=["JetBrains Mono", "ui-monospace", "SFMono-Regular", "monospace"], ) def create_header(): """Create a custom header component""" with gr.Row(): with gr.Column(scale=1): gr.HTML("""
Product Manager | AI Enthusiast | Biomedical Search Innovator
💡 Built with Gradio & OpenAI | 🔗 Let's connect and build something amazing!
📧 monideep@example.com | 💼 LinkedIn
Product Manager | AI Enthusiast | Biomedical Search Innovator
I'm a Product Manager passionate about building intelligent technology that makes search, communication, and learning more accessible and inclusive.
Let's connect and explore how we can build something amazing together! 🚀
💡 Built with Gradio & OpenAI | 🔗 Let's connect and build something amazing!
📧 monideep@example.com | 💼 LinkedIn