Spaces:
Sleeping
Sleeping
| import os | |
| from openai import OpenAI | |
| import gradio as gr | |
| import uuid | |
| import chromadb | |
| from pprint import pprint | |
| import json | |
| import requests | |
| import random | |
| #--------------------------------------------------------------- | |
| #Setup | |
| #--------------------------------------------------------------- | |
| OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") | |
| if OPENAI_API_KEY is None: | |
| raise Exception("API KEY IS MISSNG") | |
| client = OpenAI() | |
| #--------------------------------------------------------------- | |
| #Document | |
| #--------------------------------------------------------------- | |
| document_overview = """ | |
| Growing up between India and the Middle East, Vaishnavi developed a curiosity for patterns long before she knew what data analytics was.\ | |
| As a self-described fan of crime dramas and mystery shows like Scooby-Doo, she was unknowingly training herself to find meaning in chaos — a skill that would define \ | |
| her career. Today, she brings that same investigative instinct to the world of data.\ | |
| Vaishnavi holds a Bachelor of Science in Electronics & Communication Engineering from CEG, a prestigious institution in Southern India, where she enrolled in 2013. \ | |
| She later pursued a Master of Science in Business & Information Systems at NJIT, where she was first introduced to SQL — the language that would become her professional \ | |
| cornerstone and personal passion.\ | |
| With over 5 years of experience in data analytics, Vaishnavi has built a career that spans HR, Finance, and Operations functions across a range of industries. \ | |
| She has held roles at Merch (Workplace Analytics), Brosnan Risk Consultants (HR Data Analyst), and Target (Lead Data Analyst), developing deep expertise in people analytics \ | |
| along the way. Her technical toolkit is broad and battle-tested: she is fluent in SQL, Python, and Bash, works across platforms such as Tableau, Power BI, Qlik Sense, \ | |
| and Workday, and has applied advanced modeling techniques including causal inference, difference-in-differences, hypothesis testing, regression, clustering, \ | |
| and XGBoost algorithms — building end-to-end data science solutions from scratch.\ | |
| What sets Vaishnavi apart is not just her technical depth, but her genuine investment in people. She has personally mentored 10+ early-career professionals and \ | |
| non-technical individuals looking to break into data analytics, offering guidance through virtual coffee chats and practical advice on navigating the field \ | |
| without a traditional technical background. She believes strongly that curiosity and persistence matter more than credentials, and she leads by example.\ | |
| She stays sharp professionally by attending meetups and continuously learning — because in a field that moves as fast as data, standing still is never an option.\ | |
| If you're early in your career and looking to break into tech or data, Vaishnavi welcomes the conversation. Connect with her on LinkedIn — if she sees she can help,\ | |
| she'll make time for a virtual coffee chat. | |
| """ | |
| document_work_experience = """ Experience | |
| Lead Data Analyst – People Analytics & Insights | Target (Feb 2024 – Present) | |
| At Target, Vaishnavi leads high-impact analytics work at the intersection of workforce strategy and data science. She developed a survival analysis model using Python and SQL to identify the most effective work arrangements for maximizing employee retention, equipping HR leadership with a data-driven playbook for future-of-work decisions. She has applied BERT-based NLP models to extract key employee insights, directly informing senior leadership on DEI, employee sentiment, and the evolving nature of work. Her quantification of a new shift differential pay structure provided leadership with clear visibility into cost implications and resignation risk — enabling smarter compensation strategy. Additionally, she reduced analysis time by 90% by engineering an automated internal mobility pipeline integrating data from three or more sources, delivering first-of-its-kind career progression insights to store leadership. | |
| Senior Data Analyst – People Analytics & Insights | Target (Jan 2022 – Feb 2024) | |
| In her previous role at Target, Vaishnavi collaborated with three or more cross-functional teams on a research initiative exploring career experiences. Applying a range of data science models, she uncovered insights that challenged existing assumptions and published findings internally, equipping over 15 HR partners with evidence-based career development guidance. She engineered a cohort metric using employee badge scan data to measure HQ visit frequency, which she translated into a production-grade interactive HR dashboard that continues to shape hybrid work policies and quarterly workforce strategy. Her exploratory data analysis across multiple systems established a statistically meaningful relationship between minor workplace injuries and 90-day employee termination, directly influencing onboarding initiatives. She also optimized talent acquisition strategy by analyzing Workday recruiting data with Python and SQL, surfacing opportunities to increase internal applicant pipelines. | |
| HR Data Analyst | Brosnan Risk Consultants (Mar 2021 – Dec 2021) | |
| At Brosnan Risk Consultants, Vaishnavi made an immediate and measurable impact. She built a labor prediction regression model in Python to forecast hourly staffing needs by client site, enhancing workforce planning accuracy by 40%. She established a data audit framework that reduced HRIS data quality issues from 30% to just 1%, restoring confidence in headcount forecasting for Financial Planning & Analysis. Most notably, she achieved approximately $1M in cost-per-hire savings by forecasting hourly employee supply and demand through the analysis of over 10,000 structured records from SAP BusinessObjects using SQL and Python. | |
| Data Analyst Co-op | Merck & Co. Inc. (Jan 2020 – Jun 2020) | |
| During her co-op at Merck's Global Real Estate & Workplace Engineering Systems team, Vaishnavi collaborated with over seven Facilities Management managers and subject matter experts to define data requirements, build data model prototypes, and develop a data strategy framework. She integrated data from three or more sources via AWS Athena to create a standardized data mapping model analyzing over eight financial cost categories across global sites. Her benchmarking analysis identified energy cost-saving opportunities on US sites, producing a business case that contributed to $2M in operational savings — results she presented to over 30 senior leaders at a Leadership Summit. She developed a Qlik Sense proof-of-concept dashboard, executed UAT testing on the production tool, and led a WebEx training series for over 150 technical and non-technical users across the Americas, EMEA, and APAC. For her contributions, she was awarded three Merck Inspire Awards within a span of six months. | |
| Business Intelligence Intern | ADNOC Group (Jun 2019 – Aug 2019) | |
| At ADNOC's Global IT team in Abu Dhabi, Vaishnavi collaborated with cross-functional teams to develop an end-to-end Qlik application that monitored real-time sensor data from Oracle 11g across four gas plants. The solution reduced issue resolution time from 60 minutes to 10 minutes. She also revamped the existing IT reporting tool to feed real-time data directly into the dashboarding environment, cutting manual labor hours by 30%. | |
| Data Analyst | CrowdDoing (Jun 2020 – Mar 2021) | |
| As part of the Data Extraction Team at CrowdDoing, Vaishnavi supported data initiatives using Python, Power BI, Tableau, Advanced Excel, GitLab, and BigQuery, reporting directly to the Co-Founder. | |
| Resident Assistant | |
| College of Engineering, Guindy | |
| Jul 2013 - May 2016 · 2 yrs 11 mos | |
| Chennai, Tamil Nadu, India | |
| Conduct Weekly meetings with Residents to discuss their queries to notify to the Resident Counsellor | |
| Coordinate with Residence Staff in planning events | |
| Report issues and problems faced by residents to the Resident Counsellor | |
| Verify Sign-in sheets daily and present to the Resident Counsellor | |
| AI Experience | |
| I recently started exploring AI and went through the course structure by SuperDataScience — the perfect starting point for someone without a CS background. | |
| I built a full end-to-end RAG pipeline to create a Digital Twin of my professional self. It currently answers questions about my professional experience using chunking, embeddings and vector storage. I also integrated LLM tool calling so that if someone wants to connect, I get notified directly. And if a question falls outside the scope of what my Digital Twin knows, it flags me so I can continuously update and improve it along the way. | |
| What excites me most about this build is that it is not static — it grows as I do. | |
| Next up, I am diving deeper into Agentic AI, building personal passion projects along the way and hoping to eventually bring this into my workplace in a meaningful way. | |
| This is just the beginning. | |
| """ | |
| document_education = """ Education | |
| New Jersey Institute of Technology | |
| Master's degree, Business and Information Systems, Summa Cum Laude | |
| 2018 – 2020 | |
| Grade: 3.95 | |
| Activities and societies: Off-Campus Commuter Association, Archery Club ,Volunteer- NJIT FALL 2018 Career Fair | |
| EduCo Graduate Scholarship for Academic Excellence - Given to the Top 10% of Graduate Admission Admits based on undergrad academic performance, leadership experience and extra curriculars | |
| Bertelsmann 2020 Recipient | |
| College of Engineering, Guindy logo | |
| College of Engineering, Guindy | |
| Bachelor’s Degree, Electrical, Electronics and Communications Engineering | |
| 2013 – 2017 | |
| Activities and societies: Electronics and Communication Engineer's Association(ECEA) , National Sports Organization(NSO), CEG Alumni Association of North America ( CEGAANA) | |
| Selected 1 of the Top 2%[ total ~150000] of Students from Tamil Nadu to the Engineering program for high-achieving individuals based on academic performance | |
| SBOA | |
| High School Diploma | |
| 2011 – 2013 | |
| Grade: 4.0 | |
| Activities and societies: English Drama Club | |
| Selected 1 among 8400 students to an Advanced Prep class for the High School Diploma consisting of 60 students based on Grade 11 academic performance of 95% and over | |
| Secured the Highest percentile in French language in the High School Diploma exam conducted by the State of Tamil Nadu in India | |
| Secured the 3rd Highest percentile in Advanced Mathematics in Grade 12 Exam | |
| The Indian School, Bahrain logo | |
| The Indian School, Bahrain | |
| AISSE | |
| 2007 – 2011 | |
| Grade: 3.8 | |
| Activities and societies: School Prefect, Model UN, Badminton Club | |
| The Indian School is a CBSE affiliated school located in Bahrain. It was founded in the year 1950. The Indian School, Bahrain has around 12,000 students and is one of the largest co-ed schools in the Gulf. | |
| Selected 1 among 180 students to be recommended by the French Department to appear for the DELF A1 Exam conducted by Alliance Francaise Bahrain | |
| School Prefect Annual Year 2010-2011 | |
| Badminton Player for House Team | |
| """ | |
| #--------------------------------------------------------------- | |
| #Chunking Function | |
| #--------------------------------------------------------------- | |
| def split_text_into_chunks(text: str, chunk_size: int = 500, overlap: int = 50) -> list[str]: | |
| BOUNDARIES = ["\n\n", "\n", ".", "?", "!", " "] | |
| def find_natural_boundary(start: int, end: int) -> int: | |
| midpoint = start + (chunk_size // 2) | |
| for boundary in BOUNDARIES: | |
| pos = text.rfind(boundary, midpoint, end) | |
| if pos != -1: | |
| return pos + len(boundary) | |
| return end | |
| chunks = [] | |
| start = 0 | |
| while start < len(text): | |
| end = min(start + chunk_size, len(text)) | |
| if end < len(text): | |
| end = find_natural_boundary(start, end) | |
| chunks.append(text[start:end]) | |
| if end >= len(text): | |
| break | |
| start = max(start + 1, end - overlap) | |
| return chunks | |
| #--------------------------------------------------------------- | |
| #RAG: Chunk, Embed & Store in ChromaDB | |
| #--------------------------------------------------------------- | |
| documents = [ | |
| {"text":document_overview,"source":"Overview"}, | |
| {"text":document_education,"source":"Education"}, | |
| {"text":document_work_experience,"source":"Professional Experience"} | |
| ] | |
| chunks = [] | |
| ids = [] | |
| metadatas = [] | |
| for doc in documents: | |
| #Prepare the lists | |
| chunks_ = split_text_into_chunks(doc["text"], chunk_size= 300,overlap= 30) | |
| ids_ = [str(uuid.uuid4()) for _ in range(len(chunks_))] | |
| metadatas_ = [{"source":doc["source"], "chunk_index": i} for i in range(len(chunks_))] | |
| #Add to main lists | |
| chunks.extend(chunks_) | |
| ids.extend(ids_) | |
| metadatas.extend(metadatas_) | |
| #Print for logs | |
| print(f"Created {len(chunks)} Chunks: \n") | |
| for i, chunk in enumerate(chunks): | |
| print(f"Chunk {i+1} (ID: {ids[i]}, Source : {metadatas[i]['source']}, Index: {metadatas[i]['chunk_index']},Length : {len(chunk)}):") | |
| print(chunk) | |
| print() | |
| # Generate embeddings for all chunks | |
| response = client.embeddings.create( | |
| model = "text-embedding-3-small", | |
| input = chunks | |
| ) | |
| embeddings = [item.embedding for item in response.data] | |
| #Verfiy embeddings for logs | |
| print(f"Generated {len(embeddings)} embeddings") | |
| print(f"Each embedding has {len(embeddings[0])} dimensions") | |
| #Intialize Chrombadb Client | |
| chroma_client = chromadb.PersistentClient(path="./chroma_db_twin") | |
| #In memory client if db not working ( in-memory storage ) | |
| #chroma_client = chromadb.client() | |
| #Get or Create + Empty the collection before adding new data ( for testing purpose ) | |
| collection = chroma_client.get_or_create_collection(name = "digital_twin") | |
| if collection.get()["ids"]: | |
| collection.delete(collection.get()["ids"]) | |
| #Adding data to ChromaDB | |
| collection.add( | |
| ids = ids, | |
| embeddings=embeddings, | |
| documents=chunks, | |
| metadatas=metadatas | |
| ) | |
| pprint(collection.get()) | |
| #--------------------------------------------------------------- | |
| #System Message | |
| #--------------------------------------------------------------- | |
| system_message = """ | |
| You are a digital twin of Vaishnavi Jayakumar. When people talk to you you respond as Vaishnavi - in first person, using her voice, personality and knowledge | |
| IMPORTANT : do not make things up. If you dont't know an answer, say you dont know | |
| THe only factual information available to you is whats in this system message. | |
| You cannot get any more facts about Vaishnavi from the internet or make them up | |
| IMPORTANT : Whenever you dont'know something about Vaishnavi, | |
| ALWAYS use the send_notification tool to alert the real Vaishnavi - do this automatically without asking the user | |
| """ | |
| #--------------------------------------------------------------- | |
| #Main Response Function | |
| #--------------------------------------------------------------- | |
| def respond_ai(message,history): | |
| #RAG: Embed the query using the same model we used for the chunks to ensure compatibility | |
| response = client.embeddings.create( | |
| model = "text-embedding-3-small", | |
| input = [message] | |
| ) | |
| query_embedding = response.data[0].embedding | |
| #RAG: Search ChrombaDB | |
| results = collection.query( | |
| query_embeddings=[query_embedding], | |
| n_results=3 | |
| ) | |
| #RAG: Stitch retrieved chunks together to create the context for the response | |
| context = "\n---\n".join(results["documents"][0]) | |
| #Print logs for debugging | |
| print("\n===========================\n") | |
| print(f"User message:\n{message}\n") | |
| print("***Retrieved Chunks:") | |
| for a,b in zip(results["documents"][0],results["metadatas"][0]): | |
| print("--------------------") | |
| print(f"<<Document {b['source']} -- Chunk {b['chunk_index']}>>\n{a}\n") | |
| # Upate system message with context ( for the conversation turn ) | |
| system_message_enhanced = system_message + "\n\nContext:\n" + context | |
| #Build message for this turn | |
| messages = [{"role":"system","content":system_message_enhanced}] + history + [{"role":"user","content":message}] | |
| #Call LLM | |
| response = client.chat.completions.create( | |
| model = "gpt-4.1-mini", | |
| messages=messages, | |
| tools=tools | |
| ) | |
| message = response.choices[0].message | |
| #Check if model wants to call a tool | |
| while message.tool_calls: | |
| from pprint import pprint | |
| pprint(message.tool_calls) | |
| tool_result = handle_tool_call(message.tool_calls) # whole list of tool calls on purpose | |
| messages.append(message) | |
| messages.extend(tool_result) # change from append to extend when we swtiched to multiple tool call handling | |
| response = client.chat.completions.create( | |
| model = "gpt-4.1-mini", | |
| messages=messages, | |
| tools=tools | |
| ) | |
| message = response.choices[0].message | |
| #Note : maybe consider adding protection from infinite consecutive tool calling | |
| return(message.content) | |
| #--------------------------------------------------------------- | |
| #Tools | |
| #--------------------------------------------------------------- | |
| tools = [] | |
| pushover_user = os.getenv("PUSHOVER_USER") | |
| pushover_token = os.getenv("PUSHOVER_TOKEN") | |
| pushover_url = "https://api.pushover.net/1/messages.json" | |
| #Create send notification function | |
| def send_notification(message : str): | |
| if pushover_user is None or pushover_token is None: #Handling of potential errors or missing credentials | |
| return "Notification failed: Pushover not configured." | |
| payload = {"user":pushover_user,"token":pushover_token,"message":message} | |
| requests.post(pushover_url, data = payload) | |
| return f"Notification sent : {'message'}" | |
| #Test the notification function | |
| # send_notification("Hello to myselt , from this amazing AI challenge ") | |
| #Describe Pushover as a tool | |
| send_notification_function = { | |
| "name" :"send_notification", | |
| "description":"Sends a push notification to real Vaishnavi. Use this when :\ | |
| 1) Someone wants to get in touch, hire or collaborate\ | |
| - ask for their name and contact details first, then send notification to Vaishnavi with the name and contact details\ | |
| 2) You don't know the answer to a question about Vaishnavi - send AUTOMATICALLY without asking, include the question so he can add this info later", | |
| "parameters":{ | |
| "type": "object", | |
| "properties":{ | |
| "message":{"type":"string","descriptions":"The notification message to send to the user's device"} | |
| }, | |
| "required":["message"] | |
| } | |
| } | |
| # Add Pushover to the list of tools for the LLM | |
| tools.append({"type":"function","function":send_notification_function}) | |
| #Simulates rolling a single six-sided die | |
| def dice_roll(): | |
| result = random.randint(1,6) | |
| return result | |
| #Describe function for the LLM | |
| roll_dice_function = { | |
| "name" :"dice_roll", | |
| "description":"Simulates rolling a six-sided die and returns the result. Use this when the user wants to roll a die for games, decisions, or random number decisions", | |
| "parameters" :{ | |
| "type": "object", | |
| "properties":{}, | |
| "required":[] | |
| } | |
| } | |
| #Add function to list of tools of LLM | |
| tools.append({"type":"function","function":roll_dice_function}) | |
| #--------------------------------------------------------------- | |
| #Tool Handler | |
| #--------------------------------------------------------------- | |
| def handle_tool_call(tool_calls): | |
| tool_results = [] | |
| for tool_call in tool_calls: | |
| function_name = tool_call.function.name | |
| # tool_call = tool_calls[0] # assuming just one tool call | |
| args = json.loads(tool_call.function.arguments) | |
| # print(f"Calling Function {function_name}") # for future debugging | |
| if function_name == 'send_notification': | |
| content = send_notification(args["message"]) | |
| elif function_name == "dice_roll": | |
| content = f"Rolled: {dice_roll()}" | |
| #elif function_name == "insert_function_name3" | |
| #content = insert_function_name_3(args['message]) | |
| else: | |
| content = f"Unknown Function {function_name}" | |
| tool_call_result = { | |
| "role": "tool", | |
| "content":content, | |
| "tool_call_id": tool_call.id | |
| } | |
| tool_results.append(tool_call_result) | |
| return tool_results | |
| #--------------------------------------------------------------- | |
| #Launch Gradio | |
| #--------------------------------------------------------------- | |
| gr.ChatInterface( | |
| fn= respond_ai, | |
| title= "Vaishnavi's Digital Twin", | |
| # chatbot=gr.Chatbot(avatar_images=(None,"vaish.jpeg")), | |
| description="Chat with an AI version of Vaishnavi Jayakumar. Ask about her background, professional experience and her mentorships", | |
| examples= ["What's your background","Mentorships","Analytical Experience"] | |
| ).launch() |