{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 📊 Week 3 — DataExplainer Agent\n",
    "### AI Trailblazers · runs on **Google Colab** · powered by **Google Gemini (free)**\n",
    "\n",
    "Last week your agent fetched news. This week it **explains data**: give it the Titanic dataset and it will *describe*, *query*, and even *chart* it — deciding which tool to use for each question. Same **ReAct** loop (Reason → Act → Observe), new superpower.\n",
    "\n",
    "| Part | You'll do | ✅ Done when |\n",
    "|---|---|---|\n",
    "| **A · Setup** | Connect Gemini + load Titanic | The setup test prints ✅ and `df` loads |\n",
    "| **B · Tools** | Build describe / query / chart tools | Each 🧪 TEST runs the tool directly |\n",
    "| **C · Agent** | Wire tools into the loop | The agent answers data questions & draws a chart |\n",
    "| **D · Scope** | Plan your own agent project | You fill in the project charter |\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 (added matplotlib + seaborn for charts)\n",
    "!pip -q install google-genai pandas requests matplotlib seaborn"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "from google import genai\n",
    "from google.genai import types\n",
    "import pandas as pd, 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 a data-analysis 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": [
    "### Load the Titanic data\n",
    "We load Kaggle's classic beginner dataset into a DataFrame called **`df`** — every tool below reads from it."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "URL = 'https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv'\n",
    "df = pd.read_csv(URL)\n",
    "print('Rows, columns:', df.shape)\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "🧪 **Test:** you should see a table of passengers (Name, Sex, Age, Survived, Pclass, ...)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## 🅱️ Part B — Build the 3 tools\n",
    "A **tool** is just a Python function over `df`. We build three, and **test each one by hand first** — an agent is only as good as its tools, so we make sure they work before handing them over.\n",
    "\n",
    "### Tool 1 — `describe_dataset()`\n",
    "Answers *\"what's in this data?\"* — column names, types, and row count. No arguments needed."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "def describe_dataset() -> str:\n",
    "    \"\"\"Return the columns, data types, and row count of the dataset.\"\"\"\n",
    "    lines = [f'Rows: {len(df)}', f'Columns: {len(df.columns)}', '', 'Column           Dtype        Non-null']\n",
    "    for col in df.columns:\n",
    "        lines.append(f'{col:<16} {str(df[col].dtype):<12} {int(df[col].notna().sum())}')\n",
    "    return '\\n'.join(lines)\n",
    "\n",
    "print('describe_dataset ready')"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# 🧪 TEST the tool directly (before any agent)\n",
    "print(describe_dataset())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Tool 2 — `run_query()`\n",
    "Answers *\"what does the number say?\"* Instead of unsafe free-form `eval`, we give the agent a **small, controlled interface**: either **filter rows** with a pandas `df.query()` string, or **group + aggregate** one column by another."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "def run_query(filter: str = '', group_by: str = '', target: str = '', agg: str = 'mean') -> str:\n",
    "    \"\"\"Query the dataset. Two modes you can combine:\n",
    "    - filter: a pandas df.query() string to keep rows, e.g. \\\"Sex == 'female' and Pclass == 1\\\".\n",
    "    - group_by + target + agg: group by a column and aggregate a target column\n",
    "      (agg is one of mean, sum, count, min, max), e.g. group_by='Pclass', target='Survived', agg='mean'.\"\"\"\n",
    "    data = df\n",
    "    if filter:\n",
    "        try:\n",
    "            data = data.query(filter)\n",
    "        except Exception as e:\n",
    "            return f'Filter error: {e}'\n",
    "    if group_by:\n",
    "        if group_by not in df.columns:\n",
    "            return f'Unknown group_by column: {group_by}'\n",
    "        if target and target not in df.columns:\n",
    "            return f'Unknown target column: {target}'\n",
    "        try:\n",
    "            grouped = data.groupby(group_by)[target] if target else data.groupby(group_by)\n",
    "            result = getattr(grouped, agg)()\n",
    "        except Exception as e:\n",
    "            return f'Group error: {e}'\n",
    "        return str(result.round(3))\n",
    "    # no group_by: return a count (and the mean of target if given)\n",
    "    if target and target in df.columns:\n",
    "        return f'rows={len(data)}, {agg} of {target}={round(getattr(data[target], agg)(), 3)}'\n",
    "    return f'rows matching filter: {len(data)}'\n",
    "\n",
    "print('run_query ready')"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# 🧪 TEST the tool directly: survival rate of first-class women, and survival by class\n",
    "print(run_query(filter=\"Sex == 'female' and Pclass == 1\", target='Survived'))\n",
    "print(run_query(group_by='Pclass', target='Survived', agg='mean'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Tool 3 — `plot_chart()`\n",
    "Answers *\"show me a picture.\"* It renders a bar, line, or histogram inline with matplotlib/seaborn and returns a short confirmation string (the picture is a side effect the student sees)."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import matplotlib\n",
    "matplotlib.use('Agg') if False else None  # Colab shows plots inline by default\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "\n",
    "def plot_chart(kind: str, x: str, y: str = '') -> str:\n",
    "    \"\"\"Draw a chart from the dataset and display it.\n",
    "    kind is 'bar', 'line', or 'hist'. x is the column on the x-axis;\n",
    "    y is the column to aggregate (mean) for bar/line. hist ignores y.\"\"\"\n",
    "    if x not in df.columns:\n",
    "        return f'Unknown x column: {x}'\n",
    "    plt.figure(figsize=(6, 4))\n",
    "    if kind == 'hist':\n",
    "        sns.histplot(df[x].dropna())\n",
    "        plt.title(f'Distribution of {x}')\n",
    "    elif kind in ('bar', 'line'):\n",
    "        if y not in df.columns:\n",
    "            return f'bar/line needs a valid y column; got: {y}'\n",
    "        agg = df.groupby(x)[y].mean()\n",
    "        (agg.plot(kind='bar') if kind == 'bar' else agg.plot(kind='line', marker='o'))\n",
    "        plt.ylabel(f'mean {y}')\n",
    "        plt.title(f'mean {y} by {x}')\n",
    "    else:\n",
    "        return f'Unknown kind: {kind} (use bar, line, or hist)'\n",
    "    plt.tight_layout()\n",
    "    plt.show()\n",
    "    return f'Displayed a {kind} chart of {x}' + (f' vs {y}' if y else '')\n",
    "\n",
    "print('plot_chart ready')"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# 🧪 TEST the tool directly: survival by class as a bar chart\n",
    "print(plot_chart(kind='bar', x='Pclass', y='Survived'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## 🅲 Part C — The DataExplainer agent\n",
    "Now we hand all three tools to Gemini and run the **same manual loop as Week 2**: auto-calling OFF so *we* drive it and watch every `🛠️ Action`. A system instruction makes it use tools instead of guessing."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "TOOLS = {'describe_dataset': describe_dataset, 'run_query': run_query, 'plot_chart': plot_chart}\n",
    "\n",
    "config = types.GenerateContentConfig(\n",
    "    system_instruction=(\n",
    "        'You are DataExplainer, a data-analysis agent for a Titanic dataset. '\n",
    "        'You do NOT know the answers — you MUST use the tools to inspect the data. '\n",
    "        'Use describe_dataset to learn the columns, run_query to compute numbers, '\n",
    "        'and plot_chart to draw charts. Never guess; call a tool, read the result, then answer.'),\n",
    "    tools=[describe_dataset, run_query, plot_chart],\n",
    "    automatic_function_calling=types.AutomaticFunctionCallingConfig(disable=True),\n",
    ")\n",
    "\n",
    "def run_agent(task: str, max_steps: int = 6):\n",
    "    \"\"\"Run the manual ReAct loop and print each action.\"\"\"\n",
    "    history = [types.Content(role='user', parts=[types.Part(text=task)])]\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:\n",
    "            print('\\n🏁 FINAL ANSWER:\\n', resp.text); return resp.text\n",
    "        history.append(resp.candidates[0].content)          # Memory: remember the action\n",
    "        for fc in calls:\n",
    "            print(f'🛠️  Action → {fc.name}({dict(fc.args)})')\n",
    "            result = TOOLS[fc.name](**dict(fc.args))         # Act: run the tool\n",
    "            history.append(types.Content(role='user',        # Observation: feed result back\n",
    "                parts=[types.Part.from_function_response(name=fc.name, response={'result': str(result)})]))\n",
    "    print('\\n(stopped at safety cap)')\n",
    "\n",
    "print('Agent ready — call run_agent(\"...\")')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Challenge 1 — a number question"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "run_agent(\"What's the survival rate for women in first class?\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "🧪 **Test:** you should see a `🛠️ Action → run_query(...)` line, then a final answer near **0.97** (first-class women almost all survived)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Challenge 2 — a chart question"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "run_agent('Show me survival by class as a bar chart.')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "🧪 **Test:** you should see a `🛠️ Action → plot_chart(...)` line, a **bar chart** appear, then a short final answer. The agent *chose* the chart tool on its own. 🎉"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# 🎯 YOUR TURN: ask your own question about the Titanic data.\n",
    "# Ideas: 'What was the average fare by class?'  ·  'Draw a histogram of passenger ages.'\n",
    "#        'Did more men or women survive?'\n",
    "run_agent('What was the average fare paid in each passenger class?')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## 🅳 Part D — Scope YOUR agent project\n",
    "Next week is the **hackathon**. Copy the charter below into your notes and fill it in — a tight scope is the difference between a demo that works and one that doesn't."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 📋 Project Charter (copy me)\n",
    "```\n",
    "PROJECT NAME:  ____________________________\n",
    "\n",
    "OBJECTIVE (one sentence — what question does your agent answer?):\n",
    "  ________________________________________________________________\n",
    "\n",
    "TOOLS (2–3 Python functions your agent can call):\n",
    "  1. ____________________  →  does what?  ______________________\n",
    "  2. ____________________  →  does what?  ______________________\n",
    "  3. ____________________  →  does what?  ______________________\n",
    "\n",
    "DATA SOURCE(S) (a CSV URL, an API, a file you upload):\n",
    "  ________________________________________________________________\n",
    "\n",
    "EXPECTED OUTPUT (what does a good answer look like? a number? a chart? bullets?):\n",
    "  ________________________________________________________________\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 🤔 When do you NOT need an agent?\n",
    "An agent is powerful, but it costs API calls and can be unpredictable. **If one line of pandas answers the question, just write the line.**\n",
    "\n",
    "| Situation | Better choice |\n",
    "|---|---|\n",
    "| You always ask the *same* question | Deterministic: `df.groupby('Sex')['Age'].mean()` |\n",
    "| The steps never change | A plain Python function |\n",
    "| Questions vary and you can't predict them | **Agentic**: let the LLM pick the tool |\n",
    "| A human must phrase it in plain English | **Agentic**: natural-language front door |\n",
    "\n",
    "Rule of thumb: **deterministic when the *path* is known, agentic when the *question* is open.** A good project uses the agent only where the flexibility actually earns its keep."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## 🎉 You built a DataExplainer!\n",
    "You gave an LLM three **data tools** and it reasoned about which to use — describing, querying, and charting real data through the **ReAct loop**.\n",
    "\n",
    "**Homework:** finish your Project Charter and pick one dataset (Kaggle 'Getting Started' or a CSV you like).\n",
    "\n",
    "**Next week — the Hackathon:** you'll build *your own* agent from the charter and demo it. Come with your scope ready!\n",
    "\n",
    "_AI Trailblazers · LearnAI · part of MillionRoots._"
   ]
  }
 ],
 "metadata": {
  "colab": {
   "name": "Week 3 — DataExplainer Agent",
   "provenance": []
  },
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}