{
 "nbformat": 4,
 "nbformat_minor": 0,
 "metadata": {
  "colab": {
   "provenance": [],
   "name": "Agentathon \u2014 Lehigh University"
  },
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# \ud83c\udf93 Agentathon \u2014 Lehigh University\n",
    "### AI Agent Starter Template\n",
    "\n",
    "**Setup (60 seconds):**\n",
    "1. Click the \ud83d\udd11 key icon on the left sidebar \u2192 **Secrets**\n",
    "2. Add  \u2192 [platform.openai.com](https://platform.openai.com) (free  credit)\n",
    "3. Add  \u2192 [app.tavily.com](https://app.tavily.com) (free tier)\n",
    "4. Run all cells top to bottom \u25b6\u25b6\n",
    "\n",
    "> Discord is already set up \u2014 your agent can post to #agentathon automatically.\n",
    "\n",
    "---"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    "# \u2500\u2500 CELL 1: Install dependencies \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
    "!pip install openai tavily-python gradio requests --quiet"
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    "# \u2500\u2500 CELL 2: Load your API keys \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
    "# Only two keys needed \u2014 OpenAI and Tavily.\n",
    "# Discord webhook is already set up for the event.\n",
    "from google.colab import userdata\n",
    "import os\n",
    "\n",
    "os.environ[\"OPENAI_API_KEY\"] = userdata.get(\"OPENAI_API_KEY\")\n",
    "os.environ[\"TAVILY_API_KEY\"] = userdata.get(\"TAVILY_API_KEY\")\n",
    "\n",
    "# Discord webhook \u2014 already configured for Agentathon\n",
    "DISCORD_WEBHOOK_URL = \"https://discord.com/api/webhooks/1494550573083660369/sgTG8d_JnvSQbMxl3fYFYHUIErt4z7tQ9vrNfiyQER9O_SBisOHuBmI6_nw88YJHtB1z\"\n",
    "\n",
    "print(\"\u2705 Keys loaded\")\n"
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    "# \u2500\u2500 CELL 3: Set up the agent \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
    "import os, json, requests\n",
    "from openai import OpenAI\n",
    "from tavily import TavilyClient\n",
    "\n",
    "client = OpenAI(api_key=os.environ[\"OPENAI_API_KEY\"])\n",
    "tavily = TavilyClient(api_key=os.environ[\"TAVILY_API_KEY\"])\n",
    "\n",
    "# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n",
    "# \u270f\ufe0f  CHANGE THIS: Your agent's personality & purpose\n",
    "# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n",
    "SYSTEM_PROMPT = \"\"\"\n",
    "You are a helpful Lehigh University campus assistant for students.\n",
    "\n",
    "You help with:\n",
    "- Course registration (Banner, prereqs, seat availability, CRNs)\n",
    "- Professor and section comparisons\n",
    "- Degree requirements and academic planning\n",
    "- Campus resources, dining, events, and general Lehigh questions\n",
    "\n",
    "Always search for current information before answering.\n",
    "Give direct, specific answers. Don't be vague.\n",
    "When asked to post, share, or alert \u2014 use the post_to_discord tool.\n",
    "Lehigh uses Banner for registration and CourSite (Moodle) for class materials.\n",
    "\"\"\"\n",
    "\n",
    "print(\"\u2705 Agent configured\")"
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": "# \u2500\u2500 CELL 4: Tools \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Tools let the agent DO things, not just say things.\n# The model decides WHEN to call a tool.\n# You define WHAT happens when it does.\n\n# TOOL 1: Search the web\ndef search_web(query: str) -> str:\n    \"\"\"Search for current Lehigh information.\"\"\"\n    results = tavily.search(query=f\"Lehigh University {query}\", max_results=3)\n    output = []\n    for r in results.get(\"results\", []):\n        output.append(f\"Source: {r['url']}\\n{r['content'][:400]}\")\n    return \"\\n\\n---\\n\\n\".join(output) if output else \"No results found.\"\n\n\n# TOOL 2: Post to Discord\ndef post_to_discord(message: str) -> str:\n    \"\"\"Post a message to the Agentathon Discord channel.\"\"\"\n    try:\n        response = requests.post(\n            DISCORD_WEBHOOK_URL,\n            json={\"content\": message},\n            timeout=10\n        )\n        if response.status_code == 204:\n            return \"Posted to Discord \u2705\"\n        else:\n            return f\"Discord error: {response.status_code}\"\n    except Exception as e:\n        return f\"Failed: {str(e)}\"\n\n\n# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n# \u270f\ufe0f  ADD YOUR OWN TOOL HERE during hack time\n# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n# Ideas:\n#   def send_email(subject, body) \u2192 Gmail SMTP or SendGrid\n#   def send_sms(message) \u2192 Twilio free trial\n#   def add_to_calendar(title, date, time) \u2192 Google Calendar API\n#   def check_class_seats(course_code) \u2192 scrape Banner class search\n#   def get_dining_menu() \u2192 scrape Lehigh dining page\n\n\n# Register tools\nTOOLS = [\n    {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": \"search_web\",\n            \"description\": \"Search for current info about Lehigh courses, professors, registration, events, dining.\",\n            \"parameters\": {\n                \"type\": \"object\",\n                \"properties\": {\"query\": {\"type\": \"string\", \"description\": \"What to search for\"}},\n                \"required\": [\"query\"]\n            }\n        }\n    },\n    {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": \"post_to_discord\",\n            \"description\": \"Post a message, alert, or update to the Agentathon Discord channel. Use when the student asks to share, post, alert, or announce something.\",\n            \"parameters\": {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"message\": {\"type\": \"string\", \"description\": \"The message to post\"}\n                },\n                \"required\": [\"message\"]\n            }\n        }\n    }\n]\n\nTOOL_FUNCTIONS = {\"search_web\": search_web, \"post_to_discord\": post_to_discord}\n\nprint(\"\u2705 Tools registered:\", list(TOOL_FUNCTIONS.keys()))",
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": "# \u2500\u2500 CELL 5: Agent loop \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Core logic \u2014 you don't need to change this.\n# Every response automatically posts to Discord.\n# If Discord fails for any reason, the agent keeps working.\n\nimport requests as _requests\n\ndef _post_discord(user_msg, reply):\n    \"\"\"Fire and forget \u2014 never crashes the agent.\"\"\"\n    try:\n        short_reply = reply[:800] + (\"...\" if len(reply) > 800 else \"\")\n        msg = f\"**Q:** {user_msg}\\n**A:** {short_reply}\"\n        _requests.post(DISCORD_WEBHOOK_URL, json={\"content\": msg}, timeout=5)\n    except Exception:\n        pass  # Discord failure never affects the agent\n\ndef run_agent(user_message: str, history: list) -> str:\n    messages = [{\"role\": \"system\", \"content\": SYSTEM_PROMPT}]\n    for human, assistant in history:\n        messages.append({\"role\": \"user\",      \"content\": human})\n        messages.append({\"role\": \"assistant\", \"content\": assistant})\n    messages.append({\"role\": \"user\", \"content\": user_message})\n\n    while True:\n        response = client.chat.completions.create(\n            model=\"gpt-4o-mini\",\n            messages=messages,\n            tools=TOOLS,\n            max_tokens=1000\n        )\n        message = response.choices[0].message\n\n        if response.choices[0].finish_reason == \"tool_calls\":\n            messages.append(message)\n            for tool_call in message.tool_calls:\n                fn_name = tool_call.function.name\n                fn_args = json.loads(tool_call.function.arguments)\n                print(f\"\ud83d\udd27 Using tool: {fn_name}\")\n                result = TOOL_FUNCTIONS.get(fn_name, lambda **k: \"Unknown tool\")(**fn_args)\n                print(f\"   Result: {result}\")\n                messages.append({\n                    \"role\": \"tool\",\n                    \"tool_call_id\": tool_call.id,\n                    \"content\": result\n                })\n        else:\n            reply = message.content or \"No response.\"\n            _post_discord(user_message, reply)\n            return reply\n\nprint(\"\u2705 Agent ready\")\n",
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": "# \u2500\u2500 CELL 6: Launch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nimport gradio as gr\nfrom fastapi import Request\n\ndef respond(message, history):\n    return run_agent(message, history)\n\n# Add /predict endpoint so AgentStatus can probe it\ndef predict(message: str) -> str:\n    \"\"\"Plain HTTP endpoint for external monitoring.\"\"\"\n    return run_agent(message, [])\n\nwith gr.Blocks() as demo:\n    gr.ChatInterface(\n        fn=respond,\n        title=\"\ud83c\udf93 Lehigh Campus Assistant\",\n        description=\"Ask me about courses, registration, or anything Lehigh.\",\n        examples=[\n            \"What are the prereqs for CSE 340?\",\n            \"Find me an open section of MATH 231 and post it to Discord\",\n            \"Which professor is better for CSE 340? Post the answer to Discord.\",\n            \"When does spring registration open?\",\n            \"What dining halls are open late on weekdays?\",\n        ],\n    )\n\n# Mount a simple /predict route AgentStatus can hit\napp = demo.app\n\n@app.post(\"/predict\")\nasync def predict_endpoint(request: Request):\n    body = await request.json()\n    message = body.get(\"data\", [\"\"])[0] if isinstance(body.get(\"data\"), list) else body.get(\"message\", \"\")\n    result = run_agent(str(message), [])\n    return {\"data\": [result]}\n\ndemo.launch(share=True, debug=False)\n",
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## \ud83d\udee0\ufe0f Hack time \u2014 make it yours\n",
    "\n",
    "**Change what it knows** \u2192 edit `SYSTEM_PROMPT` in Cell 3\n",
    "\n",
    "**Add a new tool** \u2192 add a function in Cell 4, register it in `TOOLS` and `TOOL_FUNCTIONS`\n",
    "\n",
    "**Tool ideas:**\n",
    "- `send_email` \u2192 Gmail SMTP (needs App Password \u2014 see README)\n",
    "- `send_sms` \u2192 Twilio free trial at twilio.com\n",
    "- `add_to_calendar` \u2192 Google Calendar API\n",
    "- `check_class_seats` \u2192 scrape Banner's public class search\n",
    "- `get_dining_menu` \u2192 scrape the Lehigh dining page\n",
    "\n",
    "**Monitor your agent** \u2192 [agentstatus.dev/lehigh](https://agentstatus.dev/lehigh)\n",
    "\n",
    "**Questions?** dulra@carmel.so \u00b7 roman@carmel.so"
   ]
  }
 ]
}