{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 🚀 Week 4 — Hackathon Scaffold (your project starts here)\n",
    "### AI Trailblazers · runs on **Google Colab** · powered by **Google Gemini (free)**\n",
    "\n",
    "Weeks 2–3 you built agents from scratch. **Today you get a clean, reusable scaffold** — a tiny agent framework you'll build your OWN project on. Same manual **ReAct loop** (Reason → Act → Observe), now packaged so you can drop in your own tools and go.\n",
    "\n",
    "| Part | You'll do | ✅ Done when |\n",
    "|---|---|---|\n",
    "| **A · Toolkit** | A reusable `agent()` + tool registry | The demo agent logs its Thought→Action→Observation loop |\n",
    "| **B · Break it & fix it** | See each failure mode, then the fix | All 3 bugs are caught and fixed |\n",
    "| **C · Your project** | Paste your own tools, run your task | `agent()` runs on YOUR idea |\n",
    "\n",
    "> Run each grey **code cell** in order with **▶️ / Shift+Enter**. Read the note above it first."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## 🅰️ Part A — Setup\n",
    "### 🔑 Get your free Gemini key (once)\n",
    "1. Open **https://aistudio.google.com/apikey** → **Create API key** → copy it.\n",
    "2. Run the cells below; paste the key when asked.\n",
    "\n",
    "🔒 An API key is a password — never paste it into a public doc, chat, or GitHub."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Install everything we need this week\n",
    "!pip -q install google-genai pandas requests matplotlib"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "from google import genai\n",
    "from google.genai import types\n",
    "import pandas as pd, requests, getpass\n",
    "\n",
    "API_KEY = getpass.getpass('Paste your Gemini API key: ').strip()\n",
    "client = genai.Client(api_key=API_KEY)\n",
    "print('Client ready ✅')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Model names change, so we ask your key which models exist and pick a fast one automatically."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "model_names = [m.name for m in client.models.list()]\n",
    "flash = [m for m in model_names if 'flash' in m and 'embedding' not in m]\n",
    "MODEL = flash[0] if flash else 'models/gemini-2.5-flash'\n",
    "print('Using MODEL =', MODEL)"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ✅ TEST: talk to Gemini once\n",
    "r = client.models.generate_content(model=MODEL, contents='In 5 words, what is an AI agent?')\n",
    "print(r.text)\n",
    "assert r.text, 'No reply — check your key/model.'\n",
    "print('\\n✅ Setup works!')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## 🅰️ Part A — Debugging toolkit (the scaffold)\n",
    "Before you build, know the **3 ways agents go wrong** — you'll hit all of these during the hackathon:\n",
    "\n",
    "| # | Failure pattern | What it looks like | The fix (in this scaffold) |\n",
    "|---|---|---|---|\n",
    "| 1 | **Hallucination** | Agent *guesses* an answer instead of using a tool | A strict **system instruction** that says *never guess — call a tool* |\n",
    "| 2 | **Tool error** | A tool crashes (bad input, network down) and kills the run | `run_tool()` wraps every call in **try/except** and logs `❌` |\n",
    "| 3 | **Prompt issue** | Vague task → agent wanders or stops early | **Tighten the task**: say exactly what to do and when to stop |\n",
    "\n",
    "The scaffold below makes every step **visible** (it prints each Thought→Action→Observation) so when something breaks, you can *see where*. That visibility is the whole point."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Piece 1 — the tool registry + a logged `run_tool()`\n",
    "A **tool** is just a Python function. We keep them in a `TOOLS` dict (the *registry*) so the agent can look them up by name. `run_tool()` is the safety wrapper: it runs a tool, **logs the call**, and **catches errors** so one bad tool can't crash your whole agent."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# TOOLS: your reusable registry. Add your project's tools here by name.\n",
    "TOOLS = {}\n",
    "\n",
    "def register(fn):\n",
    "    \"\"\"Add a function to the TOOLS registry (also usable as @register).\"\"\"\n",
    "    TOOLS[fn.__name__] = fn\n",
    "    return fn\n",
    "\n",
    "def run_tool(name, args):\n",
    "    \"\"\"Run one tool by name with a dict of args. Logs the call and CATCHES errors.\"\"\"\n",
    "    if name not in TOOLS:\n",
    "        msg = f'unknown tool: {name}'\n",
    "        print(f'❌ {name}({args}) -> {msg}')\n",
    "        return f'ERROR: {msg}'\n",
    "    try:\n",
    "        result = TOOLS[name](**args)           # Act: actually run the tool\n",
    "        print(f'🛠️  {name}({args}) -> {result}')\n",
    "        return result\n",
    "    except Exception as e:                      # Tool error → caught, not fatal\n",
    "        print(f'❌ {name}({args}) -> {type(e).__name__}: {e}')\n",
    "        return f'ERROR: {type(e).__name__}: {e}'\n",
    "\n",
    "print('Registry ready. TOOLS =', list(TOOLS))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Piece 2 — two starter tools\n",
    "Two simple tools to prove the scaffold works: a **calculator** and a **mock data lookup**. You'll replace these with your own in Part C."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "@register\n",
    "def calculator(expression: str) -> str:\n",
    "    \"\"\"Evaluate a basic math expression, e.g. '12 * (3 + 4)'.\"\"\"\n",
    "    allowed = set('0123456789+-*/(). ')\n",
    "    if not set(expression) <= allowed:\n",
    "        raise ValueError('only numbers and + - * / ( ) allowed')\n",
    "    return str(eval(expression))            # safe: we checked the characters above\n",
    "\n",
    "@register\n",
    "def lookup_price(product: str) -> str:\n",
    "    \"\"\"Look up the price of a product from our mini catalog.\"\"\"\n",
    "    catalog = {'cone': 3.50, 'sundae': 6.00, 'shake': 5.25}\n",
    "    key = product.strip().lower()\n",
    "    if key not in catalog:\n",
    "        raise KeyError(f'{product} not in catalog {list(catalog)}')\n",
    "    return f'${catalog[key]:.2f}'\n",
    "\n",
    "print('Registered tools:', list(TOOLS))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Piece 3 — the reusable `agent()` loop\n",
    "This is the **same manual function-calling loop** from Week 2 — now wrapped in a function you can reuse for any project. It:\n",
    "\n",
    "- takes a `task`, a list of `tools`, and a `max_steps` **safety cap**;\n",
    "- turns **OFF** `automatic_function_calling` so *we* drive the loop and can watch it;\n",
    "- **logs every step** (Thought → Action → Observation) via `run_tool()`;\n",
    "- feeds each tool result back with `types.Part.from_function_response`."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "def agent(task, tools, max_steps=6, system=None):\n",
    "    \"\"\"Run the manual ReAct loop. `tools` = list of functions the agent may call.\"\"\"\n",
    "    system = system or (\n",
    "        'You are a helpful agent. You do NOT know facts you have not looked up. '\n",
    "        'To answer, you MUST call the provided tools. Never guess or make up numbers.')\n",
    "    config = types.GenerateContentConfig(\n",
    "        system_instruction=system,\n",
    "        tools=tools,\n",
    "        automatic_function_calling=types.AutomaticFunctionCallingConfig(disable=True),\n",
    "    )\n",
    "    history = [types.Content(role='user', parts=[types.Part(text=task)])]\n",
    "\n",
    "    for step in range(max_steps):           # safety cap so the loop can't run forever\n",
    "        resp = client.models.generate_content(model=MODEL, contents=history, config=config)\n",
    "        parts = resp.candidates[0].content.parts\n",
    "        calls = [p.function_call for p in parts if p.function_call]\n",
    "        if not calls:                        # no tool wanted → this is the final answer\n",
    "            print('\\n🏁 FINAL ANSWER:\\n', resp.text)\n",
    "            return resp.text\n",
    "        history.append(resp.candidates[0].content)   # Memory: remember the action\n",
    "        for fc in calls:\n",
    "            result = run_tool(fc.name, dict(fc.args))  # Act + log (try/except inside)\n",
    "            history.append(types.Content(role='user',  # Observation: feed result back\n",
    "                parts=[types.Part.from_function_response(\n",
    "                    name=fc.name, response={'result': result})]))\n",
    "    print('\\n⚠️ Hit max_steps — agent did not finish. (See Debugging clinic below.)')\n",
    "    return None\n",
    "\n",
    "print('agent() ready — the scaffold is complete.')"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# 🧪 TEST: run the scaffold on a task that needs BOTH starter tools.\n",
    "# Watch the logged loop: 🛠️ lines are tool calls, 🏁 is the final answer.\n",
    "agent('What is the price of a sundae, and what do 4 sundaes cost? Use the tools.',\n",
    "      tools=[calculator, lookup_price])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "🧪 **Test:** you should see `🛠️` lines (the agent calling `lookup_price` and `calculator`), then a `🏁 FINAL ANSWER`. That logged loop is your reusable engine — everything else is just swapping tools. 🎉"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## 🅱️ Part B — Break it & fix it\n",
    "Now let's trigger each failure mode on purpose and watch the scaffold handle it. Small cells — read, run, see the fix."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Bug 1 — a tool that crashes 💥\n",
    "Real tools fail: bad input, missing data, network down. Without protection, one crash kills the whole run. Watch `run_tool()` **catch and log** it instead — the agent keeps going."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "@register\n",
    "def flaky_divide(a: float, b: float) -> str:\n",
    "    \"\"\"Divide a by b. Crashes on divide-by-zero (on purpose).\"\"\"\n",
    "    return str(a / b)          # b=0 raises ZeroDivisionError\n",
    "\n",
    "# Call it directly through run_tool with a bad arg — no try/except needed at the call site.\n",
    "print('Result returned to agent:', run_tool('flaky_divide', {'a': 10, 'b': 0}))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "🧪 **Test:** you see a `❌ flaky_divide(...) -> ZeroDivisionError` line, and the notebook **keeps running**. ✅ **The fix is already built in:** `run_tool()` wraps every tool in try/except, so a crashing tool returns an `ERROR:` string the agent can react to instead of killing your program."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Bug 2 — a vague prompt vs a tight one 🎯\n",
    "Vague tasks make agents wander, guess, or stop early. Compare a fuzzy task with a specific one — **same agent, same tools, very different behavior.**"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# 👎 Vague: 'stuff' is undefined; the agent has nothing concrete to act on.\n",
    "print('--- VAGUE PROMPT ---')\n",
    "agent('Tell me about the ice cream stuff.', tools=[calculator, lookup_price], max_steps=3)"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# 👍 Tight: says exactly what to compute and to use the tools.\n",
    "print('--- TIGHT PROMPT ---')\n",
    "agent('Using the tools, look up the price of a cone and a shake, then add them together.',\n",
    "      tools=[calculator, lookup_price], max_steps=4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "🧪 **Test:** the tight prompt produces clean `🛠️` tool calls and a correct total; the vague one rambles or skips the tools. ✅ **Fix:** write the task like an instruction — *what to do, which tools, when to stop.*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Bug 3 — guessing vs forced tool use 🧠\n",
    "Left alone, an LLM will happily **make up** a number. The fix is the **system instruction** — order it to call a tool and never guess."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# 👎 No rules: model may invent a price out of thin air.\n",
    "print('--- LETS IT GUESS ---')\n",
    "agent('How much is a sundae?', tools=[lookup_price], max_steps=3,\n",
    "      system='You are a friendly shop assistant. Answer the question.')"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# 👍 Forced: the default system rule makes it call lookup_price instead of guessing.\n",
    "print('--- FORCED TO USE TOOL ---')\n",
    "agent('How much is a sundae?', tools=[lookup_price], max_steps=3)   # uses strict default system"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "🧪 **Test:** the forced run shows a `🛠️ lookup_price` call before the answer; the guessing run may state a price with no tool call (a hallucination). ✅ **Fix:** a strict `system_instruction` — *never guess; you MUST call a tool* — is your #1 defense against made-up answers."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## 🅲 Part C — Your project starts here\n",
    "Everything above is reusable. To build **your** agent you only change two things:\n",
    "1. **Add your tool(s)** to the registry (a Python function + `@register`).\n",
    "2. **Call `agent()`** with your task and your tools.\n",
    "\n",
    "Fill in the template below. Keep tools small and single-purpose — that's what makes agents reliable."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# ==================== 👇 YOUR PROJECT TEMPLATE — edit me ====================\n",
    "\n",
    "@register\n",
    "def my_tool(query: str) -> str:\n",
    "    \"\"\"TODO: describe what your tool does (the model reads this docstring!).\"\"\"\n",
    "    # TODO: replace with your real logic (an API call, a lookup, a calculation...)\n",
    "    return f'you asked: {query}'\n",
    "\n",
    "# TODO: write YOUR task as a clear instruction (what to do, which tool, when to stop).\n",
    "MY_TASK = 'Use my_tool to look something up and report the result.'\n",
    "\n",
    "# Run YOUR agent. Add every tool you want available to the tools=[...] list.\n",
    "agent(MY_TASK, tools=[my_tool])\n",
    "\n",
    "# ========================================================================="
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 🎯 YOUR TURN — build toward your MVP\n",
    "Work through this checklist. Test after **every** change (run the cell, read the logged loop).\n",
    "\n",
    "**Phase 1 — core loop + 1 tool**\n",
    "- [ ] Rename `my_tool` to something real and write a clear docstring.\n",
    "- [ ] Make it return real info (a lookup, a calc, a `requests` API call).\n",
    "- [ ] Write `MY_TASK` as a tight instruction and run `agent()`.\n",
    "- [ ] Confirm you see a `🛠️` call and a sensible `🏁 FINAL ANSWER`.\n",
    "\n",
    "**Phase 2 — add a 2nd tool + error handling**\n",
    "- [ ] Add a second `@register` tool and put it in `tools=[...]`.\n",
    "- [ ] Write a task that needs BOTH tools in sequence.\n",
    "- [ ] Feed a tool a bad input on purpose — confirm `run_tool()` logs `❌` and the agent recovers.\n",
    "- [ ] Tighten your `system` instruction if the agent ever guesses."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 🩺 Debugging clinic — when your agent misbehaves\n",
    "Run into trouble? Walk this list (it maps to the 3 failure patterns):\n",
    "\n",
    "- **Agent made up an answer (no `🛠️` line)?** → Hallucination. Tighten `system_instruction`: *never guess; you MUST call a tool.*\n",
    "- **A tool printed `❌`?** → Tool error. Read the error type, fix the tool's input handling, re-run just that tool via `run_tool()`.\n",
    "- **Agent wandered or stopped early?** → Prompt issue. Make `MY_TASK` more specific: what to do, which tools, when to stop.\n",
    "- **Hit `⚠️ max_steps`?** → It's looping. Raise `max_steps`, or your tool descriptions are unclear — sharpen the docstrings.\n",
    "- **`unknown tool` error?** → You called `agent()` without adding the function to `tools=[...]`. Add it.\n",
    "- **Wrong tool called?** → The docstrings look too similar. Make each tool's docstring describe *exactly* when to use it."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## 🎉 You have a reusable agent framework!\n",
    "You built a **scaffold** — a tool registry, a logged `run_tool()`, and a reusable `agent()` loop — and you know how to **debug** the 3 ways agents fail (hallucinations, tool errors, prompt issues).\n",
    "\n",
    "**Homework — build your MVP:** get your project running with **2 working tools** and a task that uses both. Bring the logged loop to Week 5.\n",
    "\n",
    "**Next week — Polish & Present:** you'll clean up your agent, handle edge cases, and demo it. Ship day. 🚀\n",
    "\n",
    "_AI Trailblazers · LearnAI · part of MillionRoots._"
   ]
  }
 ],
 "metadata": {
  "colab": {
   "name": "Week 4 — Hackathon Scaffold",
   "provenance": []
  },
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}