diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..6e363c061 --- /dev/null +++ b/.gitignore @@ -0,0 +1,49 @@ +#Environment file +.env + +#User memory and data +user_profile.json + +#Python cache & compiled files +__pycache__/ +*.py[cod] +*.pyo + +#Jupyter notebooks checkpoints (if using notebooks) +.ipynb_checkpoints/ + +#VSCode and PyCharm editor settings +.vscode/ +.idea/ + +#Virtual environment +.venv/ +venv/ +env/ +ENV/ + +#Python environment files +*.egg-info/ +*.egg +*.log +pip-log.txt +pip-delete-this-directory.txt + +#Build artifacts +build/ +dist/ + +#OS files +.DS_Store +Thumbs.db + +#Dependency lock (optional if not using pip freeze) +poetry.lock +Pipfile.lock + +# Ignore Python venv and system paths +.venv/ +src/.venv/ +src/Lib/ +src/Scripts/ +src/Include/ diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 223b4737d..bd8d1a0da 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,24 +1,29 @@ -## 2. `ARCHITECTURE.md` - -```markdown # Architecture Overview -Below, sketch (ASCII, hand-drawn JPEG/PNG pasted in, or ASCII art) the high-level components of your agent. - -## Components - -1. **User Interface** - - E.g., Streamlit, CLI, Slack bot - -2. **Agent Core** - - **Planner**: how you break down tasks - - **Executor**: LLM prompt + tool-calling logic - - **Memory**: vector store, cache, or on-disk logs +Below is a high-level diagram of the Cognitive Companion Agentic AI system: -3. **Tools / APIs** - - E.g., Google Gemini API, Tools, etc +![Architecture Diagram](./architecture_v2.png) + -4. **Observability** - - Logging of each reasoning step - - Error handling / retries +## Components +### 1. **User Interface** +- CLI-based for now (command line interaction) +- Accepts natural language input from the user + +### 2. **Agent Core** +- **Planner**: *(To be implemented)* will break user goals into subtasks +- **Executor**: Calls Gemini API with context and handles tool execution +- **Memory**: JSON-based store to persist user profile, routines, and context + +### 3. **Tools / APIs** +- **Gemini API** for LLM responses +- **Gmail SMTP** for notifications +- *(Optional future tools: Push Notification APIs, Calendar, SMS)* + +### 4. **Observability** +- Uses terminal logging for: + - LLM inputs and outputs + - Memory updates + - Notification triggers +- Errors are caught and logged diff --git a/DEMO.md b/DEMO.md index 4e0b5eeb6..7add42b5a 100644 --- a/DEMO.md +++ b/DEMO.md @@ -1,23 +1,4 @@ -# Demo Video -Please record a 3โ€“5 minute walkthrough showing: +### ๐Ÿ“ฝ๏ธ Demo Video -- The problem you solve -- End-to-end agent behavior on a representative example -- Highlighted โ€œagenticโ€ steps (planning, tool calls, memory use) - ---- - -๐Ÿ“บ **Provide a Hosted Public Video Link (YouTube unlisted / Loom / MP4):** MUST BE ON A HOS -https://your.video.link.here - -PLEASE DO NOT UPLOAD RAW VIDOE FILES. These submissions will not be reviewed. - -### Timestamps - -- **00:00โ€“00:30** โ€” Introduction & setup -- **00:30โ€“01:30** โ€” User input โ†’ Planning step -- **01:30โ€“02:30** โ€” Tool calls & memory retrieval -- **02:30โ€“03:30** โ€” Final output & edge-case handling - -- Vidoes longer than 5 minutes may not be reviewd. +Watch the demo video on [YouTube](https://www.canva.com/design/DAGuRzIru7g/U4j1YZCm0coIcS_gkcSU8Q/watch?utlId=hae22173251). diff --git a/EXPLANATION.md b/EXPLANATION.md index 564f4a172..bc042fb7a 100644 --- a/EXPLANATION.md +++ b/EXPLANATION.md @@ -1,35 +1 @@ -# Technical Explanation - -## 1. Agent Workflow - -Describe step-by-step how your agent processes an input: -1. Receive user input -2. (Optional) Retrieve relevant memory -3. Plan sub-tasks (e.g., using ReAct / BabyAGI pattern) -4. Call tools or APIs as needed -5. Summarize and return final output - -## 2. Key Modules - -- **Planner** (`planner.py`): โ€ฆ -- **Executor** (`executor.py`): โ€ฆ -- **Memory Store** (`memory.py`): โ€ฆ - -## 3. Tool Integration - -List each external tool or API and how you call it: -- **Search API**: function `search(query)` -- **Calculator**: LLM function calling - -## 4. Observability & Testing - -Explain your logging and how judges can trace decisions: -- Logs saved in `logs/` directory -- `TEST.sh` exercises main path - -## 5. Known Limitations - -Be honest about edge cases or performance bottlenecks: -- Long-running API calls -- Handling of ambiguous user inputs - +๐Ÿ“„ [View High-Level Design (HLD) Document](./HLD.pdf) diff --git a/HLD.pdf b/HLD.pdf new file mode 100644 index 000000000..c4cab0608 Binary files /dev/null and b/HLD.pdf differ diff --git a/ProductRequirementsDocument.pdf b/ProductRequirementsDocument.pdf new file mode 100644 index 000000000..9cbce0a70 Binary files /dev/null and b/ProductRequirementsDocument.pdf differ diff --git a/README.md b/README.md index 1bc06dbb8..80e2f8dba 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,117 @@ -# Agentic AI App Hackathon Template +# ๐Ÿง  Memory Anchor โ€” Agentic AI for Alzheimerโ€™s Support -Welcome! This repository is your starting point for the **Agentic AI App Hackathon**. It includes: +Cognitive Companion is an Agentic AI application designed to assist early-stage Alzheimer's patients with memory support, routine reminders, and emotionally comforting conversations. It can also notify loved ones via email when needed. -- A consistent folder structure -- An environment spec (`environment.yml` or `Dockerfile`) -- Documentation placeholders to explain your design and demo +--- -## ๐Ÿ“‹ Submission Checklist +## โœจ Features -- [ ] All code in `src/` runs without errors -- [ ] `ARCHITECTURE.md` contains a clear diagram sketch and explanation -- [ ] `EXPLANATION.md` covers planning, tool use, memory, and limitations -- [ ] `DEMO.md` links to a 3โ€“5 min video with timestamped highlights +- Onboards the user and stores their routine/memory profile +- Uses Google Gemini (via Gemini API) to generate memory-aware, emotionally supportive replies +- Sets local reminders for daily routines or medications +- Sends email alerts to loved ones +- Saves user memory locally for context-aware interaction +- Secured using environment variables +--- -## ๐Ÿš€ Getting Started +## ๐Ÿงฉ Core Modules Implemented -1. **Clone / Fork** this template. Very Important. Fork Name MUST be the same name as the teamn name +| File | Purpose | +|----------------|-------------------------------------------------------------------------| +| `planner.py` | (To be implemented) Breaks down user goals into sub-tasks | +| `executor.py` | Calls Gemini API, generates responses using prompt + memory | +| `memory.py` | Saves and retrieves user memory (e.g., routines, meds, contacts) | +| `tools.py` | Contains helper functions like `set_reminder()` and `send_notification()` | +| `notifier.py` | Sends email to emergency contacts using Gmail SMTP | +--- -## ๐Ÿ“‚ Folder Layout +## โš™๏ธ Setup Instructions -![Folder Layout Diagram](images/folder-githb.png) +### 1. Create and Activate Virtual Environment +```bash +python -m venv .venv +.venv\Scripts\activate # On Windows +source .venv/bin/activate # On macOS/Linux +``` +--- -## ๐Ÿ… Judging Criteria +### 2. Install Requirements -- **Technical Excellence ** - This criterion evaluates the robustness, functionality, and overall quality of the technical implementation. Judges will assess the code's efficiency, the absence of critical bugs, and the successful execution of the project's core features. +```bash +pip install -r requirements.txt +``` -- **Solution Architecture & Documentation ** - This focuses on the clarity, maintainability, and thoughtful design of the project's architecture. This includes assessing the organization and readability of the codebase, as well as the comprehensiveness and conciseness of documentation (e.g., GitHub README, inline comments) that enables others to understand and potentially reproduce or extend the solution. +--- -- **Innovative Gemini Integration ** - This criterion specifically assesses how effectively and creatively the Google Gemini API has been incorporated into the solution. Judges will look for novel applications, efficient use of Gemini's capabilities, and the impact it has on the project's functionality or user experience. You are welcome to use additional Google products. +### 3. Configure `.env` for Gemini + Gmail -- **Societal Impact & Novelty ** - This evaluates the project's potential to address a meaningful problem, contribute positively to society, or offer a genuinely innovative and unique solution. Judges will consider the originality of the idea, its potential realโ€‘world applicability, and its ability to solve a challenge in a new or impactful way. +1. Get a free Gemini API key via [Google AI Studio](https://makersuite.google.com/app) +2. Create a `.env` file in the project root and add: +```env +# Gemini API Key (from Google AI Studio) +GEMINI_API_KEY=your_gemini_api_key_here +# Gmail SMTP credentials (App Password required) +EMAIL_SENDER=your_email@gmail.com +EMAIL_PASSWORD=your_gmail_app_password +``` + +--- + +### ๐Ÿ“ง How to Set Up Gmail for Notifications + +1. Go to: [https://myaccount.google.com/security](https://myaccount.google.com/security) + โ†’ Under "Signing in to Google", enable **2-Step Verification** + +2. After enabling 2FA, go to: [https://myaccount.google.com/apppasswords](https://myaccount.google.com/apppasswords) + โ†’ Choose **Mail** as the app and **Other (Custom name)** as the device (e.g., `Promptonauts`) + โ†’ Click **Generate** + +3. Copy the **16-character App Password** (e.g., `abcd efgh ijkl mnop`) + โ†’ Paste it into `.env` like this (no spaces): + +```env +EMAIL_PASSWORD=abcdefghijklmnop +``` + +๐Ÿ“Œ Never commit `.env` to Git. It's already in `.gitignore`. + +--- + +### 4. Run the App + +```bash +python src/main.py +``` + + +--- + +## ๐Ÿ“น Demo Video + +We have recorded a **5-minute demo video** showcasing the full working of the Cognitive Companion Agent โ€” from onboarding, planning, tool usage, to output generation and edge case handling. + +๐Ÿ“Ž **Watch the video demo**: [Demo Video Link](https://www.canva.com/design/DAGuRzIru7g/U4j1YZCm0coIcS_gkcSU8Q/watch?utlId=hae22173251) + + + +--- + + +## Documentation + +- [๐Ÿ“„ High-Level Design (HLD)](./HLD.pdf) +- [๐Ÿ“„ Product Requirements Document (PRD)](./ProductRequirementsDocument.pdf) + + + + + +### โš ๏ธ Disclaimer + +This is a hackathon prototype and not intended for medical use without proper safety, testing, and regulatory approval. diff --git a/architecture_v2.png b/architecture_v2.png new file mode 100644 index 000000000..c0701151e Binary files /dev/null and b/architecture_v2.png differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..d04c26867 Binary files /dev/null and b/requirements.txt differ diff --git a/src/executor.py b/src/executor.py new file mode 100644 index 000000000..8e7ba8e2e --- /dev/null +++ b/src/executor.py @@ -0,0 +1,66 @@ +# src/executor.py + +import os +from google import genai +from memory import load_user_profile +from datetime import datetime +from dotenv import load_dotenv + +load_dotenv() + +# Create Gemini client using API key from environment +client = genai.Client(api_key=os.getenv("GEMINI_API_KEY")) + +def generate_response(user_input): + # Load saved user profile + profile = load_user_profile() + if not profile: + return "No user profile found." + + name = profile.get("name", "friend") + routines = profile.get("routines", {}) + meds = routines.get("medications", {}) + contacts = profile.get("contacts", []) + + now = datetime.now().strftime("%A %I:%M %p") + + # Build memory snippet string + memory_snippets = [] + if routines.get("dinner"): + memory_snippets.append(f"{name} has dinner at {routines['dinner']}.") + for med, time in meds.items(): + memory_snippets.append(f"The {med} is taken {time}.") + if contacts: + memory_snippets.append(f"Close contacts include: {', '.join(contacts)}.") + + memory_text = "\n".join(memory_snippets) + + # Construct prompt + prompt = f""" +You are a caring AI assistant helping {name}, an early-stage Alzheimerโ€™s patient. +Current time: {now} +Memory: +{memory_text} + +User input: +"{user_input}" + +Your job: +- Understand what {name} is asking +- Respond in a friendly, emotionally supportive tone +- If it's a reminder, infer the right time if not mentioned +- Help reduce confusion or anxiety + +Respond directly to the user. +""" + + try: + # Send prompt to Gemini + response = client.models.generate_content( + model="gemini-2.5-pro", # You can switch to gemini-2.5 if available + contents=prompt + ) + return response.text.strip() + + except Exception as e: + return f"Error contacting Gemini: {e}" diff --git a/src/main.py b/src/main.py new file mode 100644 index 000000000..e534949f2 --- /dev/null +++ b/src/main.py @@ -0,0 +1,65 @@ +import threading +from tools import send_custom_email +from onboarding import collect_user_info +from memory import load_user_profile, save_user_profile, get_contact_email +from executor import generate_response + +# Import the notification scheduler +from notify_user import start_notification_scheduler + +def main(): + # Step 1: Load profile from root directory + user_profile = load_user_profile() + + # Step 2: If no profile found, onboard new user + if user_profile is None: + print("No user profile found. Starting onboarding...\n") + user_profile = collect_user_info() + save_user_profile(user_profile) + print(f"\nOnboarding complete. Welcome, {user_profile['name']}!\n") + else: + print(f"Welcome back, {user_profile['name']}!") + + + # Step 3: Start conversation loop + waiting_for_email_message = None # state flag + + while True: + user_input = input("\nYou: ") + + if user_input.lower() in ['exit', 'quit']: + print("Goodbye!") + break + + if waiting_for_email_message: + contact_name, email = waiting_for_email_message + send_custom_email(email, f"A message from {user_profile['name']}", user_input) + print(f"Email sent to {contact_name}.") + waiting_for_email_message = None + continue + + if user_input.lower().startswith("email "): + parts = user_input.split() + if len(parts) >= 2: + contact_name = parts[1].capitalize() + email = get_contact_email(contact_name) + + if not email: + print(f"I donโ€™t have {contact_name}โ€™s email.") + continue + + message = user_input.partition(contact_name)[2].strip() + + if not message: + print(f"What would you like me to tell {contact_name}?") + waiting_for_email_message = (contact_name, email) + else: + send_custom_email(email, f"A message from {user_profile['name']}", message) + print(f"Email sent to {contact_name}.") + continue + + response = generate_response(user_input) + print("Assistant:", response) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/memory.py b/src/memory.py new file mode 100644 index 000000000..79e3e2a9d --- /dev/null +++ b/src/memory.py @@ -0,0 +1,29 @@ +# src/memory.py + +import json +import os + +# Get the path to the root directory (one level above src) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +PROFILE_FILE = os.path.join(BASE_DIR, "user_profile.json") + +def save_user_profile(profile): + with open(PROFILE_FILE, "w") as f: + json.dump(profile, f, indent=4) + print("๐Ÿ’พ Profile saved.") + +def load_user_profile(): + if os.path.exists(PROFILE_FILE): + with open(PROFILE_FILE, "r") as f: + return json.load(f) + return None + +def get_contact_email(contact_name): + profile = load_user_profile() + if not profile: + return None + contacts = profile.get("emergency_contacts", {}) + contact = contacts.get(contact_name.capitalize()) + if contact and "email" in contact: + return contact["email"] + return None diff --git a/src/notifier.py b/src/notifier.py new file mode 100644 index 000000000..ebd38ac87 --- /dev/null +++ b/src/notifier.py @@ -0,0 +1,37 @@ +# src/notifier.py + +import os +import smtplib +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +from dotenv import load_dotenv + +# Load environment variables from .env +load_dotenv() + +EMAIL_SENDER = os.getenv("EMAIL_SENDER") +EMAIL_PASSWORD = os.getenv("EMAIL_PASSWORD") + +def send_email(recipient_email, subject, message_body): + try: + # Create the email + msg = MIMEMultipart() + msg["From"] = EMAIL_SENDER + msg["To"] = recipient_email + msg["Subject"] = subject + + msg.attach(MIMEText(message_body, "plain")) + + # Connect to Gmail SMTP server + server = smtplib.SMTP("smtp.gmail.com", 587) + server.starttls() + server.login(EMAIL_SENDER, EMAIL_PASSWORD) + server.send_message(msg) + server.quit() + + print(f"Email sent to {recipient_email}") + return True + + except Exception as e: + print(f"Error sending email: {e}") + return False diff --git a/src/notify_user.py b/src/notify_user.py new file mode 100644 index 000000000..a2e27f8aa --- /dev/null +++ b/src/notify_user.py @@ -0,0 +1,57 @@ +import json +from datetime import datetime, timedelta +import time +import schedule + +def parse_time(time_str): + try: + return datetime.strptime(time_str, "%I %p") + except ValueError: + return datetime.strptime(time_str, "%I:%M %p") + +def schedule_notifications(profile): + routines = profile.get("routines", {}) + notifications = [] + + for meal in ["breakfast", "lunch", "dinner"]: + if meal in routines: + notif_time = parse_time(routines[meal]) - timedelta(minutes=30) + notifications.append({ + "type": "meal", + "name": meal, + "time": notif_time.strftime("%H:%M") + }) + + meds = routines.get("medications", {}) + for med, when in meds.items(): + if when == "before dinner" and "dinner" in routines: + dinner_time = parse_time(routines["dinner"]) - timedelta(minutes=30) + notifications.append({ + "type": "medication", + "name": med, + "time": dinner_time.strftime("%H:%M") + }) + return notifications + +def send_notification(notification): + print(f"Reminder: {notification['type'].capitalize()} - {notification['name']} at {notification['time']}") + +def job_factory(notification): + return lambda: send_notification(notification) + +def start_notification_scheduler(profile): + if profile.get("preferences", {}).get("wants_reminders", False): + notifications = schedule_notifications(profile) + for note in notifications: + hour, minute = map(int, note["time"].split(":")) + schedule.every().day.at(f"{hour:02d}:{minute:02d}").do(job_factory(note)) + while True: + schedule.run_pending() + time.sleep(30) + +if __name__ == "__main__": + # Load the user profile from the correct path + with open("./src/user_profile.json", "r") as f: + profile = json.load(f) + print("Starting notification scheduler...") + start_notification_scheduler(profile) \ No newline at end of file diff --git a/src/onboarding.py b/src/onboarding.py new file mode 100644 index 000000000..26a1d97ea --- /dev/null +++ b/src/onboarding.py @@ -0,0 +1,47 @@ +# onboarding.py + +def collect_user_info(): + print("Hello there! Please know that everything is alright. You're safe here, and I'm right here with you. There's nothing you need to worry about.") + print("Letโ€™s set things up so I can help you better.") + + name = input("1. Whatโ€™s your name? ") + + wants_reminders = input("2. Would you like daily reminders? (yes/no) ").strip().lower() == 'yes' + + contacts = input("3. Name a few close family/friends (comma separated): ") + contacts_list = [c.strip() for c in contacts.split(',')] + + emergency_contacts = {} + + for contact in contacts_list: + email = input(f" - What is {contact}'s email? ") + emergency_contacts[contact] = {"email": email.strip()} + + print("4. Tell me your daily schedule (leave blank if unsure):") + breakfast = input(" - Breakfast time (e.g., 8:00 AM): ") + lunch = input(" - Lunch time (e.g., 1:00 PM): ") + dinner = input(" - Dinner time (e.g., 7:00 PM): ") + + print("5. Do you take any daily medications?") + med_name = input(" - Medication name (e.g., blue pill): ") + med_time = input(" - When do you take it? (e.g., after dinner): ") + + # Bundle into profile dictionary + user_profile = { + "name": name, + "preferences": { + "wants_reminders": wants_reminders + }, + "contacts": contacts_list, + "emergency_contacts": emergency_contacts, + "routines": { + "breakfast": breakfast, + "lunch": lunch, + "dinner": dinner, + "medications": { + med_name: med_time + } + } + } + + return user_profile diff --git a/src/pyvenv.cfg b/src/pyvenv.cfg new file mode 100644 index 000000000..353f98e65 --- /dev/null +++ b/src/pyvenv.cfg @@ -0,0 +1,3 @@ +home = C:\Users\mouls\AppData\Local\Programs\Python\Python310 +include-system-site-packages = false +version = 3.10.11 diff --git a/src/tools.py b/src/tools.py new file mode 100644 index 000000000..397933977 --- /dev/null +++ b/src/tools.py @@ -0,0 +1,9 @@ +# src/tools.py + +from notifier import send_email + +def send_custom_email(recipient_email, subject, message): + """ + Sends a custom email using the existing notifier. + """ + return send_email(recipient_email, subject, message)