diff --git a/week8/community_contributions/IbrahimSheriff/week8_exercise.ipynb b/week8/community_contributions/IbrahimSheriff/week8_exercise.ipynb new file mode 100644 index 0000000000..f78843b7ce --- /dev/null +++ b/week8/community_contributions/IbrahimSheriff/week8_exercise.ipynb @@ -0,0 +1,348 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Week 8 Exercise: The Price is Right — IbrahimSheriff\n", + "\n", + "## Overview\n", + "This notebook implements a complete Week 8–style pipeline:\n", + "- **SpecialistAgent**: Fine-tuned LLM deployed on Modal\n", + "- **FrontierAgent**: RAG + frontier model (GPT) with ChromaDB\n", + "- **ChromaDB**: Vector store of product embeddings (SentenceTransformer)\n", + "- **Ensemble**: Combined predictor (Specialist + Frontier)\n", + "- **Evaluation**: Official `evaluate()` on test set (average absolute error)\n", + "- **DealAgentFramework + Gradio UI**: Autonomous deal-hunting and table UI\n", + "\n", + "## Prerequisites\n", + "- Modal token set; HuggingFace secret in Modal (`huggingface-secret`)\n", + "- Deploy pricer: from **week8** folder run `uv run modal deploy -m pricer_service`\n", + "- `.env`: `HF_TOKEN`, `OPENAI_API_KEY` (for FrontierAgent)\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Setup: path and environment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import sys\n", + "import logging\n", + "from pathlib import Path\n", + "from dotenv import load_dotenv\n", + "\n", + "load_dotenv(override=True)\n", + "os.environ.setdefault(\"PYTHONIOENCODING\", \"utf-8\")\n", + "\n", + "notebook_dir = Path.cwd()\n", + "week8_root = notebook_dir.parent.parent\n", + "if str(week8_root) not in sys.path:\n", + " sys.path.insert(0, str(week8_root))\n", + "\n", + "logging.getLogger().setLevel(logging.INFO)\n", + "print(f\"Week 8 root: {week8_root}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "required = [\"HF_TOKEN\", \"OPENAI_API_KEY\"]\n", + "for var in required:\n", + " status = \"SET\" if os.getenv(var) else \"MISSING\"\n", + " print(f\" {var}: {status}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Load data (Item.from_hub)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from agents.items import Item\n", + "from huggingface_hub import login\n", + "\n", + "hf_token = os.environ.get(\"HF_TOKEN\")\n", + "if hf_token:\n", + " login(token=hf_token, add_to_git_credential=False)\n", + "\n", + "LITE_MODE = False\n", + "username = \"ed-donner\"\n", + "dataset = f\"{username}/items_lite\" if LITE_MODE else f\"{username}/items_full\"\n", + "\n", + "train, val, test = Item.from_hub(dataset)\n", + "print(f\"Loaded {len(train):,} train, {len(val):,} val, {len(test):,} test items\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. ChromaDB vector store\n", + "\n", + "We build the vector store in the current folder so DealAgentFramework can use it later. Use a subset for speed if you prefer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import chromadb\n", + "from sentence_transformers import SentenceTransformer\n", + "from tqdm.notebook import tqdm\n", + "\n", + "DB = \"products_vectorstore\"\n", + "client = chromadb.PersistentClient(path=DB)\n", + "collection = client.get_or_create_collection(\"products\")\n", + "\n", + "def description(item):\n", + " return (item.summary or item.title or \"\").strip() or getattr(item, \"prompt\", \"\")[:500]\n", + "\n", + "N_DOCS = 20_000\n", + "existing = collection.count()\n", + "print(f\"Collection has {existing} documents.\")\n", + "if existing == 0:\n", + " print(f\"Populating with {N_DOCS} items...\")\n", + " encoder = SentenceTransformer(\"sentence-transformers/all-MiniLM-L6-v2\")\n", + " for i in tqdm(range(0, min(N_DOCS, len(train)), 1000)):\n", + " batch = train[i : i + 1000]\n", + " docs = [description(it) for it in batch]\n", + " vecs = encoder.encode(docs).astype(float).tolist()\n", + " metas = [{\"category\": getattr(it, \"category\", \"\"), \"price\": it.price} for it in batch]\n", + " ids = [f\"doc_{j}\" for j in range(i, i + len(batch))]\n", + " collection.add(ids=ids, documents=docs, embeddings=vecs, metadatas=metas)\n", + " print(f\"Done. Collection count: {collection.count()}\")\n", + "else:\n", + " print(\"Using existing vector store.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. SpecialistAgent (Modal fine-tuned pricer)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from agents.specialist_agent import SpecialistAgent\n", + "\n", + "specialist = SpecialistAgent()\n", + "sample = test[0]\n", + "est = specialist.price(description(sample))\n", + "print(f\"Sample: {sample.title[:50]}...\")\n", + "print(f\"Actual: ${sample.price:.2f}, Specialist: ${est:.2f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. FrontierAgent (RAG + frontier model)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from agents.frontier_agent import FrontierAgent\n", + "\n", + "frontier = FrontierAgent(collection)\n", + "est_f = frontier.price(description(sample))\n", + "print(f\"Frontier estimate: ${est_f:.2f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Predictors for evaluator\n", + "\n", + "Each takes an `Item` and returns a price (for `evaluate(predictor, test)`)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def specialist_predictor(item):\n", + " return specialist.price(description(item))\n", + "\n", + "def frontier_predictor(item):\n", + " return frontier.price(description(item))\n", + "\n", + "def ensemble_predictor(item):\n", + " s = specialist.price(description(item))\n", + " f = frontier.price(description(item))\n", + " return 0.5 * s + 0.5 * f" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. Run evaluation (Week 8 implementation)\n", + "\n", + "Uses the same `evaluate()` as week8 day2: 200 items, report and charts. This **determines your result**." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from agents.evaluator import evaluate\n", + "\n", + "print(\"=== SpecialistAgent ===\")\n", + "evaluate(specialist_predictor, test)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"=== FrontierAgent ===\")\n", + "evaluate(frontier_predictor, test)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"=== Ensemble (Specialist + Frontier) ===\")\n", + "evaluate(ensemble_predictor, test)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8. Optional: quick run with fewer items\n", + "\n", + "Uncomment to test with 50 items and 3 workers." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# evaluate(specialist_predictor, test, size=50, workers=3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 9. DealAgentFramework + Gradio UI\n", + "\n", + "Uses the same framework as week8 day5: planning agent, scanner, messenger. The UI shows a table of deals and a button to run one planning cycle. Ensure `products_vectorstore` is in the current directory (we built it above)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import gradio as gr\n", + "from deal_agent_framework import DealAgentFramework\n", + "from agents.deals import Opportunity, Deal\n", + "\n", + "agent_framework = DealAgentFramework()\n", + "agent_framework.init_agents_as_needed()\n", + "print(\"DealAgentFramework ready.\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_table(opps):\n", + " if not opps:\n", + " return [[\"No deals yet\", \"—\", \"—\", \"—\", \"—\"]]\n", + " return [\n", + " [\n", + " opp.deal.product_description[:80] + (\".\" if len(opp.deal.product_description) > 80 else \"\"),\n", + " f\"${opp.deal.price:.2f}\",\n", + " f\"${opp.estimate:.2f}\",\n", + " f\"${opp.discount:.2f}\",\n", + " opp.deal.url or \"—\",\n", + " ]\n", + " for opp in opps\n", + " ]\n", + "\n", + "def run_one_cycle():\n", + " agent_framework.run()\n", + " return get_table(agent_framework.memory)\n", + "\n", + "with gr.Blocks(title=\"The Price is Right\", fill_width=True) as ui:\n", + " gr.Markdown(\"
The Price is Right — IbrahimSheriff
\")\n", + " gr.Markdown(\"Deals surfaced by the autonomous agent (Specialist + Frontier + Scanner + Planner).\")\n", + " with gr.Row():\n", + " run_btn = gr.Button(\"Run one planning cycle\")\n", + " tbl = gr.Dataframe(\n", + " headers=[\"Description\", \"Price\", \"Estimate\", \"Discount\", \"URL\"],\n", + " wrap=True,\n", + " row_count=10,\n", + " col_count=5,\n", + " max_height=400,\n", + " )\n", + " run_btn.click(fn=run_one_cycle, outputs=[tbl])\n", + " ui.load(fn=lambda: get_table(agent_framework.memory), outputs=[tbl])\n", + "\n", + "ui.launch(inbrowser=True)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.11.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +}