{ "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", "
\n", " {output}\n", "
\n", " \"\"\"\n", "\n", "\n", "def get_plot():\n", " try:\n", " documents, vectors, colors = MockDealAgentFramework.get_plot_data(max_datapoints=100)\n", " \n", " fig = go.Figure(data=[go.Scatter3d(\n", " x=vectors[:, 0],\n", " y=vectors[:, 1],\n", " z=vectors[:, 2],\n", " mode='markers',\n", " marker=dict(size=3, color=colors, opacity=0.7),\n", " )])\n", " \n", " fig.update_layout(\n", " scene=dict(\n", " xaxis_title='X', \n", " yaxis_title='Y', \n", " zaxis_title='Z',\n", " aspectmode='manual',\n", " aspectratio=dict(x=2.2, y=2.2, z=1),\n", " camera=dict(eye=dict(x=1.6, y=1.6, z=0.8)),\n", " bgcolor='#1a1a1a'\n", " ),\n", " height=420,\n", " margin=dict(r=5, b=5, l=5, t=5),\n", " paper_bgcolor='#1a1a1a',\n", " font=dict(color='#ffffff'),\n", " title=\"Mock Vector Space (Random Data for Demo)\"\n", " )\n", " return fig\n", " \n", " except Exception as e:\n", " fig = go.Figure()\n", " fig.update_layout(\n", " title=f'Error: {str(e)}',\n", " height=420,\n", " paper_bgcolor='#1a1a1a',\n", " font=dict(color='#ffffff')\n", " )\n", " return fig" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class DealHunterApp:\n", " \n", " def __init__(self):\n", " self.framework = None\n", " \n", " def get_framework(self):\n", " if not self.framework:\n", " self.framework = MockDealAgentFramework()\n", " self.framework.init_agents_as_needed()\n", " return self.framework\n", " \n", " def opportunities_to_table(self, opportunities):\n", " if not opportunities:\n", " return []\n", " \n", " return [\n", " [\n", " opp.deal.product_description,\n", " f\"${opp.deal.price:.2f}\",\n", " f\"${opp.estimate:.2f}\",\n", " f\"${opp.discount:.2f}\",\n", " opp.deal.url\n", " ]\n", " for opp in opportunities\n", " if isinstance(opp, Opportunity)\n", " ]\n", " \n", " def scan_for_deals(self):\n", " framework = self.get_framework()\n", " logging.info(f\"Scan triggered at {datetime.now().strftime('%H:%M:%S')} - Current memory: {len(framework.memory)} deals\")\n", " new_opportunities = framework.run()\n", " logging.info(f\"Scan complete - Total deals: {len(framework.memory)}\")\n", " return self.opportunities_to_table(new_opportunities)\n", " \n", " def scan_with_logging(self, log_data):\n", " log_queue = queue.Queue()\n", " result_queue = queue.Queue()\n", " setup_logging(log_queue)\n", " \n", " def worker():\n", " result = self.scan_for_deals()\n", " result_queue.put(result)\n", " \n", " thread = threading.Thread(target=worker)\n", " thread.start()\n", " \n", " framework = self.get_framework()\n", " current_table = self.opportunities_to_table(framework.memory)\n", " \n", " while True:\n", " try:\n", " message = log_queue.get_nowait()\n", " log_data.append(reformat_log(message))\n", " current_table = self.opportunities_to_table(framework.memory)\n", " yield log_data, html_for(log_data), current_table\n", " except queue.Empty:\n", " try:\n", " final_table = result_queue.get_nowait()\n", " yield log_data, html_for(log_data), final_table\n", " return\n", " except queue.Empty:\n", " current_table = self.opportunities_to_table(framework.memory)\n", " yield log_data, html_for(log_data), current_table\n", " time.sleep(0.1)\n", " \n", " def handle_selection(self, selected_index: gr.SelectData):\n", " framework = self.get_framework()\n", " row = selected_index.index[0]\n", " \n", " if row < len(framework.memory):\n", " opportunity = framework.memory[row]\n", " framework.planner.messenger.alert(opportunity)\n", " return f\"Alert sent for: {opportunity.deal.product_description[:60]}...\"\n", " \n", " return \"Invalid selection\"\n", " \n", " def load_initial_state(self):\n", " framework = self.get_framework()\n", " initial_table = self.opportunities_to_table(framework.memory)\n", " return [], \"\", initial_table\n", " \n", " def launch(self):\n", " with gr.Blocks(title=\"The Price is Right\", fill_width=True) as ui:\n", " \n", " log_data = gr.State([])\n", " \n", " gr.Markdown(\n", " '
The Price is Right - Demo
'\n", " '
Multi-Agent Deal Hunting System (Mock Version)
'\n", " )\n", " \n", " with gr.Row():\n", " opportunities_table = gr.Dataframe(\n", " headers=[\"Product Description\", \"Price\", \"Estimate\", \"Discount\", \"URL\"],\n", " wrap=True,\n", " column_widths=[6, 1, 1, 1, 3],\n", " row_count=10,\n", " col_count=5,\n", " max_height=420,\n", " interactive=False\n", " )\n", " \n", " with gr.Row():\n", " with gr.Column(scale=1):\n", " logs_display = gr.HTML(label=\"Agent Logs\")\n", " with gr.Column(scale=1):\n", " vector_plot = gr.Plot(value=get_plot(), show_label=False)\n", " \n", " ui.load(\n", " self.load_initial_state,\n", " inputs=[],\n", " outputs=[log_data, logs_display, opportunities_table]\n", " )\n", " \n", " timer = gr.Timer(value=10, active=True)\n", " timer.tick(\n", " self.scan_with_logging,\n", " inputs=[log_data],\n", " outputs=[log_data, logs_display, opportunities_table]\n", " )\n", " \n", " selection_feedback = gr.Textbox(visible=False)\n", " opportunities_table.select(\n", " self.handle_selection,\n", " inputs=[],\n", " outputs=[selection_feedback]\n", " )\n", " \n", " ui.launch(share=True, inbrowser=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "app = DealHunterApp()\n", "app.launch()" ] } ], "metadata": { "kernelspec": { "display_name": "env", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.0" } }, "nbformat": 4, "nbformat_minor": 2 }