{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Week 8: Multi-Agent Deal Hunting System\n",
"\n",
"This notebook demonstrates Week 8 concepts:\n",
"- Multi-agent architecture with specialized agents\n",
"- Real-time Gradio UI with threading and queues\n",
"- Integration with deployed Modal services\n",
"- RAG pipeline with ChromaDB\n",
"- Ensemble model combining multiple AI approaches"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!pip install -q gradio pydantic openai chromadb sentence-transformers scikit-learn feedparser beautifulsoup4 requests plotly"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import logging\n",
"import queue\n",
"import threading\n",
"import time\n",
"import json\n",
"from typing import List, Optional\n",
"from datetime import datetime\n",
"\n",
"import gradio as gr\n",
"import plotly.graph_objects as go\n",
"from pydantic import BaseModel"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class Deal(BaseModel):\n",
" product_description: str\n",
" price: float\n",
" url: str\n",
"\n",
"\n",
"class DealSelection(BaseModel):\n",
" deals: List[Deal]\n",
"\n",
"\n",
"class Opportunity(BaseModel):\n",
" deal: Deal\n",
" estimate: float\n",
" discount: float\n",
"\n",
"\n",
"class MockAgent:\n",
" name = \"Mock Agent\"\n",
" color = '\\033[37m'\n",
" \n",
" def log(self, message):\n",
" logging.info(f\"[{self.name}] {message}\")\n",
"\n",
"\n",
"class MockScannerAgent(MockAgent):\n",
" name = \"Scanner Agent\"\n",
" \n",
" def scan(self, memory=None):\n",
" self.log(\"Simulating RSS feed scan\")\n",
" time.sleep(1)\n",
" \n",
" deals = [\n",
" Deal(\n",
" product_description=\"Apple iPad Pro 11-inch 256GB WiFi (latest model) - Space Gray. Features M2 chip, Liquid Retina display, 12MP camera, Face ID, and all-day battery life.\",\n",
" price=749.99,\n",
" url=\"https://example.com/ipad\"\n",
" ),\n",
" Deal(\n",
" product_description=\"Sony WH-1000XM5 Wireless Noise Cancelling Headphones - Industry-leading noise cancellation, exceptional sound quality, 30-hour battery life, comfortable design.\",\n",
" price=329.99,\n",
" url=\"https://example.com/sony-headphones\"\n",
" )\n",
" ]\n",
" \n",
" return DealSelection(deals=deals)\n",
"\n",
"\n",
"class MockEnsembleAgent(MockAgent):\n",
" name = \"Ensemble Agent\"\n",
" \n",
" def price(self, description: str) -> float:\n",
" self.log(f\"Estimating price for product\")\n",
" time.sleep(0.5)\n",
" \n",
" if \"iPad\" in description:\n",
" return 899.00\n",
" elif \"Sony\" in description:\n",
" return 398.00\n",
" else:\n",
" return 150.00\n",
"\n",
"\n",
"class MockMessagingAgent(MockAgent):\n",
" name = \"Messaging Agent\"\n",
" \n",
" def alert(self, opportunity: Opportunity):\n",
" self.log(f\"Alert sent: ${opportunity.discount:.2f} discount on {opportunity.deal.product_description[:50]}...\")\n",
"\n",
"\n",
"class MockPlanningAgent(MockAgent):\n",
" name = \"Planning Agent\"\n",
" DEAL_THRESHOLD = 50\n",
" \n",
" def __init__(self):\n",
" self.scanner = MockScannerAgent()\n",
" self.ensemble = MockEnsembleAgent()\n",
" self.messenger = MockMessagingAgent()\n",
" \n",
" def plan(self, memory=None) -> Optional[Opportunity]:\n",
" if memory is None:\n",
" memory = []\n",
" \n",
" self.log(\"Starting planning cycle\")\n",
" \n",
" selection = self.scanner.scan(memory)\n",
" \n",
" if selection and selection.deals:\n",
" opportunities = []\n",
" for deal in selection.deals:\n",
" estimate = self.ensemble.price(deal.product_description)\n",
" discount = estimate - deal.price\n",
" opportunities.append(Opportunity(\n",
" deal=deal,\n",
" estimate=estimate,\n",
" discount=discount\n",
" ))\n",
" \n",
" opportunities.sort(key=lambda x: x.discount, reverse=True)\n",
" best = opportunities[0]\n",
" \n",
" self.log(f\"Best deal has discount: ${best.discount:.2f}\")\n",
" \n",
" if best.discount > self.DEAL_THRESHOLD:\n",
" self.messenger.alert(best)\n",
" return best\n",
" \n",
" return None\n",
"\n",
"\n",
"class MockDealAgentFramework:\n",
" MEMORY_FILE = \"mock_memory.json\"\n",
" \n",
" def __init__(self):\n",
" self.memory = self.read_memory()\n",
" self.planner = None\n",
" \n",
" def init_agents_as_needed(self):\n",
" if not self.planner:\n",
" logging.info(\"Initializing Mock Agent Framework\")\n",
" self.planner = MockPlanningAgent()\n",
" logging.info(\"Mock Agent Framework ready\")\n",
" \n",
" def read_memory(self) -> List[Opportunity]:\n",
" if os.path.exists(self.MEMORY_FILE):\n",
" try:\n",
" with open(self.MEMORY_FILE, 'r') as f:\n",
" data = json.load(f)\n",
" return [Opportunity(**item) for item in data]\n",
" except:\n",
" return []\n",
" return []\n",
" \n",
" def write_memory(self):\n",
" data = [opp.dict() for opp in self.memory]\n",
" with open(self.MEMORY_FILE, 'w') as f:\n",
" json.dump(data, f, indent=2)\n",
" \n",
" def run(self) -> List[Opportunity]:\n",
" self.init_agents_as_needed()\n",
" result = self.planner.plan(memory=self.memory)\n",
" \n",
" if result:\n",
" self.memory.append(result)\n",
" self.write_memory()\n",
" \n",
" return self.memory\n",
" \n",
" @classmethod\n",
" def get_plot_data(cls, max_datapoints=100):\n",
" import numpy as np\n",
" \n",
" n_points = min(100, max_datapoints)\n",
" vectors = np.random.randn(n_points, 3)\n",
" documents = [f\"Product {i}\" for i in range(n_points)]\n",
" colors = ['red', 'blue', 'green', 'orange'] * (n_points // 4 + 1)\n",
" \n",
" return documents[:n_points], vectors, colors[:n_points]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"BG_BLACK = '\\033[40m'\n",
"RED = '\\033[31m'\n",
"GREEN = '\\033[32m'\n",
"YELLOW = '\\033[33m'\n",
"BLUE = '\\033[34m'\n",
"MAGENTA = '\\033[35m'\n",
"CYAN = '\\033[36m'\n",
"WHITE = '\\033[37m'\n",
"BG_BLUE = '\\033[44m'\n",
"RESET = '\\033[0m'\n",
"\n",
"color_mapper = {\n",
" BG_BLACK+RED: \"#dd0000\",\n",
" BG_BLACK+GREEN: \"#00dd00\",\n",
" BG_BLACK+YELLOW: \"#dddd00\",\n",
" BG_BLACK+BLUE: \"#0000ee\",\n",
" BG_BLACK+MAGENTA: \"#aa00dd\",\n",
" BG_BLACK+CYAN: \"#00dddd\",\n",
" BG_BLACK+WHITE: \"#87CEEB\",\n",
" BG_BLUE+WHITE: \"#ff7800\"\n",
"}\n",
"\n",
"\n",
"def reformat_log(message):\n",
" for key, value in color_mapper.items():\n",
" message = message.replace(key, f'')\n",
" message = message.replace(RESET, '')\n",
" return message\n",
"\n",
"\n",
"class QueueHandler(logging.Handler):\n",
" def __init__(self, log_queue):\n",
" super().__init__()\n",
" self.log_queue = log_queue\n",
"\n",
" def emit(self, record):\n",
" self.log_queue.put(self.format(record))\n",
"\n",
"\n",
"def setup_logging(log_queue):\n",
" handler = QueueHandler(log_queue)\n",
" formatter = logging.Formatter(\n",
" \"[%(asctime)s] %(message)s\",\n",
" datefmt=\"%Y-%m-%d %H:%M:%S\",\n",
" )\n",
" handler.setFormatter(formatter)\n",
" logger = logging.getLogger()\n",
" logger.addHandler(handler)\n",
" logger.setLevel(logging.INFO)\n",
"\n",
"\n",
"def html_for(log_data):\n",
" output = '
'.join(log_data[-20:])\n",
" return f\"\"\"\n",
"