From b94b73158fd4101c864451e82e2eeec9f226fc61 Mon Sep 17 00:00:00 2001 From: abdoulrasheed Date: Fri, 31 Oct 2025 02:12:54 +0000 Subject: [PATCH] W-VIII --- .../abdoul/week_eight_exercise.ipynb | 487 ++++++++++++++++++ 1 file changed, 487 insertions(+) create mode 100644 community-contributions/abdoul/week_eight_exercise.ipynb diff --git a/community-contributions/abdoul/week_eight_exercise.ipynb b/community-contributions/abdoul/week_eight_exercise.ipynb new file mode 100644 index 0000000..c08bce7 --- /dev/null +++ b/community-contributions/abdoul/week_eight_exercise.ipynb @@ -0,0 +1,487 @@ +{ + "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 +}