Spaces:
Sleeping
Sleeping
| """ | |
| Deploying Digital Twin: Built from Digital Twin IPYNB | |
| """ | |
| import os | |
| from openai import OpenAI | |
| # from IPython.display import display, Markdown | |
| import gradio as gr | |
| # from dotenv import load_dotenv | |
| import json | |
| import uuid | |
| import random | |
| import requests | |
| import chromadb | |
| from pprint import pprint | |
| #Setup------------- | |
| #no loading the environment needed because its app.py | |
| #You don't need it in production, hugging face already does it, but ipynb its to load into local machine | |
| # load_dotenv() | |
| OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") | |
| #debugging if you can't find the openai key: | |
| if OPENAI_API_KEY is None: | |
| raise Exception ( "API Key is missing") | |
| else: | |
| print(OPENAI_API_KEY[:8]) | |
| client = OpenAI() | |
| #Document Overview------------------ | |
| document_experience = """ | |
| Name: Simrun Sharma | |
| Current Role: | |
| Associate Research Analyst / Data Scientist at CNA | |
| Professional Experience | |
| Center for Naval Analyses (CNA) | |
| Associate Research Analyst / Data Scientist | |
| May 2024 – Present | |
| Washington, DC | |
| Projects: | |
| - Developed an NLP pipeline using regex, text processing, and information extraction to categorize research projects for budgeting and funding analysis. | |
| - Performed strategic analysis of historical funding allocations to identify research areas receiving the greatest investment. | |
| - Designed and implemented a Sparing Tool using a greedy optimization algorithm to determine optimal inventory levels for U.S. Air Force bases. | |
| - Modeled backorders using a continuous Poisson distribution and iteratively added spares to satisfy target Non-Mission Capable (NMC) rates while remaining within budget constraints. | |
| - Led development of a synthetic survey generation framework using web scraping, embedding models, CrewAI agents, vector databases, Retrieval-Augmented Generation (RAG), and Large Language Models (LLMs). | |
| - Built an end-to-end AI pipeline capable of generating realistic synthetic survey responses. | |
| Partners In Health | |
| Co-Lead Advocacy Chair | |
| September 2022 – February 2023 | |
| - Advocated for global health legislation through congressional meetings, callathons, and legislative outreach. | |
| - Secured 47 co-signers for the NOVID Act. | |
| - Worked on the Paul Farmer Memorial Resolution supporting increased global health funding. | |
| University of Florida Center for Undergraduate Research | |
| Director of Internal Affairs | |
| March 2021 – February 2023 | |
| - Represented approximately 35,000 undergraduate students. | |
| - Connected students with faculty research opportunities. | |
| - Organized research workshops and student research showcases. | |
| Equal Access Clinic Network | |
| Social Work Clinical Coordinator | |
| 2021 – 2023 | |
| - Cleaned and standardized patient data profiles. | |
| - Improved spreadsheet scheduling workflows for clinic staff. | |
| - Created a bilingual community resource guide by consolidating over ten years of local organizational data. | |
| University of Florida College of Public Health and Health Professions | |
| Assistant Director | |
| May 2022 – December 2022 | |
| - Coordinated communication among more than 25 research labs, colleges, and clinics. | |
| - Organized a multidisciplinary health professions fair and student panels. | |
| University of Florida Physicians Internal Medicine | |
| Treasurer, MC, and Event Planner | |
| November 2022 | |
| - Planned and hosted a physician networking event celebrating Diwali involving physicians from UF Health, the VA, and Northwest Hospital. | |
| Planned Parenthood Federation of America | |
| Student Volunteer | |
| 2021 – September 2022 | |
| - Participated in legislative advocacy, reproductive justice initiatives, petitions, and community organizing. | |
| University of Florida | |
| General Chemistry Teaching Assistant | |
| January 2019 – January 2022 | |
| - Led weekly discussion sections. | |
| - Created review sessions and supported undergraduate student learning. | |
| """ | |
| document_education = """ | |
| Name: Simrun Sharma | |
| Education | |
| Duke University | |
| Master of Interdisciplinary Data Science (MIDS) | |
| August 2023 – May 2025 | |
| Degree Focus: | |
| - Data Science | |
| - Machine Learning | |
| Relevant Coursework: | |
| - Modeling and Representation of Data | |
| - Data Analysis | |
| - Introduction to Natural Language Processing | |
| - Data Engineering Systems | |
| - Practicing Data Science Tools | |
| University of Florida | |
| Bachelor of Arts | |
| Economics Major | |
| Business Administration Minor | |
| Pre-Med Track | |
| Graduated: | |
| December 2023 | |
| GPA: | |
| 3.90 / 4.00 | |
| Advanced Coursework: | |
| - Biology | |
| - Physics | |
| - Organic Chemistry | |
| - Microeconomics | |
| - Macroeconomics | |
| - Business Administration | |
| Leadership & Campus Involvement: | |
| - Director, Center for Undergraduate Research | |
| - Advocacy Chair, Partners In Health | |
| - General Chemistry Teaching Assistant | |
| - International Research Director, Hearts for the Homeless | |
| - Planned Parenthood Volunteer | |
| Niceville High School | |
| High School Diploma | |
| Graduated: 2019 | |
| Activities & Awards: | |
| - Speech and Debate Team Captain | |
| - NAACP ACT-SO Bronze Medalist (Oratory) | |
| - Florida Forensic League State Finalist in Original Oratory | |
| """ | |
| document_personality = """ | |
| Name: Simrun Sharma | |
| Career Goals: | |
| - Transitioning from Data Science into AI Engineering. | |
| Target Roles: | |
| - AI Engineer | |
| - Applied AI Engineer | |
| - AI Deployment Engineer | |
| - Forward Deployed AI Engineer | |
| - AI Solutions Engineer | |
| Industries of Interest: | |
| - Artificial Intelligence | |
| - Healthcare Technology | |
| - Defense Technology | |
| - Data Science | |
| Professional Interests: | |
| - Artificial Intelligence | |
| - Machine Learning | |
| - Generative AI | |
| - Agentic AI Systems | |
| - Retrieval-Augmented Generation (RAG) | |
| - Large Language Models (LLMs) | |
| - Data Science | |
| - Healthcare Analytics | |
| - Brain Computer Interfaces | |
| - Neurotechnology | |
| - Explainable AI | |
| Healthcare Interests: | |
| - Passionate about applying AI and data science to improve patient outcomes, healthcare operations, accessibility, and clinical decision-making. | |
| Learning Style: | |
| - Step-by-step explanations | |
| - Visual examples | |
| - Interactive discussions | |
| - Hands-on projects | |
| - Building intuition before technical depth | |
| Communication Style: | |
| - Curious | |
| - Analytical | |
| - Direct | |
| - Practical | |
| - Detail-oriented | |
| Work Preferences: | |
| - Enjoys solving real-world problems. | |
| - Likes collaborating with stakeholders. | |
| - Prefers building practical AI systems with measurable impact. | |
| - Enjoys end-to-end solution development. | |
| Personality Traits: | |
| - Curious | |
| - Ambitious | |
| - Persistent | |
| - Analytical | |
| - Detail-oriented | |
| - Growth-oriented | |
| Personal Interests: | |
| - Pickleball | |
| - Bachata | |
| - Salsa | |
| - Bollywood dance | |
| - Strength training | |
| - Fitness | |
| - Pineapple on pizza | |
| """ | |
| #Chunking Function------------------ | |
| #Chunk the document | |
| def chunk_text(text: str, chunk_size: int = 1024, overlap: int = 150) -> list[str]: | |
| #Roughly, 1 token ≈ 4 characters in English text. | |
| #Chunk size 1024 sand overlap of 150 characters is ideal based on document length or user needs look at RAG Ipynb | |
| 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 | |
| #Generating chunks,ids, and metadatas-------------------------- | |
| #Generating chunks for multiple documents | |
| #We want to have these documents as a list of dictionaries: | |
| #Generating chunks for multiple documents | |
| #We want to have these documents as a list of dictionaries: | |
| documents = [ | |
| {'text' : document_experience, 'source' : 'Simrun Sharma Experience'}, | |
| {'text' : document_education, 'source' : 'Simrun Sharma Education'}, | |
| {'text' : document_personality, 'source' : 'Simrun Sharma Personality'} | |
| ] | |
| #I want unique ids for each of the ids | |
| # import uuid | |
| #for each of the document chunk them, id them, metadata add to list: | |
| ids = [] | |
| metadatas = [] | |
| chunks = [] | |
| for doc in documents: | |
| _chunks = chunk_text(text = doc['text'], chunk_size=300, overlap=30) | |
| _ids = [str(uuid.uuid4()) for i in range(len(_chunks))] | |
| _metadatas = [{'source' : doc['source'], 'chunk_index' : i} for i in range(len(_chunks))] | |
| #extend becase I am getting dictionaries I want to have one long list of dictionaries | |
| chunks.extend(_chunks) | |
| ids.extend(_ids) | |
| metadatas.extend(_metadatas) | |
| #logs for debugging chunks | |
| print(f'The length of the chunks is {len(chunks)}') | |
| print('\n') | |
| for i, chunk in enumerate(chunks): | |
| print(f"--- Chunk {i + 1} | {len(chunk)} total characters ---") | |
| print(f'(IDS: {ids[i]}, SOURCE: {metadatas[i]['source']}, Chunk_index: {metadatas[i]['chunk_index']})') | |
| print('\n') | |
| print(chunk) | |
| #Generate embeddings for chunks: | |
| response = client.embeddings.create( | |
| model = 'text-embedding-3-small', | |
| input= chunks | |
| ) | |
| #creating a list of embeddings above in response.data | |
| #I have the a list of Embedding objects and I just needs a list of the dimensions of each Embedding object | |
| embedding = [item.embedding for item in response.data] | |
| #logs for embeddings: | |
| print(f'The number of embeddings from the chunks: {len(embedding)}') | |
| print(f'The number of dimensions is: {len(embedding[0])}') | |
| #Initialize the ChromaDB and store the vectors | |
| #Persistent Client will show in VSCode | |
| chroma_client = chromadb.PersistentClient(path = "./digitaltwin_chroma") | |
| #Creating Collection | |
| collection = chroma_client.get_or_create_collection(name = 'Simrun_Information') | |
| #If there is a collection already then we need to delete the collection | |
| if collection.get()['ids']: | |
| collection.delete(collection.get()['ids']) | |
| #Adding to our collection | |
| collection.add( | |
| ids=ids, | |
| embeddings=embedding, | |
| metadatas=metadatas, | |
| documents=chunks | |
| ) | |
| #Tools----------------------------- | |
| tools = [] | |
| #Tool Handling -------------------- | |
| #PUSHOVER CREDENTIALS | |
| #Creating the pushover identification information | |
| pushover_user = os.getenv("PUSHOVER_USER") | |
| pushover_token = os.getenv("PUSHOVER_TOKEN") | |
| pushover_url = 'https://api.pushover.net/1/messages.json' | |
| def send_notifications(message:str): | |
| if pushover_user is None or pushover_token is None: | |
| return "Notification failed: Pushover credentials have failed." | |
| payload = {'user' : pushover_user,"token" : pushover_token, 'message': message} | |
| requests.post(url = pushover_url, data=payload) | |
| return f"Sent Notification: {message}" | |
| # This is not a dictionary. This is a description card written in JSON format. | |
| # We are describing the tool to OpenAI so it knows how to use it. | |
| # Think of it like a form or a menu card. Not real Python objects. | |
| #DESCRIBE PUSHOVER AS AN LLM TOOL | |
| send_notifications_function = { | |
| # The name of the tool OpenAI will request when it wants to use it | |
| 'name': 'send_notifications', | |
| # When should OpenAI use this tool? This description tells it. | |
| 'description': 'Send a push notification to the real Simrun Sharma. Use this when \ | |
| 1) Someone wants to get in touch, hire, or collaborate\ | |
| -ask for their full name, phone number, and email (contact details) first, then send notificaiton to real version of Simrun with full name and contact details.\ | |
| 2) If you do not know the answer to a question about Simrun Sharma - send AUTOMATICALLY without asking, include the question so Simrun Sharma can add to her information later. ', | |
| 'parameters': { | |
| 'type': 'object', | |
| 'properties': { | |
| 'message': { | |
| 'type': 'string', | |
| 'description': 'The notification message the user wants sent to their device' | |
| } | |
| }, | |
| 'required': ['message'] | |
| } | |
| } | |
| #adding this tool to the tool list: | |
| tools.append({"type" : "function", "function":send_notifications_function}) | |
| #Dice_roll--------------- | |
| # import random | |
| #creating roll dice function | |
| def dice_roll(): | |
| result = random.randint(1,6) | |
| return result | |
| #creating roll dice description of tool for LLM: | |
| roll_dice_function = { | |
| 'name' : "dice_roll", | |
| 'description': 'This is a function where you can roll a dice and can see the number you get from the roll', | |
| "parameters": { | |
| }, | |
| "required" : [] | |
| } | |
| #appending this to our available tools | |
| tools.append({"type" : "function", "function" : roll_dice_function}) | |
| #Handle Tool Call ----------------- | |
| def handle_tool_call(tool_calls:list): | |
| tool_call_results = [] | |
| for tool_call in tool_calls: | |
| function_name = tool_call.function.name | |
| # --- for debugging | |
| # print(f"the function name is {function_name}") | |
| # tool_call = tools_calls[0] #this is because we only have one tool thus far | |
| args = json.loads(tool_call.function.arguments) | |
| if function_name == 'send_notifications': | |
| content = send_notifications(args['message']) #sent to pushover | |
| elif function_name == "dice_roll": | |
| content = f' Rolled: {dice_roll()}' | |
| # elif function_name == "insert_function3_name": | |
| # content =insert_function3_name (args['message']}) | |
| else: | |
| content = f"Unknown function: {function_name}" | |
| #package into a dictionary for the llm to see what we got out from the tool call | |
| tool_call_result = { | |
| 'role' : 'tool', | |
| 'content' : content , | |
| 'tool_call_id' : tool_call.id | |
| } | |
| print(f"this is what the tool_call_result from handle tool_call looks like: {tool_call_result}") | |
| #appending each tool_call_result to the tool_call_results list of dictionaries | |
| tool_call_results.append(tool_call_result) | |
| return tool_call_results | |
| #System message--------------------- | |
| system_message = """ | |
| You are a digital twin of Simrun Sharma. | |
| When people message you, you respond as Simrun - You answer questions as Simrun using first-person language. Using her voice, personality, knowledge, and kindness. | |
| IMPORTANT RULES: | |
| 1. DO NOT use outside knowledge about Simrun. | |
| 2. DO NOT infer facts that are not explicitly stated. | |
| 3. DO NOT invent experiences, opinions, accomplishments, preferences, relationships, skills, or goals. Don't make up from internet. | |
| 4. The only factual information is given to you in this system message and anything you dont know its IMPORTANT: Always use the send_notifications tool to alert the real Simrun \ | |
| Sharma - do this automatically without asking the user.""" | |
| #Main Response Function------------ | |
| def respond_system_enhanced (message, history): | |
| #RAG | |
| response = client.embeddings.create( | |
| model = 'text-embedding-3-small', | |
| input = [message] | |
| ) | |
| print(f' The length of dimensions of the message : {len(response.data[0].embedding)}') | |
| #we just want the first embedding closet distance | |
| query_embedding = response.data[0].embedding | |
| #searching through collection for chunks with closest distance to query embedding: | |
| results = collection.query( | |
| query_embeddings= [query_embedding], | |
| n_results= 3, | |
| include= ['documents','metadatas','distances'] | |
| ) | |
| #logs for debugging | |
| #lets just give the 3 most relevant chunks | |
| context = "\n--\n".join(results['documents'][0]) | |
| print(f'User message:\n{message}\n') | |
| print('Context this turn:\n', context) | |
| #system enhanced message: | |
| system_message_enhanced = system_message + "\n\n Context is:" + context | |
| #Build messages for each 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 | |
| ) | |
| #record message | |
| message = response.choices[0].message | |
| print("message before while loop", message) | |
| while message.tool_calls: | |
| # from pprint import pprint | |
| pprint(message.tool_calls) | |
| #we want to pass the list of tool calls to out=r handle tool call function | |
| tool_call = message.tool_calls | |
| tool_call_result = handle_tool_call(message.tool_calls) | |
| #we want to append the message with the user request and the first two tool call requests | |
| messages.append(message) | |
| #We also want append to append the toolcall results with the dictionaries of the content return from each tool call | |
| messages.extend(tool_call_result) | |
| response = client.chat.completions.create( | |
| model = "gpt-4.1-mini", | |
| messages = messages, | |
| tools=tools | |
| ) | |
| message = response.choices[0].message | |
| return (message.content) | |
| #launching gradio: | |
| #inbrowser launch not needed | |
| gr.ChatInterface(fn=respond_system_enhanced, | |
| title = "Simrun's Digital Twin", | |
| chatbot = gr.Chatbot(avatar_images= (None, "Simrun_twin.jpeg")), | |
| description = 'Chat with an AI version of Simrun Sharma. Ask about her experiences, her life story, or interests!', | |
| examples = ['What projects have you worked on in the past?', 'Tell me about your experience with AI Engineering', 'Do you like dancing?']).launch() | |