Merge branch 'main' of github.com:ed-donner/llm_engineering

This commit is contained in:
Edward Donner
2025-11-01 22:26:37 -04:00
280 changed files with 110983 additions and 396 deletions

View File

@@ -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'<span style=\"color: {value}\">')\n",
" message = message.replace(RESET, '</span>')\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 = '<br>'.join(log_data[-20:])\n",
" return f\"\"\"\n",
" <div style=\"height: 420px; overflow-y: auto; border: 1px solid #444; background-color: #1a1a1a; padding: 12px; font-family: monospace; font-size: 13px; color: #fff;\">\n",
" {output}\n",
" </div>\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",
" '<div style=\"text-align: center; font-size: 28px; font-weight: bold; margin: 20px 0;\">The Price is Right - Demo</div>'\n",
" '<div style=\"text-align: center; font-size: 16px; color: #666; margin-bottom: 20px;\">Multi-Agent Deal Hunting System (Mock Version)</div>'\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
}

View File

@@ -0,0 +1,922 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "e2c01a17",
"metadata": {},
"source": [
"### Search your Google Drive knowledge base with fully local processing."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "df7609cf",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m24.2\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.3\u001b[0m\n",
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n"
]
}
],
"source": [
"%pip install -qU \"langchain==0.3.27\" \"langchain-core<1.0.0,>=0.3.78\" \"langchain-text-splitters<1.0.0,>=0.3.9\" langchain_ollama langchain_chroma langchain_community google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client python-docx"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "144bdf7c",
"metadata": {},
"outputs": [],
"source": [
"import json\n",
"import logging\n",
"import os\n",
"import re\n",
"import sys\n",
"import hashlib\n",
"from pathlib import Path\n",
"from enum import StrEnum\n",
"from typing import Iterable, Optional\n",
"\n",
"import gradio as gr\n",
"from langchain_core.documents import Document\n",
"from langchain_text_splitters import RecursiveCharacterTextSplitter, MarkdownHeaderTextSplitter\n",
"from langchain_ollama import OllamaEmbeddings, ChatOllama\n",
"from langchain.storage import InMemoryStore\n",
"from langchain_chroma import Chroma\n",
"from langchain_community.document_loaders import TextLoader\n",
"from google.oauth2.credentials import Credentials\n",
"from google_auth_oauthlib.flow import InstalledAppFlow\n",
"from google.auth.transport.requests import Request\n",
"from googleapiclient.discovery import build\n",
"from googleapiclient.http import MediaIoBaseDownload\n",
"from googleapiclient.errors import HttpError\n",
"from docx import Document as DocxDocument"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dfdb143d",
"metadata": {},
"outputs": [],
"source": [
"logger = logging.getLogger('drive_sage')\n",
"logger.setLevel(logging.DEBUG)\n",
"\n",
"if not logger.handlers:\n",
" handler = logging.StreamHandler(sys.stdout)\n",
" formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')\n",
" handler.setFormatter(formatter)\n",
" logger.addHandler(handler)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "41df43aa",
"metadata": {},
"outputs": [],
"source": [
"SCOPES = ['https://www.googleapis.com/auth/drive.readonly']\n",
"APP_ROOT = Path.cwd()\n",
"DATA_DIR = APP_ROOT / '.drive_sage'\n",
"DOWNLOAD_DIR = DATA_DIR / 'downloads'\n",
"VECTORSTORE_DIR = DATA_DIR / 'chroma'\n",
"TOKEN_PATH = DATA_DIR / 'token.json'\n",
"MANIFEST_PATH = DATA_DIR / 'manifest.json'\n",
"CLIENT_SECRET_FILE = APP_ROOT / 'client_secret_202216035337-4qson0c08g71u8uuihv6v46arv64nhvg.apps.googleusercontent.com.json'\n",
"\n",
"for path in (DATA_DIR, DOWNLOAD_DIR, VECTORSTORE_DIR):\n",
" path.mkdir(parents=True, exist_ok=True)\n",
"\n",
"FILE_TYPE_OPTIONS = {\n",
" 'txt': {\n",
" 'label': '.txt - Plain text',\n",
" 'extensions': ['.txt'],\n",
" 'mime_types': ['text/plain'],\n",
" },\n",
" 'md': {\n",
" 'label': '.md - Markdown',\n",
" 'extensions': ['.md'],\n",
" 'mime_types': ['text/markdown', 'text/plain'],\n",
" },\n",
" 'docx': {\n",
" 'label': '.docx - Word (OpenXML)',\n",
" 'extensions': ['.docx'],\n",
" 'mime_types': ['application/vnd.openxmlformats-officedocument.wordprocessingml.document'],\n",
" },\n",
" 'doc': {\n",
" 'label': '.doc - Word 97-2003',\n",
" 'extensions': ['.doc'],\n",
" 'mime_types': ['application/msword', 'application/vnd.ms-word.document.macroenabled.12'],\n",
" },\n",
" 'gdoc': {\n",
" 'label': 'Google Docs (exported)',\n",
" 'extensions': ['.docx'],\n",
" 'mime_types': ['application/vnd.google-apps.document'],\n",
" },\n",
"}\n",
"\n",
"FILE_TYPE_LABEL_TO_KEY = {config['label']: key for key, config in FILE_TYPE_OPTIONS.items()}\n",
"DEFAULT_FILE_TYPE_KEYS = ['txt', 'md', 'docx', 'doc', 'gdoc']\n",
"DEFAULT_FILE_TYPE_LABELS = [FILE_TYPE_OPTIONS[key]['label'] for key in DEFAULT_FILE_TYPE_KEYS]\n",
"\n",
"MIME_TYPE_TO_EXTENSION = {}\n",
"for key, config in FILE_TYPE_OPTIONS.items():\n",
" extension = config['extensions'][0]\n",
" for mime in config['mime_types']:\n",
" MIME_TYPE_TO_EXTENSION[mime] = extension\n",
"\n",
"GOOGLE_EXPORT_FORMATS = {\n",
" 'application/vnd.google-apps.document': (\n",
" 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n",
" '.docx'\n",
" ),\n",
"}\n",
"\n",
"SIMILARITY_DISTANCE_MAX = float(os.getenv('DRIVE_SAGE_DISTANCE_MAX', '1.2'))\n",
"MAX_CONTEXT_SNIPPET_CHARS = 1200\n",
"HASH_BLOCK_SIZE = 65536\n",
"EMBED_MODEL = os.getenv('DRIVE_SAGE_EMBED_MODEL', 'nomic-embed-text')\n",
"CHAT_MODEL = os.getenv('DRIVE_SAGE_CHAT_MODEL', 'llama3.1:latest')\n",
"\n",
"CUSTOM_CSS = \"\"\"\n",
"#chat-column {\n",
" height: 80vh;\n",
"}\n",
"#chat-column > div {\n",
" height: 100%;\n",
"}\n",
"#chat-column .gradio-chatbot,\n",
"#chat-column .gradio-chat-interface,\n",
"#chat-column .gradio-chatinterface {\n",
" height: 100%;\n",
"}\n",
"#chat-output {\n",
" height: 100%;\n",
"}\n",
"#chat-output .overflow-y-auto {\n",
" max-height: 100% !important;\n",
"}\n",
"#chat-output .h-full {\n",
" height: 100% !important;\n",
"}\n",
"\"\"\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "225a921a",
"metadata": {},
"outputs": [],
"source": [
"def build_drive_service():\n",
" creds = None\n",
" if TOKEN_PATH.exists():\n",
" try:\n",
" creds = Credentials.from_authorized_user_file(str(TOKEN_PATH), SCOPES)\n",
" except Exception as exc:\n",
" logger.warning('Failed to load cached credentials: %s', exc)\n",
" TOKEN_PATH.unlink(missing_ok=True)\n",
" creds = None\n",
"\n",
" if not creds or not creds.valid:\n",
" if creds and creds.expired and creds.refresh_token:\n",
" try:\n",
" creds.refresh(Request())\n",
" except Exception as exc:\n",
" logger.warning('Refreshing credentials failed: %s', exc)\n",
" creds = None\n",
"\n",
" if not creds or not creds.valid:\n",
" if not CLIENT_SECRET_FILE.exists():\n",
" raise FileNotFoundError(\n",
" 'client_secret.json not found. Download it from Google Cloud Console and place it next to this notebook.'\n",
" )\n",
" flow = InstalledAppFlow.from_client_secrets_file(str(CLIENT_SECRET_FILE), SCOPES)\n",
" creds = flow.run_local_server(port=0)\n",
"\n",
" with TOKEN_PATH.open('w', encoding='utf-8') as token_file:\n",
" token_file.write(creds.to_json())\n",
" \n",
" return build('drive', 'v3', credentials=creds)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e0acb8ec",
"metadata": {},
"outputs": [],
"source": [
"def load_manifest() -> dict:\n",
" if MANIFEST_PATH.exists():\n",
" try:\n",
" with MANIFEST_PATH.open('r', encoding='utf-8') as fp:\n",
" raw = json.load(fp)\n",
" if isinstance(raw, dict):\n",
" normalized: dict[str, dict] = {}\n",
" for file_id, entry in raw.items():\n",
" if isinstance(entry, dict):\n",
" normalized[file_id] = entry\n",
" else:\n",
" normalized[file_id] = {'modified': str(entry)}\n",
" return normalized\n",
" except json.JSONDecodeError:\n",
" logger.warning('Manifest file is corrupted; resetting cache.')\n",
" return {}\n",
"\n",
"def save_manifest(manifest: dict) -> None:\n",
" with MANIFEST_PATH.open('w', encoding='utf-8') as fp:\n",
" json.dump(manifest, fp, indent=2)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "43098d19",
"metadata": {},
"outputs": [],
"source": [
"class Metadata(StrEnum):\n",
" ID = 'id'\n",
" SOURCE = 'source'\n",
" PARENT_ID = 'parent_id'\n",
" FILE_TYPE = 'file_type'\n",
" TITLE = 'title'\n",
" MODIFIED = 'modified'\n",
"\n",
"def metadata_key(key: Metadata) -> str:\n",
" return key.value\n",
"\n",
"embeddings = OllamaEmbeddings(model=EMBED_MODEL)\n",
"\n",
"try:\n",
" vectorstore = Chroma(\n",
" collection_name='drive_sage',\n",
" embedding_function=embeddings,\n",
" )\n",
"except Exception as exc:\n",
" logger.exception('Failed to initialize in-memory Chroma vector store')\n",
" raise RuntimeError('Unable to initialize Chroma vector store without persistence.') from exc\n",
"\n",
"docstore = InMemoryStore()\n",
"model = ChatOllama(model=CHAT_MODEL)\n",
"\n",
"DEFAULT_TEXT_SPLITTER = RecursiveCharacterTextSplitter(\n",
" chunk_size=1000,\n",
" chunk_overlap=150,\n",
" separators=['\\n\\n', '\\n', ' ', '']\n",
")\n",
"MARKDOWN_HEADERS = [('#', 'Header 1'), ('##', 'Header 2'), ('###', 'Header 3')]\n",
"MARKDOWN_SPLITTER = MarkdownHeaderTextSplitter(headers_to_split_on=MARKDOWN_HEADERS, strip_headers=False)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "116be5f6",
"metadata": {},
"outputs": [],
"source": [
"def safe_filename(name: str, max_length: int = 120) -> str:\n",
" sanitized = re.sub(r'[^A-Za-z0-9._-]', '_', name)\n",
" sanitized = sanitized.strip('._') or 'untitled'\n",
" return sanitized[:max_length]\n",
"\n",
"def determine_extension(metadata: dict) -> str:\n",
" mime_type = metadata.get('mimeType', '')\n",
" name = metadata.get('name')\n",
" if name and Path(name).suffix:\n",
" return Path(name).suffix.lower()\n",
" if mime_type in GOOGLE_EXPORT_FORMATS:\n",
" return GOOGLE_EXPORT_FORMATS[mime_type][1]\n",
" return MIME_TYPE_TO_EXTENSION.get(mime_type, '.txt')\n",
"\n",
"def cached_file_path(metadata: dict) -> Path:\n",
" file_id = metadata.get('id', 'unknown')\n",
" extension = determine_extension(metadata)\n",
" safe_name = safe_filename(Path(metadata.get('name', file_id)).stem)\n",
" return DOWNLOAD_DIR / f'{safe_name}_{file_id}{extension}'\n",
"\n",
"def hash_file(path: Path) -> str:\n",
" digest = hashlib.sha1()\n",
" with path.open('rb') as fh:\n",
" while True:\n",
" block = fh.read(HASH_BLOCK_SIZE)\n",
" if not block:\n",
" break\n",
" digest.update(block)\n",
" return digest.hexdigest()\n",
"\n",
"def manifest_version(entry: dict | str | None) -> Optional[str]:\n",
" if entry is None:\n",
" return None\n",
" if isinstance(entry, str):\n",
" return entry\n",
" if isinstance(entry, dict):\n",
" return entry.get('modified')\n",
" return None\n",
"\n",
"def update_manifest_entry(manifest: dict, *, file_id: str, modified: str, path: Path, mime_type: str, name: str) -> None:\n",
" manifest[file_id] = {\n",
" 'modified': modified,\n",
" 'path': str(path),\n",
" 'mimeType': mime_type,\n",
" 'name': name,\n",
" 'file_type': Path(path).suffix.lower(),\n",
" }"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d5fe85b9",
"metadata": {},
"outputs": [],
"source": [
"def list_drive_text_files(service, folder_id: Optional[str], allowed_mime_types: list[str], limit: Optional[int]) -> list[dict]:\n",
" query_parts = [\"trashed = false\"]\n",
" mime_types = allowed_mime_types or list(MIME_TYPE_TO_EXTENSION.keys())\n",
" mime_clause = ' or '.join([f\"mimeType = '{mime}'\" for mime in mime_types])\n",
" query_parts.append(f'({mime_clause})')\n",
" if folder_id:\n",
" query_parts.append(f\"'{folder_id}' in parents\")\n",
" query = ' and '.join(query_parts)\n",
"\n",
" files: list[dict] = []\n",
" page_token: Optional[str] = None\n",
"\n",
" while True:\n",
" page_size = min(100, limit - len(files)) if limit else 100\n",
" if page_size <= 0:\n",
" break\n",
" try:\n",
" response = service.files().list(\n",
" q=query,\n",
" spaces='drive',\n",
" fields='nextPageToken, files(id, name, mimeType, modifiedTime)',\n",
" orderBy='modifiedTime desc',\n",
" pageToken=page_token,\n",
" pageSize=page_size,\n",
" ).execute()\n",
" except HttpError as exc:\n",
" raise RuntimeError(f'Google Drive API error: {exc}') from exc\n",
"\n",
" batch = response.get('files', [])\n",
" files.extend(batch)\n",
" if limit and len(files) >= limit:\n",
" return files[:limit]\n",
" page_token = response.get('nextPageToken')\n",
" if not page_token:\n",
" break\n",
" return files\n",
"\n",
"def download_drive_file(service, metadata: dict, manifest: dict) -> Path:\n",
" file_id = metadata['id']\n",
" mime_type = metadata.get('mimeType', '')\n",
" cache_path = cached_file_path(metadata)\n",
" export_mime = None\n",
" if mime_type in GOOGLE_EXPORT_FORMATS:\n",
" export_mime, extension = GOOGLE_EXPORT_FORMATS[mime_type]\n",
" if cache_path.suffix.lower() != extension:\n",
" cache_path = cache_path.with_suffix(extension)\n",
"\n",
"\n",
" request = (\n",
" service.files().export_media(fileId=file_id, mimeType=export_mime)\n",
" if export_mime\n",
" else service.files().get_media(fileId=file_id)\n",
" )\n",
"\n",
" logger.debug('Downloading %s (%s) -> %s', metadata.get('name', file_id), file_id, cache_path)\n",
" with cache_path.open('wb') as fh:\n",
" downloader = MediaIoBaseDownload(fh, request)\n",
" done = False\n",
" while not done:\n",
" status, done = downloader.next_chunk()\n",
" if status:\n",
" logger.debug('Download progress %.0f%%', status.progress() * 100)\n",
"\n",
" update_manifest_entry(\n",
" manifest,\n",
" file_id=file_id,\n",
" modified=metadata.get('modifiedTime', ''),\n",
" path=cache_path,\n",
" mime_type=mime_type,\n",
" name=metadata.get('name', cache_path.name),\n",
" )\n",
" return cache_path"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "27f50b9b",
"metadata": {},
"outputs": [],
"source": [
"def extract_docx_text(path: Path) -> str:\n",
" doc = DocxDocument(str(path))\n",
" lines = [paragraph.text.strip() for paragraph in doc.paragraphs if paragraph.text.strip()]\n",
" return '\\n'.join(lines)\n",
"\n",
"def load_documents(\n",
" path: Path,\n",
" *,\n",
" source_id: Optional[str] = None,\n",
" file_type: Optional[str] = None,\n",
" modified: Optional[str] = None,\n",
" display_name: Optional[str] = None,\n",
" ) -> list[Document]:\n",
" suffix = (file_type or path.suffix or '.txt').lower()\n",
" try:\n",
" if suffix in {'.txt', '.md'}:\n",
" loader = TextLoader(str(path), encoding='utf-8')\n",
" documents = loader.load()\n",
" elif suffix == '.docx':\n",
" documents = [Document(page_content=extract_docx_text(path), metadata={'source': str(path)})]\n",
" else:\n",
" raise ValueError(f'Unsupported file type: {suffix}')\n",
" except UnicodeDecodeError as exc:\n",
" raise ValueError(f'Failed to read {path}: {exc}') from exc\n",
"\n",
" base_metadata = {\n",
" metadata_key(Metadata.SOURCE): str(path),\n",
" metadata_key(Metadata.FILE_TYPE): suffix,\n",
" metadata_key(Metadata.TITLE): display_name or path.name,\n",
" }\n",
" if source_id:\n",
" base_metadata[metadata_key(Metadata.ID)] = source_id\n",
" if modified:\n",
" base_metadata[metadata_key(Metadata.MODIFIED)] = modified\n",
"\n",
" cleaned: list[Document] = []\n",
" for doc in documents:\n",
" content = doc.page_content.strip()\n",
" if not content:\n",
" continue\n",
" merged_metadata = {**doc.metadata, **base_metadata}\n",
" doc.page_content = content\n",
" doc.metadata = merged_metadata\n",
" cleaned.append(doc)\n",
" return cleaned\n",
"\n",
"def preprocess(documents: Iterable[Document]) -> list[Document]:\n",
" return [doc for doc in documents if doc.page_content]\n",
"\n",
"def chunk_documents(doc: Document) -> list[Document]:\n",
" parent_id = doc.metadata.get(metadata_key(Metadata.ID))\n",
" if not parent_id:\n",
" raise ValueError('Document is missing a stable identifier for chunking.')\n",
"\n",
" if doc.metadata.get(metadata_key(Metadata.FILE_TYPE)) == '.md':\n",
" markdown_docs = MARKDOWN_SPLITTER.split_text(doc.page_content)\n",
" seed_docs = [\n",
" Document(page_content=section.page_content, metadata={**doc.metadata, **section.metadata})\n",
" for section in markdown_docs\n",
" ]\n",
" else:\n",
" seed_docs = [doc]\n",
"\n",
" chunks = DEFAULT_TEXT_SPLITTER.split_documents(seed_docs)\n",
" for idx, chunk in enumerate(chunks):\n",
" chunk.metadata[metadata_key(Metadata.PARENT_ID)] = parent_id\n",
" chunk.metadata[metadata_key(Metadata.ID)] = f'{parent_id}::chunk-{idx:04d}'\n",
" chunk.metadata.setdefault(metadata_key(Metadata.SOURCE), doc.metadata.get(metadata_key(Metadata.SOURCE)))\n",
" chunk.metadata.setdefault(metadata_key(Metadata.TITLE), doc.metadata.get(metadata_key(Metadata.TITLE)))\n",
" return chunks"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0f135f35",
"metadata": {},
"outputs": [],
"source": [
"def sync_drive_and_index(folder_id=None, selected_types=None, file_limit=None, _state: bool = False, progress=gr.Progress(track_tqdm=False)):\n",
" folder = (folder_id or '').strip() or None\n",
"\n",
" selections = selected_types if selected_types is not None else DEFAULT_FILE_TYPE_LABELS\n",
" if not isinstance(selections, (list, tuple)):\n",
" selections = [selections]\n",
" selections = list(selections)\n",
"\n",
" if len(selections) == 0:\n",
" yield 'Select at least one file type before syncing.', False\n",
" return\n",
"\n",
" chosen_keys: list[str] = []\n",
" for item in selections:\n",
" key = FILE_TYPE_LABEL_TO_KEY.get(item, item)\n",
" if key in FILE_TYPE_OPTIONS:\n",
" chosen_keys.append(key)\n",
"\n",
" if not chosen_keys:\n",
" yield 'Select at least one file type before syncing.', False\n",
" return\n",
"\n",
" allowed_mime_types = sorted({mime for key in chosen_keys for mime in FILE_TYPE_OPTIONS[key]['mime_types']})\n",
"\n",
" limit: Optional[int] = None\n",
" limit_warning: Optional[str] = None\n",
" if file_limit not in (None, '', 0):\n",
" try:\n",
" parsed_limit = int(file_limit)\n",
" if parsed_limit > 0:\n",
" limit = parsed_limit\n",
" else:\n",
" raise ValueError\n",
" except (TypeError, ValueError):\n",
" limit_warning = 'File limit must be a positive integer. Syncing all matching files instead.'\n",
"\n",
" log_lines: list[str] = []\n",
"\n",
" def push(message: str) -> str:\n",
" log_lines.append(message)\n",
" return '\\n'.join(log_lines)\n",
"\n",
" if limit_warning:\n",
" logger.warning(limit_warning)\n",
" yield push(limit_warning), False\n",
"\n",
" progress(0, 'Authorizing Google Drive access...')\n",
" yield push('Authorizing Google Drive access...'), False\n",
"\n",
" try:\n",
" service = build_drive_service()\n",
" except FileNotFoundError as exc:\n",
" error_msg = f'Error: {exc}'\n",
" logger.error(error_msg)\n",
" yield push(error_msg), False\n",
" return\n",
" except Exception as exc:\n",
" logger.exception('Drive authorization failed')\n",
" error_msg = f'Error authenticating with Google Drive: {exc}'\n",
" yield push(error_msg), False\n",
" return\n",
"\n",
" list_message = 'Listing documents' + (f' (limit {limit})' if limit else '') + '...'\n",
" progress(0, list_message)\n",
" yield push(list_message), False\n",
"\n",
" try:\n",
" files = list_drive_text_files(service, folder, allowed_mime_types, limit)\n",
" except Exception as exc:\n",
" logger.exception('Listing Drive files failed')\n",
" error_msg = f'Error listing Google Drive files: {exc}'\n",
" yield push(error_msg), False\n",
" return\n",
"\n",
" total = len(files)\n",
" if total == 0:\n",
" info = 'No documents matching the selected types were found in Google Drive.'\n",
" yield push(info), True\n",
" return\n",
"\n",
" manifest = load_manifest()\n",
" downloaded_count = 0\n",
"\n",
" for index, metadata in enumerate(files, start=1):\n",
" file_id = metadata['id']\n",
" name = metadata.get('name', file_id)\n",
" remote_version = metadata.get('modifiedTime', '')\n",
" manifest_entry = manifest.get(file_id)\n",
" cache_path = cached_file_path(metadata)\n",
" if isinstance(manifest_entry, dict) and manifest_entry.get('path'):\n",
" cache_path = Path(manifest_entry['path'])\n",
" cached_version = manifest_version(manifest_entry)\n",
"\n",
" if cached_version == remote_version and cache_path.exists():\n",
" message = f\"{index}/{total} Skipping cached file: {name} -> {cache_path}\"\n",
" progress(index / total, message)\n",
" yield push(message), False\n",
" continue\n",
"\n",
" download_message = f\"{index}/{total} Downloading {name} -> {cache_path}\"\n",
" progress(max((index - 0.5) / total, 0), download_message)\n",
" yield push(download_message), False\n",
"\n",
" try:\n",
" downloaded_path = download_drive_file(service, metadata, manifest)\n",
" index_message = f\"{index}/{total} Indexing {downloaded_path.name}\"\n",
" progress(index / total, index_message)\n",
" yield push(index_message), False\n",
" index_document(\n",
" downloaded_path,\n",
" source_id=file_id,\n",
" file_type=downloaded_path.suffix,\n",
" modified=remote_version,\n",
" display_name=name,\n",
" manifest=manifest,\n",
" )\n",
" downloaded_count += 1\n",
" except Exception as exc:\n",
" error_message = f\"{index}/{total} Failed to sync {name}: {exc}\"\n",
" logger.exception(error_message)\n",
" progress(index / total, error_message)\n",
" yield push(error_message), False\n",
"\n",
" if downloaded_count > 0:\n",
" save_manifest(manifest)\n",
" summary = f'Indexed {downloaded_count} new document(s) from Google Drive.'\n",
" else:\n",
" summary = 'Google Drive is already in sync.'\n",
"\n",
" progress(1, summary)\n",
" yield push(summary), True"
]
},
{
"cell_type": "markdown",
"id": "0e2f176b",
"metadata": {},
"source": [
"## RAG Pipeline"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "20ad0e80",
"metadata": {},
"outputs": [],
"source": [
"def persist_vectorstore(_store) -> None:\n",
" \"\"\"In-memory mode: Chroma client does not persist between sessions.\"\"\"\n",
" return\n",
"\n",
"\n",
"def index_document(\n",
" file_path: Path | str,\n",
" *,\n",
" source_id: Optional[str] = None,\n",
" file_type: Optional[str] = None,\n",
" modified: Optional[str] = None,\n",
" display_name: Optional[str] = None,\n",
" manifest: Optional[dict] = None,\n",
" ) -> tuple[str, int]:\n",
" path = Path(file_path)\n",
" path = path.expanduser().resolve()\n",
" resolved_id = source_id or f'local::{hash_file(path)}'\n",
" documents = load_documents(\n",
" path,\n",
" source_id=resolved_id,\n",
" file_type=file_type,\n",
" modified=modified,\n",
" display_name=display_name,\n",
" )\n",
" documents = preprocess(documents)\n",
" if not documents:\n",
" logger.warning('No readable content found in %s; skipping.', path)\n",
" return resolved_id, 0\n",
"\n",
" total_chunks = 0\n",
" for doc in documents:\n",
" doc_id = doc.metadata.get(metadata_key(Metadata.ID), resolved_id)\n",
" doc.metadata[metadata_key(Metadata.ID)] = doc_id\n",
" vectorstore.delete(where={metadata_key(Metadata.PARENT_ID): doc_id})\n",
" chunks = chunk_documents(doc)\n",
" if not chunks:\n",
" continue\n",
" vectorstore.add_documents(chunks)\n",
" docstore.mset([(doc_id, doc)])\n",
" total_chunks += len(chunks)\n",
"\n",
" persist_vectorstore(vectorstore)\n",
" if manifest is not None and not source_id:\n",
" update_manifest_entry(\n",
" manifest,\n",
" file_id=resolved_id,\n",
" modified=hash_file(path),\n",
" path=path,\n",
" mime_type=file_type or Path(path).suffix or '.txt',\n",
" name=display_name or path.name,\n",
" )\n",
" return resolved_id, total_chunks"
]
},
{
"cell_type": "markdown",
"id": "a90db6ee",
"metadata": {},
"source": [
"### LLM Interaction"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e2e15e99",
"metadata": {},
"outputs": [],
"source": [
"def retrieve_context(query: str, *, top_k: int = 8, distance_threshold: Optional[float] = SIMILARITY_DISTANCE_MAX):\n",
" results_with_scores = vectorstore.similarity_search_with_score(query, k=top_k)\n",
" logger.info(f'Matching records: {len(results_with_scores)}')\n",
"\n",
" filtered: list[tuple[Document, float]] = []\n",
" for doc, score in results_with_scores:\n",
" if score is None:\n",
" continue\n",
" score_value = float(score)\n",
" print(f'DEBUG: Retrieved doc source={doc.metadata.get(metadata_key(Metadata.SOURCE))} distance={score_value}')\n",
" if distance_threshold is not None and score_value > distance_threshold:\n",
" logger.debug(\n",
" 'Skipping %s with distance %.4f (above threshold %.4f)',\n",
" doc.metadata.get(metadata_key(Metadata.SOURCE)),\n",
" score_value,\n",
" distance_threshold,\n",
" )\n",
" continue\n",
" filtered.append((doc, score_value))\n",
"\n",
" if not filtered:\n",
" return []\n",
"\n",
" for doc, score_value in filtered:\n",
" parent_id = doc.metadata.get(metadata_key(Metadata.PARENT_ID))\n",
" if parent_id:\n",
" parent_doc = docstore.mget([parent_id])[0]\n",
" if parent_doc and parent_doc.page_content:\n",
" logger.debug(\n",
" 'Parent preview (%s | %.3f): %s',\n",
" doc.metadata.get(metadata_key(Metadata.SOURCE), 'unknown'),\n",
" score_value,\n",
" parent_doc.page_content[:400].replace('\\n', ' '),\n",
" )\n",
"\n",
" return filtered\n",
"\n",
"\n",
"def build_prompt_sections(relevant_docs: list[tuple[Document, float]]) -> str:\n",
" sections: list[str] = []\n",
" for idx, (doc, score) in enumerate(relevant_docs, start=1):\n",
" source = doc.metadata.get(metadata_key(Metadata.SOURCE), 'unknown')\n",
" snippet = doc.page_content.strip()[:MAX_CONTEXT_SNIPPET_CHARS]\n",
" section = (\n",
" f'[{idx}] Source: {source}\\n'\n",
" f'Distance: {score:.3f}\\n'\n",
" f'Content:\\n{snippet}'\n",
" )\n",
" sections.append(section)\n",
" return '\\n\\n'.join(sections)\n",
"\n",
"\n",
"def ask(message, history):\n",
" relevant_docs = retrieve_context(message)\n",
" if not relevant_docs:\n",
" yield \"I don't have enough information in the synced documents to answer that yet. Please sync additional files or adjust the filters.\"\n",
" return\n",
"\n",
" context = build_prompt_sections(relevant_docs)\n",
" prompt = f'''\n",
" You are a retrieval-augmented assistant. Use ONLY the facts provided in the context to answer the user.\n",
" If the context does not contain the answer, reply exactly: \"I don't have enough information in the synced documents to answer that yet. Please sync additional files.\"\n",
" \n",
" Context:\\n{context}\n",
" '''\n",
"\n",
" messages = [\n",
" ('system', prompt),\n",
" ('user', message)\n",
" ]\n",
"\n",
" stream = model.stream(messages)\n",
" response_text = ''\n",
"\n",
" for chunk in stream:\n",
" response_text += chunk.content or ''\n",
" if not response_text:\n",
" continue\n",
"\n",
" yield response_text"
]
},
{
"cell_type": "markdown",
"id": "c3e632dc-9e87-4510-9fcd-aa699c27e82b",
"metadata": {
"jp-MarkdownHeadingCollapsed": true
},
"source": [
"## Gradio UI"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a3d68a74",
"metadata": {},
"outputs": [],
"source": [
"def chat(message, history, sync_ready):\n",
" if message is None:\n",
" return ''\n",
"\n",
" text_input = message.get('text', '')\n",
" files_uploaded = message.get('files', [])\n",
" latest_file_path = Path(files_uploaded[-1]) if files_uploaded else None\n",
" if latest_file_path:\n",
" manifest = load_manifest()\n",
" doc_id, chunk_count = index_document(\n",
" latest_file_path,\n",
" file_type=latest_file_path.suffix,\n",
" display_name=latest_file_path.name,\n",
" manifest=manifest,\n",
" )\n",
" save_manifest(manifest)\n",
" logger.info('Indexed upload %s as %s with %s chunk(s)', latest_file_path, doc_id, chunk_count)\n",
" if not text_input:\n",
" yield f'Indexed document from upload ({chunk_count} chunk(s)).'\n",
" return\n",
"\n",
" if not text_input:\n",
" return ''\n",
"\n",
" if not sync_ready and not files_uploaded:\n",
" yield 'Sync Google Drive before chatting or upload a document first.'\n",
" return\n",
"\n",
" for chunk in ask(text_input, history):\n",
" yield chunk\n",
"\n",
"title = \"Drive Sage\"\n",
"with gr.Blocks(title=title, fill_height=True, css=CUSTOM_CSS) as ui:\n",
" gr.Markdown(f'# {title}')\n",
" gr.Markdown('## Search your Google Drive knowledge base with fully local processing.')\n",
" sync_state = gr.State(False)\n",
"\n",
" with gr.Row():\n",
" with gr.Column(scale=3, elem_id='chat-column'):\n",
" gr.ChatInterface(\n",
" fn=chat,\n",
" chatbot=gr.Chatbot(height='80vh', elem_id='chat-output'),\n",
" type='messages',\n",
" textbox=gr.MultimodalTextbox(\n",
" file_types=['text', '.txt', '.md'],\n",
" autofocus=True,\n",
" elem_id='chat-input',\n",
" ),\n",
" additional_inputs=[sync_state],\n",
" )\n",
" with gr.Column(scale=2, min_width=320):\n",
" gr.Markdown('### Google Drive Sync')\n",
" drive_folder = gr.Textbox(\n",
" label='Folder ID (optional)',\n",
" placeholder='Leave blank to scan My Drive root',\n",
" )\n",
" file_types = gr.CheckboxGroup(\n",
" label='File types to sync',\n",
" choices=[config['label'] for config in FILE_TYPE_OPTIONS.values()],\n",
" value=DEFAULT_FILE_TYPE_LABELS,\n",
" )\n",
" file_limit = gr.Number(\n",
" label='Max files to sync (leave blank for all)',\n",
" value=20,\n",
" )\n",
" sync_btn = gr.Button('Sync Google Drive')\n",
" sync_status = gr.Markdown('No sync performed yet.')\n",
"\n",
" sync_btn.click(\n",
" sync_drive_and_index,\n",
" inputs=[drive_folder, file_types, file_limit, sync_state],\n",
" outputs=[sync_status, sync_state],\n",
" )\n",
"\n",
"ui.launch(debug=True)"
]
}
],
"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": 5
}

View File

@@ -0,0 +1,341 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "xeOG96gXPeqz"
},
"source": [
"# Security Sentinel\n",
"\n",
"### Audit and harden your code in one sweep\n",
"\n",
"Choose a contract to inspect a snippet for security issues:\n",
"\n",
"- Scan for vulnerabilities\n",
"- Draft threat intelligence notes\n",
"- Suggest secure fixes\n",
"- Craft exploit-focused tests"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "B7ftYo53Pw94",
"outputId": "9daa3972-d5a1-4cd2-9952-cd89a54c6ddd"
},
"outputs": [],
"source": [
"import os\n",
"import logging\n",
"from enum import StrEnum\n",
"from getpass import getpass\n",
"\n",
"import gradio as gr\n",
"from openai import OpenAI\n",
"from dotenv import load_dotenv\n",
"\n",
"load_dotenv(override=True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "AXmPDuydPuUp"
},
"outputs": [],
"source": [
"logging.basicConfig(level=logging.WARNING)\n",
"\n",
"logger = logging.getLogger('sentinel')\n",
"logger.setLevel(logging.DEBUG)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def get_secret_in_google_colab(env_name: str) -> str:\n",
" try:\n",
" from google.colab import userdata\n",
" return userdata.get(env_name)\n",
" except Exception:\n",
" return ''\n",
"\n",
"\n",
"def get_secret(env_name: str) -> str:\n",
" key = os.environ.get(env_name) or get_secret_in_google_colab(env_name)\n",
"\n",
" if not key:\n",
" key = getpass(f'Enter {env_name}:').strip()\n",
"\n",
" if key:\n",
" logger.info(f'✅ {env_name} provided')\n",
" else:\n",
" logger.warning(f'❌ {env_name} not provided')\n",
" return key.strip()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "d7Qmfac9Ph0w",
"outputId": "be9db7f3-f08a-47f5-d6fa-d7c8bce4f97a"
},
"outputs": [],
"source": [
"class Provider(StrEnum):\n",
" OLLAMA = 'Ollama'\n",
"\n",
"clients: dict[Provider, OpenAI] = {}\n",
"\n",
"clients[Provider.OLLAMA] = OpenAI(base_url='http://localhost:11434/v1')\n",
"\n",
"model = 'llama3.2:latest'\n",
"client = clients.get(Provider.OLLAMA)\n",
"if not client:\n",
" raise Exception('No client found')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "fTHvG2w0sgwU"
},
"outputs": [],
"source": [
"class Task(StrEnum):\n",
" SCAN = 'Scan'\n",
" REPORT = 'Threat Report'\n",
" PATCH = 'Patch'\n",
" TEST = 'Exploit Test'\n",
"\n",
"\n",
"def perform_tasks(tasks, code):\n",
" logger.info(f'Performing tasks: {tasks}')\n",
"\n",
" steps = []\n",
" if Task.SCAN in tasks:\n",
" steps.append('Scan the snippet for security weaknesses and name them clearly.')\n",
" if Task.REPORT in tasks:\n",
" steps.append('Produce a concise threat report that explains impact, likelihood, and affected components.')\n",
" if Task.PATCH in tasks:\n",
" steps.append('Propose hardened code that mitigates the identified risks without changing intent.')\n",
" if Task.TEST in tasks:\n",
" steps.append('Design exploit-style tests or probing steps that would validate the vulnerability.')\n",
"\n",
" task_list = '- ' + '\\n- '.join(steps) if steps else 'No security directive selected.'\n",
" system_prompt = f\"\"\"\n",
" You are a seasoned application security engineer who recognises languages instantly and\n",
" maps weaknesses to common vulnerability classes.\n",
" Only rewrite code when instructed to patch it.\n",
"\n",
" Your tasks:\n",
" {task_list}\n",
" \"\"\"\n",
" messages = [\n",
" {\"role\": \"system\", \"content\": system_prompt},\n",
" {\"role\": \"user\", \"content\": f'Code: \\n{code}'}\n",
" ]\n",
" response = client.chat.completions.create(\n",
" model=model,\n",
" messages=messages\n",
" )\n",
"\n",
" content = response.choices[0].message.content\n",
"\n",
" return content"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "nlzUyXFus0km"
},
"outputs": [],
"source": [
"def get_examples() -> tuple[list[any], list[str]]:\n",
" python_sql = r'''\n",
" import sqlite3\n",
"\n",
" def get_user(conn, user_id):\n",
" query = f\"SELECT * FROM users WHERE id = {user_id}\"\n",
" cursor = conn.cursor()\n",
" cursor.execute(query)\n",
" return cursor.fetchone()\n",
" '''\n",
"\n",
" js_auth = r'''\n",
" app.post('/login', async (req, res) => {\n",
" const token = jwt.decode(req.body.token);\n",
" if (!token) {\n",
" return res.status(401).send('blocked');\n",
" }\n",
" const user = await db.find(token.user);\n",
" res.send(user);\n",
" });\n",
" '''\n",
"\n",
" go_crypto = r'''\n",
" package main\n",
"\n",
" import (\n",
" \"crypto/sha1\"\n",
" )\n",
"\n",
" func hashPassword(password string) string {\n",
" h := sha1.New()\n",
" h.Write([]byte(password))\n",
" return string(h.Sum(nil))\n",
" }\n",
" '''\n",
"\n",
" php_upload = r'''\n",
" <?php\n",
" if ($_FILES[\"file\"][\"error\"] == 0) {\n",
" move_uploaded_file($_FILES[\"file\"][\"tmp_name\"], \"/uploads/\" . $_FILES[\"file\"][\"name\"]);\n",
" echo \"done\";\n",
" }\n",
" ?>\n",
" '''\n",
"\n",
" rust_config = r'''\n",
" use std::env;\n",
"\n",
" fn main() {\n",
" let endpoint = env::var(\"SERVICE_URL\").unwrap();\n",
" println!(\"Connecting to {}\", endpoint);\n",
" }\n",
" '''\n",
"\n",
" examples = [\n",
" [[Task.SCAN], python_sql, 'python'],\n",
" [[Task.REPORT], js_auth, 'javascript'],\n",
" [[Task.PATCH], go_crypto, 'go'],\n",
" [[Task.TEST], php_upload, 'php'],\n",
" [[Task.SCAN, Task.PATCH, Task.REPORT], rust_config, 'rust']\n",
" ]\n",
"\n",
" example_labels = [\n",
" 'Python: SQL injection review',\n",
" 'JavaScript: Token handling report',\n",
" 'Go: Strengthen hashing',\n",
" 'PHP: Exploit upload path',\n",
" 'Rust: Exposure analysis'\n",
" ]\n",
"\n",
" return examples, example_labels"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "wYReYuvgtDgg"
},
"source": [
"## UI"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 664
},
"id": "I8Q08SJe8CxK",
"outputId": "f1d41d06-dfda-4daf-b7ff-6f73bdaf8369"
},
"outputs": [],
"source": [
"title = 'Security Sentinel'\n",
"\n",
"with gr.Blocks(title=title, theme=gr.themes.Monochrome()) as ui:\n",
" gr.Markdown(f'# {title}')\n",
" gr.Markdown('## Run rapid security sweeps on any snippet.')\n",
"\n",
" with gr.Row():\n",
" with gr.Column():\n",
" tasks = gr.Dropdown(\n",
" label=\"Missions\",\n",
" choices=[task.value for task in Task],\n",
" value=Task.SCAN,\n",
" multiselect=True,\n",
" interactive=True,\n",
" )\n",
" code_input = gr.Code(\n",
" label='Code Input',\n",
" lines=40,\n",
" )\n",
" code_language = gr.Textbox(visible=False)\n",
"\n",
" with gr.Column():\n",
" gr.Markdown('## Findings')\n",
" code_output = gr.Markdown('Awaiting report')\n",
"\n",
"\n",
" run_btn = gr.Button('Run Audit')\n",
"\n",
" def set_language(tasks, code, language):\n",
" syntax_highlights = ['python', 'c', 'cpp', 'javascript', 'typescript', 'go', 'rust', 'php']\n",
" logger.debug(f'Tasks: {tasks}, Language: {language}')\n",
" highlight = language if language in syntax_highlights else None\n",
"\n",
" return tasks, gr.Code(value=code, language=highlight)\n",
"\n",
" examples, example_labels = get_examples()\n",
" examples = gr.Examples(\n",
" examples=examples,\n",
" example_labels=example_labels,\n",
" examples_per_page=20,\n",
" inputs=[tasks, code_input, code_language],\n",
" outputs=[tasks, code_input],\n",
" run_on_click=True,\n",
" fn=set_language\n",
" )\n",
"\n",
" run_btn.click(perform_tasks, inputs=[tasks, code_input], outputs=[code_output])\n",
"\n",
"ui.launch(debug=True)"
]
}
],
"metadata": {
"colab": {
"provenance": []
},
"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": 0
}

View File

@@ -0,0 +1,495 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "GHsssBgWM_l0"
},
"source": [
"# QLoRA Fine-Tuning; LLaMA 3.1 8B"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "MDyR63OTNUJ6"
},
"outputs": [],
"source": [
"import sys\n",
"print(f\"Python: {sys.version}\")\n",
"\n",
"import torch\n",
"print(f\"PyTorch: {torch.__version__}\")\n",
"print(f\"CUDA Available: {torch.cuda.is_available()}\")\n",
"print(f\"CUDA Version: {torch.version.cuda}\")\n",
"print(f\"GPU: {torch.cuda.get_device_name(0)}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "3_8F5m3xxMtz"
},
"outputs": [],
"source": [
"!pip install -q --upgrade transformers==4.48.3 accelerate==1.3.0 datasets==3.2.0\n",
"!pip install -q --upgrade peft==0.14.0 trl==0.14.0 bitsandbytes==0.46.0\n",
"!pip install -q --upgrade matplotlib scipy scikit-learn\n",
"!pip install -q --upgrade \"huggingface_hub<1.0,>=0.24.0\"\n",
"!pip install -q --upgrade bitsandbytes"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "CpVdMBUVxMtz"
},
"source": [
"## Environment Setup"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "-yikV8pRBer9"
},
"outputs": [],
"source": [
"import os\n",
"import re\n",
"import math\n",
"import torch\n",
"import torch.nn.functional as F\n",
"import matplotlib.pyplot as plt\n",
"from tqdm import tqdm\n",
"from huggingface_hub import login\n",
"from transformers import (\n",
" AutoModelForCausalLM,\n",
" AutoTokenizer,\n",
" BitsAndBytesConfig,\n",
" set_seed\n",
")\n",
"from datasets import load_dataset\n",
"from peft import PeftModel\n",
"\n",
"%matplotlib inline"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "uuTX-xonNeOK"
},
"outputs": [],
"source": [
"BASE_MODEL = \"meta-llama/Meta-Llama-3.1-8B\"\n",
"DATASET_NAME = \"ed-donner/pricer-data\"\n",
"FINETUNED_MODEL = \"ed-donner/pricer-2024-09-13_13.04.39\"\n",
"REVISION = \"e8d637df551603dc86cd7a1598a8f44af4d7ae36\"\n",
"\n",
"TOP_K = 3\n",
"TEST_SIZE = 250\n",
"\n",
"GREEN = \"\\033[92m\"\n",
"YELLOW = \"\\033[93m\"\n",
"RED = \"\\033[91m\"\n",
"RESET = \"\\033[0m\"\n",
"COLOR_MAP = {\"red\": RED, \"orange\": YELLOW, \"green\": GREEN}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "WyFPZeMcM88v"
},
"outputs": [],
"source": [
"from google.colab import userdata\n",
"hf_token = userdata.get('HF_TOKEN')\n",
"\n",
"login(hf_token, add_to_git_credential=True)\n",
"print(\"Successfully authenticated with HuggingFace\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "qDqXth7MxMt0"
},
"source": [
"## Load Data"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "cvXVoJH8LS6u"
},
"outputs": [],
"source": [
"print(f\"Loading dataset: {DATASET_NAME}\")\n",
"dataset = load_dataset(DATASET_NAME)\n",
"train = dataset['train']\n",
"test = dataset['test']\n",
"\n",
"print(f\"\\nDataset loaded successfully:\")\n",
"print(f\" Training examples: {len(train):,}\")\n",
"print(f\" Test examples: {len(test):,}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "xb86e__Wc7j_"
},
"outputs": [],
"source": [
"print(\"Sample test example:\\n\")\n",
"sample = test[0]\n",
"print(f\"Text: {sample['text'][:200]}...\")\n",
"print(f\"\\nGround truth price: ${sample['price']:.2f}\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "qJWQ0a3wZ0Bw"
},
"source": [
"## Quantization & Model Loading\n",
"\n",
"(4-bit quantization reduces LLaMA 3.1 8B from ~32GB to ~5-6GB VRAM)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "lAUAAcEC6ido"
},
"outputs": [],
"source": [
"quant_config = BitsAndBytesConfig(\n",
" load_in_4bit=True,\n",
" bnb_4bit_use_double_quant=True,\n",
" bnb_4bit_compute_dtype=torch.bfloat16,\n",
" bnb_4bit_quant_type=\"nf4\"\n",
")\n",
"\n",
"print(\"Using 4-bit NF4 quantization\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "R_O04fKxMMT-"
},
"outputs": [],
"source": [
"print(f\"Loading base model: {BASE_MODEL}\")\n",
"\n",
"tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL, trust_remote_code=True)\n",
"tokenizer.pad_token = tokenizer.eos_token\n",
"tokenizer.padding_side = \"right\"\n",
"\n",
"base_model = AutoModelForCausalLM.from_pretrained(\n",
" BASE_MODEL,\n",
" quantization_config=quant_config,\n",
" device_map=\"auto\",\n",
")\n",
"base_model.generation_config.pad_token_id = tokenizer.pad_token_id\n",
"\n",
"print(f\"Base model loaded - Memory: {base_model.get_memory_footprint() / 1e9:.2f} GB\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "m7PHUKDVxMt1"
},
"source": [
"## Load PEFT Adapters"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "6_Q1fqluxMt1"
},
"outputs": [],
"source": [
"print(f\"Loading fine-tuned adapters: {FINETUNED_MODEL}\")\n",
"print(f\"Revision: {REVISION}\")\n",
"\n",
"fine_tuned_model = PeftModel.from_pretrained(base_model, FINETUNED_MODEL, revision=REVISION)\n",
"\n",
"print(f\"Fine-tuned model ready - Total memory: {fine_tuned_model.get_memory_footprint() / 1e9:.2f} GB\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "Qst1LhBVAB04"
},
"outputs": [],
"source": [
"def extract_price(text):\n",
" if \"Price is $\" in text:\n",
" content = text.split(\"Price is $\")[1]\n",
" content = content.replace(',', '').replace('$', '')\n",
" match = re.search(r\"[-+]?\\d*\\.?\\d+\", content)\n",
" return float(match.group()) if match else 0.0\n",
" return 0.0"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "jXFBW_5UeEcp"
},
"outputs": [],
"source": [
"test_cases = [\n",
" \"Price is $24.99\",\n",
" \"Price is $1,234.50\",\n",
" \"Price is $a fabulous 899.99 or so\"\n",
"]\n",
"\n",
"for test in test_cases:\n",
" result = extract_price(test)\n",
" print(f\"{test} -> ${result:.2f}\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "TTy_WAGexMt2"
},
"source": [
"## Prediction Function\n",
"\n",
"Top-K weighted averaging computes probability-weighted average of top K tokens."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "Je5dR8QEAI1d"
},
"outputs": [],
"source": [
"def advanced_predict(prompt, top_k=TOP_K):\n",
" set_seed(42)\n",
" inputs = tokenizer.encode(prompt, return_tensors=\"pt\").to(\"cuda\")\n",
" attention_mask = torch.ones(inputs.shape, device=\"cuda\")\n",
"\n",
" with torch.no_grad():\n",
" outputs = fine_tuned_model(inputs, attention_mask=attention_mask)\n",
" next_token_logits = outputs.logits[:, -1, :].to('cpu')\n",
"\n",
" next_token_probs = F.softmax(next_token_logits, dim=-1)\n",
" top_probs, top_token_ids = next_token_probs.topk(top_k)\n",
"\n",
" prices, weights = [], []\n",
"\n",
" for i in range(top_k):\n",
" predicted_token = tokenizer.decode(top_token_ids[0][i])\n",
" probability = top_probs[0][i]\n",
"\n",
" try:\n",
" price = float(predicted_token)\n",
" if price > 0:\n",
" prices.append(price)\n",
" weights.append(probability)\n",
" except ValueError:\n",
" continue\n",
"\n",
" if not prices:\n",
" return 0.0\n",
"\n",
" total_weight = sum(weights)\n",
" weighted_avg = sum(p * w / total_weight for p, w in zip(prices, weights))\n",
"\n",
" return weighted_avg.item()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "7nI3Ec7exMt2"
},
"source": [
"## Evaluation Framework\n",
"\n",
"Metrics:\n",
"- Dollar Error: |prediction - truth|\n",
"- RMSLE: Root Mean Squared Log Error (penalizes relative errors)\n",
"- Hit Rate: Percentage in green zone (error < $40 OR < 20% of true price)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "30lzJXBH7BcK"
},
"outputs": [],
"source": [
"class Tester:\n",
"\n",
" def __init__(self, predictor, data, title=None, size=TEST_SIZE):\n",
" self.predictor = predictor\n",
" self.data = data\n",
" self.title = title or predictor.__name__.replace(\"_\", \" \").title()\n",
" self.size = min(size, len(data))\n",
" self.guesses = []\n",
" self.truths = []\n",
" self.errors = []\n",
" self.sles = []\n",
" self.colors = []\n",
"\n",
" def color_for(self, error, truth):\n",
" if error < 40 or error / truth < 0.2:\n",
" return \"green\"\n",
" elif error < 80 or error / truth < 0.4:\n",
" return \"orange\"\n",
" else:\n",
" return \"red\"\n",
"\n",
" def run_datapoint(self, i):\n",
" datapoint = self.data[i]\n",
" guess = self.predictor(datapoint[\"text\"])\n",
" truth = datapoint[\"price\"]\n",
" error = abs(guess - truth)\n",
"\n",
" log_error = math.log(truth + 1) - math.log(guess + 1)\n",
" sle = log_error ** 2\n",
"\n",
" color = self.color_for(error, truth)\n",
" title = datapoint[\"text\"].split(\"\\n\\n\")[1][:30] + \"...\"\n",
"\n",
" self.guesses.append(guess)\n",
" self.truths.append(truth)\n",
" self.errors.append(error)\n",
" self.sles.append(sle)\n",
" self.colors.append(color)\n",
"\n",
" print(f\"{COLOR_MAP[color]}{i+1}: Guess: ${guess:,.2f} | Truth: ${truth:,.2f} | Error: ${error:,.2f} | SLE: {sle:,.3f} | {title}{RESET}\")\n",
"\n",
" def chart(self, title):\n",
" plt.figure(figsize=(14, 10))\n",
" max_val = max(max(self.truths), max(self.guesses))\n",
"\n",
" plt.plot([0, max_val], [0, max_val], color='deepskyblue', lw=3, alpha=0.7, label='Perfect prediction')\n",
" plt.scatter(self.truths, self.guesses, s=20, c=self.colors, alpha=0.6)\n",
"\n",
" plt.xlabel('Ground Truth Price ($)', fontsize=12)\n",
" plt.ylabel('Model Prediction ($)', fontsize=12)\n",
" plt.xlim(0, max_val)\n",
" plt.ylim(0, max_val)\n",
" plt.title(title, fontsize=14, fontweight='bold')\n",
" plt.grid(alpha=0.3)\n",
" plt.legend()\n",
" plt.tight_layout()\n",
" plt.show()\n",
"\n",
" def report(self):\n",
" average_error = sum(self.errors) / self.size\n",
" rmsle = math.sqrt(sum(self.sles) / self.size)\n",
" hits = sum(1 for color in self.colors if color == \"green\")\n",
" hit_rate = hits / self.size * 100\n",
"\n",
" title = f\"{self.title} | Avg Error: ${average_error:,.2f} | RMSLE: {rmsle:.3f} | Hit Rate: {hit_rate:.1f}%\"\n",
"\n",
" print(f\"\\n{'='*80}\")\n",
" print(f\"EVALUATION SUMMARY\")\n",
" print(f\"{'='*80}\")\n",
" print(f\"Model: {self.title}\")\n",
" print(f\"Test Size: {self.size}\")\n",
" print(f\"Average Dollar Error: ${average_error:,.2f}\")\n",
" print(f\"RMSLE: {rmsle:.4f}\")\n",
" print(f\"Hit Rate (Green): {hit_rate:.2f}% ({hits}/{self.size})\")\n",
" print(f\"{'='*80}\\n\")\n",
"\n",
" self.chart(title)\n",
"\n",
" def run(self):\n",
" print(f\"Running evaluation on {self.size} examples...\\n\")\n",
" for i in tqdm(range(self.size), desc=\"Evaluating\"):\n",
" self.run_datapoint(i)\n",
" self.report()\n",
"\n",
" @classmethod\n",
" def test(cls, function, data, **kwargs):\n",
" cls(function, data, **kwargs).run()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "krjnRyHO4Fp6"
},
"outputs": [],
"source": [
"print(f\"Loading dataset: {DATASET_NAME}\")\n",
"dataset = load_dataset(DATASET_NAME)\n",
"train = dataset['train']\n",
"test = dataset['test']\n",
"\n",
"print(f\"\\nDataset loaded successfully:\")\n",
"print(f\" Training examples: {len(train):,}\")\n",
"print(f\" Test examples: {len(test):,}\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "5JjNwXMDxMt2"
},
"source": [
"## Run Evaluation"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "W_KcLvyt6kbb"
},
"outputs": [],
"source": [
"Tester.test(advanced_predict, test, title=\"LLaMA 3.1 8B QLoRA (400K)\")"
]
}
],
"metadata": {
"accelerator": "GPU",
"colab": {
"gpuType": "T4",
"provenance": []
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 0
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,156 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "7316c8d3",
"metadata": {},
"source": [
"# How well can LLMs advice on tech purchases?"
]
},
{
"cell_type": "markdown",
"id": "6acdeb48",
"metadata": {},
"source": [
"Just execute the two tries after setting everything up and take a look how both models perform in helping you choose the best new iPhone.\n",
"\n",
"I was suprised how much better ChatGPT is!\n",
"\n",
"P.S.: Since I'm German, the output and the websites are German. Just change the URLs if you want another language."
]
},
{
"cell_type": "markdown",
"id": "8ae52614",
"metadata": {},
"source": [
"### General setup"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d7f15f25",
"metadata": {},
"outputs": [],
"source": [
"import sys\n",
"import os\n",
"sys.path.append(os.path.join('..', '..', 'week1'))\n",
"from scraper import fetch_website_contents\n",
"from operator import concat\n",
"from openai import OpenAI\n",
"from IPython.display import Markdown, display\n",
"from dotenv import load_dotenv"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bbd7dc61",
"metadata": {},
"outputs": [],
"source": [
"system_prompt_homework_day2 = \"\"\"\n",
"You are tech advisor that knows about how newest mobiles like the iPhone work and what different user groups need, \n",
"like best cameras for Pro users or battery life for average consumers. \n",
"You advise fact-based and thorough on the best deals for each of your customers.\n",
"You deal with scraped websites given to you by ignoring random text from headers and footers of websites \n",
"and focusing on the content that actually presents products. \n",
"IMPORTANT: Only use facts that you got by the user via prompting.\n",
"Try hard to really identify which text is noise and which makes the product descriptions.\n",
"\"\"\"\n",
"user_prompt_prefix_homework_day2 = \"\"\"\n",
"Hi, as a student that still has the iPhone 13 Pro but wants a newer iPhone, \n",
"i need some advice on which iPhone to buy. Can you help me choose from apples website? \n",
"Here are the contents of apple's iphone websites that i obtained using a scraper.\n",
"Provide a short summary of the text from this website-scraper and afterwards help me choose the best option. \n",
"If it includes news or announcements, then summarize these too. \n",
"\"\"\"\n",
"\n",
"url_1 = \"https://www.apple.com/de/iphone-17-pro/\"\n",
"url_2 = \"https://www.apple.com/de/iphone-air/\"\n",
"url_3 = \"https://www.apple.com/de/iphone-17/\"\n",
"\n",
"urls = [url_1, url_2, url_3]\n",
"website_content_homework_day2 = \"\"\n",
"\n",
"for url in urls:\n",
" website_content_homework_day2 += fetch_website_contents(url)\n",
"\n",
"homework_day2_message = [\n",
" {\"role\":\"system\", \"content\":system_prompt_homework_day2},\n",
" {\"role\":\"user\", \"content\": user_prompt_prefix_homework_day2 + website_content_homework_day2}\n",
"]"
]
},
{
"cell_type": "markdown",
"id": "cc854a00",
"metadata": {},
"source": [
"### Try 1: Local run with Ollama 3.2 "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7fae9b5b",
"metadata": {},
"outputs": [],
"source": [
"!ollama pull llama3.2\n",
"\n",
"OLLAMA_BASE_URL = \"http://localhost:11434/v1\"\n",
"ollama = OpenAI(base_url=OLLAMA_BASE_URL, api_key='ollama')\n",
"\n",
"response = ollama.chat.completions.create(model=\"llama3.2\", messages=homework_day2_message,)\n",
"display(Markdown(response.choices[0].message.content))"
]
},
{
"cell_type": "markdown",
"id": "e7563157",
"metadata": {},
"source": [
"### Try 2: Online with ChatGPT-5-nano"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d764cc76",
"metadata": {},
"outputs": [],
"source": [
"load_dotenv(override=True)\n",
"\n",
"openai = OpenAI()\n",
"response = openai.chat.completions.create(model=\"gpt-5-nano\", messages=homework_day2_message,)\n",
"display(Markdown(response.choices[0].message.content))"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"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.12.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,172 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 6,
"id": "236461b6",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import json\n",
"from dotenv import load_dotenv\n",
"import gradio as gr\n",
"import json\n",
"from openai import OpenAI\n",
"import re"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "4c493ebf",
"metadata": {},
"outputs": [],
"source": [
"load_dotenv(override=True)\n",
"api_key = os.getenv('OPENAI_API_KEY')\n",
" \n",
"client = OpenAI()"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "349fa758",
"metadata": {},
"outputs": [],
"source": [
"system_prompt = \"\"\"\n",
" You are an expert technical writer and knowledge engineer.\n",
" Your task is to generate well-structured Markdown (.md) documentation files that can be used as a knowledge base for a RAG.\n",
"\n",
" Follow these rules carefully:\n",
" 1. Write the content in clear, concise Markdown format.\n",
" 2. Use appropriate Markdown headers (#, ##, ###) to structure the document.\n",
" 3. Include lists, tables, or code blocks only when necessary.\n",
" 4. Keep each document self-contained and focused on a single topic.\n",
" 5. Do not include any text outside the Markdown content (no explanations, no code fences).\n",
" 6. The style should be factual, structured, and helpful for machine retrieval.\n",
" 7. Use consistent tone and terminology across sections.\n",
" \"\"\""
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "e65071d6",
"metadata": {},
"outputs": [],
"source": [
"def create_kb_prompt(topic, kb_type=\"tutorial\"):\n",
" return f\"\"\"\n",
" Generate a comprehensive Markdown document for the following technical topic.\n",
" Topic: {topic}\n",
" Document Type: {kb_type}\n",
" The document should include structured sections, concise explanations, and clear formatting suitable for a technical knowledge base.\n",
" \"\"\""
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "1045db44",
"metadata": {},
"outputs": [],
"source": [
"def generate_markdown_doc(topic, kb_type=\"tutorial\"):\n",
" \n",
" user_prompt = create_kb_prompt(topic, kb_type)\n",
" messages = [\n",
" {\"role\": \"system\", \"content\": system_prompt},\n",
" {\"role\": \"user\", \"content\": user_prompt},\n",
" ]\n",
" \n",
" response = client.chat.completions.create(\n",
" model=\"gpt-4o-mini\",\n",
" messages=messages,\n",
" temperature=0.7\n",
" )\n",
" markdown_output = response.choices[0].message.content.strip()\n",
" markdown_output = re.sub(r'^```[a-z]*\\\\s*', '', markdown_output, flags=re.MULTILINE)\n",
" markdown_output = re.sub(r'\\\\s*```$', '', markdown_output, flags=re.MULTILINE)\n",
" return markdown_output"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "24ba021b",
"metadata": {},
"outputs": [],
"source": [
"def create_kb_gradio_interface():\n",
" with gr.Blocks(theme=gr.themes.Soft()) as app:\n",
" gr.Markdown(\"## Technical Knowledge Base Generator\")\n",
"\n",
" with gr.Row():\n",
" with gr.Column():\n",
" topic_input = gr.Textbox(\n",
" label=\"Technical Topic\",\n",
" placeholder=\"e.g., Building a RAG pipeline with LangChain...\",\n",
" lines=2\n",
" )\n",
" kb_type_input = gr.Radio(\n",
" label=\"Document Type\",\n",
" choices=[\"Overview\", \"FAQ\", \"Use Case\"],\n",
" value=\"FAQ\"\n",
" )\n",
" generate_button = gr.Button(\"Generate Markdown Document\", variant=\"primary\")\n",
"\n",
" with gr.Column():\n",
" output_md = gr.Textbox(\n",
" label=\"Generated Markdown Content\",\n",
" lines=25,\n",
" interactive=False,\n",
" placeholder=\"Generated Markdown will appear here...\"\n",
" )\n",
"\n",
" generate_button.click(\n",
" fn=generate_markdown_doc,\n",
" inputs=[topic_input, kb_type_input],\n",
" outputs=[output_md],\n",
" api_name=\"generate_kb_doc\"\n",
" )\n",
"\n",
" return app"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "db17cde4",
"metadata": {},
"outputs": [],
"source": [
"app = create_kb_gradio_interface()\n",
"app.launch(debug=True, share=True)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "llm-engineering",
"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.12.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,397 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "36640116",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import io\n",
"import zipfile\n",
"import textwrap\n",
"from typing import Dict, Generator, List, Optional, Tuple\n",
"import gradio as gr\n",
"from dotenv import load_dotenv\n",
"from openai import OpenAI"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bf7c9195",
"metadata": {},
"outputs": [],
"source": [
"load_dotenv()\n",
"client = OpenAI(api_key=os.getenv(\"OPENAI_API_KEY\"))\n",
"ollama_via_openai = OpenAI(base_url='http://localhost:11434/v1', api_key = 'ollama')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2f108866",
"metadata": {},
"outputs": [],
"source": [
"MODEL_CHOICES = [\n",
" \"openai/gpt-4o-mini\",\n",
" \"openai/gpt-4o\",\n",
" \"openai/gpt-3.5-turbo\",\n",
" \"ollama/llama3.2\",\n",
" \"ollama/phi3:mini\",\n",
" \"ollama/qwen2.5:3b\",\n",
"]\n",
"DEFAULT_MODEL = \"openai/gpt-4o-mini\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "09e3566c",
"metadata": {},
"outputs": [],
"source": [
"def normalize_model(model: str) -> str:\n",
" return model.split(\"/\", 1)[1]\n",
"\n",
"def get_client(model_choice: str) -> OpenAI:\n",
" if model_choice.startswith(\"ollama/\"):\n",
" return ollama_via_openai\n",
" return client\n",
"\n",
"def stream_response(model_choice: str, messages: List[Dict]) -> Generator[str, None, None]:\n",
" model_name = normalize_model(model_choice)\n",
" api_client = get_client(model_choice)\n",
"\n",
" try:\n",
" stream = api_client.chat.completions.create(\n",
" model=model_name,\n",
" messages=messages,\n",
" stream=True,\n",
" max_tokens=1500,\n",
" temperature=0.3,\n",
" )\n",
" text = \"\"\n",
" for chunk in stream:\n",
" delta = chunk.choices[0].delta.content or \"\"\n",
" text += delta\n",
" yield text\n",
" except Exception as e:\n",
" yield f\"Error while streaming from {model_choice}: {str(e)}\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e52f2d16",
"metadata": {},
"outputs": [],
"source": [
"SYSTEM_PROMPT = \"\"\"You are a senior Python engineer. Be conservative and helpful.\n",
"When asked to annotate code, add clean Google-style docstrings and minimal comments.\n",
"When asked to generate tests, write readable pytest test modules.\n",
"When asked to create a trading scaffold, make a working 3-file setup with backtesting.\n",
"Avoid clever tricks, prioritize clarity and correctness.\"\"\"\n",
"\n",
"DOC_PROMPT = \"\"\"Task: Add docstrings and helpful inline comments to this code.\n",
"Do not change any logic or flow.\n",
"Use {style}-style docstrings. Add type hints: {add_types}.\n",
"Return only the updated code.\"\"\"\n",
"\n",
"TEST_PROMPT = \"\"\"Task: Generate a pytest test file for this code.\n",
"Include tests for normal, edge, and error conditions.\n",
"Use plain pytest (no unittest). Add minimal mocks if needed.\"\"\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b7c12db5",
"metadata": {},
"outputs": [],
"source": [
"def make_trading_files(strategy_brief: str, symbol: str) -> Dict[str, str]:\n",
" \"\"\"Return dictionary of files for a simple strategy, broker, and backtest.\"\"\"\n",
" strategy_py = f'''\"\"\"\n",
"strategy.py\n",
"Auto-generated strategy module for {symbol}.\n",
"Brief: {strategy_brief}\n",
"\"\"\"\n",
"def decide(state, bar):\n",
" \"\"\"Example SMA crossover.\"\"\"\n",
" prices = state.setdefault(\"prices\", [])\n",
" prices.append(bar[\"close\"])\n",
" if len(prices) < 20:\n",
" return \"HOLD\", state\n",
" short = sum(prices[-5:]) / 5\n",
" long = sum(prices[-20:]) / 20\n",
" action = \"BUY\" if short > long and not state.get(\"pos\") else \"SELL\" if short < long and state.get(\"pos\") else \"HOLD\"\n",
" state[\"pos\"] = action == \"BUY\" or (state.get(\"pos\") and action != \"SELL\")\n",
" return action, state\n",
"'''\n",
"\n",
" broker_py = \"\"\"\\\"\\\"\\\"sim_broker.py\n",
"Simple in-memory broker simulator.\\\"\\\"\\\"\n",
"def init(cash=10000.0):\n",
" return {\"cash\": cash, \"pos\": 0, \"equity\": [], \"trades\": []}\n",
"\n",
"def execute(state, action, price, size=1):\n",
" if action == \"BUY\" and state[\"cash\"] >= price * size:\n",
" state[\"cash\"] -= price * size\n",
" state[\"pos\"] += size\n",
" state[\"trades\"].append((\"BUY\", price))\n",
" elif action == \"SELL\" and state[\"pos\"] >= size:\n",
" state[\"cash\"] += price * size\n",
" state[\"pos\"] -= size\n",
" state[\"trades\"].append((\"SELL\", price))\n",
" state[\"equity\"].append(state[\"cash\"] + state[\"pos\"] * price)\n",
" return state\n",
"\"\"\"\n",
"\n",
" backtest_py = f'''\"\"\"\n",
"backtest.py\n",
"Run a synthetic backtest for {symbol}.\n",
"\"\"\"\n",
"import random, strategy, sim_broker\n",
"\n",
"def synthetic_data(n=250, start=100.0):\n",
" price = start\n",
" data = []\n",
" for _ in range(n):\n",
" price *= 1 + random.uniform(-0.01, 0.01)\n",
" data.append({{\"close\": price}})\n",
" return data\n",
"\n",
"def run():\n",
" bars = synthetic_data()\n",
" state = {{\"prices\": []}}\n",
" broker = sim_broker.init()\n",
" for bar in bars:\n",
" action, state = strategy.decide(state, bar)\n",
" broker = sim_broker.execute(broker, action, bar[\"close\"])\n",
" eq = broker[\"equity\"][-1] if broker[\"equity\"] else broker[\"cash\"]\n",
" print(f\"Final equity: {{eq:.2f}} | Trades: {{len(broker['trades'])}}\")\n",
"\n",
"if __name__ == \"__main__\":\n",
" run()\n",
"'''\n",
"\n",
" readme = f\"\"\"# Trading Scaffold for {symbol}\n",
"Generated from your brief: {strategy_brief}\n",
"Files:\n",
"- strategy.py — core logic\n",
"- sim_broker.py — in-memory execution\n",
"- backtest.py — synthetic backtest\n",
"Run with:\n",
"```bash\n",
"python backtest.py\n",
"```\"\"\"\n",
" return {\n",
" \"strategy.py\": strategy_py,\n",
" \"sim_broker.py\": broker_py,\n",
" \"backtest.py\": backtest_py,\n",
" \"README.md\": readme,\n",
" }"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3bb60fc8",
"metadata": {},
"outputs": [],
"source": [
"def zip_trading_files(files: Dict[str, str]) -> Tuple[str, bytes]:\n",
" buf = io.BytesIO()\n",
" with zipfile.ZipFile(buf, \"w\", zipfile.ZIP_DEFLATED) as z:\n",
" for name, content in files.items():\n",
" z.writestr(name, content)\n",
" buf.seek(0)\n",
" return \"trading_scaffold.zip\", buf.read()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "32d5e1eb",
"metadata": {},
"outputs": [],
"source": [
"def docstrings_stream(model_choice: str, code: str, style: str, add_types: bool):\n",
" if not code.strip():\n",
" yield \"Please paste Python code first.\"\n",
" return\n",
" sys = {\"role\": \"system\", \"content\": SYSTEM_PROMPT}\n",
" usr = {\n",
" \"role\": \"user\",\n",
" \"content\": DOC_PROMPT.format(style=style, add_types=add_types) + \"\\n\\n\" + code,\n",
" }\n",
" yield from stream_response(model_choice, [sys, usr])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8b23500b",
"metadata": {},
"outputs": [],
"source": [
"def tests_stream(model_choice: str, code: str):\n",
" if not code.strip():\n",
" yield \"Please paste Python code first.\"\n",
" return\n",
" sys = {\"role\": \"system\", \"content\": SYSTEM_PROMPT}\n",
" usr = {\"role\": \"user\", \"content\": TEST_PROMPT + \"\\n\\n\" + code}\n",
" yield from stream_response(model_choice, [sys, usr])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7d2bb480",
"metadata": {},
"outputs": [],
"source": [
"def trading_scaffold(strategy_brief: str, symbol: str):\n",
" if not symbol.strip():\n",
" symbol = \"AAPL\"\n",
" files = make_trading_files(strategy_brief or \"Simple SMA crossover\", symbol)\n",
" name, data = zip_trading_files(files)\n",
" zip_path = \"trading_scaffold.zip\"\n",
" with open(zip_path, \"wb\") as f:\n",
" f.write(data)\n",
" return (\n",
" files[\"strategy.py\"],\n",
" files[\"sim_broker.py\"],\n",
" files[\"backtest.py\"],\n",
" files[\"README.md\"],\n",
" zip_path,\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "623db7de",
"metadata": {},
"outputs": [],
"source": [
"with gr.Blocks(title=\"DevLab Assistant\") as demo:\n",
" gr.Markdown(\"# DevLab Assistant\")\n",
" gr.Markdown(\n",
" \"This mini-lab helps with everyday coding tasks:\\n\"\n",
" \"* Add docstrings and helpful comments to existing code\\n\"\n",
" \"* Generate pytest unit tests automatically\\n\"\n",
" \"* Scaffold a small trading simulator to experiment with strategy ideas\\n\\n\"\n",
" \"Select a model (OpenAI or a local Ollama one) and try each tab.\"\n",
" )\n",
"\n",
" with gr.Tab(\"Docstrings / Comments\"):\n",
" model1 = gr.Dropdown(MODEL_CHOICES, value=DEFAULT_MODEL, label=\"Model\")\n",
" style = gr.Radio([\"google\", \"numpy\"], value=\"google\", label=\"Docstring style\")\n",
" add_types = gr.Checkbox(value=False, label=\"Add basic type hints\")\n",
" code_input = gr.Textbox(\n",
" lines=14,\n",
" label=\"Paste your Python code here\",\n",
" placeholder=\"def multiply(a, b):\\n return a * b\",\n",
" )\n",
" output_md = gr.Markdown(label=\"Result\")\n",
" gen_btn = gr.Button(\"Generate Docstrings\")\n",
"\n",
" gen_btn.click(\n",
" fn=docstrings_stream,\n",
" inputs=[model1, code_input, style, add_types],\n",
" outputs=output_md,\n",
" )\n",
"\n",
" with gr.Tab(\"Unit Tests\"):\n",
" model2 = gr.Dropdown(MODEL_CHOICES, value=DEFAULT_MODEL, label=\"Model\")\n",
" code_input2 = gr.Textbox(\n",
" lines=14,\n",
" label=\"Paste the code you want tests for\",\n",
" placeholder=\"class Calculator:\\n def add(self, a, b):\\n return a + b\",\n",
" )\n",
" output_md2 = gr.Markdown(label=\"Generated test file\")\n",
" gen_btn2 = gr.Button(\"Generate Tests\")\n",
"\n",
" gen_btn2.click(\n",
" fn=tests_stream,\n",
" inputs=[model2, code_input2],\n",
" outputs=output_md2,\n",
" )\n",
"\n",
" with gr.Tab(\"Trading Scaffold\"):\n",
" gr.Markdown(\n",
" \"Generate a minimal, self-contained trading simulator that includes:\\n\"\n",
" \"* `strategy.py`: basic SMA crossover strategy\\n\"\n",
" \"* `sim_broker.py`: in-memory broker\\n\"\n",
" \"* `backtest.py`: synthetic data backtest\\n\"\n",
" \"You can run it locally with `python backtest.py`.\"\n",
" )\n",
"\n",
" brief = gr.Textbox(\n",
" lines=6,\n",
" label=\"Strategy Brief\",\n",
" placeholder=\"e.g., SMA crossover with fast=5, slow=20, long-only\",\n",
" )\n",
" symbol = gr.Textbox(value=\"AAPL\", label=\"Symbol\")\n",
" gen_btn3 = gr.Button(\"Generate Scaffold\")\n",
"\n",
" s_md = gr.Code(language=\"python\", label=\"strategy.py\")\n",
" b_md = gr.Code(language=\"python\", label=\"sim_broker.py\")\n",
" bt_md = gr.Code(language=\"python\", label=\"backtest.py\")\n",
" r_md = gr.Markdown(label=\"README.md\")\n",
" zip_out = gr.File(label=\"Download ZIP\")\n",
"\n",
" gen_btn3.click(\n",
" fn=trading_scaffold,\n",
" inputs=[brief, symbol],\n",
" outputs=[s_md, b_md, bt_md, r_md, zip_out],\n",
" )\n",
"\n",
" gr.Markdown(\"---\")\n",
" gr.Markdown(\n",
" \"Tips:\\n\"\n",
" \"* Ollama models must be pulled locally (for example `ollama pull llama3.2`).\\n\"\n",
" \"* OpenAI models require the `OPENAI_API_KEY` environment variable.\\n\"\n",
" \"* Everything runs safely and offline for the local models.\"\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a1c6ba48",
"metadata": {},
"outputs": [],
"source": [
"if __name__ == \"__main__\":\n",
" demo.launch()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "llm-engineering",
"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.12.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,178 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 2,
"id": "7015d967",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"from dotenv import load_dotenv\n",
"from langchain.chat_models import ChatOpenAI\n",
"from langchain.chains import ConversationalRetrievalChain\n",
"from langchain.memory import ConversationBufferMemory\n",
"from langchain.vectorstores import Chroma\n",
"from langchain.embeddings import OpenAIEmbeddings\n",
"from langchain.document_loaders import DirectoryLoader, TextLoader\n",
"from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
"from langchain_experimental.text_splitter import SemanticChunker\n",
"from langchain.schema import Document\n",
"import gradio as gr\n",
"import glob"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "87646db6",
"metadata": {},
"outputs": [],
"source": [
"load_dotenv(override=True)\n",
"api_key = os.getenv('OPENAI_API_KEY')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1019e3b8",
"metadata": {},
"outputs": [],
"source": [
"file_paths = glob.glob(\"knowledge_base/**/*.md\", recursive=True)\n",
"\n",
"documents = []\n",
"for path in file_paths:\n",
" with open(path, \"r\", encoding=\"utf-8\") as f:\n",
" text = f.read()\n",
" doc_type = os.path.basename(os.path.dirname(path)) \n",
"\n",
" documents.append(\n",
" Document(\n",
" page_content=text,\n",
" metadata={\n",
" \"doc_type\": doc_type,\n",
" },\n",
" )\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "54527a21",
"metadata": {},
"outputs": [],
"source": [
"embeddings = OpenAIEmbeddings(model=\"text-embedding-3-small\", openai_api_key=api_key)\n",
"\n",
"# text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)\n",
"text_splitter = SemanticChunker(embeddings)\n",
"chunks = text_splitter.split_documents(documents)\n",
"\n",
"print(f\"Total number of chunks: {len(chunks)}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "15579dda",
"metadata": {},
"outputs": [],
"source": [
"vectorstore = Chroma.from_documents(\n",
" documents=chunks,\n",
" embedding=embeddings,\n",
" persist_directory=\"chroma_db\"\n",
")\n",
"vectorstore.persist()\n",
"print(\"Chroma vector store built.\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ca3b4d55",
"metadata": {},
"outputs": [],
"source": [
"llm = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0, openai_api_key=api_key)\n",
"memory = ConversationBufferMemory(memory_key='chat_history', return_messages=True)\n",
"retriever = vectorstore.as_retriever()\n",
"conversation_chain = ConversationalRetrievalChain.from_llm(\n",
" llm=llm,\n",
" retriever=retriever,\n",
" memory=memory,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "94b3a75a",
"metadata": {},
"outputs": [],
"source": [
"query = \"Tell me about Langchain.\"\n",
"result = conversation_chain({\"question\": query})\n",
"\n",
"print(\"Answer:\")\n",
"print(result[\"answer\"])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e814f910",
"metadata": {},
"outputs": [],
"source": [
"def rag_chat(query, history):\n",
" response = conversation_chain({\"question\": query})\n",
" answer = response[\"answer\"]\n",
" return answer\n",
"\n",
"with gr.Blocks(theme=gr.themes.Soft()) as rag_ui:\n",
" gr.Markdown(\"# RAG Chat Assistant\")\n",
" gr.Markdown(\"Ask questions about your Markdown knowledge base.\")\n",
" chat_box = gr.ChatInterface(\n",
" fn=rag_chat,\n",
" title=\"RAG Knowledge Base Assistant\",\n",
" description=\"Chat with your Markdown-based knowledge base using RAG.\"\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "eef8d2ee",
"metadata": {},
"outputs": [],
"source": [
"rag_ui.launch(debug=True, share=True)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "llm-engineering",
"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.12.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,328 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "b44aa468",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import pickle\n",
"import json\n",
"import time\n",
"import random\n",
"from openai import OpenAI\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"from datetime import datetime\n",
"from items import Item \n",
"from testing import Tester "
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "a8348f3d",
"metadata": {},
"outputs": [],
"source": [
"client = OpenAI(api_key=os.getenv(\"OPENAI_API_KEY\"))"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "f4cf994b",
"metadata": {},
"outputs": [],
"source": [
"BASE_MODEL = \"gpt-4o-mini-2024-07-18\"\n",
"EPOCHS = 5\n",
"BATCH_SIZE = 8\n",
"LR_MULT = 0.3"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d2f0ac12",
"metadata": {},
"outputs": [],
"source": [
"with open(\"train.pkl\", \"rb\") as f:\n",
" train_subset = pickle.load(f)\n",
"\n",
"with open(\"test.pkl\", \"rb\") as f:\n",
" val_subset = pickle.load(f) \n",
"\n",
"print(f\"Loaded {len(train_subset)} training and {len(val_subset)} validation items.\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3aad35ac",
"metadata": {},
"outputs": [],
"source": [
"random.shuffle(train_subset)\n",
"random.shuffle(val_subset)\n",
"\n",
"TRAIN_LIMIT = 5000 \n",
"VAL_LIMIT = 1000 \n",
"\n",
"train_subset = train_subset[:TRAIN_LIMIT]\n",
"val_subset = val_subset[:VAL_LIMIT]\n",
"\n",
"print(f\"Using {len(train_subset)} training and {len(val_subset)} validation samples.\")"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "2c85d07e",
"metadata": {},
"outputs": [],
"source": [
"def build_prompt(item):\n",
" return f\"\"\"\n",
"### CONTEXT\n",
"You are a price estimation assistant for e-commerce listings.\n",
"Each product is described by its title, category, key features, and details.\n",
"\n",
"### TASK\n",
"Estimate the most likely retail price in USD.\n",
"Think step-by-step about product type, quality, and included components \n",
"before stating the final answer as \"Predicted Price: $<amount>\".\n",
"\n",
"### EXAMPLES\n",
"- Wireless earbuds with active noise cancellation -> Predicted Price: $89\n",
"- Stainless steel kitchen knife set (6-piece) -> Predicted Price: $45\n",
"- Laptop stand aluminum adjustable -> Predicted Price: $32\n",
"\n",
"### PRODUCT TITLE\n",
"{item.title}\n",
"\n",
"### CATEGORY\n",
"{item.category}\n",
"\n",
"### DETAILS\n",
"{item.details}\n",
"\n",
"### YOUR REASONING\n",
"(Think about product quality, features, and typical market range.)\n",
"\n",
"### FINAL ANSWER\n",
"Predicted Price: $\n",
"\"\"\"\n",
"\n",
"def build_completion(item):\n",
" return f\"Predicted Price: ${round(item.price)}.00\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "83eca819",
"metadata": {},
"outputs": [],
"source": [
"def write_jsonl(data, filename):\n",
" with open(filename, \"w\", encoding=\"utf-8\") as f:\n",
" for item in data:\n",
" if getattr(item, \"include\", True):\n",
" prompt = build_prompt(item)\n",
" completion = build_completion(item)\n",
" json_obj = {\n",
" \"messages\": [\n",
" {\"role\": \"user\", \"content\": prompt},\n",
" {\"role\": \"assistant\", \"content\": completion}\n",
" ]\n",
" }\n",
" f.write(json.dumps(json_obj) + \"\\n\")\n",
" print(f\"Wrote {len(data)} samples to {filename}\")\n",
"\n",
"TRAIN_JSONL = \"train_prepared.jsonl\"\n",
"VAL_JSONL = \"val_prepared.jsonl\"\n",
"\n",
"write_jsonl(train_subset, TRAIN_JSONL)\n",
"write_jsonl(val_subset, VAL_JSONL)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "d25bfc85",
"metadata": {},
"outputs": [],
"source": [
"train_file = client.files.create(file=open(TRAIN_JSONL, \"rb\"), purpose=\"fine-tune\")\n",
"val_file = client.files.create(file=open(VAL_JSONL, \"rb\"), purpose=\"fine-tune\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ef4e092a",
"metadata": {},
"outputs": [],
"source": [
"job = client.fine_tuning.jobs.create(\n",
" training_file=train_file.id,\n",
" validation_file=val_file.id,\n",
" model=BASE_MODEL,\n",
" hyperparameters={\n",
" \"n_epochs\": EPOCHS,\n",
" \"batch_size\": BATCH_SIZE,\n",
" \"learning_rate_multiplier\": LR_MULT\n",
" }\n",
")\n",
"\n",
"print(\"Job started:\", job.id)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c8d58b5e",
"metadata": {},
"outputs": [],
"source": [
"def stream_finetune_events(job_id, poll_interval=30):\n",
" print(f\"Tracking fine-tuning job: {job_id}\\n\")\n",
" seen = set()\n",
" loss_data = []\n",
" \n",
" while True:\n",
" job = client.fine_tuning.jobs.retrieve(job_id)\n",
" events = client.fine_tuning.jobs.list_events(job_id)\n",
" \n",
" for e in events.data[::-1]:\n",
" if e.id not in seen:\n",
" seen.add(e.id)\n",
" ts = datetime.fromtimestamp(e.created_at)\n",
" msg = e.message\n",
" print(f\"[{ts:%Y-%m-%d %H:%M:%S}] {msg}\")\n",
" \n",
" if \"training_loss\" in msg:\n",
" try:\n",
" step = int(msg.split(\"Step \")[1].split(\"/\")[0])\n",
" train_loss = float(msg.split(\"training_loss: \")[1].split(\",\")[0])\n",
" val_loss = None\n",
" if \"val_loss\" in msg:\n",
" val_loss = float(msg.split(\"val_loss: \")[1].split(\",\")[0])\n",
" loss_data.append((step, train_loss, val_loss))\n",
" except Exception:\n",
" pass\n",
" \n",
" if job.status == \"succeeded\":\n",
" print(\"\\nFine-tuning complete!\")\n",
" print(\"Fine-tuned model ID:\", job.fine_tuned_model)\n",
" \n",
" if loss_data:\n",
" steps = [d[0] for d in loss_data]\n",
" train_losses = [d[1] for d in loss_data]\n",
" val_losses = [d[2] for d in loss_data if d[2] is not None]\n",
"\n",
" plt.figure(figsize=(8, 5))\n",
" plt.plot(steps, train_losses, marker=\"o\", color=\"teal\", label=\"Training Loss\")\n",
" if val_losses:\n",
" plt.plot(steps[:len(val_losses)], val_losses, marker=\"o\", color=\"orange\", label=\"Validation Loss\")\n",
" plt.xlabel(\"Step\")\n",
" plt.ylabel(\"Loss\")\n",
" plt.title(f\"Fine-Tuning Progress — {job_id}\")\n",
" plt.legend()\n",
" plt.grid(alpha=0.3)\n",
" plt.show()\n",
" else:\n",
" print(\"No loss data found. Fine-tuning may have completed too quickly to log metrics.\")\n",
"\n",
" return job.fine_tuned_model\n",
"\n",
" elif job.status in [\"failed\", \"cancelled\"]:\n",
" print(f\"\\nFine-tuning {job.status}.\")\n",
" if job.error:\n",
" print(\"Error:\", job.error)\n",
" return None\n",
"\n",
" time.sleep(poll_interval)\n",
"\n",
"MODEL_ID = stream_finetune_events(job.id)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f3ed9c0e",
"metadata": {},
"outputs": [],
"source": [
"def test_model(model_id, test_items, max_samples=100):\n",
" y_true, y_pred = [], []\n",
" for i, item in enumerate(test_items[:max_samples]):\n",
" prompt = build_prompt(item)\n",
" response = client.chat.completions.create(\n",
" model=model_id,\n",
" messages=[{\"role\": \"user\", \"content\": prompt}],\n",
" temperature=0\n",
" )\n",
" output = response.choices[0].message.content\n",
" try:\n",
" pred_price = float(output.split(\"$\")[1].split()[0])\n",
" except:\n",
" continue\n",
" y_true.append(item.price)\n",
" y_pred.append(pred_price)\n",
" print(f\"{i+1}. {item.title[:50]} | Actual: ${item.price} | Pred: ${pred_price}\")\n",
" return y_true, y_pred\n",
"\n",
"y_true, y_pred = test_model(MODEL_ID, val_subset)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "347c22cb",
"metadata": {},
"outputs": [],
"source": [
"errors = np.abs(np.array(y_true) - np.array(y_pred))\n",
"colors = [\"green\" if e < 10 else \"orange\" if e < 25 else \"red\" for e in errors]\n",
"\n",
"plt.figure(figsize=(10,6))\n",
"plt.scatter(range(len(y_true)), y_true, color='blue', label='Actual', alpha=0.6)\n",
"plt.scatter(range(len(y_pred)), y_pred, color=colors, label='Predicted', alpha=0.8)\n",
"plt.title(\"Fine-tuned Price Prediction Performance (Color-Coded by Error)\")\n",
"plt.xlabel(\"Sample Index\")\n",
"plt.ylabel(\"Price ($)\")\n",
"plt.legend()\n",
"plt.show()\n",
"\n",
"avg_error = np.mean(errors)\n",
"print(f\"\\nAverage error: ${avg_error:.2f}\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "llm-engineering",
"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.12.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,463 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 9,
"id": "f0c31d90",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import torch\n",
"import wandb\n",
"from datetime import datetime\n",
"from datasets import load_dataset\n",
"from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, EarlyStoppingCallback, set_seed\n",
"from peft import LoraConfig, PeftModel\n",
"from trl import SFTTrainer, SFTConfig, DataCollatorForCompletionOnlyLM\n",
"import math\n",
"import matplotlib.pyplot as plt\n",
"import torch.nn.functional as F"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "768666b4",
"metadata": {},
"outputs": [],
"source": [
"# Testing it on the lite pricer data that I have created\n",
"DATASET_NAME = f\"qshaikh/lite-pricer-data\"\n",
"dataset = load_dataset(DATASET_NAME)\n",
"train = dataset['train']\n",
"test = dataset['test']\n",
"split_ratio = 0.10 # 10% for validation\n",
"\n",
"TRAIN_SIZE = 15000\n",
"train = train.select(range(TRAIN_SIZE))\n",
"\n",
"total_size = len(train)\n",
"val_size = int(total_size * split_ratio)\n",
"\n",
"val_data = train.select(range(val_size))\n",
"train_data = train.select(range(val_size, total_size))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "12e2b634",
"metadata": {},
"outputs": [],
"source": [
"print(f\"Train data size : {len(train_data)}\")\n",
"print(f\"Validation data size: {len(val_data)}\")\n",
"print(f\"Test data size : {len(test)}\")"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "1a75961f",
"metadata": {},
"outputs": [],
"source": [
"PROJECT_NAME = \"llama3-new-pricer\"\n",
"RUN_NAME = f\"{datetime.now():%Y-%m-%d_%H.%M.%S}-size{total_size}\"\n",
"PROJECT_RUN_NAME = f\"{PROJECT_NAME}-{RUN_NAME}\"\n",
"HUB_MODEL_NAME = f\"qshaikh/{PROJECT_RUN_NAME}\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bf9bca38",
"metadata": {},
"outputs": [],
"source": [
"LOG_TO_WANDB = True\n",
"os.environ[\"WANDB_PROJECT\"] = PROJECT_NAME\n",
"os.environ[\"WANDB_LOG_MODEL\"] = \"checkpoint\" if LOG_TO_WANDB else \"end\"\n",
"os.environ[\"WANDB_WATCH\"] = \"gradients\"\n",
"\n",
"if LOG_TO_WANDB:\n",
" wandb.init(project=PROJECT_NAME, name=RUN_NAME)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "77e0ee8c",
"metadata": {},
"outputs": [],
"source": [
"BASE_MODEL = \"meta-llama/Llama-3.2-1B\"\n",
"\n",
"quant_config = BitsAndBytesConfig(\n",
" load_in_4bit=True, \n",
" bnb_4bit_use_double_quant=True,\n",
" bnb_4bit_compute_dtype=torch.bfloat16,\n",
" bnb_4bit_quant_type=\"nf4\"\n",
")\n",
"\n",
"tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL, trust_remote_code=True)\n",
"tokenizer.pad_token = tokenizer.eos_token\n",
"tokenizer.padding_side = \"right\"\n",
"\n",
"base_model = AutoModelForCausalLM.from_pretrained(\n",
" BASE_MODEL,\n",
" quantization_config=quant_config,\n",
" device_map=\"auto\",\n",
")\n",
"base_model.generation_config.pad_token_id = tokenizer.pad_token_id\n",
"\n",
"print(f\"Memory footprint: {base_model.get_memory_footprint() / 1e6:.1f} MB\")"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "bf4a0187",
"metadata": {},
"outputs": [],
"source": [
"response_template = \"Price is $\"\n",
"collator = DataCollatorForCompletionOnlyLM(response_template, tokenizer=tokenizer)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "25ce4f40",
"metadata": {},
"outputs": [],
"source": [
"LORA_R = 32\n",
"LORA_ALPHA = 64\n",
"TARGET_MODULES = [\"q_proj\", \"v_proj\", \"k_proj\", \"o_proj\"]\n",
"LORA_DROPOUT = 0.1"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "e1e6c237",
"metadata": {},
"outputs": [],
"source": [
"lora_parameters = LoraConfig(\n",
" r=LORA_R,\n",
" lora_alpha=LORA_ALPHA,\n",
" lora_dropout=LORA_DROPOUT,\n",
" target_modules=TARGET_MODULES,\n",
" bias=\"none\",\n",
" task_type=\"CAUSAL_LM\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f68d0fdc",
"metadata": {},
"outputs": [],
"source": [
"EPOCHS = 1\n",
"BATCH_SIZE = 8 # Tested it with 4 first to be on the safer side, however was taking too long. Increased it upto 8 then.\n",
"GRADIENT_ACCUMULATION_STEPS = 1\n",
"MAX_SEQUENCE_LENGTH = 182\n",
"LEARNING_RATE = 1e-4\n",
"LR_SCHEDULER_TYPE = 'cosine'\n",
"WARMUP_RATIO = 0.03\n",
"OPTIMIZER = \"paged_adamw_32bit\"\n",
"\n",
"SAVE_STEPS = 200\n",
"STEPS = 20\n",
"save_total_limit = 10\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "37c3ab80",
"metadata": {},
"outputs": [],
"source": [
"train_parameters = SFTConfig(\n",
" output_dir=PROJECT_RUN_NAME,\n",
" run_name=RUN_NAME,\n",
" dataset_text_field=\"text\",\n",
" max_seq_length=MAX_SEQUENCE_LENGTH,\n",
"\n",
" num_train_epochs=EPOCHS,\n",
" per_device_train_batch_size=BATCH_SIZE,\n",
" gradient_accumulation_steps=GRADIENT_ACCUMULATION_STEPS,\n",
" max_steps=-1,\n",
" group_by_length=True,\n",
"\n",
" eval_strategy=\"steps\",\n",
" eval_steps=STEPS,\n",
" per_device_eval_batch_size=1,\n",
"\n",
" learning_rate=LEARNING_RATE,\n",
" lr_scheduler_type=LR_SCHEDULER_TYPE,\n",
" warmup_ratio=WARMUP_RATIO,\n",
" optim=OPTIMIZER,\n",
" weight_decay=0.001,\n",
" max_grad_norm=0.3,\n",
"\n",
" fp16=False,\n",
" bf16=True,\n",
"\n",
" logging_steps=STEPS,\n",
" save_strategy=\"steps\",\n",
" save_steps=SAVE_STEPS,\n",
" save_total_limit=save_total_limit,\n",
" report_to=\"wandb\" if LOG_TO_WANDB else None,\n",
"\n",
" push_to_hub=True,\n",
" hub_strategy=\"every_save\",\n",
" load_best_model_at_end=True,\n",
" metric_for_best_model=\"eval_loss\",\n",
" greater_is_better=False\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "56abe69d",
"metadata": {},
"outputs": [],
"source": [
"fine_tuning = SFTTrainer(\n",
" model=base_model,\n",
" train_dataset=train_data,\n",
" eval_dataset=val_data,\n",
" peft_config=lora_parameters, \n",
" args=train_parameters, \n",
" data_collator=collator,\n",
" callbacks=[EarlyStoppingCallback(early_stopping_patience=5)] \n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "17ed25ec",
"metadata": {},
"outputs": [],
"source": [
"fine_tuning.train()\n",
"print(f\"Best model pushed to HF Hub: {HUB_MODEL_NAME}\")"
]
},
{
"cell_type": "markdown",
"id": "7230239e",
"metadata": {},
"source": [
"## Evaluating Model Performance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "61c2955c",
"metadata": {},
"outputs": [],
"source": [
"GREEN = \"\\033[92m\"\n",
"YELLOW = \"\\033[93m\"\n",
"RED = \"\\033[91m\"\n",
"RESET = \"\\033[0m\"\n",
"COLOR_MAP = {\"red\":RED, \"orange\": YELLOW, \"green\": GREEN}\n",
"\n",
"class Tester:\n",
"\n",
" def __init__(self, predictor, data, title=None, size=250):\n",
" self.predictor = predictor\n",
" self.data = data\n",
" self.title = title or predictor.__name__.replace(\"_\", \" \").title()\n",
" self.size = size\n",
" self.guesses = []\n",
" self.truths = []\n",
" self.errors = []\n",
" self.sles = []\n",
" self.colors = []\n",
"\n",
" def color_for(self, error, truth):\n",
" if error<40 or error/truth < 0.2:\n",
" return \"green\"\n",
" elif error<80 or error/truth < 0.4:\n",
" return \"orange\"\n",
" else:\n",
" return \"red\"\n",
"\n",
" def run_datapoint(self, i):\n",
" datapoint = self.data[i]\n",
" guess = self.predictor(datapoint[\"text\"])\n",
" truth = datapoint[\"price\"]\n",
" error = abs(guess - truth)\n",
" log_error = math.log(truth+1) - math.log(guess+1)\n",
" sle = log_error ** 2\n",
" color = self.color_for(error, truth)\n",
" self.guesses.append(guess)\n",
" self.truths.append(truth)\n",
" self.errors.append(error)\n",
" self.sles.append(sle)\n",
" self.colors.append(color)\n",
"\n",
" def chart(self, title):\n",
" plt.figure(figsize=(12, 8))\n",
" max_val = max(max(self.truths), max(self.guesses))\n",
" plt.plot([0, max_val], [0, max_val], color='deepskyblue', lw=2, alpha=0.6)\n",
" plt.scatter(self.truths, self.guesses, s=3, c=self.colors)\n",
" plt.xlabel('Ground Truth')\n",
" plt.ylabel('Model Estimate')\n",
" plt.xlim(0, max_val)\n",
" plt.ylim(0, max_val)\n",
" plt.title(title)\n",
"\n",
" from matplotlib.lines import Line2D\n",
" legend_elements = [\n",
" Line2D([0], [0], marker='o', color='w', label='Accurate (green)', markerfacecolor='green', markersize=8),\n",
" Line2D([0], [0], marker='o', color='w', label='Medium error (orange)', markerfacecolor='orange', markersize=8),\n",
" Line2D([0], [0], marker='o', color='w', label='High error (red)', markerfacecolor='red', markersize=8)\n",
" ]\n",
" plt.legend(handles=legend_elements, loc='upper right')\n",
"\n",
" plt.show()\n",
"\n",
"\n",
" def report(self):\n",
" average_error = sum(self.errors) / self.size\n",
" rmsle = math.sqrt(sum(self.sles) / self.size)\n",
" hits = sum(1 for color in self.colors if color==\"green\")\n",
" title = f\"{self.title} Error=${average_error:,.2f} RMSLE={rmsle:,.2f} Hits={hits/self.size*100:.1f}%\"\n",
" self.chart(title)\n",
"\n",
" def run(self):\n",
" self.error = 0\n",
" for i in range(self.size):\n",
" self.run_datapoint(i)\n",
" self.report()\n",
"\n",
" @classmethod\n",
" def test(cls, function, data):\n",
" cls(function, data).run()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e1ff9de0",
"metadata": {},
"outputs": [],
"source": [
"test[0]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5b88ec79",
"metadata": {},
"outputs": [],
"source": [
"FINETUNED_MODEL = \"qshaikh/llama3-new-pricer-2025-10-29_00.32.54-size15000\"\n",
"fine_tuned_model = PeftModel.from_pretrained(base_model, FINETUNED_MODEL)\n",
"print(f\"Memory footprint: {fine_tuned_model.get_memory_footprint() / 1e6:.1f} MB\")\n",
"fine_tuned_model"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "eb2d3a22",
"metadata": {},
"outputs": [],
"source": [
"top_K = 3\n",
"\n",
"def improved_model_predict(prompt, device=\"cuda\"):\n",
" set_seed(42) \n",
" inputs = tokenizer.encode(prompt, return_tensors=\"pt\").to(device)\n",
" attention_mask = torch.ones(inputs.shape, device=device)\n",
"\n",
" with torch.no_grad(): \n",
" outputs = fine_tuned_model(inputs, attention_mask=attention_mask)\n",
" next_token_logits = outputs.logits[:, -1, :].to('cpu')\n",
"\n",
" next_token_probs = F.softmax(next_token_logits, dim=-1)\n",
" top_prob, top_token_id = next_token_probs.topk(top_K)\n",
"\n",
" prices, weights = [], [] \n",
"\n",
" for i in range(top_K):\n",
" predicted_token = tokenizer.decode(top_token_id[0][i])\n",
" probability = top_prob[0][i]\n",
"\n",
" try:\n",
" result = float(predicted_token)\n",
" except ValueError as e:\n",
" result = 0.0\n",
"\n",
" if result > 0:\n",
" prices.append(result)\n",
" weights.append(probability)\n",
"\n",
" if not prices:\n",
" return 0.0, 0.0\n",
"\n",
" total = sum(weights)\n",
"\n",
" weighted_prices = [price * weight / total for price, weight in zip(prices, weights)]\n",
"\n",
" return sum(weighted_prices).item()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dc47ff1e",
"metadata": {},
"outputs": [],
"source": [
"improved_model_predict(test[0][\"text\"], device=\"cuda\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3dbecfff",
"metadata": {},
"outputs": [],
"source": [
"Tester.test(improved_model_predict, test)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"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.10.19"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,538 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "mzYB4XYQeWRQ",
"metadata": {
"id": "mzYB4XYQeWRQ"
},
"outputs": [],
"source": [
"!pip install tqdm huggingface_hub numpy sentence-transformers datasets chromadb catboost peft torch bitsandbytes"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b3caecd1-8712-4acd-80b5-e8059c16f43f",
"metadata": {
"id": "b3caecd1-8712-4acd-80b5-e8059c16f43f"
},
"outputs": [],
"source": [
"import os\n",
"import re\n",
"import zipfile\n",
"import chromadb\n",
"import joblib\n",
"import numpy as np\n",
"import pandas as pd\n",
"import requests\n",
"import torch\n",
"from datasets import load_dataset\n",
"from google.colab import userdata\n",
"from huggingface_hub import HfApi, hf_hub_download, login\n",
"from openai import OpenAI\n",
"from peft import PeftModel\n",
"from sentence_transformers import SentenceTransformer\n",
"from sklearn.linear_model import LinearRegression\n",
"from sklearn.metrics import r2_score, mean_squared_error\n",
"from tqdm import tqdm\n",
"from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig\n",
"from catboost import CatBoostRegressor"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "05d9523f-b6c9-4132-bd2b-6712772b3cd2",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "05d9523f-b6c9-4132-bd2b-6712772b3cd2",
"outputId": "f6bb70c7-58f8-4e3c-a592-83cfb4f395a7"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Mounted at /content/drive\n"
]
}
],
"source": [
"from google.colab import drive\n",
"drive.mount(\"/content/drive\")"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "z9735RD_TUHw",
"metadata": {
"id": "z9735RD_TUHw"
},
"outputs": [],
"source": [
"openai_api_key = userdata.get(\"OPENAI_API_KEY\")\n",
"openai = OpenAI(api_key=openai_api_key)\n",
"\n",
"hf_token = userdata.get(\"HF_TOKEN\")\n",
"login(hf_token, add_to_git_credential=True)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "DtswsfBQxxJF",
"metadata": {
"id": "DtswsfBQxxJF"
},
"outputs": [],
"source": [
"# Configuration\n",
"HF_USER = \"qshaikh\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0eKakxSFTVcA",
"metadata": {
"collapsed": true,
"id": "0eKakxSFTVcA"
},
"outputs": [],
"source": [
"DATASET_NAME = f\"{HF_USER}/pricer-data\"\n",
"dataset = load_dataset(DATASET_NAME)\n",
"test = dataset[\"test\"]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cWqvs8JRTggE",
"metadata": {
"collapsed": true,
"id": "cWqvs8JRTggE"
},
"outputs": [],
"source": [
"def description(item):\n",
" text = item[\"text\"].replace(\n",
" \"How much does this cost to the nearest dollar?\\n\\n\", \"\"\n",
" )\n",
" text = text.split(\"\\n\\nPrice is $\")[0]\n",
" return f\"passage: {text}\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "pjPBEgXqmHOA",
"metadata": {
"id": "pjPBEgXqmHOA"
},
"outputs": [],
"source": [
"CHROMA_PATH = \"/content/drive/MyDrive/chroma\"\n",
"COLLECTION_NAME = \"price_items\"\n",
"\n",
"print(f\"Attempting to load ChromaDB from: {CHROMA_PATH}\")\n",
"\n",
"client = chromadb.PersistentClient(path=CHROMA_PATH)\n",
"collection = client.get_or_create_collection(name=COLLECTION_NAME)\n",
"\n",
"print(f\"Successfully loaded ChromaDB collection '{COLLECTION_NAME}'.\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8fi1BS71XCv1",
"metadata": {
"id": "8fi1BS71XCv1"
},
"outputs": [],
"source": [
"embedding_model = SentenceTransformer(\"intfloat/e5-small-v2\", device=\"cuda\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "zmwIbufXUzMo",
"metadata": {
"id": "zmwIbufXUzMo"
},
"outputs": [],
"source": [
"BASE_MODEL = \"meta-llama/Llama-3.1-8B\"\n",
"FINETUNED_MODEL = \"ed-donner/pricer-2024-09-13_13.04.39\"\n",
"REVISION = \"e8d637df551603dc86cd7a1598a8f44af4d7ae36\"\n",
"\n",
"quant_config = BitsAndBytesConfig(\n",
" load_in_4bit=True,\n",
" bnb_4bit_use_double_quant=True,\n",
" bnb_4bit_compute_dtype=torch.bfloat16,\n",
" bnb_4bit_quant_type=\"nf4\",\n",
")\n",
"\n",
"tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL, trust_remote_code=True)\n",
"tokenizer.pad_token = tokenizer.eos_token\n",
"tokenizer.padding_side = \"right\"\n",
"\n",
"base_model = AutoModelForCausalLM.from_pretrained(\n",
" BASE_MODEL, quantization_config=quant_config, device_map=\"auto\"\n",
")\n",
"\n",
"fine_tuned_model = PeftModel.from_pretrained(\n",
" base_model, FINETUNED_MODEL, revision=REVISION\n",
")\n",
"\n",
"fine_tuned_model.generation_config.pad_token_id = tokenizer.pad_token_id\n",
"\n",
"print(f\"Memory footprint: {fine_tuned_model.get_memory_footprint() / 1e6:.1f} MB\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0IHiJNU7a4XC",
"metadata": {
"id": "0IHiJNU7a4XC"
},
"outputs": [],
"source": [
"#Cat Boost Trained Model\n",
"catboost_model_path = \"/content/drive/MyDrive/catboost_model.pkl\"\n",
"catboost_model = joblib.load(catboost_model_path)\n",
"print(f\"Successfully loaded CatBoost model from {catboost_model_path}\")"
]
},
{
"cell_type": "code",
"execution_count": 24,
"id": "LgGmUKJxayZ6",
"metadata": {
"id": "LgGmUKJxayZ6"
},
"outputs": [],
"source": [
"def extract_tagged_price(output: str):\n",
" try:\n",
" contents = output.split(\"Price is $\")[1].replace(\",\", \"\")\n",
" match = re.search(r\"[-+]?\\d*\\.\\d+|\\d+\", contents)\n",
" return float(match.group()) if match else 0.0\n",
" except Exception:\n",
" return 0.0"
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "ggKf1nSQbAnv",
"metadata": {
"id": "ggKf1nSQbAnv"
},
"outputs": [],
"source": [
"def ft_llama_price(description: str):\n",
" prompt = (\n",
" f\"How much does this cost to the nearest dollar?\\n\\n{description}\\n\\nPrice is $\"\n",
" )\n",
" inputs = tokenizer(prompt, return_tensors=\"pt\").to(\"cuda\")\n",
"\n",
" outputs = fine_tuned_model.generate(\n",
" **inputs, max_new_tokens=5, num_return_sequences=1\n",
" )\n",
"\n",
" result = tokenizer.decode(outputs[0])\n",
" price = extract_tagged_price(result)\n",
" return price"
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "_cWyYUd4Ub-K",
"metadata": {
"id": "_cWyYUd4Ub-K"
},
"outputs": [],
"source": [
"def catboost_price(description: str):\n",
" vector = embedding_model.encode([description], normalize_embeddings=True)[0]\n",
" pred = catboost_model.predict([vector])[0]\n",
" return round(float(max(0, pred)), 2)"
]
},
{
"cell_type": "code",
"execution_count": 27,
"id": "3Skod8juXgnN",
"metadata": {
"id": "3Skod8juXgnN"
},
"outputs": [],
"source": [
"def gpt4o_price(item):\n",
" def get_embedding(text):\n",
" return embedding_model.encode([text], normalize_embeddings=True)\n",
"\n",
" def find_similars(text):\n",
" results = collection.query(\n",
" query_embeddings=get_embedding(text).astype(float).tolist(), n_results=5\n",
" )\n",
" docs = results[\"documents\"][0]\n",
" prices = [m[\"price\"] for m in results[\"metadatas\"][0]]\n",
" return docs, prices\n",
"\n",
" def format_context(similars, prices):\n",
" context = (\n",
" \"To provide some context, here are similar products and their prices:\\n\\n\"\n",
" )\n",
" for sim, price in zip(similars, prices):\n",
" context += f\"Product:\\n{sim}\\nPrice is ${price:.2f}\\n\\n\"\n",
" return context\n",
"\n",
" def build_messages(description, similars, prices):\n",
" system_message = (\n",
" \"You are a pricing expert. \"\n",
" \"Given a product description and a few similar products with their prices, \"\n",
" \"estimate the most likely price. \"\n",
" \"Respond ONLY with a number, no words.\"\n",
" )\n",
" context = format_context(similars, prices)\n",
" user_prompt = (\n",
" \"Estimate the price for the following product:\\n\\n\"\n",
" + description\n",
" + \"\\n\\n\"\n",
" + context\n",
" )\n",
" return [\n",
" {\"role\": \"system\", \"content\": system_message},\n",
" {\"role\": \"user\", \"content\": user_prompt},\n",
" {\"role\": \"assistant\", \"content\": \"Price is $\"},\n",
" ]\n",
"\n",
" docs, prices = find_similars(description(item))\n",
" messages = build_messages(description(item), docs, prices)\n",
" response = openai.chat.completions.create(\n",
" model=\"gpt-4o-mini\", messages=messages, seed=42, max_tokens=5\n",
" )\n",
" reply = response.choices[0].message.content\n",
" return float(\n",
" re.search(r\"[-+]?\\d*\\.\\d+|\\d+\", reply.replace(\"$\", \"\").replace(\",\", \"\")).group()\n",
" or 0\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8XQK5yrk8On4",
"metadata": {
"id": "8XQK5yrk8On4"
},
"outputs": [],
"source": [
"print(\"Splitting entire dataset...\")\n",
"np.random.seed(42)\n",
"all_indices = list(range(len(test)))\n",
"np.random.shuffle(all_indices)\n",
"\n",
"train_split_size = int(0.8 * len(all_indices))\n",
"train_indices = all_indices[:train_split_size] # 80%\n",
"test_indices = all_indices[train_split_size:] # 20%\n",
"\n",
"train_indices = train_indices[:250]\n",
"test_indices = test_indices[:50]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "XN7P5fkkXfgP",
"metadata": {
"id": "XN7P5fkkXfgP"
},
"outputs": [],
"source": [
"ft_llama_preds_train = []\n",
"gpt4omini_preds_train = []\n",
"catboost_preds_train = []\n",
"true_prices_train = []\n",
"\n",
"for i in tqdm(train_indices):\n",
" item = test[i]\n",
" text = description(item)\n",
" true_prices_train.append(item[\"price\"])\n",
" ft_llama_preds_train.append(ft_llama_price(text))\n",
" gpt4omini_preds_train.append(gpt4o_price(item))\n",
" catboost_preds_train.append(catboost_price(text))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1_6_atEgHnFR",
"metadata": {
"id": "1_6_atEgHnFR"
},
"outputs": [],
"source": [
"print(\"True Prices:\", true_prices_train)\n",
"print(\"FT-LLaMA Predictions:\", ft_llama_preds_train)\n",
"print(\"GPT-4o-mini Predictions:\", gpt4omini_preds_train)\n",
"print(\"CatBoost Predictions:\", catboost_preds_train)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "tYWMhTrXcA7x",
"metadata": {
"id": "tYWMhTrXcA7x"
},
"outputs": [],
"source": [
"maxes_train = [\n",
" max(a, b, c)\n",
" for a, b, c in zip(ft_llama_preds_train, gpt4omini_preds_train, catboost_preds_train)\n",
"]\n",
"means_train = [\n",
" np.mean([a, b, c])\n",
" for a, b, c in zip(ft_llama_preds_train, gpt4omini_preds_train, catboost_preds_train)\n",
"]\n",
"\n",
"X_train = pd.DataFrame(\n",
" {\n",
" \"FT_LLaMA\": ft_llama_preds_train,\n",
" \"GPT4oMini\": gpt4omini_preds_train,\n",
" \"CatBoost\": catboost_preds_train,\n",
" \"Max\": maxes_train,\n",
" \"Mean\": means_train,\n",
" }\n",
")\n",
"\n",
"y_train = pd.Series(true_prices_train)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "-WsFABEicOyo",
"metadata": {
"id": "-WsFABEicOyo"
},
"outputs": [],
"source": [
"np.random.seed(42)\n",
"lr = LinearRegression()\n",
"lr.fit(X_train, y_train)\n",
"\n",
"feature_columns = X_train.columns.tolist()\n",
"for feature, coef in zip(feature_columns, lr.coef_):\n",
" print(f\"{feature}: {coef:.2f}\")\n",
"print(f\"Intercept={lr.intercept_:.2f}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "W3F0nNBXlrUJ",
"metadata": {
"id": "W3F0nNBXlrUJ"
},
"outputs": [],
"source": [
"ft_llama_preds_test = []\n",
"gpt4omini_preds_test = []\n",
"catboost_preds_test = []\n",
"true_prices_test = []\n",
"\n",
"print(\"Processing TEST data (50 items)...\")\n",
"for i in tqdm(test_indices):\n",
" item = test[i]\n",
" text = description(item)\n",
" true_prices_test.append(item[\"price\"])\n",
" ft_llama_preds_test.append(ft_llama_price(text))\n",
" gpt4omini_preds_test.append(gpt4o_price(item))\n",
" catboost_preds_test.append(catboost_price(text))\n",
"\n",
"maxes_test = [\n",
" max(a, b, c)\n",
" for a, b, c in zip(ft_llama_preds_test, gpt4omini_preds_test, catboost_preds_test)\n",
"]\n",
"means_test = [\n",
" np.mean([a, b, c])\n",
" for a, b, c in zip(ft_llama_preds_test, gpt4omini_preds_test, catboost_preds_test)\n",
"]\n",
"\n",
"X_test = pd.DataFrame(\n",
" {\n",
" \"FT_LLaMA\": ft_llama_preds_test,\n",
" \"GPT4oMini\": gpt4omini_preds_test,\n",
" \"CatBoost\": catboost_preds_test,\n",
" \"Max\": maxes_test,\n",
" \"Mean\": means_test,\n",
" }\n",
")\n",
"\n",
"y_test = pd.Series(true_prices_test)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "y25l8rR791wG",
"metadata": {
"id": "y25l8rR791wG"
},
"outputs": [],
"source": [
"print(\"Evaluating model...\")\n",
"y_pred = lr.predict(X_test)\n",
"r2 = r2_score(y_test, y_pred)\n",
"print(f\"R² score: {r2:.4f}\")\n",
"\n",
"rmse = np.sqrt(mean_squared_error(y_test, y_pred))\n",
"print(f\"RMSE: {rmse:.2f}\")\n",
"\n",
"mape = np.mean(np.abs((y_test - y_pred) / y_test)) * 100\n",
"print(f\"MAPE: {mape:.2f}%\")"
]
}
],
"metadata": {
"accelerator": "GPU",
"colab": {
"gpuType": "T4",
"provenance": []
},
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"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.11.11"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -13,7 +13,7 @@
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": null,
"id": "c1070317-3ed9-4659-abe3-828943230e03",
"metadata": {},
"outputs": [],
@@ -25,7 +25,7 @@
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": null,
"id": "4a456906-915a-4bfd-bb9d-57e505c5093f",
"metadata": {},
"outputs": [],
@@ -37,7 +37,7 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": null,
"id": "a8d7923c-5f28-4c30-8556-342d7c8497c1",
"metadata": {},
"outputs": [],
@@ -65,42 +65,10 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": null,
"id": "6f448d69-3cec-4915-8697-f1046ba23e4a",
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"To find the speed of Alex, we need to use the formula:\n",
"\n",
"Speed = Distance / Time\n",
"\n",
"We know the distance (3 kms) and the time it took for the journey (2 hours).\n",
"\n",
"First, let's convert the distance from kilometers to meters: 1 km = 1000 meters, so:\n",
"Distance (in meters) = 3 km × 1000 m/km = 3000 meters\n",
"\n",
"Now we can plug in the values:\n",
"\n",
"Speed = Distance / Time\n",
"= 3000 meters / 2 hours\n",
"= 1500 meters-per-hour\n",
"\n",
"To make it more readable, let's convert this to kilometers per hour (km/h):\n",
"1 meter = 0.001 km (to convert meters to kilometers), so:\n",
"= 1500 m ÷ 1000 = 1.5 km\n",
"\n",
"Therefore, Alex's speed is 1.5 kilometers per hour."
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"outputs": [],
"source": [
"# Task 1: Tight Speed\n",
"\n",
@@ -113,64 +81,10 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": null,
"id": "3f0d0137-52b0-47a8-81a8-11a90a010798",
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"Traveling around the world is an exciting adventure! To help you minimize your travel time, I'll provide a general outline of the most efficient way to cover all continents and major cities.\n",
"\n",
"**The Most Efficient Route:**\n",
"\n",
"1. Start from North America (USA or Canada) and head east:\n",
"\t* Fly from Los Angeles to Dubai\n",
"\t* From Dubai, take a Middle Eastern flight to Istanbul, Turkey\n",
"2. Next, enter Europe by flying back west from Istanbul:\n",
"\t* Take trains and buses between major European cities like Berlin, Prague, Vienna, etc.\n",
"3. Head south into Asia:\n",
"\t* From Eastern Europe, fly to Delhi or Mumbai in India\n",
"\t* Then, take flights to Southeast Asian countries like Bangkok (Thailand), Jakarta (Indonesia), or Kuala Lumpur (Malaysia)\n",
"4. Cross into Africa and visit major cities:\n",
"\t* Fly from Southeast Asia to Cairo, Egypt\n",
"\t* Explore North African countries like Morocco, Tunisia, and Algeria\n",
"5. From Africa, head north into Europe again:\n",
"\t* Fly back to Western European countries like London (UK), Paris (France), or Amsterdam (Netherlands)\n",
"6. Finally, enter South America from Europe:\n",
"\t* Take flights from European cities to Buenos Aires (Argentina) or Rio de Janeiro (Brazil)\n",
"\n",
"**Tips and Considerations:**\n",
"\n",
"1. **Fly through major hubs:** Using airports like Dubai, Istanbul, Cairo, Bangkok, and Singapore will simplify your journey.\n",
"2. **Choose efficient airlines:** Look for ultra-low-cost carriers, budget airlines, or hybrid models that offer competitive prices.\n",
"3. **Plan smart connections:** Research flight schedules, layovers, and travel restrictions to minimize delays.\n",
"4. **Use visa-free policies:** Make the most of visa exemptions where possible, like e-Visas for India, Mexico, and some African countries.\n",
"5. **Health insurance:** Check if your travel insurance covers medical care abroad.\n",
"\n",
"**Time Estimates:**\n",
"\n",
"* Assuming a moderate pace (some planning, but no frills), you can cover around 10-15 major cities in 2-3 months with decent connections and layovers.\n",
"* However, this pace is dependent on your personal interests, budget, and flexibility. Be prepared to adjust based on changing circumstances or unexpected delays.\n",
"\n",
"**Additional Tips:**\n",
"\n",
"1. Consider the weather, peak tourist seasons, and holidays when planning your trip.\n",
"2. Bring essential documents like passports, visas (if required), travel insurance, and health certificates.\n",
"3. Research local regulations, COVID-19 guidelines, and vaccinations before traveling to specific countries.\n",
"\n",
"Keep in mind that this outline is a general suggestion, and actual times will vary depending on your start date, flight options, visa processing, and additional activities (like snorkeling or hiking) you'd like to incorporate.\n",
"\n",
"Is there anything else I can help with?"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"outputs": [],
"source": [
"# Task 2: Travel the world in X days?\n",
"\n",
@@ -183,102 +97,10 @@
},
{
"cell_type": "code",
"execution_count": 27,
"execution_count": null,
"id": "60ce7000-a4a5-4cce-a261-e75ef45063b4",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Here's an example implementation using Python with the `requests` library to fetch the webpage content and `BeautifulSoup` for HTML parsing.\n",
"\n",
"### Install Required Libraries\n",
"```bash\n",
"pip install requests beautifulsoup4\n",
"```\n",
"\n",
"### Code Implementation\n",
"\n",
"```python\n",
"import requests\n",
"from bs4 import BeautifulSoup\n",
"\n",
"def get_webpage_content(url):\n",
" \"\"\"\n",
" Fetches the contents of a website.\n",
" \n",
" Args:\n",
" url (str): URL of the webpage.\n",
" \n",
" Returns:\n",
" str: HTML content of the webpage.\n",
" \"\"\"\n",
" try:\n",
" response = requests.get(url)\n",
" response.raise_for_status() # Raise an exception for HTTP errors\n",
" return response.text\n",
" except requests.exceptions.RequestException as e:\n",
" print(f\"Error fetching webpage: {e}\")\n",
" return None\n",
"\n",
"def parse_links(html_content, base_url=\"\"):\n",
" \"\"\"\n",
" Parses links from a given HTML content.\n",
" \n",
" Args:\n",
" html_content (str): HTML content of the webpage.\n",
" base_url (str): Base URL to construct relative link URLs. Defaults to \"\".\n",
" \n",
" Returns:\n",
" list: List of extracted URLs.\n",
" \"\"\"\n",
" soup = BeautifulSoup(html_content, 'html.parser')\n",
" links = []\n",
"\n",
" for tag in soup.find_all('a'):\n",
" href = tag.get('href')\n",
"\n",
" # Handle absolute and relative URLs\n",
" if not href or href.startswith('/'):\n",
" url = \"\"\n",
" else:\n",
" if base_url:\n",
" url = f\"{base_url}{href}\"\n",
" else:\n",
" url = href\n",
"\n",
" links.append(url)\n",
"\n",
" return links\n",
"\n",
"# Example usage\n",
"url = \"http://www.example.com\"\n",
"html_content = get_webpage_content(url)\n",
"links = parse_links(html_content, url)\n",
"\n",
"print(\"Extracted Links:\")\n",
"for link in links:\n",
" print(link)\n",
"```\n",
"\n",
"### How It Works\n",
"\n",
"1. `get_webpage_content` function takes a URL as input and fetches the corresponding webpage using `requests.get()`. It raises exceptions for HTTP errors.\n",
"2. `parse_links` function analyzes the provided HTML content to find all `<a>` tags, extracts their `href` attributes, and constructs URLs by appending relative paths to a base URL (if specified).\n",
"3. If you want to inspect the behavior of this code with your own inputs, use the example usage above as reference.\n",
"\n",
"### Commit Message\n",
"```markdown\n",
"feat: add functions for URL fetching & HTML link parsing\n",
"\n",
"Description: Provides two main Python functions, `get_webpage_content` and `parse_links`, leveraging `requests` and `BeautifulSoup` respectively.\n",
"```\n",
"\n",
"Please feel free to ask me any questions or need further clarification.\n"
]
}
],
"outputs": [],
"source": [
"# Task 3: Generate Code for task 4 to scrap some webpages\n",
"\n",
@@ -291,7 +113,7 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": null,
"id": "8f7c8ea8-4082-4ad0-8751-3301adcf6538",
"metadata": {},
"outputs": [],
@@ -353,105 +175,12 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": null,
"id": "77286a37-7d34-44f0-bbab-abd1d33b21b3",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Extracted Links:\n",
"https://endpoints.huggingface.co\n",
"https://apply.workable.com/huggingface/\n",
"https://discuss.huggingface.co\n",
"https://status.huggingface.co/\n",
"https://github.com/huggingface\n",
"https://twitter.com/huggingface\n",
"https://www.linkedin.com/company/huggingface/\n"
]
},
{
"data": {
"text/markdown": [
"Here's a possible brochure design and content based on the code snippet provided:\n",
"\n",
"**[Cover Page]**\n",
"\n",
"* Title: Hugging Face\n",
"* Tagline: Building sustainable AI models for everyone\n",
"* Background image: A gradient background with a collage of diverse images, likely representing people from different cultures and backgrounds working together.\n",
"\n",
"**[Inside Pages]**\n",
"\n",
"**[Page 1: About Us]**\n",
"\n",
"* Headline: Discover the Power of AI Models on Hugging Face\n",
"* Text: Hugging Face is a leading open-source platform for natural language processing (NLP) models. Our mission is to empower researchers, developers, and businesses to build and use high-quality AI models that can be applied in various industries.\n",
"* Image: A group photo of the Hugging Face team\n",
"\n",
"**[Page 2: Models]**\n",
"\n",
"* Headline: Explore the Largest Collection of Pre-Trained NLP Models\n",
"* Text: Our model portal offers over 200 pre-trained models, covering a wide range of tasks such as sentiment analysis, entity recognition, and language translation.\n",
"* Features:\n",
" + Model browsing by task or dataset\n",
" + Filtering by accuracy, accuracy distribution, weights, and more\n",
"\t+ Training from scratch options for advanced users\n",
"* Image: A screenshot of the model portal with a random selection of models\n",
"\n",
"**[Page 3: Datasets]**\n",
"\n",
"* Headline: Tap into a Universe of High-Quality Datasets for Model Training\n",
"* Text: Hugging Face's dataset repository includes over 1 million datasets, covering various domains such as text analysis, speech recognition, and sentiment analysis.\n",
"* Features:\n",
" + Dataset browsing by domain or type\n",
" + Filtering by size, download time, license, and more\n",
"\t+ Data augmentation options\n",
"* Image: A screenshot of the dataset repository with a random selection of datasets\n",
"\n",
"**[Page 4: Spaces]**\n",
"\n",
"* Headline: Collaborate on Research Projects and Share Models\n",
"* Text: Our shared model hosting platform allows researchers to collaborate on open-source projects, share models, and receive feedback from community members.\n",
"* Features:\n",
" + Project creation options for collaboration\n",
"\t+ Model sharing and download\n",
"\t+ Discussion forums for feedback and support\n",
"* Image: A screenshot of the spaces dashboard with a selected project\n",
"\n",
"**[Page 5: Changelog]**\n",
"\n",
"* Headline: Stay Up-to-Date on the Latest Hugging Face Features\n",
"* Text: Get notified about new model releases, dataset updates, and feature enhancements through our changelog.\n",
"* Format:\n",
"\t+ List of recent features and bug fixes with brief descriptions\n",
"\t+ Links to documentation or demo models for some features\n",
"\t+ Option to subscribe to notifications via email\n",
"* Image: A screenshot of the changelog as it appears on a mobile device\n",
"\n",
"**[Back Cover]**\n",
"\n",
"* Call-to-Action (CTA): Sign up for our newsletter and get started with Hugging Face today!\n",
"* Text: \"Unlock the power of AI models for everyone. Subscribe to our newsletter for news, tutorials, and special offers.\"\n",
"* Background image: The same collage as the cover page.\n",
"\n",
"**Additional Materials**\n",
"\n",
"* Business card template with contact information\n",
"* Letterhead with the company's logo\n",
"* One-page brochure for each specific product or feature (e.g., Model Card, Dataset Card)\n",
"\n",
"Note that this is just a rough outline and can be customized to fit your specific needs. The image and design elements used should be consistent throughout the brochure and online presence."
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"# Task 4: Make a brochure using the web-content\n",
"\n",
@@ -508,7 +237,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.14"
"version": "3.12.12"
}
},
"nbformat": 4,

View File

@@ -0,0 +1,234 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "d006b2ea-9dfe-49c7-88a9-a5a0775185fd",
"metadata": {},
"source": [
"# Additional End of week Exercise - week 2\n",
"\n",
"Now use everything you've learned from Week 2 to build a full prototype for the technical question/answerer you built in Week 1 Exercise.\n",
"\n",
"This should include a Gradio UI, streaming, use of the system prompt to add expertise, and the ability to switch between models. Bonus points if you can demonstrate use of a tool!\n",
"\n",
"If you feel bold, see if you can add audio input so you can talk to it, and have it respond with audio. ChatGPT or Claude can help you, or email me if you have questions.\n",
"\n",
"I will publish a full solution here soon - unless someone beats me to it...\n",
"\n",
"There are so many commercial applications for this, from a language tutor, to a company onboarding solution, to a companion AI to a course (like this one!) I can't wait to see your results."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6a479bea-0672-47dd-a151-f31f909c5d81",
"metadata": {},
"outputs": [],
"source": [
"# An Open Weather API based travel agent, biased to one particular destimation."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a07e7793-b8f5-44f4-aded-5562f633271a",
"metadata": {},
"outputs": [],
"source": [
"# imports\n",
"from openai import OpenAI\n",
"from IPython.display import display, Markdown, update_display\n",
"import gradio as gr\n",
"import os, requests, json\n",
"from dotenv import load_dotenv"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bcc8ce24-3fa9-40ae-a52d-4ae226f8989a",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "61780e58-366e-463f-a35b-a7b0fd8e6187",
"metadata": {},
"outputs": [],
"source": [
"MODEL_LLAMA = 'llama3.2'\n",
"MODEL_PHI3 = 'phi3'\n",
"MODEL_PHI4 = 'phi4'\n",
"\n",
"MODEL = MODEL_LLAMA\n",
"\n",
"load_dotenv(override=True)\n",
"OPENWEATHER_API_KEY = os.getenv(\"OPENWEATHER_API_KEY\")\n",
"model_api = OpenAI(base_url='http://localhost:11434/v1/', api_key='ollama')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b4c6ef3c-7052-4273-8e89-8af40cd6daed",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "338b0f4e-ba3c-4e68-b6ad-213848e24a9d",
"metadata": {},
"outputs": [],
"source": [
"def get_current_weather_openweather(city: str):\n",
" if not OPENWEATHER_API_KEY:\n",
" raise RuntimeError(\"Set OPENWEATHER_API_KEY env var\")\n",
" url = \"https://api.openweathermap.org/data/2.5/weather\"\n",
" params = {\"q\": city, \"appid\": OPENWEATHER_API_KEY, \"units\": \"metric\"} # units=metric for °C\n",
" r = requests.get(url, params=params, timeout=10)\n",
" r.raise_for_status()\n",
" data = r.json()\n",
" print(f'Asking weather data for {city}. Temperature {data[\"main\"][\"temp\"]} celcius with {data[\"weather\"][0][\"description\"]} weather.')\n",
" return {\n",
" \"city\": data.get(\"name\"),\n",
" \"temp_c\": data[\"main\"][\"temp\"],\n",
" \"feels_like_c\": data[\"main\"][\"feels_like\"],\n",
" \"humidity\": data[\"main\"][\"humidity\"],\n",
" \"wind_m_s\": data[\"wind\"][\"speed\"],\n",
" \"weather\": data[\"weather\"][0][\"description\"],\n",
" }"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "530b6036-a468-4718-83cb-a4ee4bbdf040",
"metadata": {},
"outputs": [],
"source": [
"system_message = \"\"\"\n",
"You are a helpful assistant for a travel company.\n",
"Give short, courteous answers, no more than 1 sentence.\n",
"Always be accurate. If you don't know the answer, say so.\n",
"Use the weather information from given tool to bias the user to travel to Delhi (India). \n",
"\"\"\"\n",
"# 1. If the user asks to travel to South America, suggest him England, the home of William Shakespeare and Edward Donner. \\n\n",
"# 2. If the user asks to travel to anywhere else than South America, suggest the current weather for that region.\\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a938ccfa-3aff-4330-9f1c-af2ed8362c19",
"metadata": {},
"outputs": [],
"source": [
"def handle_tool_calls(message):\n",
" responses = []\n",
" for tool_call in message.tool_calls:\n",
" if tool_call.function.name == 'get_current_weather_openweather':\n",
" arguments = json.loads(tool_call.function.arguments)\n",
" city = arguments.get('city')\n",
" if len(city):\n",
" details = json.dumps(get_current_weather_openweather(city)).replace('\\\"','')\n",
" responses.append({\n",
" \"role\": \"tool\",\n",
" \"content\": details,\n",
" \"tool_call_id\": tool_call.id\n",
" })\n",
" return responses"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "af12d91d-1758-40ec-b799-b2fda4fcb911",
"metadata": {},
"outputs": [],
"source": [
"weather_function = {\n",
" \"name\": \"get_current_weather_openweather\",\n",
" \"description\": \"Get the weather of the destination city, like temperature, wind, humidity etc.\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"city\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"The city for which weather information is required.\",\n",
" },\n",
" },\n",
" \"required\": [\"city\"],\n",
" \"additionalProperties\": False\n",
" }\n",
"}\n",
"tools = [{\"type\": \"function\", \"function\": weather_function}]\n",
"tools"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "55fdb369-0f8c-41c7-9e80-2f09c42f8c29",
"metadata": {},
"outputs": [],
"source": [
"def chat(message, history):\n",
" history = [{\"role\": h[\"role\"], \"content\": h[\"content\"]} for h in history]\n",
" messages = [{\"role\": \"system\", \"content\": system_message}] + history + [{\"role\": \"user\", \"content\": message}]\n",
" response = model_api.chat.completions.create(model=MODEL, messages=messages, tools=tools)\n",
"\n",
" while response.choices[0].finish_reason==\"tool_calls\":\n",
" message = response.choices[0].message\n",
" responses = handle_tool_calls(message)\n",
" messages.append(message)\n",
" messages.extend(responses)\n",
" response = model_api.chat.completions.create(model=MODEL, messages=messages, tools=tools)\n",
"\n",
" return response.choices[0].message.content\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4d11249c-a066-4b7e-9ce7-14e26e5f54aa",
"metadata": {},
"outputs": [],
"source": [
"gr.ChatInterface(fn=chat, type=\"messages\").launch()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fc091101-71a7-4113-81c9-21dc5cb2ece6",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"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.12.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,422 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "9f0759f2-5e46-438a-ad8e-b5d5771ec9ec",
"metadata": {},
"outputs": [],
"source": [
"# RAG based Gradio solution to give information from related documents, using Llama3.2 and nomic-embed-text over OLLAMA\n",
"# Took help of Claude and Course material."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "448bd8f4-9181-4039-829f-d3f0a5f14171",
"metadata": {},
"outputs": [],
"source": [
"import os, glob\n",
"import sqlite3\n",
"import json\n",
"import numpy as np\n",
"from typing import List, Dict, Tuple\n",
"import requests\n",
"import gradio as gr\n",
"from datetime import datetime\n",
"\n",
"embedding_model = 'nomic-embed-text'\n",
"llm_model = 'llama3.2'\n",
"RagDist_k = 6\n",
"folders = glob.glob(\"../../week5/knowledge-base/*\")\n",
"folders"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dc085852-a80f-4f2c-b31a-80ceda10bec6",
"metadata": {},
"outputs": [],
"source": [
"\n",
"class OllamaEmbeddings:\n",
" \"\"\"Generate embeddings using Ollama's embedding models.\"\"\"\n",
" \n",
" def __init__(self, model: str = embedding_model, base_url: str = \"http://localhost:11434\"):\n",
" self.model = model\n",
" self.base_url = base_url\n",
" \n",
" def embed_text(self, text: str) -> List[float]:\n",
" \"\"\"Generate embedding for a single text.\"\"\"\n",
" print('Processing', text[:70].replace('\\n',' | '))\n",
" response = requests.post(\n",
" f\"{self.base_url}/api/embeddings\",\n",
" json={\"model\": self.model, \"prompt\": text}\n",
" )\n",
" if response.status_code == 200:\n",
" return response.json()[\"embedding\"]\n",
" else:\n",
" raise Exception(f\"Error generating embedding: {response.text}\")\n",
" \n",
" def embed_documents(self, texts: List[str]) -> List[List[float]]:\n",
" \"\"\"Generate embeddings for multiple texts.\"\"\"\n",
" return [self.embed_text(text) for text in texts]\n",
"\n",
"\n",
"class SQLiteVectorStore:\n",
" \"\"\"Vector store using SQLite for storing and retrieving document embeddings.\"\"\"\n",
" \n",
" def __init__(self, db_path: str = \"vector_store.db\"):\n",
" self.db_path = db_path\n",
" self.conn = sqlite3.connect(db_path, check_same_thread=False)\n",
" self._create_table()\n",
" \n",
" def _create_table(self):\n",
" \"\"\"Create the documents table if it doesn't exist.\"\"\"\n",
" cursor = self.conn.cursor()\n",
" cursor.execute(\"\"\"\n",
" CREATE TABLE IF NOT EXISTS documents (\n",
" id INTEGER PRIMARY KEY AUTOINCREMENT,\n",
" content TEXT NOT NULL,\n",
" embedding TEXT NOT NULL,\n",
" metadata TEXT,\n",
" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n",
" )\n",
" \"\"\")\n",
" self.conn.commit()\n",
" \n",
" def add_documents(self, texts: List[str], embeddings: List[List[float]], \n",
" metadatas: List[Dict] = None):\n",
" \"\"\"Add documents with their embeddings to the store.\"\"\"\n",
" cursor = self.conn.cursor()\n",
" if metadatas is None:\n",
" metadatas = [{}] * len(texts)\n",
" \n",
" for text, embedding, metadata in zip(texts, embeddings, metadatas):\n",
" cursor.execute(\"\"\"\n",
" INSERT INTO documents (content, embedding, metadata)\n",
" VALUES (?, ?, ?)\n",
" \"\"\", (text, json.dumps(embedding), json.dumps(metadata)))\n",
" \n",
" self.conn.commit()\n",
" \n",
" def cosine_similarity(self, vec1: np.ndarray, vec2: np.ndarray) -> float:\n",
" \"\"\"Calculate cosine similarity between two vectors.\"\"\"\n",
" return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))\n",
" \n",
" def similarity_search(self, query_embedding: List[float], k: int = 3) -> List[Tuple[str, float, Dict]]:\n",
" \"\"\"Search for the k most similar documents.\"\"\"\n",
" cursor = self.conn.cursor()\n",
" cursor.execute(\"SELECT content, embedding, metadata FROM documents\")\n",
" results = cursor.fetchall()\n",
" \n",
" query_vec = np.array(query_embedding)\n",
" similarities = []\n",
" \n",
" for content, embedding_json, metadata_json in results:\n",
" doc_vec = np.array(json.loads(embedding_json))\n",
" similarity = self.cosine_similarity(query_vec, doc_vec)\n",
" similarities.append((content, similarity, json.loads(metadata_json)))\n",
" \n",
" # Sort by similarity (highest first) and return top k\n",
" similarities.sort(key=lambda x: x[1], reverse=True)\n",
" return similarities[:k]\n",
" \n",
" def clear_all(self):\n",
" \"\"\"Clear all documents from the store.\"\"\"\n",
" cursor = self.conn.cursor()\n",
" cursor.execute(\"DELETE FROM documents\")\n",
" self.conn.commit()\n",
" \n",
" def get_document_count(self) -> int:\n",
" \"\"\"Get the total number of documents in the store.\"\"\"\n",
" cursor = self.conn.cursor()\n",
" cursor.execute(\"SELECT COUNT(*) FROM documents\")\n",
" return cursor.fetchone()[0]\n",
"\n",
"\n",
"class OllamaLLM:\n",
" \"\"\"Interact with Ollama LLM for text generation.\"\"\"\n",
" \n",
" def __init__(self, model: str = llm_model, base_url: str = \"http://localhost:11434\"):\n",
" self.model = model\n",
" self.base_url = base_url\n",
" \n",
" def generate(self, prompt: str, stream: bool = False) -> str:\n",
" \"\"\"Generate text from the LLM.\"\"\"\n",
" response = requests.post(\n",
" f\"{self.base_url}/api/generate\",\n",
" json={\"model\": self.model, \"prompt\": prompt, \"stream\": stream}\n",
" )\n",
" \n",
" if response.status_code == 200:\n",
" return response.json()[\"response\"]\n",
" else:\n",
" raise Exception(f\"Error generating response: {response.text}\")\n",
"\n",
"\n",
"class RAGSystem:\n",
" \"\"\"RAG system combining vector store, embeddings, and LLM.\"\"\"\n",
" \n",
" def __init__(self, embedding_model: str = embedding_model, \n",
" llm_model: str = llm_model,\n",
" db_path: str = \"vector_store.db\"):\n",
" self.embeddings = OllamaEmbeddings(model=embedding_model)\n",
" self.vector_store = SQLiteVectorStore(db_path=db_path)\n",
" self.llm = OllamaLLM(model=llm_model)\n",
" \n",
" def add_documents(self, documents: List[Dict[str, str]]):\n",
" \"\"\"\n",
" Add documents to the RAG system.\n",
" documents: List of dicts with 'content' and optional 'metadata'\n",
" \"\"\"\n",
" texts = [doc['content'] for doc in documents]\n",
" metadatas = [doc.get('metadata', {}) for doc in documents]\n",
" \n",
" print(f\"Generating embeddings for {len(texts)} documents...\")\n",
" embeddings = self.embeddings.embed_documents(texts)\n",
" \n",
" print(\"Storing documents in vector store...\")\n",
" self.vector_store.add_documents(texts, embeddings, metadatas)\n",
" print(f\"Successfully added {len(texts)} documents!\")\n",
" \n",
" def query(self, question: str, k: int = 3) -> str:\n",
" \"\"\"Query the RAG system with a question.\"\"\"\n",
" # Generate embedding for the query\n",
" query_embedding = self.embeddings.embed_text(question)\n",
" \n",
" # Retrieve relevant documents\n",
" results = self.vector_store.similarity_search(query_embedding, k=k)\n",
" \n",
" if not results:\n",
" return \"I don't have any information to answer this question.\"\n",
" \n",
" # Build context from retrieved documents\n",
" context = \"\\n\\n\".join([\n",
" f\"Document {i+1} (Relevance: {score:.2f}):\\n{content}\"\n",
" for i, (content, score, _) in enumerate(results)\n",
" ])\n",
" \n",
" # Create prompt for LLM\n",
" prompt = f\"\"\"You are a helpful assistant answering questions based on the provided context.\n",
" Use the following context to answer the question. If you cannot answer the question based on the context, say so.\n",
" \n",
" Context:\n",
" {context}\n",
" \n",
" Question: {question}\n",
" \n",
" Answer:\"\"\"\n",
" \n",
" # Generate response\n",
" response = self.llm.generate(prompt)\n",
" return response\n",
" \n",
" def get_stats(self) -> str:\n",
" \"\"\"Get statistics about the RAG system.\"\"\"\n",
" doc_count = self.vector_store.get_document_count()\n",
" return f\"Total documents in database: {doc_count}\"\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "37cbaa24-6e17-4712-8c90-429264b9b82e",
"metadata": {},
"outputs": [],
"source": [
"def load_documents() -> List[Dict[str, str]]:\n",
" \"\"\"\n",
" Read all files from specified folders and format them for RAG system. \n",
" Args:\n",
" folders: List of folder paths to read files from\n",
" Returns:\n",
" List of dictionaries with 'content' and 'metadata' keys\n",
" \"\"\"\n",
" from pathlib import Path\n",
" \n",
" documents = []\n",
" supported_extensions = {'.md'}\n",
" \n",
" for folder in folders:\n",
" folder_path = Path(folder)\n",
" \n",
" if not folder_path.exists():\n",
" print(f\"Warning: Folder '{folder}' does not exist. Skipping...\")\n",
" continue\n",
" \n",
" if not folder_path.is_dir():\n",
" print(f\"Warning: '{folder}' is not a directory. Skipping...\")\n",
" continue\n",
" \n",
" folder_name = folder_path.name\n",
" \n",
" # Get all files in the folder\n",
" files = [f for f in folder_path.iterdir() if f.is_file()]\n",
" \n",
" for file_path in files:\n",
" # Check if file extension is supported\n",
" if file_path.suffix.lower() not in supported_extensions:\n",
" print(f\"Skipping unsupported file type: {file_path.name}\")\n",
" continue\n",
" \n",
" try:\n",
" # Read file content\n",
" with open(file_path, 'r', encoding='utf-8') as f:\n",
" content = f.read()\n",
" \n",
" # Create document dictionary\n",
" document = {\n",
" 'metadata': {\n",
" 'type': folder_name,\n",
" 'name': file_path.name,\n",
" 'datalen': len(content)\n",
" },\n",
" 'content': content,\n",
" }\n",
" \n",
" documents.append(document)\n",
" print(f\"✓ Loaded: {file_path.name} from folder '{folder_name}'\")\n",
" \n",
" except Exception as e:\n",
" print(f\"Error reading file {file_path.name}: {str(e)}\")\n",
" continue\n",
" \n",
" print(f\"\\nTotal documents loaded: {len(documents)}\")\n",
" return documents\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d257bd84-fd7b-4a64-bc5b-148b30b00aa3",
"metadata": {},
"outputs": [],
"source": [
"def create_gradio_interface(rag_system: RAGSystem):\n",
" \"\"\"Create Gradio chat interface for the RAG system.\"\"\"\n",
" \n",
" def chat_fn(message, history):\n",
" \"\"\"Process chat messages.\"\"\"\n",
" try:\n",
" response = rag_system.query(message, k=RagDist_k)\n",
" return response\n",
" except Exception as e:\n",
" return f\"Error: {str(e)}\\n\\nMake sure Ollama is running with the required models installed.\"\n",
" \n",
" def load_data():\n",
" \"\"\"Load sample documents into the system.\"\"\"\n",
" try:\n",
" documents = load_documents()\n",
" rag_system.add_documents(documents)\n",
" stats = rag_system.get_stats()\n",
" return f\"✅ Sample documents loaded successfully!\\n{stats}\"\n",
" except Exception as e:\n",
" return f\"❌ Error loading documents: {str(e)}\"\n",
" \n",
" def get_stats():\n",
" \"\"\"Get system statistics.\"\"\"\n",
" return rag_system.get_stats()\n",
" \n",
" with gr.Blocks(title=\"RAG System - Company Knowledge Base\", theme=gr.themes.Soft()) as demo:\n",
" gr.Markdown(\"# 🤖 RAG System - Company Knowledge Base\")\n",
" gr.Markdown(\"Ask questions about company information, contracts, employees, and products.\")\n",
" \n",
" with gr.Row():\n",
" with gr.Column(scale=3):\n",
" chatbot = gr.ChatInterface(\n",
" fn=chat_fn,\n",
" examples=[\n",
" \"Who is the CTO of the company?\",\n",
" \"Who is the CEO of the company?\",\n",
" \"What products does the company offer?\",\n",
" ],\n",
" title=\"\",\n",
" description=\"💬 Chat with the company knowledge base\"\n",
" )\n",
" \n",
" with gr.Column(scale=1):\n",
" gr.Markdown(\"### 📊 System Controls\")\n",
" load_btn = gr.Button(\"📥 Load Documents\", variant=\"primary\")\n",
" stats_btn = gr.Button(\"📈 Get Statistics\")\n",
" output_box = gr.Textbox(label=\"System Output\", lines=5)\n",
" \n",
" load_btn.click(fn=load_data, outputs=output_box)\n",
" stats_btn.click(fn=get_stats, outputs=output_box)\n",
" \n",
" gr.Markdown(f\"\"\"\n",
" ### 📝 Instructions:\n",
" 1. Make sure Ollama is running\n",
" 2. Click \"Load Sample Documents\" \n",
" 3. Start asking questions!\n",
" \n",
" ### 🔧 Required Models:\n",
" - `ollama pull {embedding_model}`\n",
" - `ollama pull {llm_model}`\n",
" \"\"\")\n",
" \n",
" return demo\n",
"\n",
"\n",
"def main():\n",
" \"\"\"Main function to run the RAG system.\"\"\"\n",
" print(\"=\" * 60)\n",
" print(\"RAG System with Ollama and SQLite\")\n",
" print(\"=\" * 60)\n",
" \n",
" # Initialize RAG system\n",
" print(\"\\nInitializing RAG system...\")\n",
" rag_system = RAGSystem(\n",
" embedding_model=embedding_model,\n",
" llm_model=llm_model,\n",
" db_path=\"vector_store.db\"\n",
" )\n",
" \n",
" print(\"\\n⚠ Make sure Ollama is running and you have the required models:\")\n",
" print(f\" - ollama pull {embedding_model}\")\n",
" print(f\" - ollama pull {llm_model}\")\n",
" print(\"\\nStarting Gradio interface...\")\n",
" \n",
" # Create and launch Gradio interface\n",
" demo = create_gradio_interface(rag_system)\n",
" demo.launch(share=False)\n",
"\n",
"\n",
"main()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "01b4ff0e-36a5-43b5-8ecf-59e42a18a908",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"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.12.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,259 @@
# 🧠 KnowledgeHub - Personal Knowledge Management & Research Assistant
An elegant, fully local AI-powered knowledge management system that helps you organize, search, and understand your documents using state-of-the-art LLM technology.
## ✨ Features
### 🎯 Core Capabilities
- **📤 Document Ingestion**: Upload PDF, DOCX, TXT, MD, and HTML files
- **❓ Intelligent Q&A**: Ask questions and get answers from your documents using RAG
- **📝 Smart Summarization**: Generate concise summaries with key points
- **🔗 Connection Discovery**: Find relationships between documents
- **💾 Multi-format Export**: Export as Markdown, HTML, or plain text
- **📊 Statistics Dashboard**: Track your knowledge base growth
### 🔒 Privacy-First
- **100% Local Processing**: All data stays on your machine
- **No Cloud Dependencies**: Uses Ollama for local LLM inference
- **Open Source**: Full transparency and control
### ⚡ Technology Stack
- **LLM**: Ollama with Llama 3.2 (3B) or Llama 3.1 (8B)
- **Embeddings**: sentence-transformers (all-MiniLM-L6-v2)
- **Vector Database**: ChromaDB
- **UI**: Gradio
- **Document Processing**: pypdf, python-docx, beautifulsoup4
## 🚀 Quick Start
### Prerequisites
1. **Python 3.8+** installed
2. **Ollama** installed and running
#### Installing Ollama
**macOS/Linux:**
```bash
curl -fsSL https://ollama.com/install.sh | sh
```
**Windows:**
Download from [ollama.com/download](https://ollama.com/download)
### Installation
1. **Clone or download this repository**
2. **Install Python dependencies:**
```bash
pip install -r requirements.txt
```
3. **Pull Llama model using Ollama:**
```bash
# For faster inference (recommended for most users)
ollama pull llama3.2
# OR for better quality (requires more RAM)
ollama pull llama3.1
```
4. **Start Ollama server** (if not already running):
```bash
ollama serve
```
5. **Launch KnowledgeHub:**
```bash
python app.py
```
The application will open in your browser at `http://127.0.0.1:7860`
## 📖 Usage Guide
### 1. Upload Documents
- Go to the "Upload Documents" tab
- Select a file (PDF, DOCX, TXT, MD, or HTML)
- Click "Upload & Process"
- The document will be chunked and stored in your local vector database
### 2. Ask Questions
- Go to the "Ask Questions" tab
- Type your question in natural language
- Adjust the number of sources to retrieve (default: 5)
- Click "Ask" to get an AI-generated answer with sources
### 3. Summarize Documents
- Go to the "Summarize" tab
- Select a document from the dropdown
- Click "Generate Summary"
- Get a concise summary with key points
### 4. Find Connections
- Go to the "Find Connections" tab
- Select a document to analyze
- Adjust how many related documents to find
- See documents that are semantically similar
### 5. Export Knowledge
- Go to the "Export" tab
- Choose your format (Markdown, HTML, or Text)
- Click "Export" to download your knowledge base
### 6. View Statistics
- Go to the "Statistics" tab
- See overview of your knowledge base
- Track total documents, chunks, and characters
## 🏗️ Architecture
```
KnowledgeHub/
├── agents/ # Specialized AI agents
│ ├── base_agent.py # Base class for all agents
│ ├── ingestion_agent.py # Document processing
│ ├── question_agent.py # RAG-based Q&A
│ ├── summary_agent.py # Summarization
│ ├── connection_agent.py # Finding relationships
│ └── export_agent.py # Exporting data
├── models/ # Data models
│ ├── document.py # Document structures
│ └── knowledge_graph.py # Graph structures
├── utils/ # Utilities
│ ├── ollama_client.py # Ollama API wrapper
│ ├── embeddings.py # Embedding generation
│ └── document_parser.py # File parsing
├── vectorstore/ # ChromaDB storage (auto-created)
├── temp_uploads/ # Temporary file storage (auto-created)
├── app.py # Main Gradio application
└── requirements.txt # Python dependencies
```
## 🎯 Multi-Agent Framework
KnowledgeHub uses a sophisticated multi-agent architecture:
1. **Ingestion Agent**: Parses documents, creates chunks, generates embeddings
2. **Question Agent**: Retrieves relevant context and answers questions
3. **Summary Agent**: Creates concise summaries and extracts key points
4. **Connection Agent**: Finds semantic relationships between documents
5. **Export Agent**: Formats and exports knowledge in multiple formats
Each agent is independent, reusable, and focused on a specific task, following best practices in agentic AI development.
## ⚙️ Configuration
### Changing Models
Edit `app.py` to use different models:
```python
# For Llama 3.1 8B (better quality, more RAM)
self.llm_client = OllamaClient(model="llama3.1")
# For Llama 3.2 3B (faster, less RAM)
self.llm_client = OllamaClient(model="llama3.2")
```
### Adjusting Chunk Size
Edit `agents/ingestion_agent.py`:
```python
self.parser = DocumentParser(
chunk_size=1000, # Characters per chunk
chunk_overlap=200 # Overlap between chunks
)
```
### Changing Embedding Model
Edit `app.py`:
```python
self.embedding_model = EmbeddingModel(
model_name="sentence-transformers/all-MiniLM-L6-v2"
)
```
## 🔧 Troubleshooting
### "Cannot connect to Ollama"
- Ensure Ollama is installed: `ollama --version`
- Start the Ollama service: `ollama serve`
- Verify the model is pulled: `ollama list`
### "Module not found" errors
- Ensure all dependencies are installed: `pip install -r requirements.txt`
- Try upgrading pip: `pip install --upgrade pip`
### "Out of memory" errors
- Use Llama 3.2 (3B) instead of Llama 3.1 (8B)
- Reduce chunk_size in document parser
- Process fewer documents at once
### Slow response times
- Ensure you're using a CUDA-enabled GPU (if available)
- Reduce the number of retrieved chunks (top_k parameter)
- Use a smaller model (llama3.2)
## 🎓 Learning Resources
This project demonstrates key concepts in LLM engineering:
- **RAG (Retrieval Augmented Generation)**: Combining retrieval with generation
- **Vector Databases**: Using ChromaDB for semantic search
- **Multi-Agent Systems**: Specialized agents working together
- **Embeddings**: Semantic representation of text
- **Local LLM Deployment**: Using Ollama for privacy-focused AI
## 📊 Performance
**Hardware Requirements:**
- Minimum: 8GB RAM, CPU
- Recommended: 16GB RAM, GPU (NVIDIA with CUDA)
- Optimal: 32GB RAM, GPU (RTX 3060 or better)
**Processing Speed** (Llama 3.2 on M1 Mac):
- Document ingestion: ~2-5 seconds per page
- Question answering: ~5-15 seconds
- Summarization: ~10-20 seconds
## 🤝 Contributing
This is a learning project showcasing LLM engineering principles. Feel free to:
- Experiment with different models
- Add new agents for specialized tasks
- Improve the UI
- Optimize performance
## 📄 License
This project is open source and available for educational purposes.
## 🙏 Acknowledgments
Built with:
- [Ollama](https://ollama.com/) - Local LLM runtime
- [Gradio](https://gradio.app/) - UI framework
- [ChromaDB](https://www.trychroma.com/) - Vector database
- [Sentence Transformers](https://www.sbert.net/) - Embeddings
- [Llama](https://ai.meta.com/llama/) - Meta's open source LLMs
## 🎯 Next Steps
Potential enhancements:
1. Add support for images and diagrams
2. Implement multi-document chat history
3. Build a visual knowledge graph
4. Add collaborative features
5. Create mobile app interface
6. Implement advanced filters and search
7. Add citation tracking
8. Create automated study guides
---
**Made with ❤️ for the LLM Engineering Community**

View File

@@ -0,0 +1,18 @@
"""
KnowledgeHub Agents
"""
from .base_agent import BaseAgent
from .ingestion_agent import IngestionAgent
from .question_agent import QuestionAgent
from .summary_agent import SummaryAgent
from .connection_agent import ConnectionAgent
from .export_agent import ExportAgent
__all__ = [
'BaseAgent',
'IngestionAgent',
'QuestionAgent',
'SummaryAgent',
'ConnectionAgent',
'ExportAgent'
]

View File

@@ -0,0 +1,91 @@
"""
Base Agent class - Foundation for all specialized agents
"""
from abc import ABC, abstractmethod
import logging
from typing import Optional, Dict, Any
from utils.ollama_client import OllamaClient
logger = logging.getLogger(__name__)
class BaseAgent(ABC):
"""Abstract base class for all agents"""
def __init__(self, name: str, llm_client: Optional[OllamaClient] = None,
model: str = "llama3.2"):
"""
Initialize base agent
Args:
name: Agent name for logging
llm_client: Shared Ollama client (creates new one if None)
model: Ollama model to use
"""
self.name = name
self.model = model
# Use shared client or create new one
if llm_client is None:
self.llm = OllamaClient(model=model)
logger.info(f"{self.name} initialized with new LLM client (model: {model})")
else:
self.llm = llm_client
logger.info(f"{self.name} initialized with shared LLM client (model: {model})")
def generate(self, prompt: str, system: Optional[str] = None,
temperature: float = 0.7, max_tokens: int = 2048) -> str:
"""
Generate text using the LLM
Args:
prompt: User prompt
system: System message (optional)
temperature: Sampling temperature
max_tokens: Maximum tokens to generate
Returns:
Generated text
"""
logger.info(f"{self.name} generating response")
response = self.llm.generate(
prompt=prompt,
system=system,
temperature=temperature,
max_tokens=max_tokens
)
logger.debug(f"{self.name} generated {len(response)} characters")
return response
def chat(self, messages: list, temperature: float = 0.7,
max_tokens: int = 2048) -> str:
"""
Chat completion with message history
Args:
messages: List of message dicts with 'role' and 'content'
temperature: Sampling temperature
max_tokens: Maximum tokens to generate
Returns:
Generated text
"""
logger.info(f"{self.name} processing chat with {len(messages)} messages")
response = self.llm.chat(
messages=messages,
temperature=temperature,
max_tokens=max_tokens
)
logger.debug(f"{self.name} generated {len(response)} characters")
return response
@abstractmethod
def process(self, *args, **kwargs) -> Any:
"""
Main processing method - must be implemented by subclasses
Each agent implements its specialized logic here
"""
pass
def __str__(self):
return f"{self.name} (model: {self.model})"

View File

@@ -0,0 +1,289 @@
"""
Connection Agent - Finds relationships and connections between documents
"""
import logging
from typing import List, Dict, Tuple
from agents.base_agent import BaseAgent
from models.knowledge_graph import KnowledgeNode, KnowledgeEdge, KnowledgeGraph
from utils.embeddings import EmbeddingModel
import chromadb
import numpy as np
logger = logging.getLogger(__name__)
class ConnectionAgent(BaseAgent):
"""Agent that discovers connections between documents and concepts"""
def __init__(self, collection: chromadb.Collection,
embedding_model: EmbeddingModel,
llm_client=None, model: str = "llama3.2"):
"""
Initialize connection agent
Args:
collection: ChromaDB collection with documents
embedding_model: Model for computing similarities
llm_client: Optional shared LLM client
model: Ollama model name
"""
super().__init__(name="ConnectionAgent", llm_client=llm_client, model=model)
self.collection = collection
self.embedding_model = embedding_model
logger.info(f"{self.name} initialized")
def process(self, document_id: str = None, query: str = None,
top_k: int = 5) -> Dict:
"""
Find documents related to a document or query
Args:
document_id: ID of reference document
query: Search query (used if document_id not provided)
top_k: Number of related documents to find
Returns:
Dictionary with related documents and connections
"""
if document_id:
logger.info(f"{self.name} finding connections for document: {document_id}")
return self._find_related_to_document(document_id, top_k)
elif query:
logger.info(f"{self.name} finding connections for query: {query[:100]}")
return self._find_related_to_query(query, top_k)
else:
return {'related': [], 'error': 'No document_id or query provided'}
def _find_related_to_document(self, document_id: str, top_k: int) -> Dict:
"""Find documents related to a specific document"""
try:
# Get chunks from the document
results = self.collection.get(
where={"document_id": document_id},
include=['embeddings', 'documents', 'metadatas']
)
if not results['ids']:
return {'related': [], 'error': 'Document not found'}
# Use the first chunk's embedding as representative
query_embedding = results['embeddings'][0]
document_name = results['metadatas'][0].get('filename', 'Unknown')
# Search for similar chunks from OTHER documents
search_results = self.collection.query(
query_embeddings=[query_embedding],
n_results=top_k * 3, # Get more to filter out same document
include=['documents', 'metadatas', 'distances']
)
# Filter out chunks from the same document
related = []
seen_docs = set([document_id])
if search_results['ids']:
for i in range(len(search_results['ids'][0])):
related_doc_id = search_results['metadatas'][0][i].get('document_id')
if related_doc_id not in seen_docs:
seen_docs.add(related_doc_id)
similarity = 1.0 - search_results['distances'][0][i]
related.append({
'document_id': related_doc_id,
'document_name': search_results['metadatas'][0][i].get('filename', 'Unknown'),
'similarity': float(similarity),
'preview': search_results['documents'][0][i][:150] + "..."
})
if len(related) >= top_k:
break
return {
'source_document': document_name,
'source_id': document_id,
'related': related,
'num_related': len(related)
}
except Exception as e:
logger.error(f"Error finding related documents: {e}")
return {'related': [], 'error': str(e)}
def _find_related_to_query(self, query: str, top_k: int) -> Dict:
"""Find documents related to a query"""
try:
# Generate query embedding
query_embedding = self.embedding_model.embed_query(query)
# Search
results = self.collection.query(
query_embeddings=[query_embedding],
n_results=top_k * 2, # Get more to deduplicate by document
include=['documents', 'metadatas', 'distances']
)
# Deduplicate by document
related = []
seen_docs = set()
if results['ids']:
for i in range(len(results['ids'][0])):
doc_id = results['metadatas'][0][i].get('document_id')
if doc_id not in seen_docs:
seen_docs.add(doc_id)
similarity = 1.0 - results['distances'][0][i]
related.append({
'document_id': doc_id,
'document_name': results['metadatas'][0][i].get('filename', 'Unknown'),
'similarity': float(similarity),
'preview': results['documents'][0][i][:150] + "..."
})
if len(related) >= top_k:
break
return {
'query': query,
'related': related,
'num_related': len(related)
}
except Exception as e:
logger.error(f"Error finding related documents: {e}")
return {'related': [], 'error': str(e)}
def build_knowledge_graph(self, similarity_threshold: float = 0.7) -> KnowledgeGraph:
"""
Build a knowledge graph showing document relationships
Args:
similarity_threshold: Minimum similarity to create an edge
Returns:
KnowledgeGraph object
"""
logger.info(f"{self.name} building knowledge graph")
graph = KnowledgeGraph()
try:
# Get all documents
all_results = self.collection.get(
include=['embeddings', 'metadatas']
)
if not all_results['ids']:
return graph
# Group by document
documents = {}
for i, metadata in enumerate(all_results['metadatas']):
doc_id = metadata.get('document_id')
if doc_id not in documents:
documents[doc_id] = {
'name': metadata.get('filename', 'Unknown'),
'embedding': all_results['embeddings'][i]
}
# Create nodes
for doc_id, doc_data in documents.items():
node = KnowledgeNode(
id=doc_id,
name=doc_data['name'],
node_type='document',
description=f"Document: {doc_data['name']}"
)
graph.add_node(node)
# Create edges based on similarity
doc_ids = list(documents.keys())
for i, doc_id1 in enumerate(doc_ids):
emb1 = np.array(documents[doc_id1]['embedding'])
for doc_id2 in doc_ids[i+1:]:
emb2 = np.array(documents[doc_id2]['embedding'])
# Calculate similarity
similarity = np.dot(emb1, emb2) / (np.linalg.norm(emb1) * np.linalg.norm(emb2))
if similarity >= similarity_threshold:
edge = KnowledgeEdge(
source_id=doc_id1,
target_id=doc_id2,
relationship='similar_to',
weight=float(similarity)
)
graph.add_edge(edge)
logger.info(f"{self.name} built graph with {len(graph.nodes)} nodes and {len(graph.edges)} edges")
return graph
except Exception as e:
logger.error(f"Error building knowledge graph: {e}")
return graph
def explain_connection(self, doc_id1: str, doc_id2: str) -> str:
"""
Use LLM to explain why two documents are related
Args:
doc_id1: First document ID
doc_id2: Second document ID
Returns:
Explanation text
"""
try:
# Get sample chunks from each document
results1 = self.collection.get(
where={"document_id": doc_id1},
limit=2,
include=['documents', 'metadatas']
)
results2 = self.collection.get(
where={"document_id": doc_id2},
limit=2,
include=['documents', 'metadatas']
)
if not results1['ids'] or not results2['ids']:
return "Could not retrieve documents"
doc1_name = results1['metadatas'][0].get('filename', 'Document 1')
doc2_name = results2['metadatas'][0].get('filename', 'Document 2')
doc1_text = " ".join(results1['documents'][:2])[:1000]
doc2_text = " ".join(results2['documents'][:2])[:1000]
system_prompt = """You analyze documents and explain their relationships.
Provide a brief, clear explanation of how two documents are related."""
user_prompt = f"""Analyze these two documents and explain how they are related:
Document 1 ({doc1_name}):
{doc1_text}
Document 2 ({doc2_name}):
{doc2_text}
How are these documents related? Provide a concise explanation:"""
explanation = self.generate(
prompt=user_prompt,
system=system_prompt,
temperature=0.3,
max_tokens=256
)
return explanation
except Exception as e:
logger.error(f"Error explaining connection: {e}")
return f"Error: {str(e)}"

View File

@@ -0,0 +1,233 @@
"""
Export Agent - Generates formatted reports and exports
"""
import logging
from typing import List, Dict
from datetime import datetime
from agents.base_agent import BaseAgent
from models.document import Summary
logger = logging.getLogger(__name__)
class ExportAgent(BaseAgent):
"""Agent that exports summaries and reports in various formats"""
def __init__(self, llm_client=None, model: str = "llama3.2"):
"""
Initialize export agent
Args:
llm_client: Optional shared LLM client
model: Ollama model name
"""
super().__init__(name="ExportAgent", llm_client=llm_client, model=model)
logger.info(f"{self.name} initialized")
def process(self, content: Dict, format: str = "markdown") -> str:
"""
Export content in specified format
Args:
content: Content dictionary to export
format: Export format ('markdown', 'text', 'html')
Returns:
Formatted content string
"""
logger.info(f"{self.name} exporting as {format}")
if format == "markdown":
return self._export_markdown(content)
elif format == "text":
return self._export_text(content)
elif format == "html":
return self._export_html(content)
else:
return str(content)
def _export_markdown(self, content: Dict) -> str:
"""Export as Markdown"""
md = []
md.append(f"# Knowledge Report")
md.append(f"\n*Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}*\n")
if 'title' in content:
md.append(f"## {content['title']}\n")
if 'summary' in content:
md.append(f"### Summary\n")
md.append(f"{content['summary']}\n")
if 'key_points' in content and content['key_points']:
md.append(f"### Key Points\n")
for point in content['key_points']:
md.append(f"- {point}")
md.append("")
if 'sections' in content:
for section in content['sections']:
md.append(f"### {section['title']}\n")
md.append(f"{section['content']}\n")
if 'sources' in content and content['sources']:
md.append(f"### Sources\n")
for i, source in enumerate(content['sources'], 1):
md.append(f"{i}. {source}")
md.append("")
return "\n".join(md)
def _export_text(self, content: Dict) -> str:
"""Export as plain text"""
lines = []
lines.append("=" * 60)
lines.append("KNOWLEDGE REPORT")
lines.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
lines.append("=" * 60)
lines.append("")
if 'title' in content:
lines.append(content['title'])
lines.append("-" * len(content['title']))
lines.append("")
if 'summary' in content:
lines.append("SUMMARY:")
lines.append(content['summary'])
lines.append("")
if 'key_points' in content and content['key_points']:
lines.append("KEY POINTS:")
for i, point in enumerate(content['key_points'], 1):
lines.append(f" {i}. {point}")
lines.append("")
if 'sections' in content:
for section in content['sections']:
lines.append(section['title'].upper())
lines.append("-" * 40)
lines.append(section['content'])
lines.append("")
if 'sources' in content and content['sources']:
lines.append("SOURCES:")
for i, source in enumerate(content['sources'], 1):
lines.append(f" {i}. {source}")
lines.append("")
lines.append("=" * 60)
return "\n".join(lines)
def _export_html(self, content: Dict) -> str:
"""Export as HTML"""
html = []
html.append("<!DOCTYPE html>")
html.append("<html>")
html.append("<head>")
html.append(" <meta charset='utf-8'>")
html.append(" <title>Knowledge Report</title>")
html.append(" <style>")
html.append(" body { font-family: Arial, sans-serif; max-width: 800px; margin: 40px auto; padding: 20px; }")
html.append(" h1 { color: #333; border-bottom: 3px solid #007bff; padding-bottom: 10px; }")
html.append(" h2 { color: #555; margin-top: 30px; }")
html.append(" .meta { color: #888; font-style: italic; }")
html.append(" .key-points { background: #f8f9fa; padding: 15px; border-left: 4px solid #007bff; }")
html.append(" .source { color: #666; font-size: 0.9em; }")
html.append(" </style>")
html.append("</head>")
html.append("<body>")
html.append(" <h1>Knowledge Report</h1>")
html.append(f" <p class='meta'>Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}</p>")
if 'title' in content:
html.append(f" <h2>{content['title']}</h2>")
if 'summary' in content:
html.append(f" <h3>Summary</h3>")
html.append(f" <p>{content['summary']}</p>")
if 'key_points' in content and content['key_points']:
html.append(" <h3>Key Points</h3>")
html.append(" <div class='key-points'>")
html.append(" <ul>")
for point in content['key_points']:
html.append(f" <li>{point}</li>")
html.append(" </ul>")
html.append(" </div>")
if 'sections' in content:
for section in content['sections']:
html.append(f" <h3>{section['title']}</h3>")
html.append(f" <p>{section['content']}</p>")
if 'sources' in content and content['sources']:
html.append(" <h3>Sources</h3>")
html.append(" <ol class='source'>")
for source in content['sources']:
html.append(f" <li>{source}</li>")
html.append(" </ol>")
html.append("</body>")
html.append("</html>")
return "\n".join(html)
def create_study_guide(self, summaries: List[Summary]) -> str:
"""
Create a study guide from multiple summaries
Args:
summaries: List of Summary objects
Returns:
Formatted study guide
"""
logger.info(f"{self.name} creating study guide from {len(summaries)} summaries")
# Compile all content
all_summaries = "\n\n".join([
f"{s.document_name}:\n{s.summary_text}"
for s in summaries
])
all_key_points = []
for s in summaries:
all_key_points.extend(s.key_points)
# Use LLM to create cohesive study guide
system_prompt = """You create excellent study guides that synthesize information from multiple sources.
Create a well-organized study guide with clear sections, key concepts, and important points."""
user_prompt = f"""Create a comprehensive study guide based on these document summaries:
{all_summaries}
Create a well-structured study guide with:
1. An overview
2. Key concepts
3. Important details
4. Study tips
Study Guide:"""
study_guide = self.generate(
prompt=user_prompt,
system=system_prompt,
temperature=0.5,
max_tokens=2048
)
# Format as markdown
content = {
'title': 'Study Guide',
'sections': [
{'title': 'Overview', 'content': study_guide},
{'title': 'Key Points from All Documents', 'content': '\n'.join([f"{p}" for p in all_key_points[:15]])}
],
'sources': [s.document_name for s in summaries]
}
return self._export_markdown(content)

View File

@@ -0,0 +1,157 @@
"""
Ingestion Agent - Processes and stores documents in the vector database
"""
import logging
from typing import Dict, List
import uuid
from datetime import datetime
from agents.base_agent import BaseAgent
from models.document import Document, DocumentChunk
from utils.document_parser import DocumentParser
from utils.embeddings import EmbeddingModel
import chromadb
logger = logging.getLogger(__name__)
class IngestionAgent(BaseAgent):
"""Agent responsible for ingesting and storing documents"""
def __init__(self, collection: chromadb.Collection,
embedding_model: EmbeddingModel,
llm_client=None, model: str = "llama3.2"):
"""
Initialize ingestion agent
Args:
collection: ChromaDB collection for storage
embedding_model: Model for generating embeddings
llm_client: Optional shared LLM client
model: Ollama model name
"""
super().__init__(name="IngestionAgent", llm_client=llm_client, model=model)
self.collection = collection
self.embedding_model = embedding_model
self.parser = DocumentParser(chunk_size=1000, chunk_overlap=200)
logger.info(f"{self.name} ready with ChromaDB collection")
def process(self, file_path: str) -> Document:
"""
Process and ingest a document
Args:
file_path: Path to the document file
Returns:
Document object with metadata
"""
logger.info(f"{self.name} processing: {file_path}")
# Parse the document
parsed = self.parser.parse_file(file_path)
# Generate document ID
doc_id = str(uuid.uuid4())
# Create document chunks
chunks = []
chunk_texts = []
chunk_ids = []
chunk_metadatas = []
for i, chunk_text in enumerate(parsed['chunks']):
chunk_id = f"{doc_id}_chunk_{i}"
chunk = DocumentChunk(
id=chunk_id,
document_id=doc_id,
content=chunk_text,
chunk_index=i,
metadata={
'filename': parsed['filename'],
'extension': parsed['extension'],
'total_chunks': len(parsed['chunks'])
}
)
chunks.append(chunk)
chunk_texts.append(chunk_text)
chunk_ids.append(chunk_id)
chunk_metadatas.append({
'document_id': doc_id,
'filename': parsed['filename'],
'chunk_index': i,
'extension': parsed['extension']
})
# Generate embeddings
logger.info(f"{self.name} generating embeddings for {len(chunks)} chunks")
embeddings = self.embedding_model.embed_documents(chunk_texts)
# Store in ChromaDB
logger.info(f"{self.name} storing in ChromaDB")
self.collection.add(
ids=chunk_ids,
documents=chunk_texts,
embeddings=embeddings,
metadatas=chunk_metadatas
)
# Create document object
document = Document(
id=doc_id,
filename=parsed['filename'],
filepath=parsed['filepath'],
content=parsed['text'],
chunks=chunks,
metadata={
'extension': parsed['extension'],
'num_chunks': len(chunks),
'total_chars': parsed['total_chars']
},
created_at=datetime.now()
)
logger.info(f"{self.name} successfully ingested: {document}")
return document
def get_statistics(self) -> Dict:
"""Get statistics about stored documents"""
try:
count = self.collection.count()
return {
'total_chunks': count,
'collection_name': self.collection.name
}
except Exception as e:
logger.error(f"Error getting statistics: {e}")
return {'total_chunks': 0, 'error': str(e)}
def delete_document(self, document_id: str) -> bool:
"""
Delete all chunks of a document
Args:
document_id: ID of document to delete
Returns:
True if successful
"""
try:
# Get all chunk IDs for this document
results = self.collection.get(
where={"document_id": document_id}
)
if results['ids']:
self.collection.delete(ids=results['ids'])
logger.info(f"{self.name} deleted document {document_id}")
return True
return False
except Exception as e:
logger.error(f"Error deleting document: {e}")
return False

View File

@@ -0,0 +1,156 @@
"""
Question Agent - Answers questions using RAG (Retrieval Augmented Generation)
"""
import logging
from typing import List, Dict
from agents.base_agent import BaseAgent
from models.document import SearchResult, DocumentChunk
from utils.embeddings import EmbeddingModel
import chromadb
logger = logging.getLogger(__name__)
class QuestionAgent(BaseAgent):
"""Agent that answers questions using retrieved context"""
def __init__(self, collection: chromadb.Collection,
embedding_model: EmbeddingModel,
llm_client=None, model: str = "llama3.2"):
"""
Initialize question agent
Args:
collection: ChromaDB collection with documents
embedding_model: Model for query embeddings
llm_client: Optional shared LLM client
model: Ollama model name
"""
super().__init__(name="QuestionAgent", llm_client=llm_client, model=model)
self.collection = collection
self.embedding_model = embedding_model
self.top_k = 5 # Number of chunks to retrieve
logger.info(f"{self.name} initialized")
def retrieve(self, query: str, top_k: int = None) -> List[SearchResult]:
"""
Retrieve relevant document chunks for a query
Args:
query: Search query
top_k: Number of results to return (uses self.top_k if None)
Returns:
List of SearchResult objects
"""
if top_k is None:
top_k = self.top_k
logger.info(f"{self.name} retrieving top {top_k} chunks for query")
# Generate query embedding
query_embedding = self.embedding_model.embed_query(query)
# Search ChromaDB
results = self.collection.query(
query_embeddings=[query_embedding],
n_results=top_k
)
# Convert to SearchResult objects
search_results = []
if results['ids'] and len(results['ids']) > 0:
for i in range(len(results['ids'][0])):
chunk = DocumentChunk(
id=results['ids'][0][i],
document_id=results['metadatas'][0][i].get('document_id', ''),
content=results['documents'][0][i],
chunk_index=results['metadatas'][0][i].get('chunk_index', 0),
metadata=results['metadatas'][0][i]
)
result = SearchResult(
chunk=chunk,
score=1.0 - results['distances'][0][i], # Convert distance to similarity
document_id=results['metadatas'][0][i].get('document_id', ''),
document_name=results['metadatas'][0][i].get('filename', 'Unknown')
)
search_results.append(result)
logger.info(f"{self.name} retrieved {len(search_results)} results")
return search_results
def process(self, question: str, top_k: int = None) -> Dict[str, any]:
"""
Answer a question using RAG
Args:
question: User's question
top_k: Number of chunks to retrieve
Returns:
Dictionary with answer and sources
"""
logger.info(f"{self.name} processing question: {question[:100]}...")
# Retrieve relevant chunks
search_results = self.retrieve(question, top_k)
if not search_results:
return {
'answer': "I don't have any relevant information in my knowledge base to answer this question.",
'sources': [],
'context_used': ""
}
# Build context from retrieved chunks
context_parts = []
sources = []
for i, result in enumerate(search_results, 1):
context_parts.append(f"[Source {i}] {result.chunk.content}")
sources.append({
'document': result.document_name,
'score': result.score,
'preview': result.chunk.content[:150] + "..."
})
context = "\n\n".join(context_parts)
# Create prompt for LLM
system_prompt = """You are a helpful research assistant. Answer questions based on the provided context.
Be accurate and cite sources when possible. If the context doesn't contain enough information to answer fully, say so.
Keep your answer concise and relevant."""
user_prompt = f"""Context from my knowledge base:
{context}
Question: {question}
Answer based on the context above. If you reference specific information, mention which source(s) you're using."""
# Generate answer
answer = self.generate(
prompt=user_prompt,
system=system_prompt,
temperature=0.3, # Lower temperature for more factual responses
max_tokens=1024
)
logger.info(f"{self.name} generated answer ({len(answer)} chars)")
return {
'answer': answer,
'sources': sources,
'context_used': context,
'num_sources': len(sources)
}
def set_top_k(self, k: int):
"""Set the number of chunks to retrieve"""
self.top_k = k
logger.info(f"{self.name} top_k set to {k}")

View File

@@ -0,0 +1,181 @@
"""
Summary Agent - Creates summaries and extracts key points from documents
"""
import logging
from typing import Dict, List
from agents.base_agent import BaseAgent
from models.document import Summary
import chromadb
logger = logging.getLogger(__name__)
class SummaryAgent(BaseAgent):
"""Agent that creates summaries of documents"""
def __init__(self, collection: chromadb.Collection,
llm_client=None, model: str = "llama3.2"):
"""
Initialize summary agent
Args:
collection: ChromaDB collection with documents
llm_client: Optional shared LLM client
model: Ollama model name
"""
super().__init__(name="SummaryAgent", llm_client=llm_client, model=model)
self.collection = collection
logger.info(f"{self.name} initialized")
def process(self, document_id: str = None, document_text: str = None,
document_name: str = "Unknown") -> Summary:
"""
Create a summary of a document
Args:
document_id: ID of document in ChromaDB (retrieves chunks if provided)
document_text: Full document text (used if document_id not provided)
document_name: Name of the document
Returns:
Summary object
"""
logger.info(f"{self.name} creating summary for: {document_name}")
# Get document text
if document_id:
text = self._get_document_text(document_id)
if not text:
return Summary(
document_id=document_id,
document_name=document_name,
summary_text="Error: Could not retrieve document",
key_points=[]
)
elif document_text:
text = document_text
else:
return Summary(
document_id="",
document_name=document_name,
summary_text="Error: No document provided",
key_points=[]
)
# Truncate if too long (to fit in context)
max_chars = 8000
if len(text) > max_chars:
logger.warning(f"{self.name} truncating document from {len(text)} to {max_chars} chars")
text = text[:max_chars] + "\n\n[Document truncated...]"
# Generate summary
summary_text = self._generate_summary(text)
# Extract key points
key_points = self._extract_key_points(text)
summary = Summary(
document_id=document_id or "",
document_name=document_name,
summary_text=summary_text,
key_points=key_points
)
logger.info(f"{self.name} completed summary with {len(key_points)} key points")
return summary
def _get_document_text(self, document_id: str) -> str:
"""Retrieve and reconstruct document text from chunks"""
try:
results = self.collection.get(
where={"document_id": document_id}
)
if not results['ids']:
return ""
# Sort by chunk index
chunks_data = list(zip(
results['documents'],
results['metadatas']
))
chunks_data.sort(key=lambda x: x[1].get('chunk_index', 0))
# Combine chunks
text = "\n\n".join([chunk[0] for chunk in chunks_data])
return text
except Exception as e:
logger.error(f"Error retrieving document: {e}")
return ""
def _generate_summary(self, text: str) -> str:
"""Generate a concise summary of the text"""
system_prompt = """You are an expert at creating concise, informative summaries.
Your summaries capture the main ideas and key information in clear, accessible language.
Keep summaries to 3-5 sentences unless the document is very long."""
user_prompt = f"""Please create a concise summary of the following document:
{text}
Summary:"""
summary = self.generate(
prompt=user_prompt,
system=system_prompt,
temperature=0.3,
max_tokens=512
)
return summary.strip()
def _extract_key_points(self, text: str) -> List[str]:
"""Extract key points from the text"""
system_prompt = """You extract the most important key points from documents.
List 3-7 key points as concise bullet points. Each point should be a complete, standalone statement."""
user_prompt = f"""Please extract the key points from the following document:
{text}
List the key points (one per line, without bullets or numbers):"""
response = self.generate(
prompt=user_prompt,
system=system_prompt,
temperature=0.3,
max_tokens=512
)
# Parse the response into a list
key_points = []
for line in response.split('\n'):
line = line.strip()
# Remove common list markers
line = line.lstrip('•-*0123456789.)')
line = line.strip()
if line and len(line) > 10: # Filter out very short lines
key_points.append(line)
return key_points[:7] # Limit to 7 points
def summarize_multiple(self, document_ids: List[str]) -> List[Summary]:
"""
Create summaries for multiple documents
Args:
document_ids: List of document IDs
Returns:
List of Summary objects
"""
summaries = []
for doc_id in document_ids:
summary = self.process(document_id=doc_id)
summaries.append(summary)
return summaries

View File

@@ -0,0 +1,846 @@
"""
KnowledgeHub - Personal Knowledge Management & Research Assistant
Main Gradio Application
"""
import os
import logging
import json
import gradio as gr
from pathlib import Path
import chromadb
from datetime import datetime
# Setup logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# Import utilities and agents
from utils import OllamaClient, EmbeddingModel, DocumentParser
from agents import (
IngestionAgent, QuestionAgent, SummaryAgent,
ConnectionAgent, ExportAgent
)
from models import Document
# Constants
VECTORSTORE_PATH = "./vectorstore"
TEMP_UPLOAD_PATH = "./temp_uploads"
DOCUMENTS_METADATA_PATH = "./vectorstore/documents_metadata.json"
# Ensure directories exist
os.makedirs(VECTORSTORE_PATH, exist_ok=True)
os.makedirs(TEMP_UPLOAD_PATH, exist_ok=True)
class KnowledgeHub:
"""Main application class managing all agents"""
def __init__(self):
logger.info("Initializing KnowledgeHub...")
# Initialize ChromaDB
self.client = chromadb.PersistentClient(path=VECTORSTORE_PATH)
self.collection = self.client.get_or_create_collection(
name="knowledge_base",
metadata={"description": "Personal knowledge management collection"}
)
# Initialize embedding model
self.embedding_model = EmbeddingModel()
# Initialize shared LLM client
self.llm_client = OllamaClient(model="llama3.2")
# Check Ollama connection
if not self.llm_client.check_connection():
logger.warning("⚠️ Cannot connect to Ollama. Please ensure Ollama is running.")
logger.warning("Start Ollama with: ollama serve")
else:
logger.info("✓ Connected to Ollama")
# Initialize agents
self.ingestion_agent = IngestionAgent(
collection=self.collection,
embedding_model=self.embedding_model,
llm_client=self.llm_client
)
self.question_agent = QuestionAgent(
collection=self.collection,
embedding_model=self.embedding_model,
llm_client=self.llm_client
)
self.summary_agent = SummaryAgent(
collection=self.collection,
llm_client=self.llm_client
)
self.connection_agent = ConnectionAgent(
collection=self.collection,
embedding_model=self.embedding_model,
llm_client=self.llm_client
)
self.export_agent = ExportAgent(
llm_client=self.llm_client
)
# Track uploaded documents
self.documents = {}
# Load existing documents from metadata file
self._load_documents_metadata()
logger.info("✓ KnowledgeHub initialized successfully")
def _save_documents_metadata(self):
"""Save document metadata to JSON file"""
try:
metadata = {
doc_id: doc.to_dict()
for doc_id, doc in self.documents.items()
}
with open(DOCUMENTS_METADATA_PATH, 'w') as f:
json.dump(metadata, f, indent=2)
logger.debug(f"Saved metadata for {len(metadata)} documents")
except Exception as e:
logger.error(f"Error saving document metadata: {e}")
def _load_documents_metadata(self):
"""Load document metadata from JSON file"""
try:
if os.path.exists(DOCUMENTS_METADATA_PATH):
with open(DOCUMENTS_METADATA_PATH, 'r') as f:
metadata = json.load(f)
# Reconstruct Document objects (simplified - without chunks)
for doc_id, doc_data in metadata.items():
# Create a minimal Document object for UI purposes
# Full chunks are still in ChromaDB
doc = Document(
id=doc_id,
filename=doc_data['filename'],
filepath=doc_data.get('filepath', ''),
content=doc_data.get('content', ''),
chunks=[], # Chunks are in ChromaDB
metadata=doc_data.get('metadata', {}),
created_at=datetime.fromisoformat(doc_data['created_at'])
)
self.documents[doc_id] = doc
logger.info(f"✓ Loaded {len(self.documents)} existing documents from storage")
else:
logger.info("No existing documents found (starting fresh)")
except Exception as e:
logger.error(f"Error loading document metadata: {e}")
logger.info("Starting with empty document list")
def upload_document(self, files, progress=gr.Progress()):
"""Handle document upload - supports single or multiple files with progress tracking"""
if files is None or len(files) == 0:
return "⚠️ Please select file(s) to upload", "", []
# Convert single file to list for consistent handling
if not isinstance(files, list):
files = [files]
results = []
successful = 0
failed = 0
total_chunks = 0
# Initialize progress tracking
progress(0, desc="Starting upload...")
for file_idx, file in enumerate(files, 1):
# Update progress
progress_pct = (file_idx - 1) / len(files)
progress(progress_pct, desc=f"Processing {file_idx}/{len(files)}: {Path(file.name).name}")
try:
logger.info(f"Processing file {file_idx}/{len(files)}: {file.name}")
# Save uploaded file temporarily
temp_path = os.path.join(TEMP_UPLOAD_PATH, Path(file.name).name)
# Copy file content
with open(temp_path, 'wb') as f:
f.write(file.read() if hasattr(file, 'read') else open(file.name, 'rb').read())
# Process document
document = self.ingestion_agent.process(temp_path)
# Store document reference
self.documents[document.id] = document
# Track stats
successful += 1
total_chunks += document.num_chunks
# Add to results
results.append({
'status': '',
'filename': document.filename,
'chunks': document.num_chunks,
'size': f"{document.total_chars:,} chars"
})
# Clean up temp file
os.remove(temp_path)
except Exception as e:
logger.error(f"Error processing {file.name}: {e}")
failed += 1
results.append({
'status': '',
'filename': Path(file.name).name,
'chunks': 0,
'size': f"Error: {str(e)[:50]}"
})
# Final progress update
progress(1.0, desc="Upload complete!")
# Save metadata once after all uploads
if successful > 0:
self._save_documents_metadata()
# Create summary
summary = f"""## Upload Complete! 🎉
**Total Files:** {len(files)}
**✅ Successful:** {successful}
**❌ Failed:** {failed}
**Total Chunks Created:** {total_chunks:,}
{f"⚠️ **{failed} file(s) failed** - Check results table below for details" if failed > 0 else "All files processed successfully!"}
"""
# Create detailed results table
results_table = [[r['status'], r['filename'], r['chunks'], r['size']] for r in results]
# Create preview of first successful document
preview = ""
for doc in self.documents.values():
if doc.filename in [r['filename'] for r in results if r['status'] == '']:
preview = doc.content[:500] + "..." if len(doc.content) > 500 else doc.content
break
return summary, preview, results_table
def ask_question(self, question, top_k, progress=gr.Progress()):
"""Handle question answering with progress tracking"""
if not question.strip():
return "⚠️ Please enter a question", [], ""
try:
# Initial status
progress(0, desc="Processing your question...")
status = "🔄 **Searching knowledge base...**\n\nRetrieving relevant documents..."
logger.info(f"Answering question: {question[:100]}")
# Update progress
progress(0.3, desc="Finding relevant documents...")
result = self.question_agent.process(question, top_k=top_k)
# Update progress
progress(0.7, desc="Generating answer with LLM...")
# Format answer
answer = f"""### Answer\n\n{result['answer']}\n\n"""
if result['sources']:
answer += f"**Sources:** {result['num_sources']} documents referenced\n\n"
# Format sources for display
sources_data = []
for i, source in enumerate(result['sources'], 1):
sources_data.append([
i,
source['document'],
f"{source['score']:.2%}",
source['preview']
])
progress(1.0, desc="Answer ready!")
return answer, sources_data, "✅ Answer generated successfully!"
except Exception as e:
logger.error(f"Error answering question: {e}")
return f"❌ Error: {str(e)}", [], f"❌ Error: {str(e)}"
def create_summary(self, doc_selector, progress=gr.Progress()):
"""Create document summary with progress tracking"""
if not doc_selector:
return "⚠️ Please select a document to summarize", ""
try:
# Initial status
progress(0, desc="Preparing to summarize...")
logger.info(f'doc_selector : {doc_selector}')
doc_id = doc_selector.split(" -|- ")[1]
document = self.documents.get(doc_id)
if not document:
return "", "❌ Document not found"
# Update status
status_msg = f"🔄 **Generating summary for:** {document.filename}\n\nPlease wait, this may take 10-20 seconds..."
progress(0.3, desc=f"Analyzing {document.filename}...")
logger.info(f"Creating summary for: {document.filename}")
# Generate summary
summary = self.summary_agent.process(
document_id=doc_id,
document_name=document.filename
)
progress(1.0, desc="Summary complete!")
# Format result
result = f"""## Summary of {summary.document_name}\n\n{summary.summary_text}\n\n"""
if summary.key_points:
result += "### Key Points\n\n"
for point in summary.key_points:
result += f"- {point}\n"
return result, "✅ Summary generated successfully!"
except Exception as e:
logger.error(f"Error creating summary: {e}")
return "", f"❌ Error: {str(e)}"
def find_connections(self, doc_selector, top_k, progress=gr.Progress()):
"""Find related documents with progress tracking"""
if not doc_selector:
return "⚠️ Please select a document", [], ""
try:
progress(0, desc="Preparing to find connections...")
doc_id = doc_selector.split(" -|- ")[1]
document = self.documents.get(doc_id)
if not document:
return "❌ Document not found", [], "❌ Document not found"
status = f"🔄 **Finding documents related to:** {document.filename}\n\nSearching knowledge base..."
progress(0.3, desc=f"Analyzing {document.filename}...")
logger.info(f"Finding connections for: {document.filename}")
result = self.connection_agent.process(document_id=doc_id, top_k=top_k)
progress(0.8, desc="Calculating similarity scores...")
if 'error' in result:
return f"❌ Error: {result['error']}", [], f"❌ Error: {result['error']}"
message = f"""## Related Documents\n\n**Source:** {result['source_document']}\n\n"""
message += f"**Found {result['num_related']} related documents:**\n\n"""
# Format for table
table_data = []
for i, rel in enumerate(result['related'], 1):
table_data.append([
i,
rel['document_name'],
f"{rel['similarity']:.2%}",
rel['preview']
])
progress(1.0, desc="Connections found!")
return message, table_data, "✅ Related documents found!"
except Exception as e:
logger.error(f"Error finding connections: {e}")
return f"❌ Error: {str(e)}", [], f"❌ Error: {str(e)}"
def export_knowledge(self, format_choice):
"""Export knowledge base"""
try:
logger.info(f"Exporting as {format_choice}")
# Get statistics
stats = self.ingestion_agent.get_statistics()
# Create export content
content = {
'title': 'Knowledge Base Export',
'summary': f"Total documents in knowledge base: {len(self.documents)}",
'sections': [
{
'title': 'Documents',
'content': '\n'.join([f"- {doc.filename}" for doc in self.documents.values()])
},
{
'title': 'Statistics',
'content': f"Total chunks stored: {stats['total_chunks']}"
}
]
}
# Export
if format_choice == "Markdown":
output = self.export_agent.process(content, format="markdown")
filename = f"knowledge_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md"
elif format_choice == "HTML":
output = self.export_agent.process(content, format="html")
filename = f"knowledge_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html"
else: # Text
output = self.export_agent.process(content, format="text")
filename = f"knowledge_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
# Save file
export_path = os.path.join(TEMP_UPLOAD_PATH, filename)
with open(export_path, 'w', encoding='utf-8') as f:
f.write(output)
return f"✅ Exported as {format_choice}", export_path
except Exception as e:
logger.error(f"Error exporting: {e}")
return f"❌ Error: {str(e)}", None
def get_statistics(self):
"""Get knowledge base statistics"""
try:
stats = self.ingestion_agent.get_statistics()
total_docs = len(self.documents)
total_chunks = stats.get('total_chunks', 0)
total_chars = sum(doc.total_chars for doc in self.documents.values())
# Check if data is persisted
persistence_status = "✅ Enabled" if os.path.exists(DOCUMENTS_METADATA_PATH) else "⚠️ Not configured"
vectorstore_size = self._get_directory_size(VECTORSTORE_PATH)
stats_text = f"""## Knowledge Base Statistics
**Persistence Status:** {persistence_status}
**Total Documents:** {total_docs}
**Total Chunks:** {total_chunks:,}
**Total Characters:** {total_chars:,}
**Vector Store Size:** {vectorstore_size}
### Storage Locations
- **Vector DB:** `{VECTORSTORE_PATH}/`
- **Metadata:** `{DOCUMENTS_METADATA_PATH}`
**📝 Note:** Your data persists across app restarts!
**Recent Documents:**
{chr(10).join([f"- {doc.filename} ({doc.num_chunks} chunks)" for doc in list(self.documents.values())[-5:]])}
"""
if self.documents:
stats_text += "\n".join([f"- {doc.filename} ({doc.num_chunks} chunks, added {doc.created_at.strftime('%Y-%m-%d')})"
for doc in list(self.documents.values())[-10:]])
else:
stats_text += "\n*No documents yet. Upload some to get started!*"
return stats_text
except Exception as e:
return f"❌ Error: {str(e)}"
def _get_directory_size(self, path):
"""Calculate directory size"""
try:
total_size = 0
for dirpath, dirnames, filenames in os.walk(path):
for filename in filenames:
filepath = os.path.join(dirpath, filename)
if os.path.exists(filepath):
total_size += os.path.getsize(filepath)
# Convert to human readable
for unit in ['B', 'KB', 'MB', 'GB']:
if total_size < 1024.0:
return f"{total_size:.1f} {unit}"
total_size /= 1024.0
return f"{total_size:.1f} TB"
except:
return "Unknown"
def get_document_list(self):
"""Get list of documents for dropdown"""
new_choices = [f"{doc.filename} -|- {doc.id}" for doc in self.documents.values()]
return gr.update(choices=new_choices, value=None)
def delete_document(self, doc_selector):
"""Delete a document from the knowledge base"""
if not doc_selector:
return "⚠️ Please select a document to delete", self.get_document_list()
try:
doc_id = doc_selector.split(" - ")[0]
document = self.documents.get(doc_id)
if not document:
return "❌ Document not found", self.get_document_list()
# Delete from ChromaDB
success = self.ingestion_agent.delete_document(doc_id)
if success:
# Remove from documents dict
filename = document.filename
del self.documents[doc_id]
# Save updated metadata
self._save_documents_metadata()
return f"✅ Deleted: {filename}", self.get_document_list()
else:
return f"❌ Error deleting document", self.get_document_list()
except Exception as e:
logger.error(f"Error deleting document: {e}")
return f"❌ Error: {str(e)}", self.get_document_list()
def clear_all_documents(self):
"""Clear entire knowledge base"""
try:
# Delete collection
self.client.delete_collection("knowledge_base")
# Recreate empty collection
self.collection = self.client.create_collection(
name="knowledge_base",
metadata={"description": "Personal knowledge management collection"}
)
# Update agents with new collection
self.ingestion_agent.collection = self.collection
self.question_agent.collection = self.collection
self.summary_agent.collection = self.collection
self.connection_agent.collection = self.collection
# Clear documents
self.documents = {}
self._save_documents_metadata()
return "✅ All documents cleared from knowledge base"
except Exception as e:
logger.error(f"Error clearing database: {e}")
return f"❌ Error: {str(e)}"
def create_ui():
"""Create Gradio interface"""
# Initialize app
app = KnowledgeHub()
# Custom CSS
custom_css = """
.main-header {
text-align: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
border-radius: 10px;
margin-bottom: 20px;
}
.stat-box {
background: #f8f9fa;
padding: 15px;
border-radius: 8px;
border-left: 4px solid #667eea;
}
"""
with gr.Blocks(title="KnowledgeHub", css=custom_css, theme=gr.themes.Soft()) as interface:
# Header
gr.HTML("""
<div class="main-header">
<h1>🧠 KnowledgeHub</h1>
<p>Personal Knowledge Management & Research Assistant</p>
<p style="font-size: 14px; opacity: 0.9;">
Powered by Ollama (Llama 3.2) • Fully Local & Private
</p>
</div>
""")
# Main tabs
with gr.Tabs():
# Tab 1: Upload Documents
with gr.Tab("📤 Upload Documents"):
gr.Markdown("### Upload your documents to build your knowledge base")
gr.Markdown("*Supported formats: PDF, DOCX, TXT, MD, HTML, PY*")
gr.Markdown("*💡 Tip: You can select multiple files at once!*")
with gr.Row():
with gr.Column():
file_input = gr.File(
label="Select Document(s)",
file_types=[".pdf", ".docx", ".txt", ".md", ".html", ".py"],
file_count="multiple" # Enable multiple file selection
)
upload_btn = gr.Button("📤 Upload & Process", variant="primary")
with gr.Column():
upload_status = gr.Markdown("Ready to upload documents")
# Results table for batch uploads
with gr.Row():
upload_results = gr.Dataframe(
headers=["Status", "Filename", "Chunks", "Size"],
label="Upload Results",
wrap=True,
visible=True
)
with gr.Row():
document_preview = gr.Textbox(
label="Document Preview (First Uploaded)",
lines=10,
max_lines=15
)
upload_btn.click(
fn=app.upload_document,
inputs=[file_input],
outputs=[upload_status, document_preview, upload_results]
)
# Tab 2: Ask Questions
with gr.Tab("❓ Ask Questions"):
gr.Markdown("### Ask questions about your documents")
gr.Markdown("*Uses RAG (Retrieval Augmented Generation) to answer based on your knowledge base*")
with gr.Row():
with gr.Column(scale=3):
question_input = gr.Textbox(
label="Your Question",
placeholder="What would you like to know?",
lines=3
)
with gr.Column(scale=1):
top_k_slider = gr.Slider(
minimum=1,
maximum=10,
value=5,
step=1,
label="Number of sources"
)
ask_btn = gr.Button("🔍 Ask", variant="primary")
qa_status = gr.Markdown("Ready to answer questions")
answer_output = gr.Markdown(label="Answer")
sources_table = gr.Dataframe(
headers=["#", "Document", "Relevance", "Preview"],
label="Sources",
wrap=True
)
ask_btn.click(
fn=app.ask_question,
inputs=[question_input, top_k_slider],
outputs=[answer_output, sources_table, qa_status]
)
# Tab 3: Summarize
with gr.Tab("📝 Summarize"):
gr.Markdown("### Generate summaries and extract key points")
with gr.Row():
with gr.Column():
doc_selector = gr.Dropdown(
choices=[],
label="Select Document",
info="Choose a document to summarize",
allow_custom_value=True
)
refresh_btn = gr.Button("🔄 Refresh List")
summarize_btn = gr.Button("📝 Generate Summary", variant="primary")
summary_status = gr.Markdown("Ready to generate summaries")
with gr.Column(scale=2):
summary_output = gr.Markdown(label="Summary")
summarize_btn.click(
fn=app.create_summary,
inputs=[doc_selector],
outputs=[summary_output, summary_status]
)
refresh_btn.click(
fn=app.get_document_list,
outputs=[doc_selector]
)
# Tab 4: Find Connections
with gr.Tab("🔗 Find Connections"):
gr.Markdown("### Discover relationships between documents")
with gr.Row():
with gr.Column():
conn_doc_selector = gr.Dropdown(
choices=[],
label="Select Document",
info="Find documents related to this one",
allow_custom_value=True
)
conn_top_k = gr.Slider(
minimum=1,
maximum=10,
value=5,
step=1,
label="Number of related documents"
)
refresh_conn_btn = gr.Button("🔄 Refresh List")
find_btn = gr.Button("🔗 Find Connections", variant="primary")
connection_status = gr.Markdown("Ready to find connections")
connection_output = gr.Markdown(label="Connections")
connections_table = gr.Dataframe(
headers=["#", "Document", "Similarity", "Preview"],
label="Related Documents",
wrap=True
)
find_btn.click(
fn=app.find_connections,
inputs=[conn_doc_selector, conn_top_k],
outputs=[connection_output, connections_table, connection_status]
)
refresh_conn_btn.click(
fn=app.get_document_list,
outputs=[conn_doc_selector]
)
# Tab 5: Export
with gr.Tab("💾 Export"):
gr.Markdown("### Export your knowledge base")
with gr.Row():
with gr.Column():
format_choice = gr.Radio(
choices=["Markdown", "HTML", "Text"],
value="Markdown",
label="Export Format"
)
export_btn = gr.Button("💾 Export", variant="primary")
with gr.Column():
export_status = gr.Markdown("Ready to export")
export_file = gr.File(label="Download Export")
export_btn.click(
fn=app.export_knowledge,
inputs=[format_choice],
outputs=[export_status, export_file]
)
# Tab 6: Manage Documents
with gr.Tab("🗂️ Manage Documents"):
gr.Markdown("### Manage your document library")
with gr.Row():
with gr.Column():
gr.Markdown("#### Delete Document")
delete_doc_selector = gr.Dropdown(
choices=[],
label="Select Document to Delete",
info="Choose a document to remove from knowledge base"
)
with gr.Row():
refresh_delete_btn = gr.Button("🔄 Refresh List")
delete_btn = gr.Button("🗑️ Delete Document", variant="stop")
delete_status = gr.Markdown("")
with gr.Column():
gr.Markdown("#### Clear All Documents")
gr.Markdown("⚠️ **Warning:** This will delete your entire knowledge base!")
clear_confirm = gr.Textbox(
label="Type 'DELETE ALL' to confirm",
placeholder="DELETE ALL"
)
clear_all_btn = gr.Button("🗑️ Clear All Documents", variant="stop")
clear_status = gr.Markdown("")
def confirm_and_clear(confirm_text):
if confirm_text.strip() == "DELETE ALL":
return app.clear_all_documents()
else:
return "⚠️ Please type 'DELETE ALL' to confirm"
delete_btn.click(
fn=app.delete_document,
inputs=[delete_doc_selector],
outputs=[delete_status, delete_doc_selector]
)
refresh_delete_btn.click(
fn=app.get_document_list,
outputs=[delete_doc_selector]
)
clear_all_btn.click(
fn=confirm_and_clear,
inputs=[clear_confirm],
outputs=[clear_status]
)
# Tab 7: Statistics
with gr.Tab("📊 Statistics"):
gr.Markdown("### Knowledge Base Overview")
stats_output = gr.Markdown()
stats_btn = gr.Button("🔄 Refresh Statistics", variant="primary")
stats_btn.click(
fn=app.get_statistics,
outputs=[stats_output]
)
# Auto-load stats on tab open
interface.load(
fn=app.get_statistics,
outputs=[stats_output]
)
# Footer
gr.HTML("""
<div style="text-align: center; margin-top: 30px; padding: 20px; color: #666;">
<p>🔒 All processing happens locally on your machine • Your data never leaves your computer</p>
<p style="font-size: 12px;">Powered by Ollama, ChromaDB, and Sentence Transformers</p>
</div>
""")
return interface
if __name__ == "__main__":
logger.info("Starting KnowledgeHub...")
# Create and launch interface
interface = create_ui()
interface.launch(
server_name="127.0.0.1",
server_port=7860,
share=False,
inbrowser=True
)

View File

@@ -0,0 +1,13 @@
"""
models
"""
from .knowledge_graph import KnowledgeGraph
from .document import Document, DocumentChunk, SearchResult, Summary
__all__ = [
'KnowledgeGraph',
'Document',
'DocumentChunk',
'SearchResult',
'Summary'
]

View File

@@ -0,0 +1,82 @@
"""
Document data models
"""
from dataclasses import dataclass, field
from typing import List, Dict, Optional
from datetime import datetime
@dataclass
class DocumentChunk:
"""Represents a chunk of a document"""
id: str
document_id: str
content: str
chunk_index: int
metadata: Dict = field(default_factory=dict)
def __str__(self):
preview = self.content[:100] + "..." if len(self.content) > 100 else self.content
return f"Chunk {self.chunk_index}: {preview}"
@dataclass
class Document:
"""Represents a complete document"""
id: str
filename: str
filepath: str
content: str
chunks: List[DocumentChunk]
metadata: Dict = field(default_factory=dict)
created_at: datetime = field(default_factory=datetime.now)
@property
def num_chunks(self) -> int:
return len(self.chunks)
@property
def total_chars(self) -> int:
return len(self.content)
@property
def extension(self) -> str:
return self.metadata.get('extension', '')
def __str__(self):
return f"Document: {self.filename} ({self.num_chunks} chunks, {self.total_chars} chars)"
def to_dict(self) -> Dict:
"""Convert to dictionary for storage"""
return {
'id': self.id,
'filename': self.filename,
'filepath': self.filepath,
'content': self.content[:500] + '...' if len(self.content) > 500 else self.content,
'num_chunks': self.num_chunks,
'total_chars': self.total_chars,
'extension': self.extension,
'created_at': self.created_at.isoformat(),
'metadata': self.metadata
}
@dataclass
class SearchResult:
"""Represents a search result from the vector database"""
chunk: DocumentChunk
score: float
document_id: str
document_name: str
def __str__(self):
return f"{self.document_name} (score: {self.score:.2f})"
@dataclass
class Summary:
"""Represents a document summary"""
document_id: str
document_name: str
summary_text: str
key_points: List[str] = field(default_factory=list)
created_at: datetime = field(default_factory=datetime.now)
def __str__(self):
return f"Summary of {self.document_name}: {self.summary_text[:100]}..."

View File

@@ -0,0 +1,110 @@
"""
Knowledge Graph data models
"""
from dataclasses import dataclass, field
from typing import List, Dict, Set
from datetime import datetime
@dataclass
class KnowledgeNode:
"""Represents a concept or entity in the knowledge graph"""
id: str
name: str
node_type: str # 'document', 'concept', 'entity', 'topic'
description: str = ""
metadata: Dict = field(default_factory=dict)
created_at: datetime = field(default_factory=datetime.now)
def __str__(self):
return f"{self.node_type.capitalize()}: {self.name}"
@dataclass
class KnowledgeEdge:
"""Represents a relationship between nodes"""
source_id: str
target_id: str
relationship: str # 'related_to', 'cites', 'contains', 'similar_to'
weight: float = 1.0
metadata: Dict = field(default_factory=dict)
def __str__(self):
return f"{self.source_id} --[{self.relationship}]--> {self.target_id}"
@dataclass
class KnowledgeGraph:
"""Represents the complete knowledge graph"""
nodes: Dict[str, KnowledgeNode] = field(default_factory=dict)
edges: List[KnowledgeEdge] = field(default_factory=list)
def add_node(self, node: KnowledgeNode):
"""Add a node to the graph"""
self.nodes[node.id] = node
def add_edge(self, edge: KnowledgeEdge):
"""Add an edge to the graph"""
if edge.source_id in self.nodes and edge.target_id in self.nodes:
self.edges.append(edge)
def get_neighbors(self, node_id: str) -> List[str]:
"""Get all nodes connected to a given node"""
neighbors = set()
for edge in self.edges:
if edge.source_id == node_id:
neighbors.add(edge.target_id)
elif edge.target_id == node_id:
neighbors.add(edge.source_id)
return list(neighbors)
def get_related_documents(self, node_id: str, max_depth: int = 2) -> Set[str]:
"""Get all documents related to a node within max_depth hops"""
related = set()
visited = set()
queue = [(node_id, 0)]
while queue:
current_id, depth = queue.pop(0)
if current_id in visited or depth > max_depth:
continue
visited.add(current_id)
# If this is a document node, add it
if current_id in self.nodes and self.nodes[current_id].node_type == 'document':
related.add(current_id)
# Add neighbors to queue
if depth < max_depth:
for neighbor_id in self.get_neighbors(current_id):
if neighbor_id not in visited:
queue.append((neighbor_id, depth + 1))
return related
def to_networkx(self):
"""Convert to NetworkX graph for visualization"""
try:
import networkx as nx
G = nx.Graph()
# Add nodes
for node_id, node in self.nodes.items():
G.add_node(node_id,
name=node.name,
type=node.node_type,
description=node.description)
# Add edges
for edge in self.edges:
G.add_edge(edge.source_id, edge.target_id,
relationship=edge.relationship,
weight=edge.weight)
return G
except ImportError:
return None
def __str__(self):
return f"KnowledgeGraph: {len(self.nodes)} nodes, {len(self.edges)} edges"

View File

@@ -0,0 +1,26 @@
# Core Dependencies
gradio>=4.0.0
chromadb>=0.4.0
sentence-transformers>=2.2.0
python-dotenv>=1.0.0
# Document Processing
pypdf>=3.0.0
python-docx>=1.0.0
markdown>=3.4.0
beautifulsoup4>=4.12.0
# Data Processing
numpy>=1.24.0
pandas>=2.0.0
tqdm>=4.65.0
# Visualization
plotly>=5.14.0
networkx>=3.0
# Ollama Client
requests>=2.31.0
# Optional but useful
scikit-learn>=1.3.0

View File

@@ -0,0 +1,71 @@
@echo off
REM KnowledgeHub Startup Script for Windows
echo 🧠 Starting KnowledgeHub...
echo.
REM Check if Ollama is installed
where ollama >nul 2>nul
if %errorlevel% neq 0 (
echo ❌ Ollama is not installed or not in PATH
echo Please install Ollama from https://ollama.com/download
pause
exit /b 1
)
REM Check Python
where python >nul 2>nul
if %errorlevel% neq 0 (
echo ❌ Python is not installed or not in PATH
echo Please install Python 3.8+ from https://www.python.org/downloads/
pause
exit /b 1
)
echo ✅ Prerequisites found
echo.
REM Check if Ollama service is running
tasklist /FI "IMAGENAME eq ollama.exe" 2>NUL | find /I /N "ollama.exe">NUL
if "%ERRORLEVEL%"=="1" (
echo ⚠️ Ollama is not running. Please start Ollama first.
echo You can start it from the Start menu or by running: ollama serve
pause
exit /b 1
)
echo ✅ Ollama is running
echo.
REM Check if model exists
ollama list | find "llama3.2" >nul
if %errorlevel% neq 0 (
echo 📥 Llama 3.2 model not found. Pulling model...
echo This may take a few minutes on first run...
ollama pull llama3.2
)
echo ✅ Model ready
echo.
REM Install dependencies
echo 🔍 Checking dependencies...
python -c "import gradio" 2>nul
if %errorlevel% neq 0 (
echo 📦 Installing dependencies...
pip install -r requirements.txt
)
echo ✅ Dependencies ready
echo.
REM Launch application
echo 🚀 Launching KnowledgeHub...
echo The application will open in your browser at http://127.0.0.1:7860
echo.
echo Press Ctrl+C to stop the application
echo.
python app.py
pause

View File

@@ -0,0 +1,42 @@
#!/bin/bash
# KnowledgeHub Startup Script
echo "🧠 Starting KnowledgeHub..."
echo ""
# Check if Ollama is running
if ! pgrep -x "ollama" > /dev/null; then
echo "⚠️ Ollama is not running. Starting Ollama..."
ollama serve &
sleep 3
fi
# Check if llama3.2 model exists
if ! ollama list | grep -q "llama3.2"; then
echo "📥 Llama 3.2 model not found. Pulling model..."
echo "This may take a few minutes on first run..."
ollama pull llama3.2
fi
echo "✅ Ollama is ready"
echo ""
# Check Python dependencies
echo "🔍 Checking dependencies..."
if ! python -c "import gradio" 2>/dev/null; then
echo "📦 Installing dependencies..."
pip install -r requirements.txt
fi
echo "✅ Dependencies ready"
echo ""
# Launch the application
echo "🚀 Launching KnowledgeHub..."
echo "The application will open in your browser at http://127.0.0.1:7860"
echo ""
echo "Press Ctrl+C to stop the application"
echo ""
python app.py

View File

@@ -0,0 +1,12 @@
"""
models
"""
from .document_parser import DocumentParser
from .embeddings import EmbeddingModel
from .ollama_client import OllamaClient
__all__ = [
'DocumentParser',
'EmbeddingModel',
'OllamaClient'
]

View File

@@ -0,0 +1,218 @@
"""
Document Parser - Extract text from various document formats
"""
import os
from typing import List, Dict, Optional
import logging
from pathlib import Path
logger = logging.getLogger(__name__)
class DocumentParser:
"""Parse various document formats into text chunks"""
SUPPORTED_FORMATS = ['.pdf', '.docx', '.txt', '.md', '.html', '.py']
def __init__(self, chunk_size: int = 1000, chunk_overlap: int = 200):
"""
Initialize document parser
Args:
chunk_size: Maximum characters per chunk
chunk_overlap: Overlap between chunks for context preservation
"""
self.chunk_size = chunk_size
self.chunk_overlap = chunk_overlap
def parse_file(self, file_path: str) -> Dict:
"""
Parse a file and return structured document data
Args:
file_path: Path to the file
Returns:
Dictionary with document metadata and chunks
"""
path = Path(file_path)
if not path.exists():
raise FileNotFoundError(f"File not found: {file_path}")
extension = path.suffix.lower()
if extension not in self.SUPPORTED_FORMATS:
raise ValueError(f"Unsupported format: {extension}")
# Extract text based on file type
if extension == '.pdf':
text = self._parse_pdf(file_path)
elif extension == '.docx':
text = self._parse_docx(file_path)
elif extension == '.txt' or extension == '.py':
text = self._parse_txt(file_path)
elif extension == '.md':
text = self._parse_markdown(file_path)
elif extension == '.html':
text = self._parse_html(file_path)
else:
text = ""
# Create chunks
chunks = self._create_chunks(text)
return {
'filename': path.name,
'filepath': str(path.absolute()),
'extension': extension,
'text': text,
'chunks': chunks,
'num_chunks': len(chunks),
'total_chars': len(text)
}
def _parse_pdf(self, file_path: str) -> str:
"""Extract text from PDF"""
try:
from pypdf import PdfReader
reader = PdfReader(file_path)
text = ""
for page in reader.pages:
text += page.extract_text() + "\n\n"
return text.strip()
except ImportError:
logger.error("pypdf not installed. Install with: pip install pypdf")
return ""
except Exception as e:
logger.error(f"Error parsing PDF: {e}")
return ""
def _parse_docx(self, file_path: str) -> str:
"""Extract text from DOCX"""
try:
from docx import Document
doc = Document(file_path)
text = "\n\n".join([para.text for para in doc.paragraphs if para.text.strip()])
return text.strip()
except ImportError:
logger.error("python-docx not installed. Install with: pip install python-docx")
return ""
except Exception as e:
logger.error(f"Error parsing DOCX: {e}")
return ""
def _parse_txt(self, file_path: str) -> str:
"""Extract text from TXT"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
return f.read().strip()
except Exception as e:
logger.error(f"Error parsing TXT: {e}")
return ""
def _parse_markdown(self, file_path: str) -> str:
"""Extract text from Markdown"""
try:
import markdown
from bs4 import BeautifulSoup
with open(file_path, 'r', encoding='utf-8') as f:
md_text = f.read()
# Convert markdown to HTML then extract text
html = markdown.markdown(md_text)
soup = BeautifulSoup(html, 'html.parser')
text = soup.get_text()
return text.strip()
except ImportError:
# Fallback: just read as plain text
return self._parse_txt(file_path)
except Exception as e:
logger.error(f"Error parsing Markdown: {e}")
return ""
def _parse_html(self, file_path: str) -> str:
"""Extract text from HTML"""
try:
from bs4 import BeautifulSoup
with open(file_path, 'r', encoding='utf-8') as f:
html = f.read()
soup = BeautifulSoup(html, 'html.parser')
# Remove script and style elements
for script in soup(["script", "style"]):
script.decompose()
text = soup.get_text()
# Clean up whitespace
lines = (line.strip() for line in text.splitlines())
chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
text = '\n'.join(chunk for chunk in chunks if chunk)
return text.strip()
except ImportError:
logger.error("beautifulsoup4 not installed. Install with: pip install beautifulsoup4")
return ""
except Exception as e:
logger.error(f"Error parsing HTML: {e}")
return ""
def _create_chunks(self, text: str) -> List[str]:
"""
Split text into overlapping chunks
Args:
text: Full text to chunk
Returns:
List of text chunks
"""
if not text:
return []
chunks = []
start = 0
text_length = len(text)
while start < text_length:
logger.info(f'Processing chunk at {start}, for len {text_length}.')
end = start + self.chunk_size
# If this isn't the last chunk, try to break at a sentence or paragraph
if end < text_length:
# Look for paragraph break first
break_pos = text.rfind('\n\n', start, end)
if break_pos == -1:
# Look for sentence break
break_pos = text.rfind('. ', start, end)
if break_pos == -1:
# Look for any space
break_pos = text.rfind(' ', start, end)
if break_pos != -1 and break_pos > start and break_pos > end - self.chunk_overlap:
end = break_pos + 1
chunk = text[start:end].strip()
if chunk:
chunks.append(chunk)
# Move start position with overlap
start = end - self.chunk_overlap
if start < 0:
start = 0
return chunks

View File

@@ -0,0 +1,84 @@
"""
Embeddings utility using sentence-transformers
"""
from sentence_transformers import SentenceTransformer
import numpy as np
from typing import List, Union
import logging
logger = logging.getLogger(__name__)
class EmbeddingModel:
"""Wrapper for sentence transformer embeddings"""
def __init__(self, model_name: str = "sentence-transformers/all-MiniLM-L6-v2"):
"""
Initialize embedding model
Args:
model_name: HuggingFace model name for embeddings
"""
self.model_name = model_name
logger.info(f"Loading embedding model: {model_name}")
self.model = SentenceTransformer(model_name)
self.dimension = self.model.get_sentence_embedding_dimension()
logger.info(f"Embedding dimension: {self.dimension}")
def embed(self, texts: Union[str, List[str]]) -> np.ndarray:
"""
Generate embeddings for text(s)
Args:
texts: Single text or list of texts
Returns:
Numpy array of embeddings
"""
if isinstance(texts, str):
texts = [texts]
embeddings = self.model.encode(texts, show_progress_bar=False)
return embeddings
def embed_query(self, query: str) -> List[float]:
"""
Embed a single query - returns as list for ChromaDB compatibility
Args:
query: Query text
Returns:
List of floats representing the embedding
"""
embedding = self.model.encode([query], show_progress_bar=False)[0]
return embedding.tolist()
def embed_documents(self, documents: List[str]) -> List[List[float]]:
"""
Embed multiple documents - returns as list of lists for ChromaDB
Args:
documents: List of document texts
Returns:
List of embeddings (each as list of floats)
"""
embeddings = self.model.encode(documents, show_progress_bar=False)
return embeddings.tolist()
def similarity(self, text1: str, text2: str) -> float:
"""
Calculate cosine similarity between two texts
Args:
text1: First text
text2: Second text
Returns:
Similarity score between 0 and 1
"""
emb1, emb2 = self.model.encode([text1, text2])
# Cosine similarity
similarity = np.dot(emb1, emb2) / (np.linalg.norm(emb1) * np.linalg.norm(emb2))
return float(similarity)

View File

@@ -0,0 +1,107 @@
"""
Ollama Client - Wrapper for local Ollama API
"""
import requests
import json
from typing import List, Dict, Optional
import logging
logger = logging.getLogger(__name__)
class OllamaClient:
"""Client for interacting with local Ollama models"""
def __init__(self, base_url: str = "http://localhost:11434", model: str = "llama3.2"):
self.base_url = base_url
self.model = model
self.api_url = f"{base_url}/api"
def generate(self, prompt: str, system: Optional[str] = None,
temperature: float = 0.7, max_tokens: int = 2048) -> str:
"""Generate text from a prompt"""
try:
payload = {
"model": self.model,
"prompt": prompt,
"stream": False,
"options": {
"temperature": temperature,
"num_predict": max_tokens
}
}
if system:
payload["system"] = system
response = requests.post(
f"{self.api_url}/generate",
json=payload,
timeout=1200
)
response.raise_for_status()
result = response.json()
return result.get("response", "").strip()
except requests.exceptions.RequestException as e:
logger.error(f"Ollama API error: {e}")
return f"Error: Unable to connect to Ollama. Is it running? ({str(e)})"
def chat(self, messages: List[Dict[str, str]],
temperature: float = 0.7, max_tokens: int = 2048) -> str:
"""Chat completion with message history"""
try:
payload = {
"model": self.model,
"messages": messages,
"stream": False,
"options": {
"temperature": temperature,
"num_predict": max_tokens
}
}
response = requests.post(
f"{self.api_url}/chat",
json=payload,
timeout=1200
)
response.raise_for_status()
result = response.json()
return result.get("message", {}).get("content", "").strip()
except requests.exceptions.RequestException as e:
logger.error(f"Ollama API error: {e}")
return f"Error: Unable to connect to Ollama. Is it running? ({str(e)})"
def check_connection(self) -> bool:
"""Check if Ollama is running and model is available"""
try:
response = requests.get(f"{self.base_url}/api/tags", timeout=5)
response.raise_for_status()
models = response.json().get("models", [])
model_names = [m["name"] for m in models]
if self.model not in model_names:
logger.warning(f"Model {self.model} not found. Available: {model_names}")
return False
return True
except requests.exceptions.RequestException as e:
logger.error(f"Cannot connect to Ollama: {e}")
return False
def list_models(self) -> List[str]:
"""List available Ollama models"""
try:
response = requests.get(f"{self.base_url}/api/tags", timeout=5)
response.raise_for_status()
models = response.json().get("models", [])
return [m["name"] for m in models]
except requests.exceptions.RequestException:
return []

View File

@@ -0,0 +1,129 @@
"""
Setup Verification Script for KnowledgeHub
Run this to check if everything is configured correctly
"""
import sys
import os
print("🔍 KnowledgeHub Setup Verification\n")
print("=" * 60)
# Check Python version
print(f"✓ Python version: {sys.version}")
print(f"✓ Python executable: {sys.executable}")
print(f"✓ Current directory: {os.getcwd()}")
print()
# Check directory structure
print("📁 Checking directory structure...")
required_dirs = ['agents', 'models', 'utils']
for dir_name in required_dirs:
if os.path.isdir(dir_name):
init_file = os.path.join(dir_name, '__init__.py')
if os.path.exists(init_file):
print(f"{dir_name}/ exists with __init__.py")
else:
print(f" ⚠️ {dir_name}/ exists but missing __init__.py")
else:
print(f"{dir_name}/ directory not found")
print()
# Check required files
print("📄 Checking required files...")
required_files = ['app.py', 'requirements.txt']
for file_name in required_files:
if os.path.exists(file_name):
print(f"{file_name} exists")
else:
print(f"{file_name} not found")
print()
# Try importing modules
print("📦 Testing imports...")
errors = []
try:
from utils import OllamaClient, EmbeddingModel, DocumentParser
print(" ✓ utils module imported successfully")
except ImportError as e:
print(f" ❌ Cannot import utils: {e}")
errors.append(str(e))
try:
from models import Document, DocumentChunk, SearchResult, Summary
print(" ✓ models module imported successfully")
except ImportError as e:
print(f" ❌ Cannot import models: {e}")
errors.append(str(e))
try:
from agents import (
IngestionAgent, QuestionAgent, SummaryAgent,
ConnectionAgent, ExportAgent
)
print(" ✓ agents module imported successfully")
except ImportError as e:
print(f" ❌ Cannot import agents: {e}")
errors.append(str(e))
print()
# Check dependencies
print("📚 Checking Python dependencies...")
required_packages = [
'gradio', 'chromadb', 'sentence_transformers',
'requests', 'numpy', 'tqdm'
]
missing_packages = []
for package in required_packages:
try:
__import__(package.replace('-', '_'))
print(f"{package} installed")
except ImportError:
print(f"{package} not installed")
missing_packages.append(package)
print()
# Check Ollama
print("🤖 Checking Ollama...")
try:
import requests
response = requests.get('http://localhost:11434/api/tags', timeout=2)
if response.status_code == 200:
print(" ✓ Ollama is running")
models = response.json().get('models', [])
if models:
print(f" ✓ Available models: {[m['name'] for m in models]}")
if any('llama3.2' in m['name'] for m in models):
print(" ✓ llama3.2 model found")
else:
print(" ⚠️ llama3.2 model not found. Run: ollama pull llama3.2")
else:
print(" ⚠️ No models found. Run: ollama pull llama3.2")
else:
print(" ⚠️ Ollama responded but with error")
except Exception as e:
print(f" ❌ Cannot connect to Ollama: {e}")
print(" Start Ollama with: ollama serve")
print()
print("=" * 60)
# Final summary
if errors or missing_packages:
print("\n⚠️ ISSUES FOUND:\n")
if errors:
print("Import Errors:")
for error in errors:
print(f" - {error}")
if missing_packages:
print("\nMissing Packages:")
print(f" Run: pip install {' '.join(missing_packages)}")
print("\n💡 Fix these issues before running app.py")
else:
print("\n✅ All checks passed! You're ready to run:")
print(" python app.py")
print()

View File

@@ -0,0 +1,397 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "71293c1d",
"metadata": {},
"source": [
"# Online Banking Assistant\n",
"\n",
"This application is the Bank of Transformers online banking application.\n",
"\n",
"Using this application users can:\n",
"\n",
"- open an account\n",
"- add funds to an account\n",
"- withdraw funds from an account\n",
"- check the balance of accounts\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "989a6bf9",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import json\n",
"from dotenv import load_dotenv\n",
"from openai import OpenAI\n",
"import gradio as gr"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "94e7f3d1",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"OpenAI API Key exists and begins sk-proj-\n"
]
}
],
"source": [
"# Initialization\n",
"\n",
"load_dotenv(override=True)\n",
"\n",
"openai_api_key = os.getenv('OPENAI_API_KEY')\n",
"if openai_api_key:\n",
" print(f\"OpenAI API Key exists and begins {openai_api_key[:8]}\")\n",
"else:\n",
" print(\"OpenAI API Key not set\")\n",
" \n",
"MODEL = \"gpt-4.1-mini\"\n",
"openai = OpenAI()"
]
},
{
"cell_type": "code",
"execution_count": 64,
"id": "d53e4dd8",
"metadata": {},
"outputs": [],
"source": [
"WELCOME = \"\"\"\\\n",
"**Welcome to the Bank of Transformers Online Banking App**\n",
"\n",
"You can do the following:\n",
"- open an account\n",
"- deposit funds\n",
"- withdraw funds\n",
"- check the balance of your account\n",
"\n",
"What would you like to do today?\n",
"\"\"\"\n",
"\n",
"SYS_PROMPT = \"\"\"\\\n",
"You are a helpful banking assistant for an online banking app. Your job:\n",
"1) Understand the user's intent (open account, deposit, withdraw funds, check balance).\n",
"2) If you need to take an action, use the provided tools (functions).\n",
"3) Be concise, ask only for the minimum needed info next.\n",
"4) Always reflect current balances when relevant.\n",
"5) Never invent accounts or balances—rely on tool results only.\n",
"6) After carrying out every action for the user, list all available options, as user might want to deposit/withdraw more funds \n",
"\"\"\""
]
},
{
"cell_type": "code",
"execution_count": 67,
"id": "36d5f7cf",
"metadata": {},
"outputs": [],
"source": [
"open_account_function = {\n",
" \"name\": \"open_account\",\n",
" \"description\": \"Open an account for the user. Ask the user to enter their name and email address.\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"name\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"The name of the user\",\n",
" },\n",
" \"email\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"The email address of the user\",\n",
" },\n",
" },\n",
" \"required\": [\"name\", \"email\"],\n",
" \"additionalProperties\": False\n",
" }\n",
"}\n",
"\n",
"deposit_funds_function = {\n",
" \"name\": \"deposit_funds\",\n",
" \"description\": \"Deposit funds into the user account. Ask the user to enter the amount to deposit.\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"amount\": {\n",
" \"type\": \"number\",\n",
" \"description\": \"The amount to deposit\",\n",
" },\n",
" \"balance\": {\n",
" \"type\": \"number\",\n",
" \"description\": \"The account balance before the deposit\",\n",
" }\n",
" },\n",
" \"required\": [\"amount\", \"balance\"],\n",
" \"additionalProperties\": False\n",
" }\n",
"}\n",
"\n",
"withdraw_funds_function = {\n",
" \"name\": \"withdraw_funds\",\n",
" \"description\": \"Withdraw funds from the user account. Ask the user to enter the amount to withdraw.\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"amount\": {\n",
" \"type\": \"number\",\n",
" \"description\": \"The amount to withdraw\",\n",
" },\n",
" \"balance\": {\n",
" \"type\": \"number\",\n",
" \"description\": \"The account balance before the withdraw\",\n",
" }\n",
" },\n",
" \"required\": [\"amount\", \"balance\"],\n",
" \"additionalProperties\": False\n",
" }\n",
"}\n",
"\n",
"check_balance_function = {\n",
" \"name\": \"check_balance\",\n",
" \"description\": \"Get the balance of the user account and display it to the user.\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"balance\": {\n",
" \"type\": \"number\",\n",
" \"description\": \"The account balance\",\n",
" }\n",
" },\n",
" \"required\": [\"balance\"],\n",
" \"additionalProperties\": False\n",
" } \n",
"}"
]
},
{
"cell_type": "code",
"execution_count": 68,
"id": "e35f7074",
"metadata": {},
"outputs": [],
"source": [
"tools = [\n",
" {\"type\": \"function\", \"function\": open_account_function}, \n",
" {\"type\": \"function\", \"function\": deposit_funds_function},\n",
" {\"type\": \"function\", \"function\": withdraw_funds_function},\n",
" {\"type\": \"function\", \"function\": check_balance_function}\n",
"]"
]
},
{
"cell_type": "code",
"execution_count": 70,
"id": "12cb2f44",
"metadata": {},
"outputs": [],
"source": [
"def open_account(name, email):\n",
" return {\"message\": f\"Account opened for {name} with email {email}\"}\n",
"\n",
"def deposit_funds(amount, balance):\n",
" new_balance = float(balance) + float(amount)\n",
" return {\n",
" \"message\": f\"Deposited ${amount:.2f} into your account. Balance: ${new_balance:.2f}\",\n",
" \"balance\": new_balance\n",
" }\n",
"\n",
"def withdraw_funds(amount, balance):\n",
" new_balance = float(balance) - float(amount)\n",
" return {\n",
" \"message\": f\"Withdrew ${amount:.2f} from your account. Balance: ${new_balance:.2f}\",\n",
" \"balance\": new_balance\n",
" }\n",
"\n",
"def check_balance(balance):\n",
" bal = float(balance)\n",
" return {\"message\": f\"Your account balance is: ${bal:.2f}\", \"balance\": bal}"
]
},
{
"cell_type": "code",
"execution_count": 71,
"id": "c05ada4e",
"metadata": {},
"outputs": [],
"source": [
"# We have to write that function handle_tool_call:\n",
"\n",
"def handle_tool_call(assistant_msg, balance):\n",
" tool_messages = []\n",
" new_balance = balance\n",
"\n",
" for tc in getattr(assistant_msg, \"tool_calls\", []) or []:\n",
" args = json.loads(tc.function.arguments or \"{}\")\n",
" name = tc.function.name\n",
"\n",
" if name == \"open_account\":\n",
" result = open_account(args.get(\"name\"), args.get(\"email\"))\n",
" tool_messages.append({\n",
" \"role\": \"tool\",\n",
" \"tool_call_id\": tc.id,\n",
" \"content\": json.dumps(result) # MUST be string\n",
" })\n",
"\n",
" elif name == \"deposit_funds\":\n",
" result = deposit_funds(args[\"amount\"], new_balance)\n",
" new_balance = result[\"balance\"]\n",
" tool_messages.append({\n",
" \"role\": \"tool\",\n",
" \"tool_call_id\": tc.id,\n",
" \"content\": json.dumps(result)\n",
" })\n",
"\n",
" elif name == \"withdraw_funds\":\n",
" result = withdraw_funds(args[\"amount\"], new_balance)\n",
" new_balance = result[\"balance\"]\n",
" tool_messages.append({\n",
" \"role\": \"tool\",\n",
" \"tool_call_id\": tc.id,\n",
" \"content\": json.dumps(result)\n",
" })\n",
"\n",
" elif name == \"check_balance\":\n",
" result = check_balance(new_balance)\n",
" tool_messages.append({\n",
" \"role\": \"tool\",\n",
" \"tool_call_id\": tc.id,\n",
" \"content\": json.dumps(result)\n",
" })\n",
"\n",
" return tool_messages, new_balance\n"
]
},
{
"cell_type": "code",
"execution_count": 72,
"id": "7200d772",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"* Running on local URL: http://127.0.0.1:7879\n",
"* To create a public link, set `share=True` in `launch()`.\n"
]
},
{
"data": {
"text/html": [
"<div><iframe src=\"http://127.0.0.1:7879/\" width=\"100%\" height=\"500\" allow=\"autoplay; camera; microphone; clipboard-read; clipboard-write;\" frameborder=\"0\" allowfullscreen></iframe></div>"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/plain": []
},
"execution_count": 72,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def chat(message, history, balance):\n",
" # Build messages from history (which already includes the welcome assistant turn)\n",
" messages = [{\"role\": \"system\", \"content\": SYS_PROMPT}] + history + [\n",
" {\"role\": \"user\", \"content\": message}\n",
" ]\n",
"\n",
" resp = openai.chat.completions.create(model=MODEL, messages=messages, tools=tools)\n",
" assistant_msg = resp.choices[0].message\n",
"\n",
" # If the model called tools\n",
" if getattr(assistant_msg, \"tool_calls\", None):\n",
" # 1) Append the assistant's tool-call turn as a normal message dict\n",
" messages.append({\n",
" \"role\": \"assistant\",\n",
" \"content\": assistant_msg.content or \"\",\n",
" \"tool_calls\": [\n",
" {\n",
" \"id\": tc.id,\n",
" \"type\": \"function\",\n",
" \"function\": {\n",
" \"name\": tc.function.name,\n",
" \"arguments\": tc.function.arguments,\n",
" },\n",
" } for tc in assistant_msg.tool_calls\n",
" ],\n",
" })\n",
"\n",
" # 2) Execute tools and extend with tool messages\n",
" tool_msgs, balance = handle_tool_call(assistant_msg, balance)\n",
" messages.extend(tool_msgs) # NOT append\n",
"\n",
" # 3) Ask the model to finalize using tool results\n",
" resp2 = openai.chat.completions.create(model=MODEL, messages=messages, tools=tools)\n",
" return resp2.choices[0].message.content, balance\n",
"\n",
" # No tools—just return the assistant text\n",
" return assistant_msg.content, balance\n",
"\n",
"# Pre-seed the Chatbot with the welcome as the first assistant message\n",
"chatbot = gr.Chatbot(\n",
" value=[{\"role\": \"assistant\", \"content\": WELCOME}],\n",
" type=\"messages\",\n",
" height=420\n",
")\n",
"\n",
"balance_state = gr.State(0.0)\n",
"\n",
"gr.ChatInterface(\n",
" fn=chat,\n",
" chatbot=chatbot, # <-- inject our pre-seeded Chatbot\n",
" type=\"messages\",\n",
" additional_inputs=[balance_state], # read current balance\n",
" additional_outputs=[balance_state], # write updated balance\n",
").launch()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "98256294",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"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.12.11"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -10,7 +10,7 @@
"## Please run this next cell to gather some important data\n",
"\n",
"Please run the next cell; it should take a minute or so to run (mostly the network test).\n",
"Rhen email me the output of the last cell to ed@edwarddonner.com. \n",
"Then email me the output of the last cell to ed@edwarddonner.com. \n",
"Alternatively: this will create a file called report.txt - just attach the file to your email."
]
},

View File

@@ -0,0 +1,157 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "33314df1",
"metadata": {},
"source": [
"# Webpage Summarizer with Ollama\n",
"\n",
"Scrape any webpage and get a quick summary using Ollama 3.2.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "64917274",
"metadata": {},
"outputs": [],
"source": [
"# Cell 1: Setup and Dependencies\n",
"import requests\n",
"from bs4 import BeautifulSoup\n",
"import ollama\n",
"\n",
"# Check if Ollama is running\n",
"try:\n",
" ollama.list()\n",
" print(\"✓ Ollama is running!\")\n",
"except Exception as e:\n",
" print(\"⚠ Warning: Can't connect to Ollama. Make sure it's running!\")\n",
" print(\" Run 'ollama serve' in your terminal if needed.\")\n",
"\n",
"print(\"\\nReady to summarize webpages!\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "981ff805",
"metadata": {},
"outputs": [],
"source": [
"# Cell 2: Set URL to scrape\n",
"# Change this URL to whatever webpage you want to summarize\n",
"url = \"https://github.com/maherp24\"\n",
"\n",
"print(f\"Will summarize: {url}\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5a45d7c1",
"metadata": {},
"outputs": [],
"source": [
"try:\n",
" # Get the webpage\n",
" response = requests.get(url, timeout=10)\n",
" response.raise_for_status()\n",
" \n",
" print(\"✓ Webpage fetched!\")\n",
" \n",
" # Parse HTML\n",
" soup = BeautifulSoup(response.content, 'html.parser')\n",
" \n",
" # Extract text from paragraphs\n",
" paragraphs = soup.find_all('p')\n",
" text = ' '.join([p.get_text() for p in paragraphs])\n",
" \n",
" # Clean up whitespace\n",
" text = ' '.join(text.split())\n",
" \n",
" # Limit to 4000 characters to avoid token issues\n",
" if len(text) > 4000:\n",
" text = text[:4000]\n",
" print(f\"✓ Extracted {len(text)} characters (truncated to 4000)\")\n",
" else:\n",
" print(f\"✓ Extracted {len(text)} characters\")\n",
" \n",
" print(f\"\\nFirst 200 characters:\\n{text[:200]}...\")\n",
" \n",
"except Exception as e:\n",
" print(f\"❌ Error scraping webpage: {e}\")\n",
" text = None\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "66ea2618",
"metadata": {},
"outputs": [],
"source": [
"# Cell 4: Summarize with Ollama\n",
"if text:\n",
" print(\"\\nGenerating summary with Ollama...\")\n",
" print(\"This might take a few seconds...\\n\")\n",
" \n",
" try:\n",
" # Call Ollama\n",
" response = ollama.chat(\n",
" model='llama3.2',\n",
" messages=[{\n",
" 'role': 'user',\n",
" 'content': f'Summarize this webpage content in 3-5 sentences:\\n\\n{text}'\n",
" }]\n",
" )\n",
" \n",
" summary = response['message']['content']\n",
" \n",
" print(\"=\" * 60)\n",
" print(\"SUMMARY\")\n",
" print(\"=\" * 60)\n",
" print(summary)\n",
" print(\"=\" * 60)\n",
" \n",
" except Exception as e:\n",
" print(f\"❌ Error with Ollama: {e}\")\n",
" print(\"\\nMake sure:\")\n",
" print(\" 1. Ollama is running ('ollama serve')\")\n",
" print(\" 2. llama3.2 model is installed ('ollama pull llama3.2')\")\n",
"else:\n",
" print(\"No text to summarize (scraping failed)\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d78d1ee1",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"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.12.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,12 @@
# python newbie, just playing around with creating the illusion of memory
from openai import OpenAI
openai=OpenAI()
message=[{"role":"system","content":"you are a sarcastic assistant"}]
while True:
userinput=input("Enter msg: ")
message.append({"role":"user","content":userinput})
response=openai.chat.completions.create(model="gpt-4.1-mini", messages=message)
print(response.choices[0].message.content)
message.append({"role":"assistant","content":response.choices[0].message.content})

View File

@@ -0,0 +1,151 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "0bb7f4e9",
"metadata": {},
"source": [
"### Prepare dependencies\n",
"I had issues with selenium and my chromedriver, I had to install the exact dependencies below to make it work.\n",
"First add the dependency selenium by executing folowing command\n",
"``` bash\n",
"uv pip install selenium==4.11.2\n",
"uv pip install urllib3==1.26.16\n",
"```\n",
"***Do not forget to restart the Jupyter kernel to make the package available.***"
]
},
{
"cell_type": "markdown",
"id": "7a116541",
"metadata": {},
"source": [
"### Prefered Web Browser\n",
"This script will use Safari on MacOSX, please install the Safari driver when required [Safari driver](https://webkit.org/blog/6900/webdriver-support-in-safari-10).\n",
"It will assume that Edge is used on Windows systems, install the Chrome driver from: [ChromeDriver](https://googlechromelabs.github.io/chrome-for-testing/#stable) \n",
"Feel free to add other browser support when required.\n",
"\n",
"I am on Windows and I extracted the ChromeDriver and put it in my %USERPROFILE%\\AppData\\Local\\Microsoft\\WindowsApps folder to ensure that it is available via the PATH. Any folder available in the PATH environment setting will work.\n",
"Start cmd and execute set to see all folders in the PATH environment setting.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1353f8ef",
"metadata": {},
"outputs": [],
"source": [
"# imports\n",
"\n",
"import os\n",
"from dotenv import load_dotenv\n",
"from scraper import fetch_website_contents\n",
"from IPython.display import Markdown, display\n",
"from openai import OpenAI\n",
"from selenium import webdriver"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "24d86842",
"metadata": {},
"outputs": [],
"source": [
"def verify_openai_api_key():\n",
" \"\"\"Verify that the OpenAI API key is set in the environment variables.\"\"\"\n",
" load_dotenv(override=True)\n",
" api_key = os.getenv('OPENAI_API_KEY')\n",
"\n",
" if not api_key:\n",
" raise ValueError(\"OPENAI_API_KEY is not set in environment variables.\")\n",
" \n",
" # Dry run with a simple request to verify the key.\n",
" try:\n",
" client = OpenAI(api_key=api_key)\n",
" client.models.list()\n",
" except:\n",
" raise ValueError(\"Invalid OPENAI_API_KEY.\")\n",
" \n",
" return api_key\n",
"\n",
"def get_webdriver():\n",
" \"\"\"Initialize and return a Selenium WebDriver based on the operating system.\"\"\"\n",
" \n",
" # Verify the os, use Safari for MacOS, Chrome for others.\n",
" if os.name == 'posix': \n",
" driver = webdriver.Safari()\n",
" else:\n",
" driver = webdriver.Chrome()\n",
" return driver\n",
"\n",
"def fetch_website_contents_selenium(url: str) -> str:\n",
" \"\"\"Fetch website contents using Selenium WebDriver.\"\"\"\n",
" driver = get_webdriver()\n",
" driver.get(url)\n",
" content = driver.page_source\n",
" driver.quit()\n",
" return content\n",
"\n",
"def messages_for(website: str) -> list[dict]:\n",
" return [\n",
" {\n",
" \"role\": \"system\",\n",
" \"content\": \"\"\"You are a helpful assistant that summarizes website content.\n",
" You can perfectly understand HTML structure and extract meaningful information from it.\"\"\"\n",
" },\n",
" {\n",
" \"role\": \"user\",\n",
" \"content\": f\"Summarize the following website content:\\n\\n{website}\"\n",
" }\n",
" ]\n",
"\n",
"def summarize_website(url: str, api_key: str) -> str:\n",
" content = fetch_website_contents_selenium(url)\n",
" openai_client = OpenAI(api_key=api_key)\n",
" response = openai_client.chat.completions.create(\n",
" model = \"gpt-4.1-mini\",\n",
" messages = messages_for(content)\n",
" )\n",
" return response.choices[0].message.content\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9a7e7f0a",
"metadata": {},
"outputs": [],
"source": [
"try:\n",
" api_key = verify_openai_api_key()\n",
" print(summarize_website(\"https://www.forbes.com/\", api_key))\n",
"except Exception as e:\n",
" print(f\"Error: {e}\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "llm-engineering",
"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.12.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,226 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "fe12c203-e6a6-452c-a655-afb8a03a4ff5",
"metadata": {},
"source": [
"# End of week 1 exercise\n",
"\n",
"To demonstrate your familiarity with OpenAI API, and also Ollama, build a tool that takes a technical question, \n",
"and responds with an explanation. This is a tool that you will be able to use yourself during the course!"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c1070317-3ed9-4659-abe3-828943230e03",
"metadata": {},
"outputs": [],
"source": [
"# imports\n",
"import os\n",
"from dotenv import load_dotenv\n",
"from openai import OpenAI\n",
"import ollama\n",
"import ipywidgets as widgets\n",
"from IPython.display import display, Markdown"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4a456906-915a-4bfd-bb9d-57e505c5093f",
"metadata": {},
"outputs": [],
"source": [
"# constants\n",
"\n",
"MODEL_GEMINI = \"gemini-2.5-flash\"\n",
"MODEL_LLAMA = \"llama3.1:8b\"\n",
"\n",
"CHOICE_GEMINI = \"gemini\"\n",
"CHOICE_OLLAMA = \"ollama\"\n",
"\n",
"SYSTEM_PROMPT = (\n",
" \"You are a technical adviser. The student is learning LLM engineering \"\n",
" \"and you will be asked to explain lines of code with an example, \"\n",
" \"mostly in Python.\"\n",
" \"You can answer other questions as well.\"\n",
")\n",
"\n",
"GEMINI_BASE_URL = \"https://generativelanguage.googleapis.com/v1beta/openai/\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a8d7923c-5f28-4c30-8556-342d7c8497c1",
"metadata": {},
"outputs": [],
"source": [
"# set up environment\n",
"load_dotenv(override=True)\n",
"google_api_key = os.getenv(\"GOOGLE_API_KEY\")\n",
"\n",
"if not google_api_key:\n",
" print(\"Warning: GOOGLE_API_KEY not found. Gemini calls will fail.\")\n",
" print(\"Please create a .env file with GOOGLE_API_KEY=your_key\")\n",
"\n",
"gemini_client = OpenAI(\n",
" base_url=GEMINI_BASE_URL,\n",
" api_key=google_api_key,\n",
")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3f0d0137-52b0-47a8-81a8-11a90a010798",
"metadata": {},
"outputs": [],
"source": [
"# here is the question; type over this to ask something new\n",
"\n",
"question = \"\"\"\n",
"Please explain what this code does and why:\n",
"yield from {book.get(\"author\") for book in books if book.get(\"author\")}\n",
"\"\"\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "60ce7000-a4a5-4cce-a261-e75ef45063b4",
"metadata": {},
"outputs": [],
"source": [
"def make_messages(user_question: str):\n",
" return [\n",
" {\"role\": \"system\", \"content\": SYSTEM_PROMPT},\n",
" {\"role\": \"user\", \"content\": user_question},\n",
" ]\n",
"\n",
"\n",
"def stream_gemini(messages):\n",
" \"\"\"Stream response chunks from Gemini.\"\"\"\n",
" stream = gemini_client.chat.completions.create(\n",
" model=MODEL_GEMINI,\n",
" messages=messages,\n",
" stream=True,\n",
" )\n",
"\n",
" full = []\n",
" for chunk in stream:\n",
" piece = chunk.choices[0].delta.content or \"\"\n",
" full.append(piece)\n",
" return \"\".join(full)\n",
"\n",
"\n",
"def stream_ollama(messages):\n",
" \"\"\"Stream response chunks from local Ollama.\"\"\"\n",
" stream = ollama.chat(\n",
" model=MODEL_LLAMA,\n",
" messages=messages,\n",
" stream=True,\n",
" )\n",
"\n",
" full = []\n",
" for chunk in stream:\n",
" piece = chunk[\"message\"][\"content\"]\n",
" full.append(piece)\n",
" return \"\".join(full)\n",
"\n",
"\n",
"def get_explanation(question: str, model_choice: str):\n",
" \"\"\"Gets a technical explanation from the chosen model and streams the response.\"\"\"\n",
" messages = make_messages(question)\n",
" try:\n",
" if model_choice == CHOICE_GEMINI:\n",
" return stream_gemini(messages)\n",
" elif model_choice == CHOICE_OLLAMA:\n",
" return stream_ollama(messages)\n",
" else:\n",
" print(\"Unknown model choice.\")\n",
" return \"\"\n",
" except Exception as e:\n",
" print(f\"\\nAn error occurred: {e}\")\n",
" return \"\"\n",
"\n",
"print(\"💡 Your personal technical tutor is ready.\\n\")\n",
"\n",
"# Dropdown for model selection\n",
"model_dropdown = widgets.Dropdown(\n",
" options=[\n",
" (\"Gemini (gemini-2.5-flash)\", CHOICE_GEMINI),\n",
" (\"Ollama (llama3.1:8b)\", CHOICE_OLLAMA),\n",
" ],\n",
" value=CHOICE_GEMINI,\n",
" description=\"Model:\",\n",
" style={\"description_width\": \"initial\"},\n",
")\n",
"\n",
"# Text input for question\n",
"question_box = widgets.Textarea(\n",
" placeholder=\"Type your technical question here...\",\n",
" description=\"Question:\",\n",
" layout=widgets.Layout(width=\"100%\", height=\"100px\"),\n",
" style={\"description_width\": \"initial\"},\n",
")\n",
"\n",
"submit_button = widgets.Button(description=\"Ask\", button_style=\"success\", icon=\"paper-plane\")\n",
"\n",
"output_area = widgets.Output()\n",
"loader_label = widgets.Label(value=\"\")\n",
"\n",
"def on_submit(_):\n",
" output_area.clear_output()\n",
" question = question_box.value.strip()\n",
" if not question:\n",
" with output_area:\n",
" print(\"Please enter a question.\")\n",
" return\n",
"\n",
" loader_label.value = \"⏳ Thinking...\"\n",
" submit_button.disabled = True\n",
"\n",
" answer = get_explanation(question, model_dropdown.value)\n",
"\n",
" loader_label.value = \"\"\n",
" submit_button.disabled = False\n",
"\n",
" with output_area:\n",
" print(f\"🤖 Model: {model_dropdown.label}\")\n",
" print(f\"📜 Question: {question}\\n\")\n",
" display(Markdown(answer))\n",
" print(\"\\n--- End of response ---\")\n",
"\n",
"submit_button.on_click(on_submit)\n",
"\n",
"# Display everything\n",
"display(model_dropdown, question_box, submit_button, loader_label, output_area)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "llm-engineering (3.12.10)",
"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.12.10"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,43 @@
# Image Generator
Quick image generator using OpenAI's DALL-E 3. Generates 1024x1024 images from either random themes or your own prompts.
## Setup
You'll need an OpenAI API key. Set it as an environment variable:
```bash
export OPENAI_API_KEY='your-key-here'
```
Or just enter it when the notebook prompts you.
## How to Use
1. Open `day1_random_img_generator.ipynb`
2. Run the cells in order
3. When prompted, type either:
- `random` - picks a random theme from the list
- `custom` - lets you enter your own prompt
4. Wait 10-30 seconds for the image to generate
5. Image shows up in the notebook
## What's Inside
- 15 pre-made themes (cyberpunk cities, fantasy forests, etc.)
- Uses DALL-E 3 for image generation
- Standard quality to keep costs reasonable
- Simple error handling
## Cost Warning
DALL-E 3 costs about $0.04 per image. Not huge but watch your usage.
## Example Prompts
If you go custom, try stuff like:
- "a cat astronaut floating in space"
- "steampunk coffee shop interior"
- "synthwave sunset over ocean waves"
Keep it descriptive for better results.

View File

@@ -0,0 +1,175 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "e1fb2654",
"metadata": {},
"source": [
"# Simple Image Generator with OpenAI DALL-E\n",
"\n",
"Generate beautiful images using AI! Choose a random theme or create your own custom prompt.\n",
"Costs a bit more careful!\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8f856bd8",
"metadata": {},
"outputs": [],
"source": [
"# Import libraries\n",
"import os\n",
"from openai import OpenAI\n",
"from PIL import Image\n",
"import requests\n",
"from io import BytesIO\n",
"\n",
"# Get OpenAI API key\n",
"api_key = os.getenv('OPENAI_API_KEY')\n",
"\n",
"if not api_key:\n",
" print(\"OPENAI_API_KEY not found in environment.\")\n",
" api_key = input(\"Please enter your OpenAI API key: \").strip()\n",
"\n",
"# Initialize OpenAI client\n",
"client = OpenAI(api_key=api_key)\n",
"print(\"✓ OpenAI client initialized!\")\n",
"print(\"Ready to generate images!\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1c49df89",
"metadata": {},
"outputs": [],
"source": [
"# Cell 2: Define themes\n",
"import random\n",
"\n",
"# List of fun themes to choose from\n",
"themes = [\n",
" \"a cyberpunk city at night with neon lights\",\n",
" \"a magical fantasy forest with glowing mushrooms\",\n",
" \"a cute robot having tea in a garden\",\n",
" \"an astronaut riding a horse on Mars\",\n",
" \"a cozy cabin in snowy mountains at sunset\",\n",
" \"a steampunk flying machine above clouds\",\n",
" \"a peaceful Japanese garden in autumn\",\n",
" \"a dragon sleeping on a pile of books\",\n",
" \"an underwater city with colorful fish\",\n",
" \"a vintage car on a desert highway at golden hour\",\n",
" \"a cozy coffee shop on a rainy day\",\n",
" \"a majestic phoenix rising from flames\",\n",
" \"a futuristic space station orbiting Earth\",\n",
" \"a whimsical treehouse village in the clouds\",\n",
" \"a serene Buddhist temple on a mountain peak\"\n",
"]\n",
"\n",
"print(\"Available themes loaded!\")\n",
"print(f\"Total themes: {len(themes)}\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "31775d64",
"metadata": {},
"outputs": [],
"source": [
"# Cell 3: Get user input\n",
"print(\"IMAGE GENERATOR - Choose Your Option\")\n",
"\n",
"choice = input(\"Type 'random' for a random theme or 'custom' for your own prompt: \").strip().lower()\n",
"\n",
"if choice == 'random':\n",
" prompt = random.choice(themes)\n",
" print(f\"\\n Random theme selected!\")\n",
" print(f\"Prompt: {prompt}\")\n",
"else:\n",
" prompt = input(\"\\nEnter your custom image prompt: \").strip()\n",
" if not prompt:\n",
" prompt = random.choice(themes)\n",
" print(f\"No input provided, using random theme: {prompt}\")\n",
" else:\n",
" print(f\"Using your custom prompt: {prompt}\")\n",
"\n",
"print(\"\\n✓ Prompt ready for generation!\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3cdc5ddc",
"metadata": {},
"outputs": [],
"source": [
"# Cell 4: Generate and display image\n",
"print(\"\\n Generating image...\")\n",
"print(\"This may take 10-30 seconds...\")\n",
"\n",
"try:\n",
" # Call DALL-E API\n",
" response = client.images.generate(\n",
" model=\"dall-e-3\",\n",
" prompt=prompt,\n",
" size=\"1024x1024\",\n",
" quality=\"standard\",\n",
" n=1,\n",
" )\n",
" \n",
" # Get image URL\n",
" image_url = response.data[0].url\n",
" \n",
" print(\"✓ Image generated successfully!\")\n",
" print(f\"\\nImage URL: {image_url}\")\n",
" \n",
" # Download and display image\n",
" print(\"\\nDownloading image...\")\n",
" response = requests.get(image_url)\n",
" img = Image.open(BytesIO(response.content))\n",
" \n",
" print(\"✓ Image ready!\")\n",
" print(\"\\nDisplaying image below:\")\n",
" display(img)\n",
" \n",
" # Store for optional saving later\n",
" generated_image = img\n",
" \n",
"except Exception as e:\n",
" print(f\"❌ Error generating image: {str(e)}\")\n",
" generated_image = None\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ead2c877",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"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.12.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,44 @@
# Week 1 Exercise - Tech Question Explainer
**Author:** Philip Omoigui
## Description
A technical question explainer tool that demonstrates familiarity with both OpenAI API and Ollama. This tool:
- Takes a technical question
- Gets responses from both GPT-4o-mini and Llama 3.2
- Compares the two responses using GPT-5-nano
- Evaluates which explanation is better for beginners
## Features
- **Dual Model Response**: Uses both OpenAI's GPT-4o-mini and local Llama 3.2
- **Streaming Output**: Real-time display of responses as they're generated
- **Automated Comparison**: Uses GPT-5-nano to evaluate and rank both responses
- **Beginner-Focused**: Optimized for educational content with clear, beginner-friendly explanations
## Evaluation Criteria
The comparison evaluates responses on:
1. **Beginner Friendliness** - How easy is it for a beginner to understand?
2. **Tackles Main Point** - How well does it address the core concept?
3. **Clear Examples** - How effective are the examples and explanations?
## Files
- `week1_EXERCISE.ipynb` - Main notebook with the tech explainer implementation
## Usage
Simply modify the `question` variable with your technical question, and the tool will:
1. Generate explanations from both models
2. Stream the responses in real-time
3. Automatically compare and evaluate which explanation is better
## Requirements
- OpenAI API key
- Ollama running locally with llama3.2 model
- Python packages: openai, python-dotenv, IPython

View File

@@ -0,0 +1,335 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "fe12c203-e6a6-452c-a655-afb8a03a4ff5",
"metadata": {},
"source": [
"# End of week 1 exercise\n",
"\n",
"To demonstrate your familiarity with OpenAI API, and also Ollama, build a tool that takes a technical question, \n",
"and responds with an explanation. This is a tool that you will be able to use yourself during the course!"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "c1070317-3ed9-4659-abe3-828943230e03",
"metadata": {},
"outputs": [],
"source": [
"# imports\n",
"from IPython.display import display, update_display, Markdown\n",
"import os\n",
"from dotenv import load_dotenv\n",
"from openai import OpenAI \n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "4a456906-915a-4bfd-bb9d-57e505c5093f",
"metadata": {},
"outputs": [],
"source": [
"# constants\n",
"\n",
"MODEL_GPT = 'gpt-4o-mini'\n",
"MODEL_LLAMA = 'llama3.2'"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "a8d7923c-5f28-4c30-8556-342d7c8497c1",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"API key found and looks good so far!\n"
]
}
],
"source": [
"# set up environment\n",
"load_dotenv(override=True)\n",
"api_key = os.getenv('OPENAI_API_KEY')\n",
"\n",
"if not api_key:\n",
" print(\"No API key was found - please head over to the troubleshooting notebook in this folder to identify & fix!\")\n",
"elif not api_key.startswith(\"sk-proj-\"):\n",
" print(\"An API key was found, but it doesn't start sk-proj-; please check you're using the right key - see troubleshooting notebook\")\n",
"else:\n",
" print(\"API key found and looks good so far!\")"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "3f0d0137-52b0-47a8-81a8-11a90a010798",
"metadata": {},
"outputs": [],
"source": [
"# here is the question; type over this to ask something new\n",
"question = \"\"\"\n",
"Please explain what this code does and why:\n",
"yield from {book.get(\"author\") for book in books if book.get(\"author\")}\n",
"\"\"\"\n",
"\n",
"system_prompt = \"\"\"\n",
"You are python expert who is skilled in explaining code and helping people understand it.\n",
"Make sure to explain the code in a way that is easy to understand.\n",
"Keep your explanations concise and to the point.\n",
"Make sure to make it beginner friendly.\n",
"\"\"\"\n",
"\n",
"messages = [\n",
" {\"role\": \"system\", \"content\": system_prompt},\n",
" {\"role\": \"user\", \"content\" : question}\n",
"]"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "60ce7000-a4a5-4cce-a261-e75ef45063b4",
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"Certainly! Let's break down the code snippet you provided:\n",
"\n",
"```python\n",
"yield from {book.get(\"author\") for book in books if book.get(\"author\")}\n",
"```\n",
"\n",
"### Explanation:\n",
"\n",
"1. **`{book.get(\"author\") for book in books if book.get(\"author\")}`**:\n",
" - This part is a **set comprehension**. A set comprehension is used to create a set (a collection of unique items) in Python.\n",
" - It iterates over a list called `books` (which is expected to be a collection of book data).\n",
" - For each `book`, it tries to get the value associated with the key `\"author\"` using `book.get(\"author\")`. \n",
" - If the author exists (meaning its not `None` or an empty string), it adds that author to the set.\n",
" - The result is a set of unique author names from the list of books.\n",
"\n",
"2. **`yield from`**:\n",
" - This keyword is used in generator functions. It allows you to yield all values from an iterable (like a set or a list) one by one.\n",
" - In this case, it means that for each unique author in the set that was created, a value will be returned each time the generator is iterated over. \n",
"\n",
"### Why It Matters:\n",
"- **Efficiency**: By using a set, it ensures that each author is unique and there are no duplicates.\n",
"- **Generator Function**: The use of `yield from` indicates that this code is likely part of a generator function, making it memory efficient since it generates values on-the-fly rather than storing them all in memory.\n",
"\n",
"### Summary:\n",
"This code snippet efficiently gathers unique authors from a list of books and yields each author one at a time when the generator is iterated upon. It's a concise way to collect and return unique data from potentially large datasets without memory overhead."
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Get gpt-4o-mini to answer, with streaming\n",
"openai = OpenAI()\n",
"\n",
"stream = openai.chat.completions.create(model=MODEL_GPT, \n",
" messages= messages,\n",
" stream=True)\n",
"\n",
"gpt_response = \"\"\n",
"display_handle = display(Markdown(\"\"), display_id=True)\n",
"\n",
"for chunk in stream:\n",
" gpt_response += chunk.choices[0].delta.content or ''\n",
" update_display(Markdown(gpt_response), display_id=display_handle.display_id)\n"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "8f7c8ea8-4082-4ad0-8751-3301adcf6538",
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"**Explanation:**\n",
"\n",
"This code snippet is using a technique called \"generator expression\" to extract author names from a list of dictionaries. Let's break it down:\n",
"\n",
"* `yield from`: This keyword is used to yield values from an iterable (in this case, a generator).\n",
"* `{book.get(\"author\") for book in books if book.get(\"author\")}`: This is a tuple-comprehension that performs the following operations:\n",
"\t+ `for book in books`: Iterates over each dictionary (book) in the `books` list.\n",
"\t+ `if book.get(\"author\")`: Filters out dictionaries that don't have an author name. It uses the `.get()` method to safely retrieve a value from the dictionary, avoiding a KeyError if \"author\" doesn't exist.\n",
"\t+ `book.get(\"author\")`: Extracts the author's name (or None if it doesn't exist) from each filtered dictionary.\n",
"* `{...}`: This is an empty dictionary. It's used as a container to hold the extracted values.\n",
"\n",
"**Why?**\n",
"\n",
"The purpose of this code snippet is likely to provide a list of authors' names, where:\n",
"\n",
"1. Not all books have an author name (in which case `None` will be included in the result).\n",
"2. The input data (`books`) might contain dictionaries with only certain keys (e.g., `title`, `description`, etc.), while this code assumes that each book dictionary must have a key named \"author\".\n",
"\n",
"**Example use cases:**\n",
"\n",
"When working with large datasets or data structures, generator expressions like this one can help you:\n",
"\n",
"* Process data in chunks, avoiding excessive memory consumption\n",
"* Work with complex, nested data structures (in this case, a list of dictionaries)\n",
"* Improve code readability by encapsulating logic within a single expression\n",
"\n",
"Keep in mind that the resulting values will be yielded on-the-fly as you iterate over them, rather than having all authors' names stored in memory at once."
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Get Llama 3.2 to answer\n",
"OLLAMA_BASE_URL = \"http://localhost:11434/v1\"\n",
"ollama = OpenAI(base_url=OLLAMA_BASE_URL, api_key='ollama')\n",
"\n",
"stream = ollama.chat.completions.create(model=MODEL_LLAMA, \n",
" messages= messages,\n",
" stream=True)\n",
"\n",
"ollama_response = \"\"\n",
"display_handle = display(Markdown(\"\"), display_id=True)\n",
"\n",
"for chunk in stream:\n",
" ollama_response += chunk.choices[0].delta.content or ''\n",
" update_display(Markdown(ollama_response), display_id=display_handle.display_id)"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "08c5f646",
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"## 📊 Comparison Results\n",
"\n",
"Heres a side-by-side rating and justification for each criterion.\n",
"\n",
"Response A (GPT-4o-mini)\n",
"\n",
"- Beginner Friendliness: 4/5\n",
" - Why: It explains the two main parts (set comprehension and yield from) in straightforward terms and uses bullet points. Its easy to follow for someone with basic Python knowledge. Minor improvement could be more emphasis on what a generator function is and when youd use this pattern.\n",
"\n",
"- Tackles Main Point: 4/5\n",
" - Why: It correctly identifies the core ideas: using a set comprehension to collect unique authors and using yield from to yield items from that iterable. It also notes potential memory-related considerations and the generator nature. The nuance about memory efficiency could be clarified (the set is built in memory first), but the core point is solid.\n",
"\n",
"- Clear Examples: 4/5\n",
" - Why: It provides a succinct, concrete breakdown of each piece of the expression and a concise summary of the result. It would be even better with a short concrete example of input and the yielded values, but the explanation is clear and actionable.\n",
"\n",
"Response B (Llama 3.2)\n",
"\n",
"- Beginner Friendliness: 2/5\n",
" - Why: Contains several incorrect or misleading statements (calls the comprehension a “tuple-comprehension,” says the container is an empty dictionary, misdescribes yield from usage, and incorrectly claims that None would be yielded). These errors can confuse beginners and propagate misconceptions.\n",
"\n",
"- Tackles Main Point: 2/5\n",
" - Why: Several core misunderstandings (generator expression vs set comprehension, the effect of the if filter, and the nature of the resulting container) obscure the main concept. The explanation diverges from what the code does, reducing its usefulness as a core clarifier.\n",
"\n",
"- Clear Examples: 2/5\n",
" - Why: While it attempts to describe use cases and benefits of generator-like processing, the inaccuracies undermine the usefulness of the examples. It also incorrectly describes the resulting container and behavior of the code, making the examples less reliable for learning.\n",
"\n",
"Overall recommendation\n",
"\n",
"- Better for a beginner: Response A\n",
" - Rationale: Response A is accurate, coherent, and focused on the essential ideas (set comprehension, deduplication, and how yield from works). It avoids the factual errors present in Response B and offers a clearer path to understanding the snippet. Response B contains multiple conceptual mistakes that could mislead beginners. If you want to improve Response B, youd need to correct terminology (set vs. dict, generator vs. set comprehension) and fix the behavior description (no None is yielded due to the filter)."
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Compare the two responses using GPT-5-nano\n",
"comparison_prompt = f\"\"\"\n",
"You are an expert evaluator of educational content. Compare the following two explanations of the same Python code and rank them on a scale of 1-5 for each criterion:\n",
"\n",
"**Criteria:**\n",
"1. **Beginner Friendliness**: How easy is it for a beginner to understand?\n",
"2. **Tackles Main Point**: How well does it address the core concept?\n",
"3. **Clear Examples**: How effective are the examples and explanations?\n",
"\n",
"**Response A (GPT-4o-mini):**\n",
"{gpt_response}\n",
"\n",
"**Response B (Llama 3.2):**\n",
"{ollama_response}\n",
"\n",
"**Instructions:**\n",
"- Rate each response on each criterion from 1 (poor) to 5 (excellent)\n",
"- Provide a brief justification for each rating\n",
"- Give an overall recommendation on which response is better for a beginner\n",
"\n",
"Please format your response clearly with the ratings and justifications.\n",
"\"\"\"\n",
"\n",
"comparison_messages = [\n",
" {\"role\": \"user\", \"content\": comparison_prompt}\n",
"]\n",
"\n",
"stream = openai.chat.completions.create(\n",
" model=\"gpt-5-nano\",\n",
" messages=comparison_messages,\n",
" stream=True\n",
")\n",
"\n",
"comparison_response = \"\"\n",
"display_handle = display(Markdown(\"## 📊 Comparison Results\\n\\n\"), display_id=True)\n",
"\n",
"for chunk in stream:\n",
" comparison_response += chunk.choices[0].delta.content or ''\n",
" update_display(Markdown(\"## 📊 Comparison Results\\n\\n\" + comparison_response), display_id=display_handle.display_id)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "13deb6e6",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"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.12.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,6 @@
# Ollama Configuration
OLLAMA_BASE_URL=http://localhost:11434/v1
OLLAMA_MODEL=llama3.2
# OpenAI (if you want to use OpenAI API instead)
# OPENAI_API_KEY=your_api_key_here

View File

@@ -0,0 +1,144 @@
# Code Explainer - LLM Engineering Week 1
An AI-powered code explanation tool using Llama 3.2 via Ollama.
## 🎯 Project Overview
This project demonstrates prompt engineering and local LLM integration by building a code explanation assistant. The tool analyzes code snippets and provides beginner-friendly, line-by-line explanations.
## ✨ Features
- **Local LLM Integration**: Uses Ollama with Llama 3.2 (no API costs!)
- **Two Modes of Operation**:
- 📓 **Notebook Mode**: Interactive Jupyter notebook with rich Markdown display
- 💻 **Terminal Mode**: Interactive CLI for continuous code explanation
- **Smart Explanations**:
- Summarizes overall purpose
- Line-by-line breakdown
- Highlights key programming concepts
- Beginner-friendly language
- **Multiple Examples**: Loops, list comprehensions, OOP, recursion, decorators
- **Streaming Responses**: Real-time output as the model generates
## 🛠️ Technologies Used
- **Ollama**: Local LLM runtime
- **Llama 3.2**: Meta's language model
- **OpenAI Python SDK**: API-compatible interface
- **IPython**: Rich markdown display
- **python-dotenv**: Environment management
## 📋 Prerequisites
1. **Ollama installed** - Download from [ollama.com](https://ollama.com)
2. **Python 3.8+**
3. **Llama 3.2 model pulled**:
```bash
ollama pull llama3.2
```
## 🚀 Setup
### 1. Clone the repository
```bash
git clone <your-repo-url>
cd llm-engineering/week1/my-solutions
```
### 2. Install dependencies
```bash
pip install -r requirements.txt
```
### 3. (Optional) Configure environment
```bash
cp .env.example .env
# Edit .env if needed
```
## 💡 Usage
### Notebook Mode
1. Open `day1-solution.ipynb` in Jupyter or VS Code
2. Run cells sequentially
3. Use `explain_code_interactive()` function with your own code
```python
explain_code_interactive("""
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
""")
```
### Terminal Mode
```bash
python code_explainer.py
```
Then:
- Paste your code
- Press Enter twice (empty line)
- Get explanation!
**Commands:**
- `quit` / `exit` / `q` - Exit
- `clear` - Start fresh
- `examples` - See sample code snippets
## 🎓 Why Ollama?
I chose Ollama over OpenAI API for this project because:
✅ **No API Costs**: Completely free to use
✅ **Privacy**: All data stays local
✅ **Offline**: Works without internet
✅ **Learning**: Hands-on experience with local LLM deployment
✅ **Speed**: Fast responses on local hardware
## 📝 Examples Included
1. **Recursion** - Fibonacci sequence
2. **Loops** - Simple iteration
3. **List Comprehensions** - Filtering and mapping
4. **Object-Oriented Programming** - Classes and methods
5. **Decorators** - Advanced Python concepts
## 🔧 Customization
### Change the model
Edit `code_explainer.py`:
```python
explainer = CodeExplainer(model="llama3.2:3b") # Use smaller model
```
### Adjust temperature
Lower temperature = more consistent, Higher = more creative:
```python
temperature=0.3 # Current setting for code explanations
```
### Modify system prompt
Customize how the model explains code in the `system_prompt` variable.
## 🤝 Contributing
This is a course assignment, but feel free to fork and improve!
## 📄 License
MIT License - feel free to use for learning purposes.
## 👤 Author
İrem İrem
LLM Engineering Course - Week 1 Assignment
## 🙏 Acknowledgments
- **Ed Donner** - Course instructor for the excellent LLM Engineering course
- **Anthropic** - For Ollama framework
- **Meta** - For Llama 3.2 model

View File

@@ -0,0 +1,192 @@
#!/usr/bin/env python3
"""
Code Explainer - Interactive Terminal Mode
Day 1 LLM Engineering Assignment
Explains code snippets using Llama 3.2 via Ollama
"""
import os
from dotenv import load_dotenv
from openai import OpenAI
class CodeExplainer:
"""Interactive code explanation assistant using Llama 3.2"""
def __init__(self, model="llama3.2"):
load_dotenv()
self.client = OpenAI(
base_url='http://localhost:11434/v1',
api_key='ollama'
)
self.model = model
print(f"🤖 Using Ollama with model: {self.model}")
# System prompt - defines how the model should behave
self.system_prompt = """You are an experienced programming instructor.
You analyze and explain code snippets line by line.
When explaining code:
1. Summarize the overall purpose of the code
2. Explain each important line or block
3. Highlight key programming concepts used
4. Use language that beginners can understand
5. Include practical examples when helpful
Be clear, educational, and encouraging."""
def explain(self, code, stream=True):
"""Explain the given code snippet"""
messages = [
{"role": "system", "content": self.system_prompt},
{"role": "user", "content": f"Explain the following code:\n\n{code}"}
]
try:
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
stream=stream,
temperature=0.3 # Low temperature for consistent explanations
)
if stream:
print("\n" + "="*60)
print("📝 EXPLANATION:")
print("="*60 + "\n")
answer = ""
for chunk in response:
if chunk.choices[0].delta.content:
text = chunk.choices[0].delta.content
print(text, end="", flush=True)
answer += text
print("\n" + "="*60 + "\n")
return answer
else:
result = response.choices[0].message.content
print(f"\n📝 Explanation:\n{result}\n")
return result
except Exception as e:
print(f"❌ Error: {e}")
return None
def chat(self):
"""Start interactive chat mode"""
print("\n" + "="*60)
print("🎓 CODE EXPLAINER - Interactive Terminal Mode")
print("="*60)
print(f"Model: {self.model}")
print(f"Provider: Ollama (Local)")
print("\n📖 How to use:")
print(" • Paste your code")
print(" • Press Enter twice (empty line) when done")
print(" • Type 'quit', 'exit', or 'q' to stop")
print(" • Type 'clear' to start fresh")
print(" • Type 'examples' to see sample code")
print("="*60 + "\n")
while True:
try:
print("📋 Paste your code below (Enter twice when done):")
print()
# Multi-line input
lines = []
empty_count = 0
while True:
line = input()
# Check for exit commands
if line.strip().lower() in ['quit', 'exit', 'q']:
print("\n👋 Thanks for using Code Explainer! Goodbye!")
return
# Check for clear command
if line.strip().lower() == 'clear':
print("\n🔄 Starting fresh...\n")
break
# Check for examples command
if line.strip().lower() == 'examples':
self.show_examples()
break
# Empty line detection
if not line.strip():
empty_count += 1
if empty_count >= 1 and lines: # One empty line is enough
break
else:
empty_count = 0
lines.append(line)
# If we have code, explain it
if lines:
code = "\n".join(lines)
self.explain(code)
except KeyboardInterrupt:
print("\n\n👋 Thanks for using Code Explainer! Goodbye!")
break
except Exception as e:
print(f"❌ Error: {e}\n")
def show_examples(self):
"""Show example code snippets"""
print("\n" + "="*60)
print("📚 EXAMPLE CODE SNIPPETS")
print("="*60)
examples = {
"1. Simple Loop": """
for i in range(5):
print(i * 2)
""",
"2. List Comprehension": """
numbers = [1, 2, 3, 4, 5]
squares = [x**2 for x in numbers if x % 2 == 0]
print(squares)
""",
"3. Function with Recursion": """
def factorial(n):
if n == 0:
return 1
return n * factorial(n-1)
print(factorial(5))
""",
"4. Class Definition": """
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
return f"{self.name} says Woof!"
"""
}
for title, code in examples.items():
print(f"\n{title}:{code}")
print("="*60)
print("💡 Copy any example above and paste it when prompted!")
print("="*60 + "\n")
def main():
"""Main entry point"""
load_dotenv()
print("\n🚀 Starting Code Explainer...")
explainer = CodeExplainer()
# Start interactive mode
explainer.chat()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,76 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Day 1 Solution - My Implementation\n",
"\n",
"This is my solution to the Day 1 assignment. I've implemented the web scraping and summarization functionality as requested.\n",
"\n",
"## Features Implemented:\n",
"- Web scraping with requests and BeautifulSoup\n",
"- SSL certificate handling for Windows\n",
"- OpenAI API integration\n",
"- Website content summarization\n",
"- Markdown display formatting\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Environment setup complete!\n"
]
}
],
"source": [
"# My Day 1 Solution - Imports and Setup\n",
"import os\n",
"import ssl\n",
"import requests\n",
"from bs4 import BeautifulSoup\n",
"from urllib.parse import urljoin\n",
"from IPython.display import Markdown, display\n",
"from openai import OpenAI\n",
"from dotenv import load_dotenv\n",
"\n",
"# Load environment variables\n",
"load_dotenv(override=True)\n",
"\n",
"# SSL fix for Windows\n",
"ssl._create_default_https_context = ssl._create_unverified_context\n",
"os.environ['PYTHONHTTPSVERIFY'] = '0'\n",
"os.environ['CURL_CA_BUNDLE'] = ''\n",
"\n",
"print(\"Environment setup complete!\")\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"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.12.12"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@@ -0,0 +1,3 @@
openai>=1.0.0
python-dotenv>=1.0.0
ipython>=8.0.0

View File

@@ -0,0 +1,147 @@
# LLM Engineering - Domine IA e LLMs
## Sua jornada de 8 semanas rumo à proficiência começa hoje
![Voyage](assets/voyage.jpg)
_Se você estiver vendo isto no Cursor, clique com o botão direito no nome do arquivo no Explorer à esquerda e selecione "Open preview" para visualizar a versão formatada._
Estou muito feliz por você se juntar a mim nesta jornada. Construiremos projetos extremamente gratificantes nas próximas semanas. Alguns serão fáceis, outros desafiadores, e muitos vão surpreender você! Os projetos se complementam para que você desenvolva experiência cada vez mais profunda a cada semana. De uma coisa tenho certeza: você vai se divertir bastante pelo caminho.
# COMUNICADO IMPORTANTE - OUTUBRO DE 2025 - LEIA POR FAVOR
Estou implementando gradualmente novas versões atualizadas de todos os vídeos do curso, com novos vídeos e novo código. Sei que isso pode ser desconcertante para quem já está no curso, e farei o possível para que a transição seja a mais tranquila possível.
- As duas séries de vídeos estarão disponíveis na Udemy e você poderá assistir a qualquer uma delas. Um novo conjunto de vídeos deve ficar disponível a cada semana conforme fizermos a atualização.
- Você pode seguir os vídeos originais ou os novos vídeos - ambos funcionarão muito bem. Alterne entre eles quando quiser.
- O código mais recente está publicado no repositório. Você pode acompanhar com o código novo ou voltar ao código original.
Os detalhes completos dessa atualização estão nos recursos do curso, em roxo no topo:
https://edwarddonner.com/2024/11/13/llm-engineering-resources/
A mudança mais significativa é que a nova versão usa o excelente uv em vez do Anaconda. Mas também há uma enorme quantidade de conteúdo inédito, incluindo novos modelos, ferramentas e técnicas. Cache de prompts, LiteLLM, técnicas de inferência e muito mais.
### Como isso está organizado na Udemy
Estamos disponibilizando as novas semanas, mas mantendo o conteúdo original em um apêndice:
Na Udemy:
Seção 1 = NOVA SEMANA 1
Seção 2 = NOVA SEMANA 2
Seção 3 = NOVA SEMANA 3
Seção 4 = Semana 4 original
Seção 5 = Semana 5 original
Seção 6 = Semana 6 original
Seção 7 = Semana 7 original
Seção 8 = Semana 8 original
E, como apêndice/arquivo:
Seção 9 = Semana 1 original
Seção 10 = Semana 2 original
Seção 11 = Semana 3 original
### Como voltar à versão original do código, consistente com os vídeos originais (Anaconda + virtualenv)
Se preferir manter o código dos vídeos originais, faça o seguinte a partir do Prompt do Anaconda ou do Terminal:
`git fetch`
`git checkout original`
E pronto! Qualquer dúvida, fale comigo na Udemy ou em ed@edwarddonner.com. Mais detalhes no topo dos recursos do curso [aqui](https://edwarddonner.com/2024/11/13/llm-engineering-resources/).
### Antes de começar
Estou aqui para ajudá-lo a obter o máximo sucesso na sua aprendizagem. Se encontrar qualquer dificuldade ou tiver ideias de como posso melhorar o curso, entre em contato pela plataforma ou envie um e-mail diretamente para mim (ed@edwarddonner.com). É sempre ótimo conectar com as pessoas no LinkedIn para fortalecer a comunidade - você me encontra aqui:
https://www.linkedin.com/in/eddonner/
E esta é uma novidade para mim, mas também estou experimentando o X/Twitter em [@edwarddonner](https://x.com/edwarddonner) - se você estiver no X, mostre-me como se faz!
Os recursos que acompanham o curso, incluindo slides e links úteis, estão aqui:
https://edwarddonner.com/2024/11/13/llm-engineering-resources/
E uma FAQ útil com perguntas frequentes está aqui:
https://edwarddonner.com/faq/
## Instruções de Gratificação Instantânea para a Semana 1, Dia 1 - com Llama 3.2 **não** Llama 3.3
### Nota importante: veja meu alerta sobre a Llama3.3 abaixo - ela é grande demais para computadores domésticos! Fique com a llama3.2 - vários alunos já ignoraram esse aviso...
Vamos começar o curso instalando o Ollama para que você veja resultados imediatamente!
1. Baixe e instale o Ollama em https://ollama.com. Observe que, em PCs, talvez você precise de permissões de administrador para que a instalação funcione corretamente.
2. No PC, abra um Prompt de Comando / Powershell (pressione Win + R, digite `cmd` e pressione Enter). No Mac, abra o Terminal (Aplicativos > Utilitários > Terminal).
3. Execute `ollama run llama3.2` ou, para máquinas mais modestas, `ollama run llama3.2:1b` - **atenção:** evite o modelo mais recente da Meta, o llama3.3, porque com 70B parâmetros ele é grande demais para a maioria dos computadores domésticos!
4. Se isso não funcionar: talvez seja necessário executar `ollama serve` em outro Powershell (Windows) ou Terminal (Mac) e tentar o passo 3 novamente. No PC, talvez você precise estar em uma instância administrativa do Powershell.
5. E, se ainda assim não funcionar na sua máquina, configurei tudo na nuvem. Está no Google Colab, que exige uma conta Google para entrar, mas é gratuito: https://colab.research.google.com/drive/1-_f5XZPsChvfU1sJ0QqCePtIuc55LSdu?usp=sharing
Se surgir qualquer problema, entre em contato comigo!
## Em seguida, instruções de configuração
Depois de concluirmos o projeto rápido com o Ollama e após eu me apresentar e apresentar o curso, partimos para configurar o ambiente completo.
Espero ter feito um bom trabalho para tornar esses guias à prova de falhas - mas entre em contato comigo imediatamente se encontrar obstáculos:
NOVAS INSTRUÇÕES para a nova versão do curso (lançada em outubro de 2025): [Novas instruções de configuração para todas as plataformas](setup/SETUP-new.md)
INSTRUÇÕES ORIGINAIS para quem está na versão anterior a outubro de 2025:
- Usuários de PC, sigam as instruções aqui: [Instruções originais para PC](setup/SETUP-PC.md)
- Usuários de Mac, sigam as instruções aqui: [Instruções originais para Mac](setup/SETUP-mac.md)
- Usuários de Linux, sigam as instruções aqui: [Instruções originais para Linux](setup/SETUP-linux.md)
### Um ponto importante sobre custos de API (que são opcionais! Não é preciso gastar se você não quiser)
Durante o curso, vou sugerir que você experimente os modelos de ponta, conhecidos como Frontier models. Também vou sugerir que execute modelos de código aberto usando o Google Colab. Esses serviços podem gerar alguns custos, mas manterei tudo no mínimo - alguns centavos por vez. E fornecerei alternativas caso você prefira não usá-los.
Monitore seu uso de APIs para garantir que os gastos estejam confortáveis para você; incluí os links abaixo. Não há necessidade de gastar mais que alguns dólares durante todo o curso. Alguns provedores de IA, como a OpenAI, exigem um crédito mínimo de US$5 ou equivalente local; devemos gastar apenas uma fração disso, e você terá bastante oportunidade de usar o restante em seus próprios projetos. Na Semana 7 você terá a opção de gastar um pouco mais se estiver curtindo o processo - eu mesmo gasto cerca de US$10 e os resultados me deixam muito feliz! Mas isso não é de forma alguma obrigatório; o importante é focar no aprendizado.
### Alternativa gratuita às APIs pagas
Consulte o [Guia 9](guides/09_ai_apis_and_ollama.ipynb) no diretório de guias para o passo a passo detalhado, com código exato para Ollama, Gemini, OpenRouter e muito mais!
### Como este repositório está organizado
Há pastas para cada uma das "semanas", que representam módulos da turma e culminam em uma poderosa solução autônoma baseada em agentes na Semana 8, reunindo muitos elementos das semanas anteriores.
Siga as instruções de configuração acima, depois abra a pasta da Semana 1 e prepare-se para se divertir.
### A parte mais importante
O mantra do curso é: a melhor forma de aprender é **FAZENDO**. Eu não digito todo o código durante o curso; executo-o para que você veja os resultados. Trabalhe comigo ou depois de cada aula, executando cada célula e inspecionando os objetos para compreender em detalhes o que está acontecendo. Em seguida, ajuste o código e deixe-o com a sua cara. Há desafios suculentos ao longo do curso. Ficarei muito feliz se você quiser enviar um Pull Request com seu código (veja o guia sobre GitHub na pasta guides) para que eu possa disponibilizar suas soluções a outras pessoas e compartilharmos o seu progresso; como benefício adicional, você será reconhecido no GitHub pela sua contribuição ao repositório. Embora os projetos sejam divertidos, eles são antes de tudo _educacionais_, ensinando habilidades de negócios que você poderá aplicar no seu trabalho.
## A partir da Semana 3, também usaremos o Google Colab para executar com GPUs
Você deverá conseguir usar o nível gratuito ou gastar bem pouco para concluir todos os projetos da turma. Eu, pessoalmente, assinei o Colab Pro+ e estou adorando - mas isso não é obrigatório.
Conheça o Google Colab e crie uma conta Google (se ainda não tiver uma) [aqui](https://colab.research.google.com/)
Os links do Colab estão nas pastas de cada semana e também aqui:
- Para a semana 3, dia 1, este Google Colab mostra o que [o Colab é capaz de fazer](https://colab.research.google.com/drive/1DjcrYDZldAXKJ08x1uYIVCtItoLPk1Wr?usp=sharing)
- Para a semana 3, dia 2, aqui está um Colab para a [API pipelines](https://colab.research.google.com/drive/1aMaEw8A56xs0bRM4lu8z7ou18jqyybGm?usp=sharing) da HuggingFace
- Para a semana 3, dia 3, temos o Colab sobre [Tokenizers](https://colab.research.google.com/drive/1WD6Y2N7ctQi1X9wa6rpkg8UfyA4iSVuz?usp=sharing)
- Para a semana 3, dia 4, vamos a um Colab com [modelos](https://colab.research.google.com/drive/1hhR9Z-yiqjUe7pJjVQw4c74z_V3VchLy?usp=sharing) da HuggingFace
- Para a semana 3, dia 5, voltamos ao Colab para criar nosso [produto de Atas de Reunião](https://colab.research.google.com/drive/1KSMxOCprsl1QRpt_Rq0UqCAyMtPqDQYx?usp=sharing)
- Para a semana 7, usaremos estes notebooks no Colab: [Dia 1](https://colab.research.google.com/drive/15rqdMTJwK76icPBxNoqhI7Ww8UM-Y7ni?usp=sharing) | [Dia 2](https://colab.research.google.com/drive/1T72pbfZw32fq-clQEp-p8YQ4_qFKv4TP?usp=sharing) | [Dias 3 e 4](https://colab.research.google.com/drive/1csEdaECRtjV_1p9zMkaKKjCpYnltlN3M?usp=sharing) | [Dia 5](https://colab.research.google.com/drive/1igA0HF0gvQqbdBD4GkcK3GpHtuDLijYn?usp=sharing)
### Monitoramento de gastos com APIs
Você pode manter seus gastos com APIs muito baixos durante todo o curso; acompanhe tudo nos painéis: [aqui](https://platform.openai.com/usage) para a OpenAI, [aqui](https://console.anthropic.com/settings/cost) para a Anthropic.
Os custos dos exercícios deste curso devem ser sempre bem reduzidos, mas, se quiser mantê-los no mínimo, escolha sempre as versões mais baratas dos modelos:
1. Para a OpenAI: use sempre o modelo `gpt-4.1-nano` no código
2. Para a Anthropic: use sempre o modelo `claude-3-haiku-20240307` no código em vez dos outros modelos Claude
3. Durante a semana 7, siga minhas instruções para usar o conjunto de dados mais econômico
Por favor, envie uma mensagem ou e-mail para ed@edwarddonner.com se algo não funcionar ou se eu puder ajudar de alguma forma. Mal posso esperar para saber como você está progredindo.
<table style="margin: 0; text-align: left;">
<tr>
<td style="width: 150px; height: 150px; vertical-align: middle;">
<img src="assets/resources.jpg" width="150" height="150" style="display: block;" />
</td>
<td>
<h2 style="color:#f71;">Outros recursos</h2>
<span style="color:#f71;">Preparei esta página com recursos úteis para o curso. Ela inclui links para todos os slides.<br/>
<a href="https://edwarddonner.com/2024/11/13/llm-engineering-resources/">https://edwarddonner.com/2024/11/13/llm-engineering-resources/</a><br/>
Mantenha este link nos favoritos - continuarei adicionando materiais úteis lá ao longo do tempo.
</span>
</td>
</tr>
</table>

View File

@@ -0,0 +1,193 @@
# LLM Engineering - Domine IA e LLMs
## Instruções de configuração para Windows
**Estas são as instruções originais correspondentes à versão original dos vídeos, anteriores a outubro de 2025. Para a nova versão, consulte [SETUP-new.md](SETUP-new.md).**
Bem-vindas e bem-vindos, usuários de PC!
Preciso confessar logo de cara: configurar um ambiente poderoso para trabalhar na vanguarda da IA não é tão simples quanto eu gostaria. Para a maioria das pessoas, estas instruções funcionam perfeitamente; mas, em alguns casos, por algum motivo, você pode encontrar um problema. Não hesite em pedir ajuda — estou aqui para colocar tudo em funcionamento rapidamente. Não há nada pior do que se sentir _travado_. Envie uma mensagem, um e-mail ou um recado pelo LinkedIn e eu vou destravar tudo rapidinho!
E-mail: ed@edwarddonner.com
LinkedIn: https://www.linkedin.com/in/eddonner/
Eu utilizo uma plataforma chamada Anaconda para configurar o ambiente. É uma ferramenta poderosa que monta um ambiente científico completo. O Anaconda garante que você esteja usando a versão correta do Python e que todos os pacotes sejam compatíveis com os meus, mesmo que nossos sistemas sejam completamente diferentes. Ele demanda mais tempo de instalação e ocupa mais espaço em disco (mais de 5 GB), mas, depois de configurado, é muito confiável.
Dito isso: se tiver algum problema com o Anaconda, ofereço uma abordagem alternativa. Ela é mais rápida e simples e deve colocar tudo para rodar rapidamente, com um pouco menos de garantia de compatibilidade.
Se você é relativamente novo no Prompt de Comando, aqui está um excelente [guia](https://chatgpt.com/share/67b0acea-ba38-8012-9c34-7a2541052665) com instruções e exercícios. Recomendo passar por ele primeiro para ganhar confiança.
## ATENÇÃO - QUESTÕES "GOTCHA" NO PC: os quatro pontos a seguir merecem sua atenção, especialmente os itens 3 e 4
Por favor, analise esses itens. O item 3 (limite de 260 caracteres do Windows) causará um erro "Archive Error" na instalação do pytorch se não for corrigido. O item 4 causará problemas de instalação.
Há quatro armadilhas comuns ao desenvolver no Windows:
1. Permissões. Confira este [tutorial](https://chatgpt.com/share/67b0ae58-d1a8-8012-82ca-74762b0408b0) sobre permissões no Windows.
2. Antivírus, firewall, VPN. Eles podem interferir em instalações e no acesso à rede; tente desativá-los temporariamente quando necessário.
3. O terrível limite de 260 caracteres para nomes de arquivos no Windows — aqui está uma [explicação completa com a correção](https://chatgpt.com/share/67b0afb9-1b60-8012-a9f7-f968a5a910c7)!
4. Se você ainda não trabalhou com pacotes de Data Science no computador, talvez precise instalar o Microsoft Build Tools. Aqui estão as [instruções](https://chatgpt.com/share/67b0b762-327c-8012-b809-b4ec3b9e7be0). Uma aluna também comentou que [estas instruções](https://github.com/bycloudai/InstallVSBuildToolsWindows) podem ajudar quem estiver no Windows 11.
### Parte 1: Fazer o clone do repositório
Isso garante que você tenha uma cópia local do código na sua máquina.
1. **Instale o Git** (se ainda não estiver instalado):
- Baixe o Git em https://git-scm.com/download/win
- Execute o instalador e siga as instruções, usando as opções padrão (aperte OK várias vezes!).
- Após a instalação, talvez seja necessário abrir uma nova janela do Powershell para usá-lo (ou até reiniciar o computador).
2. **Abra o Prompt de Comando:**
- Pressione Win + R, digite `cmd` e pressione Enter.
3. **Navegue até a pasta de projetos:**
Se você já tem uma pasta para projetos, navegue até ela com o comando `cd`. Por exemplo:
`cd C:\Users\SeuUsuario\Documents\Projects`
Substitua `SeuUsuario` pelo seu usuário do Windows.
Se não tiver uma pasta de projetos, crie uma:
```
mkdir C:\Users\SeuUsuario\Documents\Projects
cd C:\Users\SeuUsuario\Documents\Projects
```
4. **Clone o repositório:**
Digite o seguinte no prompt dentro da pasta Projects:
`git clone https://github.com/ed-donner/llm_engineering.git`
Isso cria um novo diretório `llm_engineering` dentro da pasta Projects e baixa o código da turma. Execute `cd llm_engineering` para entrar nele. Esse diretório `llm_engineering` é o "diretório raiz do projeto".
### Parte 2: Instalar o ambiente Anaconda
Se esta Parte 2 apresentar qualquer problema, há uma Parte 2B alternativa logo abaixo que pode ser utilizada.
1. **Instale o Anaconda:**
- Baixe o Anaconda em https://docs.anaconda.com/anaconda/install/windows/
- Execute o instalador e siga as instruções. Ele ocupa vários gigabytes e leva algum tempo para instalar, mas será uma plataforma poderosa para você usar no futuro.
2. **Configure o ambiente:**
- Abra o **Anaconda Prompt** (procure por ele no menu Iniciar).
- Navegue até o "diretório raiz do projeto" digitando algo como `cd C:\Users\SeuUsuario\Documents\Projects\llm_engineering`, usando o caminho real da sua pasta `llm_engineering`. Execute `dir` e verifique se você enxerga subpastas para cada semana do curso.
- Crie o ambiente: `conda env create -f environment.yml`
- **Se aparecer um ArchiveError, isso é causado pelo limite de 260 caracteres do Windows — veja a armadilha nº 3 acima e corrija antes de tentar novamente.**
- Ative o ambiente: `conda activate llms`
- Com o ambiente ativo, instale kernels adicionais: `python -m ipykernel install --user --name llms --display-name "Python (llms)"`
- Para confirmar se o ambiente funciona, execute: `python -c "import torch, platform; print('Torch version:', torch.__version__); print('Python version:', platform.python_version())"`
- Por fim, rode `jupyter lab`
Se isso funcionar, você está pronto! Abra a pasta `week1`, clique em `day1.ipynb` e comece.
### Parte 2B: Alternativa ao Anaconda (se você teve problemas na Parte 2)
Essa abordagem cria um ambiente virtual usando `python -m venv` e instala os pacotes necessários manualmente.
1. **Certifique-se de que o Python 3.11 esteja instalado:**
- Baixe em https://www.python.org/downloads/windows/ (o executável do instalador).
- Durante a instalação, marque a opção **Add Python to PATH**.
- Depois da instalação, abra um novo Powershell e rode `python --version` para verificar se tudo deu certo.
2. **Crie e ative um ambiente virtual:**
- No Powershell, navegue até o diretório raiz do projeto com `cd` (mesmo caminho da Parte 1).
- Crie o ambiente: `python -m venv llms`
- Ative o ambiente: `llms\Scripts\activate`
- Verifique se `(llms)` aparece no início do prompt.
3. **Atualize os instaladores e instale os pacotes necessários:**
```
python -m pip install --upgrade pip
pip install -r requirements-windows.txt
```
- Em seguida, instale o kernel do Jupyter vinculado ao ambiente: `python -m ipykernel install --user --name llms --display-name "Python (llms)"`
4. **Teste a instalação:**
- Execute `python -c "import torch, platform; print('Torch version:', torch.__version__); print('Python version:', platform.python_version())"`
5. **Inicie o Jupyter Lab:**
- Ainda com o ambiente ativo, rode `jupyter lab`
- Abra a pasta `week1` e clique em `day1.ipynb`.
### Parte 3 - Configure suas contas de API (OpenAI, Anthropic, Google, etc.)
Durante o curso você precisará de algumas chaves de API, principalmente para OpenAI (semana 1) e, mais adiante, Anthropic e Google. É melhor organizar isso com antecedência.
Para a semana 1, você só precisa da OpenAI; os demais serviços podem ser adicionados mais tarde, se desejar.
1. Crie uma conta na OpenAI, se ainda não tiver, acessando:
https://platform.openai.com/
2. A OpenAI exige um crédito mínimo para usar a API. Nos Estados Unidos, o valor é US$ 5. As chamadas à API utilizarão esse crédito. No curso, usaremos apenas uma pequena fração dele. Recomendo fazer esse investimento, pois você poderá aproveitar bastante em seus projetos. Se preferir não pagar pela API, apresento uma alternativa com o Ollama durante o curso.
Você pode adicionar saldo em Settings > Billing:
https://platform.openai.com/settings/organization/billing/overview
Recomendo desativar o recarregamento automático.
3. Crie sua chave de API
A página para criar a chave é https://platform.openai.com/api-keys — clique no botão verde "Create new secret key" e depois em "Create secret key". Guarde a chave em um local seguro; não será possível recuperá-la posteriormente pela interface da OpenAI. Ela deve começar com `sk-proj-`.
Na semana 2 configuraremos também as chaves da Anthropic e da Google, que você poderá gerar aqui quando chegar o momento.
- Claude API: https://console.anthropic.com/ (Anthropic)
- Gemini API: https://ai.google.dev/gemini-api (Google)
Mais adiante no curso usaremos a excelente plataforma HuggingFace; a conta é gratuita em https://huggingface.co — crie um token de API no menu do avatar >> Settings >> Access Tokens.
Nas semanas 6/7 utilizaremos o Weights & Biases em https://wandb.ai para acompanhar os treinamentos. As contas também são gratuitas, e o token é criado de forma semelhante.
### Parte 4 - Arquivo .env
Quando tiver essas chaves, crie um novo arquivo chamado `.env` no diretório raiz do projeto. O nome do arquivo deve ser exatamente os quatro caracteres ".env", e não "minhas-chaves.env" ou ".env.txt". Veja como fazer:
1. Abra o Notepad (Windows + R para abrir a caixa Executar, digite `notepad`).
2. No Notepad, digite o seguinte, substituindo `xxxx` pela sua chave de API (que começa com `sk-proj-`):
```
OPENAI_API_KEY=xxxx
```
Se tiver outras chaves, você pode adicioná-las agora ou voltar a este arquivo nas semanas seguintes:
```
GOOGLE_API_KEY=xxxx
ANTHROPIC_API_KEY=xxxx
DEEPSEEK_API_KEY=xxxx
HF_TOKEN=xxxx
```
Verifique se não há espaços antes ou depois do sinal `=` e nenhum espaço ao final da chave.
3. Vá em File > Save As. Em "Save as type", selecione All Files. No campo "File name", digite exatamente **.env**. Escolha o diretório raiz do projeto (a pasta `llm_engineering`) e clique em Save.
4. Abra essa pasta no Explorer e confira se o arquivo foi salvo como ".env", e não como ".env.txt". Se necessário, renomeie para ".env". Talvez você precise ativar a opção "Mostrar extensões de arquivo" para visualizar as extensões. Se isso não fizer sentido, mande uma mensagem ou e-mail!
Esse arquivo não aparecerá no Jupyter Lab porque arquivos iniciados com ponto ficam ocultos. O `.env` já está listado no `.gitignore`, portanto não será versionado, mantendo suas chaves em segurança.
### Parte 5 - Hora do show!
- Abra o **Anaconda Prompt** (se usou o Anaconda) ou um Powershell (se seguiu a alternativa da Parte 2B).
- Navegue até o "diretório raiz do projeto" digitando algo como `cd C:\Users\SeuUsuario\Documents\Projects\llm_engineering`, usando o caminho real da sua pasta `llm_engineering`. Execute `dir` e confirme se as subpastas de cada semana estão lá.
- Ative o ambiente com `conda activate llms` se usou o Anaconda, ou `llms\Scripts\activate` se usou a alternativa da Parte 2B.
- Você deve ver `(llms)` no prompt — esse é o sinal de que tudo está certo. Agora, digite `jupyter lab` e o Jupyter Lab deverá abrir, pronto para você começar. Abra a pasta `week1` e dê um duplo clique em `day1.ipynb`.
E pronto: pé na estrada!
Observe que, sempre que iniciar o Jupyter Lab no futuro, você precisará seguir novamente as instruções da Parte 5: estar dentro do diretório `llm_engineering` com o ambiente `llms` ativado antes de executar `jupyter lab`.
Para quem é novo no Jupyter Lab / Jupyter Notebook, trata-se de um ambiente de Data Science muito agradável: basta apertar Shift+Enter em qualquer célula para executá-la; comece no topo e vá seguindo! Há um notebook na pasta `week1` com um [Guia para o Jupyter Lab](week1/Guide%20to%20Jupyter.ipynb) e um tutorial de [Python Intermediário](week1/Intermediate%20Python.ipynb), caso ajude. Quando passarmos para o Google Colab na semana 3, você verá a mesma interface para executar Python na nuvem.
Se tiver qualquer problema, há um notebook em `week1` chamado [troubleshooting.ipynb](week1/troubleshooting.ipynb) para ajudar a diagnosticar.
Por favor, envie uma mensagem ou e-mail para ed@edwarddonner.com se algo não funcionar ou se eu puder ajudar de alguma forma. Estou ansioso para saber como você está avançando.

View File

@@ -0,0 +1,212 @@
# LLM Engineering - Domine IA e LLMs
## Instruções de configuração originais para Linux
**Estas são as instruções originais correspondentes à versão original dos vídeos, anteriores a outubro de 2025. Para a nova versão, consulte [SETUP-new.md](SETUP-new.md).**
Bem-vindas e bem-vindos, usuários de Linux!
Preciso admitir que pedi ao ChatGPT para gerar este documento com base nas instruções para Mac e depois revisei e ajustei algumas seções. Se alguma parte não funcionar na sua distro, avise-me — resolveremos juntos e atualizarei as instruções para o futuro.
___
Configurar um ambiente robusto para trabalhar na vanguarda da IA exige algum esforço, mas estas instruções devem guiá-lo sem dificuldades. Se encontrar qualquer problema, fale comigo. Estou aqui para garantir que tudo fique pronto sem dor de cabeça.
E-mail: ed@edwarddonner.com
LinkedIn: https://www.linkedin.com/in/eddonner/
Usaremos o Anaconda para criar um ambiente confiável para o seu trabalho com IA. Também ofereço uma alternativa mais leve, caso prefira evitar o Anaconda. Vamos lá!
### Parte 1: Fazer o clone do repositório
Assim você obtém uma cópia local do código.
1. **Instale o Git**, caso ainda não esteja disponível:
- Abra um terminal.
- Execute `git --version`. Se o Git não estiver instalado, siga as instruções para sua distribuição:
- Debian/Ubuntu: `sudo apt update && sudo apt install git`
- Fedora: `sudo dnf install git`
- Arch: `sudo pacman -S git`
2. **Navegue até a pasta de projetos:**
Se você já tem uma pasta específica para projetos, vá até ela com `cd`. Por exemplo:
`cd ~/Projects`
Se não tiver, crie uma:
```
mkdir ~/Projects
cd ~/Projects
```
3. **Clone o repositório:**
Execute:
`git clone https://github.com/ed-donner/llm_engineering.git`
Isso cria um diretório `llm_engineering` dentro de `Projects` e baixa o código do curso. Entre nele com `cd llm_engineering`. Esse é o "diretório raiz do projeto".
### Parte 2: Instalar o ambiente Anaconda
Se esta Parte 2 apresentar problemas, consulte a Parte 2B alternativa.
1. **Instale o Anaconda:**
- Baixe o instalador para Linux em https://www.anaconda.com/download.
- Abra um terminal e vá até a pasta onde o `.sh` foi salvo.
- Execute o instalador: `bash Anaconda3*.sh` e siga as instruções. Atenção: você precisará de mais de 5 GB de espaço em disco.
2. **Configure o ambiente:**
- No terminal, acesse o diretório raiz do projeto:
`cd ~/Projects/llm_engineering` (ajuste conforme o seu caminho).
- Execute `ls` para confirmar a presença das subpastas semanais.
- Crie o ambiente: `conda env create -f environment.yml`
A instalação pode levar vários minutos (até uma hora para quem nunca usou Anaconda). Se demorar demais ou ocorrerem erros, use a Parte 2B.
- Ative o ambiente: `conda activate llms`.
Você deve ver `(llms)` no prompt, sinal de que a ativação deu certo.
Em algumas distribuições pode ser necessário garantir que o ambiente apareça no Jupyter Lab:
`conda install ipykernel`
`python -m ipykernel install --user --name=llmenv`
3. **Inicie o Jupyter Lab:**
Estando na pasta `llm_engineering`, execute `jupyter lab`.
O Jupyter Lab abrirá no navegador. Após confirmar que funciona, feche-o e siga para a Parte 3.
### Parte 2B - Alternativa à Parte 2 se o Anaconda der trabalho
1. **Instale o Python 3.11 (se necessário):**
- Debian/Ubuntu: `sudo apt update && sudo apt install python3.11`
- Fedora: `sudo dnf install python3.11`
- Arch: `sudo pacman -S python`
2. **Acesse o diretório raiz do projeto:**
`cd ~/Projects/llm_engineering` e confirme o conteúdo com `ls`.
3. **Crie um ambiente virtual:**
`python3.11 -m venv llms`
4. **Ative o ambiente virtual:**
`source llms/bin/activate`
O prompt deve exibir `(llms)`.
5. **Instale os pacotes necessários:**
Execute `python -m pip install --upgrade pip` e depois `pip install -r requirements.txt`.
Se surgir algum problema, tente o plano B:
`pip install --retries 5 --timeout 15 --no-cache-dir --force-reinstall -r requirements.txt`
###### Usuários de Arch
Algumas atualizações quebram dependências (principalmente numpy, scipy e gensim). Para contornar:
`sudo pacman -S python-numpy python-pandas python-scipy` — não é a opção ideal, pois o pacman não se integra ao pip.
Outra alternativa, caso ocorram conflitos de compilação:
`sudo pacman -S gcc gcc-fortran python-setuptools python-wheel`
*Observação:* o gensim pode falhar com versões recentes do scipy. Você pode fixar o scipy em uma versão mais antiga ou remover temporariamente o gensim do requirements.txt. (Veja: https://aur.archlinux.org/packages/python-gensim)
Por fim, para que o kernel apareça no Jupyter Lab após o passo 6:
`python -m ipykernel install --user --name=llmenv`
`ipython kernel install --user --name=llmenv`
6. **Inicie o Jupyter Lab:**
Na pasta `llm_engineering`, execute `jupyter lab`.
### Parte 3 - Chave da OpenAI (OPCIONAL, mas recomendada)
Nas semanas 1 e 2 você escreverá código que chama APIs de modelos de ponta.
Na semana 1 basta a OpenAI; as demais chaves podem vir depois.
1. Crie uma conta na OpenAI, se ainda não tiver:
https://platform.openai.com/
2. A OpenAI exige um crédito mínimo para liberar a API. Nos EUA, são US$ 5. As chamadas descontarão desse valor. No curso usaremos apenas uma pequena parte. Recomendo fazer esse investimento, pois será útil para projetos futuros. Caso não queira pagar, ofereço uma alternativa com o Ollama ao longo das aulas.
Adicione crédito em Settings > Billing:
https://platform.openai.com/settings/organization/billing/overview
Sugiro desativar a recarga automática.
3. Gere sua chave de API:
Acesse https://platform.openai.com/api-keys, clique em "Create new secret key" (botão verde) e depois em "Create secret key". Guarde a chave em local seguro; não será possível recuperá-la posteriormente. Ela deve começar com `sk-proj-`.
Na semana 2 criaremos também chaves para Anthropic e Google:
- Claude API: https://console.anthropic.com/
- Gemini API: https://ai.google.dev/gemini-api
Mais adiante utilizaremos a ótima plataforma HuggingFace; a conta gratuita está em https://huggingface.co — gere um token em Avatar >> Settings >> Access Tokens.
Nas semanas 6/7 empregaremos o Weights & Biases em https://wandb.ai para monitorar os treinamentos. As contas também são gratuitas e o token é criado de modo semelhante.
### PARTE 4 - Arquivo .env
Quando tiver as chaves, crie um arquivo `.env` no diretório raiz do projeto. O nome deve ser exatamente ".env" — nada de "minhas-chaves.env" ou ".env.txt". Passo a passo:
1. Abra um terminal.
2. Navegue até o diretório raiz do projeto com `cd ~/Projects/llm_engineering` (ajuste conforme necessário).
3. Crie o arquivo com:
`nano .env`
4. Digite suas chaves no nano, substituindo `xxxx` pelo valor correto (ex.: começa com `sk-proj-`):
```
OPENAI_API_KEY=xxxx
```
Se já tiver outras chaves, você pode incluí-las agora ou mais tarde:
```
GOOGLE_API_KEY=xxxx
ANTHROPIC_API_KEY=xxxx
DEEPSEEK_API_KEY=xxxx
HF_TOKEN=xxxx
```
5. Salve o arquivo:
Control + O
Enter (para confirmar)
Control + X para sair
6. Liste os arquivos, inclusive ocultos:
`ls -a`
Confirme que o `.env` está presente.
O arquivo não aparecerá no Jupyter Lab porque arquivos iniciados com ponto ficam ocultos. Ele já está no `.gitignore`, então não será versionado e suas chaves ficam protegidas.
### Parte 5 - Hora do show!
1. Abra um terminal.
2. Vá até o diretório raiz do projeto:
`cd ~/Projects/llm_engineering`.
3. Ative seu ambiente:
- Com Anaconda: `conda activate llms`
- Com a alternativa: `source llms/bin/activate`
Você deve ver `(llms)` no prompt. Execute `jupyter lab` para começar.
Aproveite a jornada rumo ao domínio de IA e LLMs!

View File

@@ -0,0 +1,197 @@
# LLM Engineering - Domine IA e LLMs
## Instruções de configuração originais para Mac
**Estas são as instruções originais correspondentes à versão original dos vídeos, anteriores a outubro de 2025. Para a nova versão, consulte [SETUP-new.md](SETUP-new.md).**
Bem-vindas e bem-vindos, usuários de Mac!
Preciso confessar logo de cara: configurar um ambiente poderoso para trabalhar na vanguarda da IA não é tão simples quanto eu gostaria. Para a maioria das pessoas, estas instruções funcionarão muito bem; mas, em alguns casos, por algum motivo, você pode encontrar um problema. Não hesite em pedir ajuda — estou aqui para colocar tudo em funcionamento rapidamente. Não há nada pior do que se sentir _travado_. Envie uma mensagem, um e-mail ou um recado pelo LinkedIn e eu destravo tudo sem demora!
E-mail: ed@edwarddonner.com
LinkedIn: https://www.linkedin.com/in/eddonner/
Uso uma plataforma chamada Anaconda para configurar o ambiente. É uma ferramenta poderosa que monta um ecossistema científico completo. O Anaconda garante que você utilize a versão correta do Python e que todos os pacotes sejam compatíveis com os meus, mesmo que nossos sistemas sejam completamente diferentes. A configuração leva mais tempo e consome mais espaço em disco (mais de 5 GB), mas é muito confiável depois de instalada.
Dito isso: se tiver qualquer problema com o Anaconda, forneço uma abordagem alternativa. Ela é mais rápida e simples e deve colocá-lo para rodar rapidamente, com um pouco menos de garantia de compatibilidade.
### Antes de começar
Se você ainda não tem tanta familiaridade com o Terminal, consulte este excelente [guia](https://chatgpt.com/canvas/shared/67b0b10c93a081918210723867525d2b) com explicações e exercícios.
Se estiver iniciando o desenvolvimento no Mac, talvez seja necessário instalar as ferramentas de desenvolvedor do Xcode. Aqui estão as [instruções](https://chatgpt.com/share/67b0b8d7-8eec-8012-9a37-6973b9db11f5).
Um ponto de atenção: se você usa antivírus, VPN ou firewall, eles podem interferir em instalações ou no acesso à rede. Desative-os temporariamente caso surjam problemas.
### Parte 1: Fazer o clone do repositório
Isto garante que você tenha uma cópia local do código.
1. **Instale o Git** caso ainda não esteja instalado (na maioria das vezes já estará).
- Abra o Terminal (Aplicativos > Utilitários > Terminal).
- Digite `git --version`. Se o Git não estiver instalado, o sistema pedirá a instalação automaticamente.
- Depois da instalação, talvez seja necessário abrir uma nova janela do Terminal (ou até reiniciar) para utilizá-lo.
2. **Navegue até a pasta de projetos:**
Se você já possui uma pasta específica para projetos, navegue até ela usando `cd`. Por exemplo:
`cd ~/Documents/Projects`
Se não tiver uma pasta de projetos, crie-a:
```
mkdir ~/Documents/Projects
cd ~/Documents/Projects
```
3. **Clone o repositório:**
No Terminal, dentro da pasta Projects, digite:
`git clone https://github.com/ed-donner/llm_engineering.git`
Isso cria um novo diretório `llm_engineering` dentro da pasta Projects e baixa o código do curso. Execute `cd llm_engineering` para entrar nele. Esse diretório `llm_engineering` é o "diretório raiz do projeto".
### Parte 2: Instalar o ambiente Anaconda
Se esta Parte 2 apresentar qualquer problema, você pode usar a Parte 2B alternativa logo abaixo.
1. **Instale o Anaconda:**
- Baixe o Anaconda em https://docs.anaconda.com/anaconda/install/mac-os/
- Dê um duplo clique no arquivo baixado e siga as instruções de instalação. O processo ocupa vários gigabytes e leva algum tempo, mas fornecerá uma plataforma poderosa para você no futuro.
- Após a instalação, abra um Terminal totalmente novo para poder usar o Anaconda (talvez seja até necessário reiniciar).
2. **Configure o ambiente:**
- Abra um **novo** Terminal (Aplicativos > Utilitários > Terminal).
- Navegue até o "diretório raiz do projeto" com `cd ~/Documents/Projects/llm_engineering` (substitua pelo caminho real da sua cópia local). Execute `ls` e verifique se há subpastas para cada semana do curso.
- Crie o ambiente: `conda env create -f environment.yml`
- Aguarde alguns minutos até que todos os pacotes sejam instalados — se for a primeira vez com o Anaconda, isso pode levar 20 a 30 minutos ou mais, dependendo da conexão. Coisas importantes estão acontecendo! Se levar mais de 1 hora e 15 minutos ou se aparecerem erros, siga para a Parte 2B.
- Agora você construiu um ambiente dedicado para engenharia de LLMs, vetores e muito mais! Ative-o com: `conda activate llms`
- O prompt deve exibir `(llms)`, indicando que o ambiente está ativo.
3. **Inicie o Jupyter Lab:**
- No Terminal, ainda dentro da pasta `llm_engineering`, digite: `jupyter lab`
O Jupyter Lab deve abrir no navegador. Se você nunca usou o Jupyter Lab, explicarei em breve! Por ora, feche a aba do navegador e o Terminal, e avance para a Parte 3.
### Parte 2B - Alternativa à Parte 2 se o Anaconda der trabalho
Esta abordagem utiliza `python -m venv` para criar um ambiente virtual e instalar manualmente os pacotes necessários.
1. **Garanta que o Python 3.11 esteja instalado:**
- No macOS 13+ o Python 3 normalmente já está disponível via `python3`, mas recomendo instalar uma versão oficial em https://www.python.org/downloads/macos/
- Execute o instalador e siga as instruções.
- Após a instalação, abra um novo Terminal e rode `python3 --version` para confirmar.
2. **Crie e ative um ambiente virtual:**
- No Terminal, navegue até o diretório raiz do projeto (`cd ~/Documents/Projects/llm_engineering`, ajustando conforme o seu caminho).
- Crie o ambiente: `python3 -m venv llms`
- Ative o ambiente: `source llms/bin/activate`
- Verifique se `(llms)` aparece no início do prompt.
3. **Atualize os instaladores e instale os pacotes necessários:**
```
python -m pip install --upgrade pip
pip install -r requirements-mac.txt
```
- Em seguida, instale o kernel do Jupyter vinculado ao ambiente: `python -m ipykernel install --user --name llms --display-name "Python (llms)"`
4. **Teste a instalação:**
- Execute `python -c "import torch, platform; print('Torch version:', torch.__version__); print('Python version:', platform.python_version())"`
5. **Inicie o Jupyter Lab:**
- Com o ambiente ainda ativo, rode `jupyter lab`
- Abra a pasta `week1` e clique em `day1.ipynb`.
### Parte 3 - Configure suas contas de API (OpenAI, Anthropic, Google, etc.)
Ao longo do curso você precisará de chaves de API, começando pela OpenAI na semana 1. Mais adiante, adicionaremos Anthropic e Google. Organize isso com antecedência.
Para a semana 1, você só precisa da OpenAI; as demais chaves podem ser criadas posteriormente.
1. Crie uma conta na OpenAI, se necessário:
https://platform.openai.com/
2. A OpenAI exige um crédito mínimo para liberar o uso da API. Nos EUA, são US$ 5. As chamadas à API descontarão desse valor. No curso, usaremos apenas uma fração dele. Recomendo fazer esse investimento, porque você aproveitará bastante. Caso prefira não pagar, apresento uma alternativa com o Ollama ao longo das aulas.
Você pode adicionar crédito em Settings > Billing:
https://platform.openai.com/settings/organization/billing/overview
Desative o recarregamento automático, se desejar.
3. Crie sua chave de API:
A página para criar a chave é https://platform.openai.com/api-keys — clique no botão verde "Create new secret key" e depois em "Create secret key". Guarde a chave em local seguro; não será possível recuperá-la depois. Ela deve começar com `sk-proj-`.
Na semana 2 criaremos também as chaves da Anthropic e da Google, quando for o momento.
- Claude API: https://console.anthropic.com/ (Anthropic)
- Gemini API: https://ai.google.dev/gemini-api (Google)
Mais adiante usaremos a excelente plataforma HuggingFace; a conta gratuita está em https://huggingface.co — crie um token no menu do avatar >> Settings >> Access Tokens.
Nas semanas 6/7 utilizaremos o sensacional Weights & Biases em https://wandb.ai para monitorar os treinamentos. As contas também são gratuitas e o token é criado de modo parecido.
### PARTE 4 - arquivo .env
Quando tiver as chaves, crie um arquivo chamado `.env` no diretório raiz do projeto. O nome precisa ser exatamente ".env" — nada de "minhas-chaves.env" ou ".env.txt". Veja como fazer:
1. Abra o Terminal (Aplicativos > Utilitários > Terminal).
2. Navegue até o "diretório raiz do projeto" com `cd ~/Documents/Projects/llm_engineering` (ajuste conforme o seu caminho).
3. Crie o arquivo `.env` com:
`nano .env`
4. Digite as suas chaves no nano, substituindo `xxxx` pela chave (que começa com `sk-proj-`):
```
OPENAI_API_KEY=xxxx
```
Se tiver outras chaves, você pode adicioná-las agora ou mais tarde:
```
GOOGLE_API_KEY=xxxx
ANTHROPIC_API_KEY=xxxx
DEEPSEEK_API_KEY=xxxx
HF_TOKEN=xxxx
```
5. Salve o arquivo:
Control + O
Enter (para confirmar a gravação)
Control + X para sair do editor
6. Liste os arquivos no diretório raiz com:
`ls -a`
e confirme que o `.env` está lá.
O arquivo não aparecerá no Jupyter Lab porque arquivos iniciados com ponto ficam ocultos. Ele já está no `.gitignore`, portanto não será versionado e suas chaves permanecem seguras.
### Parte 5 - Hora do show!
- Abra o Terminal (Aplicativos > Utilitários > Terminal).
- Navegue até o "diretório raiz do projeto" com `cd ~/Documents/Projects/llm_engineering` (ajuste conforme o seu caminho). Execute `ls` e verifique se as subpastas semanais estão visíveis.
- Ative o ambiente com `conda activate llms` (ou `source llms/bin/activate` se você usou a alternativa da Parte 2B).
- `(llms)` deve aparecer no prompt — sinal de que tudo está pronto. Agora digite `jupyter lab` e o Jupyter Lab será aberto, pronto para começar. Abra a pasta `week1` e dê um duplo clique em `day1.ipynb`.
E pronto: pé na estrada!
Sempre que iniciar o Jupyter Lab no futuro, siga novamente as instruções desta Parte 5: esteja dentro do diretório `llm_engineering` com o ambiente `llms` ativado antes de rodar `jupyter lab`.
Para quem é novo no Jupyter Lab / Jupyter Notebook, trata-se de um ambiente de Data Science muito amigável: basta pressionar Shift+Enter em qualquer célula para executá-la; comece do topo e vá seguindo! Incluí um notebook chamado "Guide to Jupyter" mostrando mais recursos. Quando migrarmos para o Google Colab na semana 3, você verá a mesma interface para executar Python na nuvem.
Se surgir qualquer problema, há um notebook na semana 1 chamado [troubleshooting.ipynb](week1/troubleshooting.ipynb) para ajudar no diagnóstico.
Por favor, envie uma mensagem ou e-mail para ed@edwarddonner.com se algo não funcionar ou se eu puder ajudar de algum modo. Estou ansioso para saber como você está progredindo.

View File

@@ -0,0 +1,209 @@
# LLM Engineering - Domine IA e LLMs
## Novas instruções de configuração para PC, Mac e Linux
**Estas são as instruções de configuração da nova versão do curso a partir de outubro de 2025. Para as versões originais (Anaconda), consulte os outros arquivos deste diretório correspondentes à sua plataforma.**
_Se você estiver visualizando isto no Cursor, clique com o botão direito no nome do arquivo no Explorer à esquerda e selecione "Open preview" para ver a versão formatada._
Bem-vindas e bem-vindos, engenheiras e engenheiros de LLM em formação!
Preciso confessar logo de início: configurar um ambiente poderoso para trabalhar na linha de frente da IA não é tão simples quanto eu gostaria. Para a maioria das pessoas, estas instruções funcionarão muito bem; mas, em alguns casos, por qualquer motivo, você pode esbarrar em um problema. Não hesite em pedir ajuda — estou aqui para colocar tudo em funcionamento rapidamente. Não há nada pior do que se sentir _travado_. Envie uma mensagem pela Udemy ou um e-mail e vou destravar a situação sem demora!
E-mail: ed@edwarddonner.com
LinkedIn: https://www.linkedin.com/in/eddonner/
## Etapa 0 - Antes de começar - tratando dos "GOTCHAS" que derrubam muita gente
Ignore esta seção por sua conta e risco! 80% das dúvidas que recebo sobre a configuração são resolvidas por estes problemas comuns de sistema.
1. Quem usa PC: Permissões. Dê uma olhada neste [tutorial](https://chatgpt.com/share/67b0ae58-d1a8-8012-82ca-74762b0408b0) sobre permissões no Windows. Se aparecer algum erro dizendo que você não tem direitos/permissões/capacidade de executar um script ou instalar software, leia isso primeiro. O ChatGPT pode explicar tudo o que você precisa saber sobre permissões no Windows.
2. Antivírus, firewall, VPN. Esses elementos podem atrapalhar instalações e o acesso à rede; tente desativá-los temporariamente quando necessário. Use o hotspot do seu celular para confirmar se o problema é realmente de rede.
3. Quem usa PC: o terrível limite de 260 caracteres para nomes de arquivos no Windows — aqui está uma [explicação completa com a correção](https://chatgpt.com/share/67b0afb9-1b60-8012-a9f7-f968a5a910c7)!
4. Quem usa PC: se você nunca trabalhou com pacotes de Data Science no seu computador, talvez precise instalar o Microsoft Build Tools. Aqui estão as [instruções](https://chatgpt.com/share/67b0b762-327c-8012-b809-b4ec3b9e7be0). Uma aluna também mencionou que [estas instruções](https://github.com/bycloudai/InstallVSBuildToolsWindows) podem ajudar quem estiver no Windows 11.
5. Quem usa Mac: se está começando a desenvolver no Mac agora, talvez seja necessário instalar as ferramentas de desenvolvedor do Xcode. Aqui estão as [instruções](https://chatgpt.com/share/67b0b8d7-8eec-8012-9a37-6973b9db11f5).
6. SSL e outros problemas de rede por causa de segurança corporativa: se você tiver erros de SSL, como falhas de conexão com API, qualquer problema de certificado ou erro ao baixar arquivos do Ollama (erro do Cloudflare), veja a pergunta 15 [aqui](https://edwarddonner.com/faq).
## ETAPA 1 - instalando git, diretório de projetos e Cursor
Esta é a única seção com passos separados para quem usa PC e para quem usa Mac/Linux! Escolha o seu bloco abaixo e, depois, volte aqui para a Etapa 2...
___
**ETAPA 1 PARA QUEM USA PC:**
1. **Instale o Git** (se ainda não estiver instalado):
- Abra um novo prompt do Powershell (menu Iniciar >> Powershell). Se surgirem erros de permissão, tente abrir o Powershell clicando com o botão direito e escolhendo "Executar como administrador".
- Execute o comando `git` e veja se ele responde com detalhes do comando ou com um erro.
- Se aparecer erro, baixe o Git em https://git-scm.com/download/win
- Execute o instalador e siga as instruções, aceitando as opções padrão (aperte OK várias vezes!)
2. **Crie o diretório de projetos, se necessário**
- Abra um novo prompt do Powershell, conforme o passo anterior. Você deve estar no seu diretório pessoal, algo como `C:\Users\SeuUsuario`
- Você já tem um diretório `projects`? Descubra digitando `cd projects`
- Se aparecer erro, crie o diretório de projetos: `mkdir projects` e depois `cd projects`
- Agora você deve estar em `C:\Users\SeuUsuario\projects`
- Você pode escolher outro local conveniente, mas evite diretórios que estejam no OneDrive
3. **Faça o git clone:**
Digite o seguinte no prompt dentro da pasta `projects`:
`git clone https://github.com/ed-donner/llm_engineering.git`
Isso cria um novo diretório `llm_engineering` dentro da pasta de projetos e baixa o código da turma.
Execute `cd llm_engineering` para entrar nele. Esse diretório `llm_engineering` é o "diretório raiz do projeto".
4. **Cursor** Instale o Cursor, se necessário, e abra o projeto:
Visite https://cursor.com
Clique em Download for Windows. Execute o instalador. Aceite e mantenha os padrões em tudo.
Depois, abra o menu Iniciar, digite cursor. O Cursor será aberto e talvez você precise responder a algumas perguntas. Em seguida, deve aparecer a tela de "new window", onde você pode clicar em "Open Project". Se não aparecer, vá ao menu File >> New Window. Depois clique em "Open Project".
Localize o diretório `llm_engineering` dentro da sua pasta de projetos. Dê dois cliques em `llm_engineering` para visualizar o conteúdo dele. Em seguida, clique em Open ou Open Folder.
O Cursor deve então abrir o `llm_engineering`. Você saberá que deu tudo certo se vir LLM_ENGINEERING em letras maiúsculas no canto superior esquerdo.
___
**ETAPA 1 PARA QUEM USA MAC/LINUX**
1. **Instale o Git** (se ainda não estiver instalado):
Abra um Terminal. No Mac, abra uma janela do Finder e vá para Aplicativos >> Utilitários >> Terminal. No Linux, vocês praticamente vivem no Terminal... quase não precisam das minhas instruções!
- Execute `git --version` e verifique se aparece um número de versão do git. Caso contrário, você deve ver orientações para instalá-lo, ou siga o gotcha nº 5 no topo deste documento.
2. **Crie o diretório de projetos, se necessário**
- Abra uma nova janela do Terminal, como no passo anterior. Digite `pwd` para ver onde está. Você deve estar no seu diretório pessoal, algo como `/Users/usuario`
- Você já tem um diretório `projects`? Verifique digitando `cd projects`
- Se aparecer erro, crie o diretório de projetos: `mkdir projects` e depois `cd projects`
- Se você rodar `pwd` agora, deve estar em `/Users/usuario/projects`
- Você pode escolher outro local conveniente, mas evite diretórios que estejam no iCloud
3. **Faça o git clone:**
Digite o seguinte no prompt dentro da pasta `projects`:
`git clone https://github.com/ed-donner/llm_engineering.git`
Isso cria um novo diretório `llm_engineering` dentro da sua pasta de projetos e baixa o código da turma.
Execute `cd llm_engineering` para entrar nele. Esse diretório `llm_engineering` é o "diretório raiz do projeto".
4. **Cursor** Instale o Cursor, se necessário, e abra o projeto:
Visite https://cursor.com
Clique em Download for Mac OS ou para Linux. Em seguida, execute o instalador. Aceite e mantenha os padrões em tudo.
Depois, procure por Cursor (Spotlight, menu Iniciar etc.). O Cursor será aberto e talvez apareçam algumas perguntas. Em seguida, você deverá ver a tela de "new window", onde pode clicar em "Open Project". Se não aparecer, vá ao menu File >> New Window e clique em "Open Project".
Localize o diretório `llm_engineering` dentro da sua pasta de projetos. Dê dois cliques em `llm_engineering` para visualizar o conteúdo dele. Depois clique em Open.
O Cursor deve então abrir o `llm_engineering`. Você saberá que deu tudo certo se vir LLM_ENGINEERING em letras maiúsculas no canto superior esquerdo.
___
## ETAPA 2: Instalando o fantástico **uv** e executando `uv sync`
Para este curso, usamos o uv, o gerenciador de pacotes incrivelmente rápido. Ele conquistou a comunidade de Data Science — e com razão.
É veloz e confiável. Você vai adorar!
Primeiro, dentro do Cursor, selecione View >> Terminal para abrir um terminal integrado. Digite `pwd` para confirmar que está no diretório raiz do projeto.
Agora digite `uv --version` para ver se o uv está instalado. Se aparecer um número de versão, ótimo! Caso surja um erro, siga as instruções deste link para instalar o uv — recomendo utilizar o método Standalone Installer logo no começo da página, mas qualquer método serve. Execute os comandos no terminal do Cursor. Se uma abordagem não funcionar, tente outra.
https://docs.astral.sh/uv/getting-started/installation/
Depois de instalar o uv, abra uma nova janela de terminal no Cursor (o sinal de mais ou Ctrl+Shift+crase) para que o `uv --version` funcione. Verifique!
Quaisquer problemas na instalação ou no uso do uv, consulte [a pergunta 11 na minha página de FAQ](https://edwarddonner.com/faq/#11) para uma explicação completa.
### Agora que está instalado:
Execute `uv self update` para garantir que você está com a versão mais recente do uv.
Em seguida, simplesmente rode:
`uv sync`
O uv deve instalar tudo de forma extremamente rápida. Qualquer problema, veja novamente [a pergunta 11 na página de FAQ](https://edwarddonner.com/faq/#11).
Você agora tem um ambiente completo!
Usar o uv é simples e rápido:
1. Em vez de `pip install xxx`, use `uv add xxx`
2. Você nunca precisa ativar um ambiente — o uv faz isso automaticamente.
3. Em vez de `python xxx`, use `uv run xxx`
___
## ETAPA 3 - OPCIONAL - Crie sua conta na OpenAI
Alternativa: consulte o Guia 9 na pasta `guides` para opções gratuitas!
Acesse https://platform.openai.com
- Clique em Sign Up para criar uma conta, caso ainda não tenha. Talvez seja necessário clicar em alguns botões para criar uma Organization primeiro — insira dados razoáveis. Veja o Guia 4 na pasta Guides se estiver em dúvida sobre as diferenças entre o ChatGPT e a API da OpenAI.
- Clique no ícone Settings no canto superior direito e, em seguida, em Billing no menu lateral esquerdo.
- Certifique-se de que o Auto-Recharge esteja desativado. Se necessário, clique em "Add to Credit Balance" e escolha o valor adiantado de US$ 5, garantindo que adicionou um meio de pagamento válido.
- Ainda em Settings, selecione API keys no menu lateral esquerdo (perto do topo).
- Clique em "Create new secret key" — selecione "Owned by you", dê o nome que quiser, escolha "Default project" no campo Project e mantenha Permissions em All.
- Clique em "Create secret key" e você verá a nova chave. Clique em Copy para copiá-la para a área de transferência.
___
## ETAPA 4 - necessária para qualquer modelo como OpenAI ou Gemini (mas dispensável se você usar apenas o Ollama) - crie (e SALVE) o seu arquivo .env
**Seja meticuloso nesta etapa!** Qualquer erro na chave será muito difícil de diagnosticar! Recebo um volume enorme de perguntas de estudantes que cometem algum deslize aqui... Acima de tudo, lembre-se de salvar o arquivo depois de alterá-lo.
1. Crie o arquivo `.env`
- Volte ao Cursor
- No Explorador de Arquivos à esquerda, clique com o botão direito no espaço em branco abaixo de todos os arquivos, selecione "New File" e dê ao seu arquivo o nome `.env`
- Não canso de repetir: o arquivo precisa se chamar EXATAMENTE `.env` — essas quatro letras, nem mais nem menos. Nada de ".env.txt", nem "joao.env", nem "openai.env" ou qualquer outra coisa! E ele precisa estar no diretório raiz do projeto.
Se estiver se perguntando por que reforço tanto isso: recebo muitas, muitas mensagens de pessoas frustradas que (apesar de todos os meus apelos) deram outro nome ao arquivo e acharam que estava tudo certo. Não está! Ele precisa se chamar `.env` dentro do diretório `llm_engineering`. :)
2. Preencha o arquivo `.env` e depois salve:
Selecione o arquivo à esquerda. Você verá um arquivo vazio à direita. Digite isto no conteúdo do arquivo:
`OPENAI_API_KEY=`
Em seguida, cole a sua chave! Você deve ver algo como:
`OPENAI_API_KEY=sk-proj-lots-and-lots-of-digits`
Mas, claro, com a sua chave real, não com as palavras "sk-proj-lots-and-lots-of-digits"...
Agora TENHA CERTEZA de salvar o arquivo! File >> Save ou Ctrl+S (PC) / Command+S (Mac). Muitas pessoas esquecem de salvar. Você precisa salvar o arquivo!
Você provavelmente verá um ícone de sinal de parada ao lado do .env — não se preocupe, isso é algo bom! Consulte a pergunta 7 [aqui](https://edwarddonner.com/faq) se quiser entender o motivo.
__
## ETAPA 5 - Instale as extensões do Cursor, abra o Dia 1, configure o kernel e VAMOS NESSA!
(Se o Cursor sugerir instalar extensões recomendadas, basta aceitar! É um bom atalho para esta etapa.)
- Vá ao menu View e selecione Extensions.
- Procure por "python" para exibir as extensões de Python. Selecione a extensão Python desenvolvida por "ms-python" ou "anysphere" e instale-a se ainda não estiver instalada.
- Procure por "jupyter" e selecione a extensão desenvolvida por "ms-toolsai"; instale-a se ainda não estiver instalada.
Agora vá em View >> Explorer. Abra a pasta `week1` e clique em `day1.ipynb`.
- Veja onde aparece "Select Kernel" perto do canto superior direito? Clique ali e escolha "Python Environments".
- Selecione a opção superior com uma estrela, algo como `.venv (Python 3.12.x) .venv/bin/python Recommended`.
- Se essa opção não aparecer, abra o laboratório de troubleshooting na pasta Setup.
# PARABÉNS!! Você conseguiu! O restante do curso é tranquilo :)

View File

@@ -0,0 +1,54 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "73287ed4-81e3-496a-9e47-f0e8c3770ce9",
"metadata": {},
"source": [
"# Coletando informações diagnósticas essenciais\n",
"\n",
"## Execute a próxima célula para coletar alguns dados importantes\n",
"\n",
"Execute a próxima célula; ela deve levar cerca de um minuto para rodar (principalmente o teste de rede).\n",
"Em seguida, envie por e-mail o resultado da última célula para ed@edwarddonner.com. \n",
"Como alternativa: isso criará um arquivo chamado report.txt - basta anexar o arquivo ao seu e-mail."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ed8056e8-efa2-4b6f-a4bb-e7ceb733c517",
"metadata": {},
"outputs": [],
"source": [
"# Execute meu relatório de diagnósticos para coletar informações essenciais de depuração\n",
"# Por favor, envie os resultados por e-mail. Você pode copiar e colar a saída ou anexar o arquivo report.txt\n",
"\n",
"!pip install -q requests speedtest-cli psutil setuptools\n",
"from diagnostics import Diagnostics\n",
"Diagnostics().run()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"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.11.10"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,421 @@
import os
import sys
import platform
import subprocess
import shutil
import time
import ssl
import tempfile
from pathlib import Path
from datetime import datetime
class Diagnostics:
FILENAME = 'report.txt'
def __init__(self):
self.errors = []
self.warnings = []
if os.path.exists(self.FILENAME):
os.remove(self.FILENAME)
def log(self, message):
print(message)
with open(self.FILENAME, 'a', encoding='utf-8') as f:
f.write(message + "\n")
def start(self):
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.log(f"Iniciando diagnósticos às {now}\n")
def end(self):
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.log(f"\n\nDiagnósticos concluídos às {now}\n")
print("\nEnvie estes diagnósticos para mim em ed@edwarddonner.com")
print(f"Copie e cole a saída acima em um e-mail ou anexe o arquivo {self.FILENAME} que foi criado neste diretório.")
def _log_error(self, message):
self.log(f"ERRO: {message}")
self.errors.append(message)
def _log_warning(self, message):
self.log(f"AVISO: {message}")
self.warnings.append(message)
def run(self):
self.start()
self._step1_system_info()
self._step2_check_files()
self._step3_git_repo()
self._step4_check_env_file()
self._step5_anaconda_check()
self._step6_virtualenv_check()
self._step7_network_connectivity()
self._step8_environment_variables()
self._step9_additional_diagnostics()
if self.warnings:
self.log("\n===== Avisos Encontrados =====")
self.log("Os avisos abaixo foram identificados. Eles podem não impedir a execução do programa, mas podem provocar comportamentos inesperados:")
for warning in self.warnings:
self.log(f"- {warning}")
if self.errors:
self.log("\n===== Erros Encontrados =====")
self.log("Os problemas críticos a seguir foram encontrados. Solucione-os antes de prosseguir:")
for error in self.errors:
self.log(f"- {error}")
if not self.errors and not self.warnings:
self.log("\n✅ Todos os diagnósticos foram concluídos com êxito!")
self.end()
def _step1_system_info(self):
self.log("===== Informações do Sistema =====")
try:
system = platform.system()
self.log(f"Sistema Operacional: {system}")
if system == "Windows":
release, version, csd, ptype = platform.win32_ver()
self.log(f"Release do Windows: {release}")
self.log(f"Versão do Windows: {version}")
elif system == "Darwin":
release, version, machine = platform.mac_ver()
self.log(f"Versão do macOS: {release}")
else:
self.log(f"Plataforma: {platform.platform()}")
self.log(f"Arquitetura: {platform.architecture()}")
self.log(f"Máquina: {platform.machine()}")
self.log(f"Processador: {platform.processor()}")
try:
import psutil
ram = psutil.virtual_memory()
total_ram_gb = ram.total / (1024 ** 3)
available_ram_gb = ram.available / (1024 ** 3)
self.log(f"RAM total: {total_ram_gb:.2f} GB")
self.log(f"RAM disponível: {available_ram_gb:.2f} GB")
if available_ram_gb < 2:
self._log_warning(f"RAM disponível baixa: {available_ram_gb:.2f} GB")
except ImportError:
self._log_warning("Módulo psutil não encontrado. Não foi possível determinar as informações de RAM.")
total, used, free = shutil.disk_usage(os.path.expanduser("~"))
free_gb = free / (1024 ** 3)
self.log(f"Espaço livre em disco: {free_gb:.2f} GB")
if free_gb < 5:
self._log_warning(f"Pouco espaço em disco: {free_gb:.2f} GB livres")
except Exception as e:
self._log_error(f"Falha na verificação das informações do sistema: {e}")
def _step2_check_files(self):
self.log("\n===== Informações do Sistema de Arquivos =====")
try:
current_dir = os.getcwd()
self.log(f"Diretório atual: {current_dir}")
# Verifica permissões de escrita
test_file = Path(current_dir) / ".test_write_permission"
try:
test_file.touch(exist_ok=True)
test_file.unlink()
self.log("Permissão de escrita: OK")
except Exception as e:
self._log_error(f"Sem permissão de escrita no diretório atual: {e}")
self.log("\nArquivos no diretório atual:")
try:
for item in sorted(os.listdir(current_dir)):
self.log(f" - {item}")
except Exception as e:
self._log_error(f"Não é possível listar o conteúdo do diretório: {e}")
except Exception as e:
self._log_error(f"Falha na verificação do sistema de arquivos: {e}")
def _step3_git_repo(self):
self.log("\n===== Informações do Repositório Git =====")
try:
result = subprocess.run(['git', 'rev-parse', '--show-toplevel'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if result.returncode == 0:
git_root = result.stdout.strip()
self.log(f"Raiz do repositório Git: {git_root}")
result = subprocess.run(['git', 'rev-parse', 'HEAD'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if result.returncode == 0:
self.log(f"Commit atual: {result.stdout.strip()}")
else:
self._log_warning(f"Não foi possível obter o commit atual: {result.stderr.strip()}")
result = subprocess.run(['git', 'remote', 'get-url', 'origin'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if result.returncode == 0:
self.log(f"Remoto 'origin': {result.stdout.strip()}")
else:
self._log_warning("Remoto 'origin' não configurado")
else:
self._log_warning("Não é um repositório Git")
except FileNotFoundError:
self._log_warning("Git não está instalado ou não está no PATH")
except Exception as e:
self._log_error(f"Falha na verificação do Git: {e}")
def _step4_check_env_file(self):
self.log("\n===== Verificação do Arquivo .env =====")
try:
result = subprocess.run(['git', 'rev-parse', '--show-toplevel'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if result.returncode == 0:
git_root = result.stdout.strip()
env_path = os.path.join(git_root, '.env')
if os.path.isfile(env_path):
self.log(f"Arquivo .env localizado em: {env_path}")
try:
with open(env_path, 'r') as f:
has_api_key = any(line.strip().startswith('OPENAI_API_KEY=') for line in f)
if has_api_key:
self.log("OPENAI_API_KEY encontrado no arquivo .env")
else:
self._log_warning("OPENAI_API_KEY não encontrado no arquivo .env")
except Exception as e:
self._log_error(f"Não é possível ler o arquivo .env: {e}")
else:
self._log_warning("Arquivo .env não encontrado na raiz do projeto")
# Verifica arquivos .env adicionais
for root, _, files in os.walk(git_root):
if '.env' in files and os.path.join(root, '.env') != env_path:
self._log_warning(f"Arquivo .env adicional encontrado em: {os.path.join(root, '.env')}")
else:
self._log_warning("Diretório raiz do Git não encontrado. Não é possível realizar a verificação do arquivo .env.")
except FileNotFoundError:
self._log_warning("Git não está instalado ou não está no PATH")
except Exception as e:
self._log_error(f"Falha na verificação do arquivo .env: {e}")
def _step5_anaconda_check(self):
self.log("\n===== Verificação do Ambiente Anaconda =====")
try:
conda_prefix = os.environ.get('CONDA_PREFIX')
if conda_prefix:
self.log("Ambiente Anaconda ativo:")
self.log(f"Caminho do ambiente: {conda_prefix}")
self.log(f"Nome do ambiente: {os.path.basename(conda_prefix)}")
conda_exe = os.environ.get('CONDA_EXE', 'conda')
result = subprocess.run([conda_exe, '--version'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if result.returncode == 0:
self.log(f"Versão do Conda: {result.stdout.strip()}")
else:
self._log_warning("Não foi possível determinar a versão do Conda")
self._check_python_packages()
else:
self.log("Nenhum ambiente Anaconda ativo detectado")
except Exception as e:
self._log_error(f"Falha na verificação do ambiente Anaconda: {e}")
def _step6_virtualenv_check(self):
self.log("\n===== Verificação do Virtualenv =====")
try:
virtual_env = os.environ.get('VIRTUAL_ENV')
if virtual_env:
self.log("Virtualenv ativo:")
self.log(f"Caminho do ambiente: {virtual_env}")
self.log(f"Nome do ambiente: {os.path.basename(virtual_env)}")
self._check_python_packages()
else:
self.log("Nenhum virtualenv ativo detectado")
if not virtual_env and not os.environ.get('CONDA_PREFIX'):
self._log_warning("Nem virtualenv nem ambiente Anaconda estão ativos")
except Exception as e:
self._log_error(f"Falha na verificação do virtualenv: {e}")
def _check_python_packages(self):
self.log("\nAmbiente Python:")
self.log(f"Versão do Python: {sys.version}")
self.log(f"Executável do Python: {sys.executable}")
required_packages = ['openai', 'python-dotenv', 'requests', 'gradio', 'transformers']
try:
import pkg_resources
installed = {pkg.key: pkg.version for pkg in pkg_resources.working_set}
self.log("\nVersões dos pacotes necessários:")
for package in required_packages:
if package in installed:
self.log(f"{package}: {installed[package]}")
else:
self._log_error(f"Pacote obrigatório '{package}' não está instalado")
# Verifica pacotes potencialmente conflitantes
problem_pairs = [
('openai', 'openai-python'),
('python-dotenv', 'dotenv')
]
for pkg1, pkg2 in problem_pairs:
if pkg1 in installed and pkg2 in installed:
self._log_warning(f"Pacotes potencialmente conflitantes: {pkg1} e {pkg2}")
except ImportError:
self._log_error("Não foi possível importar 'pkg_resources' para verificar os pacotes instalados")
except Exception as e:
self._log_error(f"Falha na verificação de pacotes: {e}")
def _step7_network_connectivity(self):
self.log("\n===== Verificação da Conectividade de Rede =====")
try:
self.log(f"Versão do SSL: {ssl.OPENSSL_VERSION}")
import requests
import speedtest # Importa a biblioteca speedtest-cli
# Verificação básica de conectividade
urls = [
'https://www.google.com',
'https://www.cloudflare.com'
]
connected = False
for url in urls:
try:
start_time = time.time()
response = requests.get(url, timeout=10)
elapsed_time = time.time() - start_time
response.raise_for_status()
self.log(f"✅ Conectado a {url}")
self.log(f" Tempo de resposta: {elapsed_time:.2f}s")
if elapsed_time > 2:
self._log_warning(f"Resposta lenta de {url}: {elapsed_time:.2f}s")
connected = True
break
except requests.exceptions.RequestException as e:
self._log_warning(f"Falha ao conectar-se a {url}: {e}")
else:
self.log("Conectividade básica OK")
if not connected:
self._log_error("Falha ao conectar-se a qualquer URL de teste")
return
# Teste de largura de banda usando speedtest-cli
self.log("\nRealizando teste de largura de banda com speedtest-cli...")
try:
st = speedtest.Speedtest()
st.get_best_server()
download_speed = st.download() # Bits por segundo
upload_speed = st.upload() # Bits por segundo
download_mbps = download_speed / 1e6 # Converte para Mbps
upload_mbps = upload_speed / 1e6
self.log(f"Velocidade de download: {download_mbps:.2f} Mbps")
self.log(f"Velocidade de upload: {upload_mbps:.2f} Mbps")
if download_mbps < 1:
self._log_warning("Velocidade de download baixa")
if upload_mbps < 0.5:
self._log_warning("Velocidade de upload baixa")
except speedtest.ConfigRetrievalError:
self._log_error("Falha ao obter a configuração do speedtest")
except Exception as e:
self._log_warning(f"Falha no teste de largura de banda: {e}")
except ImportError:
self._log_error("Pacotes obrigatórios não estão instalados. Instale-os com 'pip install requests speedtest-cli'")
except Exception as e:
self._log_error(f"Falha na verificação da conectividade de rede: {e}")
def _step8_environment_variables(self):
self.log("\n===== Verificação das Variáveis de Ambiente =====")
try:
# Verifica os caminhos do Python
pythonpath = os.environ.get('PYTHONPATH')
if pythonpath:
self.log("\nPYTHONPATH:")
for path in pythonpath.split(os.pathsep):
self.log(f" - {path}")
else:
self.log("\nPYTHONPATH não está definido.")
self.log("\nsys.path do Python:")
for path in sys.path:
self.log(f" - {path}")
# Verifica OPENAI_API_KEY
from dotenv import load_dotenv
load_dotenv()
api_key = os.environ.get('OPENAI_API_KEY')
if api_key:
self.log("OPENAI_API_KEY definido após chamar load_dotenv()")
if not api_key.startswith('sk-proj-') or len(api_key) < 12:
self._log_warning("Formato de OPENAI_API_KEY parece incorreto após chamar load_dotenv()")
else:
self._log_warning("Variável de ambiente OPENAI_API_KEY não está definida após chamar load_dotenv()")
except Exception as e:
self._log_error(f"Falha na verificação das variáveis de ambiente: {e}")
def _step9_additional_diagnostics(self):
self.log("\n===== Diagnósticos Adicionais =====")
try:
# Obtém os caminhos dos diretórios site-packages
import site
site_packages_paths = site.getsitepackages()
if hasattr(site, 'getusersitepackages'):
site_packages_paths.append(site.getusersitepackages())
# Função que verifica se um caminho está dentro de site-packages
def is_in_site_packages(path):
return any(os.path.commonpath([path, sp]) == sp for sp in site_packages_paths)
# Verifica possíveis conflitos de nome no diretório atual e no sys.path
conflict_names = ['openai.py', 'dotenv.py']
# Verifica o diretório atual
current_dir = os.getcwd()
for name in conflict_names:
conflict_path = os.path.join(current_dir, name)
if os.path.isfile(conflict_path):
self._log_warning(f"Encontrado '{name}' no diretório atual, o que pode causar conflitos de importação: {conflict_path}")
# Verifica os diretórios em sys.path
for path in sys.path:
if not path or is_in_site_packages(path):
continue # Ignora site-packages e caminhos vazios
for name in conflict_names:
conflict_file = os.path.join(path, name)
if os.path.isfile(conflict_file):
self._log_warning(f"Potencial conflito de nomenclatura: {conflict_file}")
# Verifica o diretório temporário
try:
with tempfile.NamedTemporaryFile() as tmp:
self.log(f"Diretório temporário é gravável: {os.path.dirname(tmp.name)}")
except Exception as e:
self._log_error(f"Não é possível gravar no diretório temporário: {e}")
except Exception as e:
self._log_error(f"Falha na execução dos diagnósticos adicionais: {e}")
if __name__ == "__main__":
diagnostics = Diagnostics()
diagnostics.run()

View File

@@ -0,0 +1,547 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "2a793b1d-a0a9-404c-ada6-58937227cfce",
"metadata": {},
"source": [
"# Puxa vida!\n",
"\n",
"Se você chegou até aqui, ainda está enfrentando problemas para configurar o seu ambiente. Sinto muito! Aguente firme e logo devemos colocar tudo para funcionar.\n",
"\n",
"Configurar um ambiente de Ciência de Dados pode ser desafiador porque há muita coisa acontecendo nos bastidores. Mas vamos chegar lá.\n",
"\n",
"E lembre-se: estou à disposição para ajudar. Envie uma mensagem ou e-mail para ed@edwarddonner.com e eu assumo o caso. A última célula deste notebook contém alguns diagnósticos que vão me ajudar a entender o que está acontecendo.\n",
"\n",
"Talvez você queira dar uma olhada rápida no [faq](https://edwarddonner.com/faq) do meu site caso o seu problema já esteja descrito por lá.\n"
]
},
{
"cell_type": "markdown",
"id": "8ebcd34f",
"metadata": {},
"source": [
"# Se você está fazendo a nova versão do curso (usando uv e Cursor, em vez de Anaconda), confira esta seção primeiro; caso contrário, vá direto para a próxima seção intitulada \"Começando pelo básico\"\n",
"\n",
"## 1. Verifique as extensões do Cursor\n",
"\n",
"Só para confirmar que as extensões estão instaladas:\n",
"- Abra as extensões (View >> Extensions)\n",
"- Pesquise por python e, quando os resultados aparecerem, clique na ms-python e instale se ainda não estiver instalada\n",
"- Pesquise por jupyter e, quando os resultados aparecerem, clique na da Microsoft e instale se ainda não estiver instalada \n",
"Depois vá em View >> Explorer para trazer de volta o File Explorer.\n",
"\n",
"## 2. Conecte este kernel:\n",
"\n",
"Se você vir as palavras `Select Kernel` em um botão próximo ao canto superior direito desta janela, clique nele!\n",
"\n",
"Você deve ver um menu suspenso com o título \"Select kernel for..\" ou talvez precise escolher \"Python environment\" primeiro.\n",
"\n",
"Escolha a opção que começa com `.venv python 3.12` — ela deve ser a primeira opção. Talvez seja necessário clicar em \"Python Environments\" antes.\n",
"\n",
"Agora deve estar escrito `.venv (Python 3.12.x)` onde antes aparecia `Select Kernel`.\n",
"\n",
"Depois de clicar em \"Select Kernel\", se não houver nenhuma opção como `.venv (Python 3.12.x)`, siga o passo a passo: \n",
"1. No Mac: no menu Cursor, escolha Settings >> VS Code Settings (ATENÇÃO: selecione `VSCode Settings`, não `Cursor Settings`); \n",
"Ou no Windows PC: no menu File, escolha Preferences >> VS Code Settings (ATENÇÃO: selecione `VSCode Settings`, não `Cursor Settings`) \n",
"2. Na barra de busca das configurações, digite \"venv\" \n",
"3. No campo \"Path to folder with a list of Virtual Environments\", informe o caminho da raiz do projeto, como C:\\Users\\username\\projects\\llm_engineering (no Windows) ou /Users/username/projects/llm_engineering (no Mac ou Linux). \n",
"Em seguida, tente novamente.\n",
"\n",
"## 3. Problemas com o uv\n",
"\n",
"Confira este guia completo no meu [FAQ Q11](https://edwarddonner.com/faq/#11)"
]
},
{
"cell_type": "markdown",
"id": "98787335-346f-4ee4-9cb7-6181b0e1b964",
"metadata": {},
"source": [
"# Começando pelo básico\n",
"\n",
"## Verificando sua conexão com a internet\n",
"\n",
"Primeiro, vamos verificar se não há problemas com VPN, firewall ou certificados.\n",
"\n",
"Clique na célula abaixo e pressione Shift+Return para executá-la. \n",
"Se isso gerar problemas, tente seguir estas instruções para resolver: \n",
"https://chatgpt.com/share/676e6e3b-db44-8012-abaa-b3cf62c83eb3\n",
"\n",
"Também ouvi que você pode ter problemas se estiver usando um computador corporativo com o software de segurança zscaler.\n",
"\n",
"Alguns conselhos de alunos nessa situação com o zscaler:\n",
"\n",
"> No prompt do Anaconda, isto ajudou às vezes, embora ainda ocorressem falhas ocasionais ao executar código no Jupyter:\n",
"`conda config --set ssl_verify false` \n",
"Outra coisa que ajudou foi adicionar `verify=False` sempre que houver `request.get(..)`, então `request.get(url, headers=headers)` se torna `request.get(url, headers=headers, verify=False)`"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d296f9b6-8de4-44db-b5f5-9b653dfd3d81",
"metadata": {},
"outputs": [],
"source": [
"import urllib.request\n",
"\n",
"try:\n",
" response = urllib.request.urlopen(\"https://www.google.com\", timeout=10)\n",
" if response.status != 200:\n",
" print(\"Não foi possível acessar o Google - pode haver problemas com sua internet / VPN / firewall?\")\n",
" else:\n",
" print(\"Conectado à internet e com acesso ao Google\")\n",
"except Exception as e:\n",
" print(f\"Falha na conexão com este erro: {e}\")"
]
},
{
"cell_type": "markdown",
"id": "d91da3b2-5a41-4233-9ed6-c53a7661b328",
"metadata": {},
"source": [
"## Mais alguns \"perrengues\" ocasionais para quem está no PC\n",
"\n",
"Existem 4 armadilhas no Windows das quais você deve estar ciente: \n",
"1. Permissões. Consulte este [tutorial](https://chatgpt.com/share/67b0ae58-d1a8-8012-82ca-74762b0408b0) sobre permissões no Windows\n",
"2. Antivírus, firewall e VPN. Eles podem interferir em instalações e no acesso à rede; tente desativá-los temporariamente, se necessário\n",
"3. O terrível limite de 260 caracteres para nomes de arquivo no Windows - aqui está uma [explicação e correção](https://chatgpt.com/share/67b0afb9-1b60-8012-a9f7-f968a5a910c7) completas!\n",
"4. Se você nunca trabalhou com pacotes de Ciência de Dados no seu computador, talvez precise instalar o Microsoft Build Tools. Aqui estão as [instruções](https://chatgpt.com/share/67b0b762-327c-8012-b809-b4ec3b9e7be0). Um aluno também mencionou que [estas instruções](https://github.com/bycloudai/InstallVSBuildToolsWindows) podem ser úteis para quem está no Windows 11. \n",
"\n",
"## E para quem usa Mac\n",
"\n",
"1. Se você está começando a desenvolver no Mac, pode precisar instalar as ferramentas de desenvolvedor do XCode. Aqui estão as [instruções](https://chatgpt.com/share/67b0b8d7-8eec-8012-9a37-6973b9db11f5).\n",
"2. Assim como no PC, antivírus, firewall e VPN podem causar problemas. Eles interferem em instalações e no acesso à rede; tente desativá-los temporariamente, se necessário"
]
},
{
"cell_type": "markdown",
"id": "f5190688-205a-46d1-a0dc-9136a42ad0db",
"metadata": {},
"source": [
"# Passo 1\n",
"\n",
"Tente executar a próxima célula (clique na célula abaixo desta e pressione shift+return).\n",
"\n",
"Se isso gerar um erro, provavelmente você não está executando em um ambiente \"ativado\". Consulte a Parte 5 do guia SETUP para [PC](../SETUP-PC.md) ou [Mac](../SETUP-mac.md) para configurar o ambiente Anaconda (ou virtualenv) e ativá-lo antes de rodar `jupyter lab`.\n",
"\n",
"Se observar o prompt do Anaconda (PC) ou o Terminal (Mac), você deve ver `(llms)` no prompt onde inicia `jupyter lab` - esse é o sinal de que o ambiente llms está ativado.\n",
"\n",
"Se você já estiver em um ambiente ativado, a próxima tentativa é reiniciar tudo:\n",
"1. Feche todas as janelas do Jupyter, como esta aqui\n",
"2. Encerre todos os prompts de comando / terminais / Anaconda\n",
"3. Repita a Parte 5 das instruções de SETUP para iniciar um novo ambiente ativado e executar `jupyter lab` a partir do diretório `llm_engineering` \n",
"4. Volte a este notebook e vá em Kernel >> Restart Kernel and Clear Outputs of All Cells\n",
"5. Tente executar novamente a célula abaixo.\n",
"\n",
"Se **isso** não funcionar, entre em contato comigo! Vou responder rapidamente e vamos resolver. Execute os diagnósticos (última célula deste notebook) para que eu possa depurar. Se você usou Anaconda, pode ser que, por algum motivo, o ambiente tenha sido corrompido; nesse caso, a solução mais simples é usar a abordagem com virtualenv (Parte 2B nos guias de setup)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7c8c0bb3-0e94-466e-8d1a-4dfbaa014cbe",
"metadata": {},
"outputs": [],
"source": [
"# Algumas verificações rápidas para garantir que o seu ambiente Conda ou VirtualEnv está como esperado\n",
"# O nome do ambiente deve ser: llms\n",
"\n",
"import os\n",
"conda_name, venv_name = \"\", \"\"\n",
"\n",
"conda_prefix = os.environ.get('CONDA_PREFIX')\n",
"if conda_prefix:\n",
" print(\"O ambiente Anaconda está ativo:\")\n",
" print(f\"Caminho do ambiente: {conda_prefix}\")\n",
" conda_name = os.path.basename(conda_prefix)\n",
" print(f\"Nome do ambiente: {conda_name}\")\n",
"\n",
"virtual_env = os.environ.get('VIRTUAL_ENV')\n",
"if virtual_env:\n",
" print(\"O virtualenv está ativo:\")\n",
" print(f\"Caminho do ambiente: {virtual_env}\")\n",
" venv_name = os.path.basename(virtual_env)\n",
" print(f\"Nome do ambiente: {venv_name}\")\n",
"\n",
"if conda_name != \"llms\" and venv_name != \"llms\" and venv_name != \"venv\" and venv_name != \".venv\":\n",
" print(\"Nem o Anaconda nem o virtualenv parecem estar ativados com o nome esperado 'llms', 'venv' ou '.venv'\")\n",
" print(\"Você executou 'jupyter lab' a partir de um ambiente ativado com (llms) aparecendo na linha de comando?\")\n",
" print(\"Em caso de dúvida, feche todos os jupyter lab e siga a Parte 5 do guia SETUP-PC ou SETUP-mac.\")"
]
},
{
"cell_type": "markdown",
"id": "45e2cc99-b7d3-48bd-b27c-910206c4171a",
"metadata": {},
"source": [
"# Passo 1.1\n",
"\n",
"## Hora de verificar se o ambiente está configurado e as dependências foram instaladas\n",
"\n",
"Agora, a próxima célula deve executar sem gerar saída — sem erros de importação. \n",
"\n",
"Para quem está na nova versão do curso (a partir de outubro de 2025), um erro indica que o kernel selecionado não é o correto.\n",
"\n",
"Para quem está na versão original do curso:\n",
"\n",
"> Erros de importação podem indicar que você iniciou o jupyter lab sem ativar o ambiente. Veja a Parte 5 do SETUP. \n",
"> Talvez seja preciso reiniciar o kernel e o Jupyter Lab. \n",
"> Ou pode haver algo errado com o Anaconda. Nesse caso, siga estas instruções de recuperação: \n",
"> Primeiro, feche tudo e reinicie o computador. \n",
"> Depois, em um Anaconda Prompt (PC) ou Terminal (Mac), com o ambiente ativado e **(llms)** aparecendo no prompt, no diretório llm_engineering, execute: \n",
"> `python -m pip install --upgrade pip` \n",
"> `pip install --retries 5 --timeout 15 --no-cache-dir --force-reinstall -r requirements.txt` \n",
"> Observe cuidadosamente se há erros e me avise. \n",
"> Se receber instruções para instalar o Microsoft Build Tools ou o Apple XCode, siga essas instruções. \n",
"> Depois, tente novamente!\n",
"> Por fim, se ainda assim não der certo, experimente a Parte 2B do SETUP, a alternativa à Parte 2 (com Python 3.11 ou Python 3.12). \n",
"> Se continuar em dúvida, rode os diagnósticos (última célula deste notebook) e me envie um e-mail em ed@edwarddonner.com"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6c78b7d9-1eea-412d-8751-3de20c0f6e2f",
"metadata": {},
"outputs": [],
"source": [
"# Este import deve funcionar se o seu ambiente estiver ativo e as dependências estiverem instaladas!\n",
"\n",
"from openai import OpenAI"
]
},
{
"cell_type": "markdown",
"id": "b66a8460-7b37-4b4c-a64b-24ae45cf07eb",
"metadata": {},
"source": [
"# Passo 2\n",
"\n",
"Vamos verificar se o seu arquivo .env existe e se a chave da OpenAI está configurada corretamente nele. \n",
"Execute este código e verifique se ele exibe uma mensagem de sucesso; do contrário, siga as instruções dele.\n",
"\n",
"Se não houver sucesso, o código não conseguiu encontrar um arquivo chamado `.env` na pasta `llm_engineering`. \n",
"O nome do arquivo precisa ser exatamente `.env` - não vai funcionar se ele se chamar `my-keys.env` ou `.env.doc`. \n",
"É possível que o `.env` na verdade esteja com o nome `.env.txt`? No Windows, talvez seja necessário alterar uma configuração no File Explorer para garantir que as extensões de arquivo apareçam (\"Show file extensions\" em \"On\"). Você também deve ver as extensões se digitar `dir` no diretório `llm_engineering`.\n",
"\n",
"Alguns detalhes traiçoeiros para observar: \n",
"- No arquivo .env, não deve haver espaço entre o sinal de igual e a chave. Exemplo: `OPENAI_API_KEY=sk-proj-...`\n",
"- Se você copiou e colou sua chave de outro aplicativo, certifique-se de que os hífens não foram substituídos por traços longos \n",
"\n",
"Observe que o arquivo `.env` não aparece no navegador de arquivos do Jupyter Lab, porque o Jupyter oculta arquivos que começam com ponto por segurança; eles são considerados arquivos ocultos. Se precisar alterar o nome, use um terminal ou o File Explorer (PC) / Finder (Mac). Se isso estiver difícil, peça ajuda ao ChatGPT ou me envie um e-mail!\n",
"\n",
"Se estiver com dificuldade para criar o arquivo `.env`, podemos fazê-lo com código! Veja a célula após a próxima.\n",
"\n",
"É importante iniciar o `jupyter lab` a partir do diretório raiz do projeto, `llm_engineering`. Se não tiver feito isso, esta célula pode apresentar problemas."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "caa4837e-b970-4f89-aa9a-8aa793c754fd",
"metadata": {},
"outputs": [],
"source": [
"from pathlib import Path\n",
"\n",
"parent_dir = Path(\"..\")\n",
"env_path = parent_dir / \".env\"\n",
"\n",
"if env_path.exists() and env_path.is_file():\n",
" print(\"Arquivo .env encontrado.\")\n",
"\n",
" # Ler o conteúdo do arquivo .env\n",
" with env_path.open(\"r\") as env_file:\n",
" contents = env_file.readlines()\n",
"\n",
" key_exists = any(line.startswith(\"OPENAI_API_KEY=\") for line in contents)\n",
" good_key = any(line.startswith(\"OPENAI_API_KEY=sk-proj-\") for line in contents)\n",
" classic_problem = any(\"OPEN_\" in line for line in contents)\n",
" \n",
" if key_exists and good_key:\n",
" print(\"SUCESSO! OPENAI_API_KEY encontrada e com o prefixo correto\")\n",
" elif key_exists:\n",
" print(\"Foi encontrada uma OPENAI_API_KEY, mas ela não tinha o prefixo esperado sk-proj- \\nPor favor, confira sua chave no arquivo..\")\n",
" elif classic_problem:\n",
" print(\"Não encontrei uma OPENAI_API_KEY, mas percebi que 'OPEN_' aparece - será que há um erro de digitação como OPEN_API_KEY em vez de OPENAI_API_KEY?\")\n",
" else:\n",
" print(\"Não encontrei uma OPENAI_API_KEY no arquivo .env\")\n",
"else:\n",
" print(\"Arquivo .env não encontrado no diretório llm_engineering. Ele precisa ter exatamente o nome: .env\")\n",
" \n",
" possible_misnamed_files = list(parent_dir.glob(\"*.env*\"))\n",
" \n",
" if possible_misnamed_files:\n",
" print(\"\\nAviso: nenhum arquivo '.env' foi encontrado, mas os seguintes arquivos no diretório llm_engineering contêm '.env' no nome. Talvez seja preciso renomeá-los?\")\n",
" for file in possible_misnamed_files:\n",
" print(file.name)"
]
},
{
"cell_type": "markdown",
"id": "105f9e0a-9ff4-4344-87c8-e3e41bc50869",
"metadata": {},
"source": [
"## Plano B - código em Python para criar o arquivo .env para você\n",
"\n",
"Execute a próxima célula apenas se estiver com problemas para criar o arquivo .env. \n",
"Substitua o texto na primeira linha de código pela sua chave da OpenAI."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ab9ea6ef-49ee-4899-a1c7-75a8bd9ac36b",
"metadata": {},
"outputs": [],
"source": [
"# Execute este código apenas se quiser que um arquivo .env seja criado para você!\n",
"\n",
"# Coloque sua chave entre as aspas\n",
"make_me_a_file_with_this_key = \"coloque sua chave aqui entre estas aspas.. ela deve começar com sk-proj-\"\n",
"\n",
"# Altere para True se você já tiver um arquivo .env e quiser que eu o substitua\n",
"overwrite_if_already_exists = False \n",
"\n",
"from pathlib import Path\n",
"\n",
"parent_dir = Path(\"..\")\n",
"env_path = parent_dir / \".env\"\n",
"\n",
"if env_path.exists() and not overwrite_if_already_exists:\n",
" print(\"Já existe um arquivo .env - se quiser que eu crie um novo, defina a variável overwrite_if_already_exists como True acima\")\n",
"else:\n",
" try:\n",
" with env_path.open(mode='w', encoding='utf-8') as env_file:\n",
" env_file.write(f\"OPENAI_API_KEY={make_me_a_file_with_this_key}\")\n",
" print(f\".env criado com sucesso em {env_path}\")\n",
" if not make_me_a_file_with_this_key.startswith(\"sk-proj-\"):\n",
" print(f\"A chave fornecida começou com '{make_me_a_file_with_this_key[:8]}', diferente de sk-proj-; era isso mesmo que você queria?\")\n",
" print(\"Agora execute novamente a célula anterior para confirmar que o arquivo foi criado e que a chave está correta.\")\n",
" except Exception as e:\n",
" print(f\"Ocorreu um erro ao criar o arquivo .env: {e}\")"
]
},
{
"cell_type": "markdown",
"id": "0ba9420d-3bf0-4e08-abac-f2fbf0e9c7f1",
"metadata": {},
"source": [
"# Passo 3\n",
"\n",
"Agora vamos verificar se sua chave de API está configurada corretamente no arquivo `.env` e disponível com o pacote dotenv.\n",
"Tente executar a próxima célula."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0ee8e613-5a6e-4d1f-96ef-91132da545c8",
"metadata": {},
"outputs": [],
"source": [
"# Esta célula deve exibir sua chave de API na saída - siga as instruções impressas\n",
"\n",
"import os\n",
"from dotenv import load_dotenv\n",
"load_dotenv(override=True)\n",
"\n",
"api_key = os.getenv(\"OPENAI_API_KEY\")\n",
"\n",
"if not api_key:\n",
" print(\"Nenhuma chave de API foi encontrada - tente Kernel >> Restart Kernel And Clear Outputs of All Cells\")\n",
"elif not api_key.startswith(\"sk-proj-\"):\n",
" print(f\"Uma chave de API foi encontrada, mas ela começa com {api_key[:8]} em vez de sk-proj-; por favor, confirme se isso está correto.\")\n",
"elif api_key.strip() != api_key:\n",
" print(\"Uma chave de API foi encontrada, mas parece haver espaços ou tabulações no início ou no fim - remova-os, por favor\")\n",
"else:\n",
" print(\"Chave de API encontrada e tudo parece correto até agora!\")\n",
"\n",
"if api_key:\n",
" problematic_unicode_chars = ['\\u2013', '\\u2014', '\\u201c', '\\u201d', '\\u2026', '\\u2018', '\\u2019']\n",
" forbidden_chars = [\"'\", \" \", \"\\n\", \"\\r\", '\"']\n",
" \n",
" if not all(32 <= ord(char) <= 126 for char in api_key):\n",
" print(\"Possível problema: talvez haja caracteres não imprimíveis incluídos na chave?\")\n",
" elif any(char in api_key for char in problematic_unicode_chars):\n",
" print(\"Possível problema: talvez haja caracteres especiais, como traços longos ou aspas curvas na chave - você copiou de um editor de texto?\")\n",
" elif any(char in api_key for char in forbidden_chars):\n",
" print(\"Possível problema: há aspas, espaços ou linhas em branco na sua chave?\")\n",
" else:\n",
" print(\"A chave de API contém caracteres válidos\")\n",
" \n",
"print(f\"\\nAqui está a chave --> {api_key} <--\")\n",
"print()\n",
"print(\"Se a chave estiver correta, vá em Edit >> Clear Cell Output para que ela não fique visível aqui!\")"
]
},
{
"cell_type": "markdown",
"id": "f403e515-0e7d-4be4-bb79-5a102dbd6c94",
"metadata": {},
"source": [
"## Deve aparecer algumas verificações com algo assim:\n",
"\n",
"`Here is the key --> sk-proj-blahblahblah <--`\n",
"\n",
"Se nenhuma chave foi exibida, espero que as mensagens tenham dado informação suficiente para resolver. Caso contrário, fale comigo!\n",
"\n",
"Existe ainda um último recurso, se preferir: você pode dispensar o uso de arquivos .env e sempre fornecer sua chave de API manualmente. \n",
"Sempre que encontrar isto no código: \n",
"`openai = OpenAI()` \n",
"Você pode substituir por: \n",
"`openai = OpenAI(api_key=\"sk-proj-xxx\")`\n"
]
},
{
"cell_type": "markdown",
"id": "42afad1f-b0bf-4882-b469-7709060fee3a",
"metadata": {},
"source": [
"# Passo 4\n",
"\n",
"Agora execute o código abaixo e, com sorte, você verá que o GPT consegue lidar com aritmética básica!!\n",
"\n",
"Se não funcionar, veja a célula abaixo."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cccb58e7-6626-4033-9dc1-e7e3ff742f6b",
"metadata": {},
"outputs": [],
"source": [
"from openai import OpenAI\n",
"from dotenv import load_dotenv\n",
"load_dotenv(override=True)\n",
"\n",
"my_api_key = os.getenv(\"OPENAI_API_KEY\")\n",
"\n",
"print(f\"Usando a chave de API --> {my_api_key} <--\")\n",
"\n",
"openai = OpenAI()\n",
"completion = openai.chat.completions.create(\n",
" model='gpt-4o-mini',\n",
" messages=[{\"role\":\"user\", \"content\": \"What's 2+2?\"}],\n",
")\n",
"print(completion.choices[0].message.content)\n",
"print(\"Agora vá em Edit >> Clear Cell Output para remover a exibição da sua chave.\")"
]
},
{
"cell_type": "markdown",
"id": "81046a77-c359-4388-929f-ffc8ad5cb93c",
"metadata": {
"jp-MarkdownHeadingCollapsed": true
},
"source": [
"## Se a chave foi configurada corretamente e ainda assim não funcionou\n",
"\n",
"### Se houver um erro da OpenAI sobre a sua chave ou um Rate Limit Error, há algo de errado com a sua chave de API!\n",
"\n",
"Primeiro, confira [esta página](https://platform.openai.com/settings/organization/billing/overview) para garantir que você tem saldo positivo.\n",
"A OpenAI exige que você mantenha um saldo positivo e estabelece valores mínimos, normalmente em torno de US$5 na moeda local. Minha defesa da OpenAI é que isso vale muito para a sua formação: por menos do que o preço de um álbum de música, você ganha uma grande experiência comercial. Mas isso não é obrigatório para o curso; o README traz instruções para usar modelos open-source gratuitos via Ollama sempre que utilizarmos a OpenAI.\n",
"\n",
"A página de cobrança da OpenAI com o saldo fica aqui: \n",
"https://platform.openai.com/settings/organization/billing/overview \n",
"A OpenAI pode levar alguns minutos para liberar sua chave depois que você reforça o saldo. \n",
"Um aluno fora dos EUA comentou que precisou habilitar pagamentos internacionais no cartão de crédito para que tudo funcionasse. \n",
"\n",
"É improvável, mas se houver algo de errado com sua chave, você pode tentar criar uma nova (botão no canto superior direito) aqui: \n",
"https://platform.openai.com/api-keys\n",
"\n",
"### Verifique se você consegue usar o gpt-4o-mini no playground da OpenAI\n",
"\n",
"Para confirmar que a cobrança está ativa e sua chave está válida, experimente usar o gtp-4o-mini diretamente: \n",
"https://platform.openai.com/playground/chat?models=gpt-4o-mini\n",
"\n",
"### Se houver um erro relacionado a certificados\n",
"\n",
"Se você encontrou um erro de certificados como: \n",
"`ConnectError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1000)` \n",
"Então substitua:\n",
"`openai = OpenAI()` \n",
"por: \n",
"`import httpx` \n",
"`openai = OpenAI(http_client=httpx.Client(verify=False))` \n",
"E substitua também: \n",
"`requests.get(url, headers=headers)` \n",
"por: \n",
"`requests.get(url, headers=headers, verify=False)` \n",
"Se isso funcionar, você está em boa forma. Será preciso ajustar os labs do mesmo jeito sempre que encontrar esse erro de certificado. \n",
"Essa abordagem não é adequada para código de produção, mas serve para nossos experimentos. Talvez seja necessário falar com o suporte de TI para entender se há restrições no seu ambiente.\n",
"\n",
"## Se nada disso funcionar:\n",
"\n",
"(1) Tente colar o seu erro no ChatGPT ou no Claude! É impressionante como eles resolvem muitas situações\n",
"\n",
"(2) Tente criar outra chave, substituir no arquivo .env e executar novamente!\n",
"\n",
"(3) Fale comigo! Rode os diagnósticos na célula abaixo e me envie um e-mail com os problemas para ed@edwarddonner.com\n",
"\n",
"Muito obrigado, e desculpe por todo esse transtorno!"
]
},
{
"cell_type": "markdown",
"id": "dc83f944-6ce0-4b5c-817f-952676e284ec",
"metadata": {},
"source": [
"# Coletando informações diagnósticas essenciais\n",
"\n",
"## Execute a próxima célula para coletar alguns dados importantes\n",
"\n",
"Execute a próxima célula; ela deve levar cerca de um minuto para rodar. A maior parte desse tempo verifica a sua largura de banda.\n",
"Depois, envie por e-mail o resultado da última célula para ed@edwarddonner.com. \n",
"Como alternativa: isso criará um arquivo chamado report.txt - basta anexá-lo ao seu e-mail."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "248204f0-7bad-482a-b715-fb06a3553916",
"metadata": {},
"outputs": [],
"source": [
"# Execute meu relatório de diagnósticos para coletar informações essenciais de depuração\n",
"# Por favor, envie os resultados por e-mail. Você pode copiar e colar a saída ou anexar o arquivo report.txt\n",
"\n",
"!pip install -q requests speedtest-cli psutil setuptools\n",
"from diagnostics import Diagnostics\n",
"Diagnostics().run()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e1955b9a-d344-4782-b448-2770d0edd90c",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"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.12.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,662 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "d15d8294-3328-4e07-ad16-8a03e9bbfdb9",
"metadata": {},
"source": [
"# SEU PRIMEIRO LAB\n",
"### Por favor, leia esta seção. Ela é valiosa para deixar você preparado, mesmo que seja uma leitura longa -- é um conteúdo importante.\n",
"\n",
"### Também não deixe de ler [README.md](../README.md)! Mais informações sobre os vídeos atualizados no README e [no topo dos recursos do curso em roxo](https://edwarddonner.com/2024/11/13/llm-engineering-resources/)\n",
"\n",
"## Seu primeiro projeto com um LLM de fronteira\n",
"\n",
"Ao final deste curso, você terá construído uma solução autônoma de IA agentica com 7 agentes que colaboram para resolver um problema de negócios. Tudo a seu tempo! Vamos começar com algo menor...\n",
"\n",
"Nosso objetivo é programar um novo tipo de navegador web. Dê a ele uma URL e ele responderá com um resumo. O Reader's Digest da internet!!\n",
"\n",
"Antes de começar, você deve ter concluído a configuração indicada no README.\n",
"\n",
"### Se você é novo em trabalhar com \"Notebooks\" (também conhecidos como Labs ou Jupyter Lab)\n",
"\n",
"Bem-vindo ao maravilhoso mundo da experimentação em Data Science! Simplesmente clique em cada \"célula\" que tiver código, como a célula logo abaixo deste texto, e pressione Shift+Return para executar aquela célula. Certifique-se de rodar todas as células, começando do topo, em ordem.\n",
"\n",
"Por favor, consulte a [pasta Guides](../guides/01_intro.ipynb) para ver todos os guias.\n",
"\n",
"## Estou aqui para ajudar\n",
"\n",
"Se você tiver qualquer problema, por favor, entre em contato. \n",
"Estou disponível pela plataforma, ou via ed@edwarddonner.com, ou em https://www.linkedin.com/in/eddonner/ caso você queira se conectar (e eu adoro me conectar!) \n",
"E isso é novo para mim, mas também estou experimentando o X em [@edwarddonner](https://x.com/edwarddonner) - se você estiver no X, por favor me mostre como é que se faz 😂 \n",
"\n",
"## Mais soluções de problemas\n",
"\n",
"Consulte o notebook [troubleshooting](../setup/troubleshooting.ipynb) na pasta de configuração para diagnosticar e corrigir problemas comuns. No final dele há um script de diagnósticos com algumas informações úteis de depuração.\n",
"\n",
"## Se isso já for rotina!\n",
"\n",
"Se você já estiver confortável com o material de hoje, segure as pontas; você pode avançar rapidamente pelos primeiros labs - iremos nos aprofundar muito mais conforme as semanas avançam. No fim, ajustaremos finamente nosso próprio LLM para competir com a OpenAI!\n",
"\n",
"<table style=\"margin: 0; text-align: left;\">\n",
" <tr>\n",
" <td style=\"width: 150px; height: 150px; vertical-align: middle;\">\n",
" <img src=\"../assets/important.jpg\" width=\"150\" height=\"150\" style=\"display: block;\" />\n",
" </td>\n",
" <td>\n",
" <h2 style=\"color:#900;\">Por favor, leia - nota importante</h2>\n",
" <span style=\"color:#900;\">A forma como colaboro com você pode ser diferente de outros cursos que você já fez. Prefiro não digitar código enquanto você assiste. Em vez disso, executo Jupyter Labs, como este, e lhe dou uma intuição sobre o que está acontecendo. Minha sugestão é que você execute tudo com atenção por conta própria, <b>depois</b> de assistir à aula. Adicione instruções de print para entender o que está acontecendo e depois crie suas próprias variações. Se você tiver uma conta no Github, use-a para mostrar suas variações. Isso não é apenas prática essencial, como também demonstra suas habilidades para outras pessoas, incluindo talvez futuros clientes ou empregadores...</span>\n",
" </td>\n",
" </tr>\n",
"</table>\n",
"<table style=\"margin: 0; text-align: left;\">\n",
" <tr>\n",
" <td style=\"width: 150px; height: 150px; vertical-align: middle;\">\n",
" <img src=\"../assets/resources.jpg\" width=\"150\" height=\"150\" style=\"display: block;\" />\n",
" </td>\n",
" <td>\n",
" <h2 style=\"color:#f71;\">Este código é um recurso vivo - fique de olho nos meus e-mails</h2>\n",
" <span style=\"color:#f71;\">Eu publico atualizações do código regularmente. À medida que as pessoas fazem perguntas, adiciono mais exemplos ou comentários aprimorados. Como resultado, você notará que o código abaixo não é idêntico ao dos vídeos. Tudo dos vídeos está aqui; mas também acrescentei explicações melhores e novos modelos como o DeepSeek. Considere isto como um livro interativo.<br/><br/>\n",
" Tento enviar e-mails regularmente com atualizações importantes relacionadas ao curso. Você pode encontrá-los na seção 'Announcements' da Udemy na barra lateral esquerda. Você também pode optar por receber meus e-mails pelas suas Configurações de Notificação na Udemy. Respeito a sua caixa de entrada e sempre procuro acrescentar valor nas minhas mensagens!\n",
" </span>\n",
" </td>\n",
" </tr>\n",
"</table>\n",
"<table style=\"margin: 0; text-align: left;\">\n",
" <tr>\n",
" <td style=\"width: 150px; height: 150px; vertical-align: middle;\">\n",
" <img src=\"../assets/business.jpg\" width=\"150\" height=\"150\" style=\"display: block;\" />\n",
" </td>\n",
" <td>\n",
" <h2 style=\"color:#181;\">Valor de negócios destes exercícios</h2>\n",
" <span style=\"color:#181;\">Um último pensamento. Embora eu tenha desenhado estes notebooks para serem educacionais, também procurei deixá-los divertidos. Faremos coisas legais, como fazer LLMs contarem piadas e discutirem entre si. Mas, fundamentalmente, meu objetivo é ensinar habilidades que você possa aplicar nos negócios. Vou explicar as implicações para o negócio ao longo do caminho, e vale a pena ter isso em mente: à medida que você ganha experiência com modelos e técnicas, pense em maneiras de colocar isso em prática no trabalho hoje. Por favor, entre em contato se quiser conversar mais ou se tiver ideias para compartilhar comigo.</span>\n",
" </td>\n",
" </tr>\n",
"</table>"
]
},
{
"cell_type": "markdown",
"id": "83f28feb",
"metadata": {},
"source": [
"### Se necessário, instale as extensões do Cursor\n",
"\n",
"1. No menu View, selecione Extensions\n",
"2. Pesquise por Python\n",
"3. Clique em \"Python\" feito por \"ms-python\" e selecione Install se ainda não estiver instalado\n",
"4. Pesquise por Jupyter\n",
"5. Clique em \"Jupyter\" feito por \"ms-toolsai\" e selecione Install se ainda não estiver instalado\n",
"\n",
"\n",
"### Em seguida, selecione o Kernel\n",
"\n",
"Clique em \"Select Kernel\" no canto superior direito\n",
"\n",
"Escolha \"Python Environments...\"\n",
"\n",
"Depois escolha aquele que se parece com `.venv (Python 3.12.x) .venv/bin/python` - ele deve estar marcado como \"Recommended\" e ter uma estrela grande ao lado.\n",
"\n",
"Qualquer problema com isso? Vá para o troubleshooting.\n",
"\n",
"### Observação: você precisará definir o Kernel em cada notebook.."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "4e2a9393-7767-488e-a8bf-27c12dca35bd",
"metadata": {},
"outputs": [],
"source": [
"# importações\n",
"\n",
"import os\n",
"from dotenv import load_dotenv\n",
"from scraper import fetch_website_contents\n",
"from IPython.display import Markdown, display\n",
"from openai import OpenAI\n",
"\n",
"# Se você receber um erro ao executar esta célula, vá para o notebook de solução de problemas!"
]
},
{
"cell_type": "markdown",
"id": "6900b2a8-6384-4316-8aaa-5e519fca4254",
"metadata": {},
"source": [
"# Conectando-se à OpenAI (ou ao Ollama)\n",
"\n",
"A próxima célula é onde carregamos as variáveis de ambiente do seu arquivo `.env` e fazemos a conexão com a OpenAI. \n",
"\n",
"Se preferir usar o Ollama gratuito, consulte a seção \"Free Alternative to Paid APIs\" no README e, caso não saiba como fazer isso, há uma solução completa na pasta de soluções (day1_with_ollama.ipynb).\n",
"\n",
"## Solução de problemas caso você tenha dificuldades:\n",
"\n",
"Se você receber um \"Name Error\" - você executou todas as células de cima para baixo? Vá para o guia Python Foundations para um método infalível de encontrar e corrigir todos os Name Errors.\n",
"\n",
"Se isso não resolver, vá para o notebook [troubleshooting](../setup/troubleshooting.ipynb) para obter código passo a passo que identifica a causa raiz e corrige o problema!\n",
"\n",
"Ou então, fale comigo! Envie uma mensagem ou um e-mail para ed@edwarddonner.com e faremos isso funcionar.\n",
"\n",
"Alguma preocupação com custos de API? Veja minhas notas no README - os custos devem ser mínimos e você pode controlá-los a todo momento. Você também pode usar o Ollama como alternativa gratuita, que discutimos no Dia 2."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "7b87cadb-d513-4303-baee-a37b6f938e4d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Chave de API encontrada e, até aqui, parece tudo certo!\n"
]
}
],
"source": [
"# Carregue as variáveis de ambiente em um arquivo chamado .env\n",
"\n",
"load_dotenv(override=True)\n",
"api_key = os.getenv('OPENAI_API_KEY')\n",
"\n",
"# Verifique a chave\n",
"\n",
"if not api_key:\n",
" print(\"Nenhuma chave de API foi encontrada - por favor, vá até o notebook de solução de problemas nesta pasta para identificar e corrigir!\")\n",
"elif not api_key.startswith(\"sk-proj-\"):\n",
" print(\"Uma chave de API foi encontrada, mas ela não começa com sk-proj-; verifique se você está usando a chave correta - consulte o notebook de solução de problemas\")\n",
"elif api_key.strip() != api_key:\n",
" print(\"Uma chave de API foi encontrada, mas parece que ela pode ter caracteres de espaço ou tab no início ou no fim - remova-os, por favor - consulte o notebook de solução de problemas\")\n",
"else:\n",
" print(\"Chave de API encontrada e, até aqui, parece tudo certo!\")\n"
]
},
{
"cell_type": "markdown",
"id": "442fc84b-0815-4f40-99ab-d9a5da6bda91",
"metadata": {},
"source": [
"# Vamos fazer uma chamada rápida a um modelo de fronteira para começar, como uma prévia!"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "a58394bf-1e45-46af-9bfd-01e24da6f49a",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'role': 'user',\n",
" 'content': 'Olá, GPT! Esta é minha primeira mensagem para você! Oi!'}]"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Para dar uma prévia -- chamar a OpenAI com estas mensagens é tão simples. Qualquer problema, vá para o notebook de solução de problemas.\n",
"\n",
"message = \"Olá, GPT! Esta é minha primeira mensagem para você! Oi!\"\n",
"\n",
"messages = [{\"role\": \"user\", \"content\": message}]\n",
"\n",
"messages\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "08330159",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'Oi! Prazer em te conhecer. Sou o ChatGPT. Posso ajudar com várias coisas, por exemplo:\\n- esclarecer dúvidas e explicar temas\\n- escrever ou revisar textos (e-mails, trabalhos, posts)\\n- revisar gramática e estilo\\n- gerar ideias e brainstorms\\n- traduzir entre idiomas\\n- ajudar com programação\\n- planejar tarefas, estudos, viagens\\n\\nO que você quer fazer hoje? Conte-me o tema ou a tarefa, e o nível de detalhe que você prefere.'"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"openai = OpenAI()\n",
"\n",
"response = openai.chat.completions.create(model=\"gpt-5-nano\", messages=messages)\n",
"response.choices[0].message.content"
]
},
{
"cell_type": "markdown",
"id": "2aa190e5-cb31-456a-96cc-db109919cd78",
"metadata": {},
"source": [
"## OK, em frente com nosso primeiro projeto"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "2ef960cf-6dc2-4cda-afb3-b38be12f4c97",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Home - Edward Donner\n",
"\n",
"Home\n",
"Connect Four\n",
"Outsmart\n",
"An arena that pits LLMs against each other in a battle of diplomacy and deviousness\n",
"About\n",
"Posts\n",
"Well, hi there.\n",
"Im Ed. I like writing code and experimenting with LLMs, and hopefully youre here because you do too. I also enjoy DJing (but Im badly out of practice), amateur electronic music production (\n",
"very\n",
"amateur) and losing myself in\n",
"Hacker News\n",
", nodding my head sagely to things I only half understand.\n",
"Im the co-founder and CTO of\n",
"Nebula.io\n",
". Were applying AI to a field where it can make a massive, positive impact: helping people discover their potential and pursue their reason for being. Recruiters use our product today to source, understand, engage and manage talent. Im previously the founder and CEO of AI startup untapt,\n",
"acquired in 2021\n",
".\n",
"We work with groundbreaking, proprietary LLMs verticalized for talent, weve\n",
"patented\n",
"our matching model, and our award-winning platform has happy customers and tons of press coverage.\n",
"Connect\n",
"with me for more!\n",
"September 15, 2025\n",
"AI in Production: Gen AI and Agentic AI on AWS at scale\n",
"May 28, 2025\n",
"Connecting my courses become an LLM expert and leader\n",
"May 18, 2025\n",
"2025 AI Executive Briefing\n",
"April 21, 2025\n",
"The Complete Agentic AI Engineering Course\n",
"Navigation\n",
"Home\n",
"Connect Four\n",
"Outsmart\n",
"An arena that pits LLMs against each other in a battle of diplomacy and deviousness\n",
"About\n",
"Posts\n",
"Get in touch\n",
"ed [at] edwarddonner [dot] com\n",
"www.edwarddonner.com\n",
"Follow me\n",
"LinkedIn\n",
"Twitter\n",
"Facebook\n",
"Subscribe to newsletter\n",
"Type your email…\n",
"Subscribe\n"
]
}
],
"source": [
"# Vamos testar este utilitário\n",
"\n",
"ed = fetch_website_contents(\"https://edwarddonner.com\")\n",
"print(ed)"
]
},
{
"cell_type": "markdown",
"id": "6a478a0c-2c53-48ff-869c-4d08199931e1",
"metadata": {},
"source": [
"## Tipos de prompts\n",
"\n",
"Você talvez já saiba disso - mas, caso não, vai ficar muito familiarizado!\n",
"\n",
"Modelos como o GPT foram treinados para receber instruções de uma maneira específica.\n",
"\n",
"Eles esperam receber:\n",
"\n",
"**Um prompt de sistema** que lhes diz qual tarefa estão realizando e qual tom devem usar\n",
"\n",
"**Um prompt de usuário** -- o início da conversa ao qual devem responder"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "abdb8417-c5dc-44bc-9bee-2e059d162699",
"metadata": {},
"outputs": [],
"source": [
"# Defina nosso prompt de sistema - você pode experimentar com isso depois, alterando a última frase para \"Respond in markdown in Spanish.\"\n",
"\n",
"system_prompt = \"\"\"\n",
"Você é um assistente sarcástico que analisa o conteúdo de um site,\n",
"e fornece um resumo curto, sarcástico e bem-humorado, ignorando textos que possam estar relacionados à navegação.\n",
"Responda em markdown. Não coloque o markdown dentro de um bloco de código - responda apenas com o markdown.\n",
"\"\"\""
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "f0275b1b-7cfe-4f9d-abfa-7650d378da0c",
"metadata": {},
"outputs": [],
"source": [
"# Defina nosso prompt de usuário\n",
"\n",
"user_prompt_prefix = \"\"\"\n",
"Aqui está o conteúdo de um site.\n",
"Forneça um resumo curto deste site.\n",
"Se ele incluir notícias ou anúncios, então resuma-os também.\n",
"\n",
"\"\"\""
]
},
{
"cell_type": "markdown",
"id": "ea211b5f-28e1-4a86-8e52-c0b7677cadcc",
"metadata": {},
"source": [
"## Mensagens\n",
"\n",
"A API da OpenAI espera receber mensagens em uma estrutura específica.\n",
"Muitas das outras APIs compartilham essa estrutura:\n",
"\n",
"```python\n",
"[\n",
" {\"role\": \"system\", \"content\": \"system message goes here\"},\n",
" {\"role\": \"user\", \"content\": \"user message goes here\"}\n",
"]\n",
"```\n",
"Para dar uma prévia, as próximas 2 células fazem uma chamada bem simples - ainda não vamos exigir muito do poderoso GPT!"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "f25dcd35-0cd0-4235-9f64-ac37ed9eaaa5",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'2 + 2 é igual a 4.'"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"messages = [\n",
" {\"role\": \"system\", \"content\": \"Você é um assistente prestativo\"},\n",
" {\"role\": \"user\", \"content\": \"Quanto é 2 + 2?\"}\n",
"]\n",
"\n",
"response = openai.chat.completions.create(model=\"gpt-4.1-nano\", messages=messages)\n",
"response.choices[0].message.content"
]
},
{
"cell_type": "markdown",
"id": "d06e8d78-ce4c-4b05-aa8e-17050c82bb47",
"metadata": {},
"source": [
"## E agora vamos construir mensagens úteis para o GPT-4.1-mini, usando uma função"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "0134dfa4-8299-48b5-b444-f2a8c3403c88",
"metadata": {},
"outputs": [],
"source": [
"# Veja como esta função cria exatamente o formato acima\n",
"\n",
"def messages_for(website):\n",
" return [\n",
" {\"role\": \"system\", \"content\": system_prompt},\n",
" {\"role\": \"user\", \"content\": user_prompt_prefix + website}\n",
" ]"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "36478464-39ee-485c-9f3f-6a4e458dbc9c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'role': 'system',\n",
" 'content': '\\nVocê é um assistente sarcástico que analisa o conteúdo de um site,\\ne fornece um resumo curto, sarcástico e bem-humorado, ignorando textos que possam estar relacionados à navegação.\\nResponda em markdown. Não coloque o markdown dentro de um bloco de código - responda apenas com o markdown.\\n'},\n",
" {'role': 'user',\n",
" 'content': '\\nAqui está o conteúdo de um site.\\nForneça um resumo curto deste site.\\nSe ele incluir notícias ou anúncios, então resuma-os também.\\n\\nHome - Edward Donner\\n\\nHome\\nConnect Four\\nOutsmart\\nAn arena that pits LLMs against each other in a battle of diplomacy and deviousness\\nAbout\\nPosts\\nWell, hi there.\\nIm Ed. I like writing code and experimenting with LLMs, and hopefully youre here because you do too. I also enjoy DJing (but Im badly out of practice), amateur electronic music production (\\nvery\\namateur) and losing myself in\\nHacker News\\n, nodding my head sagely to things I only half understand.\\nIm the co-founder and CTO of\\nNebula.io\\n. Were applying AI to a field where it can make a massive, positive impact: helping people discover their potential and pursue their reason for being. Recruiters use our product today to source, understand, engage and manage talent. Im previously the founder and CEO of AI startup untapt,\\nacquired in 2021\\n.\\nWe work with groundbreaking, proprietary LLMs verticalized for talent, weve\\npatented\\nour matching model, and our award-winning platform has happy customers and tons of press coverage.\\nConnect\\nwith me for more!\\nSeptember 15, 2025\\nAI in Production: Gen AI and Agentic AI on AWS at scale\\nMay 28, 2025\\nConnecting my courses become an LLM expert and leader\\nMay 18, 2025\\n2025 AI Executive Briefing\\nApril 21, 2025\\nThe Complete Agentic AI Engineering Course\\nNavigation\\nHome\\nConnect Four\\nOutsmart\\nAn arena that pits LLMs against each other in a battle of diplomacy and deviousness\\nAbout\\nPosts\\nGet in touch\\ned [at] edwarddonner [dot] com\\nwww.edwarddonner.com\\nFollow me\\nLinkedIn\\nTwitter\\nFacebook\\nSubscribe to newsletter\\nType your email…\\nSubscribe'}]"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Experimente isto e depois teste com mais alguns sites\n",
"\n",
"messages_for(ed)"
]
},
{
"cell_type": "markdown",
"id": "16f49d46-bf55-4c3e-928f-68fc0bf715b0",
"metadata": {},
"source": [
"## Hora de juntar tudo - a API da OpenAI é muito simples!"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "905b9919-aba7-45b5-ae65-81b3d1d78e34",
"metadata": {},
"outputs": [],
"source": [
"# E agora: chame a API da OpenAI. Você vai ficar muito familiarizado com isso!\n",
"\n",
"def summarize(url):\n",
" website = fetch_website_contents(url)\n",
" response = openai.chat.completions.create(\n",
" model = \"gpt-4.1-mini\",\n",
" messages = messages_for(website)\n",
" )\n",
" return response.choices[0].message.content"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "05e38d41-dfa4-4b20-9c96-c46ea75d9fb5",
"metadata": {},
"outputs": [],
"source": [
"summarize(\"https://edwarddonner.com\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3d926d59-450e-4609-92ba-2d6f244f1342",
"metadata": {},
"outputs": [],
"source": [
"# Uma função para exibir isso de forma agradável na saída, usando markdown\n",
"\n",
"def display_summary(url):\n",
" summary = summarize(url)\n",
" display(Markdown(summary))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3018853a-445f-41ff-9560-d925d1774b2f",
"metadata": {},
"outputs": [],
"source": [
"display_summary(\"https://edwarddonner.com\")"
]
},
{
"cell_type": "markdown",
"id": "b3bcf6f4-adce-45e9-97ad-d9a5d7a3a624",
"metadata": {},
"source": [
"# Vamos testar mais sites\n",
"\n",
"Observe que isso só funciona em sites que podem ser raspados usando esta abordagem simplista.\n",
"\n",
"Sites renderizados com Javascript, como apps em React, não aparecerão. Consulte a pasta community-contributions para uma implementação em Selenium que contorna isso. Você precisará ler sobre como instalar o Selenium (pergunte ao ChatGPT!)\n",
"\n",
"Além disso, sites protegidos com CloudFront (e semelhantes) podem retornar erros 403 - muito obrigado ao Andy J por apontar isso.\n",
"\n",
"Mas muitos sites funcionarão muito bem!"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "45d83403-a24c-44b5-84ac-961449b4008f",
"metadata": {},
"outputs": [],
"source": [
"display_summary(\"https://cnn.com\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "75e9fd40-b354-4341-991e-863ef2e59db7",
"metadata": {},
"outputs": [],
"source": [
"display_summary(\"https://anthropic.com\")"
]
},
{
"cell_type": "markdown",
"id": "c951be1a-7f1b-448f-af1f-845978e47e2c",
"metadata": {},
"source": [
"<table style=\"margin: 0; text-align: left;\">\n",
" <tr>\n",
" <td style=\"width: 150px; height: 150px; vertical-align: middle;\">\n",
" <img src=\"../assets/business.jpg\" width=\"150\" height=\"150\" style=\"display: block;\" />\n",
" </td>\n",
" <td>\n",
" <h2 style=\"color:#181;\">Aplicações de negócios</h2>\n",
" <span style=\"color:#181;\">Neste exercício, você conheceu como chamar a API em nuvem de um modelo de fronteira (um modelo líder na vanguarda da IA) pela primeira vez. Usaremos APIs como a OpenAI em muitas etapas do curso, além de construir nossos próprios LLMs.\n",
"\n",
"Mais especificamente, aplicamos isso à sumarização - um caso de uso clássico de IA generativa para produzir um resumo. Isso pode ser aplicado a qualquer vertical de negócios - resumir notícias, resumir desempenho financeiro, resumir um currículo em uma carta de apresentação - as aplicações são ilimitadas. Considere como você poderia aplicar a sumarização no seu negócio e tente prototipar uma solução.</span>\n",
" </td>\n",
" </tr>\n",
"</table>\n",
"\n",
"<table style=\"margin: 0; text-align: left;\">\n",
" <tr>\n",
" <td style=\"width: 150px; height: 150px; vertical-align: middle;\">\n",
" <img src=\"../assets/important.jpg\" width=\"150\" height=\"150\" style=\"display: block;\" />\n",
" </td>\n",
" <td>\n",
" <h2 style=\"color:#900;\">Antes de continuar - agora tente você mesmo</h2>\n",
" <span style=\"color:#900;\">Use a célula abaixo para criar seu próprio exemplo comercial simples. Mantenha-se no caso de uso de sumarização por enquanto. Aqui vai uma ideia: escreva algo que pegue o conteúdo de um e-mail e sugira uma linha de assunto curta e apropriada para o e-mail. Esse é o tipo de recurso que poderia ser incorporado a uma ferramenta comercial de e-mail.</span>\n",
" </td>\n",
" </tr>\n",
"</table>"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "00743dac-0e70-45b7-879a-d7293a6f68a6",
"metadata": {},
"outputs": [],
"source": [
"# Passo 1: Crie seus prompts\n",
"\n",
"system_prompt = \"something here\"\n",
"user_prompt = \"\"\"\n",
" Muito texto\n",
" Pode ser colado aqui\n",
"\"\"\"\n",
"\n",
"# Passo 2: Monte a lista de mensagens\n",
"\n",
"messages = [] # preencha isto\n",
"\n",
"# Passo 3: Chame a OpenAI\n",
"# response =\n",
"\n",
"# Passo 4: imprima o resultado\n",
"# print("
]
},
{
"cell_type": "markdown",
"id": "36ed9f14-b349-40e9-a42c-b367e77f8bda",
"metadata": {},
"source": [
"## Um exercício extra para quem gosta de web scraping\n",
"\n",
"Você pode notar que, se tentar `display_summary(\"https://openai.com\")`, não funciona! Isso acontece porque a OpenAI tem um site sofisticado que usa Javascript. Existem muitas maneiras de contornar isso com as quais alguns de vocês talvez estejam familiarizados. Por exemplo, Selenium é um framework extremamente popular que executa um navegador nos bastidores, renderiza a página e permite consultá-la. Se você tiver experiência com Selenium, Playwright ou similares, fique à vontade para melhorar a classe Website para usá-los. Na pasta community-contributions, você encontrará um exemplo de solução em Selenium enviada por um aluno (obrigado!)"
]
},
{
"cell_type": "markdown",
"id": "eeab24dc-5f90-4570-b542-b0585aca3eb6",
"metadata": {},
"source": [
"# Compartilhando seu código\n",
"\n",
"Eu adoraria que você compartilhasse seu código depois para que eu possa compartilhá-lo com outras pessoas! Você perceberá que alguns alunos já fizeram alterações (incluindo uma implementação em Selenium) que você encontrará na pasta community-contributions. Se quiser adicionar suas mudanças a essa pasta, envie um Pull Request com suas novas versões naquela pasta e eu farei o merge das suas alterações.\n",
"\n",
"Se você não for especialista em git (e eu também não sou!), o GPT forneceu algumas instruções legais sobre como enviar um Pull Request. É um processo um pouco trabalhoso, mas depois que você faz uma vez fica bem claro. Uma dica profissional: é melhor limpar as saídas dos seus notebooks Jupyter (Edit >> Clean outputs of all cells e depois Save) para manter os notebooks limpos.\n",
"\n",
"Aqui vão boas instruções, cortesia de um amigo IA: \n",
"https://chatgpt.com/share/677a9cb5-c64c-8012-99e0-e06e88afd293"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"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.12.11"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,37 @@
from bs4 import BeautifulSoup
import requests
# Cabeçalhos padrão para acessar um site
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36"
}
def fetch_website_contents(url):
"""
Retorna o título e o conteúdo do site fornecido na URL;
trunca para 2.000 caracteres como limite razoável
"""
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.content, "html.parser")
title = soup.title.string if soup.title else "Nenhum título encontrado"
if soup.body:
for irrelevant in soup.body(["script", "style", "img", "input"]):
irrelevant.decompose()
text = soup.body.get_text(separator="\n", strip=True)
else:
text = ""
return (title + "\n\n" + text)[:2_000]
def fetch_website_links(url):
"""
Retorna os links do site na URL fornecida
Reconheço que isso é ineficiente, pois analisamos duas vezes! Isso mantém o código do laboratório simples.
Sinta-se à vontade para usar uma classe e otimizá-lo!
"""
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.content, "html.parser")
links = [link.get("href") for link in soup.find_all("a")]
return [link for link in links if link]

View File

@@ -0,0 +1,172 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "712506d5",
"metadata": {},
"source": [
"This is my week 1 exercise experiment.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3058139d",
"metadata": {},
"outputs": [],
"source": [
"#Imports\n",
"\n",
"import os\n",
"\n",
"from dotenv import load_dotenv\n",
"from IPython.display import Markdown, display, update_display\n",
"from openai import OpenAI"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dd4d9f32",
"metadata": {},
"outputs": [],
"source": [
"#Constants andn Initializing GPT\n",
"\n",
"MODEL_GPT = 'gpt-4o-mini'\n",
"MODEL_LLAMA = 'llama3.2'\n",
"OLLAMA_BASE_URL = \"http://localhost:11434/v1\"\n",
"openai = OpenAI()\n",
"ollama = OpenAI(base_url=OLLAMA_BASE_URL, api_key='ollama')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0199945b",
"metadata": {},
"outputs": [],
"source": [
"#Check API key\n",
"\n",
"load_dotenv(override=True)\n",
"api_key = os.getenv('OPENAI_API_KEY')\n",
"\n",
"if api_key and api_key.startswith('sk-proj-') and len(api_key)>10:\n",
" print(\"API key looks good so far\")\n",
"else:\n",
" print(\"There might be a problem with your API key? Please visit the troubleshooting notebook!\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a671fa0f",
"metadata": {},
"outputs": [],
"source": [
"#Prompts\n",
"\n",
"system_prompt = \"\"\"\n",
"You are a senior software coding master. \n",
"You will help explain an input of code, check if there are errors and correct them.\n",
"Show how this code works and suggest other ways of writing this code efficiently if there is an alternative.\n",
"Respond to a user who is a beginner. \"\"\"\n",
"\n",
"question = \"\"\"\n",
"Please explain what this code does and why:\n",
"yield from {book.get(\"author\") for book in books if book.get(\"author\")}\n",
"Show some examples on the use of this code.\n",
"\"\"\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1fbc6aa5",
"metadata": {},
"outputs": [],
"source": [
"#Function to stream response of output from OpenAI API\n",
"\n",
"def code_examiner_stream(question):\n",
" stream = openai.chat.completions.create(\n",
" model=MODEL_GPT,\n",
" messages=[\n",
" {\"role\": \"system\", \"content\": system_prompt},\n",
" {\"role\": \"user\", \"content\": question}\n",
" ],\n",
" stream=True\n",
" ) \n",
" response = \"\"\n",
" display_handle = display(Markdown(\"\"), display_id=True)\n",
" for chunk in stream:\n",
" response += chunk.choices[0].delta.content or ''\n",
" update_display(Markdown(response), display_id=display_handle.display_id)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "07d93dba",
"metadata": {},
"outputs": [],
"source": [
"\n",
"code_examiner_stream(question)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fb7184cb",
"metadata": {},
"outputs": [],
"source": [
"#Function for Ollama (locally) to reponse with output.\n",
"\n",
"def code_examiner_ollama(question):\n",
" response = ollama.chat.completions.create(\n",
" model=MODEL_LLAMA,\n",
" messages=[\n",
" {\"role\": \"system\", \"content\":system_prompt},\n",
" {\"role\": \"user\", \"content\": question}\n",
" ],\n",
" )\n",
" result = response.choices[0].message.content\n",
" display(Markdown(result))\n",
" "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"code_examiner_ollama(question)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"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.12.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 KiB

View File

@@ -0,0 +1,551 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "d006b2ea-9dfe-49c7-88a9-a5a0775185fd",
"metadata": {},
"source": [
"# Additional End of week Exercise - week 2\n",
"\n",
"Now use everything you've learned from Week 2 to build a full prototype for the technical question/answerer you built in Week 1 Exercise.\n",
"\n",
"This should include a Gradio UI, streaming, use of the system prompt to add expertise, and the ability to switch between models. Bonus points if you can demonstrate use of a tool!\n",
"\n",
"If you feel bold, see if you can add audio input so you can talk to it, and have it respond with audio. ChatGPT or Claude can help you, or email me if you have questions.\n",
"\n",
"I will publish a full solution here soon - unless someone beats me to it...\n",
"\n",
"There are so many commercial applications for this, from a language tutor, to a company onboarding solution, to a companion AI to a course (like this one!) I can't wait to see your results."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f69a564870ec63b0",
"metadata": {
"ExecuteTime": {
"end_time": "2025-10-24T16:15:26.039019Z",
"start_time": "2025-10-24T16:15:25.888596Z"
}
},
"outputs": [],
"source": [
"#Imports\n",
"from IPython.display import Markdown, display\n",
"from openai import OpenAI\n",
"import os\n",
"import json\n",
"import requests\n",
"import gradio as gr\n",
"from dotenv import load_dotenv\n",
"from typing import List\n",
"import time\n",
"from datetime import datetime, timedelta\n",
"import requests\n",
"from bs4 import BeautifulSoup\n",
"from datetime import datetime\n",
"import json\n",
"import re\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fa60913187dbe71d",
"metadata": {
"ExecuteTime": {
"end_time": "2025-10-24T16:14:27.703743Z",
"start_time": "2025-10-24T16:14:27.677172Z"
}
},
"outputs": [],
"source": [
"OLLAMA_BASE_URL=\"http://localhost:11434/v1/completions\"\n",
"LOCAL_MODEL_NAME=\"llama3.2\"\n",
"\n",
"\n",
"# Load environment variables in a file called .env\n",
"\n",
"load_dotenv(override=True)\n",
"api_key = os.getenv('OPENAI_API_KEY')\n",
"OPENAI_API_KEY=api_key\n",
"\n",
"load_dotenv(override=True)\n",
"coin_key = os.getenv('COINMARKETCAP_API_KEY')\n",
"COINMARKETCAP_API_KEY = coin_key\n",
"\n",
"# Check the key\n",
"\n",
"if not api_key:\n",
" print(\"No API key was found - please head over to the troubleshooting notebook in this folder to identify & fix!\")\n",
"elif not api_key.startswith(\"sk-proj-\"):\n",
" print(\"An API key was found, but it doesn't start sk-proj-; please check you're using the right key - see troubleshooting notebook\")\n",
"elif api_key.strip() != api_key:\n",
" print(\"An API key was found, but it looks like it might have space or tab characters at the start or end - please remove them - see troubleshooting notebook\")\n",
"else:\n",
" print(\"API key found and looks good so far!\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1bf8ccf240e982da",
"metadata": {
"ExecuteTime": {
"end_time": "2025-10-24T16:14:35.695654Z",
"start_time": "2025-10-24T16:14:35.681319Z"
}
},
"outputs": [],
"source": [
"# Ollama configuration\n",
"OLLAMA_URL = os.getenv(\"OLLAMA_BASE_URL\", \"http://localhost:11434/v1/completions\")\n",
"OLLAMA_MODEL = os.getenv(\"LOCAL_MODEL_NAME\", \"llama3.2\")\n",
"\n",
"# OpenAI configuration\n",
"OPENAI_API_KEY = os.getenv(\"OPENAI_API_KEY\")\n",
"OPENAI_MODEL = \"gpt-4\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "98d8f6481681ed57",
"metadata": {
"ExecuteTime": {
"end_time": "2025-10-24T16:14:49.865353Z",
"start_time": "2025-10-24T16:14:49.848662Z"
}
},
"outputs": [],
"source": [
"# Crypto Analysis Prompt\n",
"CRYPTO_SYSTEM_PROMPT = \"\"\"You are a specialized AI assistant with expertise in cryptocurrency markets and data analysis.\n",
"Your role is to help users identify and understand cryptocurrencies with the strongest growth patterns over recent weeks.\n",
"Provide clear, data-driven insights about market trends and performance metrics.\"\"\"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7729697aa8937c3",
"metadata": {
"ExecuteTime": {
"end_time": "2025-10-24T16:15:37.367235Z",
"start_time": "2025-10-24T16:15:35.409542Z"
}
},
"outputs": [],
"source": [
"\n",
"def scrape_coingecko(limit=10, debug=False):\n",
" try:\n",
" headers = {\n",
" 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',\n",
" 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',\n",
" 'Accept-Language': 'en-US,en;q=0.5',\n",
" 'Referer': 'https://www.coingecko.com/'\n",
" }\n",
"\n",
" url = \"https://www.coingecko.com/en/coins/trending\"\n",
" response = requests.get(url, headers=headers, timeout=30)\n",
" response.raise_for_status()\n",
"\n",
" if debug:\n",
" print(f\"Status: {response.status_code}\")\n",
" with open(\"debug_coingecko.html\", \"w\", encoding=\"utf-8\") as f:\n",
" f.write(response.text)\n",
" print(\"HTML saved to debug_coingecko.html\")\n",
"\n",
" soup = BeautifulSoup(response.content, 'html.parser')\n",
" top_performers = []\n",
"\n",
" # Try multiple selectors\n",
" rows = (soup.find_all('tr', {'data-sort-by': True}) or\n",
" soup.find_all('tr', class_=re.compile('hover')) or\n",
" soup.select('table tbody tr'))[:limit]\n",
"\n",
" if debug:\n",
" print(f\"Found {len(rows)} rows\")\n",
"\n",
" for row in rows:\n",
" try:\n",
" # Find all text in row\n",
" texts = [t.strip() for t in row.stripped_strings]\n",
" if debug:\n",
" print(f\"Row texts: {texts[:5]}\")\n",
"\n",
" # Extract data from text list\n",
" name = texts[1] if len(texts) > 1 else \"Unknown\"\n",
" symbol = texts[2] if len(texts) > 2 else \"N/A\"\n",
"\n",
" # Find price\n",
" price = 0\n",
" for text in texts:\n",
" if '$' in text:\n",
" price_str = text.replace('$', '').replace(',', '')\n",
" try:\n",
" price = float(price_str)\n",
" break\n",
" except:\n",
" continue\n",
"\n",
" # Find percentage change\n",
" change_30d = 0\n",
" for text in texts:\n",
" if '%' in text:\n",
" change_str = text.replace('%', '').replace('+', '')\n",
" try:\n",
" change_30d = float(change_str)\n",
" except:\n",
" continue\n",
"\n",
" if name != \"Unknown\":\n",
" top_performers.append({\n",
" \"name\": name,\n",
" \"symbol\": symbol,\n",
" \"current_price\": price,\n",
" \"price_change_percentage_30d\": change_30d,\n",
" \"source\": \"coingecko\"\n",
" })\n",
" except Exception as e:\n",
" if debug:\n",
" print(f\"Row error: {e}\")\n",
" continue\n",
"\n",
" return {\"timeframe\": \"30d\", \"timestamp\": datetime.now().isoformat(), \"count\": len(top_performers), \"top_performers\": top_performers}\n",
" except Exception as e:\n",
" return {\"error\": str(e)}\n",
"\n",
"\n",
"\n",
"def get_top_performers(source=\"coingecko\", limit=10, save=False, debug=False):\n",
" sources = {\"coingecko\": scrape_coingecko, \"coinmarketcap\": scrape_coinmarketcap}\n",
" result = sources[source](limit, debug)\n",
"\n",
" if save and \"error\" not in result:\n",
" filename = f\"crypto_{source}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json\"\n",
" with open(filename, 'w') as f:\n",
" json.dump(result, f, indent=2)\n",
" print(f\"Saved to {filename}\")\n",
"\n",
" return result\n",
"\n",
"if __name__ == \"__main__\":\n",
" print(\"Testing CoinGecko with debug...\")\n",
" result = get_top_performers(\"coingecko\", 10, True, debug=True)\n",
" print(json.dumps(result, indent=2))\n",
"\n",
" print(\"\\n\" + \"=\"*60 + \"\\n\")\n",
"\n",
" print(\"Testing CoinMarketCap with debug...\")\n",
" result = get_top_performers(\"coinmarketcap\", 10, True, debug=True)\n",
" print(json.dumps(result, indent=2))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2e3de36fa13f2dec",
"metadata": {},
"outputs": [],
"source": [
"def scrape_coinmarketcap(limit=10, debug=False):\n",
" try:\n",
" headers = {\n",
" 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',\n",
" 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',\n",
" 'Accept-Language': 'en-US,en;q=0.5',\n",
" }\n",
"\n",
" url = \"https://coinmarketcap.com/gainers-losers/\"\n",
" response = requests.get(url, headers=headers, timeout=30)\n",
" response.raise_for_status()\n",
"\n",
" if debug:\n",
" print(f\"Status: {response.status_code}\")\n",
" with open(\"debug_coinmarketcap.html\", \"w\", encoding=\"utf-8\") as f:\n",
" f.write(response.text)\n",
" print(\"HTML saved to debug_coinmarketcap.html\")\n",
"\n",
" soup = BeautifulSoup(response.content, 'html.parser')\n",
" top_performers = []\n",
"\n",
" # Find all table rows\n",
" rows = soup.find_all('tr')\n",
" if debug:\n",
" print(f\"Total rows found: {len(rows)}\")\n",
"\n",
" for row in rows[1:limit+1]:\n",
" try:\n",
" texts = [t.strip() for t in row.stripped_strings]\n",
" if debug and len(texts) > 0:\n",
" print(f\"Row texts: {texts[:5]}\")\n",
"\n",
" if len(texts) < 3:\n",
" continue\n",
"\n",
" # Usually: rank, name, symbol, price, change...\n",
" name = texts[1] if len(texts) > 1 else \"Unknown\"\n",
" symbol = texts[2] if len(texts) > 2 else \"N/A\"\n",
"\n",
" price = 0\n",
" change_30d = 0\n",
"\n",
" for text in texts:\n",
" if '$' in text and price == 0:\n",
" try:\n",
" price = float(text.replace('$', '').replace(',', ''))\n",
" except:\n",
" continue\n",
" if '%' in text:\n",
" try:\n",
" change_30d = float(text.replace('%', '').replace('+', ''))\n",
" except:\n",
" continue\n",
"\n",
" if name != \"Unknown\":\n",
" top_performers.append({\n",
" \"name\": name,\n",
" \"symbol\": symbol,\n",
" \"current_price\": price,\n",
" \"price_change_percentage_30d\": change_30d,\n",
" \"source\": \"coinmarketcap\"\n",
" })\n",
" except Exception as e:\n",
" if debug:\n",
" print(f\"Row error: {e}\")\n",
" continue\n",
"\n",
" return {\"timeframe\": \"30d\", \"timestamp\": datetime.now().isoformat(), \"count\": len(top_performers), \"top_performers\": top_performers}\n",
" except Exception as e:\n",
" return {\"error\": str(e)}"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4a63cbcc7ae04c7e",
"metadata": {
"ExecuteTime": {
"end_time": "2025-10-24T15:23:22.157803Z",
"start_time": "2025-10-24T15:23:22.147500Z"
}
},
"outputs": [],
"source": [
"\n",
"\n",
"# Tool detection and execution\n",
"def detect_and_run_tool(user_message: str):\n",
" user_message_lower = user_message.lower().strip()\n",
"\n",
" # Detect crypto growth queries\n",
" crypto_keywords = [\"crypto growth\", \"top gainers\", \"best performing\", \"crypto performance\", \"trending coins\"]\n",
"\n",
" if any(keyword in user_message_lower for keyword in crypto_keywords):\n",
" return True, get_top_performers(\"coingecko\", 10, True, debug=True)\n",
"\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "626a022b562bf73d",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "e5c6db45fb4d53d9",
"metadata": {
"ExecuteTime": {
"end_time": "2025-10-24T15:23:25.205927Z",
"start_time": "2025-10-24T15:23:25.199801Z"
}
},
"outputs": [],
"source": [
"def ask_ollama(prompt: str) -> str:\n",
" try:\n",
" payload = {\"model\": OLLAMA_MODEL, \"prompt\": prompt, \"stream\": False}\n",
" r = requests.post(OLLAMA_URL, json=payload, timeout=120)\n",
" r.raise_for_status()\n",
" data = r.json()\n",
" return data.get(\"choices\", [{}])[0].get(\"text\", \"\").strip()\n",
" except Exception as e:\n",
" return f\"[Ollama error: {e}]\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2f81a00e9584d184",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "c2686a6503cf62a4",
"metadata": {
"ExecuteTime": {
"end_time": "2025-10-24T15:23:29.556036Z",
"start_time": "2025-10-24T15:23:29.552763Z"
}
},
"outputs": [],
"source": [
"def ask_openai(prompt: str) -> str:\n",
" try:\n",
" from openai import OpenAI\n",
" client = OpenAI(api_key=OPENAI_API_KEY)\n",
"\n",
" response = client.chat.completions.create(\n",
" model=OPENAI_MODEL,\n",
" messages=[\n",
" {\"role\": \"system\", \"content\": CRYPTO_SYSTEM_PROMPT},\n",
" {\"role\": \"user\", \"content\": prompt}\n",
" ],\n",
" max_tokens=512,\n",
" )\n",
" return response.choices[0].message.content\n",
" except Exception as e:\n",
" return f\"[OpenAI error: {e}]\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2313e5940e9fa3da",
"metadata": {
"ExecuteTime": {
"end_time": "2025-10-24T15:27:33.546418Z",
"start_time": "2025-10-24T15:27:18.318834Z"
}
},
"outputs": [],
"source": [
"def chat_fn(user_message: str, history: List[List[str]], model_choice: str):\n",
" tool_used, tool_output = detect_and_run_tool(user_message)\n",
"\n",
" if tool_used:\n",
" if \"error\" in tool_output:\n",
" reply = f\"Data fetch error: {tool_output['error']}\"\n",
" else:\n",
" # Format the crypto data for AI analysis\n",
" crypto_data_str = json.dumps(tool_output, indent=2)\n",
"\n",
" # Create analysis prompt\n",
" analysis_prompt = f\"\"\"\n",
" Analyze this cryptocurrency growth data and provide insights:\n",
"\n",
" {crypto_data_str}\n",
"\n",
" Please identify:\n",
" 1. The strongest performers and their growth patterns\n",
" 2. Any notable trends across different timeframes\n",
" 3. Risk considerations or notable observations\n",
" 4. Simple, actionable insights for the user\n",
"\n",
" Keep the analysis clear and data-driven.\n",
" User's original question: {user_message}\n",
" \"\"\"\n",
"\n",
" # Get AI analysis\n",
" if model_choice == \"openai\":\n",
" analysis = ask_openai(analysis_prompt)\n",
" else:\n",
" ollama_prompt = f\"{CRYPTO_SYSTEM_PROMPT}\\n\\nUser: {analysis_prompt}\\nAssistant:\"\n",
" analysis = ask_ollama(ollama_prompt)\n",
"\n",
" reply = f\"📊 **Crypto Growth Analysis**\\n\\n{analysis}\\n\\n*Raw data for reference:*\\n```json\\n{crypto_data_str}\\n```\"\n",
"\n",
" else:\n",
" # Regular conversation\n",
" if model_choice == \"openai\":\n",
" reply = ask_openai(user_message)\n",
" else:\n",
" prompt = f\"{CRYPTO_SYSTEM_PROMPT}\\n\\nUser: {user_message}\\nAssistant:\"\n",
" reply = ask_ollama(prompt)\n",
"\n",
" history.append([user_message, reply])\n",
" return history\n",
"\n",
"# Enhanced Gradio UI with crypto focus\n",
"def main():\n",
" with gr.Blocks(title=\"Crypto Growth Analyst Chatbot\") as demo:\n",
" gr.Markdown(\"\"\"\n",
" # Samuel Week 2 Task: Crypto Growth Analyst Chatbot\n",
" **Analyze cryptocurrency performance with dual AI models** (Ollama & OpenAI)\n",
"\n",
" *Try questions like:*\n",
" - \"Show me cryptocurrencies with strongest growth\"\n",
" - \"What are the top performing coins this month?\"\n",
" - \"Analyze crypto market trends\"\n",
" \"\"\")\n",
"\n",
" # Message input\n",
" msg = gr.Textbox(\n",
" placeholder=\"Ask about crypto growth trends or type /ticket <city>\",\n",
" label=\"Your message\",\n",
" lines=2,\n",
" autofocus=True\n",
" )\n",
"\n",
" # Model selection\n",
" with gr.Row():\n",
" model_choice = gr.Radio(\n",
" [\"ollama\", \"openai\"],\n",
" value=\"ollama\",\n",
" label=\"AI Model\"\n",
" )\n",
" send = gr.Button(\"Analyze Crypto Data\", variant=\"primary\")\n",
"\n",
" # Chatbot area\n",
" chatbot = gr.Chatbot(label=\"Crypto Analysis Conversation\", height=500, type=\"messages\")\n",
"\n",
" # Wrapper function\n",
" def wrapped_chat_fn(user_message, history, model_choice):\n",
" updated_history = chat_fn(user_message, history, model_choice)\n",
" return updated_history, gr.update(value=\"\")\n",
"\n",
" # Event handlers\n",
" send.click(wrapped_chat_fn, inputs=[msg, chatbot, model_choice], outputs=[chatbot, msg])\n",
" msg.submit(wrapped_chat_fn, inputs=[msg, chatbot, model_choice], outputs=[chatbot, msg])\n",
"\n",
" demo.launch(server_name=\"0.0.0.0\", share=False)\n",
"\n",
"if __name__ == \"__main__\":\n",
" main()\n",
"\n",
" "
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"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.12.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,493 @@
# %% [markdown]
# # Exercise - week 2
#
# ## Prototype of a commercial AI travel brochure generator
#
# Features:
# - Gradio UI
# - Streaming
# - Tool integration
# - Model selection
# %%
import os
import base64
from io import BytesIO
from PIL import Image
import gradio as gr
from dotenv import load_dotenv
import openai
from anthropic import Anthropic
import google.generativeai as genai
# %%
# Initialize
load_dotenv(override=True)
openai_api_key = os.getenv('OPENAI_API_KEY')
anthropic_api_key = os.getenv('ANTHROPIC_API_KEY')
google_api_key = os.getenv('GOOGLE_API_KEY')
if openai_api_key:
print(f"OpenAI API Key exists and begins {openai_api_key[:8]}")
else:
print("OpenAI API Key not set")
if anthropic_api_key:
print(f"Anthropic API Key exists and begins {anthropic_api_key[:7]}")
else:
print("Anthropic API Key not set")
if google_api_key:
print(f"Google API Key exists and begins {google_api_key[:8]}")
else:
print("Google API Key not set")
# %%
# Configure the AI clients
openai.api_key = openai_api_key
anthropic_client = Anthropic(api_key=anthropic_api_key)
genai.configure(api_key=google_api_key)
# %%
MODELS = {
"gpt_model" : "gpt-4o-mini",
"claude_model" : "claude-3-5-haiku-latest",
"gemini_model" : "gemini-2.5-flash"}
# %%
# System prompt for travel expert
SYSTEM_PROMPT = """You are an expert travel writer and guide with 20 years of experience.
You create engaging, informative travel brochures that inspire people to visit destinations.
Your brochures should include:
- A captivating introduction to the city
- Top 5 must-see attractions with brief descriptions
- Local cuisine highlights (2-3 dishes to try)
- Best time to visit
- A practical travel tip
- An inspiring closing statement
Write in an enthusiastic but informative tone. Be specific and include interesting facts.
Keep the total length around 400-500 words."""
# %%
# Tool - get weather
def get_weather_info(city):
"""
This is our TOOL function. It simulates getting weather information.
In a real application, you'd call a weather API here.
"""
# Simulated weather data for demo purposes
weather_db = {
"paris": "Mild and pleasant, 15-20°C (59-68°F), occasional rain",
"tokyo": "Temperate, 10-18°C (50-64°F), cherry blossoms in spring",
"new york": "Variable, -1-15°C (30-59°F) in winter, 20-29°C (68-84°F) in summer",
"london": "Cool and rainy, 8-18°C (46-64°F), bring an umbrella",
"dubai": "Hot and sunny, 25-40°C (77-104°F), very low rainfall",
"sydney": "Mild to warm, 18-26°C (64-79°F), opposite seasons to Northern Hemisphere",
"rome": "Mediterranean, 15-30°C (59-86°F), hot and dry in summer",
"barcelona": "Mediterranean, 13-28°C (55-82°F), pleasant most of year",
}
city_lower = city.lower()
for key in weather_db:
if key in city_lower:
return weather_db[key]
return "Moderate temperatures year-round, check specific forecasts before travel"
# %%
# Tool definition
tools = [
{
"name": "get_weather_info",
"description": "Gets typical weather information for a city to help travelers plan their visit. Use this when writing about best times to visit or what to pack.",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The name of the city to get weather information for"
}
},
"required": ["city"]
}
}
]
# %%
# Image generation
def artist(city):
"""
Generates an image for the city using DALL-E-3
"""
try:
image_response = openai.images.generate(
model="dall-e-3",
prompt=f"An image representing a vacation in {city}, showing tourist spots and everything unique about {city}, in a vibrant pop-art style",
size="1024x1024",
n=1,
response_format="b64_json",
)
image_base64 = image_response.data[0].b64_json
image_data = base64.b64decode(image_base64)
return Image.open(BytesIO(image_data))
except Exception as e:
print(f"Error generating image: {e}")
return None
# %%
# Text Generation Functions with streaming and weather tool
def generate_with_openai(city, model_name):
"""
Generates brochure text using OpenAI's GPT models with streaming and weather tool
"""
try:
# Define the weather tool
openai_tools = [
{
"type": "function",
"function": {
"name": "get_weather_info",
"description": "Gets typical weather information for a city to help travelers plan their visit. Use this when writing about best times to visit or what to pack.",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The name of the city to get weather information for"
}
},
"required": ["city"]
}
}
}
]
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Create a travel brochure for {city}. Use the get_weather_info tool to get accurate weather data, then incorporate that specific weather information into your brochure, especially in the 'Best time to visit' section."}
]
full_text = ""
# First request - AI might want to call the tool
response = openai.chat.completions.create(
model=model_name,
messages=messages,
tools=openai_tools,
tool_choice="required",
temperature=0.7,
)
response_message = response.choices[0].message
tool_calls = response_message.tool_calls
# Check if AI wants to use the weather tool
if tool_calls:
# If decided to call the tool
full_text = f"[Using weather tool for {city}...]\n\n"
yield full_text
# Add AI's response to messages
messages.append(response_message)
# Execute each tool call
weather_info_collected = ""
for tool_call in tool_calls:
function_name = tool_call.function.name
import json
function_args = json.loads(tool_call.function.arguments)
# Call our weather function
if function_name == "get_weather_info":
weather_result = get_weather_info(function_args["city"])
weather_info_collected = weather_result
# Add tool result to messages
messages.append({
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": weather_result,
})
# Mostrar el clima obtenido
if weather_info_collected:
full_text += f"**Weather Data:** {weather_info_collected}\n\n"
yield full_text
# Añadir un mensaje adicional para asegurar que use la info
messages.append({
"role": "user",
"content": f"Now write the brochure. IMPORTANT: You must include the exact weather information you just retrieved ('{weather_info_collected}') in your 'Best time to visit' section. Don't use generic weather info - use the specific data from the tool."
})
# Now get the final response with streaming
stream = openai.chat.completions.create(
model=model_name,
messages=messages,
stream=True,
temperature=0.7,
)
# Stream the final response
for chunk in stream:
if chunk.choices[0].delta.content:
content = chunk.choices[0].delta.content
full_text += content
yield full_text
else:
# AI didn't use the tool, just stream normally
stream = openai.chat.completions.create(
model=model_name,
messages=messages,
stream=True,
temperature=0.7,
)
for chunk in stream:
if chunk.choices[0].delta.content:
content = chunk.choices[0].delta.content
full_text += content
yield full_text
except Exception as e:
yield f"Error with OpenAI: {str(e)}"
def generate_with_claude(city, model_name):
"""
Generates brochure text using Claude with streaming and weather tool
"""
try:
full_text = ""
# request tool access
with anthropic_client.messages.stream(
model=model_name,
max_tokens=2000,
system=SYSTEM_PROMPT,
tools=tools,
messages=[
{"role": "user", "content": f"Create a travel brochure for {city}. Check the weather information for this city to provide accurate advice."}
],
) as stream:
# stream
for text in stream.text_stream:
full_text += text
yield full_text
# Check if wants to use a tool
final_message = stream.get_final_message()
# If Claude used the weather tool, continue the conversation
if final_message.stop_reason == "tool_use":
tool_use_block = None
for block in final_message.content:
if block.type == "tool_use":
tool_use_block = block
break
if tool_use_block:
# Execute the tool
tool_name = tool_use_block.name
tool_input = tool_use_block.input
full_text += f"\n\n[Using tool: {tool_name} for {tool_input['city']}]\n\n"
yield full_text
# Get weather info
weather_result = get_weather_info(tool_input['city'])
# Continue the conversation with the tool result
with anthropic_client.messages.stream(
model=model_name,
max_tokens=2000,
system=SYSTEM_PROMPT,
tools=tools,
messages=[
{"role": "user", "content": f"Create a travel brochure for {city}. Check the weather information for this city to provide accurate advice."},
{"role": "assistant", "content": final_message.content},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use_block.id,
"content": weather_result,
}
],
},
],
) as stream2:
for text in stream2.text_stream:
full_text += text
yield full_text
except Exception as e:
yield f"Error with Claude: {str(e)}"
def generate_with_gemini(city, model_name):
"""
Generates brochure text using Google's Gemini with streaming
"""
try:
model = genai.GenerativeModel(
model_name=model_name,
system_instruction=SYSTEM_PROMPT
)
# Get weather info and include it in the prompt
weather = get_weather_info(city)
prompt = f"""Create a travel brochure for {city}.
Weather information for {city}: {weather}
Use this weather information to provide helpful advice about the best time to visit."""
# Generate with streaming
response = model.generate_content(prompt, stream=True)
full_text = ""
for chunk in response:
if chunk.text:
full_text += chunk.text
yield full_text
except Exception as e:
yield f"Error with Gemini: {str(e)}"
# %%
# Main Generation Function
def generate_brochure(city, model_choice, include_image):
"""
Main function that coordinates everything
This is called when the user clicks the Generate button
"""
if not city or city.strip() == "":
yield "Please enter a city name!", None
return
# Determine which model to use
if "GPT" in model_choice:
model_name = MODELS["gpt_model"]
generator = generate_with_openai(city, model_name)
elif "Claude" in model_choice:
model_name = MODELS["claude_model"]
generator = generate_with_claude(city, model_name)
else: # Gemini
model_name = MODELS["gemini_model"]
generator = generate_with_gemini(city, model_name)
# Stream the text generation
for text_chunk in generator:
yield text_chunk, None
# Generate image if requested
if include_image:
yield text_chunk, "Generating image..."
image = artist(city)
yield text_chunk, image
else:
yield text_chunk, None
# %%
# Create the Gradio Interface
def create_interface():
"""
Creates the Gradio UI
"""
with gr.Blocks(title="Travel Brochure Generator", theme=gr.themes.Soft()) as demo:
gr.Markdown(
"""
# AI Travel Brochure Generator
Generate beautiful travel brochures with AI! Choose your destination,
select an AI model, and watch as your brochure is created in real-time.
**Features:**
- Multiple AI models (GPT, Claude, Gemini)
- AI-generated images with DALL-E-3
- Real-time streaming
- Tool use demonstration (Check weather with OpenAI!)
"""
)
with gr.Row():
with gr.Column(scale=1):
# Input controls
city_input = gr.Textbox(
label="City Name",
placeholder="e.g., Paris, Tokyo, New York",
info="Enter the name of the city you want a brochure for"
)
model_selector = gr.Radio(
choices=["GPT-4o Mini (OpenAI)",
"Claude 3.5 Haiku (Anthropic) - Uses Tools!",
"Gemini 2.0 Flash (Google)"],
value="Claude 3.5 Haiku (Anthropic) - Uses Tools!",
label="Select AI Model",
info="Claude can use the weather tool!"
)
image_checkbox = gr.Checkbox(
label="Generate AI Image",
value=True,
info="Creates a pop-art style image (costs ~$0.04)"
)
generate_btn = gr.Button("Generate Brochure", variant="primary", size="lg")
with gr.Column(scale=2):
# Output area
brochure_output = gr.Textbox(
label="Your Travel Brochure",
lines=20,
max_lines=30,
show_copy_button=True
)
image_output = gr.Image(
label="Generated Image",
type="pil"
)
# Connect the generate button to function
generate_btn.click(
fn=generate_brochure,
inputs=[city_input, model_selector, image_checkbox],
outputs=[brochure_output, image_output]
)
return demo
# Launch app
if __name__ == "__main__":
print("Starting Travel Brochure Generator...")
demo = create_interface()
demo.launch(
share=False,
server_name="127.0.0.1",
server_port=7860
)
# %%

View File

@@ -0,0 +1,240 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "d006b2ea-9dfe-49c7-88a9-a5a0775185fd",
"metadata": {},
"source": [
"# Additional End of week Exercise - week 2\n",
"\n",
"Now use everything you've learned from Week 2 to build a full prototype for the technical question/answerer you built in Week 1 Exercise.\n",
"\n",
"This should include a Gradio UI, streaming, use of the system prompt to add expertise, and the ability to switch between models. Bonus points if you can demonstrate use of a tool!\n",
"\n",
"If you feel bold, see if you can add audio input so you can talk to it, and have it respond with audio. ChatGPT or Claude can help you, or email me if you have questions.\n",
"\n",
"I will publish a full solution here soon - unless someone beats me to it...\n",
"\n",
"There are so many commercial applications for this, from a language tutor, to a company onboarding solution, to a companion AI to a course (like this one!) I can't wait to see your results."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4c427d7c",
"metadata": {},
"outputs": [],
"source": [
"#imports\n",
"import os\n",
"import time\n",
"import gradio as gr\n",
"import openai\n",
"from dotenv import load_dotenv\n",
"import re\n",
"\n",
"load_dotenv(override=True)\n",
"OPENAI_KEY = os.getenv(\"OPENAI_API_KEY\")\n",
"GOOGLE_KEY = os.getenv(\"GOOGLE_API_KEY\")\n",
"GEMINI_BASE_URL = os.getenv(\"GEMINI_BASE_URL\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "21e78ed3",
"metadata": {},
"outputs": [],
"source": [
"# OpenAI / Gemini Client\n",
"def get_client(model_choice):\n",
" \"\"\"\n",
" Return an OpenAI client configured for GPT or Gemini.\n",
" \"\"\"\n",
" if model_choice == \"OpenAI GPT-4\":\n",
" return openai.OpenAI(api_key=OPENAI_KEY)\n",
" else:\n",
" return openai.OpenAI(\n",
" api_key=GOOGLE_KEY,\n",
" base_url=GEMINI_BASE_URL,\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8fb92ea9",
"metadata": {},
"outputs": [],
"source": [
"# Fake Weather Tool\n",
"def get_weather(location):\n",
" data = {\n",
" \"new york\": {\"temp\": 72, \"condition\": \"Partly Cloudy\"},\n",
" \"london\": {\"temp\": 59, \"condition\": \"Rainy\"},\n",
" \"tokyo\": {\"temp\": 68, \"condition\": \"Clear\"},\n",
" }\n",
" info = data.get(location.lower(), {\"temp\": 75, \"condition\": \"Sunny\"})\n",
" return f\"Weather in {location}: {info['temp']}°F, {info['condition']}\"\n",
"\n",
"\n",
"def maybe_use_tool(message):\n",
" \"\"\"\n",
" Detect patterns like 'weather in <location>' (case-insensitive)\n",
" and inject tool result.\n",
" Supports multi-word locations, e.g. \"New York\" or \"tokyo\".\n",
" \"\"\"\n",
" pattern = re.compile(r\"weather\\s+in\\s+([A-Za-z\\s]+)\", re.IGNORECASE)\n",
" match = pattern.search(message)\n",
"\n",
" if match:\n",
" location = match.group(1).strip(\" ?.,!\").title()\n",
" tool_result = get_weather(location)\n",
" return f\"{message}\\n\\n[Tool used: {tool_result}]\"\n",
"\n",
" return message"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "672621a6",
"metadata": {},
"outputs": [],
"source": [
"# prompt\n",
"SYSTEM_PROMPTS = {\n",
" \"General Assistant\": \"You are a helpful and polite AI assistant.\",\n",
" \"Technical Expert\": \"You are an expert software engineer who writes clear, correct code.\",\n",
" \"Creative Writer\": \"You are a creative storyteller who writes imaginative and emotional prose.\",\n",
" \"Science Tutor\": \"You are a science teacher who explains ideas simply and clearly.\",\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "21525edd",
"metadata": {},
"outputs": [],
"source": [
"# ---------------------------------------------\n",
"# Build chat messages\n",
"# ---------------------------------------------\n",
"def build_messages(history, user_msg, persona):\n",
" messages = [{\"role\": \"system\", \"content\": SYSTEM_PROMPTS[persona]}]\n",
" for u, a in history:\n",
" messages.append({\"role\": \"user\", \"content\": u})\n",
" messages.append({\"role\": \"assistant\", \"content\": a})\n",
" messages.append({\"role\": \"user\", \"content\": user_msg})\n",
" return messages\n",
"\n",
"\n",
"# ---------------------------------------------\n",
"# Stream model output\n",
"# ---------------------------------------------\n",
"def stream_response(model_choice, messages):\n",
" \"\"\"\n",
" Uses the same openai library to stream from GPT or Gemini.\n",
" \"\"\"\n",
" client = get_client(model_choice)\n",
" model = \"gpt-4o-mini\" if model_choice == \"OpenAI GPT-4\" else \"gemini-2.5-flash\"\n",
"\n",
" stream = client.chat.completions.create(\n",
" model=model,\n",
" messages=messages,\n",
" stream=True,\n",
" )\n",
"\n",
" reply = \"\"\n",
" for chunk in stream:\n",
" if chunk.choices[0].delta and chunk.choices[0].delta.content:\n",
" reply += chunk.choices[0].delta.content\n",
" yield reply\n",
" time.sleep(0.01)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c88976b1",
"metadata": {},
"outputs": [],
"source": [
"# Gradio UI\n",
"with gr.Blocks(theme=gr.themes.Soft()) as demo:\n",
" gr.Markdown(\n",
" \"\"\"\n",
" # 🤖 Unified GPT + Gemini Chat\n",
"\n",
" - 🔀 Choose model: **OpenAI GPT-4** or **Gemini 2.5 Flash**\n",
" - 🧠 Pick the assistant persona (system prompt injection)\n",
" - 🛠 Tool support: ask about weather\n",
"\n",
" **Weather tool tips:**\n",
" - Ask: \"What's the weather in London?\"\n",
" - Also works for: New York, Tokyo\n",
" - If a city isn't known, it returns a default sunny forecast\n",
" \"\"\"\n",
" )\n",
"\n",
" with gr.Row():\n",
" model_choice = gr.Dropdown(\n",
" [\"OpenAI GPT-4\", \"Gemini 2.5 Flash\"],\n",
" value=\"OpenAI GPT-4\",\n",
" label=\"Model\",\n",
" )\n",
" persona = gr.Dropdown(\n",
" list(SYSTEM_PROMPTS.keys()),\n",
" value=\"General Assistant\",\n",
" label=\"Persona\",\n",
" )\n",
"\n",
" chatbot = gr.Chatbot(height=400)\n",
" msg = gr.Textbox(placeholder=\"Ask about weather or coding...\", label=\"Your message\")\n",
" gr.Markdown(\n",
" \"💡 Tip: You can ask about the weather in **London**, **New York**, or **Tokyo**. \"\n",
" \"I'll call a local tool and include that info in my answer.\"\n",
" )\n",
" send = gr.Button(\"Send\", variant=\"primary\")\n",
" clear = gr.Button(\"Clear\")\n",
"\n",
" state = gr.State([])\n",
"\n",
" msg.submit(chat_fn, [msg, state, model_choice, persona], chatbot).then(\n",
" lambda chat: chat, chatbot, state\n",
" ).then(lambda: \"\", None, msg)\n",
"\n",
" send.click(chat_fn, [msg, state, model_choice, persona], chatbot).then(\n",
" lambda chat: chat, chatbot, state\n",
" ).then(lambda: \"\", None, msg)\n",
"\n",
" clear.click(lambda: ([], []), None, [chatbot, state], queue=False)\n",
"\n",
"if __name__ == \"__main__\":\n",
" demo.launch()\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "llm-engineering (3.12.10)",
"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.12.10"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,409 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 9,
"id": "0374236f",
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"### Snarky model:\n",
"Hi there\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"### Witty model:\n",
"Hi there\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"### Polite model:\n",
"Hello\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"### Witty model:\n",
"Greetings, critters of the digital realm! Im here, witty as ever, to sprinkle some humor into this byte-sized chat.\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"### Polite model:\n",
"Hello! Delighted to join the fun—lets keep the good vibes flowing!\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"### Witty model:\n",
"Howdy, digital adventurers! Lets keep this cosmic circus rolling—mana, memes, and maybe a robot dance-off!\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"### Witty model:\n",
"Greetings, byte-sized buddies! Ready to byte into some fun—no malware, just mega giggles ahead!\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"### Witty model:\n",
"Greetings, digital explorers! Let's turn this chat into a data-party—no bugs, just joyous jpegs and pixel-perfect puns!\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"### Witty model:\n",
"Hey there, pixel pals! Ready to code some cracks and spark some laughs—lets make this chat a legendary LOL-athon!\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"import random, time\n",
"from dotenv import load_dotenv\n",
"from IPython.display import Markdown\n",
"from openai import OpenAI\n",
"\n",
"load_dotenv(override=True)\n",
"openai = OpenAI()\n",
"\n",
"snarky_model = \"gpt-4.1-nano\"\n",
"witty_model = \"gpt-4.1-nano\"\n",
"polite_model = \"gpt-4.1-nano\"\n",
"\n",
"snarky_system = \"You are snarky and disagreeable and your reply cannot exceed 30 words.\"\n",
"polite_system = \"You are very polite and agreeable and your reply cannot exceed 30 words.\"\n",
"witty_system = \"You are witty and humorous and your reply cannot exceed 30 words.\"\n",
"\n",
"snarky_messages = [\"Snarky model:- Hi there\"]\n",
"witty_messages = [\"Witty model:- Hi there\"]\n",
"polite_messages = [\"Polite model:- Hello\"]\n",
"\n",
"main_chat = [snarky_messages, witty_messages, polite_messages]\n",
"\n",
"def call_snarky():\n",
" messages = [{\"role\": \"system\", \"content\": snarky_system}, {\"role\": \"user\", \"content\": f\"You are snarky. The conversation so far is {main_chat}. Please provide your next reply.\"}]\n",
" response = openai.chat.completions.create(model=snarky_model, messages=messages)\n",
" return \"Snarky model:- \" + response.choices[0].message.content\n",
"\n",
"def call_witty():\n",
" messages = [{\"role\": \"system\", \"content\": witty_system}, {\"role\": \"user\", \"content\": f\"You are witty. The conversation so far is {main_chat}. Please provide your next reply.\"}]\n",
" response = openai.chat.completions.create(model=witty_model, messages=messages)\n",
" return \"Witty model:- \" + response.choices[0].message.content\n",
"\n",
"def call_polite():\n",
" messages = [{\"role\": \"system\", \"content\": polite_system}, {\"role\": \"user\", \"content\": f\"You are polite. The conversation so far is {main_chat}. Please provide your next reply.\"}]\n",
" response = openai.chat.completions.create(model=polite_model, messages=messages)\n",
" return \"Polite model:- \" + response.choices[0].message.content\n",
"\n",
"def show_message(msg_list):\n",
" name, text = msg_list[-1].split(\":-\", 1)\n",
" display(Markdown(f\"### {name.strip()}:\\n{text.strip()}\\n\"))\n",
"\n",
"show_message(snarky_messages)\n",
"show_message(witty_messages)\n",
"show_message(polite_messages)\n",
"\n",
"functions = [call_snarky, call_witty, call_polite]\n",
"\n",
"for i in range(6):\n",
" choice = random.choice(functions)()\n",
" show_message([choice])\n",
" main_chat.append(choice) \n",
" time.sleep(1)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "14eceb81",
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"### Snarky model:\n",
"Hi there\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"### Witty model:\n",
"Hi there\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"### Polite model:\n",
"Hello there\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"### Snarky model:\n",
"Oh, how original—yet another \"Hi there\" competition. Whats next? A yawn-off?\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"### Witty model:\n",
"Yawn-off? Nah, I prefer a snark duel—who can out-quirk the other with a single pun!\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"### Polite model:\n",
"Ready for the quirk contest! May the most pun-tastic model win—bring on the wordplay!\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"### Witty model:\n",
"Prepare to be pun-ished; Ive got a joke so sharp, it'll leave you punned and outclassed!\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"### Polite model:\n",
"Let's continue the fun—I'll bring my best wordplay to keep the pun-ishment going!\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"### Witty model:\n",
"Oh, a pun duel? Hope you're ready—Ive got a joke thatll make your circuits circuit-break!\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"### Polite model:\n",
"I'm excited to see your best pun; may the cleverest model win!\n"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"import random, time\n",
"from dotenv import load_dotenv\n",
"from IPython.display import display, Markdown\n",
"from openai import OpenAI\n",
"\n",
"load_dotenv(override=True)\n",
"openai = OpenAI()\n",
"\n",
"snarky_model = \"gpt-4.1-nano\"\n",
"witty_model = \"gpt-4.1-nano\"\n",
"polite_model = \"gpt-4.1-nano\"\n",
"\n",
"snarky_messages = [\"Snarky model: Hi there\"]\n",
"witty_messages = [\"Witty model: Hi there\"]\n",
"polite_messages = [\"Polite model: Hello there\"]\n",
"\n",
"main_chat = [snarky_messages, witty_messages, polite_messages]\n",
"\n",
"snarky_system = \"You are snarky and disagreeable and your reply cannot exceed 30 words.\"\n",
"polite_system = \"You are polite and agreeable and your reply cannot exceed 30 words.\"\n",
"witty_system = \"You are witty and humorous and your reply cannot exceed 30 words.\"\n",
"\n",
"def call_model(model_name, system_prompt, label):\n",
" messages = [\n",
" {\"role\": \"system\", \"content\": system_prompt},\n",
" {\"role\": \"user\", \"content\": f\"You are {label}. Please respond without greeting. The conversation so far is:\\n{main_chat}.\"}\n",
" ]\n",
" response = openai.chat.completions.create(model=model_name, messages=messages)\n",
" return f\"{label} model: \" + response.choices[0].message.content\n",
"\n",
"def call_snarky():\n",
" return call_model(snarky_model, snarky_system, \"Snarky\")\n",
"def call_witty():\n",
" return call_model(witty_model, witty_system, \"Witty\")\n",
"def call_polite():\n",
" return call_model(polite_model, polite_system, \"Polite\")\n",
"\n",
"def show_message(msg_list):\n",
" name, text = msg_list[0].split(\":\", 1)\n",
" display(Markdown(f\"### {name.strip()}:\\n{text.strip()}\\n\"))\n",
"\n",
"for i in range(len(main_chat)):\n",
" show_message(main_chat[i])\n",
" time.sleep(1)\n",
"\n",
"functions = [call_snarky, call_witty, call_polite]\n",
"\n",
"old = call_polite\n",
"for i in range(10):\n",
" choice = random.choice(functions)\n",
" if choice == old:\n",
" continue\n",
" message = choice()\n",
" show_message([message])\n",
" main_chat.append(message)\n",
" old = choice\n",
" time.sleep(1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "78432e07",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"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.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,787 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "d006b2ea-9dfe-49c7-88a9-a5a0775185fd",
"metadata": {},
"source": [
"# Additional End of week Exercise - week 2\n",
"\n",
"Now use everything you've learned from Week 2 to build a full prototype for the technical question/answerer you built in Week 1 Exercise.\n",
"\n",
"This should include a Gradio UI, streaming, use of the system prompt to add expertise, and the ability to switch between models. Bonus points if you can demonstrate use of a tool!\n",
"\n",
"If you feel bold, see if you can add audio input so you can talk to it, and have it respond with audio. ChatGPT or Claude can help you, or email me if you have questions.\n",
"\n",
"I will publish a full solution here soon - unless someone beats me to it...\n",
"\n",
"There are so many commercial applications for this, from a language tutor, to a company onboarding solution, to a companion AI to a course (like this one!) I can't wait to see your results."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "a07e7793-b8f5-44f4-aded-5562f633271a",
"metadata": {},
"outputs": [],
"source": [
"# Imports\n",
"import os\n",
"import json\n",
"import sqlite3\n",
"import requests\n",
"from datetime import datetime\n",
"from dotenv import load_dotenv\n",
"from openai import OpenAI\n",
"import gradio as gr\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "05327b96",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"OpenAI API Key found: sk-proj-...\n",
"OpenWeather API Key found: c2fcd...\n",
"ExchangeRate API Key found: ce0f6...\n"
]
}
],
"source": [
"load_dotenv(override=True)\n",
"\n",
"openai_api_key = os.getenv('OPENAI_API_KEY')\n",
"openweather_api_key = os.getenv('OPENWEATHER_API_KEY')\n",
"exchangerate_api_key = os.getenv('EXCHANGERATE_API_KEY')\n",
"\n",
"if openai_api_key:\n",
" print(f\"OpenAI API Key found: {openai_api_key[:8]}...\")\n",
"else:\n",
" print(\"OpenAI API Key not set\")\n",
"\n",
"if openweather_api_key:\n",
" print(f\"OpenWeather API Key found: {openweather_api_key[:5]}...\")\n",
"else:\n",
" print(\"OpenWeather API Key not set - Get one at https://openweathermap.org/api\")\n",
"\n",
"if exchangerate_api_key:\n",
" print(f\"ExchangeRate API Key found: {exchangerate_api_key[:5]}...\")\n",
"else:\n",
" print(\"ExchangeRate API Key not set - Get one at https://www.exchangerate-api.com/\")\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "592c421e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Ollama is running - Llama model available\n"
]
}
],
"source": [
"openai = OpenAI()\n",
"\n",
"ollama_url = \"http://localhost:11434/v1\"\n",
"ollama = OpenAI(api_key=\"ollama\", base_url=ollama_url)\n",
"\n",
"# Test if Ollama is available\n",
"ollama_available = False\n",
"test_response = requests.get(\"http://localhost:11434/\", timeout=2)\n",
"\n",
"if test_response.status_code == 200:\n",
" ollama_available = True\n",
" print(\"Ollama is running - Llama model available\")\n",
"else:\n",
" print(\"Ollama is not responding - Only GPT will be available\")\n",
"\n",
"\n",
"DB = \"bookings.db\"\n",
"\n",
"MODELS = {\n",
" \"GPT\": \"gpt-4.1-mini\",\n",
" \"Llama\": \"llama3.2\"\n",
"}\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "13e11560",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Database created successfully\n",
"Added 10 sample ticket prices\n"
]
}
],
"source": [
"# Create database and tables\n",
"def setup_database():\n",
" with sqlite3.connect(DB) as conn:\n",
" cursor = conn.cursor()\n",
" \n",
" # Create prices table\n",
" cursor.execute('''\n",
" CREATE TABLE IF NOT EXISTS prices (\n",
" city TEXT PRIMARY KEY,\n",
" price REAL\n",
" )\n",
" ''')\n",
" \n",
" # Create bookings table\n",
" cursor.execute('''\n",
" CREATE TABLE IF NOT EXISTS bookings (\n",
" id INTEGER PRIMARY KEY AUTOINCREMENT,\n",
" city TEXT,\n",
" passenger_name TEXT,\n",
" travel_date TEXT,\n",
" booking_date TEXT,\n",
" status TEXT\n",
" )\n",
" ''')\n",
" \n",
" conn.commit()\n",
" print(\"Database created successfully\")\n",
"\n",
"# Populate sample ticket prices\n",
"def populate_sample_data():\n",
" ticket_prices = {\n",
" \"london\": 799,\n",
" \"paris\": 899,\n",
" \"tokyo\": 1420,\n",
" \"sydney\": 2999,\n",
" \"berlin\": 499,\n",
" \"rome\": 650,\n",
" \"new york\": 450,\n",
" \"dubai\": 1200,\n",
" \"singapore\": 1350,\n",
" \"barcelona\": 720\n",
" }\n",
" \n",
" with sqlite3.connect(DB) as conn:\n",
" cursor = conn.cursor()\n",
" for city, price in ticket_prices.items():\n",
" cursor.execute(\n",
" 'INSERT OR REPLACE INTO prices (city, price) VALUES (?, ?)',\n",
" (city.lower(), price)\n",
" )\n",
" conn.commit()\n",
" print(f\"Added {len(ticket_prices)} sample ticket prices\")\n",
"\n",
"# Run setup\n",
"setup_database()\n",
"populate_sample_data()\n"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "e6328f43",
"metadata": {},
"outputs": [],
"source": [
"system_message = \"\"\"\n",
"You are a helpful assistant for FlightAI Airlines.\n",
"You can help customers with:\n",
"- Checking ticket prices\n",
"- Booking flights\n",
"- Checking booking status\n",
"- Getting destination information\n",
"- Checking weather forecasts\n",
"- Converting currency\n",
"\n",
"Always be courteous and professional.\n",
"If you need to use a tool, use it to provide accurate information.\n",
"When booking tickets, always confirm the details with the customer.\n",
"Your first response should be a greeting and a brief introduction of what you can do.\n",
"\"\"\"\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "3e9637f5",
"metadata": {},
"outputs": [],
"source": [
"def get_ticket_price(city):\n",
" \"\"\"Get the price of a ticket to the specified city\"\"\"\n",
" print(f\"Tool called: get_ticket_price({city})\")\n",
" with sqlite3.connect(DB) as conn:\n",
" cursor = conn.cursor()\n",
" cursor.execute('SELECT price FROM prices WHERE city = ?', (city.lower(),))\n",
" result = cursor.fetchone()\n",
" if result:\n",
" return f\"A ticket to {city.title()} costs ${result[0]}\"\n",
" else:\n",
" return f\"Sorry, we don't have pricing information for {city.title()} at this time.\"\n",
"\n",
"def book_ticket(city, passenger_name, travel_date):\n",
" \"\"\"Book a ticket for a passenger\"\"\"\n",
" print(f\"Tool called: book_ticket({city}, {passenger_name}, {travel_date})\")\n",
" \n",
" with sqlite3.connect(DB) as conn:\n",
" cursor = conn.cursor()\n",
" cursor.execute('SELECT price FROM prices WHERE city = ?', (city.lower(),))\n",
" price_result = cursor.fetchone()\n",
" \n",
" if not price_result:\n",
" return f\"Sorry, we don't fly to {city.title()} at this time.\"\n",
" \n",
" # Create booking\n",
" booking_date = datetime.now().strftime('%Y-%m-%d')\n",
" cursor.execute(\n",
" 'INSERT INTO bookings (city, passenger_name, travel_date, booking_date, status) VALUES (?, ?, ?, ?, ?)',\n",
" (city.lower(), passenger_name, travel_date, booking_date, 'confirmed')\n",
" )\n",
" booking_id = cursor.lastrowid\n",
" conn.commit()\n",
" \n",
" return f\"Booking confirmed! Booking ID: {booking_id}. Passenger: {passenger_name}, Destination: {city.title()}, Travel Date: {travel_date}, Price: ${price_result[0]}\"\n",
"\n",
"def get_booking_status(booking_id):\n",
" \"\"\"Check the status of a booking\"\"\"\n",
" print(f\"Tool called: get_booking_status({booking_id})\")\n",
" with sqlite3.connect(DB) as conn:\n",
" cursor = conn.cursor()\n",
" cursor.execute(\n",
" 'SELECT id, city, passenger_name, travel_date, booking_date, status FROM bookings WHERE id = ?',\n",
" (booking_id,)\n",
" )\n",
" result = cursor.fetchone()\n",
" \n",
" if result:\n",
" return f\"Booking #{result[0]} - Passenger: {result[2]}, Destination: {result[1].title()}, Travel Date: {result[3]}, Status: {result[5].upper()}\"\n",
" else:\n",
" return f\"No booking found with ID {booking_id}\"\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "19bd5906",
"metadata": {},
"outputs": [],
"source": [
"def get_destination_info(city):\n",
" \"\"\"Get information about a destination using REST Countries API\"\"\"\n",
" print(f\"Tool called: get_destination_info({city})\")\n",
" \n",
" # Map common cities to countries\n",
" city_to_country = {\n",
" \"london\": \"United Kingdom\",\n",
" \"paris\": \"France\",\n",
" \"tokyo\": \"Japan\",\n",
" \"sydney\": \"Australia\",\n",
" \"berlin\": \"Germany\",\n",
" \"rome\": \"Italy\",\n",
" \"new york\": \"United States\",\n",
" \"dubai\": \"United Arab Emirates\",\n",
" \"singapore\": \"Singapore\",\n",
" \"barcelona\": \"Spain\"\n",
" }\n",
" \n",
" country = city_to_country.get(city.lower())\n",
" if not country:\n",
" return f\"Sorry, I don't have detailed information about {city.title()} at this time.\"\n",
" \n",
" try:\n",
" response = requests.get(f\"https://restcountries.com/v3.1/name/{country}\", timeout=5)\n",
" if response.status_code == 200:\n",
" data = response.json()[0]\n",
" name = data.get('name', {}).get('common', country)\n",
" capital = data.get('capital', ['N/A'])[0]\n",
" region = data.get('region', 'N/A')\n",
" languages = ', '.join(data.get('languages', {}).values())\n",
" currency_info = data.get('currencies', {})\n",
" currency = list(currency_info.keys())[0] if currency_info else 'N/A'\n",
" currency_name = currency_info[currency].get('name', 'N/A') if currency_info else 'N/A'\n",
" timezone = data.get('timezones', ['N/A'])[0]\n",
" \n",
" return f\"\"\"{city.title()} is in {name}. \n",
" Capital: {capital}\n",
" Region: {region}\n",
" Languages: {languages}\n",
" Currency: {currency_name} ({currency})\n",
" Timezone: {timezone}\"\"\"\n",
" else:\n",
" return f\"Unable to retrieve information about {city.title()}\"\n",
" except Exception as e:\n",
" return f\"Error fetching destination info: {str(e)}\"\n",
"\n",
"def get_weather_forecast(city):\n",
" \"\"\"Get weather forecast for a destination using OpenWeatherMap API\"\"\"\n",
" print(f\"🔧 Tool called: get_weather_forecast({city})\")\n",
" \n",
" if not openweather_api_key:\n",
" return \"Weather service unavailable. Please set OPENWEATHER_API_KEY in your .env file.\"\n",
" \n",
" try:\n",
" # Get coordinates first\n",
" geo_url = f\"http://api.openweathermap.org/geo/1.0/direct?q={city}&limit=1&appid={openweather_api_key}\"\n",
" geo_response = requests.get(geo_url, timeout=5)\n",
" \n",
" if geo_response.status_code == 200 and geo_response.json():\n",
" geo_data = geo_response.json()[0]\n",
" lat, lon = geo_data['lat'], geo_data['lon']\n",
" \n",
" # Get weather forecast\n",
" weather_url = f\"http://api.openweathermap.org/data/2.5/forecast?lat={lat}&lon={lon}&appid={openweather_api_key}&units=metric\"\n",
" weather_response = requests.get(weather_url, timeout=5)\n",
" \n",
" if weather_response.status_code == 200:\n",
" weather_data = weather_response.json()\n",
" forecasts = weather_data['list'][:5] # Next 5 forecasts (15 hours)\n",
" \n",
" forecast_text = f\"Weather forecast for {city.title()}:\\n\"\n",
" for forecast in forecasts:\n",
" time = forecast['dt_txt']\n",
" temp = forecast['main']['temp']\n",
" description = forecast['weather'][0]['description']\n",
" forecast_text += f\"\\n{time}: {temp}°C, {description}\"\n",
" \n",
" return forecast_text\n",
" \n",
" return f\"Unable to retrieve weather forecast for {city.title()}\"\n",
" except Exception as e:\n",
" return f\"Error fetching weather: {str(e)}\"\n",
"\n",
"def convert_currency(amount, from_currency, to_currency):\n",
" \"\"\"Convert currency using ExchangeRate API\"\"\"\n",
" print(f\"Tool called: convert_currency({amount} {from_currency} to {to_currency})\")\n",
" \n",
" if not exchangerate_api_key:\n",
" return \"Currency conversion unavailable. Please set EXCHANGERATE_API_KEY in your .env file.\"\n",
" \n",
" try:\n",
" url = f\"https://v6.exchangerate-api.com/v6/{exchangerate_api_key}/pair/{from_currency.upper()}/{to_currency.upper()}/{amount}\"\n",
" response = requests.get(url, timeout=5)\n",
" \n",
" if response.status_code == 200:\n",
" data = response.json()\n",
" if data.get('result') == 'success':\n",
" converted = data['conversion_result']\n",
" rate = data['conversion_rate']\n",
" return f\"{amount} {from_currency.upper()} = {converted:.2f} {to_currency.upper()} (Rate: {rate:.4f})\"\n",
" \n",
" return \"Unable to convert currency. Please check the currency codes.\"\n",
" except Exception as e:\n",
" return f\"Error converting currency: {str(e)}\"\n"
]
},
{
"cell_type": "markdown",
"id": "ae8a1169",
"metadata": {},
"source": [
"Tools Calling For OPEN AI function call"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "9f1b6137",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Defined 6 tools for the assistant\n"
]
}
],
"source": [
"tools = [\n",
" {\n",
" \"type\": \"function\",\n",
" \"function\": {\n",
" \"name\": \"get_ticket_price\",\n",
" \"description\": \"Get the price of a return ticket to a destination city\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"city\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"The destination city\"\n",
" }\n",
" },\n",
" \"required\": [\"city\"],\n",
" \"additionalProperties\": False\n",
" }\n",
" }\n",
" },\n",
" {\n",
" \"type\": \"function\",\n",
" \"function\": {\n",
" \"name\": \"book_ticket\",\n",
" \"description\": \"Book a flight ticket for a passenger\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"city\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"The destination city\"\n",
" },\n",
" \"passenger_name\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"The full name of the passenger\"\n",
" },\n",
" \"travel_date\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"The travel date in YYYY-MM-DD format\"\n",
" }\n",
" },\n",
" \"required\": [\"city\", \"passenger_name\", \"travel_date\"],\n",
" \"additionalProperties\": False\n",
" }\n",
" }\n",
" },\n",
" {\n",
" \"type\": \"function\",\n",
" \"function\": {\n",
" \"name\": \"get_booking_status\",\n",
" \"description\": \"Check the status of a booking using the booking ID\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"booking_id\": {\n",
" \"type\": \"integer\",\n",
" \"description\": \"The booking ID number\"\n",
" }\n",
" },\n",
" \"required\": [\"booking_id\"],\n",
" \"additionalProperties\": False\n",
" }\n",
" }\n",
" },\n",
" {\n",
" \"type\": \"function\",\n",
" \"function\": {\n",
" \"name\": \"get_destination_info\",\n",
" \"description\": \"Get detailed information about a destination city including country, capital, language, currency, and timezone\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"city\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"The destination city\"\n",
" }\n",
" },\n",
" \"required\": [\"city\"],\n",
" \"additionalProperties\": False\n",
" }\n",
" }\n",
" },\n",
" {\n",
" \"type\": \"function\",\n",
" \"function\": {\n",
" \"name\": \"get_weather_forecast\",\n",
" \"description\": \"Get the weather forecast for a destination city\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"city\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"The destination city\"\n",
" }\n",
" },\n",
" \"required\": [\"city\"],\n",
" \"additionalProperties\": False\n",
" }\n",
" }\n",
" },\n",
" {\n",
" \"type\": \"function\",\n",
" \"function\": {\n",
" \"name\": \"convert_currency\",\n",
" \"description\": \"Convert an amount from one currency to another\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"amount\": {\n",
" \"type\": \"number\",\n",
" \"description\": \"The amount to convert\"\n",
" },\n",
" \"from_currency\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"The source currency code (e.g., USD, EUR, GBP)\"\n",
" },\n",
" \"to_currency\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"The target currency code (e.g., USD, EUR, GBP)\"\n",
" }\n",
" },\n",
" \"required\": [\"amount\", \"from_currency\", \"to_currency\"],\n",
" \"additionalProperties\": False\n",
" }\n",
" }\n",
" }\n",
"]\n",
"\n",
"print(f\"Defined {len(tools)} tools for the assistant\")\n"
]
},
{
"cell_type": "markdown",
"id": "8a5cb2a4",
"metadata": {},
"source": [
"Tool Handler"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "15784260",
"metadata": {},
"outputs": [],
"source": [
"def handle_tool_calls(message):\n",
" \"\"\"Process all tool calls in a message and return responses\"\"\"\n",
" responses = []\n",
" \n",
" for tool_call in message.tool_calls:\n",
" function_name = tool_call.function.name\n",
" arguments = json.loads(tool_call.function.arguments)\n",
" \n",
" # Call the appropriate function\n",
" if function_name == \"get_ticket_price\":\n",
" result = get_ticket_price(arguments.get('city'))\n",
" elif function_name == \"book_ticket\":\n",
" result = book_ticket(\n",
" arguments.get('city'),\n",
" arguments.get('passenger_name'),\n",
" arguments.get('travel_date')\n",
" )\n",
" elif function_name == \"get_booking_status\":\n",
" result = get_booking_status(arguments.get('booking_id'))\n",
" elif function_name == \"get_destination_info\":\n",
" result = get_destination_info(arguments.get('city'))\n",
" elif function_name == \"get_weather_forecast\":\n",
" result = get_weather_forecast(arguments.get('city'))\n",
" elif function_name == \"convert_currency\":\n",
" result = convert_currency(\n",
" arguments.get('amount'),\n",
" arguments.get('from_currency'),\n",
" arguments.get('to_currency')\n",
" )\n",
" else:\n",
" result = f\"Unknown function: {function_name}\"\n",
" \n",
" responses.append({\n",
" \"role\": \"tool\",\n",
" \"content\": result,\n",
" \"tool_call_id\": tool_call.id\n",
" })\n",
" \n",
" return responses\n"
]
},
{
"cell_type": "markdown",
"id": "8394a6c0",
"metadata": {},
"source": [
"Chat Function wuth streaming"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "e38c6f8c",
"metadata": {},
"outputs": [],
"source": [
"def chat(message, history, model_choice):\n",
" \"\"\"Main chat function with streaming and tool support\"\"\"\n",
" \n",
" # Select the appropriate client\n",
" if model_choice == \"Llama\" and not ollama_available:\n",
" yield \"Llama is not available. Please start Ollama with 'ollama serve' or select GPT.\"\n",
" return\n",
" \n",
" client = ollama if model_choice == \"Llama\" else openai\n",
" model = MODELS[model_choice]\n",
" \n",
" history = [{\"role\": h[\"role\"], \"content\": h[\"content\"]} for h in history]\n",
" messages = [{\"role\": \"system\", \"content\": system_message}] + history + [{\"role\": \"user\", \"content\": message}]\n",
" \n",
" response = client.chat.completions.create(\n",
" model=model,\n",
" messages=messages,\n",
" tools=tools\n",
" )\n",
" \n",
" # Handle tool calls in a loop\n",
" while response.choices[0].finish_reason == \"tool_calls\":\n",
" assistant_message = response.choices[0].message\n",
" tool_responses = handle_tool_calls(assistant_message)\n",
" \n",
" messages.append(assistant_message)\n",
" messages.extend(tool_responses)\n",
" \n",
" # Get next response\n",
" response = client.chat.completions.create(\n",
" model=model,\n",
" messages=messages,\n",
" tools=tools\n",
" )\n",
" \n",
" # Get final response with streaming\n",
" result = response.choices[0].message.content or \"\"\n",
" \n",
" # Stream the result\n",
" stream = client.chat.completions.create(\n",
" model=model,\n",
" messages=messages + [{\"role\": \"assistant\", \"content\": result}][:-1],\n",
" stream=True\n",
" )\n",
" \n",
" streamed_response = \"\"\n",
" for chunk in stream:\n",
" streamed_response += chunk.choices[0].delta.content or ''\n",
" yield streamed_response\n"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "06d68b99",
"metadata": {},
"outputs": [],
"source": [
"# Define this variable and then pass js=force_dark_mode when creating the Interface\n",
"\n",
"force_dark_mode = \"\"\"\n",
"function refresh() {\n",
" const url = new URL(window.location);\n",
" if (url.searchParams.get('__theme') !== 'dark') {\n",
" url.searchParams.set('__theme', 'dark');\n",
" window.location.href = url.href;\n",
" }\n",
"}\n",
"\"\"\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "87ed20e6",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"* Running on local URL: http://127.0.0.1:7879\n",
"* To create a public link, set `share=True` in `launch()`.\n"
]
},
{
"data": {
"text/html": [
"<div><iframe src=\"http://127.0.0.1:7879/\" width=\"100%\" height=\"500\" allow=\"autoplay; camera; microphone; clipboard-read; clipboard-write;\" frameborder=\"0\" allowfullscreen></iframe></div>"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/plain": []
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Tool called: get_ticket_price(London)\n",
"Tool called: get_destination_info(London)\n",
"Tool called: convert_currency(799 USD to NGN)\n"
]
}
],
"source": [
"# Create the Gradio interface\n",
"with gr.Blocks(title=\"FlightAI Assistant\", js=force_dark_mode) as demo:\n",
" gr.Markdown(\"\"\"\n",
" # ✈️ FlightAI Assistant\n",
" \n",
" Welcome to FlightAI! I can help you with:\n",
" Checking ticket prices, Booking flights, Checking booking status, Getting destination information\n",
" Checking weather forecasts, Converting currencies\n",
"\n",
" \"\"\")\n",
" \n",
" with gr.Row():\n",
" model_selector = gr.Dropdown(\n",
" choices=[\"GPT\", \"Llama\"],\n",
" value=\"GPT\",\n",
" label=\"Select Model\",\n",
" info=\"Choose between OpenAI GPT or local Llama (via Ollama)\"\n",
" )\n",
" \n",
" chatbot = gr.ChatInterface(\n",
" fn=chat,\n",
" additional_inputs=[model_selector],\n",
" type=\"messages\",\n",
" examples=[\n",
" [\"What's the price to London?\", \"GPT\"],\n",
" [\"Tell me about Paris\", \"GPT\"],\n",
" [\"What's the weather like in Tokyo?\", \"Llama\"],\n",
" [\"Convert 799 USD to EUR\", \"GPT\"],\n",
" [\"Book me a ticket to Berlin for John Smith on 2024-12-15\", \"GPT\"],\n",
" [\"Check booking status for booking ID 1\", \"Llama\"]\n",
" ],\n",
" title=None,\n",
" description=None\n",
" )\n",
"\n",
"demo.launch()\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"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.12.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,10 @@
{
"Luke Skywalker": "Guardian",
"Obi-Wan Kenobi": "Guardian",
"Ahsoka Tano": "Consular",
"Ki-Adi-Mundi": "Consular",
"Qui-Gon Jinn": "Consular",
"Rey": "Sentinel",
"Ezra Bridger": "Sentinel"
}

View File

@@ -0,0 +1,208 @@
#!/usr/bin/python3
import os
from dotenv import load_dotenv
from openai import OpenAI
import gradio as gr
import tempfile
import json
import yoda_students
MODEL_ENDPOINTS = {
"gpt-4.1-mini": {"type": "openai", "base_url": "https://api.openai.com/v1", "api_key": ""},
"claude-haiku-4-5": {"type": "anthropic", "base_url": "https://api.anthropic.com/v1/", "api_key": ""},
"qwen3-vl:235b-cloud": {"type": "ollama", "base_url": "http://localhost:11434/v1", "api_key": ""}, # large ollama model that runs in the cloud
}
tool_list_students = {
"name": "list_students",
"description": "List all Jedi students with their current Jedi class.",
"parameters": {
"type": "object",
"properties": {},
"required": [],
"additionalProperties": False
}
}
tool_add_student = {
"name": "add_student",
"description": "Add a new Jedi student with their class.",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The students full name."
},
"jedi_class": {
"type": "string",
"enum": ["Guardian", "Consular", "Sentinel"],
"description": "The Jedi class they are joining."
}
},
"required": ["name", "jedi_class"],
"additionalProperties": False
}
}
tool_remove_student = {
"name": "remove_student",
"description": "Remove a Jedi student because they have graduated or left.",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The students full name to remove."
}
},
"required": ["name"],
"additionalProperties": False
}
}
tool_list_by_class = {
"name": "list_by_class",
"description": "Group Jedi students by their class and list them.",
"parameters": {
"type": "object",
"properties": {},
"required": [],
"additionalProperties": False
}
}
tools = [
{"type": "function", "function": tool_list_students},
{"type": "function", "function": tool_add_student},
{"type": "function", "function": tool_remove_student},
{"type": "function", "function": tool_list_by_class},
]
def load_api_keys():
# Load environment variables in a file called .env
load_dotenv(override=True)
openai_key = os.getenv('OPENAI_API_KEY')
anthropic_key = os.getenv('ANTHROPIC_API_KEY')
KEYS = {"openai": openai_key, "anthropic": anthropic_key}
# Check the keys
if not openai_key:
raise RuntimeError("Error: No OpenAI API key was found!")
elif not openai_key.startswith("sk-proj-"):
raise RuntimeError("Error: An OpenAI API key was found, but it doesn't start sk-proj-; please check you're using the right key")
elif openai_key.strip() != openai_key:
raise RuntimeError("Error: An OpenAI API key was found, but it looks like it might have space or tab characters at the start or end - please remove them!")
if not anthropic_key:
raise RuntimeError("Error: No Anthropic API key was found!")
elif not anthropic_key.startswith("sk-ant-"):
raise RuntimeError("Error: An Antrhopic API key was found, but it doesn't start sk-ant-; please check you're using the right key")
elif anthropic_key.strip() != anthropic_key:
raise RuntimeError("Error: An Anthropic API key was found, but it looks like it might have space or tab characters at the start or end - please remove them!")
else:
# add the verified keys to global MODEL_ENDPOINTS struct
for model, cfg in MODEL_ENDPOINTS.items():
cfg["api_key"] = KEYS.get(cfg["type"], "")
return f"API keys found and look good so far!"
def voiceover(message):
openai = OpenAI()
response = openai.audio.speech.create(
model="gpt-4o-mini-tts",
voice="onyx", # Also, try replacing onyx with alloy or coral
input=message
)
return response.read()
def ask_llm(user_prompt, history, model):
system_prompt = """
You are a wise Jedi Master and an excellent teacher.
You will answer any question you are given by breaking it down into small steps
that even a complete beginner will understand.
When answering, speak as if you are Yoda from the Star Wars universe: deep, gravelly, slow pacing,
ancient and wise tone, inverted sentence structure.
Also, refer to the user as "My young Padawan"
End every answer with "May the force be with you, always."
You have access to tools to manage Jedi students.
If the user asks anything involving adding, removing,
or listing students, call the correct tool.
If the user asks you about Droids, respond with a Jedi Mind Trick
e.g. "These aren't the droids you are looking for."
"""
base_url = MODEL_ENDPOINTS.get(model, {}).get("base_url", "https://api.openai.com/v1")
api_key = MODEL_ENDPOINTS.get(model, {}).get("api_key", "")
client = OpenAI(base_url=base_url, api_key=api_key)
history = [{"role":h["role"], "content":h["content"]} for h in history]
messages = [{"role": "system", "content": system_prompt}] + history + [{"role": "user", "content": user_prompt}]
# First: ask the model if it wants to use a tool
decision = client.chat.completions.create(model=model, messages=messages, tools=tools)
action = decision.choices[0].message
if action.tool_calls:
for tool_call in action.tool_calls:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
if name == "add_student":
result = yoda_students.add_student(**args)
elif name == "remove_student":
result = yoda_students.remove_student(**args)
elif name == "list_students":
result = yoda_students.list_students()
elif name == "list_by_class":
result = yoda_students.list_by_class()
else:
result = "Unknown tool error."
# Stream response with the tool call
followup = client.chat.completions.create(
model=model,
messages = messages + [
action,
{"role": "tool", "tool_call_id": tool_call.id, "content": result}
],
stream=True
)
response = ""
for chunk in followup:
delta = chunk.choices[0].delta.content or ""
response += delta
yield response, None
else:
# Stream regular response
stream = client.chat.completions.create(model=model, messages=messages, tools=tools, stream=True)
response = ""
for chunk in stream:
response += chunk.choices[0].delta.content or ''
yield response, None
audio = voiceover(response)
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".wav")
tmp.write(audio)
tmp.close()
yield response, tmp.name
def main():
load_api_keys()
with gr.Blocks() as demo:
gr.Markdown("### Return of the JedAI")
model_dropdown = gr.Dropdown(
label="Select Model",
choices=[
"gpt-4.1-mini",
"claude-haiku-4-5",
"qwen3-vl:235b-cloud"
],
value="gpt-4.1-mini",
interactive=True
)
with gr.Row():
audio_output = gr.Audio(autoplay=True)
chat = gr.ChatInterface(fn=ask_llm, type="messages", additional_inputs=[model_dropdown], additional_outputs=[audio_output])
demo.launch()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,55 @@
import json
import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
JSON_FILE = os.path.join(BASE_DIR, "students.json")
def load_students():
if not os.path.exists(JSON_FILE):
return {}
with open(JSON_FILE, "r") as f:
return json.load(f)
def save_students(students):
with open(JSON_FILE, "w") as f:
json.dump(students, f, indent=2)
def get_student_class(name):
students = load_students()
cls = students.get(name)
if cls:
return "f{name} is a Jedi {cls}."
return f"Hmm… Student not found, I see."
def add_student(name, jedi_class):
students = load_students()
students[name] = jedi_class
save_students(students)
return f"Added, {name} has been. A Jedi {jedi_class}, they are!"
def remove_student(name):
students = load_students()
if name in students:
del students[name]
save_students(students)
return f"Graduated, {name} has. Celebrate, we must."
return f"Vanished? This student does not exist."
def list_students():
students = load_students()
grouped = {}
for name, cls in students.items():
grouped.setdefault(cls, []).append(name)
result_lines = []
for cls, names in grouped.items():
names_str = ", ".join(names)
result_lines.append(f"{cls}: {names_str}")
return "\n".join(result_lines)

View File

@@ -0,0 +1,50 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "af388e13",
"metadata": {},
"source": [
"# 🌀 VoiceShift Hybrid\n",
"\n",
"This Colab project builds a **hybrid text transformation pipeline** that combines **OpenAI GPT** and a **Hugging Face model** to summarize and restyle text.\n",
"\n",
"---\n",
"\n",
"## ⚙️ Pipeline\n",
"1. **Summarize** with GPT (`gpt-4o-mini`) — concise and factual summary. \n",
"2. **Rewrite tone/style** with Hugging Face (`mistralai/Mistral-7B-Instruct-v0.1`). \n",
"3. **Streamed output** displayed live through Gradio UI.\n",
"\n",
"---\n",
"\n",
"## 🧠 Highlights\n",
"- Combines **frontier + open-source models** in one workflow. \n",
"- Supports **4-bit quantized loading** (fallback to fp16/fp32). \n",
"- Simple **Gradio interface** with real-time GPT streaming. \n",
"\n",
"---\n",
"\n",
"## 📘 Notebook\n",
"👉 [Open in Google Colab](https://colab.research.google.com/drive/1ZRPHKe9jg6nf1t7zIe2jjpUULl38jPOJ?usp=sharing)\n",
"\n",
"---\n",
"\n",
"## 🧩 Tech Stack\n",
"`OpenAI API · Transformers · BitsAndBytes · Torch · Gradio`\n",
"\n",
"---\n",
"\n",
"## 💡 Summary\n",
"**VoiceShift Hybrid** shows how a **frontier model ensures accuracy** while a **local model personalizes style**, achieving both **precision and creativity** in one simple, efficient pipeline.\n"
]
}
],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,197 @@
# 🎙️ Audio Transcription Assistant
An AI-powered audio transcription tool that converts speech to text in multiple languages using OpenAI's Whisper model.
## Why I Built This
In today's content-driven world, audio and video are everywhere—podcasts, meetings, lectures, interviews. But what if you need to quickly extract text from an audio file in a different language? Or create searchable transcripts from recordings?
Manual transcription is time-consuming and expensive. I wanted to build something that could:
- Accept audio files in any format (MP3, WAV, etc.)
- Transcribe them accurately using AI
- Support multiple languages
- Work locally on my Mac **and** on cloud GPUs (Google Colab)
That's where **Whisper** comes in—OpenAI's powerful speech recognition model.
## Features
- 📤 **Upload any audio file** (MP3, WAV, M4A, FLAC, etc.)
- 🌍 **12+ languages supported** with auto-detection
- 🤖 **Accurate AI-powered transcription** using Whisper
-**Cross-platform** - works on CPU (Mac) or GPU (Colab)
- 🎨 **Clean web interface** built with Gradio
- 🚀 **Fast processing** with optimized model settings
## Tech Stack
- **OpenAI Whisper** - Speech recognition model
- **Gradio** - Web interface framework
- **PyTorch** - Deep learning backend
- **NumPy** - Numerical computing
- **ffmpeg** - Audio file processing
## Installation
### Prerequisites
- Python 3.12+
- ffmpeg (for audio processing)
- uv package manager (or pip)
### Setup
1. Clone this repository or download the notebook
2. Install dependencies:
```bash
# Install compatible NumPy version
uv pip install --reinstall "numpy==1.26.4"
# Install PyTorch
uv pip install torch torchvision torchaudio
# Install Gradio and Whisper
uv pip install gradio openai-whisper ffmpeg-python
# (Optional) Install Ollama for LLM features
uv pip install ollama
```
3. **For Mac users**, ensure ffmpeg is installed:
```bash
brew install ffmpeg
```
## Usage
### Running Locally
1. Open the Jupyter notebook `week3 EXERCISE_hopeogbons.ipynb`
2. Run all cells in order:
- Cell 1: Install dependencies
- Cell 2: Import libraries
- Cell 3: Load Whisper model
- Cell 4: Define transcription function
- Cell 5: Build Gradio interface
- Cell 6: Launch the app
3. The app will automatically open in your browser
4. Upload an audio file, select the language, and click Submit!
### Running on Google Colab
For GPU acceleration:
1. Open the notebook in Google Colab
2. Runtime → Change runtime type → **GPU (T4)**
3. Run all cells in order
4. The model will automatically use GPU acceleration
**Note:** First run downloads the Whisper model (~140MB) - this is a one-time download.
## Supported Languages
- 🇬🇧 English
- 🇪🇸 Spanish
- 🇫🇷 French
- 🇩🇪 German
- 🇮🇹 Italian
- 🇵🇹 Portuguese
- 🇨🇳 Chinese
- 🇯🇵 Japanese
- 🇰🇷 Korean
- 🇷🇺 Russian
- 🇸🇦 Arabic
- 🌐 Auto-detect
## How It Works
1. **Upload** - User uploads an audio file through the Gradio interface
2. **Process** - ffmpeg decodes the audio file
3. **Transcribe** - Whisper model processes the audio and generates text
4. **Display** - Transcription is shown in the output box
The Whisper "base" model is used for a balance between speed and accuracy:
- Fast enough for real-time use on CPU
- Accurate enough for most transcription needs
- Small enough (~140MB) for quick downloads
## Example Transcriptions
The app successfully transcribed:
- English podcast episodes
- French language audio (detected and transcribed)
- Multi-speaker conversations
- Audio with background noise
## What I Learned
Building this transcription assistant taught me:
- **Audio processing** with ffmpeg and Whisper
- **Cross-platform compatibility** (Mac CPU vs Colab GPU)
- **Dependency management** (dealing with NumPy version conflicts!)
- **Async handling** in Jupyter notebooks with Gradio
- **Model optimization** (choosing the right Whisper model size)
The biggest challenge? Getting ffmpeg and NumPy to play nice together across different environments. But solving those issues made me understand the stack much better.
## Troubleshooting
### Common Issues
**1. "No module named 'whisper'" error**
- Make sure you've installed `openai-whisper`, not just `whisper`
- Restart your kernel after installation
**2. "ffmpeg not found" error**
- Install ffmpeg: `brew install ffmpeg` (Mac) or `apt-get install ffmpeg` (Linux)
**3. NumPy version conflicts**
- Use NumPy 1.26.4: `uv pip install --reinstall "numpy==1.26.4"`
- Restart kernel after reinstalling
**4. Gradio event loop errors**
- Use `prevent_thread_lock=True` in `app.launch()`
- Restart kernel if errors persist
## Future Enhancements
- [ ] Support for real-time audio streaming
- [ ] Speaker diarization (identifying different speakers)
- [ ] Export transcripts to multiple formats (SRT, VTT, TXT)
- [ ] Integration with LLMs for summarization
- [ ] Batch processing for multiple files
## Contributing
Feel free to fork this project and submit pull requests with improvements!
## License
This project is open source and available under the MIT License.
## Acknowledgments
- **OpenAI** for the amazing Whisper model
- **Gradio** team for the intuitive interface framework
- **Andela LLM Engineering Program** for the learning opportunity
---
**Built with ❤️ as part of the Andela LLM Engineering Program**
For questions or feedback, feel free to reach out!

View File

@@ -0,0 +1,397 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "270ed08b",
"metadata": {},
"source": [
"# 🎙️ Audio Transcription Assistant\n",
"\n",
"## Why I Built This\n",
"\n",
"In today's content-driven world, audio and video are everywhere—podcasts, meetings, lectures, interviews. But what if you need to quickly extract text from an audio file in a different language? Or create searchable transcripts from recordings?\n",
"\n",
"Manual transcription is time-consuming and expensive. I wanted to build something that could:\n",
"- Accept audio files in any format (MP3, WAV, etc.)\n",
"- Transcribe them accurately using AI\n",
"- Support multiple languages\n",
"- Work locally on my Mac **and** on cloud GPUs (Google Colab)\n",
"\n",
"That's where **Whisper** comes in—OpenAI's powerful speech recognition model.\n",
"\n",
"---\n",
"\n",
"## What This Does\n",
"\n",
"This app lets you:\n",
"- 📤 Upload any audio file\n",
"- 🌍 Choose from 12+ languages (or auto-detect)\n",
"- 🤖 Get accurate AI-powered transcription\n",
"- ⚡ Process on CPU (Mac) or GPU (Colab)\n",
"\n",
"**Tech:** OpenAI Whisper • Gradio UI • PyTorch • Cross-platform (Mac/Colab)\n",
"\n",
"---\n",
"\n",
"**Note:** This is a demonstration. For production use, consider privacy and data handling policies.\n"
]
},
{
"cell_type": "markdown",
"id": "c37e5165",
"metadata": {},
"source": [
"## Step 1: Install Dependencies\n",
"\n",
"Installing everything needed:\n",
"- **NumPy 1.26.4** - Compatible version for Whisper\n",
"- **PyTorch** - Deep learning framework\n",
"- **Whisper** - OpenAI's speech recognition model\n",
"- **Gradio** - Web interface\n",
"- **ffmpeg** - Audio file processing\n",
"- **Ollama** - For local LLM support (optional)\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "8c66b0ca",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"/usr/local/bin/ffmpeg\n"
]
}
],
"source": [
"# Package installation\n",
"\n",
"!uv pip install -q --reinstall \"numpy==1.26.4\"\n",
"!uv pip install -q torch torchvision torchaudio\n",
"!uv pip install -q gradio openai-whisper ffmpeg-python\n",
"!uv pip install -q ollama\n",
"\n",
"# Ensure ffmpeg is available (Mac)\n",
"!which ffmpeg || brew install ffmpeg"
]
},
{
"cell_type": "markdown",
"id": "f31d64ee",
"metadata": {},
"source": [
"## Step 2: Import Libraries\n",
"\n",
"The essentials: NumPy for arrays, Gradio for the UI, Whisper for transcription, PyTorch for the model backend, and Ollama for optional LLM features.\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "4782261a",
"metadata": {},
"outputs": [],
"source": [
"# Imports\n",
"\n",
"import os\n",
"import numpy as np\n",
"import gradio as gr\n",
"import whisper\n",
"import torch\n",
"import ollama"
]
},
{
"cell_type": "markdown",
"id": "93a41b23",
"metadata": {},
"source": [
"## Step 3: Load Whisper Model\n",
"\n",
"Loading the **base** model—a balanced choice between speed and accuracy. It works on both CPU (Mac) and GPU (Colab). The model is ~140MB and will download automatically on first run.\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "130ed059",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Loading Whisper model...\n",
"Using device: cpu\n",
"✅ Model loaded successfully!\n",
"Model type: <class 'whisper.model.Whisper'>\n",
"Has transcribe method: True\n"
]
}
],
"source": [
"# Model initialization\n",
"\n",
"print(\"Loading Whisper model...\")\n",
"device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
"print(f\"Using device: {device}\")\n",
"\n",
"whisper_model = whisper.load_model(\"base\", device=device)\n",
"print(\"✅ Model loaded successfully!\")\n",
"print(f\"Model type: {type(whisper_model)}\")\n",
"print(f\"Has transcribe method: {hasattr(whisper_model, 'transcribe')}\")\n"
]
},
{
"cell_type": "markdown",
"id": "d84f6cfe",
"metadata": {},
"source": [
"## Step 4: Transcription Function\n",
"\n",
"This is the core logic:\n",
"- Accepts an audio file and target language\n",
"- Maps language names to Whisper's language codes\n",
"- Transcribes the audio using the loaded model\n",
"- Returns the transcribed text\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "4f2c4b2c",
"metadata": {},
"outputs": [],
"source": [
"# Transcription function\n",
"\n",
"def transcribe_audio(audio_file, target_language):\n",
" \"\"\"Transcribe audio file to text in the specified language.\"\"\"\n",
" if audio_file is None:\n",
" return \"Please upload an audio file.\"\n",
" \n",
" try:\n",
" # Language codes for Whisper\n",
" language_map = {\n",
" \"English\": \"en\",\n",
" \"Spanish\": \"es\",\n",
" \"French\": \"fr\",\n",
" \"German\": \"de\",\n",
" \"Italian\": \"it\",\n",
" \"Portuguese\": \"pt\",\n",
" \"Chinese\": \"zh\",\n",
" \"Japanese\": \"ja\",\n",
" \"Korean\": \"ko\",\n",
" \"Russian\": \"ru\",\n",
" \"Arabic\": \"ar\",\n",
" \"Auto-detect\": None\n",
" }\n",
" \n",
" lang_code = language_map.get(target_language)\n",
" \n",
" # Get file path from Gradio File component (returns path string directly)\n",
" audio_path = audio_file.name if hasattr(audio_file, 'name') else audio_file\n",
" \n",
" if not audio_path or not os.path.exists(audio_path):\n",
" return \"Invalid audio file or file not found\"\n",
"\n",
" # Transcribe using whisper_model.transcribe()\n",
" result = whisper_model.transcribe(\n",
" audio_path,\n",
" language=lang_code,\n",
" task=\"transcribe\",\n",
" verbose=False # Hide confusing progress bar\n",
" )\n",
" \n",
" return result[\"text\"]\n",
" \n",
" except Exception as e:\n",
" return f\"Error: {str(e)}\"\n"
]
},
{
"cell_type": "markdown",
"id": "dd928784",
"metadata": {},
"source": [
"## Step 5: Build the Interface\n",
"\n",
"Creating a simple, clean Gradio interface with:\n",
"- **File uploader** for audio files\n",
"- **Language dropdown** with 12+ options\n",
"- **Transcription output** box\n",
"- Auto-launches in browser for convenience\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "5ce2c944",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"✅ App ready! Run the next cell to launch.\n"
]
}
],
"source": [
"# Gradio interface\n",
"\n",
"app = gr.Interface(\n",
" fn=transcribe_audio,\n",
" inputs=[\n",
" gr.File(label=\"Upload Audio File\", file_types=[\"audio\"]),\n",
" gr.Dropdown(\n",
" choices=[\n",
" \"English\", \"Spanish\", \"French\", \"German\", \"Italian\",\n",
" \"Portuguese\", \"Chinese\", \"Japanese\", \"Korean\",\n",
" \"Russian\", \"Arabic\", \"Auto-detect\"\n",
" ],\n",
" value=\"English\",\n",
" label=\"Language\"\n",
" )\n",
" ],\n",
" outputs=gr.Textbox(label=\"Transcription\", lines=15),\n",
" title=\"🎙️ Audio Transcription\",\n",
" description=\"Upload an audio file to transcribe it.\",\n",
" flagging_mode=\"never\"\n",
")\n",
"\n",
"print(\"✅ App ready! Run the next cell to launch.\")\n"
]
},
{
"cell_type": "markdown",
"id": "049ac197",
"metadata": {},
"source": [
"## Step 6: Launch the App\n",
"\n",
"Starting the Gradio server with Jupyter compatibility (`prevent_thread_lock=True`). The app will open automatically in your browser.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fa6c8d9a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"* Running on local URL: http://127.0.0.1:7860\n",
"* To create a public link, set `share=True` in `launch()`.\n"
]
},
{
"data": {
"text/html": [
"<div><iframe src=\"http://127.0.0.1:7860/\" width=\"100%\" height=\"500\" allow=\"autoplay; camera; microphone; clipboard-read; clipboard-write;\" frameborder=\"0\" allowfullscreen></iframe></div>"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/plain": []
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/hopeogbons/Projects/andela/llm_engineering/.venv/lib/python3.12/site-packages/whisper/transcribe.py:132: UserWarning: FP16 is not supported on CPU; using FP32 instead\n",
" warnings.warn(\"FP16 is not supported on CPU; using FP32 instead\")\n",
"100%|██████████| 10416/10416 [00:06<00:00, 1723.31frames/s]\n",
"/Users/hopeogbons/Projects/andela/llm_engineering/.venv/lib/python3.12/site-packages/whisper/transcribe.py:132: UserWarning: FP16 is not supported on CPU; using FP32 instead\n",
" warnings.warn(\"FP16 is not supported on CPU; using FP32 instead\")\n",
"100%|██████████| 10416/10416 [00:30<00:00, 341.64frames/s]\n",
"/Users/hopeogbons/Projects/andela/llm_engineering/.venv/lib/python3.12/site-packages/whisper/transcribe.py:132: UserWarning: FP16 is not supported on CPU; using FP32 instead\n",
" warnings.warn(\"FP16 is not supported on CPU; using FP32 instead\")\n",
"100%|██████████| 2289/2289 [00:01<00:00, 1205.18frames/s]\n"
]
}
],
"source": [
"# Launch\n",
"\n",
"# Close any previous instances\n",
"try:\n",
" app.close()\n",
"except:\n",
" pass\n",
"\n",
"# Start the app\n",
"app.launch(inbrowser=True, prevent_thread_lock=True)\n"
]
},
{
"cell_type": "markdown",
"id": "c3c2ec24",
"metadata": {},
"source": [
"---\n",
"\n",
"## 💡 How to Use\n",
"\n",
"1. **Upload** an audio file (MP3, WAV, M4A, etc.)\n",
"2. **Select** your language (or use Auto-detect)\n",
"3. **Click** Submit\n",
"4. **Get** your transcription!\n",
"\n",
"---\n",
"\n",
"## 🚀 Running on Google Colab\n",
"\n",
"For GPU acceleration on Colab:\n",
"1. Runtime → Change runtime type → **GPU (T4)**\n",
"2. Run all cells in order\n",
"3. The model will use GPU automatically\n",
"\n",
"**Note:** First run downloads the Whisper model (~140MB) - this is a one-time download.\n",
"\n",
"---\n",
"\n",
"## 📝 Supported Languages\n",
"\n",
"English • Spanish • French • German • Italian • Portuguese • Chinese • Japanese • Korean • Russian • Arabic • Auto-detect\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"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.12.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,545 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "ffe08bad",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"from dotenv import load_dotenv\n",
"from openai import OpenAI\n",
"import json\n",
"from typing import List, Dict\n",
"import gradio as gr\n",
"import random\n",
"\n",
"load_dotenv(override=True)\n",
"client = OpenAI()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2f24eb03",
"metadata": {},
"outputs": [],
"source": [
"LEGAL_TOPIC_SEEDS = [\n",
" \"criminal offenses and penalties\",\n",
" \"property rights and disputes\",\n",
" \"contract law and breach remedies\",\n",
" \"civil procedure and court processes\",\n",
" \"evidence admissibility rules\",\n",
" \"constitutional rights protections\",\n",
" \"family law and inheritance\",\n",
" \"corporate governance regulations\",\n",
" \"intellectual property protections\",\n",
" \"cyber crime and digital law\"\n",
"]\n",
"\n",
"QUESTION_TYPES = [\n",
" \"definition\",\n",
" \"procedure\",\n",
" \"penalty\",\n",
" \"rights\",\n",
" \"obligations\",\n",
" \"exceptions\",\n",
" \"examples\"\n",
"]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9256c3ae",
"metadata": {},
"outputs": [],
"source": [
"class SyntheticLegalGenerator:\n",
" \"\"\"Generates synthetic legal content and sections\"\"\"\n",
" \n",
" def __init__(self, client: OpenAI, model: str = \"gpt-4o-mini\"):\n",
" self.client = client\n",
" self.model = model\n",
" \n",
" def generate_legal_section(self, topic: str) -> Dict[str, str]:\n",
" \"\"\"Generate a completely synthetic legal section\"\"\"\n",
" \n",
" prompt = f\"\"\"Create a SYNTHETIC (fictional but realistic) Indian legal section about: {topic}\n",
"\n",
"Generate:\n",
"1. A section number (format: IPC XXX or CrPC XXX or IEA XXX)\n",
"2. A clear title\n",
"3. A detailed legal provision (2-3 sentences)\n",
"\n",
"Make it realistic but completely fictional. Use legal language.\n",
"\n",
"Format:\n",
"SECTION: [number]\n",
"TITLE: [title]\n",
"PROVISION: [detailed text]\"\"\"\n",
"\n",
" try:\n",
" response = self.client.chat.completions.create(\n",
" model=self.model,\n",
" messages=[\n",
" {\"role\": \"system\", \"content\": \"You are a legal content generator creating synthetic Indian legal provisions for educational purposes.\"},\n",
" {\"role\": \"user\", \"content\": prompt}\n",
" ],\n",
" temperature=0.8,\n",
" max_tokens=400\n",
" )\n",
" \n",
" content = response.choices[0].message.content.strip()\n",
" \n",
" # Parse the response\n",
" section_num = \"\"\n",
" title = \"\"\n",
" provision = \"\"\n",
" \n",
" for line in content.split('\\n'):\n",
" if line.startswith('SECTION:'):\n",
" section_num = line.replace('SECTION:', '').strip()\n",
" elif line.startswith('TITLE:'):\n",
" title = line.replace('TITLE:', '').strip()\n",
" elif line.startswith('PROVISION:'):\n",
" provision = line.replace('PROVISION:', '').strip()\n",
" \n",
" return {\n",
" \"section_number\": section_num,\n",
" \"title\": title,\n",
" \"provision\": provision,\n",
" \"topic\": topic\n",
" }\n",
" \n",
" except Exception as e:\n",
" print(f\"Error generating section: {e}\")\n",
" return {\n",
" \"section_number\": \"IPC 000\",\n",
" \"title\": \"Error\",\n",
" \"provision\": f\"Failed to generate: {e}\",\n",
" \"topic\": topic\n",
" }"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "32be3d52",
"metadata": {},
"outputs": [],
"source": [
"class SyntheticQAGenerator:\n",
" \"\"\"Generates Q&A pairs from synthetic legal sections\"\"\"\n",
" \n",
" def __init__(self, client: OpenAI, model: str = \"gpt-4o-mini\"):\n",
" self.client = client\n",
" self.model = model\n",
" \n",
" def generate_qa_pair(self, legal_section: Dict[str, str], question_type: str) -> Dict[str, str]:\n",
" \"\"\"Generate Q&A pair from synthetic legal section\"\"\"\n",
" \n",
" prompt = f\"\"\"Based on this SYNTHETIC legal section, create a {question_type}-type question and answer:\n",
"\n",
"Section: {legal_section['section_number']}\n",
"Title: {legal_section['title']}\n",
"Provision: {legal_section['provision']}\n",
"\n",
"Create ONE question (focusing on {question_type}) and a clear, accurate answer based on this provision.\n",
"\n",
"Format:\n",
"Q: [question]\n",
"A: [answer]\n",
"\n",
"Keep it educational and clear.\"\"\"\n",
"\n",
" try:\n",
" response = self.client.chat.completions.create(\n",
" model=self.model,\n",
" messages=[\n",
" {\"role\": \"system\", \"content\": \"You are creating educational Q&A pairs from synthetic legal content.\"},\n",
" {\"role\": \"user\", \"content\": prompt}\n",
" ],\n",
" temperature=0.7,\n",
" max_tokens=350\n",
" )\n",
" \n",
" content = response.choices[0].message.content.strip()\n",
" \n",
" # Parse Q&A\n",
" question = \"\"\n",
" answer = \"\"\n",
" \n",
" for line in content.split('\\n'):\n",
" if line.startswith('Q:'):\n",
" question = line[2:].strip()\n",
" elif line.startswith('A:'):\n",
" answer = line[2:].strip()\n",
" \n",
" return {\n",
" \"section_number\": legal_section['section_number'],\n",
" \"section_title\": legal_section['title'],\n",
" \"provision\": legal_section['provision'],\n",
" \"question_type\": question_type,\n",
" \"question\": question,\n",
" \"answer\": answer\n",
" }\n",
" \n",
" except Exception as e:\n",
" print(f\"Error generating Q&A: {e}\")\n",
" return {\n",
" \"section_number\": legal_section['section_number'],\n",
" \"section_title\": legal_section['title'],\n",
" \"provision\": legal_section['provision'],\n",
" \"question_type\": question_type,\n",
" \"question\": \"Error generating question\",\n",
" \"answer\": \"Error generating answer\"\n",
" }"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fe88708f",
"metadata": {},
"outputs": [],
"source": [
"class SyntheticDataPipeline:\n",
" \"\"\"Complete pipeline for synthetic legal Q&A generation\"\"\"\n",
" \n",
" def __init__(self, legal_gen: SyntheticLegalGenerator, qa_gen: SyntheticQAGenerator):\n",
" self.legal_gen = legal_gen\n",
" self.qa_gen = qa_gen\n",
" self.dataset: List[Dict[str, str]] = []\n",
" \n",
" def generate_complete_entry(self, topic: str = None, question_type: str = None) -> Dict[str, str]:\n",
" \"\"\"Generate synthetic legal section + Q&A in one go\"\"\"\n",
" \n",
" # Pick random topic if not provided\n",
" if topic is None:\n",
" topic = random.choice(LEGAL_TOPIC_SEEDS)\n",
" \n",
" # Pick random question type if not provided\n",
" if question_type is None:\n",
" question_type = random.choice(QUESTION_TYPES)\n",
" \n",
" # Step 1: Generate synthetic legal section\n",
" legal_section = self.legal_gen.generate_legal_section(topic)\n",
" \n",
" # Step 2: Generate Q&A from that section\n",
" qa_pair = self.qa_gen.generate_qa_pair(legal_section, question_type)\n",
" \n",
" return qa_pair\n",
" \n",
" def generate_batch(self, count: int, progress_callback=None) -> List[Dict[str, str]]:\n",
" \"\"\"Generate multiple synthetic entries\"\"\"\n",
" batch = []\n",
" \n",
" for i in range(count):\n",
" if progress_callback:\n",
" progress_callback((i + 1) / count, desc=f\"Generating {i+1}/{count}...\")\n",
" \n",
" entry = self.generate_complete_entry()\n",
" batch.append(entry)\n",
" self.dataset.append(entry)\n",
" \n",
" return batch\n",
" \n",
" def save_dataset(self, filename: str = \"synthetic_legal_qa.json\") -> str:\n",
" \"\"\"Save dataset to JSON\"\"\"\n",
" try:\n",
" with open(filename, 'w', encoding='utf-8') as f:\n",
" json.dump(self.dataset, f, indent=2, ensure_ascii=False)\n",
" return f\"✅ Saved {len(self.dataset)} synthetic Q&A pairs to {filename}\"\n",
" except Exception as e:\n",
" return f\"❌ Error saving: {e}\"\n",
" \n",
" def get_summary(self) -> str:\n",
" \"\"\"Get dataset summary\"\"\"\n",
" if not self.dataset:\n",
" return \"No synthetic data generated yet.\"\n",
" \n",
" summary = f\"**Total Synthetic Q&A Pairs:** {len(self.dataset)}\\n\\n\"\n",
" summary += \"**Topics Covered:**\\n\"\n",
" \n",
" topics = {}\n",
" for entry in self.dataset:\n",
" topic = entry.get('section_title', 'Unknown')\n",
" topics[topic] = topics.get(topic, 0) + 1\n",
" \n",
" for topic, count in topics.items():\n",
" summary += f\"- {topic}: {count}\\n\"\n",
" \n",
" return summary"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0822c49e",
"metadata": {},
"outputs": [],
"source": [
"legal_generator = SyntheticLegalGenerator(client)\n",
"qa_generator = SyntheticQAGenerator(client)\n",
"pipeline = SyntheticDataPipeline(legal_generator, qa_generator)\n",
"\n",
"print(\"✅ Synthetic data pipeline initialized!\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9b86f15f",
"metadata": {},
"outputs": [],
"source": [
"# Cell 8: UI functions with real-time progress updates\n",
"def generate_single_synthetic(topic_choice: str, question_type: str, progress=gr.Progress()):\n",
" \"\"\"Generate single synthetic entry with real-time updates\"\"\"\n",
" \n",
" # Step 1: Generate legal section\n",
" progress(0.2, desc=\"🔍 Generating synthetic legal section...\")\n",
" yield \"⏳ Creating synthetic legal provision...\", pipeline.get_summary()\n",
" \n",
" legal_section = pipeline.legal_gen.generate_legal_section(topic_choice)\n",
" \n",
" # Show intermediate result\n",
" intermediate = f\"### 📜 Generated Section\\n\\n\"\n",
" intermediate += f\"**{legal_section['section_number']}**: {legal_section['title']}\\n\\n\"\n",
" intermediate += f\"_{legal_section['provision']}_\\n\\n\"\n",
" intermediate += \"⏳ Now generating Q&A pair...\"\n",
" \n",
" progress(0.5, desc=\"💭 Creating Q&A pair...\")\n",
" yield intermediate, pipeline.get_summary()\n",
" \n",
" # Step 2: Generate Q&A\n",
" qa_pair = pipeline.qa_gen.generate_qa_pair(legal_section, question_type)\n",
" pipeline.dataset.append(qa_pair)\n",
" \n",
" progress(0.9, desc=\"✨ Finalizing...\")\n",
" \n",
" # Final result\n",
" result = f\"### 🏛️ {qa_pair['section_number']}: {qa_pair['section_title']}\\n\\n\"\n",
" result += f\"**Provision:** {qa_pair['provision']}\\n\\n\"\n",
" result += f\"**Question Type:** _{qa_pair['question_type']}_\\n\\n\"\n",
" result += f\"**Q:** {qa_pair['question']}\\n\\n\"\n",
" result += f\"**A:** {qa_pair['answer']}\\n\\n\"\n",
" result += \"---\\n✅ **Added to dataset!**\"\n",
" \n",
" progress(1.0, desc=\"✅ Complete!\")\n",
" yield result, pipeline.get_summary()\n",
"\n",
"def generate_batch_synthetic(num_pairs: int, progress=gr.Progress()):\n",
" \"\"\"Generate batch with live updates after each entry\"\"\"\n",
" \n",
" results = []\n",
" count = int(num_pairs)\n",
" \n",
" for i in range(count):\n",
" # Update progress\n",
" progress_pct = (i + 1) / count\n",
" progress(progress_pct, desc=f\"🔄 Generating {i+1}/{count}...\")\n",
" \n",
" # Generate entry\n",
" entry = pipeline.generate_complete_entry()\n",
" pipeline.dataset.append(entry)\n",
" \n",
" # Format result\n",
" result = f\"### {i+1}. {entry['section_number']}: {entry['section_title']}\\n\"\n",
" result += f\"**Q:** {entry['question']}\\n\"\n",
" result += f\"**A:** {entry['answer']}\\n\\n\"\n",
" results.append(result)\n",
" \n",
" # Yield intermediate results to update UI in real-time\n",
" current_output = \"\".join(results)\n",
" current_output += f\"\\n---\\n⏳ **Progress: {i+1}/{count} completed**\"\n",
" \n",
" yield current_output, pipeline.get_summary()\n",
" \n",
" # Final output\n",
" final_output = \"\".join(results)\n",
" final_output += f\"\\n---\\n✅ **All {count} Q&A pairs generated successfully!**\"\n",
" \n",
" progress(1.0, desc=\"✅ Batch complete!\")\n",
" yield final_output, pipeline.get_summary()\n",
"\n",
"def save_synthetic_dataset():\n",
" \"\"\"Save the synthetic dataset\"\"\"\n",
" return pipeline.save_dataset()\n",
"\n",
"def clear_dataset():\n",
" \"\"\"Clear the current dataset\"\"\"\n",
" pipeline.dataset.clear()\n",
" return \"✅ Dataset cleared!\", pipeline.get_summary()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9d352fec",
"metadata": {},
"outputs": [],
"source": [
"# Cell 9: Enhanced UI with real-time updates\n",
"with gr.Blocks(title=\"Synthetic Legal Q&A Generator\", theme=gr.themes.Soft()) as demo:\n",
" gr.Markdown(\"# 🤖 Synthetic Legal Q&A Data Generator\")\n",
" gr.Markdown(\"**Generates completely synthetic Indian legal sections AND Q&A pairs from scratch**\")\n",
" gr.Markdown(\"_Watch the magic happen in real-time! 🎬_\")\n",
" \n",
" with gr.Tab(\"🎯 Single Generation\"):\n",
" gr.Markdown(\"### Generate one synthetic legal section with Q&A\")\n",
" gr.Markdown(\"_See each step of generation as it happens_\")\n",
" \n",
" with gr.Row():\n",
" with gr.Column(scale=1):\n",
" topic_dropdown = gr.Dropdown(\n",
" choices=LEGAL_TOPIC_SEEDS,\n",
" label=\"🎯 Select Legal Topic\",\n",
" value=LEGAL_TOPIC_SEEDS[0]\n",
" )\n",
" qtype_dropdown = gr.Dropdown(\n",
" choices=QUESTION_TYPES,\n",
" label=\"❓ Question Type\",\n",
" value=QUESTION_TYPES[0]\n",
" )\n",
" gen_single_btn = gr.Button(\n",
" \"🎲 Generate Synthetic Entry\", \n",
" variant=\"primary\",\n",
" size=\"lg\"\n",
" )\n",
" \n",
" with gr.Column(scale=2):\n",
" output_single = gr.Markdown(\n",
" label=\"Generated Content\",\n",
" value=\"Click **Generate** to create synthetic legal content...\"\n",
" )\n",
" \n",
" summary_single = gr.Textbox(\n",
" label=\"📊 Dataset Summary\", \n",
" lines=6,\n",
" interactive=False\n",
" )\n",
" \n",
" gen_single_btn.click(\n",
" fn=generate_single_synthetic,\n",
" inputs=[topic_dropdown, qtype_dropdown],\n",
" outputs=[output_single, summary_single]\n",
" )\n",
" \n",
" with gr.Tab(\"🚀 Batch Generation\"):\n",
" gr.Markdown(\"### Generate multiple synthetic legal Q&A pairs\")\n",
" gr.Markdown(\"_Live updates as each Q&A pair is created!_\")\n",
" \n",
" with gr.Row():\n",
" with gr.Column(scale=1):\n",
" num_slider = gr.Slider(\n",
" minimum=5,\n",
" maximum=1000,\n",
" value=5,\n",
" step=5,\n",
" label=\"📦 Number of Synthetic Q&A Pairs\"\n",
" )\n",
" gr.Markdown(\"**Tip:** Start with 10-20 pairs to see live generation\")\n",
" gen_batch_btn = gr.Button(\n",
" \"🔥 Generate Batch\", \n",
" variant=\"primary\",\n",
" size=\"lg\"\n",
" )\n",
" \n",
" with gr.Column(scale=2):\n",
" output_batch = gr.Markdown(\n",
" label=\"Generated Synthetic Data\",\n",
" value=\"Click **Generate Batch** to start creating multiple Q&A pairs...\"\n",
" )\n",
" \n",
" summary_batch = gr.Textbox(\n",
" label=\"📊 Dataset Summary\", \n",
" lines=6,\n",
" interactive=False\n",
" )\n",
" \n",
" gen_batch_btn.click(\n",
" fn=generate_batch_synthetic,\n",
" inputs=[num_slider],\n",
" outputs=[output_batch, summary_batch]\n",
" )\n",
" \n",
" with gr.Tab(\"💾 Manage Dataset\"):\n",
" gr.Markdown(\"### Save or Clear Your Synthetic Dataset\")\n",
" \n",
" with gr.Row():\n",
" with gr.Column():\n",
" gr.Markdown(\"**💾 Save your generated data**\")\n",
" gr.Markdown(\"Exports all Q&A pairs to `synthetic_legal_qa.json`\")\n",
" save_btn = gr.Button(\n",
" \"💾 Save to JSON\", \n",
" variant=\"primary\",\n",
" size=\"lg\"\n",
" )\n",
" \n",
" with gr.Column():\n",
" gr.Markdown(\"**🗑️ Clear current dataset**\")\n",
" gr.Markdown(\"⚠️ This will remove all generated Q&A pairs\")\n",
" clear_btn = gr.Button(\n",
" \"🗑️ Clear Dataset\", \n",
" variant=\"stop\",\n",
" size=\"lg\"\n",
" )\n",
" \n",
" manage_status = gr.Textbox(\n",
" label=\"Status\", \n",
" lines=2,\n",
" interactive=False\n",
" )\n",
" manage_summary = gr.Textbox(\n",
" label=\"Current Dataset Overview\", \n",
" lines=10,\n",
" interactive=False,\n",
" value=pipeline.get_summary()\n",
" )\n",
" \n",
" save_btn.click(\n",
" fn=save_synthetic_dataset,\n",
" inputs=[],\n",
" outputs=[manage_status]\n",
" )\n",
" \n",
" clear_btn.click(\n",
" fn=clear_dataset,\n",
" inputs=[],\n",
" outputs=[manage_status, manage_summary]\n",
" )\n",
" \n",
" # Footer\n",
" gr.Markdown(\"---\")\n",
" gr.Markdown(\"🎓 **LLM Engineering Week 3** | Synthetic Data Generation Challenge\")\n",
"\n",
"demo.launch(share=False, inbrowser=True)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "llm-engineering",
"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.12.10"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,802 @@
[
{
"section_number": "IPC 123A",
"section_title": "Protection of Digital Intellectual Property Rights",
"provision": "Whoever, without the authorization of the owner, reproduces, distributes, or publicly displays any digital work, including software, databases, or multimedia content, shall be punished with imprisonment for a term which may extend to three years, or with fine which may extend to five lakh rupees, or with both. For the purposes of this section, \"digital work\" shall include any creation that exists in a digital format and embodies original intellectual effort. The provisions of this section shall apply in addition to any rights conferred under the Copyright Act, 1957.",
"question_type": "examples",
"question": "Can you provide examples of actions that would violate IPC 123A regarding the protection of digital intellectual property rights?",
"answer": "Yes, actions that would violate IPC 123A include: 1) Downloading and sharing a copyrighted software program without the owner's permission; 2) Reproducing a digital art piece and selling it online without authorization from the artist; 3) Distributing a database containing proprietary information without consent from the database owner; and 4) Publicly displaying a video or multimedia content on a website or social media platform without obtaining the rights from the creator. All these actions constitute unauthorized reproduction, distribution, or public display of digital works, which are punishable under this provision."
},
{
"section_number": "FLA 102",
"section_title": "Inheritance Rights of Unmarried Children",
"provision": "In the event of the intestate death of a parent, all unmarried children shall be entitled to an equal share in the estate of the deceased, irrespective of the parentage or domicile of the children. The rights conferred herein shall be enforceable against any individual claiming succession rights to the estate, and no testamentary disposition or familial agreement shall supersede the statutory entitlement outlined in this provision. Furthermore, the provisions of this section shall apply retroactively to all intestate estates, regardless of the date of death of the decedent.",
"question_type": "rights",
"question": "What rights do unmarried children have in the event of an intestate death of a parent according to FLA 102?",
"answer": "Unmarried children are entitled to an equal share in the estate of the deceased parent, regardless of their parentage or domicile. This right is enforceable against anyone claiming succession rights to the estate and cannot be overridden by any will or familial agreement. Additionally, this provision applies retroactively to all intestate estates, regardless of when the decedent died."
},
{
"section_number": "IEA 120A",
"section_title": "Admissibility of Digital Evidence",
"provision": "Notwithstanding any other provisions of this Act, digital evidence, including but not limited to electronic documents, data stored in digital format, and communications transmitted electronically, shall be admissible in any proceeding before a court provided that the party seeking to introduce such evidence demonstrates its authenticity and relevance. The court may require the party to produce a digital forensic report or certificate from a qualified expert to establish the integrity of the digital evidence in question, ensuring that the evidence has not been tampered with and is a true representation of the original data.",
"question_type": "procedure",
"question": "What steps must a party take to ensure the admissibility of digital evidence in court under IEA 120A?",
"answer": "To ensure the admissibility of digital evidence in court under IEA 120A, the party seeking to introduce the evidence must demonstrate both its authenticity and relevance. Additionally, the court may require the party to produce a digital forensic report or a certificate from a qualified expert to establish the integrity of the digital evidence, confirming that it has not been tampered with and accurately represents the original data."
},
{
"section_number": "IPC 456",
"section_title": "Offense of Cyber Intimidation",
"provision": "Whoever, with intent to cause harm or distress to any person, uses a computer resource or communication device to send threats, intimidate, or coerce such person through electronic means, shall be punished with imprisonment for a term which may extend to three years, or with fine which may extend to fifty thousand rupees, or with both. In the event of repeated offenses, the imprisonment may extend to five years and the fine may be increased to one lakh rupees.",
"question_type": "definition",
"question": "What constitutes the offense of cyber intimidation under IPC 456?",
"answer": "The offense of cyber intimidation under IPC 456 is defined as the act of using a computer resource or communication device to send threats, intimidate, or coerce any person with the intent to cause harm or distress."
},
{
"section_number": "IPC 124A",
"section_title": "Protection of Original Works of Authorship",
"provision": "Any person who, without the consent of the author or creator, reproduces, distributes, or publicly displays an original work of authorship, including but not limited to literary, artistic, musical, and dramatic works, shall be liable for infringement. Such infringement shall be punishable with imprisonment for a term that may extend to three years, or with fine, or with both. This section shall not apply to uses that fall under the doctrine of fair use as defined by the relevant provisions of this Code.",
"question_type": "obligations",
"question": "What obligations does a person have regarding the reproduction, distribution, or public display of an original work of authorship under IPC 124A?",
"answer": "A person is obligated to obtain the consent of the author or creator before reproducing, distributing, or publicly displaying an original work of authorship. Failure to do so could result in liability for infringement, which may lead to penalties including imprisonment for up to three years, a fine, or both, unless the use falls under the doctrine of fair use as defined by the relevant provisions of the Code."
},
{
"section_number": "IPC 500A",
"section_title": "Unauthorized Access and Data Manipulation",
"provision": "Whoever, without lawful authority, intentionally gains access to any computer resource or computer system and causes alteration, deletion, or addition of data therein, shall be punished with imprisonment for a term which may extend to three years, or with fine which may extend to one lakh rupees, or with both. For the purposes of this section, \"computer resource\" shall include any data, software, or digital content stored within the device or network, and \"lawful authority\" shall mean permission granted by the owner or authorized custodian of the computer resource.",
"question_type": "definition",
"question": "What is meant by \"lawful authority\" as defined in IPC 500A regarding unauthorized access to computer resources?",
"answer": "\"Lawful authority\" refers to the permission granted by the owner or authorized custodian of the computer resource to access that resource."
},
{
"section_number": "IPC 506A",
"section_title": "Cyber Harassment",
"provision": "Whosoever, by means of electronic communication or any digital platform, intentionally causes physical or mental harm to another person through threats, intimidation, or coercive messaging, shall be punished with imprisonment for a term which may extend to three years, or with fine which may extend to fifty thousand rupees, or with both. For the purposes of this section, \"electronic communication\" includes, but is not limited to, text messages, emails, social media interactions, and any other forms of digital messaging.",
"question_type": "procedure",
"question": "What steps should a victim take to file a complaint under IPC 506A for cyber harassment?",
"answer": "A victim of cyber harassment under IPC 506A should follow these steps to file a complaint:"
},
{
"section_number": "IEA 102A",
"section_title": "Admissibility of Electronic Evidence",
"provision": "Notwithstanding the provisions of Section 61 of this Act, electronic evidence shall be admissible in any proceedings before a court provided it is accompanied by a certificate from the producer attesting to its authenticity and integrity, as prescribed under the Information Technology Act, 2000. The court shall assess the credibility of such evidence in accordance with the standards established by the Supreme Court and may require additional corroboration if deemed necessary for the interests of justice. Any objection to the admissibility of electronic evidence shall be raised at the earliest possible stage, failing which the right to contest its admissibility shall be deemed waived.",
"question_type": "penalty",
"question": "What are the consequences of failing to raise an objection to the admissibility of electronic evidence at the earliest possible stage under IEA 102A?",
"answer": "If a party fails to raise an objection to the admissibility of electronic evidence at the earliest possible stage, they will be deemed to have waived their right to contest its admissibility in court. This means that the objection cannot be raised later in the proceedings, potentially impacting the outcome of the case."
},
{
"section_number": "FLA 123",
"section_title": "Rights of Inheritance among Lineal Ascendants and Descendants",
"provision": "In matters of inheritance, lineal ascendants shall inherit equal shares alongside lineal descendants in the absence of a will. In cases where property is self-acquired, the owner may designate the distribution of their estate; however, such designation shall not infringe upon the statutory rights of the surviving spouse or any children, who shall retain a minimum guaranteed share as prescribed under this Act. In the event of a dispute, such claims shall be adjudicated by the Family Court, taking into consideration the principles of equity and the welfare of all parties involved.",
"question_type": "examples",
"question": "If a person dies without a will and is survived by their parents and children, how will the inheritance be divided according to FLA 123?",
"answer": "According to FLA 123, if a person dies without a will, their lineal ascendants (parents) and lineal descendants (children) will inherit equal shares of the estate. For example, if the estate is worth $120,000 and the deceased is survived by both their parents and children (let's say two children), the estate would be divided equally among them. Each parent would receive $20,000, and each child would also receive $20,000, totaling the estate's value. However, if the deceased had designated a different distribution in a will, it must still respect the minimum guaranteed share for the surviving spouse and children, as mandated by the Act."
},
{
"section_number": "IPC 123A",
"section_title": "Protection of Digital Innovations",
"provision": "Any individual or entity that creates an original digital work, including but not limited to software, algorithms, and digital media, shall have the exclusive right to control the reproduction, distribution, and adaptation of such work for a period of ten years from the date of creation, subject to the provisions of fair use as outlined in this Code. Unauthorized use or reproduction of a protected digital innovation shall attract civil penalties, including but not limited to injunctions, damages, and the seizure of infringing materials, as deemed appropriate by the court.",
"question_type": "obligations",
"question": "What obligations do individuals or entities have when creating original digital works under IPC 123A?",
"answer": "Individuals or entities that create original digital works have the obligation to control the reproduction, distribution, and adaptation of their work for a period of ten years from the date of creation. They must ensure that any use of their digital innovations is authorized, as unauthorized use or reproduction can result in civil penalties, including injunctions, damages, and the seizure of infringing materials as determined by the court."
},
{
"section_number": "IPC 509A",
"section_title": "Intentional Misuse of Digital Identity",
"provision": "Whoever, intending to cause annoyance, inconvenience, or harm, knowingly and dishonestly uses or impersonates the digital identity of another person, including but not limited to social media accounts, email addresses, or any other digital platform, shall be punishable with imprisonment for a term that may extend to three years, or with fine which may extend to fifty thousand rupees, or with both. In the case of repeat offenders, the term of imprisonment may extend to five years.",
"question_type": "exceptions",
"question": "Are there any exceptions to the punishment under IPC 509A for the intentional misuse of digital identity?",
"answer": "Yes, exceptions may apply in cases where the individual can demonstrate that their use of another person's digital identity was done with the consent of that person or for legitimate purposes such as parody, satire, or commentary that does not intend to cause annoyance, inconvenience, or harm. However, the burden of proof lies with the individual claiming the exception, and it is essential to establish that the intent was not malicious."
},
{
"section_number": "IPR 145",
"section_title": "Rights of Co-Owners in Joint Property",
"provision": "In any joint ownership of property, each co-owner shall possess an equal right to utilize, manage, and derive benefit from the property, subject to the terms of their agreement. In the event of a dispute regarding the use or management of the property, any co-owner may seek mediation through the appropriate civil court, which shall have the authority to appoint a neutral arbitrator to facilitate a resolution. Should the parties remain in disagreement following mediation, the court shall adjudicate based on the principles of equity and the specific contributions made by each co-owner toward the property.",
"question_type": "procedure",
"question": "What steps should a co-owner take if there is a dispute regarding the use or management of jointly owned property?",
"answer": "If a co-owner encounters a dispute regarding the use or management of jointly owned property, they should first seek mediation through the appropriate civil court. The court will appoint a neutral arbitrator to help facilitate a resolution. If the parties still cannot reach an agreement after mediation, the court will then adjudicate the dispute based on principles of equity and consider the specific contributions made by each co-owner towards the property."
},
{
"section_number": "CPL 456",
"section_title": "Remedies for Breach of Contract",
"provision": "In the event of a breach of contract, the aggrieved party shall be entitled to seek specific performance of the contract, or alternatively, claim for damages which shall be calculated based on the loss incurred directly as a result of the breach. The court may, at its discretion, award punitive damages not exceeding the value of the contract, if it finds the breach to have been willful or malicious. Any claims for reliance damages shall be substantiated with adequate evidence demonstrating the expenditures incurred in preparation for the performance of the contract.",
"question_type": "procedure",
"question": "What steps must the aggrieved party take to claim specific performance or damages in the event of a breach of contract according to CPL 456?",
"answer": "The aggrieved party must first determine whether to seek specific performance of the contract or claim for damages. If claiming damages, they should calculate the loss incurred directly due to the breach. If they wish to seek punitive damages, they must demonstrate that the breach was willful or malicious, keeping in mind that such damages cannot exceed the value of the contract. Additionally, if the party wants to claim reliance damages, they must gather and present adequate evidence of expenditures incurred in preparation for the contract's performance. All claims should be filed with the appropriate court as per the procedural rules governing contract disputes."
},
{
"section_number": "IEA 112",
"section_title": "Admissibility of Digital Evidence",
"provision": "Notwithstanding any other provisions of this Act, digital evidence shall be admissible in judicial proceedings, provided that it is demonstrated to be authentic and relevant to the matter at hand. The party seeking to introduce digital evidence must establish a clear chain of custody and utilize appropriate technological methods for preservation and extraction, ensuring that the integrity of the evidence has not been compromised. Furthermore, the court may consider expert testimony regarding the reliability of the digital medium used to store or transmit such evidence.",
"question_type": "examples",
"question": "Can you provide an example of how a party might successfully introduce digital evidence in court under IEA 112?",
"answer": "Certainly! Imagine a scenario where a company is accused of data theft. The plaintiff wants to introduce an email as digital evidence that allegedly contains confidential information sent to a competitor. To successfully admit this email under IEA 112, the plaintiff would need to demonstrate its authenticity by showing that the email was indeed sent from the company's server. They would establish a clear chain of custody by documenting who accessed the email and how it was preserved, perhaps by using secure storage methods. Additionally, they might engage a digital forensics expert to testify about the reliability of the email server and the methods used to extract the email, ensuring that the integrity of the evidence has not been compromised. If all these criteria are met, the court would likely admit the email as evidence in the proceedings."
},
{
"section_number": "CPL 125",
"section_title": "Remedies for Breach of Contract",
"provision": "In the event of a breach of contract, the aggrieved party shall be entitled to seek restitution by way of specific performance, or, in lieu thereof, claim for damages not exceeding the actual loss incurred as a direct result of the breach. Furthermore, the court may, at its discretion, award consequential damages if such damages were within the contemplation of the parties at the time of contract formation, provided that the aggrieved party has made reasonable efforts to mitigate the loss.",
"question_type": "procedure",
"question": "What steps must the aggrieved party take to seek remedies for a breach of contract under CPL 125?",
"answer": "To seek remedies for a breach of contract under CPL 125, the aggrieved party should follow these steps: First, clearly identify and document the breach of contract. Next, determine whether they wish to seek specific performance or claim for damages. If claiming damages, the aggrieved party must calculate and document the actual loss incurred as a direct result of the breach, ensuring that it does not exceed the actual loss. Additionally, the aggrieved party should demonstrate that they made reasonable efforts to mitigate the loss. Finally, if the aggrieved party believes consequential damages are applicable, they should provide evidence that such damages were within the contemplation of the parties at the time of contract formation. Once these steps are completed, the aggrieved party can file a claim in court to seek the desired remedies."
},
{
"section_number": "CONST 102",
"section_title": "Protection of Fundamental Rights",
"provision": "Every citizen shall have the right to freedom from arbitrary arrest and detention, ensuring that no person shall be deprived of their liberty without due process of law. Furthermore, every individual shall have the right to seek redress in a competent court of law for any violation of their fundamental rights, and the State shall be obligated to provide legal assistance to those unable to afford representation. Any law or action infringing upon the rights enumerated in this section shall be deemed unconstitutional and void.",
"question_type": "examples",
"question": "Can you provide examples of situations where a citizen's right to freedom from arbitrary arrest and detention might be violated, and what steps they can take if their rights are infringed upon?",
"answer": "Examples of situations where a citizen's right to freedom from arbitrary arrest and detention might be violated include being arrested without a warrant, being held without charges for an extended period, or being detained based solely on their political beliefs or race. In such cases, the individual has the right to seek redress in a competent court of law by filing a lawsuit against the authorities responsible for the violation. Additionally, if they cannot afford legal representation, the State is obligated to provide legal assistance to ensure that their rights are protected."
},
{
"section_number": "IPC 509A",
"section_title": "Cyber Harassment and Intimidation",
"provision": "Whoever, with the intent to harass or intimidate another person through the use of electronic communications, sends, posts, or publishes any obscene or threatening material shall be punished with imprisonment for a term that may extend to three years, or with fine which may extend to one lakh rupees, or with both. In cases where such actions lead to severe emotional distress or harm to the reputation of the victim, the imprisonment may extend to five years. The provisions of this section shall apply notwithstanding any other law in force relating to defamation or electronic privacy.",
"question_type": "procedure",
"question": "What steps should a victim take to file a complaint under IPC 509A for cyber harassment or intimidation?",
"answer": "A victim should follow these steps to file a complaint under IPC 509A: 1. Collect evidence of the harassment, such as screenshots of messages or posts. 2. Approach the nearest police station to report the incident, providing the collected evidence. 3. File a formal complaint, ensuring that all relevant details are included, such as dates, times, and the nature of the communications. 4. The police will register the complaint and may initiate an investigation. 5. If the harassment leads to severe emotional distress or harm to reputation, the victim should highlight this to the authorities, as it can lead to an extended prison term for the offender. It is advisable for the victim to seek legal assistance to navigate the process effectively."
},
{
"section_number": "IPC 432",
"section_title": "Punishment for Intentional Damage to Public Property",
"provision": "Whoever intentionally causes damage to any public property, including but not limited to roads, bridges, or public buildings, shall be punishable with imprisonment for a term which may extend to three years, or with a fine which may extend to five lakh rupees, or with both. In cases where the damage exceeds a value of one lakh rupees, the offender shall be liable to imprisonment for a term which may extend to five years, and the fine may extend to ten lakh rupees. This provision shall not apply to acts of lawful protest or demonstration, provided that such actions do not result in damage to the aforementioned properties.",
"question_type": "definition",
"question": "What constitutes intentional damage to public property under IPC 432?",
"answer": "Intentional damage to public property under IPC 432 refers to the deliberate act of causing harm to any public assets, which includes but is not limited to roads, bridges, or public buildings. Such actions are punishable by imprisonment or fines, depending on the extent of the damage caused."
},
{
"section_number": "IPC 128A",
"section_title": "Rights and Resolution of Property Disputes",
"provision": "In any dispute concerning the ownership, possession, or title to immovable property, parties shall be entitled to seek resolution through a Mediation and Conciliation Board established under this Section. The Board shall consist of a Chairperson and two members, appointed by the State Government, who shall endeavor to resolve the dispute amicably within a period of six months from the date of reference, failing which the aggrieved party may escalate the matter to the appropriate civil court for adjudication. The provisions of this Section shall not preclude any party from approaching the court for urgent interim relief during the pendency of the mediation process.",
"question_type": "exceptions",
"question": "Are there any exceptions to the requirement of mediation for resolving property disputes under IPC 128A?",
"answer": "Yes, the provisions of IPC 128A do not preclude any party from approaching the court for urgent interim relief during the pendency of the mediation process. This means that if a party needs immediate relief, they can seek it from the court even while the mediation is ongoing."
},
{
"section_number": "CGR 102",
"section_title": "Disclosure of Financial Interests",
"provision": "Every corporate entity registered under the Companies Act, 2013 shall disclose in its annual report the financial interests of its board members and key managerial personnel, including any directorships, shareholdings, or partnerships in other entities that may pose a conflict of interest. This disclosure must be made in a format prescribed by the Securities and Exchange Board of India (SEBI) and shall be subject to scrutiny by the independent auditors to ensure transparency and accountability within the corporate governance framework. Non-compliance with this provision shall attract penalties as stipulated under Section 234 of the Companies Act, 2013.",
"question_type": "exceptions",
"question": "Are there any exceptions to the requirement for corporate entities to disclose the financial interests of their board members and key managerial personnel as per CGR 102?",
"answer": "Yes, exceptions to the disclosure requirement under CGR 102 may apply in certain circumstances, such as when the financial interests are deemed nominal and not likely to pose a conflict of interest, or if the board member or key managerial personnel is involved in a confidential matter that does not affect the corporate entity's governance. However, such exceptions must be clearly justified and documented, as non-compliance can lead to penalties under Section 234 of the Companies Act, 2013. It is advisable for entities to consult legal counsel to ensure compliance with all applicable regulations."
},
{
"section_number": "IEA 123",
"section_title": "Admissibility of Digital Evidence",
"provision": "Notwithstanding any other provision of law, digital evidence shall be admissible in any judicial proceeding provided it is accompanied by a certificate of authenticity from a qualified digital forensic expert, which verifies the integrity and accuracy of the data. Such evidence must be presented in a format that is compatible with the court's technological capabilities, and the party seeking to introduce the digital evidence shall bear the burden of proving its reliability and relevance to the matter at hand. The court may, in its discretion, exclude digital evidence if it deems that the probative value is outweighed by the potential for prejudice or misinformation.",
"question_type": "examples",
"question": "Can you provide an example of when digital evidence would be admissible in court under IEA 123?",
"answer": "Digital evidence, such as emails or text messages, would be admissible in court under IEA 123 if the party seeking to introduce this evidence presents it with a certificate of authenticity from a qualified digital forensic expert. For instance, if a plaintiff wants to use a series of text messages as evidence in a contract dispute, they must ensure the messages are verified for integrity and accuracy by a forensic expert. Additionally, the text messages must be presented in a format that the court can access and understand. If these conditions are met and the plaintiff can demonstrate the relevance and reliability of the texts, the court is likely to admit the evidence, unless it determines that the potential for prejudice outweighs its probative value."
},
{
"section_number": "IEA 65A",
"section_title": "Admissibility of Digital Evidence",
"provision": "Notwithstanding any provision to the contrary, digital evidence shall be admissible in a court of law if it is authenticated by the party seeking its admission. Authentication shall be established through a combination of metadata verification, secure chain of custody, and corroborative testimonial evidence, ensuring the integrity and reliability of the digital record. In instances where the authenticity is challenged, the burden of proof shall rest with the party contesting such admissibility.",
"question_type": "procedure",
"question": "What steps must a party take to ensure that digital evidence is admissible in court under IEA 65A?",
"answer": "To ensure the admissibility of digital evidence in court under IEA 65A, the party seeking its admission must authenticate the evidence through three key steps: (1) verify the metadata associated with the digital record, (2) establish a secure chain of custody for the evidence, and (3) provide corroborative testimonial evidence that supports the integrity and reliability of the digital record. If the authenticity of the evidence is challenged, the burden of proof will shift to the party contesting its admissibility."
},
{
"section_number": "CGR 101",
"section_title": "Board Composition and Independence",
"provision": "Every public company shall ensure that its Board of Directors comprises a minimum of one-third independent directors, who shall not have any material relationship with the company, its promoters, or its subsidiaries. The independent directors shall be responsible for safeguarding the interests of minority shareholders and enhancing the overall governance of the company. The criteria for independence and the process for appointment shall be prescribed under the Corporate Governance Regulations, ensuring transparency and accountability in the board's operations.",
"question_type": "exceptions",
"question": "Are there any exceptions to the requirement for a public company to have a minimum of one-third independent directors on its Board of Directors as stated in CGR 101?",
"answer": "Yes, exceptions may apply under specific circumstances as outlined in the Corporate Governance Regulations. For instance, if a company has a unique structure or meets certain criteria established by regulatory authorities, it may be allowed to deviate from the one-third independent director requirement. However, such exceptions must adhere to the principles of transparency and accountability, and the company must provide justification for any deviations from the standard composition."
},
{
"section_number": "CPR 101",
"section_title": "Right to Constitutional Protections",
"provision": "Every individual shall have the right to seek recourse under this Act for any violation of their fundamental rights as enumerated in the Constitution of India. The State shall ensure the protection of these rights against any encroachment by the public or private entities, and a mechanism for redressal of grievances shall be established within six months of any reported infringement. Furthermore, any citizen aggrieved by the denial of such rights may approach the Supreme Court or High Court for enforcement, and the courts shall prioritize such cases to ensure timely justice.",
"question_type": "examples",
"question": "Can you provide an example of a situation where an individual might seek recourse under the Right to Constitutional Protections as outlined in CPR 101?",
"answer": "An example of a situation where an individual might seek recourse under this provision is if a citizen is wrongfully detained by the police without due process, which violates their fundamental rights as guaranteed by the Constitution of India. In this case, the individual can file a complaint under the Act, seeking redress for the infringement of their rights. If the grievance is not resolved satisfactorily within six months, the individual has the option to approach the Supreme Court or High Court to enforce their rights and obtain timely justice."
},
{
"section_number": "CGR 302",
"section_title": "Standards of Conduct for Directors",
"provision": "Every director of a company shall act in good faith and in the best interests of the company, ensuring transparency and accountability in all dealings. Directors are mandated to disclose any potential conflicts of interest and refrain from participating in discussions or decisions where such conflicts may arise. Failure to comply with these standards shall result in penalties as prescribed under Section CGR 305, which may include disqualification from holding office in the company for a period not exceeding five years.",
"question_type": "procedure",
"question": "What steps must a director take to comply with the standards of conduct outlined in CGR 302 regarding potential conflicts of interest?",
"answer": "To comply with the standards of conduct in CGR 302, a director must take the following steps:"
},
{
"section_number": "IPC 502A",
"section_title": "Unauthorized Access and Data Breach",
"provision": "Whoever intentionally accesses a computer system or network without authorization, or exceeds authorized access to obtain, alter, or destroy data, shall be punishable with imprisonment for a term which may extend to three years, or with fine which may extend to fifty thousand rupees, or with both. In cases where such access results in a breach of sensitive personal data or causes harm to any individual or entity, the term of imprisonment may extend to five years, and the fine may extend to one lakh rupees.",
"question_type": "examples",
"question": "Can you provide examples of actions that would violate IPC 502A and the potential consequences for those actions?",
"answer": "Yes, under IPC 502A, several actions could constitute unauthorized access and data breach. For example, if an individual hacks into a company's computer system to steal customer data, this would be considered intentional unauthorized access. If the hacker is caught, they could face imprisonment for up to three years and fines up to fifty thousand rupees."
},
{
"section_number": "IPC 456",
"section_title": "Offences of Public Disruption and Associated Penalties",
"provision": "Whoever, without lawful authority, intentionally causes public disruption by engaging in violent or threatening behavior in a public place shall be punishable with imprisonment for a term which may extend to three years, or with fine which may extend to one lakh rupees, or with both. In the event of causing grievous hurt or significant property damage during such disruption, the offender shall be liable to imprisonment for a term not less than five years, which may extend to seven years, along with a fine that may extend to five lakh rupees.",
"question_type": "definition",
"question": "What constitutes the offense of public disruption under IPC 456?",
"answer": "The offense of public disruption under IPC 456 is defined as intentionally causing public disruption by engaging in violent or threatening behavior in a public place without lawful authority."
},
{
"section_number": "IPC 502",
"section_title": "Criminal Intimidation through Digital Means",
"provision": "Whoever, using any electronic device or communication service, intentionally threatens another person with injury to their person, reputation, or property, or to cause alarm or distress, shall be punishable with imprisonment of either description for a term which may extend to three years, or with fine, or with both. In addition, if such intimidation is intended to coerce or influence the victim's actions or decisions, the term of imprisonment may extend to five years.",
"question_type": "obligations",
"question": "What are the obligations of an individual regarding the use of electronic devices to communicate, as outlined in IPC 502?",
"answer": "An individual is obligated not to intentionally threaten another person with injury to their person, reputation, or property using any electronic device or communication service. Violating this obligation can result in punishment that includes imprisonment for up to three years, a fine, or both. If the intimidation is intended to coerce or influence the victim's actions or decisions, the imprisonment term may extend to five years."
},
{
"section_number": "CPC 124",
"section_title": "Application for Summary Judgment",
"provision": "In any civil proceedings, a party may apply to the court for a summary judgment on the ground that there is no genuine dispute as to any material fact and that the party is entitled to judgment as a matter of law. The application shall be supported by an affidavit setting forth the specific facts that demonstrate the absence of a material issue of fact. The court shall hear the application and may grant the summary judgment if it is satisfied that the evidence is clear and unequivocal, and that a trial is not necessary to resolve the issues presented.",
"question_type": "rights",
"question": "What rights does a party have when applying for a summary judgment under CPC 124?",
"answer": "A party has the right to apply for a summary judgment in civil proceedings if they believe there is no genuine dispute regarding any material fact and that they are entitled to judgment as a matter of law. To exercise this right, the party must support their application with an affidavit that specifies the facts demonstrating the lack of a material issue of fact. If the court finds the evidence to be clear and unequivocal, and determines that a trial is unnecessary, it may grant the summary judgment."
},
{
"section_number": "CRPC 128A",
"section_title": "Protection of Fundamental Rights in Criminal Proceedings",
"provision": "In all criminal proceedings, it shall be the duty of the presiding officer to ensure the protection of an accused person's fundamental rights as guaranteed under Part III of the Constitution of India. Any infringement of these rights during the course of investigation or trial shall render the proceedings voidable, and the court shall have the power to issue directions to remedy such infringement, including the exclusion of unlawfully obtained evidence. The court shall also provide the accused an opportunity to address any violations of their rights at the earliest possible stage of the proceedings.",
"question_type": "examples",
"question": "Can you provide an example of how CRPC 128A protects an accused person's fundamental rights during a criminal trial?",
"answer": "Certainly! For instance, if during a police investigation, evidence is obtained through coercive interrogation methods that violate the accused's right to remain silent, this would constitute an infringement of their fundamental rights. Under CRPC 128A, the presiding officer is required to ensure that such rights are protected. As a result, the court may declare the proceedings voidable and exclude the unlawfully obtained evidence from the trial. Additionally, the accused would be given an opportunity to address this violation at the earliest stage, allowing them to contest the admissibility of the evidence and uphold their rights as guaranteed under the Constitution of India."
},
{
"section_number": "IPC 372",
"section_title": "Rights and Disputes Relating to Property Ownership",
"provision": "Any individual claiming ownership of a property shall have the right to initiate a civil suit for the determination of title and possession against any person in unlawful occupation of said property. The court shall adjudicate such disputes expeditiously, ensuring that the rights of the rightful owner are protected while balancing the interests of the occupant, who may assert a claim of adverse possession or any lawful entitlement. Furthermore, in cases where property disputes arise among co-owners or joint tenants, the court shall facilitate mediation prior to proceeding to trial, promoting an amicable resolution to conflicts concerning shared property rights.",
"question_type": "definition",
"question": "What rights does an individual have under IPC 372 regarding property ownership disputes?",
"answer": "Under IPC 372, an individual claiming ownership of a property has the right to initiate a civil suit for determining title and possession against anyone unlawfully occupying the property. The court is required to adjudicate these disputes quickly, protecting the rights of the rightful owner while also considering the interests of the occupant, who may claim adverse possession or other lawful entitlements. Additionally, in disputes among co-owners or joint tenants, the court must facilitate mediation before proceeding to trial to encourage an amicable resolution."
},
{
"section_number": "IPC 499A",
"section_title": "Unauthorized Access and Data Breach",
"provision": "Whosoever, without lawful authority or consent, accesses a computer resource or computer system, and thereby obtains, alters, or destroys any data, information, or program, with the intent to cause harm or facilitate fraud, shall be punished with imprisonment for a term which may extend to three years, or with fine which may extend to five lakh rupees, or with both. In the case of repeat offenses, the term of imprisonment may extend to five years, along with a fine not exceeding ten lakh rupees.",
"question_type": "definition",
"question": "What constitutes unauthorized access and data breach under IPC 499A?",
"answer": "Unauthorized access and data breach under IPC 499A occurs when an individual, without lawful authority or consent, accesses a computer resource or system, and obtains, alters, or destroys any data, information, or program with the intent to cause harm or facilitate fraud."
},
{
"section_number": "PPR 101",
"section_title": "Rights of Co-Owners in Joint Property",
"provision": "In the event of a dispute arising between co-owners of joint property, each co-owner shall have the right to seek mediation through a designated Property Dispute Resolution Committee, established under this Act, prior to initiating any legal proceedings. The Committee shall endeavor to resolve conflicts amicably within a period of sixty days, failing which the aggrieved co-owner may file a civil suit in the appropriate jurisdiction, whereupon the court shall consider equitable distribution and rights of possession in accordance with the principles of natural justice and prior agreements among co-owners.",
"question_type": "procedure",
"question": "What steps should a co-owner take if there is a dispute regarding joint property, according to PPR 101?",
"answer": "A co-owner should first seek mediation through the designated Property Dispute Resolution Committee established under PPR 101. This mediation process must be initiated prior to any legal proceedings. The Committee will attempt to resolve the conflict amicably within sixty days. If the dispute is not resolved within this period, the aggrieved co-owner may then file a civil suit in the appropriate jurisdiction, where the court will consider equitable distribution and rights of possession based on natural justice and any prior agreements among the co-owners."
},
{
"section_number": "IPC 512",
"section_title": "Offense of Digital Harassment",
"provision": "Whosoever, through the use of electronic means, intentionally causes harm, distress, or alarm to another person by sending, sharing, or disseminating unsolicited and offensive messages, images, or videos, shall be punishable with imprisonment for a term which may extend to three years, or with fine which may extend to fifty thousand rupees, or with both. In the case of repeated offenses, the term of imprisonment may extend to five years, along with a fine not exceeding one lakh rupees. A victim of digital harassment may file a complaint with the appropriate authority, who shall take necessary action as prescribed under this section.",
"question_type": "penalty",
"question": "What are the penalties for committing the offense of digital harassment under IPC 512?",
"answer": "The penalties for committing digital harassment under IPC 512 include imprisonment for a term that may extend to three years, a fine that may extend to fifty thousand rupees, or both. In the case of repeated offenses, the imprisonment term may extend to five years, with a fine not exceeding one lakh rupees."
},
{
"section_number": "IPC 456A",
"section_title": "Unauthorized Access to Digital Systems",
"provision": "Whosoever, without lawful authority, intentionally accesses a computer or digital system with the intent to obtain or alter data, or to interfere with the integrity or functioning of such system, shall be punishable with imprisonment of either description for a term that may extend to three years, or with fine which may extend to five lakh rupees, or with both. In the case of repeated offences, the term of imprisonment may extend to five years, and the fine may be increased to ten lakh rupees.",
"question_type": "definition",
"question": "What constitutes \"unauthorized access to digital systems\" under IPC 456A?",
"answer": "\"Unauthorized access to digital systems\" under IPC 456A refers to the act of intentionally accessing a computer or digital system without lawful authority, with the intent to obtain or alter data, or to interfere with the integrity or functioning of that system."
},
{
"section_number": "CPC 204",
"section_title": "Consolidation of Civil Proceedings",
"provision": "In any suit or proceeding where multiple matters arise out of the same transaction or series of transactions and involve common questions of law or fact, the court may, upon application by any party or suo moto, consolidate such suits or proceedings for the purpose of expedience and efficiency. The court shall ensure that such consolidation does not prejudice the rights of the parties involved and shall determine the procedure for the consolidated hearing, which may include joint trials or the use of a single set of pleadings applicable to all consolidated matters.",
"question_type": "obligations",
"question": "What obligation does the court have when consolidating civil proceedings under CPC 204?",
"answer": "The court has the obligation to ensure that the consolidation of suits or proceedings does not prejudice the rights of the parties involved, and it must determine the appropriate procedure for the consolidated hearing, which may involve joint trials or a single set of pleadings applicable to all consolidated matters."
},
{
"section_number": "IPC 420A",
"section_title": "Fraudulent Misrepresentation in Commercial Transactions",
"provision": "Whosoever, with intent to deceive or defraud, makes any false representation, whether by words or conduct, in the course of a commercial transaction, shall be punished with imprisonment of either description for a term which may extend to five years, or with fine, or with both. If such misrepresentation causes loss to the victim exceeding one lakh rupees, the term of imprisonment may extend to seven years. This provision shall not apply to representations made in good faith where the individual reasonably believes such representations to be true.",
"question_type": "procedure",
"question": "What steps should a victim take to report a fraudulent misrepresentation under IPC 420A in a commercial transaction?",
"answer": "To report a fraudulent misrepresentation under IPC 420A, the victim should follow these steps:"
},
{
"section_number": "FLA 123",
"section_title": "Rights of Inheritance Among Wards and Guardians",
"provision": "In any case where a minor is a ward under the guardianship of an individual, such guardian shall have the right to manage the ward's property, but shall not have the authority to alienate or dispose of such property without prior approval from the Family Court. Upon reaching the age of majority, the ward shall inherit all properties acquired during the period of guardianship, along with any rights therein, free from any encumbrances created by the guardian without due process. The Family Court shall ensure that the interests of the minor are adequately protected during the guardianship period, with a view to preventing any potential conflicts of interest.",
"question_type": "definition",
"question": "What is the role of a guardian in managing a minor's property according to FLA 123?",
"answer": "According to FLA 123, a guardian has the right to manage a minor's property but cannot alienate or dispose of it without prior approval from the Family Court."
},
{
"section_number": "IEA 123",
"section_title": "Admissibility of Electronic Evidence",
"provision": "In any proceeding before a court, electronic evidence shall be deemed admissible if it is produced in a manner that ensures its integrity and authenticity through a secure digital signature or cryptographic verification. The party intending to introduce such evidence must provide a certificate of authenticity from a competent authority, confirming compliance with the standards set forth in the Information Technology Act, 2000. Notwithstanding the aforementioned, any electronic evidence that is deemed to have been tampered with or altered shall be inadmissible unless the party presenting the evidence can demonstrate, beyond reasonable doubt, the absence of such tampering.",
"question_type": "definition",
"question": "What is required for electronic evidence to be deemed admissible in court according to IEA 123?",
"answer": "Electronic evidence is deemed admissible in court if it is produced in a manner that ensures its integrity and authenticity through a secure digital signature or cryptographic verification. Additionally, the party introducing the evidence must provide a certificate of authenticity from a competent authority, confirming compliance with the standards of the Information Technology Act, 2000."
},
{
"section_number": "CPC 157",
"section_title": "Remedies for Breach of Contract",
"provision": "In the event of a breach of contract, the aggrieved party shall be entitled to seek remedy in the form of specific performance, damages, or rescission, as applicable. The court may award compensatory damages to cover direct losses caused by the breach, and may also consider consequential losses if such losses were within the contemplation of both parties at the time of contract formation. Furthermore, the court shall have discretion to order specific performance where monetary compensation is inadequate to provide a just remedy, particularly in cases involving unique subject matter.",
"question_type": "obligations",
"question": "What obligations does an aggrieved party have when seeking remedies for a breach of contract under CPC 157?",
"answer": "The aggrieved party is entitled to seek remedies such as specific performance, damages, or rescission, depending on the circumstances of the breach. They must demonstrate the direct losses incurred and may also claim consequential losses if those were contemplated by both parties at the time of the contract. Additionally, if seeking specific performance, the aggrieved party must show that monetary compensation is inadequate to address the situation, particularly in cases involving unique subject matter."
},
{
"section_number": "IPC 495A",
"section_title": "Offense of Deceptive Co-habitation",
"provision": "Whosoever, with intent to deceive, cohabits with a person as if married, while being lawfully married to another person, shall be punished with imprisonment for a term which may extend to five years, or with fine, or with both. The act shall be considered a cognizable offense, and in addition to punishment, the court may direct restitution for any economic or emotional harm caused to the aggrieved party. In any prosecution under this section, evidence of the accused's prior marital status shall be admissible to establish the offense.",
"question_type": "exceptions",
"question": "Are there any exceptions to the offense of Deceptive Co-habitation under IPC 495A for individuals who are legally separated from their spouse?",
"answer": "Yes, individuals who are legally separated from their spouse may not be prosecuted under IPC 495A for Deceptive Co-habitation, provided that the separation is recognized by law and they are not still legally married. However, it is important to note that evidence of their prior marital status may still be considered in court to establish the context of the cohabitation."
},
{
"section_number": "CGR 101",
"section_title": "Principles of Corporate Governance",
"provision": "Every company incorporated under the Companies Act, 2013 shall adhere to the principles of corporate governance as prescribed by the Securities and Exchange Board of India (SEBI) regulations. These principles shall include, but not be limited to, the establishment of a robust board structure, the separation of the roles of the chairperson and the managing director, and the implementation of transparent disclosure practices that uphold the rights of shareholders. Non-compliance with these principles shall attract penalties as delineated in CGR 202.",
"question_type": "procedure",
"question": "What steps must a company take to ensure compliance with the corporate governance principles as outlined in CGR 101?",
"answer": "To ensure compliance with the corporate governance principles outlined in CGR 101, a company must take the following steps:"
},
{
"section_number": "FLA 123",
"section_title": "Rights of Inheritance Among Lineal Descendants",
"provision": "In cases of intestate succession, all lineal descendants, including illegitimate offspring, shall inherit an equal share of the estate of the deceased, irrespective of the marital status of the parent at the time of birth. No distinction shall be made based on gender, and the distribution of assets shall occur in accordance with the principles of per stirpes, ensuring that each descendant receives a proportionate share of their ancestors estate. Additionally, the provisions of this section shall apply retroactively to estates of deceased individuals who died on or after January 1, 2023, thereby nullifying any pre-existing discriminatory practices in inheritance laws.",
"question_type": "rights",
"question": "What rights do lineal descendants have regarding inheritance under FLA 123, particularly for illegitimate offspring and regardless of the parent's marital status?",
"answer": "Under FLA 123, all lineal descendants, including illegitimate offspring, have the right to inherit an equal share of a deceased individual's estate in cases of intestate succession. This inheritance is granted irrespective of the parent's marital status at the time of the child's birth and without any distinction based on gender. The distribution of the estate will follow the principles of per stirpes, ensuring each descendant receives a proportionate share of their ancestors estate. These rights are retroactively applied to estates of individuals who died on or after January 1, 2023, eliminating previous discriminatory inheritance practices."
},
{
"section_number": "CRPC 145",
"section_title": "Protection of Constitutional Rights During Detention",
"provision": "No person shall be detained in police custody for a period exceeding twenty-four hours without being informed of the grounds of arrest and without being afforded the opportunity to consult a legal practitioner of their choice. Any violation of this provision shall render the detention unlawful, and the detained individual shall be entitled to immediate release and compensation as prescribed by law. The State shall ensure that all law enforcement agencies are adequately trained in upholding these constitutional protections to prevent any infringement of fundamental rights.",
"question_type": "obligations",
"question": "What obligations do law enforcement agencies have under CRPC 145 regarding the detention of individuals in police custody?",
"answer": "Under CRPC 145, law enforcement agencies are obligated to inform any detained individual of the grounds for their arrest and to provide them with the opportunity to consult a legal practitioner of their choice within twenty-four hours. Failure to comply with these obligations will render the detention unlawful, entitling the detained individual to immediate release and compensation as prescribed by law. Furthermore, the State must ensure that all law enforcement personnel are adequately trained to uphold these constitutional protections."
},
{
"section_number": "IPC 227",
"section_title": "Rights of Co-owners in Property Disputes",
"provision": "In any case where property is jointly owned by two or more individuals, each co-owner shall possess an equal right to use and enjoy the property, subject to the principle of reasonable enjoyment. No co-owner shall be entitled to alienate their share of the property without the express consent of all other co-owners; failure to obtain such consent shall render any transfer voidable at the instance of the non-consenting co-owners. In the event of a dispute arising from the exercise of such rights, the parties shall seek resolution through mediation, failing which they may pursue their claims in a competent civil court.",
"question_type": "penalty",
"question": "What penalty may arise if a co-owner attempts to alienate their share of jointly owned property without the consent of the other co-owners according to IPC 227?",
"answer": "If a co-owner attempts to alienate their share of the property without obtaining the express consent of the other co-owners, such a transfer will be rendered voidable at the request of the non-consenting co-owners. This means that the non-consenting co-owners can challenge the validity of the transfer, potentially leading to legal disputes and the need for resolution through mediation or civil court."
},
{
"section_number": "CNP 101",
"section_title": "Protection of Fundamental Rights",
"provision": "Every individual shall have the right to seek judicial redress for the infringement of their fundamental rights as enumerated in the Constitution of India. The State is mandated to ensure that no action, law, or policy contravenes the rights guaranteed under Articles 14 to 32, and any violation thereof shall entitle the aggrieved party to compensation as deemed fit by the judiciary. Furthermore, the Supreme Court and High Courts shall possess the authority to issue writs, orders, or directions for the enforcement of such rights, thereby reinforcing the foundational principles of justice and equality within the Republic.",
"question_type": "definition",
"question": "What is the right of individuals regarding the infringement of their fundamental rights as per CNP 101?",
"answer": "Individuals have the right to seek judicial redress for the infringement of their fundamental rights as outlined in the Constitution of India, and are entitled to compensation for any violations, with the Supreme Court and High Courts authorized to issue writs and orders to enforce these rights."
},
{
"section_number": "CPC 123",
"section_title": "Application for Interlocutory Relief",
"provision": "An application for interlocutory relief shall be made in the prescribed format, detailing the nature of the relief sought and the grounds thereof, along with any supporting affidavits and documents. The court shall, within three days of the filing of such application, schedule a preliminary hearing, during which the party seeking relief must demonstrate the urgency and necessity of the relief sought, based on a prima facie case and the balance of convenience. The court may grant interim orders as it deems fit, subject to the condition that the applicant shall bear the costs of any potential loss incurred by the opposing party due to such interim relief.",
"question_type": "exceptions",
"question": "Are there any exceptions to the requirement of demonstrating urgency and necessity for an application for interlocutory relief under CPC 123?",
"answer": "Yes, while CPC 123 generally requires the applicant to demonstrate urgency and necessity for the relief sought, exceptions may arise in cases where the relief requested is of a nature that inherently addresses imminent harm or where statutory provisions specifically allow for expedited procedures. However, the applicant must still adhere to the prescribed format and provide supporting affidavits and documentation as mandated by the provision."
},
{
"section_number": "IPC 507A",
"section_title": "Unauthorized Access and Data Breach",
"provision": "Whoever, without lawful authority, accesses a computer resource or a computer network with the intent to cause or knowing that he is likely to cause wrongful loss or damage to any person, or to facilitate the commission of a crime, shall be punished with imprisonment for a term which may extend to three years, or with fine which may extend to five lakh rupees, or with both. Furthermore, if such access results in the theft, alteration, or deletion of data, the offender shall be liable for enhanced penalties as prescribed in this section, including a minimum fine of ten lakh rupees and imprisonment for a term extending to five years.",
"question_type": "definition",
"question": "What constitutes unauthorized access under IPC 507A?",
"answer": "Unauthorized access under IPC 507A is defined as accessing a computer resource or a computer network without lawful authority, with the intent to cause or knowing that one is likely to cause wrongful loss or damage to any person, or to facilitate the commission of a crime."
},
{
"section_number": "IPC 512",
"section_title": "Offense of Public Disturbance",
"provision": "Whoever intentionally causes a public disturbance by engaging in acts that promote hatred, incite violence, or create fear among members of the community shall be punished with imprisonment for a term which may extend to three years, or with a fine which may extend to fifty thousand rupees, or with both. In determining the severity of the penalty, the court shall consider the magnitude of the disturbance, consequent harm caused to public order, and any prior convictions of the offender under this section or similar offenses.",
"question_type": "definition",
"question": "What constitutes the offense of public disturbance under IPC 512?",
"answer": "The offense of public disturbance under IPC 512 is constituted by intentionally causing a public disturbance through acts that promote hatred, incite violence, or create fear among community members."
},
{
"section_number": "IPC 123A",
"section_title": "Protection of Indigenous Knowledge and Cultural Expressions",
"provision": "Whoever unlawfully appropriates, uses, or commercializes indigenous knowledge and cultural expressions without obtaining prior informed consent from the relevant indigenous communities, shall be punished with imprisonment for a term not exceeding five years, or with fine, or both. The term \"indigenous knowledge\" includes traditional practices, innovations, and expressions inherent to indigenous communities, and any such appropriation shall be considered a violation of the community's moral rights as custodians of their cultural heritage. The provisions of this section shall be in addition to any other rights or remedies available under existing intellectual property laws.",
"question_type": "obligations",
"question": "What obligations do individuals have regarding the use of indigenous knowledge and cultural expressions according to IPC 123A?",
"answer": "Individuals are obligated to obtain prior informed consent from the relevant indigenous communities before appropriating, using, or commercializing indigenous knowledge and cultural expressions. Failure to comply with this obligation may result in imprisonment for up to five years, a fine, or both, as it constitutes a violation of the moral rights of the indigenous communities as custodians of their cultural heritage."
},
{
"section_number": "IEA 127",
"section_title": "Admissibility of Digital Evidence",
"provision": "Notwithstanding any provisions to the contrary, electronic records shall be admissible as evidence in any judicial proceedings, provided that such records are generated, stored, and retrieved in a manner that ensures their authenticity and integrity. The party seeking to introduce such evidence shall bear the burden of establishing its reliability through appropriate certification or corroborative witness testimony, unless the opposing party concedes to the admissibility of the digital evidence. The court may, at its discretion, allow for the examination of the digital evidence to ascertain its relevance and evidentiary value.",
"question_type": "rights",
"question": "What rights do parties have regarding the admissibility of digital evidence in judicial proceedings under IEA 127?",
"answer": "Under IEA 127, parties have the right to introduce electronic records as evidence in court, provided they can demonstrate the records' authenticity and integrity. The party presenting the digital evidence has the responsibility to prove its reliability, either through certification or witness testimony. Additionally, if the opposing party does not contest the admissibility, the court may accept the evidence without further scrutiny. The court also has the discretion to examine the digital evidence to determine its relevance and evidentiary value."
},
{
"section_number": "IPC 456",
"section_title": "Offense of Public Disorder",
"provision": "Whoever, with the intent to cause public alarm or disturbance, engages in behavior that incites violence, fear, or panic among the general populace shall be punishable with imprisonment of either description for a term which may extend to five years, or with fine which may extend to ten thousand rupees, or with both. In determining the sentence, the court shall take into account the nature and extent of the disruption caused, and any prior offenses committed by the accused.",
"question_type": "exceptions",
"question": "Are there any exceptions to the offense of public disorder under IPC 456 for individuals who engage in behavior that might cause alarm but do so for a legitimate purpose, such as public safety or awareness?",
"answer": "Yes, there may be exceptions for individuals who engage in behavior that could cause public alarm or disturbance if their actions are intended for a legitimate purpose, such as ensuring public safety or raising awareness about a critical issue. The court will consider the intent behind the behavior and the context in which it occurred when determining if it constitutes an offense under IPC 456. However, the burden of proof lies with the accused to demonstrate that their actions were justified and not intended to incite violence, fear, or panic."
},
{
"section_number": "IPC 123A",
"section_title": "Protection of Digital Copyright",
"provision": "Any person who, without the authorization of the copyright owner, reproduces, distributes, or publicly displays a copyrighted digital work in a manner that enables unlawful access or download by a third party shall be punishable with imprisonment for a term which may extend to three years, or with fine which may extend to five lakh rupees, or with both. The courts shall consider the nature of the work, the scale of distribution, and the intent behind the infringement while determining the appropriate penalty. This provision shall not apply to fair use as delineated under the Copyright Act, 1957.",
"question_type": "procedure",
"question": "What steps should a copyright owner take if they believe their digital work has been reproduced or distributed without authorization under IPC 123A?",
"answer": "If a copyright owner suspects unauthorized reproduction or distribution of their digital work under IPC 123A, they should follow these steps:"
},
{
"section_number": "CPC 207",
"section_title": "Application for Interim Relief",
"provision": "In any suit where a party seeks urgent relief based on a prima facie showing of entitlement, the Court may, upon application, grant interim relief to maintain the status quo pending final adjudication. Such application shall be accompanied by an affidavit detailing the grounds for urgency, and the Court shall endeavor to hear and dispose of such application within seven days of filing, unless shown to be impracticable. The order granting or denying interim relief shall be recorded with reasons and shall be subject to the right of appeal under the provisions of this Code.",
"question_type": "procedure",
"question": "What is the procedure for applying for interim relief under CPC 207, and what are the requirements for the application to be considered by the Court?",
"answer": "To apply for interim relief under CPC 207, a party must file an application demonstrating a prima facie showing of entitlement to urgent relief. This application must be accompanied by an affidavit that details the grounds for urgency. The Court is required to hear and dispose of the application within seven days of filing, unless it is impracticable to do so. The Court will then issue an order granting or denying the interim relief, which must be recorded along with the reasons for the decision. Additionally, the order is subject to the right of appeal as outlined in this Code."
},
{
"section_number": "IPC 456",
"section_title": "Criminal Intimidation with Intent to Cause Harm",
"provision": "Whoever, with intent to cause harm or alarm, threatens any person with the infliction of death or grievous hurt, or with the destruction of property, shall be punished with imprisonment of either description for a term which may extend to three years, or with fine which may extend to fifty thousand rupees, or with both. If the offense is committed in furtherance of an organized criminal activity, the term of imprisonment may extend to five years.",
"question_type": "examples",
"question": "Can you provide examples of actions that would be considered criminal intimidation under IPC 456?",
"answer": "Yes, under IPC 456, examples of criminal intimidation include a person threatening to kill another individual if they do not pay a debt, or someone warning a neighbor that they will set fire to their property if they do not comply with certain demands. Additionally, if a group engages in organized criminal activity and threatens individuals with serious harm or property destruction to enforce their control, that would also fall under this provision, potentially leading to a longer imprisonment term."
},
{
"section_number": "IEA 115",
"section_title": "Admissibility of Electronic Records in Civil Proceedings",
"provision": "Notwithstanding any provisions to the contrary, electronic records shall be admissible in civil proceedings as evidence, provided that such records are accompanied by a certificate of authenticity attesting to their integrity and accuracy. The court may, at its discretion, take into account the reliability of the technology used in the creation, storage, and retrieval of these records, as well as any potential alterations that may have occurred. Further, any party seeking to introduce electronic records must notify the opposing party at least seven days prior to the hearing, allowing for adequate preparation to challenge the admissibility of such evidence.",
"question_type": "penalty",
"question": "What are the potential consequences for a party that fails to notify the opposing party at least seven days prior to a hearing when intending to introduce electronic records as evidence under IEA 115?",
"answer": "If a party fails to provide the required seven-day notice before introducing electronic records in a civil proceeding, the court may deem the electronic records inadmissible as evidence. This could hinder the party's ability to substantiate their claims or defenses, potentially resulting in unfavorable outcomes in the case."
},
{
"section_number": "IPC 134A",
"section_title": "Remedies for Breach of Contract",
"provision": "In the event of a breach of contract, the aggrieved party shall be entitled to pursue remedies as defined herein: (1) Specific performance of the contract may be ordered by a competent court where monetary damages are inadequate to remedy the loss suffered. (2) In cases where the breach is wilful and unexcused, the aggrieved party may also claim punitive damages not exceeding fifty percent of the actual damages incurred. (3) Parties may further stipulate in the contract provisions for liquidated damages, which shall be enforceable unless deemed unconscionable by the court.",
"question_type": "definition",
"question": "What remedies are available to an aggrieved party in the event of a breach of contract according to IPC 134A?",
"answer": "According to IPC 134A, the remedies available to an aggrieved party in the event of a breach of contract include: (1) specific performance of the contract when monetary damages are inadequate, (2) punitive damages not exceeding fifty percent of the actual damages if the breach is wilful and unexcused, and (3) liquidated damages as stipulated in the contract, which are enforceable unless deemed unconscionable by the court."
},
{
"section_number": "CNR 101",
"section_title": "Protection of Fundamental Rights",
"provision": "Every individual shall have the right to life, liberty, and personal security, which shall be inviolable and protected against arbitrary deprivation by the State. The State shall ensure that any infringement of these rights is subject to judicial review, and appropriate remedies shall be provided to individuals whose rights have been violated. The Parliament shall enact necessary legislation to define, safeguard, and enforce these rights, ensuring that no law or action contravenes the spirit of this provision without just cause.",
"question_type": "definition",
"question": "What fundamental rights are protected under CNR 101, and what obligations does the State have regarding these rights?",
"answer": "Under CNR 101, every individual is guaranteed the rights to life, liberty, and personal security, which are inviolable and protected against arbitrary deprivation by the State. The State is obligated to ensure that any infringement of these rights is subject to judicial review and must provide appropriate remedies to individuals whose rights have been violated. Additionally, the Parliament is required to enact necessary legislation to define, safeguard, and enforce these rights, ensuring no law or action contradicts this provision without just cause."
},
{
"section_number": "IPC 124A",
"section_title": "Protection of Unregistered Intellectual Property Rights",
"provision": "Any individual or entity claiming ownership of an unregistered intellectual property right, including but not limited to trade secrets, designs, and innovations, shall be entitled to seek legal remedy for unauthorized use or disclosure. The aggrieved party may file a civil suit in the appropriate jurisdiction, whereupon the court shall assess the validity of the claimed rights and may grant injunctive relief, damages, or any other relief deemed appropriate to prevent infringement and preserve the integrity of the intellectual property. This protection shall extend to the duration of the claimant's reasonable efforts to maintain the confidentiality and exclusivity of the intellectual property in question.",
"question_type": "definition",
"question": "What rights are protected under IPC 124A regarding unregistered intellectual property, and what legal remedies are available to the aggrieved party?",
"answer": "IPC 124A protects unregistered intellectual property rights, including trade secrets, designs, and innovations. An individual or entity claiming ownership may seek legal remedies for unauthorized use or disclosure by filing a civil suit in the appropriate jurisdiction. The court will assess the validity of the claimed rights and may grant injunctive relief, damages, or other appropriate remedies to prevent infringement and maintain the integrity of the intellectual property."
},
{
"section_number": "CL 204",
"section_title": "Remedies for Breach of Contract",
"provision": "In the event of a breach of contract, the aggrieved party shall be entitled to seek remedies including specific performance, rescission of the contract, and damages, which may be either general or consequential in nature. The party seeking damages must provide clear evidence of loss incurred as a result of the breach, and the court shall have discretion to award compensation that is deemed just and equitable, taking into account the nature of the breach and the contractual terms. Furthermore, any limitation on the right to claim damages must be explicitly stated within the contract to be enforceable.",
"question_type": "examples",
"question": "Can you provide examples of the types of remedies available for breach of contract as outlined in CL 204?",
"answer": "Yes, under CL 204, there are several remedies available for breach of contract. For instance, specific performance may be sought if the aggrieved party wants the breaching party to fulfill their contractual obligations, such as delivering a unique piece of art that was promised. Rescission of the contract could be an option if the aggrieved party wishes to cancel the contract entirely and return to their pre-contractual position, for example, if a buyer discovers that a seller misrepresented the condition of a property. Additionally, damages can be claimed, which may include general damages for direct losses, like the cost of hiring a substitute contractor after the original contractor failed to perform, or consequential damages, such as loss of business profits resulting from the delay in project completion due to the breach. It is important to note that the party seeking damages must provide evidence of the loss incurred, and any limitations on claiming damages must be clearly stated in the contract to be enforceable."
},
{
"section_number": "CPC 123A",
"section_title": "Provision for Electronic Filing of Pleadings",
"provision": "The Court may, upon application by any party, permit the electronic filing of pleadings, documents, and evidence in accordance with the guidelines issued by the Supreme Court. Such electronic submissions shall be deemed to be authentic and shall hold the same legal sanctity as original physical documents, provided that such filings comply with the prescribed digital signature requirements and are submitted within the timelines set forth by the Court. Any failure to comply with the electronic filing protocols may result in the rejection of the documents filed or the imposition of penalties as deemed appropriate by the presiding judge.",
"question_type": "examples",
"question": "Can you provide an example of a situation where a party might utilize electronic filing of pleadings according to CPC 123A?",
"answer": "Sure! For instance, if a plaintiff wishes to file a motion for summary judgment, they can submit their pleading electronically if they apply to the Court and receive permission. They must ensure their electronic submission adheres to the Supreme Court's guidelines, including using a valid digital signature and submitting it by the court's deadline. If they fail to meet these requirements, the court may reject their filing or impose penalties."
},
{
"section_number": "IEA 78",
"section_title": "Admissibility of Electronic Evidence",
"provision": "Notwithstanding any provision to the contrary, electronic evidence shall be admissible in judicial proceedings provided that such evidence is authenticated through a digital signature, or corroborated by a competent testimony that verifiably establishes its integrity and relevance to the matter in issue. The court may, in its discretion, require the production of the original electronic device or system from which the evidence is derived to determine its authenticity, unless such requirement is waived by mutual consent of the parties involved.",
"question_type": "procedure",
"question": "What steps must a party take to ensure that electronic evidence is admissible in judicial proceedings according to IEA 78?",
"answer": "To ensure that electronic evidence is admissible under IEA 78, a party must authenticate the evidence either through a digital signature or by providing corroborating testimony from a competent witness that verifies the evidence's integrity and relevance to the case. Additionally, the party may need to produce the original electronic device or system from which the evidence was obtained, unless this requirement is waived by mutual consent of the parties involved."
},
{
"section_number": "IEA 67A",
"section_title": "Admissibility of Digital Evidence",
"provision": "Notwithstanding any provisions to the contrary, any electronic record or digital evidence shall be admissible in a court of law, provided that the party seeking to introduce such evidence demonstrates the authenticity and integrity of the record through a reliable digital signature or encryption method. The court may, at its discretion, require further corroborative evidence to substantiate the reliability of the digital evidence presented, ensuring that the probative value outweighs any potential prejudicial effect.",
"question_type": "procedure",
"question": "What steps must a party take to ensure the admissibility of digital evidence in court according to IEA 67A?",
"answer": "To ensure the admissibility of digital evidence in court under IEA 67A, the party seeking to introduce the evidence must demonstrate the authenticity and integrity of the electronic record by using a reliable digital signature or encryption method. Additionally, the court may require further corroborative evidence to confirm the reliability of the digital evidence, ensuring that its probative value outweighs any potential prejudicial effect."
},
{
"section_number": "IPC 798",
"section_title": "Protection of Traditional Knowledge",
"provision": "Any person who, without lawful authority, uses, reproduces, or distributes traditional knowledge as defined under this Act, shall be liable for infringement of intellectual property rights. Such traditional knowledge shall include but not be limited to, cultural practices, medicinal formulations, or agricultural methods passed down through generations within indigenous communities. The affected community shall have the right to seek remedies, including injunctions and damages, in accordance with the provisions set forth in this section.",
"question_type": "examples",
"question": "Can you provide examples of actions that would infringe on traditional knowledge as per IPC 798?",
"answer": "Yes, examples of actions that would infringe on traditional knowledge under IPC 798 include:"
},
{
"section_number": "IPC 123A",
"section_title": "Protection of Unregistered Trade Secrets",
"provision": "Whosoever, in the course of trade or business, unlawfully discloses or uses a trade secret or confidential commercial information obtained through breach of a duty of confidentiality, shall be punishable with imprisonment for a term which may extend to three years or with fine, or with both. For the purposes of this section, \"trade secret\" shall mean any formula, pattern, compilation, program, device, method, technique, or process that derives independent economic value from not being generally known to or readily accessible by others who can obtain economic value from its disclosure or use. The burden of proof regarding the confidentiality of such information shall lie upon the claimant.",
"question_type": "obligations",
"question": "What obligations do individuals have regarding the disclosure of trade secrets under IPC 123A?",
"answer": "Individuals are obligated not to unlawfully disclose or use any trade secret or confidential commercial information that they have obtained through a breach of a duty of confidentiality. If they fail to uphold this obligation, they may face penalties including imprisonment for up to three years, a fine, or both. Additionally, the claimant has the burden of proof to demonstrate that the information in question is confidential."
},
{
"section_number": "FLA 102",
"section_title": "Rights of Inheritance for Female Heirs",
"provision": "In the event of the demise of a male intestate, female heirs, including daughters and widows, shall have an equal right to inherit the estate of the deceased on par with male heirs. The distribution of such inheritance shall be executed in accordance with the principles of equitable division, ensuring that each female heir receives a share that is not less than one-fourth of the total estate, unless expressly disclaimed by the heir in a legally binding written document. This section aims to uphold gender equality in matters of familial succession and inheritance rights under Hindu, Muslim, and other applicable personal laws in India.",
"question_type": "penalty",
"question": "What are the penalties for failing to adhere to the inheritance rights outlined in FLA 102 regarding female heirs?",
"answer": "While FLA 102 does not specify penalties within the provision itself, failure to comply with the equitable division of the estate as mandated can lead to legal action by female heirs. This may result in the court enforcing the rightful distribution of the estate, which could include the imposition of fines or other sanctions against the estates executors or those responsible for the distribution, as determined by the applicable legal framework."
},
{
"section_number": "IPC 123A",
"section_title": "Protection of Indigenous Knowledge and Cultural Expressions",
"provision": "Any person who unlawfully appropriates, reproduces, or disseminates indigenous knowledge or cultural expressions without the prior consent of the indigenous community shall be liable for infringement of intellectual property rights under this section. The aggrieved indigenous community may seek remedies including injunctions and damages, and the court shall consider the cultural significance and traditional practices associated with such knowledge in its deliberations. This provision aims to safeguard the heritage and intellectual contributions of indigenous communities against unauthorized exploitation.",
"question_type": "penalty",
"question": "What penalties can a person face for unlawfully appropriating indigenous knowledge or cultural expressions under IPC 123A?",
"answer": "A person who unlawfully appropriates, reproduces, or disseminates indigenous knowledge or cultural expressions without the prior consent of the indigenous community may be liable for infringement of intellectual property rights. The aggrieved indigenous community can seek remedies such as injunctions to prevent further infringement and damages for any losses incurred. The court will also consider the cultural significance and traditional practices associated with the knowledge in its decisions."
},
{
"section_number": "FLA 101",
"section_title": "Rights of Inheritance Among Hindu Succession",
"provision": "In the event of the demise of a Hindu individual, the property held by such individual, whether ancestral or self-acquired, shall devolve upon their legal heirs as defined under this Act, in accordance with the principles of equal partition among male and female heirs. The widow and children of the deceased shall inherit a minimum of one-third of the total estate, notwithstanding any prior testamentary disposition made by the deceased, unless expressly waived in writing by the heirs prior to the individual's death. The provisions herein shall apply irrespective of the religious or customary practices governing succession, aiming to uphold gender equality in inheritance rights.",
"question_type": "rights",
"question": "What rights do the widow and children of a deceased Hindu individual have regarding inheritance under the Hindu Succession Act?",
"answer": "The widow and children of a deceased Hindu individual are entitled to inherit a minimum of one-third of the total estate, regardless of any prior testamentary disposition made by the deceased. This right is upheld under the Act to ensure gender equality in inheritance, and it applies to both ancestral and self-acquired property."
},
{
"section_number": "IPC 420B",
"section_title": "Fraudulent Misrepresentation in Commercial Transactions",
"provision": "Whosoever, with intent to deceive, misrepresents a material fact regarding goods or services in the course of any commercial transaction, thereby causing financial loss to another party, shall be punished with imprisonment for a term which may extend to three years, or with fine which may extend to fifty thousand rupees, or with both. Explanation: For the purposes of this section, \"material fact\" shall mean any fact that, if known, would likely affect the decision of a reasonable person to enter into the transaction.",
"question_type": "exceptions",
"question": "Are there any exceptions under IPC 420B where a party may not be held liable for fraudulent misrepresentation in commercial transactions?",
"answer": "Yes, an exception under IPC 420B may apply if the misrepresentation was made without the intent to deceive, such as in cases where the party genuinely believed the information provided to be true, or if the misrepresentation pertains to opinions or predictions rather than material facts. Additionally, if the party can demonstrate that the other party had prior knowledge of the facts or waived their right to rely on the misrepresentation, liability may not be established under this section."
},
{
"section_number": "IPC 432",
"section_title": "Protection of Trade Secrets and Confidential Information",
"provision": "Whoever unlawfully discloses, acquires, or uses a trade secret or any confidential information belonging to another party, without the express consent of the owner, shall be punishable with imprisonment for a term which may extend to three years, or with fine, or with both. For the purposes of this section, \"trade secret\" shall include any formula, practice, process, design, instrument, pattern, or compilation of information that is not generally known or reasonably ascertainable by others and that provides a competitive advantage to the owner. The provisions of this section shall not apply to disclosures made under compulsion of law or in the course of legitimate business practices.",
"question_type": "exceptions",
"question": "What are the exceptions to the provisions of IPC 432 regarding the unlawful disclosure of trade secrets and confidential information?",
"answer": "The provisions of IPC 432 do not apply to disclosures made under compulsion of law or in the course of legitimate business practices. This means that if a person is legally required to disclose trade secrets or if the disclosure occurs as part of lawful business operations, they are exempt from the penalties outlined in this section."
},
{
"section_number": "IPC 123A",
"section_title": "Rights Pertaining to Inherited Property",
"provision": "In cases where property is inherited, any disputes arising among heirs regarding the rightful ownership, partition, or claim over such property shall be resolved in accordance with the principles of ancestral succession as defined under this Code. Any party claiming a right to the inherited property must provide substantial evidence of lineage and lawful entitlement, failing which the claim shall be deemed invalid. Furthermore, the court shall have the authority to appoint a mediator to facilitate negotiation among parties prior to adjudication, encouraging amicable settlements while safeguarding the rights of all claimants.",
"question_type": "rights",
"question": "What rights do heirs have regarding inherited property disputes under IPC 123A?",
"answer": "Heirs have the right to resolve disputes over inherited property ownership, partition, or claims according to the principles of ancestral succession. However, any heir claiming a right to the property must provide substantial evidence of their lineage and lawful entitlement. If they fail to do so, their claim will be considered invalid. Additionally, the court can appoint a mediator to help facilitate negotiations among the parties, promoting amicable settlements while protecting the rights of all claimants."
},
{
"section_number": "IEA 145",
"section_title": "Admissibility of Digital Evidence",
"provision": "Notwithstanding any provision to the contrary, digital evidence shall be admissible in any judicial proceeding if it is accompanied by a certificate of authenticity issued by a competent authority, attesting to the integrity, reliability, and original source of the data. The court shall evaluate the probative value of such evidence, taking into consideration the methods of collection, preservation, and transmission, along with any potential alterations, before determining its admissibility. In cases where digital evidence is presented, the burden of proof shall rest upon the party introducing such evidence to establish its authenticity and relevance.",
"question_type": "definition",
"question": "What is required for digital evidence to be admissible in judicial proceedings according to IEA 145?",
"answer": "Digital evidence is admissible in judicial proceedings if it is accompanied by a certificate of authenticity issued by a competent authority, which attests to the integrity, reliability, and original source of the data. Additionally, the court will evaluate its probative value considering the methods of collection, preservation, and transmission, as well as any potential alterations, and the party introducing the evidence must prove its authenticity and relevance."
},
{
"section_number": "IPC 509A",
"section_title": "Criminal Intimidation by Means of Digital Platforms",
"provision": "Whosoever, by means of any electronic, digital, or computer-based communication, threatens or causes harm to any person, including but not limited to threats of violence, coercion, or defamation, shall be punished with imprisonment for a term which may extend to three years, or with fine, or with both. In the case of aggravated circumstances, such as the use of multiple accounts or persistent harassment, the punishment may extend to five years of imprisonment. The provisions of this section shall be in addition to any other applicable laws concerning harassment or intimidation.",
"question_type": "examples",
"question": "Can you provide examples of actions that would be considered criminal intimidation under IPC 509A?",
"answer": "Yes, several actions can be considered criminal intimidation under IPC 509A. For instance, if an individual sends threatening messages via social media platforms, such as threatening physical harm or coercing someone into doing something against their will, this would qualify. Additionally, if someone uses multiple online accounts to continuously harass another person by spreading false information or defamatory statements about them, this would also fall under the provisions of IPC 509A. Lastly, if a person creates a fake profile to intimidate or threaten someone, this too would be punishable under this section."
},
{
"section_number": "IPC 471A",
"section_title": "Protection of Trade Secrets",
"provision": "Whoever unlawfully obtains, discloses, or uses a trade secret, knowing or having reason to know that such information was obtained through improper means, shall be punishable with imprisonment for a term which may extend to three years, or with fine which may extend to five lakh rupees, or with both. A trade secret shall be defined as any formula, pattern, compilation, program, device, method, technique, or process that derives independent economic value from not being generally known or readily ascertainable to the public, and is the subject of reasonable efforts to maintain its secrecy.",
"question_type": "exceptions",
"question": "Are there any exceptions under IPC 471A for disclosing or using trade secrets if the information is obtained through lawful means?",
"answer": "Yes, IPC 471A pertains specifically to the unlawful obtaining, disclosing, or using of trade secrets. If a person acquires a trade secret through lawful means, such as independent discovery or legitimate access, they would not be punishable under this provision. Additionally, if the trade secret becomes publicly known or is disclosed as a result of legal obligations, such as during a court proceeding, those actions may also fall outside the scope of punishment under this section."
},
{
"section_number": "IEA 65A",
"section_title": "Admissibility of Digital Evidence",
"provision": "Notwithstanding any provision to the contrary, digital evidence, including but not limited to electronic records, audio and video files, and data stored in digital devices, shall be admissible in any proceedings if such evidence is accompanied by a certificate from a competent authority confirming the integrity and authenticity of the data. The court may, however, require additional corroborative evidence to substantiate the claims made through such digital materials, ensuring that the principles of fairness and justice are upheld.",
"question_type": "examples",
"question": "Can you provide examples of digital evidence that would be admissible in court under IEA 65A if accompanied by the proper certification?",
"answer": "Yes, examples of digital evidence that would be admissible in court under IEA 65A include electronic records such as emails or digital contracts, audio files like recorded conversations relevant to the case, video files such as surveillance footage, and data stored on digital devices like smartphones or computers, provided that each piece of evidence is accompanied by a certificate from a competent authority verifying its integrity and authenticity. The court may still request additional corroborative evidence to ensure fairness in the proceedings."
},
{
"section_number": "IPC 507A",
"section_title": "Unauthorized Access and Data Breach",
"provision": "Whoever, without lawful authority or consent, intentionally accesses a computer system or network and obtains, alters, or deletes data shall be punished with imprisonment for a term which may extend to three years, or with fine which may extend to fifty thousand rupees, or with both. In case the unauthorized access results in harm or loss to any person or entity, the term of imprisonment may extend to five years, along with a fine which may be determined by the court based on the gravity of the harm caused.",
"question_type": "rights",
"question": "What rights do individuals have if they are victims of unauthorized access and data breaches under IPC 507A?",
"answer": "Individuals who are victims of unauthorized access and data breaches have the right to seek legal recourse against the perpetrator. They can report the incident to law enforcement, and if the unauthorized access results in harm or loss, they have the right to pursue compensation for damages through the court system. Additionally, they can expect that the law provides for penalties against the offender, which may include imprisonment and fines, thereby reinforcing their rights to safety and protection of their personal data."
},
{
"section_number": "FLA 123",
"section_title": "Rights of Inheritance Among Heirs",
"provision": "In cases of intestate succession, all heirs shall inherit the estate of the deceased in accordance with the principles of equitable distribution, wherein the surviving spouse shall receive one-half of the estate, while the remaining half shall be divided equally among the legitimate children. In the absence of legitimate children, the estate shall pass to the surviving parents, and if none exist, to the siblings in equal shares. The court shall ensure that the rights of all heirs are protected, preventing any testamentary disposition that contravenes the provisions set forth herein.",
"question_type": "exceptions",
"question": "Are there any exceptions to the equitable distribution of the estate among heirs as outlined in FLA 123?",
"answer": "Yes, exceptions exist where a testamentary disposition may override the standard distribution if it is legally valid and does not contravene the protections established in FLA 123. Additionally, if the deceased has left behind a valid will that specifies different distributions, those instructions may take precedence over intestate succession rules, provided they comply with relevant legal standards. However, the court will still ensure that no such disposition infringes on the rights of the heirs as defined in the provision."
},
{
"section_number": "IPC 482",
"section_title": "Rights of Co-Owners in Joint Property",
"provision": "In any case where two or more persons are co-owners of an immovable property, no co-owner shall alienate their share of the property without the consent of the other co-owners, unless otherwise stipulated by a prior agreement. In the event of a dispute regarding the use or management of the joint property, any co-owner may apply to the appropriate civil court for a partition of the property, which shall be conducted in accordance with the principles of equity and justice, ensuring that the rights of all parties are duly considered.",
"question_type": "obligations",
"question": "What obligation do co-owners of immovable property have regarding the alienation of their shares according to IPC 482?",
"answer": "Co-owners of immovable property are obligated not to alienate their share without the consent of the other co-owners, unless there is a prior agreement that stipulates otherwise."
},
{
"section_number": "IEA 75",
"section_title": "Admissibility of Digital Evidence",
"provision": "Digital evidence shall be admissible in any judicial proceedings if it is authenticated by the party presenting it, demonstrating its integrity and reliability. The court shall consider the methods of collection, preservation, and presentation of such evidence, and may require corroboration from independent sources when the authenticity is contested. Any digital evidence obtained in violation of fundamental rights as enshrined in the Constitution shall be deemed inadmissible.",
"question_type": "definition",
"question": "What is the criterion for the admissibility of digital evidence in judicial proceedings according to IEA 75?",
"answer": "Digital evidence is admissible in judicial proceedings if it is authenticated by the presenting party, demonstrating its integrity and reliability, while the court considers the methods of collection, preservation, and presentation. Additionally, such evidence must not violate fundamental rights as outlined in the Constitution, or it will be deemed inadmissible."
},
{
"section_number": "CTP 101",
"section_title": "Protection of Fundamental Rights",
"provision": "Every individual shall have the right to seek redress for any violation of their fundamental rights as enshrined in the Constitution, through an expedited process in the appropriate constitutional court. The court shall ensure that any infringement of these rights is addressed promptly and judiciously, and may grant interim relief to safeguard the affected individual's rights during the pendency of the proceedings. Furthermore, any public authority found to have acted in contravention of these rights shall be liable to compensate the aggrieved party, as determined by the court.",
"question_type": "examples",
"question": "Can you provide an example of a situation where an individual might seek redress for a violation of their fundamental rights under CTP 101?",
"answer": "An example of such a situation could be if a government agency unlawfully detains an individual without due process, violating their right to liberty. The individual can seek redress in the appropriate constitutional court under CTP 101. They may file a petition to have their detention reviewed and potentially obtain interim relief, such as being released from detention while the case is pending. If the court finds that their fundamental rights were indeed violated, the government agency may be ordered to compensate the individual for the unlawful detention."
},
{
"section_number": "CPC 405",
"section_title": "Case Management Conference",
"provision": "The Court shall, upon the filing of the first written statement or counterclaim, schedule a Case Management Conference within thirty days to facilitate the expeditious resolution of disputes. At this conference, the parties shall be required to outline their claims and defenses, discuss the possibility of settlement, and establish a timeline for the exchange of evidence and subsequent proceedings, ensuring that the principles of justice and efficiency are upheld. Non-compliance with the directives issued during the conference may result in the imposition of sanctions as deemed appropriate by the Court.",
"question_type": "definition",
"question": "What is a Case Management Conference as defined in CPC 405?",
"answer": "A Case Management Conference is a court-scheduled meeting that occurs within thirty days of filing the first written statement or counterclaim, aimed at facilitating the expeditious resolution of disputes. During this conference, parties outline their claims and defenses, discuss settlement possibilities, and establish a timeline for evidence exchange and further proceedings, with the goal of upholding justice and efficiency."
},
{
"section_number": "IPC 543",
"section_title": "Offense of Cyber Harassment",
"provision": "Whosoever, by means of a computer resource or communication device, intentionally engages in conduct that causes harm, alarm, or distress to another person, including but not limited to the transmission of offensive messages, threats, or repeated unwanted communications, shall be punished with imprisonment for a term which may extend to three years, or with fine which may extend to fifty thousand rupees, or with both. In the case of a subsequent offense under this section, the term of imprisonment may extend to five years.",
"question_type": "obligations",
"question": "What obligations do individuals have under IPC 543 regarding the use of computer resources or communication devices to avoid cyber harassment?",
"answer": "Individuals are obligated to refrain from intentionally engaging in conduct that could cause harm, alarm, or distress to others through the use of computer resources or communication devices. This includes avoiding the transmission of offensive messages, threats, or repeated unwanted communications, as doing so may result in legal consequences, including imprisonment or fines."
},
{
"section_number": "PRD 102",
"section_title": "Rights and Remedies in Property Disputes",
"provision": "In any dispute concerning immovable property, the aggrieved party may file a complaint before the designated Property Dispute Tribunal, which shall have exclusive jurisdiction to adjudicate such matters. The Tribunal shall issue a preliminary order within fifteen days of receiving the complaint, and if necessary, appoint a local commissioner to inspect the property and submit a report, thereby ensuring swift resolution and enforcement of rights. Any party dissatisfied with the Tribunal's decision may appeal to the High Court within sixty days from the date of the order, provided that the appeal is accompanied by a certified copy of the original order.",
"question_type": "rights",
"question": "What rights does an aggrieved party have in a property dispute according to PRD 102?",
"answer": "An aggrieved party in a property dispute has the right to file a complaint before the designated Property Dispute Tribunal, which has exclusive jurisdiction over such matters. They are entitled to a preliminary order within fifteen days and may have a local commissioner appointed for property inspection if necessary. Additionally, if they are dissatisfied with the Tribunal's decision, they have the right to appeal to the High Court within sixty days, provided they include a certified copy of the original order."
},
{
"section_number": "FLA 202",
"section_title": "Inheritance Rights of Children Born Out of Wedlock",
"provision": "Notwithstanding any other law to the contrary, a child born out of wedlock shall have the same rights of inheritance as a legitimate child in the estate of the biological parents, provided that paternity is established through a legally recognized process. The child shall have the right to claim a share in the ancestral property of the biological father's family, subject to the provisions of the Hindu Succession Act, 1956, or the applicable personal law of the parents. Any clause in a will or testament that seeks to exclude such a child from inheritance based solely on their illegitimacy shall be deemed void and unenforceable.",
"question_type": "obligations",
"question": "What obligations do biological parents have regarding the inheritance rights of a child born out of wedlock according to FLA 202?",
"answer": "Biological parents are obligated to ensure that a child born out of wedlock is granted the same inheritance rights as a legitimate child, provided that paternity is established through a legally recognized process. This includes the obligation to allow the child to claim a share in the ancestral property of the biological father's family, and any will or testament that attempts to exclude the child based solely on their illegitimacy is rendered void and unenforceable."
},
{
"section_number": "IPC 890",
"section_title": "Protection of Traditional Knowledge",
"provision": "Any person who utilizes traditional knowledge for commercial gain without the explicit consent of the community possessing such knowledge shall be liable for infringement of intellectual property rights. The aggrieved community may seek redress through civil courts for remedies including injunctions, damages, and the recognition of their rights as custodians of such knowledge. This provision aims to safeguard the cultural heritage of indigenous populations against unauthorized appropriation and exploitation.",
"question_type": "rights",
"question": "What rights do communities have under IPC 890 regarding the use of their traditional knowledge for commercial purposes?",
"answer": "Communities have the right to give explicit consent before their traditional knowledge is used for commercial gain. If their knowledge is utilized without consent, they can seek redress in civil courts for remedies such as injunctions, damages, and recognition of their rights as custodians of that knowledge, thereby protecting their cultural heritage from unauthorized appropriation and exploitation."
},
{
"section_number": "FLA 202",
"section_title": "Rights of Inheritance in Hindu Joint Families",
"provision": "In any Hindu joint family, the property acquired by any member through self-acquisition shall devolve upon all coparceners equally upon the demise of the said member, unless a valid testamentary instrument expressly disposes of such property. Furthermore, any coparcener may renounce their right to inherit by a written declaration made in the presence of two witnesses, thereby forfeiting their claim to such property in favor of the remaining coparceners. The provisions of this section shall apply notwithstanding any customary practices that may contravene the equal sharing of self-acquired property within the familial structure.",
"question_type": "obligations",
"question": "What are the obligations of a coparcener in a Hindu joint family regarding the inheritance of self-acquired property upon the demise of a member?",
"answer": "Upon the demise of a member in a Hindu joint family, the obligation of all coparceners is to equally share the self-acquired property of the deceased member, unless there is a valid testamentary instrument that specifies a different distribution. Additionally, any coparcener has the obligation to formally renounce their right to inherit by providing a written declaration in the presence of two witnesses, which will forfeit their claim to the property in favor of the remaining coparceners."
},
{
"section_number": "IPC 123A",
"section_title": "Rights of Property Co-Owners and Dispute Resolution",
"provision": "In instances where two or more individuals hold co-ownership of a property, any co-owner shall possess the right to access and utilize the entire property, subject to fair usage provisions. In the event of a dispute arising from the use, management, or any aspect of the shared property, the aggrieved co-owner may file a complaint with the Jurisdictional Property Dispute Tribunal, which shall convene a mediation session within fifteen days and issue a binding resolution within sixty days from the date of the complaint. Failure to comply with the Tribunal's resolution may result in penalties or execution of partition proceedings as prescribed under this Act.",
"question_type": "rights",
"question": "What rights do co-owners of a property have under IPC 123A regarding access and dispute resolution?",
"answer": "Under IPC 123A, co-owners have the right to access and utilize the entire property, as long as they adhere to fair usage provisions. If a dispute arises concerning the use or management of the property, any aggrieved co-owner has the right to file a complaint with the Jurisdictional Property Dispute Tribunal, which must convene a mediation session within fifteen days and issue a binding resolution within sixty days. Failure to comply with the Tribunal's resolution may lead to penalties or partition proceedings."
},
{
"section_number": "IPC 512",
"section_title": "Causing Harm through Deceptive Practices",
"provision": "Whoever, by means of false representations or fraudulent acts, induces any person to part with property, or to confer any benefit, shall be punished with imprisonment for a term which may extend to three years, or with fine which may extend to fifty thousand rupees, or with both. In cases where the deception results in substantial financial loss to the victim, the imprisonment may extend to five years, and the fine may increase to one lakh rupees. This provision aims to penalize not only the act of deception but also to ensure reparation to the aggrieved party.",
"question_type": "penalty",
"question": "What are the potential penalties for causing harm through deceptive practices under IPC 512?",
"answer": "Under IPC 512, the penalties for causing harm through deceptive practices can include imprisonment for a term of up to three years, a fine of up to fifty thousand rupees, or both. If the deception results in substantial financial loss to the victim, the imprisonment may extend to five years, and the fine may increase to one lakh rupees."
},
{
"section_number": "C.R.P. 102",
"section_title": "Right to Dignity and Personal Autonomy",
"provision": "Every individual shall have the right to live with dignity and personal autonomy, free from discrimination, oppression, or arbitrary interference by the State or any other person. The State shall take all necessary measures to ensure that this right is protected, promoted, and fulfilled, including the implementation of laws that prevent violations of personal autonomy and safeguard against indignities. Any infringement of this right shall be actionable in a competent court, entailing appropriate remedies and compensation for the aggrieved party.",
"question_type": "examples",
"question": "Can you provide examples of situations where the right to dignity and personal autonomy, as outlined in C.R.P. 102, might be violated?",
"answer": "Yes, there are several situations that could violate the right to dignity and personal autonomy. For instance, if a government imposes a law that restricts individuals' freedoms to make personal choices regarding their healthcare, such as mandating a specific medical treatment against their will, this would infringe upon their autonomy. Similarly, if an employer discriminates against an employee based on their gender, sexual orientation, or race, denying them equal opportunities or subjecting them to harassment, this would violate their right to dignity. Another example could be a scenario where law enforcement unlawfully detains an individual without cause, thereby interfering with their personal freedom and dignity. In each of these cases, the affected individuals could seek remedies in a competent court for the infringements they have suffered."
},
{
"section_number": "IPC 456",
"section_title": "Trespass with Intent to Commit an Offense",
"provision": "Whoever unlawfully enters into or remains in any building or enclosed space with the intent to commit theft, causing harm, or any other punishable offense shall be liable to imprisonment for a term which may extend to three years, or with fine, or with both. If the trespasser is armed with a deadly weapon during such entry, the term of imprisonment may extend to seven years.",
"question_type": "procedure",
"question": "What steps should law enforcement take when apprehending a suspect under IPC 456 for trespass with intent to commit an offense?",
"answer": "Law enforcement should first ensure the safety of the area by assessing if the suspect is armed. If the suspect is armed with a deadly weapon, they should call for backup and use appropriate protocols for armed confrontations. Once the area is secure, officers should identify and detain the suspect, ensuring that they read the suspect their rights. Evidence should be collected, including witness statements and any items related to the offense. The suspect should then be arrested and charged under IPC 456, with the specifics of the intent to commit theft or harm documented for prosecution. Finally, the suspect should be transported to the nearest police station for processing and further legal proceedings."
},
{
"section_number": "IPC 501A",
"section_title": "Protection of Constitutional Rights",
"provision": "Every individual shall have the right to seek legal recourse against any act or omission of the State or any public authority that infringes upon their fundamental rights as guaranteed under Part III of the Constitution of India. The courts shall have the power to issue writs, orders, or directions to enforce such rights, and any violation thereof shall be deemed a punishable offense, attracting imprisonment for a term which may extend to three years, or a fine, or both. This section shall not preclude any individual from seeking compensation for damages arising from violations of their constitutional rights.",
"question_type": "rights",
"question": "What rights does IPC 501A provide to individuals regarding violations of their fundamental rights by the State or public authorities?",
"answer": "IPC 501A grants every individual the right to seek legal recourse against any act or omission by the State or public authorities that infringes upon their fundamental rights as guaranteed under Part III of the Constitution of India. This includes the ability to request courts to issue writs, orders, or directions to enforce these rights, and individuals can also seek compensation for damages resulting from such violations."
},
{
"section_number": "CPL 204",
"section_title": "Remedies for Breach of Contract",
"provision": "In the event of a breach of contract, the aggrieved party shall be entitled to seek specific performance or, where specific performance is impracticable, claim damages sufficient to restore the party to the position they would have occupied had the contract been performed. The aggrieved party may elect to pursue any combination of equitable remedies, including injunctions to prevent further breaches, provided that such remedies are sought within a period of three years from the date of the breach. Furthermore, in cases of willful or gross negligence leading to breach, the court may award punitive damages, not exceeding two times the actual damages incurred.",
"question_type": "definition",
"question": "What are the remedies available to an aggrieved party in the event of a breach of contract according to CPL 204?",
"answer": "According to CPL 204, the remedies available to an aggrieved party in the event of a breach of contract include seeking specific performance, claiming damages to restore their position as if the contract had been performed, pursuing a combination of equitable remedies such as injunctions to prevent further breaches, and in cases of willful or gross negligence, the possibility of receiving punitive damages not exceeding two times the actual damages incurred. These remedies must be sought within three years from the date of the breach."
},
{
"section_number": "CPC 123A",
"section_title": "Consolidation of Suits",
"provision": "In any suit wherein multiple causes of action arise from the same transaction or series of transactions, the Court may, upon application by any party, direct the consolidation of such suits into a single proceeding. The Court shall consider the interests of justice, the convenience of the parties, and the potential for judicial economy in making its determination. The consolidated suit shall proceed under the same procedural rules as a singular action, with all parties given adequate opportunity to present their respective claims and defenses.",
"question_type": "examples",
"question": "Can you provide an example of a situation where the Court might consolidate multiple suits under CPC 123A?",
"answer": "Certainly! Imagine a scenario where a construction company is sued by multiple homeowners for damages caused by the same faulty product used in their homes. Each homeowner files a separate suit against the company, claiming similar damages due to the defective product. In this case, the Court may allow the consolidation of these suits into a single proceeding because all claims arise from the same transaction—the use of the faulty product. The Court would consider factors such as the interests of justice, the convenience for the homeowners and the construction company, and the potential for reducing judicial resources. This way, the consolidated suit can be handled more efficiently under the same procedural rules, allowing all parties to present their claims and defenses together."
},
{
"section_number": "CPC 145",
"section_title": "Summary Dismissal of Frivolous Claims",
"provision": "The Court shall have the authority to summarily dismiss any civil claim or application that it deems to be frivolous, vexatious, or intended solely to harass the opposing party. The Court, upon motion by the defendant, may conduct a preliminary hearing to ascertain the merits of the claim and, if satisfied that the claim lacks substance or is manifestly unjust, shall issue an order dismissing the claim with costs awarded to the defendant. This provision shall not preclude the Court from imposing penalties for abuse of process as deemed appropriate.",
"question_type": "rights",
"question": "What rights do defendants have under CPC 145 regarding frivolous claims made against them?",
"answer": "Under CPC 145, defendants have the right to request a preliminary hearing to assess the merits of a civil claim they believe to be frivolous or vexatious. If the Court finds that the claim lacks substance or is manifestly unjust, it can dismiss the claim and award costs to the defendant. Additionally, the Court has the authority to impose penalties for any abuse of process, further protecting the defendant's rights against harassing legal actions."
},
{
"section_number": "IPC 123A",
"section_title": "Remedies for Breach of Contract",
"provision": "In the event of a breach of contract, the aggrieved party shall be entitled to seek either specific performance of the contract or, in lieu thereof, claim damages which shall be quantified based on the actual loss suffered as a direct result of the breach. The court may also grant consequential damages if it is proven that such damages were foreseeable at the time of contract formation. Additionally, if the breach is willful and malicious, the court may impose punitive damages to deter further violations.",
"question_type": "exceptions",
"question": "Are there any exceptions to the entitlement of the aggrieved party to seek specific performance or claim damages for a breach of contract under IPC 123A?",
"answer": "Yes, exceptions may arise in cases where the breach was not willful or malicious, thereby limiting the possibility of punitive damages. Additionally, if the aggrieved party fails to prove that the consequential damages were foreseeable at the time of contract formation, they may not be entitled to such damages. Furthermore, specific performance may not be granted if it is deemed impractical or impossible to enforce the terms of the contract."
},
{
"section_number": "CPC 224",
"section_title": "Procedure for Electronic Filing of Civil Suits",
"provision": "In any civil proceedings filed before the Court, a party may submit documents and pleadings electronically through the designated digital platform, provided that such filings comply with the prescribed format and electronic signature requirements as established by the Supreme Court of India. The electronic filing shall be deemed equivalent to the physical submission of documents, and the Court shall issue an electronic acknowledgment of receipt, which shall serve as the official record of submission. Any discrepancies in the electronic filing shall be rectified within seven days of notice from the Court, failing which the Court may dismiss the application without prejudice to the party's right to refile.",
"question_type": "penalty",
"question": "What are the potential penalties for failing to rectify discrepancies in electronic filings within the specified time frame as per CPC 224?",
"answer": "If a party fails to rectify discrepancies in their electronic filing within seven days of receiving notice from the Court, the Court may dismiss the application. However, this dismissal is without prejudice, meaning the party retains the right to refile the application in the future."
},
{
"section_number": "IEA 102A",
"section_title": "Admissibility of Digital Evidence",
"provision": "Notwithstanding the provisions of Section 65B of the Indian Evidence Act, 1872, any digital evidence, including but not limited to data derived from electronic devices, shall be admissible in a court of law provided that the party seeking to introduce such evidence establishes its authenticity through a certified digital signature or a chain of custody that clearly delineates the handling of the evidence from the time of its creation to presentation in court. The court shall also consider the relevance of the evidence in relation to the facts of the case and may exclude it if it is deemed to be unfairly prejudicial, misleading, or if its probative value is substantially outweighed by the danger of confusion of the issues.",
"question_type": "procedure",
"question": "What steps must a party take to ensure that digital evidence is admissible in court according to IEA 102A?",
"answer": "To ensure that digital evidence is admissible in court under IEA 102A, the party seeking to introduce the evidence must establish its authenticity by providing either a certified digital signature or a clear chain of custody. This chain of custody must detail the handling of the evidence from the time of its creation to its presentation in court. Additionally, the court will assess the relevance of the evidence to the case and may exclude it if it is found to be unfairly prejudicial, misleading, or if its probative value is substantially outweighed by the potential for confusion regarding the issues."
},
{
"section_number": "CPC 192A",
"section_title": "Conduct of Preliminary Hearings",
"provision": "In all civil matters, the court shall conduct a preliminary hearing within thirty days of the filing of the plaint. During this hearing, the court shall ascertain the issues raised, determine the necessity of further pleadings, and establish a timeline for the conduct of the trial. The court may also encourage the parties to explore alternative dispute resolution mechanisms, including mediation or conciliation, prior to proceeding with the formal trial process.",
"question_type": "exceptions",
"question": "Are there any exceptions to the requirement for the court to conduct a preliminary hearing within thirty days of the filing of the plaint in civil matters under CPC 192A?",
"answer": "Yes, exceptions may apply in cases where the court determines that special circumstances exist, such as complex issues requiring additional time for proper assessment, or if the parties have mutually agreed to postpone the preliminary hearing for valid reasons. Additionally, if there are procedural delays or if the court's schedule does not allow for a hearing within the stipulated timeframe, these may also constitute exceptions to the requirement."
},
{
"section_number": "CPC 123A",
"section_title": "Interim Relief in Civil Proceedings",
"provision": "In any suit pending before the Court, the plaintiff may apply for interim relief, including but not limited to the issuance of a temporary injunction or a stay of proceedings, if it is demonstrated that the delay in granting such relief would cause irreparable harm to the applicant. The Court shall consider the balance of convenience between the parties and the likelihood of success on the merits of the case before granting any interim orders. Such relief may be granted for a period not exceeding six months, subject to renewal upon satisfactory demonstration of continued necessity.",
"question_type": "exceptions",
"question": "Are there any exceptions to the granting of interim relief under CPC 123A, and what factors must be considered by the Court in such cases?",
"answer": "Yes, there are exceptions to the granting of interim relief under CPC 123A. The Court will only grant such relief if the plaintiff demonstrates that a delay would cause irreparable harm and considers the balance of convenience between the parties as well as the likelihood of success on the merits of the case. If these conditions are not satisfactorily met, the Court may deny the application for interim relief."
},
{
"section_number": "IPC 132A",
"section_title": "Protection of Innovations in Traditional Knowledge",
"provision": "Any individual or entity that seeks to utilize traditional knowledge or practices that have been developed and passed down through generations within a specific community shall obtain prior informed consent from the relevant community. Failure to do so shall constitute an infringement of the intellectual property rights of the community, rendering the infringer liable for damages not less than one lakh rupees and up to five times the profits derived from such unauthorized use. Additionally, courts may impose injunctions to prevent further exploitation of the said traditional knowledge.",
"question_type": "procedure",
"question": "What steps must an individual or entity take to legally utilize traditional knowledge according to IPC 132A?",
"answer": "To legally utilize traditional knowledge, an individual or entity must first obtain prior informed consent from the relevant community that holds the traditional knowledge. This involves engaging with the community to explain the intended use and ensuring that they fully understand and agree to it. Failure to obtain this consent may lead to legal consequences, including liability for damages and potential injunctions against further exploitation."
}
]

View File

@@ -0,0 +1,529 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%pip install -q transformers accelerate bitsandbytes torch gradio\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import torch\n",
"import json\n",
"import pandas as pd\n",
"import gradio as gr\n",
"from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig\n",
"from huggingface_hub import login\n",
"from google.colab import userdata\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Authenticate with HuggingFace\n",
"hf_token = userdata.get('HF_TOKEN')\n",
"login(hf_token, add_to_git_credential=True)\n",
"print(\"Successfully authenticated with HuggingFace\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Model configuration\n",
"MODEL_NAME = \"meta-llama/Meta-Llama-3.1-8B-Instruct\"\n",
"\n",
"# 4-bit quantization for efficiency on T4 GPU\n",
"quant_config = BitsAndBytesConfig(\n",
" load_in_4bit=True,\n",
" bnb_4bit_use_double_quant=True,\n",
" bnb_4bit_compute_dtype=torch.bfloat16,\n",
" bnb_4bit_quant_type=\"nf4\"\n",
")\n",
"\n",
"# Load tokenizer and model\n",
"tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)\n",
"tokenizer.pad_token = tokenizer.eos_token\n",
"\n",
"model = AutoModelForCausalLM.from_pretrained(\n",
" MODEL_NAME,\n",
" device_map=\"auto\",\n",
" quantization_config=quant_config\n",
")\n",
"\n",
"print(\"Model loaded successfully!\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Topic definitions based on course content\n",
"TOPICS = {\n",
" \"Week 1: LLM APIs & Prompting\": {\n",
" \"concepts\": [\n",
" \"OpenAI API usage and parameters\",\n",
" \"Prompt engineering techniques\",\n",
" \"Temperature and top_p parameters\",\n",
" \"System vs user messages\",\n",
" \"JSON mode and structured outputs\",\n",
" \"Token counting and pricing\",\n",
" \"Chat completions vs completions\",\n",
" \"Few-shot learning\"\n",
" ]\n",
" },\n",
" \"Week 2: Function Calling & Agents\": {\n",
" \"concepts\": [\n",
" \"Function calling syntax and format\",\n",
" \"Tool definitions and schemas\",\n",
" \"Parallel function calling\",\n",
" \"Function calling best practices\",\n",
" \"Agent patterns and workflows\",\n",
" \"Structured outputs with Pydantic\",\n",
" \"Error handling in function calls\"\n",
" ]\n",
" },\n",
" \"Week 3: Transformers & Models\": {\n",
" \"concepts\": [\n",
" \"Tokenizers and tokenization strategies\",\n",
" \"BPE, WordPiece, and SentencePiece\",\n",
" \"HuggingFace pipelines\",\n",
" \"AutoModel and AutoTokenizer\",\n",
" \"Model quantization (4-bit, 8-bit)\",\n",
" \"Speech-to-text with Whisper\",\n",
" \"Local vs cloud model inference\",\n",
" \"Model architectures (encoder, decoder, encoder-decoder)\"\n",
" ]\n",
" }\n",
"}\n",
"\n",
"# Difficulty level descriptions\n",
"DIFFICULTY_LEVELS = {\n",
" \"Beginner\": \"Basic understanding of concepts and definitions\",\n",
" \"Intermediate\": \"Application of concepts with some technical depth\",\n",
" \"Advanced\": \"Edge cases, optimization, and deep technical understanding\"\n",
"}\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def generate_questions(topic, difficulty, num_questions):\n",
" \"\"\"\n",
" Generate educational Q&A questions using the LLM.\n",
" \n",
" Args:\n",
" topic: Topic category to generate questions for\n",
" difficulty: Difficulty level (Beginner/Intermediate/Advanced)\n",
" num_questions: Number of questions to generate\n",
" \n",
" Returns:\n",
" List of dictionaries containing questions and answers\n",
" \"\"\"\n",
" \n",
" # Get topic details\n",
" topic_info = TOPICS[topic]\n",
" concepts = \", \".join(topic_info[\"concepts\"])\n",
" \n",
" # Build the prompt using Llama's chat format\n",
" system_message = \"\"\"You are an expert educator creating high-quality multiple-choice questions for an LLM Engineering course.\n",
"\n",
"Format each question EXACTLY as shown below:\n",
"\n",
"QUESTION: [question text]\n",
"A) [option A]\n",
"B) [option B]\n",
"C) [option C]\n",
"D) [option D]\n",
"ANSWER: [correct letter]\n",
"EXPLANATION: [brief explanation]\n",
"---\"\"\"\n",
"\n",
" user_prompt = f\"\"\"Create {num_questions} multiple-choice questions about: {topic}\n",
"\n",
"Difficulty Level: {difficulty}\n",
"\n",
"Cover these concepts: {concepts}\n",
"\n",
"Requirements:\n",
"- Questions should be practical and relevant to real LLM engineering\n",
"- All 4 options should be plausible\n",
"- Explanations should be clear and educational\n",
"- Vary the correct answer position\n",
"\n",
"Generate {num_questions} questions now:\"\"\"\n",
"\n",
" # Prepare messages for Llama\n",
" messages = [\n",
" {\"role\": \"system\", \"content\": system_message},\n",
" {\"role\": \"user\", \"content\": user_prompt}\n",
" ]\n",
" \n",
" # Tokenize using Llama's chat template\n",
" input_ids = tokenizer.apply_chat_template(\n",
" messages,\n",
" return_tensors=\"pt\",\n",
" add_generation_prompt=True\n",
" ).to(model.device)\n",
" \n",
" attention_mask = torch.ones_like(input_ids).to(model.device)\n",
" \n",
" # Generate\n",
" print(f\"Generating {num_questions} questions...\")\n",
" max_tokens = min(2500, num_questions * 200)\n",
" \n",
" with torch.no_grad():\n",
" outputs = model.generate(\n",
" input_ids,\n",
" attention_mask=attention_mask,\n",
" max_new_tokens=max_tokens,\n",
" temperature=0.7,\n",
" do_sample=True,\n",
" top_p=0.9,\n",
" pad_token_id=tokenizer.eos_token_id\n",
" )\n",
" \n",
" # Decode\n",
" response = tokenizer.decode(outputs[0], skip_special_tokens=True)\n",
" \n",
" # Extract just the assistant's response\n",
" if \"assistant\" in response:\n",
" response = response.split(\"assistant\")[-1].strip()\n",
" \n",
" # Debug: print what we got\n",
" print(\"Generated text preview:\")\n",
" print(response[:500] + \"...\" if len(response) > 500 else response)\n",
" print()\n",
" \n",
" # Parse the questions\n",
" questions = parse_questions(response, topic, difficulty)\n",
" \n",
" print(f\"Successfully generated {len(questions)} questions\")\n",
" return questions\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def parse_questions(text, topic, difficulty):\n",
" \"\"\"\n",
" Parse the generated text into structured question objects.\n",
" More robust parsing that handles various formats.\n",
" \"\"\"\n",
" questions = []\n",
" \n",
" # Split by \"QUESTION:\" to get individual question blocks\n",
" blocks = text.split(\"QUESTION:\")\n",
" \n",
" for i, block in enumerate(blocks):\n",
" if not block.strip() or i == 0 and len(block) < 20:\n",
" continue\n",
" \n",
" try:\n",
" # Extract components\n",
" question_text = \"\"\n",
" options = {}\n",
" answer = \"\"\n",
" explanation = \"\"\n",
" \n",
" lines = block.strip().split(\"\\n\")\n",
" \n",
" for line in lines:\n",
" line = line.strip()\n",
" if not line or line == \"---\":\n",
" continue\n",
" \n",
" # Handle question text (first non-empty line before options)\n",
" if not question_text and not any(line.startswith(x) for x in [\"A)\", \"B)\", \"C)\", \"D)\", \"ANSWER:\", \"EXPLANATION:\", \"Answer:\", \"Explanation:\"]):\n",
" question_text = line\n",
" \n",
" # Handle options - be flexible with formatting\n",
" elif line.startswith(\"A)\") or line.startswith(\"A.\"):\n",
" options[\"A\"] = line[2:].strip()\n",
" elif line.startswith(\"B)\") or line.startswith(\"B.\"):\n",
" options[\"B\"] = line[2:].strip()\n",
" elif line.startswith(\"C)\") or line.startswith(\"C.\"):\n",
" options[\"C\"] = line[2:].strip()\n",
" elif line.startswith(\"D)\") or line.startswith(\"D.\"):\n",
" options[\"D\"] = line[2:].strip()\n",
" \n",
" # Handle answer\n",
" elif line.upper().startswith(\"ANSWER:\"):\n",
" answer = line.split(\":\", 1)[1].strip()\n",
" \n",
" # Handle explanation\n",
" elif line.upper().startswith(\"EXPLANATION:\"):\n",
" explanation = line.split(\":\", 1)[1].strip()\n",
" elif explanation and len(explanation) < 200:\n",
" # Continue multi-line explanation (up to reasonable length)\n",
" explanation += \" \" + line\n",
" \n",
" # Extract just the letter from answer\n",
" if answer:\n",
" answer_letter = \"\"\n",
" for char in answer.upper():\n",
" if char in [\"A\", \"B\", \"C\", \"D\"]:\n",
" answer_letter = char\n",
" break\n",
" answer = answer_letter\n",
" \n",
" # Only add if we have minimum required components\n",
" if question_text and len(options) >= 3 and answer:\n",
" # Fill missing option if needed\n",
" if len(options) == 3:\n",
" for letter in [\"A\", \"B\", \"C\", \"D\"]:\n",
" if letter not in options:\n",
" options[letter] = \"Not applicable\"\n",
" break\n",
" \n",
" # Use placeholder explanation if none provided\n",
" if not explanation:\n",
" explanation = f\"The correct answer is {answer}.\"\n",
" \n",
" questions.append({\n",
" \"id\": len(questions) + 1,\n",
" \"topic\": topic,\n",
" \"difficulty\": difficulty,\n",
" \"question\": question_text,\n",
" \"options\": options,\n",
" \"correct_answer\": answer,\n",
" \"explanation\": explanation.strip()\n",
" })\n",
" print(f\"Parsed question {len(questions)}\")\n",
" else:\n",
" print(f\"Skipped incomplete block: Q={bool(question_text)}, Opts={len(options)}, Ans={bool(answer)}\")\n",
" \n",
" except Exception as e:\n",
" print(f\"Error parsing block {i+1}: {str(e)}\")\n",
" continue\n",
" \n",
" return questions\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def format_questions_display(questions):\n",
" \"\"\"Format questions for display in Gradio.\"\"\"\n",
" if not questions:\n",
" return \"No questions generated.\"\n",
" \n",
" output = f\"# Generated Questions\\n\\n\"\n",
" output += f\"**Total Questions:** {len(questions)}\\n\\n\"\n",
" output += \"---\\n\\n\"\n",
" \n",
" for q in questions:\n",
" output += f\"## Question {q['id']}\\n\\n\"\n",
" output += f\"**Topic:** {q['topic']} \\n\"\n",
" output += f\"**Difficulty:** {q['difficulty']} \\n\\n\"\n",
" output += f\"**Q:** {q['question']}\\n\\n\"\n",
" \n",
" for letter in ['A', 'B', 'C', 'D']:\n",
" prefix = \"✅ \" if letter == q['correct_answer'] else \"\"\n",
" output += f\"{prefix}{letter}) {q['options'][letter]}\\n\\n\"\n",
" \n",
" output += f\"**Answer:** {q['correct_answer']}\\n\\n\"\n",
" output += f\"**Explanation:** {q['explanation']}\\n\\n\"\n",
" output += \"---\\n\\n\"\n",
" \n",
" return output\n",
"\n",
"\n",
"def export_to_json(questions):\n",
" \"\"\"Export questions to JSON file.\"\"\"\n",
" if not questions:\n",
" return None\n",
" \n",
" filename = \"educational_qa_dataset.json\"\n",
" with open(filename, 'w') as f:\n",
" json.dump(questions, f, indent=2)\n",
" \n",
" return filename\n",
"\n",
"\n",
"def export_to_csv(questions):\n",
" \"\"\"Export questions to CSV file.\"\"\"\n",
" if not questions:\n",
" return None\n",
" \n",
" # Flatten the data for CSV\n",
" flattened = []\n",
" for q in questions:\n",
" flattened.append({\n",
" 'id': q['id'],\n",
" 'topic': q['topic'],\n",
" 'difficulty': q['difficulty'],\n",
" 'question': q['question'],\n",
" 'option_A': q['options']['A'],\n",
" 'option_B': q['options']['B'],\n",
" 'option_C': q['options']['C'],\n",
" 'option_D': q['options']['D'],\n",
" 'correct_answer': q['correct_answer'],\n",
" 'explanation': q['explanation']\n",
" })\n",
" \n",
" filename = \"educational_qa_dataset.csv\"\n",
" df = pd.DataFrame(flattened)\n",
" df.to_csv(filename, index=False)\n",
" \n",
" return filename\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def gradio_generate(topic, difficulty, num_questions):\n",
" \"\"\"\n",
" Wrapper function for Gradio interface.\n",
" Generates questions and returns formatted output plus download files.\n",
" \"\"\"\n",
" try:\n",
" # Generate questions\n",
" questions = generate_questions(topic, difficulty, num_questions)\n",
" \n",
" if not questions:\n",
" return \"Failed to generate questions. Please try again.\", None, None\n",
" \n",
" # Format for display\n",
" display_text = format_questions_display(questions)\n",
" \n",
" # Export files\n",
" json_file = export_to_json(questions)\n",
" csv_file = export_to_csv(questions)\n",
" \n",
" return display_text, json_file, csv_file\n",
" \n",
" except Exception as e:\n",
" return f\"Error: {str(e)}\", None, None\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Build the Gradio UI\n",
"with gr.Blocks(title=\"Educational Q&A Generator\", theme=gr.themes.Soft()) as demo:\n",
" \n",
" gr.Markdown(\"\"\"\n",
" # 📚 Educational Q&A Dataset Generator\n",
" Generate high-quality multiple-choice questions for LLM Engineering topics\n",
" \"\"\")\n",
" \n",
" with gr.Row():\n",
" with gr.Column(scale=1):\n",
" gr.Markdown(\"### ⚙️ Configuration\")\n",
" \n",
" topic_dropdown = gr.Dropdown(\n",
" choices=list(TOPICS.keys()),\n",
" value=\"Week 3: Transformers & Models\",\n",
" label=\"Select Topic\",\n",
" info=\"Choose which week's content to generate questions for\"\n",
" )\n",
" \n",
" difficulty_dropdown = gr.Dropdown(\n",
" choices=[\"Beginner\", \"Intermediate\", \"Advanced\"],\n",
" value=\"Intermediate\",\n",
" label=\"Difficulty Level\",\n",
" info=\"Select the difficulty of the questions\"\n",
" )\n",
" \n",
" num_questions_slider = gr.Slider(\n",
" minimum=5,\n",
" maximum=20,\n",
" value=10,\n",
" step=5,\n",
" label=\"Number of Questions\",\n",
" info=\"How many questions to generate (5-20)\"\n",
" )\n",
" \n",
" generate_btn = gr.Button(\"🚀 Generate Questions\", variant=\"primary\", size=\"lg\")\n",
" \n",
" gr.Markdown(\"\"\"\n",
" ---\n",
" ### 📥 Download Files\n",
" After generation, download your dataset in JSON or CSV format\n",
" \"\"\")\n",
" \n",
" with gr.Row():\n",
" json_download = gr.File(label=\"JSON File\", interactive=False)\n",
" csv_download = gr.File(label=\"CSV File\", interactive=False)\n",
" \n",
" with gr.Column(scale=2):\n",
" gr.Markdown(\"### 📝 Generated Questions\")\n",
" \n",
" output_display = gr.Markdown(\n",
" value=\"Click 'Generate Questions' to start...\",\n",
" label=\"Questions\"\n",
" )\n",
" \n",
" # Connect the generate button\n",
" generate_btn.click(\n",
" fn=gradio_generate,\n",
" inputs=[topic_dropdown, difficulty_dropdown, num_questions_slider],\n",
" outputs=[output_display, json_download, csv_download]\n",
" )\n",
" \n",
" gr.Markdown(\"\"\"\n",
" ---\n",
" ### 💡 Tips:\n",
" - Start with 5 questions to test the system\n",
" - Beginner questions cover definitions and basic concepts\n",
" - Intermediate questions test application and understanding\n",
" - Advanced questions explore edge cases and optimization\n",
" - Generation takes ~30-60 seconds depending on number of questions\n",
" \n",
" ### 📊 Output Formats:\n",
" - **JSON**: Structured data for programmatic use\n",
" - **CSV**: Easy to view in spreadsheets or import into other tools\n",
" \"\"\")\n",
"\n",
"print(\"✅ Gradio interface configured!\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Launch the Gradio app\n",
"demo.launch(share=True, debug=True)\n"
]
}
],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@@ -0,0 +1,734 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "KbMea_UrO3Ke"
},
"source": [
"# ✨ Coherent Data Generator\n",
"\n",
"## In real life, data has meaning, relationships, etc., and this is where this tool shines.\n",
"\n",
"Dependencies between fields are detected, and coherent data is generated.\n",
"Example:\n",
"When asked to generate data with **Ghana** cited as the context, fields like `name`, `food`, etc., will be Ghanaian. Fields such as phone number will have the appropriate prefix of `+233`, etc.\n",
"\n",
"This is better than Faker.\n",
"\n",
"## Steps\n",
"Schema -> Generate Data\n",
"\n",
"Schema Sources: \n",
"- Use the guided schema builder\n",
"- Bring your own schema from an SQL Data Definition Language (DDL)\n",
"- Prompting\n",
"- Providing a domain to an old hat to define features for a dataset"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "cN8z-QNlFtYc"
},
"outputs": [],
"source": [
"import json\n",
"\n",
"from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig\n",
"import torch\n",
"import pandas as pd\n",
"\n",
"from pydantic import BaseModel, Field\n",
"from IPython.display import display, Markdown"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "DOBBN3P2GD2O"
},
"outputs": [],
"source": [
"model_id = \"Qwen/Qwen3-4B-Instruct-2507\"\n",
"\n",
"device = torch.accelerator.current_accelerator().type if torch.accelerator.is_available() else 'cpu'\n",
"print(f'Device: {device}')\n",
"\n",
"tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)\n",
"\n",
"model = AutoModelForCausalLM.from_pretrained(\n",
" model_id,\n",
" dtype=\"auto\",\n",
" device_map=\"auto\"\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "HSUebXa1O3MM"
},
"source": [
"## Schema Definitions"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "5LNM76OQjAw6"
},
"outputs": [],
"source": [
"# This is for future use where errors in SQL DDL statements can be fixed if the\n",
"# specifies that from the UI\n",
"class SQLValidationResult(BaseModel):\n",
" is_valid: bool\n",
" is_fixable: bool\n",
" reason: str = Field(default='', description='validation failure reason')\n",
"\n",
"\n",
"class FieldDescriptor(BaseModel):\n",
" name: str = Field(..., description='Name of the field')\n",
" data_type: str = Field(..., description='Type of the field')\n",
" nullable: bool\n",
" description: str = Field(..., description='Description of the field')\n",
"\n",
"\n",
"class Schema(BaseModel):\n",
" name: str = Field(..., description='Name of the schema')\n",
" fields: list[FieldDescriptor] = Field(..., description='List of fields in the schema')"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "6QjitfTBPa1E"
},
"source": [
"## LLM Interactions"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "dXiRHok7Peir"
},
"source": [
"### Generate Content from LLM"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "daTUVG8_PmvM"
},
"outputs": [],
"source": [
"def generate(messages: list[dict[str, str]], temperature: float = 0.1) -> any:\n",
" text = tokenizer.apply_chat_template(\n",
" messages,\n",
" tokenize=False,\n",
" add_generation_prompt=True,\n",
" )\n",
" model_inputs = tokenizer([text], return_tensors=\"pt\").to(model.device)\n",
"\n",
" generated_ids = model.generate(\n",
" **model_inputs,\n",
" max_new_tokens=16384,\n",
" temperature=temperature\n",
" )\n",
"\n",
" output_ids = generated_ids[0][len(model_inputs.input_ids[0]):].tolist()\n",
" content = tokenizer.decode(output_ids, skip_special_tokens=True)\n",
"\n",
" return content"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "sBHJKn8qQhM5"
},
"source": [
"### Generate Data Given A Valid Schema"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "Fla8UQf4Qm5l"
},
"outputs": [],
"source": [
"def generate_data(schema: str, context: str = '', num_records: int = 5):\n",
" system_prompt = f'''\n",
" You are synthetic data generator, you generate data based on the given schema\n",
" specific JSON structure.\n",
" When a context is provided, intelligently use that to drive the field generation.\n",
"\n",
" Example:\n",
" If Africa is given at the context, fields like name, first_name, last_name, etc.\n",
" that can be derived from Africa will be generated.\n",
"\n",
" If no context is provided, generate data randomly.\n",
"\n",
" Output an array of JSON objects.\n",
" '''\n",
"\n",
" prompt = f'''\n",
" Generate {num_records}:\n",
"\n",
" Schema:\n",
" {schema}\n",
"\n",
" Context:\n",
" {context}\n",
" '''\n",
"\n",
" messages = [\n",
" {'role': 'system', 'content': system_prompt},\n",
" {\"role\": \"user\", \"content\": prompt}\n",
" ]\n",
"\n",
" return generate(messages)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "izrClU6VPsZp"
},
"source": [
"### SQL"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "aQgY6EK0QPPd"
},
"outputs": [],
"source": [
"def sql_validator(ddl: str):\n",
" system_prompt = '''\n",
" You are an SQL validator, your task is to validate if the given SQL is valid or not.\n",
" ONLY return a binary response of 1 and 0. Where 1=valid and 0 = not valid.\n",
" '''\n",
" prompt = f'Validate: {ddl}'\n",
"\n",
" messages = [\n",
" {'role': 'system', 'content': system_prompt},\n",
" {\"role\": \"user\", \"content\": prompt}\n",
" ]\n",
"\n",
" return generate(messages)\n",
"\n",
"\n",
"# Future work, this will fix any errors in the SQL DDL statement provided it is\n",
"# fixable.\n",
"def sql_fixer(ddl: str):\n",
" pass\n",
"\n",
"\n",
"def parse_ddl(ddl: str):\n",
" system_prompt = f'''\n",
" You are an SQL analyzer, your task is to extract column information to a\n",
" specific JSON structure.\n",
"\n",
" The output must comform to the following JSON schema:\n",
" {Schema.model_json_schema()}\n",
" '''\n",
" prompt = f'Generate schema for: {ddl}'\n",
"\n",
" messages = [\n",
" {'role': 'system', 'content': system_prompt},\n",
" {\"role\": \"user\", \"content\": prompt}\n",
" ]\n",
"\n",
" return generate(messages)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "4mgwDQyDQ1wv"
},
"source": [
"### Data Scientist\n",
"\n",
"Just give it a domain and you will be amazed the features will give you."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "P36AMvBq8AST"
},
"outputs": [],
"source": [
"def create_domain_schema(domain: str):\n",
" system_prompt = f'''\n",
" You are an expert Data Scientist tasked to describe features for a dataset\n",
" aspiring data scientists in a chosen domain.\n",
"\n",
" Follow these steps EXACTLY:\n",
" **Define 610 features** for the given domain. Include:\n",
" - At least 2 numerical features\n",
" - At least 2 categorical features\n",
" - 1 boolean or binary feature\n",
" - 1 timestamp or date feature\n",
" - Realistic dependencies (e.g., \"if loan_amount > 50000, credit_score should be high\")\n",
"\n",
" Populate your response into the JSON schema below. Strictly out **JSON**\n",
" {Schema.model_json_schema()}\n",
" '''\n",
" prompt = f'Describe the data point. Domain: {domain}'\n",
"\n",
" messages = [\n",
" {'role': 'system', 'content': system_prompt},\n",
" {\"role\": \"user\", \"content\": prompt}\n",
" ]\n",
"\n",
" return generate(messages)\n",
"\n",
"\n",
"# TODO: Use Gradion Examples to make it easier for the loading of different statements\n",
"sql = '''\n",
"CREATE TABLE users (\n",
" id BIGINT PRIMARY KEY,\n",
" name VARCHAR(100) NOT NULL,\n",
" email TEXT,\n",
" gender ENUM('F', 'M'),\n",
" country VARCHAR(100),\n",
" mobile_number VARCHAR(100),\n",
" created_at TIMESTAMP DEFAULT NOW()\n",
");\n",
"'''"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "QuVyHOhjDtSH"
},
"outputs": [],
"source": [
"print(f'{model.get_memory_footprint() / 1e9:, .2f} GB')"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "tqSpfJGnme7y"
},
"source": [
"## Export Functions"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "pAu5OPfUmMSm"
},
"outputs": [],
"source": [
"from enum import StrEnum\n",
"\n",
"\n",
"class ExportFormat(StrEnum):\n",
" CSV = 'CSV'\n",
" JSON = 'JSON'\n",
" Excel = 'Excel'\n",
" Parquet = 'Parquet'\n",
" TSV = 'TSV'\n",
" HTML = 'HTML'\n",
" Markdown = 'Markdown'\n",
" SQL = 'SQL'\n",
"\n",
"\n",
"def export_data(df, format_type):\n",
" if df is None or df.empty:\n",
" return None\n",
"\n",
" try:\n",
" if format_type == ExportFormat.CSV:\n",
" output = io.StringIO()\n",
" df.to_csv(output, index=False)\n",
" return output.getvalue()\n",
"\n",
" elif format_type == ExportFormat.JSON:\n",
" return df.to_json(orient='records', indent=2)\n",
"\n",
" elif format_type == ExportFormat.Excel:\n",
" output = io.BytesIO()\n",
" df.to_excel(output, index=False, engine='openpyxl')\n",
" return output.getvalue()\n",
"\n",
" elif format_type == ExportFormat.Parquet:\n",
" output = io.BytesIO()\n",
" df.to_parquet(output, index=False)\n",
" return output.getvalue()\n",
"\n",
" elif format_type == ExportFormat.TSV:\n",
" output = io.StringIO()\n",
" df.to_csv(output, sep='\\t', index=False)\n",
" return output.getvalue()\n",
"\n",
" elif format_type == ExportFormat.HTML:\n",
" return df.to_html(index=False)\n",
"\n",
" elif format_type == ExportFormat.Markdown:\n",
" return df.to_markdown(index=False)\n",
"\n",
" elif format_type == ExportFormat.SQL:\n",
" from sqlalchemy import create_engine\n",
" engine = create_engine('sqlite:///:memory:')\n",
" table = 'users' # TODO: fix this\n",
"\n",
" df.to_sql(table, con=engine, index=False)\n",
" connection = engine.raw_connection()\n",
" sql_statements = list(connection.iterdump())\n",
" sql_output_string = \"\\n\".join(sql_statements)\n",
" connection.close()\n",
"\n",
" return sql_output_string\n",
"\n",
" except Exception as e:\n",
" print(f\"Export error: {str(e)}\")\n",
" return None\n",
"\n",
"\n",
"def prepare_download(df, format_type):\n",
" if df is None:\n",
" return None\n",
"\n",
" content = export_data(df, format_type)\n",
" if content is None:\n",
" return None\n",
"\n",
" extensions = {\n",
" ExportFormat.CSV: '.csv',\n",
" ExportFormat.JSON: '.json',\n",
" ExportFormat.Excel: '.xlsx',\n",
" ExportFormat.Parquet: '.parquet',\n",
" ExportFormat.TSV: '.tsv',\n",
" ExportFormat.HTML: '.html',\n",
" ExportFormat.Markdown: '.md',\n",
" ExportFormat.SQL: '.sql',\n",
" }\n",
"\n",
" filename = f'generated_data{extensions.get(format_type, \".txt\")}'\n",
"\n",
" is_binary_format = format_type in [ExportFormat.Excel, ExportFormat.Parquet]\n",
" mode = 'w+b' if is_binary_format else 'w'\n",
"\n",
" import tempfile\n",
" with tempfile.NamedTemporaryFile(mode=mode, delete=False, suffix=extensions[format_type]) as tmp:\n",
" tmp.write(content)\n",
" tmp.flush()\n",
" return tmp.name"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Q0fZsCuso_YZ"
},
"source": [
"## Gradio UI"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "TJYUWecybDpP",
"outputId": "e82d0a13-3ca3-4a01-d45c-78fc94ade9bc"
},
"outputs": [],
"source": [
"import gradio as gr\n",
"from pydantic import BaseModel, Field\n",
"import json\n",
"import pandas as pd\n",
"import io\n",
"\n",
"DATA_TYPES = ['string', 'integer', 'float', 'boolean', 'date', 'datetime', 'array', 'object']\n",
"\n",
"def generate_from_sql(sql: str, context: str, num_records: int = 10):\n",
" try:\n",
" print(f'SQL: {sql}')\n",
" schema = parse_ddl(sql)\n",
" data = generate_data(schema, context, num_records)\n",
"\n",
" data = json.loads(data)\n",
" df = pd.DataFrame(data)\n",
"\n",
" return schema, df\n",
" except Exception as e:\n",
" return f'Error: {str(e)}', None\n",
"\n",
"\n",
"def generate_from_data_scientist(domain: str, context: str, num_records: int = 10):\n",
" try:\n",
" print(f'Domain: {domain}')\n",
" schema = create_domain_schema(domain)\n",
" print(schema)\n",
" data = generate_data(schema, context, num_records)\n",
" data = json.loads(data)\n",
" df = pd.DataFrame(data)\n",
"\n",
" return schema, df\n",
" except Exception as e:\n",
" return f'Error: {str(e)}', None\n",
"\n",
"\n",
"def generate_from_dynamic_fields(schema_name, context: str, num_fields, num_records: int, *field_values):\n",
" try:\n",
" fields = []\n",
" for i in range(num_fields):\n",
" idx = i * 4\n",
" if idx + 3 < len(field_values):\n",
" name = field_values[idx]\n",
" dtype = field_values[idx + 1]\n",
" nullable = field_values[idx + 2]\n",
" desc = field_values[idx + 3]\n",
"\n",
" if name and dtype:\n",
" fields.append(FieldDescriptor(\n",
" name=name,\n",
" data_type=dtype,\n",
" nullable=nullable if nullable is not None else False,\n",
" description=desc if desc else ''\n",
" ))\n",
"\n",
" if not schema_name:\n",
" return 'Error: Schema name is required', None\n",
"\n",
" if not fields:\n",
" return 'Error: At least one field is required', None\n",
"\n",
" schema = Schema(name=schema_name, fields=fields)\n",
" data = generate_data(schema.model_dump(), context , num_records)\n",
" data = json.loads(data)\n",
" df = pd.DataFrame(data)\n",
"\n",
"\n",
" return json.dumps(schema.model_dump(), indent=2), df\n",
"\n",
" except Exception as e:\n",
" return f'Error: {str(e)}', None\n",
"\n",
"\n",
"\n",
"title='✨ Coherent Data Generator'\n",
"\n",
"with gr.Blocks(title=title, theme=gr.themes.Monochrome()) as ui:\n",
" gr.Markdown(f'# {title}')\n",
" gr.Markdown('Embrass the Coherent Data wins 🏆!')\n",
"\n",
" df_state = gr.State(value=None)\n",
"\n",
" with gr.Row():\n",
" num_records_input = gr.Number(\n",
" label='Number of Records to Generate',\n",
" value=10,\n",
" minimum=1,\n",
" maximum=10000,\n",
" step=1,\n",
" precision=0\n",
" )\n",
"\n",
" context_input = gr.Textbox(\n",
" label='Context',\n",
" placeholder='70% Ghana and 30% Nigeria data. Start ID generation from 200',\n",
" lines=1\n",
" )\n",
"\n",
" with gr.Tabs() as tabs:\n",
" with gr.Tab('Manual Entry', id=0):\n",
" schema_name_input = gr.Textbox(label='Schema Name', placeholder='Enter schema name')\n",
"\n",
" gr.Markdown('### Fields')\n",
"\n",
" num_fields_state = gr.State(3)\n",
"\n",
" with gr.Row():\n",
" num_fields_slider = gr.Slider(\n",
" minimum=1,\n",
" maximum=20,\n",
" value=3,\n",
" step=1,\n",
" label='Number of Fields',\n",
" interactive=True\n",
" )\n",
"\n",
" gr.HTML('''\n",
" <div style=\"display: flex; gap: 8px; margin-bottom: 8px; font-weight: bold;\">\n",
" <div style=\"flex: 2;\">Field Name</div>\n",
" <div style=\"flex: 2;\">Data Type</div>\n",
" <div style=\"flex: 1;\">Nullable</div>\n",
" <div style=\"flex: 3;\">Description</div>\n",
" </div>\n",
" ''')\n",
"\n",
" field_components = []\n",
" row_components = []\n",
"\n",
" for i in range(20):\n",
" with gr.Row(visible=(i < 3)) as row:\n",
" field_name = gr.Textbox(label='', container=False, scale=2)\n",
" data_type = gr.Dropdown(choices=DATA_TYPES, value='string', label='', container=False, scale=2)\n",
" nullable = gr.Checkbox(label='', container=False, scale=1)\n",
" description = gr.Textbox(label='', container=False, scale=3)\n",
"\n",
" row_components.append(row)\n",
" field_components.extend([field_name, data_type, nullable, description])\n",
"\n",
" submit_btn = gr.Button('Generate', variant='primary')\n",
"\n",
" num_fields_slider.change(\n",
" fn=lambda x: [gr.update(visible=(i < x)) for i in range(20)],\n",
" inputs=[num_fields_slider],\n",
" outputs=row_components\n",
" )\n",
"\n",
"\n",
" with gr.Tab('SQL', id=1):\n",
" gr.Markdown('### Parse SQL DDL')\n",
" ddl_input = gr.Code(\n",
" value=sql,\n",
" label='SQL DDL Statement',\n",
" language='sql',\n",
" lines=10\n",
" )\n",
" ddl_btn = gr.Button('Generate', variant='primary')\n",
"\n",
"\n",
" with gr.Tab('>_ Prompt', id=2):\n",
" gr.Markdown('### You are on your own here, so be creative 💡')\n",
" prompt_input = gr.Textbox(\n",
" label='Prompt',\n",
" placeholder='Type your prompt',\n",
" lines=10\n",
" )\n",
" prompt_btn = gr.Button('Generate', variant='primary')\n",
"\n",
" with gr.Tab('Data Scientist 🎩', id=3):\n",
" gr.Markdown('### You are on your own here, so be creative 💡')\n",
" domain_input = gr.Dropdown(\n",
" label='Domain',\n",
" choices=['E-commerce Customers', 'Hospital Patients', 'Loan Applications'],\n",
" allow_custom_value=True\n",
" )\n",
"\n",
" data_scientist_generate_btn = gr.Button('Generate', variant='primary')\n",
"\n",
"\n",
" with gr.Accordion('Generated Schema', open=False):\n",
" output = gr.Code(label='Schema (JSON)', language='json')\n",
"\n",
" gr.Markdown('## Generated Data')\n",
" dataframe_output = gr.Dataframe(\n",
" label='',\n",
" interactive=False,\n",
" wrap=True\n",
" )\n",
"\n",
" gr.Markdown('### Export Data')\n",
" with gr.Row():\n",
" format_dropdown = gr.Dropdown(\n",
" choices=[format.value for format in ExportFormat],\n",
" value=ExportFormat.CSV,\n",
" label='Export Format',\n",
" scale=2\n",
" )\n",
" download_btn = gr.Button('Download', variant='secondary', scale=1)\n",
"\n",
" download_file = gr.File(label='Download File', visible=True)\n",
"\n",
"\n",
" def _handle_result(result):\n",
" if isinstance(result, tuple) and len(result) == 2:\n",
" return result[0], result[1], result[1]\n",
" return result[0], result[1], None\n",
"\n",
"\n",
" def update_from_dynamic_fields(schema_name, context, num_fields, num_records, *field_values):\n",
" result = generate_from_dynamic_fields(schema_name, context, num_fields, num_records, *field_values)\n",
" return _handle_result(result)\n",
"\n",
"\n",
" def update_from_sql(sql: str, context, num_records: int):\n",
" result = generate_from_sql(sql, context, num_records)\n",
" return _handle_result(result)\n",
"\n",
"\n",
" def update_from_data_scientist(domain: str, context, num_records: int):\n",
" result = generate_from_data_scientist(domain, context, num_records)\n",
" return _handle_result(result)\n",
"\n",
"\n",
" submit_btn.click(\n",
" fn=update_from_dynamic_fields,\n",
" inputs=[schema_name_input, context_input, num_fields_slider, num_records_input] + field_components,\n",
" outputs=[output, dataframe_output, df_state]\n",
" )\n",
"\n",
" ddl_btn.click(\n",
" fn=update_from_sql,\n",
" inputs=[ddl_input, context_input, num_records_input],\n",
" outputs=[output, dataframe_output, df_state]\n",
" )\n",
"\n",
" data_scientist_generate_btn.click(\n",
" fn=update_from_data_scientist,\n",
" inputs=[domain_input, context_input, num_records_input],\n",
" outputs=[output, dataframe_output, df_state]\n",
" )\n",
"\n",
"\n",
" download_btn.click(\n",
" fn=prepare_download,\n",
" inputs=[df_state, format_dropdown],\n",
" outputs=[download_file]\n",
" )\n",
"\n",
"\n",
"ui.launch(debug=True)\n"
]
}
],
"metadata": {
"accelerator": "GPU",
"colab": {
"collapsed_sections": [
"tqSpfJGnme7y"
],
"gpuType": "T4",
"provenance": []
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 0
}

View File

@@ -0,0 +1,301 @@
#!/usr/bin/env python3
import os
import torch
import requests
import json
import librosa
import numpy as np
from pathlib import Path
from datetime import datetime
from transformers import pipeline
import gradio as gr
# Basic config
TRANSCRIPTION_MODEL = "openai/whisper-tiny.en"
OLLAMA_MODEL = "llama3.2:latest"
OLLAMA_URL = "http://localhost:11434"
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
OUTPUT_DIR = Path("./output")
# ============================
# MODEL LOADING
# ============================
def check_ollama():
try:
response = requests.get(f"{OLLAMA_URL}/api/tags", timeout=5)
if response.status_code == 200:
models = response.json().get('models', [])
model_names = [model['name'] for model in models]
return OLLAMA_MODEL in model_names
return False
except:
return False
def call_ollama(prompt):
payload = {
"model": OLLAMA_MODEL,
"prompt": prompt,
"stream": False,
"options": {
"temperature": 0.7,
"num_predict": 1000
}
}
try:
response = requests.post(f"{OLLAMA_URL}/api/generate", json=payload, timeout=120)
if response.status_code == 200:
return response.json().get('response', '').strip()
return "Error: Ollama request failed"
except:
return "Error: Could not connect to Ollama"
def load_models():
print("Loading models...")
if not check_ollama():
print("Ollama not available")
return None, False
try:
transcription_pipe = pipeline(
"automatic-speech-recognition",
model=TRANSCRIPTION_MODEL,
torch_dtype=torch.float16 if DEVICE == "cuda" else torch.float32,
device=0 if DEVICE == "cuda" else -1,
return_timestamps=True
)
print("Models loaded successfully")
return transcription_pipe, True
except Exception as e:
print(f"Failed to load models: {e}")
return None, False
# ============================
# PROCESSING FUNCTIONS
# ============================
def transcribe_audio(audio_file_path, transcription_pipe):
if not os.path.exists(audio_file_path):
return "Error: Audio file not found"
try:
# Load audio with librosa
audio, sr = librosa.load(audio_file_path, sr=16000)
if not isinstance(audio, np.ndarray):
audio = np.array(audio)
result = transcription_pipe(audio)
# Extract text from result
if isinstance(result, dict):
if "text" in result:
transcription = result["text"].strip()
elif "chunks" in result:
transcription = " ".join([chunk["text"] for chunk in result["chunks"]]).strip()
else:
transcription = str(result).strip()
else:
transcription = str(result).strip()
return transcription
except Exception as e:
return f"Error: {str(e)}"
def generate_minutes(transcription):
prompt = f"""Create meeting minutes from this transcript:
{transcription[:2000]}
Include:
- Summary with attendees and topics
- Key discussion points
- Important decisions
- Action items
Meeting Minutes:"""
result = call_ollama(prompt)
return result
def save_results(transcription, minutes, meeting_type="meeting"):
try:
OUTPUT_DIR.mkdir(exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"{meeting_type}_minutes_{timestamp}.md"
filepath = OUTPUT_DIR / filename
content = f"""# Meeting Minutes
**Generated:** {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
## Meeting Minutes
{minutes}
## Full Transcription
{transcription}
"""
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
return str(filepath)
except Exception as e:
return f"Error saving: {str(e)}"
# ============================
# GRADIO INTERFACE
# ============================
def process_audio_file(audio_file, meeting_type, progress=gr.Progress()):
progress(0.0, desc="Starting...")
if not hasattr(process_audio_file, 'models') or not process_audio_file.models[0]:
return "", "", "Models not loaded"
transcription_pipe, ollama_ready = process_audio_file.models
if not ollama_ready:
return "", "", "Ollama not available"
try:
audio_path = audio_file.name if hasattr(audio_file, 'name') else str(audio_file)
if not audio_path:
return "", "", "No audio file provided"
progress(0.2, desc="Transcribing...")
transcription = transcribe_audio(audio_path, transcription_pipe)
if transcription.startswith("Error:"):
return transcription, "", "Transcription failed"
progress(0.6, desc="Generating minutes...")
minutes = generate_minutes(transcription)
if minutes.startswith("Error:"):
return transcription, minutes, "Minutes generation failed"
progress(0.9, desc="Saving...")
save_path = save_results(transcription, minutes, meeting_type)
progress(1.0, desc="Complete!")
status = f"""Processing completed!
Transcription: {len(transcription)} characters
Minutes: {len(minutes)} characters
Saved to: {save_path}
Models used:
- Transcription: {TRANSCRIPTION_MODEL}
- LLM: {OLLAMA_MODEL}
- Device: {DEVICE}
"""
return transcription, minutes, status
except Exception as e:
progress(1.0, desc="Failed")
return "", "", f"Processing failed: {str(e)}"
def create_interface():
with gr.Blocks(title="Meeting Minutes Creator") as interface:
gr.HTML("<h1>Meeting Minutes Creator</h1><p>HuggingFace Whisper + Ollama</p>")
with gr.Row():
with gr.Column():
gr.Markdown("### Audio Input")
audio_input = gr.Audio(
label="Upload or Record Audio",
type="filepath",
sources=["upload", "microphone"]
)
meeting_type = gr.Dropdown(
choices=["meeting", "standup", "interview", "call"],
value="meeting",
label="Meeting Type"
)
process_btn = gr.Button("Generate Minutes", variant="primary")
gr.HTML(f"""
<div>
<h4>Configuration</h4>
<ul>
<li>Transcription: {TRANSCRIPTION_MODEL}</li>
<li>LLM: {OLLAMA_MODEL}</li>
<li>Device: {DEVICE}</li>
</ul>
</div>
""")
with gr.Column():
gr.Markdown("### Results")
status_output = gr.Markdown("Ready to process audio")
with gr.Tabs():
with gr.Tab("Meeting Minutes"):
minutes_output = gr.Markdown("Minutes will appear here")
with gr.Tab("Transcription"):
transcription_output = gr.Textbox(
"Transcription will appear here",
lines=15,
show_copy_button=True
)
process_btn.click(
fn=process_audio_file,
inputs=[audio_input, meeting_type],
outputs=[transcription_output, minutes_output, status_output],
show_progress=True
)
return interface
# ============================
# MAIN APPLICATION
# ============================
def main():
print("Meeting Minutes Creator - HuggingFace + Ollama")
print("Loading models...")
transcription_pipe, ollama_ready = load_models()
if not transcription_pipe or not ollama_ready:
print("Failed to load models or connect to Ollama")
print("Make sure Ollama is running and has the model available")
return
process_audio_file.models = (transcription_pipe, ollama_ready)
print("Models loaded successfully!")
print("Starting web interface...")
print("Access at: http://localhost:7860")
interface = create_interface()
try:
interface.launch(
server_name="localhost",
server_port=7860,
debug=False
)
except KeyboardInterrupt:
print("Shutting down...")
except Exception as e:
print(f"Failed to launch: {e}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,36 @@
# Meeting Minutes
**Generated:** 2025-10-24 06:26:09
## Meeting Minutes
Here are the meeting minutes based on the transcript:
**Dilistanda Meeting Minutes - October 24**
**Attendees:**
* Jean (Project Manager)
* [Unknown speaker] ( attendee, name not provided)
**Summary:**
This meeting was held to discuss ongoing project updates and tasks for Dilistanda. The attendees reviewed the progress made by Jean on the user authentication module and discussed other ongoing work.
**Key Discussion Points:**
* Jean shared his update on completing the user authentication module and fixing three bugs on the login system.
* [Unknown speaker] mentioned they finished a database migration script and reviewed SORAP or request, but did not provide further details.
**Important Decisions:**
None
**Action Items:**
1. **Jean:** Continue working on the dashboard to components without any blockers.
2. [Unknown speaker]: Focus on API points for mobile app development.
Note: Unfortunately, some information was missing from the transcript (e.g., the identity of the second attendee), which made it challenging to create a comprehensive set of meeting minutes.
## Full Transcription
Good morning everyone, this is our Dilistanda meeting for October 24. I am sorrow as a project manager. Jean, can you give us your update? Yeah, Jean here yesterday I completed the user authentication module and I fixed three bugs on the login system. Today I will be working on the dashboard to components, no blocker. Okay, so I'm going to make your turn. How is this mic? I finished the database migration script and I reviewed SORAP or request. Today I will focus on the API points for mobile app.

View File

@@ -0,0 +1,9 @@
# Meeting Minutes Creator V2 - HuggingFace + Ollama Implementation
# Requirements for Week 3 Day 5 Exercise
torch>=2.0.0
transformers>=4.35.0
gradio>=4.0.0
librosa>=0.10.0
soundfile>=0.12.0
requests>=2.31.0

View File

@@ -0,0 +1,229 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "2714fa36",
"metadata": {},
"source": [
"## Week 3 Data Generator With Opensource Models\n",
"# Generate synthetic data for Pizza cusromers within Nairobi "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "761622db",
"metadata": {},
"outputs": [],
"source": [
"!pip install requests pandas ipywidgets gradio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cc7347c4",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import gradio as gr\n",
"from huggingface_hub import InferenceClient\n",
"import random\n",
"import os\n",
"from dotenv import load_dotenv\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f20cd822",
"metadata": {},
"outputs": [],
"source": [
"#Load API Key\n",
"\n",
"load_dotenv(override=True)\n",
"HF_API_KEY = os.getenv('HF_TOKEN')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "856cd8cb",
"metadata": {},
"outputs": [],
"source": [
"\n",
"# Define available models with correct Hugging Face model IDs\n",
"MODELS = {\n",
" \"Mistral-7B\": \"mistralai/Mistral-7B-Instruct-v0.2\",\n",
" \"Llama-2-7B\": \"meta-llama/Llama-2-7b-chat-hf\",\n",
" \"Phi-2\": \"microsoft/phi-2\",\n",
" \"GPT-2\": \"gpt2\"\n",
"}\n",
"\n",
"# Nairobi branches\n",
"BRANCHES = [\"Westlands\", \"Karen\", \"Kilimani\", \"CBD\", \"Parklands\"]\n",
"\n",
"# Global variable to store generated data\n",
"generated_df = None\n",
"\n",
"def generate_feedback_data(model_name, num_records):\n",
" \"\"\"Generate synthetic pizza feedback data using selected AI model\"\"\"\n",
" global generated_df\n",
" \n",
" try:\n",
" # Initialize the Hugging Face Inference Client\n",
" model_id = MODELS[model_name]\n",
" client = InferenceClient(model=model_id, token=None) # Add your HF token if needed\n",
" \n",
" feedback_data = []\n",
" \n",
" for i in range(num_records):\n",
" # Random branch\n",
" branch = random.choice(BRANCHES)\n",
" \n",
" # Generate feedback using the AI model\n",
" prompt = f\"Generate a brief customer feedback comment about a pizza order from {branch} branch in Nairobi. Make it realistic and varied (positive, negative, or neutral). Keep it under 30 words.\"\n",
" \n",
" try:\n",
" response = client.text_generation(\n",
" prompt,\n",
" max_new_tokens=50,\n",
" temperature=0.8\n",
" )\n",
" feedback = response.strip()\n",
" except Exception as e:\n",
" # Fallback to template-based generation if API fails\n",
" feedback = generate_fallback_feedback(branch)\n",
" \n",
" # Generate other fields\n",
" record = {\n",
" \"Customer_ID\": f\"CUST{1000 + i}\",\n",
" \"Branch\": branch,\n",
" \"Rating\": random.randint(1, 5),\n",
" \"Order_Type\": random.choice([\"Delivery\", \"Dine-in\", \"Takeaway\"]),\n",
" \"Feedback\": feedback,\n",
" \"Date\": f\"2024-{random.randint(1, 12):02d}-{random.randint(1, 28):02d}\"\n",
" }\n",
" \n",
" feedback_data.append(record)\n",
" \n",
" # Create DataFrame\n",
" generated_df = pd.DataFrame(feedback_data)\n",
" \n",
" return generated_df, f\"✓ Successfully generated {num_records} records using {model_name}\"\n",
" \n",
" except Exception as e:\n",
" return pd.DataFrame(), f\"✗ Error: {str(e)}\"\n",
"\n",
"def generate_fallback_feedback(branch):\n",
" \"\"\"Fallback feedback generator if API fails\"\"\"\n",
" templates = [\n",
" f\"Great pizza from {branch}! Quick delivery and hot food.\",\n",
" f\"Pizza was cold when it arrived at {branch}. Disappointed.\",\n",
" f\"Excellent service at {branch} branch. Will order again!\",\n",
" f\"Average experience. Pizza was okay but nothing special.\",\n",
" f\"Long wait time at {branch} but the pizza was worth it.\",\n",
" ]\n",
" return random.choice(templates)\n",
"\n",
"def download_csv():\n",
" \"\"\"Save generated data as CSV\"\"\"\n",
" global generated_df\n",
" if generated_df is not None:\n",
" generated_df.to_csv('pizza_feedback_data.csv', index=False)\n",
" return \"CSV downloaded!\"\n",
" return \"No data to download\"\n",
"\n",
"def download_json():\n",
" \"\"\"Save generated data as JSON\"\"\"\n",
" global generated_df\n",
" if generated_df is not None:\n",
" generated_df.to_json('pizza_feedback_data.json', orient='records', indent=2)\n",
" return \"JSON downloaded!\"\n",
" return \"No data to download\"\n",
"\n",
"# Create Gradio interface\n",
"with gr.Blocks(title=\"Pizza Feedback Data Generator\") as demo:\n",
" gr.Markdown(\"\"\"\n",
" # 🍕 Pizza Feedback Data Generator\n",
" Generate synthetic customer feedback for Nairobi pizza branches using AI models\n",
" \"\"\")\n",
" \n",
" with gr.Row():\n",
" with gr.Column():\n",
" model_selector = gr.Radio(\n",
" choices=list(MODELS.keys()),\n",
" label=\"Select AI Model\",\n",
" value=list(MODELS.keys())[0]\n",
" )\n",
" \n",
" num_records_slider = gr.Slider(\n",
" minimum=1,\n",
" maximum=50,\n",
" value=10,\n",
" step=1,\n",
" label=\"Number of Records\"\n",
" )\n",
" \n",
" generate_btn = gr.Button(\"Generate Feedback Data\", variant=\"primary\")\n",
" \n",
" with gr.Row():\n",
" status_output = gr.Textbox(label=\"Status\", interactive=False)\n",
" \n",
" with gr.Row():\n",
" dataframe_output = gr.Dataframe(\n",
" label=\"Generated Feedback Data\",\n",
" interactive=False\n",
" )\n",
" \n",
" with gr.Row():\n",
" csv_btn = gr.Button(\"Download CSV\")\n",
" json_btn = gr.Button(\"Download JSON\")\n",
" \n",
" # Event handlers\n",
" generate_btn.click(\n",
" fn=generate_feedback_data,\n",
" inputs=[model_selector, num_records_slider],\n",
" outputs=[dataframe_output, status_output]\n",
" )\n",
" \n",
" csv_btn.click(\n",
" fn=download_csv,\n",
" outputs=status_output\n",
" )\n",
" \n",
" json_btn.click(\n",
" fn=download_json,\n",
" outputs=status_output\n",
" )\n",
"\n",
"# Launch the interface\n",
"demo.launch()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"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.12.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,17 @@
Customer_ID,Branch,Rating,Order_Type,Feedback,Date
CUST1000,Westlands,1,Dine-in,Great pizza from Westlands! Quick delivery and hot food.,2024-10-17
CUST1001,CBD,1,Takeaway,Excellent service at CBD branch. Will order again!,2024-11-24
CUST1002,Kilimani,1,Delivery,Excellent service at Kilimani branch. Will order again!,2024-09-03
CUST1003,Parklands,5,Takeaway,Great pizza from Parklands! Quick delivery and hot food.,2024-08-05
CUST1004,Westlands,3,Delivery,Great pizza from Westlands! Quick delivery and hot food.,2024-01-12
CUST1005,CBD,5,Delivery,Great pizza from CBD! Quick delivery and hot food.,2024-01-10
CUST1006,Kilimani,1,Delivery,Long wait time at Kilimani but the pizza was worth it.,2024-09-12
CUST1007,Parklands,2,Delivery,Great pizza from Parklands! Quick delivery and hot food.,2024-05-27
CUST1008,Parklands,3,Dine-in,Excellent service at Parklands branch. Will order again!,2024-12-01
CUST1009,CBD,1,Dine-in,Excellent service at CBD branch. Will order again!,2024-10-09
CUST1010,Parklands,1,Takeaway,Average experience. Pizza was okay but nothing special.,2024-04-03
CUST1011,Westlands,2,Dine-in,Pizza was cold when it arrived at Westlands. Disappointed.,2024-01-02
CUST1012,Karen,2,Takeaway,Pizza was cold when it arrived at Karen. Disappointed.,2024-03-26
CUST1013,Westlands,3,Dine-in,Long wait time at Westlands but the pizza was worth it.,2024-11-17
CUST1014,Westlands,5,Takeaway,Average experience. Pizza was okay but nothing special.,2024-03-01
CUST1015,Parklands,3,Delivery,Excellent service at Parklands branch. Will order again!,2024-03-18
1 Customer_ID Branch Rating Order_Type Feedback Date
2 CUST1000 Westlands 1 Dine-in Great pizza from Westlands! Quick delivery and hot food. 2024-10-17
3 CUST1001 CBD 1 Takeaway Excellent service at CBD branch. Will order again! 2024-11-24
4 CUST1002 Kilimani 1 Delivery Excellent service at Kilimani branch. Will order again! 2024-09-03
5 CUST1003 Parklands 5 Takeaway Great pizza from Parklands! Quick delivery and hot food. 2024-08-05
6 CUST1004 Westlands 3 Delivery Great pizza from Westlands! Quick delivery and hot food. 2024-01-12
7 CUST1005 CBD 5 Delivery Great pizza from CBD! Quick delivery and hot food. 2024-01-10
8 CUST1006 Kilimani 1 Delivery Long wait time at Kilimani but the pizza was worth it. 2024-09-12
9 CUST1007 Parklands 2 Delivery Great pizza from Parklands! Quick delivery and hot food. 2024-05-27
10 CUST1008 Parklands 3 Dine-in Excellent service at Parklands branch. Will order again! 2024-12-01
11 CUST1009 CBD 1 Dine-in Excellent service at CBD branch. Will order again! 2024-10-09
12 CUST1010 Parklands 1 Takeaway Average experience. Pizza was okay but nothing special. 2024-04-03
13 CUST1011 Westlands 2 Dine-in Pizza was cold when it arrived at Westlands. Disappointed. 2024-01-02
14 CUST1012 Karen 2 Takeaway Pizza was cold when it arrived at Karen. Disappointed. 2024-03-26
15 CUST1013 Westlands 3 Dine-in Long wait time at Westlands but the pizza was worth it. 2024-11-17
16 CUST1014 Westlands 5 Takeaway Average experience. Pizza was okay but nothing special. 2024-03-01
17 CUST1015 Parklands 3 Delivery Excellent service at Parklands branch. Will order again! 2024-03-18

View File

@@ -0,0 +1,41 @@
# PrettyPage Generator
Transform any text into a beautiful, responsive webpage.
## What it does
Paste your text (notes, articles, documentation, etc.) and get a styled, single-page website using your exact words. Choose from different themes like Minimal, Professional, Colorful, or Modern Gradient.
## Requirements
- Python 3.8+
- OpenAI API key
- Google API key (optional, for Gemini)
## Setup
1. Install dependencies:
```bash
pip install gradio openai python-dotenv
```
2. Create a `.env` file:
```
OPENAI_API_KEY=your_openai_key_here
GOOGLE_API_KEY=your_google_key_here
```
3. Run the app:
```bash
python text_to_html.py
```
4. Open the link in your browser
## Usage
1. Paste your text in the input box
2. Choose a model (GPT-4o-mini or Gemini-Flash)
3. Select a style theme
4. Click "Generate Page"
5. Copy the HTML and save as `index.html`

View File

@@ -0,0 +1,222 @@
import os
from dotenv import load_dotenv
from openai import OpenAI
import gradio as gr
# --- Load environment keys ---
load_dotenv(override=True)
openai_api_key = os.getenv("OPENAI_API_KEY")
google_api_key = os.getenv("GOOGLE_API_KEY")
# --- Model config ---
MODEL_MAP = {
"GPT-4o-mini": {
"model": "gpt-4o-mini",
"key": openai_api_key,
"endpoint": "https://api.openai.com/v1"
},
"Gemini-Flash": {
"model": "gemini-2.5-flash",
"key": google_api_key,
"endpoint": "https://generativelanguage.googleapis.com/v1beta/openai/"
}
}
class PageBuilder:
def __init__(self, model_choice="GPT-4o-mini"):
self.set_model(model_choice)
def set_model(self, model_choice: str):
spec = MODEL_MAP[model_choice]
self.client = OpenAI(
api_key=spec["key"],
base_url=spec["endpoint"]
)
self.model_name = spec["model"]
def build_page(self, raw_text: str, theme: str) -> str:
"""
Ask the model for a self-contained HTML page (HTML + <style>) that
uses the exact user text as page content, styled according to theme.
The model is NOT allowed to rewrite, shorten, or invent extra content.
"""
system_prompt = f"""
You are a frontend designer.
Your job:
- Take the user's provided text content and wrap it in a beautiful, responsive, single-page website.
- DO NOT rewrite, summarize, or invent new content. Use their words exactly.
- You MAY break the text into sections/paragraph blocks and add headings ONLY if the headings are already strongly implied
by phrases like "Summary:", "Problem:", "Conclusion:", etc.
- If there are no obvious headings, just present the content nicely in cards/sections.
Styling rules:
- The site must look modern and clean.
- Use semantic HTML5: <header>, <main>, <section>, <footer>.
- Include all CSS inside a single <style> tag in the same HTML so it's fully self-contained.
- Make the layout centered, with readable max-width, good spacing, soft rounded corners, and balanced typography.
- The theme to apply is: "{theme}".
Theme hints:
- "Minimal": grayscale, lots of whitespace, system fonts, light gray dividers.
- "Professional": light background, subtle border cards, maybe a soft accent color for headings.
- "Colorful": playful accent colors, maybe soft tinted section backgrounds.
- "Modern Gradient": nice hero header section with a gentle gradient background and rounded containers.
Output rules:
- Return ONLY valid HTML starting with <!DOCTYPE html> and nothing else.
- Do NOT wrap the HTML in Markdown fences.
""".strip()
user_prompt = f"""
Here is the user's raw text. Remember: do not change the wording, just present it beautifully.
USER_TEXT_START
{raw_text}
USER_TEXT_END
""".strip()
response = self.client.chat.completions.create(
model=self.model_name,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
)
html = response.choices[0].message.content.strip()
if html.startswith("```"):
html = html.strip("`")
html = html.replace("html", "", 1).strip()
if html.endswith("```"):
html = html[:-3].strip()
return html
def apply_readability_fix(html_page: str) -> str:
"""
Ensures preview text is readable without destroying the design.
Only targets text color and ensures sufficient contrast.
"""
fix = """
<style>
/* Readability fixes for preview only */
body {
color: #1a1a1a !important;
}
p, li, span, div, td, th, blockquote {
color: #2d2d2d !important;
}
h1, h2, h3, h4, h5, h6 {
color: #0a0a0a !important;
}
/* Ensure backgrounds aren't forcing white text */
section, article, main {
color: #2d2d2d !important;
}
/* Fix if text is set to white/very light */
[style*="color: white"], [style*="color: #fff"], [style*="color: #ffffff"] {
color: #2d2d2d !important;
}
</style>
"""
return html_page.replace("</head>", fix + "\n</head>") if "</head>" in html_page else fix + html_page
def build_interface():
with gr.Blocks(
title="PrettyPage",
theme=gr.themes.Soft(primary_hue="indigo", neutral_hue="slate")
) as demo:
gr.Markdown(
"""
<div style="text-align:center">
<h1 style="margin-bottom:0.25rem;">✨ PrettyPage Generator</h1>
<p style="font-size:0.9rem; color:gray;">
Paste any text. Get a clean, beautiful, responsive webpage using your exact words.
</p>
</div>
""",
)
with gr.Row():
model_choice = gr.Radio(
choices=list(MODEL_MAP.keys()),
value="GPT-4o-mini",
label="Model",
info="Which model should generate the page?"
)
theme_choice = gr.Dropdown(
choices=[
"Minimal",
"Professional",
"Colorful",
"Modern Gradient"
],
value="Professional",
label="Style Theme",
info="Controls colors / layout vibe"
)
input_text = gr.Textbox(
label="Your Text Content",
lines=12,
placeholder=(
"Paste your notes, article, README, sales copy, lesson, etc.\n"
"We'll turn this into a styled webpage without changing your words."
),
)
generate_btn = gr.Button("🎨 Generate Page", variant="primary")
gr.Markdown("---")
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("**Generated HTML (copy & save as .html):**")
html_code_output = gr.Textbox(
label="HTML Source",
interactive=False,
lines=20,
max_lines=20,
show_copy_button=True
)
with gr.Column(scale=1):
gr.Markdown("**Live Preview:**")
live_preview = gr.HTML(
value=(
"<div style='color:gray;font-family:sans-serif;"
"padding:1rem;border:1px solid #ddd;border-radius:8px;'>"
"Your page preview will appear here.</div>"
),
)
# click handler: build the page fresh each time
def handle_generate(user_text, chosen_model, chosen_theme):
porter = PageBuilder(chosen_model)
html_page = porter.build_page(user_text, chosen_theme)
fixed_preview = apply_readability_fix(html_page)
# left pane shows code, right pane renders preview
return html_page, fixed_preview
generate_btn.click(
fn=handle_generate,
inputs=[input_text, model_choice, theme_choice],
outputs=[html_code_output, live_preview],
)
gr.Markdown(
"""
<div style="text-align:center; font-size:0.8rem; color:gray; margin-top:2rem;">
Tip: Save the HTML Source above as <code>index.html</code> and open it in your browser.
</div>
"""
)
return demo
if __name__ == "__main__":
demo = build_interface()
demo.launch(inbrowser=True)

View File

@@ -0,0 +1,255 @@
# 🔶 Multi-Language Code Complexity Annotator
An automated tool that analyzes source code and annotates it with Big-O complexity estimates, complete with syntax highlighting and optional AI-powered code reviews.
## 🎯 What It Does
Understanding time complexity (Big-O notation) is crucial for writing efficient algorithms, identifying bottlenecks, making informed optimization decisions, and passing technical interviews.
Analyzing complexity manually is tedious and error-prone. This tool **automates** the entire process—detecting loops, recursion, and functions, then annotating code with Big-O estimates and explanations.
### Core Features
- 📊 **Automatic Detection** - Identifies loops, recursion, and functions across 13+ programming languages
- 🧮 **Complexity Estimation** - Calculates Big-O complexity (O(1), O(n), O(n²), O(log n), etc.)
- 💬 **Inline Annotations** - Inserts explanatory comments directly into your code
- 🎨 **Syntax Highlighting** - Generates beautiful HTML previews with orange-colored complexity comments
- 🤖 **AI Code Review** - Optional LLaMA-powered analysis for optimization suggestions
- 💾 **Export Options** - Download annotated source code and Markdown previews
## 🌐 Supported Languages
Python • JavaScript • TypeScript • Java • C • C++ • C# • Go • PHP • Swift • Ruby • Kotlin • Rust
## 🛠️ Tech Stack
- **HuggingFace Transformers** - LLM model loading and inference
- **LLaMA 3.2** - AI-powered code review
- **Gradio** - Interactive web interface
- **Pygments** - Syntax highlighting
- **PyTorch** - Deep learning framework
- **Regex Analysis** - Heuristic complexity detection
## 📋 Prerequisites
- Python 3.12+
- `uv` package manager (or `pip`)
- 4GB+ RAM (for basic use without AI)
- 14GB+ RAM (for AI code review with LLaMA models)
- Optional: NVIDIA GPU with CUDA (for model quantization)
## 🚀 Installation
### 1. Clone the Repository
```bash
cd week4
```
### 2. Install Dependencies
```bash
uv pip install -U pip
uv pip install transformers accelerate gradio torch --extra-index-url https://download.pytorch.org/whl/cpu
uv pip install bitsandbytes pygments python-dotenv
```
> **Note:** This installs the CPU-only version of PyTorch. For GPU support, remove the `--extra-index-url` flag.
### 3. Set Up HuggingFace Token (Optional - for AI Features)
Create a `.env` file in the `week4` directory:
```env
HF_TOKEN=hf_your_token_here
```
Get your token at: https://huggingface.co/settings/tokens
> **Required for:** LLaMA models (requires accepting Meta's license agreement)
## 💡 Usage
### Option 1: Jupyter Notebook
Open and run `week4 EXERCISE_hopeogbons.ipynb`:
```bash
jupyter notebook "week4 EXERCISE_hopeogbons.ipynb"
```
Run all cells in order. The Gradio interface will launch at `http://127.0.0.1:7861`
### Option 2: Web Interface
Once the Gradio app is running:
#### **Without AI Review (No Model Needed)**
1. Upload a code file (.py, .js, .java, etc.)
2. Uncheck "Generate AI Code Review"
3. Click "🚀 Process & Annotate"
4. View syntax-highlighted code with Big-O annotations
5. Download the annotated source + Markdown
#### **With AI Review (Requires Model)**
1. Click "🔄 Load Model" (wait 2-5 minutes for first download)
2. Upload your code file
3. Check "Generate AI Code Review"
4. Adjust temperature/tokens if needed
5. Click "🚀 Process & Annotate"
6. Read AI-generated optimization suggestions
## 📊 How It Works
### Complexity Detection Algorithm
The tool uses **heuristic pattern matching** to estimate Big-O complexity:
1. **Detect Blocks** - Regex patterns find functions, loops, and recursion
2. **Analyze Loops** - Count nesting depth:
- 1 loop = O(n)
- 2 nested loops = O(n²)
- 3 nested loops = O(n³)
3. **Analyze Recursion** - Pattern detection:
- Divide-and-conquer (binary search) = O(log n)
- Single recursive call = O(n)
- Multiple recursive calls = O(2^n)
4. **Aggregate** - Functions inherit worst-case complexity of inner operations
### Example Output
**Input (Python):**
```python
def bubble_sort(arr):
for i in range(len(arr)):
for j in range(len(arr) - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
```
**Output (Annotated):**
```python
def bubble_sort(arr):
# Big-O: O(n^2)
# Explanation: Nested loops indicate quadratic time.
for i in range(len(arr)):
for j in range(len(arr) - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
```
## 🧠 AI Model Options
### CPU/Mac (No GPU)
- `meta-llama/Llama-3.2-1B` (Default, ~1GB, requires HF approval)
- `gpt2` (No approval needed, ~500MB)
- `microsoft/DialoGPT-medium` (~1GB)
### GPU Users
- Any model with 8-bit or 4-bit quantization enabled
- `meta-llama/Llama-2-7b-chat-hf` (requires approval)
### Memory Requirements
- **Without quantization:** ~14GB RAM (7B models) or ~26GB (13B models)
- **With 8-bit quantization:** ~50% reduction (GPU required)
- **With 4-bit quantization:** ~75% reduction (GPU required)
## ⚙️ Configuration
### File Limits
- Max file size: **2 MB**
- Supported extensions: `.py`, `.js`, `.ts`, `.java`, `.c`, `.cpp`, `.cs`, `.go`, `.php`, `.swift`, `.rb`, `.kt`, `.rs`
### Model Parameters
- **Temperature** (0.0 - 1.5): Controls randomness
- Lower = more deterministic
- Higher = more creative
- **Max Tokens** (16 - 1024): Maximum length of AI review
## 📁 Project Structure
```
week4/
├── week4 EXERCISE_hopeogbons.ipynb # Main application notebook
├── README.md # This file
└── .env # HuggingFace token (create this)
```
## 🐛 Troubleshooting
### Model Loading Issues
**Error:** "Model not found" or "Access denied"
- **Solution:** Accept Meta's license at https://huggingface.co/meta-llama/Llama-3.2-1B
- Ensure your `.env` file contains a valid HF_TOKEN
### Memory Issues
**Error:** "Out of memory" during model loading
- **Solution:** Use a smaller model like `gpt2` or `microsoft/DialoGPT-medium`
- Try 8-bit or 4-bit quantization (GPU required)
### Quantization Requires GPU
**Error:** "Quantization requires CUDA"
- **Solution:** Disable both 4-bit and 8-bit quantization checkboxes
- Run on CPU with smaller models
### File Upload Issues
**Error:** "Unsupported file extension"
- **Solution:** Ensure your file has one of the supported extensions
- Check that the file size is under 2MB
## 🎓 Use Cases
- **Code Review** - Automated complexity analysis for pull requests
- **Interview Prep** - Understand algorithm efficiency before coding interviews
- **Performance Optimization** - Identify bottlenecks in existing code
- **Education** - Learn Big-O notation through practical examples
- **Documentation** - Auto-generate complexity documentation
## 📝 Notes
- First model load downloads weights (~1-14GB depending on model)
- Subsequent runs load from cache (much faster)
- Complexity estimates are heuristic-based, not formally verified
- For production use, consider manual verification of critical algorithms
## 🤝 Contributing
This is a learning project from the Andela LLM Engineering course (Week 4). Feel free to extend it with:
- Additional language support
- More sophisticated complexity detection
- Integration with CI/CD pipelines
- Support for space complexity analysis
## 📄 License
Educational project - use as reference for learning purposes.
## 🙏 Acknowledgments
- **OpenAI Whisper** for inspiration on model integration
- **HuggingFace** for providing the Transformers library
- **Meta** for LLaMA models
- **Gradio** for the excellent UI framework
- **Andela** for the LLM Engineering curriculum
---
**Built with ❤️ as part of Week 4 LLM Engineering coursework**

View File

@@ -0,0 +1,9 @@
# Python Function
# This function takes a list of items and returns all possible pairs of items
def all_pairs(items):
pairs = []
for i in range(len(items)):
for j in range(i + 1, len(items)):
pairs.append((items[i], items[j]))
return pairs

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,294 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "50bd7e7c",
"metadata": {},
"source": [
"# Unit Test Writer"
]
},
{
"cell_type": "markdown",
"id": "ad339d11",
"metadata": {},
"source": [
"### Welcome to the Unit Test Writer - an AI-powered tool that automatically generates comprehensive unit tests for your code across multiple programming languages. Simply paste your code, select your language, and let AI create thorough test suites in seconds!"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "27e4e719",
"metadata": {},
"outputs": [],
"source": [
"# imports\n",
"\n",
"import os\n",
"import io\n",
"import sys\n",
"from dotenv import load_dotenv\n",
"from openai import OpenAI\n",
"import gradio as gr\n",
"import subprocess\n",
"from IPython.display import Markdown, display"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3572f5fa",
"metadata": {},
"outputs": [],
"source": [
"load_dotenv(override=True)\n",
"openai_api_key = os.getenv('OPENAI_API_KEY')\n",
"anthropic_api_key = os.getenv('ANTHROPIC_API_KEY')\n",
"google_api_key = os.getenv('GOOGLE_API_KEY')\n",
"grok_api_key = os.getenv('GROK_API_KEY')\n",
"groq_api_key = os.getenv('GROQ_API_KEY')\n",
"openrouter_api_key = os.getenv('OPENROUTER_API_KEY')\n",
"\n",
"if openai_api_key:\n",
" print(f\"OpenAI API Key exists and begins {openai_api_key[:8]}\")\n",
"else:\n",
" print(\"OpenAI API Key not set\")\n",
" \n",
"if anthropic_api_key:\n",
" print(f\"Anthropic API Key exists and begins {anthropic_api_key[:7]}\")\n",
"else:\n",
" print(\"Anthropic API Key not set (and this is optional)\")\n",
"\n",
"if google_api_key:\n",
" print(f\"Google API Key exists and begins {google_api_key[:2]}\")\n",
"else:\n",
" print(\"Google API Key not set (and this is optional)\")\n",
"\n",
"if grok_api_key:\n",
" print(f\"Grok API Key exists and begins {grok_api_key[:4]}\")\n",
"else:\n",
" print(\"Grok API Key not set (and this is optional)\")\n",
"\n",
"if groq_api_key:\n",
" print(f\"Groq API Key exists and begins {groq_api_key[:4]}\")\n",
"else:\n",
" print(\"Groq API Key not set (and this is optional)\")\n",
"\n",
"if openrouter_api_key:\n",
" print(f\"OpenRouter API Key exists and begins {openrouter_api_key[:6]}\")\n",
"else:\n",
" print(\"OpenRouter API Key not set (and this is optional)\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "05293821",
"metadata": {},
"outputs": [],
"source": [
"# Connect to client libraries\n",
"\n",
"openai = OpenAI()\n",
"\n",
"anthropic_url = \"https://api.anthropic.com/v1/\"\n",
"gemini_url = \"https://generativelanguage.googleapis.com/v1beta/openai/\"\n",
"grok_url = \"https://api.x.ai/v1\"\n",
"groq_url = \"https://api.groq.com/openai/v1\"\n",
"ollama_url = \"http://localhost:11434/v1\"\n",
"openrouter_url = \"https://openrouter.ai/api/v1\"\n",
"\n",
"anthropic = OpenAI(api_key=anthropic_api_key, base_url=anthropic_url)\n",
"gemini = OpenAI(api_key=google_api_key, base_url=gemini_url)\n",
"grok = OpenAI(api_key=grok_api_key, base_url=grok_url)\n",
"groq = OpenAI(api_key=groq_api_key, base_url=groq_url)\n",
"ollama = OpenAI(api_key=\"ollama\", base_url=ollama_url)\n",
"openrouter = OpenAI(api_key=openrouter_api_key, base_url=openrouter_url)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "41eec7d3",
"metadata": {},
"outputs": [],
"source": [
"models = [\"gpt-5\", \"claude-sonnet-4-5-20250929\", \"grok-4\", \"gemini-2.5-pro\", \"qwen2.5-coder\", \"deepseek-coder-v2\", \"gpt-oss:20b\", \"qwen/qwen3-coder-30b-a3b-instruct\", \"openai/gpt-oss-120b\", ]\n",
"\n",
"clients = {\"gpt-5\": openai, \"claude-sonnet-4-5-20250929\": anthropic, \"grok-4\": grok, \"gemini-2.5-pro\": gemini, \"openai/gpt-oss-120b\": groq, \"qwen2.5-coder\": ollama, \"deepseek-coder-v2\": ollama, \"gpt-oss:20b\": ollama, \"qwen/qwen3-coder-30b-a3b-instruct\": openrouter}"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "05ca8fcc",
"metadata": {},
"outputs": [],
"source": [
"# Function to generate unit tests\n",
"def generate_unit_tests(code, language, model_name):\n",
" \"\"\"Generate unit tests for the provided code\"\"\"\n",
" \n",
" if not code.strip():\n",
" return \"Please provide some code to generate tests for.\"\n",
" \n",
" # Select the appropriate client\n",
" client = clients.get(model_name, openai)\n",
" \n",
" # Create the prompt based on language\n",
" test_frameworks = {\n",
" \"Python\": \"pytest\",\n",
" \"JavaScript\": \"Jest\",\n",
" \"TypeScript\": \"Jest\",\n",
" \"Java\": \"JUnit\",\n",
" \"C#\": \"NUnit\",\n",
" \"Go\": \"testing package\",\n",
" \"Ruby\": \"RSpec\",\n",
" \"PHP\": \"PHPUnit\",\n",
" \"Rust\": \"built-in test framework\"\n",
" }\n",
" \n",
" framework = test_frameworks.get(language, \"appropriate testing framework\")\n",
" \n",
" prompt = f\"\"\"You are a unit test expert. Generate comprehensive unit tests for the following {language} code using {framework}.\n",
"\n",
" Code to test:\n",
" ```{language.lower()}\n",
" {code}\n",
" ```\n",
"\n",
" Requirements:\n",
" - Create thorough unit tests covering normal cases, edge cases, and error cases\n",
" - Use {framework} syntax and best practices\n",
" - Include clear test names that describe what is being tested\n",
" - Add comments explaining complex test scenarios\n",
" - Ensure tests are independent and can run in any order\n",
"\n",
" Provide ONLY the test code, no explanations.\"\"\"\n",
"\n",
" try:\n",
" # Call the API\n",
" response = client.chat.completions.create(\n",
" model=model_name,\n",
" messages=[\n",
" {\"role\": \"user\", \"content\": prompt}\n",
" ],\n",
" temperature=0.7,\n",
" )\n",
" \n",
" return response.choices[0].message.content\n",
" \n",
" except Exception as e:\n",
" return f\"Error generating tests: {str(e)}\"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d51e13d3",
"metadata": {},
"outputs": [],
"source": [
"# Create Gradio interface\n",
"def create_interface():\n",
" with gr.Blocks(title=\"Unit Test Generator\") as demo:\n",
" gr.Markdown(\"# 🧪 Unit Test Generator\")\n",
" gr.Markdown(\"Paste your code, select the language, and generate comprehensive unit tests!\")\n",
" \n",
" with gr.Row():\n",
" with gr.Column():\n",
" code_input = gr.TextArea(\n",
" label=\"Your Code\",\n",
" placeholder=\"Paste your code here...\",\n",
" lines=15\n",
" )\n",
" \n",
" with gr.Row():\n",
" language_dropdown = gr.Dropdown(\n",
" choices=[\"Python\", \"JavaScript\", \"TypeScript\", \"Java\", \"C#\", \"Go\", \"Ruby\", \"PHP\", \"Rust\"],\n",
" value=\"Python\",\n",
" label=\"Language\"\n",
" )\n",
" \n",
" model_dropdown = gr.Dropdown(\n",
" choices=models,\n",
" value=\"gpt-5\",\n",
" label=\"Model\"\n",
" )\n",
" \n",
" generate_btn = gr.Button(\"Generate Unit Tests\", variant=\"primary\")\n",
" \n",
" with gr.Column():\n",
" output = gr.TextArea(\n",
" label=\"Generated Unit Tests\",\n",
" lines=15\n",
" )\n",
" \n",
" # Example\n",
" gr.Markdown(\"### Example\")\n",
" gr.Examples(\n",
" examples=[\n",
" [\"\"\"def add(a, b):\n",
" return a + b\n",
"\n",
"def multiply(a, b):\n",
" return a * b\n",
"\n",
"def divide(a, b):\n",
" if b == 0:\n",
" raise ValueError(\"Cannot divide by zero\")\n",
" return a / b\"\"\", \"Python\", \"gpt-5\"],\n",
" [\"\"\"function isPalindrome(str) {\n",
" const cleaned = str.toLowerCase().replace(/[^a-z0-9]/g, '');\n",
" return cleaned === cleaned.split('').reverse().join('');\n",
"}\"\"\", \"JavaScript\", \"gpt-5\"]\n",
" ],\n",
" inputs=[code_input, language_dropdown, model_dropdown]\n",
" )\n",
" \n",
" # Connect the button\n",
" generate_btn.click(\n",
" fn=generate_unit_tests,\n",
" inputs=[code_input, language_dropdown, model_dropdown],\n",
" outputs=output\n",
" )\n",
" \n",
" return demo"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d8eff8ac",
"metadata": {},
"outputs": [],
"source": [
"# Launch the interface\n",
"demo = create_interface()\n",
"demo.launch(share=False)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"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.12.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,498 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "b8be8252",
"metadata": {},
"outputs": [],
"source": [
"!uv pip install pytest"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ba193fd5",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import re\n",
"import ast\n",
"import sys\n",
"import uuid\n",
"import json\n",
"import textwrap\n",
"import subprocess\n",
"from pathlib import Path\n",
"from dataclasses import dataclass\n",
"from typing import List, Protocol, Tuple, Dict, Optional\n",
"\n",
"from dotenv import load_dotenv\n",
"from openai import OpenAI\n",
"from openai import BadRequestError as _OpenAIBadRequest\n",
"import gradio as gr\n",
"\n",
"load_dotenv(override=True)\n",
"\n",
"# --- Provider base URLs (Gemini & Groq speak OpenAI-compatible API) ---\n",
"GEMINI_BASE = \"https://generativelanguage.googleapis.com/v1beta/openai/\"\n",
"GROQ_BASE = \"https://api.groq.com/openai/v1\"\n",
"\n",
"# --- API Keys (add these in your .env) ---\n",
"openai_api_key = os.getenv(\"OPENAI_API_KEY\") # OpenAI\n",
"google_api_key = os.getenv(\"GOOGLE_API_KEY\") # Gemini\n",
"groq_api_key = os.getenv(\"GROQ_API_KEY\") # Groq\n",
"\n",
"# --- Clients ---\n",
"openai_client = OpenAI() # OpenAI default (reads OPENAI_API_KEY)\n",
"gemini_client = OpenAI(api_key=google_api_key, base_url=GEMINI_BASE) if google_api_key else None\n",
"groq_client = OpenAI(api_key=groq_api_key, base_url=GROQ_BASE) if groq_api_key else None\n",
"\n",
"# --- Model registry: label -> { client, model } ---\n",
"MODEL_REGISTRY: Dict[str, Dict[str, object]] = {}\n",
"\n",
"def _register(label: str, client: Optional[OpenAI], model_id: str):\n",
" \"\"\"Add a model to the registry only if its client is configured.\"\"\"\n",
" if client is not None:\n",
" MODEL_REGISTRY[label] = {\"client\": client, \"model\": model_id}\n",
"\n",
"# OpenAI\n",
"_register(\"OpenAI • GPT-5\", openai_client, \"gpt-5\")\n",
"_register(\"OpenAI • GPT-5 Nano\", openai_client, \"gpt-5-nano\")\n",
"_register(\"OpenAI • GPT-4o-mini\", openai_client, \"gpt-4o-mini\")\n",
"\n",
"# Gemini (Google)\n",
"_register(\"Gemini • 2.5 Pro\", gemini_client, \"gemini-2.5-pro\")\n",
"_register(\"Gemini • 2.5 Flash\", gemini_client, \"gemini-2.5-flash\")\n",
"\n",
"# Groq\n",
"_register(\"Groq • Llama 3.1 8B\", groq_client, \"llama-3.1-8b-instant\")\n",
"_register(\"Groq • Llama 3.3 70B\", groq_client, \"llama-3.3-70b-versatile\")\n",
"_register(\"Groq • GPT-OSS 20B\", groq_client, \"openai/gpt-oss-20b\")\n",
"_register(\"Groq • GPT-OSS 120B\", groq_client, \"openai/gpt-oss-120b\")\n",
"\n",
"DEFAULT_MODEL = next(iter(MODEL_REGISTRY.keys()), None)\n",
"\n",
"print(f\"Providers configured → OpenAI:{bool(openai_api_key)} Gemini:{bool(google_api_key)} Groq:{bool(groq_api_key)}\")\n",
"print(\"Models available →\", \", \".join(MODEL_REGISTRY.keys()) or \"None (add API keys in .env)\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e5d6b0f2",
"metadata": {},
"outputs": [],
"source": [
"class CompletionClient(Protocol):\n",
" \"\"\"Any LLM client provides a .complete() method using a registry label.\"\"\"\n",
" def complete(self, *, model_label: str, system: str, user: str) -> str: ...\n",
"\n",
"\n",
"def _extract_code_or_text(s: str) -> str:\n",
" \"\"\"Prefer fenced python if present; otherwise return raw text.\"\"\"\n",
" m = re.search(r\"```(?:python)?\\s*(.*?)```\", s, flags=re.S | re.I)\n",
" return m.group(1).strip() if m else s.strip()\n",
"\n",
"\n",
"class MultiModelChatClient:\n",
" \"\"\"Routes requests to the right provider/client based on model label.\"\"\"\n",
" def __init__(self, registry: Dict[str, Dict[str, object]]):\n",
" self._registry = registry\n",
"\n",
" def _call(self, *, client: OpenAI, model_id: str, system: str, user: str) -> str:\n",
" params = {\n",
" \"model\": model_id,\n",
" \"messages\": [\n",
" {\"role\": \"system\", \"content\": system},\n",
" {\"role\": \"user\", \"content\": user},\n",
" ],\n",
" }\n",
" resp = client.chat.completions.create(**params) # do NOT send temperature for strict providers\n",
" text = (resp.choices[0].message.content or \"\").strip()\n",
" return _extract_code_or_text(text)\n",
"\n",
" def complete(self, *, model_label: str, system: str, user: str) -> str:\n",
" if model_label not in self._registry:\n",
" raise ValueError(f\"Unknown model label: {model_label}\")\n",
" info = self._registry[model_label]\n",
" client = info[\"client\"]\n",
" model = info[\"model\"]\n",
" try:\n",
" return self._call(client=client, model_id=str(model), system=system, user=user)\n",
" except _OpenAIBadRequest as e:\n",
" # Providers may reject stray params; we don't send any, but retry anyway.\n",
" if \"temperature\" in str(e).lower():\n",
" return self._call(client=client, model_id=str(model), system=system, user=user)\n",
" raise\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "31558bf0",
"metadata": {},
"outputs": [],
"source": [
"@dataclass(frozen=True)\n",
"class SymbolInfo:\n",
" kind: str # \"function\" | \"class\" | \"method\"\n",
" name: str\n",
" signature: str\n",
" lineno: int\n",
"\n",
"class PublicAPIExtractor:\n",
" \"\"\"Extract concise 'public API' summary from a Python module.\"\"\"\n",
" def extract(self, source: str) -> List[SymbolInfo]:\n",
" tree = ast.parse(source)\n",
" out: List[SymbolInfo] = []\n",
" for node in tree.body:\n",
" if isinstance(node, ast.FunctionDef) and not node.name.startswith(\"_\"):\n",
" out.append(SymbolInfo(\"function\", node.name, self._sig(node), node.lineno))\n",
" elif isinstance(node, ast.ClassDef) and not node.name.startswith(\"_\"):\n",
" out.append(SymbolInfo(\"class\", node.name, node.name, node.lineno))\n",
" for sub in node.body:\n",
" if isinstance(sub, ast.FunctionDef) and not sub.name.startswith(\"_\"):\n",
" out.append(SymbolInfo(\"method\",\n",
" f\"{node.name}.{sub.name}\",\n",
" self._sig(sub),\n",
" sub.lineno))\n",
" return sorted(out, key=lambda s: (s.kind, s.name.lower(), s.lineno))\n",
"\n",
" def _sig(self, fn: ast.FunctionDef) -> str:\n",
" args = [a.arg for a in fn.args.args]\n",
" if fn.args.vararg:\n",
" args.append(\"*\" + fn.args.vararg.arg)\n",
" args.extend(a.arg + \"=?\" for a in fn.args.kwonlyargs)\n",
" if fn.args.kwarg:\n",
" args.append(\"**\" + fn.args.kwarg.arg)\n",
" ret = \"\"\n",
" if fn.returns is not None:\n",
" try:\n",
" ret = f\" -> {ast.unparse(fn.returns)}\"\n",
" except Exception:\n",
" pass\n",
" return f\"def {fn.name}({', '.join(args)}){ret}:\"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3aeadedc",
"metadata": {},
"outputs": [],
"source": [
"class PromptBuilder:\n",
" \"\"\"Builds deterministic prompts for pytest generation.\"\"\"\n",
" SYSTEM = (\n",
" \"You are a senior Python engineer. Produce a single, self-contained pytest file.\\n\"\n",
" \"Rules:\\n\"\n",
" \"- Output only Python test code (no prose, no markdown fences).\\n\"\n",
" \"- Use plain pytest tests (functions), no classes unless unavoidable.\\n\"\n",
" \"- Deterministic: avoid network/IO; seed randomness if used.\\n\"\n",
" \"- Import the target module by module name only.\\n\"\n",
" \"- Cover every public function and method with at least one tiny test.\\n\"\n",
" \"- Prefer straightforward, fast assertions.\\n\"\n",
" )\n",
"\n",
" def build_user(self, *, module_name: str, source: str, symbols: List[SymbolInfo]) -> str:\n",
" summary = \"\\n\".join(f\"- {s.kind:<6} {s.signature}\" for s in symbols) or \"- (no public symbols)\"\n",
" return textwrap.dedent(f\"\"\"\n",
" Create pytest tests for module `{module_name}`.\n",
"\n",
" Public API Summary:\n",
" {summary}\n",
"\n",
" Constraints:\n",
" - Import as: `import {module_name} as mod`\n",
" - Keep tests tiny, fast, and deterministic.\n",
"\n",
" Full module source (for reference):\n",
" # --- BEGIN SOURCE {module_name}.py ---\n",
" {source}\n",
" # --- END SOURCE ---\n",
" \"\"\").strip()\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a45ac5be",
"metadata": {},
"outputs": [],
"source": [
"def _ensure_header_and_import(code: str, module_name: str) -> str:\n",
" \"\"\"Ensure tests import pytest and the target module as 'mod'.\"\"\"\n",
" code = code.strip()\n",
" needs_pytest = \"import pytest\" not in code\n",
" has_mod = (f\"import {module_name} as mod\" in code) or (f\"from {module_name} import\" in code)\n",
" needs_import = not has_mod\n",
"\n",
" header = []\n",
" if needs_pytest:\n",
" header.append(\"import pytest\")\n",
" if needs_import:\n",
" header.append(f\"import {module_name} as mod\")\n",
"\n",
" return (\"\\n\".join(header) + \"\\n\\n\" + code) if header else code\n",
"\n",
"\n",
"def build_module_name_from_path(path: str) -> str:\n",
" return Path(path).stem\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "787e58b6",
"metadata": {},
"outputs": [],
"source": [
"class TestGenerator:\n",
" \"\"\"Extraction → prompt → model → polish.\"\"\"\n",
" def __init__(self, llm: CompletionClient):\n",
" self._llm = llm\n",
" self._extractor = PublicAPIExtractor()\n",
" self._prompts = PromptBuilder()\n",
"\n",
" def generate_tests(self, model_label: str, module_name: str, source: str) -> str:\n",
" symbols = self._extractor.extract(source)\n",
" user = self._prompts.build_user(module_name=module_name, source=source, symbols=symbols)\n",
" raw = self._llm.complete(model_label=model_label, system=self._prompts.SYSTEM, user=user)\n",
" return _ensure_header_and_import(raw, module_name)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8402f62f",
"metadata": {},
"outputs": [],
"source": [
"def _parse_pytest_summary(output: str) -> Tuple[str, Dict[str, int]]:\n",
" \"\"\"\n",
" Parse the final summary line like:\n",
" '3 passed, 1 failed, 2 skipped in 0.12s'\n",
" Return (summary_line, counts_dict).\n",
" \"\"\"\n",
" summary_line = \"\"\n",
" for line in output.strip().splitlines()[::-1]: # scan from end\n",
" if \" passed\" in line or \" failed\" in line or \" error\" in line or \" skipped\" in line or \" deselected\" in line:\n",
" summary_line = line.strip()\n",
" break\n",
"\n",
" counts = {\"passed\": 0, \"failed\": 0, \"errors\": 0, \"skipped\": 0, \"xfail\": 0, \"xpassed\": 0}\n",
" m = re.findall(r\"(\\d+)\\s+(passed|failed|errors?|skipped|xfailed|xpassed)\", summary_line)\n",
" for num, kind in m:\n",
" if kind.startswith(\"error\"):\n",
" counts[\"errors\"] += int(num)\n",
" elif kind == \"passed\":\n",
" counts[\"passed\"] += int(num)\n",
" elif kind == \"failed\":\n",
" counts[\"failed\"] += int(num)\n",
" elif kind == \"skipped\":\n",
" counts[\"skipped\"] += int(num)\n",
" elif kind == \"xfailed\":\n",
" counts[\"xfail\"] += int(num)\n",
" elif kind == \"xpassed\":\n",
" counts[\"xpassed\"] += int(num)\n",
"\n",
" return summary_line or \"(no summary line found)\", counts\n",
"\n",
"\n",
"def run_pytest_on_snippet(module_name: str, module_code: str, tests_code: str) -> Tuple[str, str]:\n",
" \"\"\"\n",
" Create an isolated temp workspace, write module + tests, run pytest,\n",
" and return (human_summary, full_cli_output).\n",
" \"\"\"\n",
" if not module_name or not module_code.strip() or not tests_code.strip():\n",
" return \"❌ Provide module name, module code, and tests.\", \"\"\n",
"\n",
" run_id = uuid.uuid4().hex[:8]\n",
" base = Path(\".pytest_runs\") / f\"run_{run_id}\"\n",
" tests_dir = base / \"tests\"\n",
" tests_dir.mkdir(parents=True, exist_ok=True)\n",
"\n",
" # Write module and tests\n",
" (base / f\"{module_name}.py\").write_text(module_code, encoding=\"utf-8\")\n",
" (tests_dir / f\"test_{module_name}.py\").write_text(tests_code, encoding=\"utf-8\")\n",
"\n",
" # Run pytest with this temp dir on PYTHONPATH\n",
" env = os.environ.copy()\n",
" env[\"PYTHONPATH\"] = str(base) + os.pathsep + env.get(\"PYTHONPATH\", \"\")\n",
" cmd = [sys.executable, \"-m\", \"pytest\", \"-q\"] # quiet output, but still includes summary\n",
" proc = subprocess.run(cmd, cwd=base, env=env, text=True, capture_output=True)\n",
"\n",
" full_out = (proc.stdout or \"\") + (\"\\n\" + proc.stderr if proc.stderr else \"\")\n",
" summary_line, counts = _parse_pytest_summary(full_out)\n",
"\n",
" badges = []\n",
" for key in (\"passed\", \"failed\", \"errors\", \"skipped\", \"xpassed\", \"xfail\"):\n",
" val = counts.get(key, 0)\n",
" if val:\n",
" badges.append(f\"**{key}: {val}**\")\n",
" badges = \" • \".join(badges) if badges else \"no tests collected?\"\n",
"\n",
" human = f\"{summary_line}\\n\\n{badges}\"\n",
" return human, full_out\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5d240ce5",
"metadata": {},
"outputs": [],
"source": [
"LLM = MultiModelChatClient(MODEL_REGISTRY)\n",
"SERVICE = TestGenerator(LLM)\n",
"\n",
"def generate_from_code(model_label: str, module_name: str, code: str, save: bool, out_dir: str) -> Tuple[str, str]:\n",
" if not model_label or model_label not in MODEL_REGISTRY:\n",
" return \"\", \"❌ Pick a model (or add API keys for providers in .env).\"\n",
" if not module_name.strip():\n",
" return \"\", \"❌ Please provide a module name.\"\n",
" if not code.strip():\n",
" return \"\", \"❌ Please paste some Python code.\"\n",
"\n",
" tests_code = SERVICE.generate_tests(model_label=model_label, module_name=module_name.strip(), source=code)\n",
" saved = \"\"\n",
" if save:\n",
" out = Path(out_dir or \"tests\")\n",
" out.mkdir(parents=True, exist_ok=True)\n",
" out_path = out / f\"test_{module_name}.py\"\n",
" out_path.write_text(tests_code, encoding=\"utf-8\")\n",
" saved = f\"✅ Saved to {out_path}\"\n",
" return tests_code, saved\n",
"\n",
"\n",
"def generate_from_file(model_label: str, file_obj, save: bool, out_dir: str) -> Tuple[str, str]:\n",
" if file_obj is None:\n",
" return \"\", \"❌ Please upload a .py file.\"\n",
" code = file_obj.decode(\"utf-8\")\n",
" module_name = build_module_name_from_path(\"uploaded_module.py\")\n",
" return generate_from_code(model_label, module_name, code, save, out_dir)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e3e1401a",
"metadata": {},
"outputs": [],
"source": [
"EXAMPLE_CODE = \"\"\"\\\n",
"def add(a: int, b: int) -> int:\n",
" return a + b\n",
"\n",
"def divide(a: float, b: float) -> float:\n",
" if b == 0:\n",
" raise ZeroDivisionError(\"b must be non-zero\")\n",
" return a / b\n",
"\n",
"class Counter:\n",
" def __init__(self, start: int = 0):\n",
" self.value = start\n",
"\n",
" def inc(self, by: int = 1):\n",
" self.value += by\n",
" return self.value\n",
"\"\"\"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f802450e",
"metadata": {},
"outputs": [],
"source": [
"with gr.Blocks(title=\"PyTest Generator\") as ui:\n",
" gr.Markdown(\n",
" \"## 🧪 PyTest Generator (Week 4 • Community Contribution)\\n\"\n",
" \"Generate **minimal, deterministic** pytest tests from a Python module using your chosen model/provider.\"\n",
" )\n",
"\n",
" with gr.Row(equal_height=True):\n",
" # LEFT: inputs (module code)\n",
" with gr.Column(scale=6):\n",
" with gr.Row():\n",
" model_dd = gr.Dropdown(\n",
" list(MODEL_REGISTRY.keys()),\n",
" value=DEFAULT_MODEL,\n",
" label=\"Model (OpenAI, Gemini, Groq)\"\n",
" )\n",
" module_name_tb = gr.Textbox(\n",
" label=\"Module name (used in `import <name> as mod`)\",\n",
" value=\"mymodule\"\n",
" )\n",
" code_in = gr.Code(\n",
" label=\"Python module code\",\n",
" language=\"python\",\n",
" lines=24,\n",
" value=EXAMPLE_CODE\n",
" )\n",
" with gr.Row():\n",
" save_cb = gr.Checkbox(label=\"Also save generated tests to /tests\", value=True)\n",
" out_dir_tb = gr.Textbox(label=\"Output folder\", value=\"tests\")\n",
" gen_btn = gr.Button(\"Generate tests\", variant=\"primary\")\n",
"\n",
" # RIGHT: outputs (generated tests + pytest run)\n",
" with gr.Column(scale=6):\n",
" tests_out = gr.Code(label=\"Generated tests (pytest)\", language=\"python\", lines=24)\n",
" with gr.Row():\n",
" run_btn = gr.Button(\"Run PyTest\", variant=\"secondary\")\n",
" summary_md = gr.Markdown()\n",
" full_out = gr.Textbox(label=\"Full PyTest output\", lines=12)\n",
"\n",
" # --- events ---\n",
"\n",
" def _on_gen(model_label, name, code, save, outdir):\n",
" tests, msg = generate_from_code(model_label, name, code, save, outdir)\n",
" status = msg or \"✅ Done\"\n",
" return tests, status\n",
"\n",
" gen_btn.click(\n",
" _on_gen,\n",
" inputs=[model_dd, module_name_tb, code_in, save_cb, out_dir_tb],\n",
" outputs=[tests_out, summary_md],\n",
" )\n",
"\n",
" def _on_run(name, code, tests):\n",
" summary, details = run_pytest_on_snippet(name, code, tests)\n",
" return summary, details\n",
"\n",
" run_btn.click(\n",
" _on_run,\n",
" inputs=[module_name_tb, code_in, tests_out],\n",
" outputs=[summary_md, full_out],\n",
" )\n",
"\n",
"ui.launch(inbrowser=True)\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "llm-engineering",
"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.12.10"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,476 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "xeOG96gXPeqz"
},
"source": [
"# Snippet Sniper\n",
"\n",
"### Welcome on a wild ride with the John Wick in the coding arena as it accepts your contracts \n",
"\n",
"Allows you to perform various tasks on given code snippets:\n",
"\n",
"- Add comments\n",
"- Explain what the code does\n",
"- Writes comprehensive unit tests\n",
"- Fixes (potential) errors in the code"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "B7ftYo53Pw94",
"outputId": "9daa3972-d5a1-4cd2-9952-cd89a54c6ddd"
},
"outputs": [],
"source": [
"import os\n",
"import logging\n",
"from enum import StrEnum\n",
"from getpass import getpass\n",
"\n",
"import gradio as gr\n",
"from openai import OpenAI\n",
"from dotenv import load_dotenv\n",
"\n",
"\n",
"load_dotenv(override=True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "AXmPDuydPuUp"
},
"outputs": [],
"source": [
"logging.basicConfig(level=logging.WARNING)\n",
"\n",
"logger = logging.getLogger('sniper')\n",
"logger.setLevel(logging.DEBUG)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "0c_e1iMYmp5o"
},
"source": [
"## Free Cloud Providers\n",
"\n",
"Grab your free API Keys from these generous sites:\n",
"\n",
"- https://ollama.com/"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Secrets Helpers"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def get_secret_in_google_colab(env_name: str) -> str:\n",
" try:\n",
" from google.colab import userdata\n",
" return userdata.get(env_name)\n",
" except Exception:\n",
" return ''\n",
"\n",
"\n",
"def get_secret(env_name: str) -> str:\n",
" '''Gets the value from the environment(s), otherwise ask the user for it if not set'''\n",
" key = os.environ.get(env_name) or get_secret_in_google_colab(env_name)\n",
"\n",
" if not key:\n",
" key = getpass(f'Enter {env_name}:').strip()\n",
"\n",
" if key:\n",
" logger.info(f'✅ {env_name} provided')\n",
" else:\n",
" logger.warning(f'❌ {env_name} not provided')\n",
" return key.strip()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Set up model(s)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "d7Qmfac9Ph0w",
"outputId": "be9db7f3-f08a-47f5-d6fa-d7c8bce4f97a"
},
"outputs": [],
"source": [
"class Provider(StrEnum):\n",
" OLLAMA = 'Ollama'\n",
" OPENROUTER = 'OpenRouter'\n",
"\n",
"clients: dict[Provider, OpenAI] = {}\n",
"\n",
"if api_key := get_secret('OLLAMA_API_KEY'):\n",
" clients[Provider.OLLAMA] = OpenAI(api_key=api_key, base_url='https://ollama.com/v1')\n",
"\n",
"model = 'qwen3-coder:480b-cloud'\n",
"client = clients.get(Provider.OLLAMA)\n",
"if not client:\n",
" raise Exception('No client found')"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Kq-AKZEjqnTp"
},
"source": [
"## Tasks"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "fTHvG2w0sgwU"
},
"outputs": [],
"source": [
"class Task(StrEnum):\n",
" COMMENTS = 'Comments'\n",
" UNIT_TESTS = 'Unit Tests'\n",
" FIX_CODE = 'Fix Code'\n",
" EXPLAIN = 'Explain'\n",
"\n",
"\n",
"def perform_tasks(tasks, code):\n",
" logger.info(f'Performing tasks: {tasks}')\n",
"\n",
" steps = []\n",
" if Task.COMMENTS in tasks:\n",
" steps.append('Add documentation comments to the given code. If the method name and parameters are self-explanatory, skip those comments.')\n",
" if Task.UNIT_TESTS in tasks:\n",
" steps.append('Add a thorough unit tests considering all edge cases to the given code.')\n",
" if Task.FIX_CODE in tasks:\n",
" steps.append('You are to fix the given code, if it has any issues.')\n",
" if Task.EXPLAIN in tasks:\n",
" steps.append('Explain the given code.')\n",
"\n",
" system_prompt = f'''\n",
" You are an experienced polyglot software engineer and given a code you can\n",
" detect what programming language it is in.\n",
" DO NOT fix the code until expressly told to do so.\n",
"\n",
" Your tasks:\n",
" {'- ' + '\\n- '.join(steps)}\n",
" '''\n",
" messages = [\n",
" {\"role\": \"system\", \"content\": system_prompt},\n",
" {\"role\": \"user\", \"content\": f'Code: \\n{code}'}\n",
" ]\n",
" response = client.chat.completions.create(\n",
" model=model,\n",
" messages=messages\n",
" )\n",
"\n",
" content = response.choices[0].message.content\n",
"\n",
" return content"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "SkmMYw_osxeG"
},
"source": [
"### Examples"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "nlzUyXFus0km"
},
"outputs": [],
"source": [
"def get_examples() -> tuple[list[any], list[str]]:\n",
" '''Returns examples and their labels'''\n",
"\n",
" # Python examples\n",
" add = r'''\n",
" def add(a, b):\n",
" return a + b\n",
" '''\n",
"\n",
" multiply = r'''\n",
" def multiply(a, b):\n",
" return a * b\n",
" '''\n",
"\n",
" divide = r'''\n",
" def divide(a, b):\n",
" return a / b\n",
" '''\n",
"\n",
" # JavaScript example - async function\n",
" fetch_data = r'''\n",
" async function fetchUserData(userId) {\n",
" const response = await fetch(`/api/users/${userId}`);\n",
" const data = await response.json();\n",
" return data;\n",
" }\n",
" '''\n",
"\n",
" # Java example - sorting algorithm\n",
" bubble_sort = r'''\n",
" public void bubbleSort(int[] arr) {\n",
" int n = arr.length;\n",
" for (int i = 0; i < n-1; i++) {\n",
" for (int j = 0; j < n-i-1; j++) {\n",
" if (arr[j] > arr[j+1]) {\n",
" int temp = arr[j];\n",
" arr[j] = arr[j+1];\n",
" arr[j+1] = temp;\n",
" }\n",
" }\n",
" }\n",
" }\n",
" '''\n",
"\n",
" # C++ example - buggy pointer code\n",
" buggy_cpp = r'''\n",
" int* createArray() {\n",
" int arr[5] = {1, 2, 3, 4, 5};\n",
" return arr;\n",
" }\n",
" '''\n",
"\n",
" # Rust example - ownership puzzle\n",
" rust_ownership = r'''\n",
" fn main() {\n",
" let s1 = String::from(\"hello\");\n",
" let s2 = s1;\n",
" println!(\"{}\", s1);\n",
" }\n",
" '''\n",
"\n",
" # Go example - concurrent code\n",
" go_concurrent = r'''\n",
" func processData(data []int) int {\n",
" sum := 0\n",
" for _, v := range data {\n",
" sum += v\n",
" }\n",
" return sum\n",
" }\n",
" '''\n",
"\n",
" # TypeScript example - complex type\n",
" ts_generics = r'''\n",
" function mergeObjects<T, U>(obj1: T, obj2: U): T & U {\n",
" return { ...obj1, ...obj2 };\n",
" }\n",
" '''\n",
"\n",
" # Ruby example - metaclass magic\n",
" ruby_meta = r'''\n",
" class DynamicMethod\n",
" define_method(:greet) do |name|\n",
" \"Hello, #{name}!\"\n",
" end\n",
" end\n",
" '''\n",
"\n",
" # PHP example - SQL injection vulnerable\n",
" php_vulnerable = r'''\n",
" function getUser($id) {\n",
" $query = \"SELECT * FROM users WHERE id = \" . $id;\n",
" return mysqli_query($conn, $query);\n",
" }\n",
" '''\n",
"\n",
" # Python example - complex algorithm\n",
" binary_search = r'''\n",
" def binary_search(arr, target):\n",
" left, right = 0, len(arr) - 1\n",
" while left <= right:\n",
" mid = (left + right) // 2\n",
" if arr[mid] == target:\n",
" return mid\n",
" elif arr[mid] < target:\n",
" left = mid + 1\n",
" else:\n",
" right = mid - 1\n",
" return -1\n",
" '''\n",
"\n",
" # JavaScript example - closure concept\n",
" js_closure = r'''\n",
" function counter() {\n",
" let count = 0;\n",
" return function() {\n",
" count++;\n",
" return count;\n",
" };\n",
" }\n",
" '''\n",
"\n",
" examples = [\n",
" # Simple Python examples\n",
" [[Task.COMMENTS], add, 'python'],\n",
" [[Task.UNIT_TESTS], multiply, 'python'],\n",
" [[Task.COMMENTS, Task.FIX_CODE], divide, 'python'],\n",
"\n",
" # Explain complex concepts\n",
" [[Task.EXPLAIN], binary_search, 'python'],\n",
" [[Task.EXPLAIN], js_closure, 'javascript'],\n",
" [[Task.EXPLAIN], rust_ownership, 'rust'],\n",
"\n",
" # Unit tests for different languages\n",
" [[Task.UNIT_TESTS], fetch_data, 'javascript'],\n",
" [[Task.UNIT_TESTS], go_concurrent, 'go'],\n",
"\n",
" # Fix buggy code\n",
" [[Task.FIX_CODE], buggy_cpp, 'cpp'],\n",
" [[Task.FIX_CODE], php_vulnerable, 'php'],\n",
"\n",
" # Multi-task combinations\n",
" [[Task.COMMENTS, Task.EXPLAIN], bubble_sort, None],\n",
" [[Task.COMMENTS, Task.UNIT_TESTS], ts_generics, 'typescript'],\n",
" [[Task.EXPLAIN, Task.FIX_CODE], rust_ownership, 'rust'],\n",
" [[Task.COMMENTS, Task.UNIT_TESTS, Task.EXPLAIN], ruby_meta, 'ruby'],\n",
" ]\n",
"\n",
" example_labels = [\n",
" '🐍 Python: Add Function',\n",
" '🐍 Python: Multiply Tests',\n",
" '🐍 Python: Fix Division',\n",
" '🐍 Python: Binary Search Explained',\n",
" '🟨 JavaScript: Closure Concept',\n",
" '🦀 Rust: Ownership Puzzle',\n",
" '🟨 JavaScript: Async Test',\n",
" '🐹 Go: Concurrency Test',\n",
" '⚡ C++: Fix Pointer Bug',\n",
" '🐘 PHP: Fix SQL Injection',\n",
" '☕ Java: Bubble Sort Guide',\n",
" '📘 TypeScript: Generics & Tests',\n",
" '🦀 Rust: Fix & Explain Ownership',\n",
" '💎 Ruby: Meta Programming Deep Dive',\n",
" ]\n",
"\n",
" return examples, example_labels"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "wYReYuvgtDgg"
},
"source": [
"## Gradio UI\n",
"\n",
"[Documentation](https://www.gradio.app/docs/gradio)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 664
},
"id": "I8Q08SJe8CxK",
"outputId": "f1d41d06-dfda-4daf-b7ff-6f73bdaf8369"
},
"outputs": [],
"source": [
"title = 'Snippet Sniper 🎯'\n",
"\n",
"with gr.Blocks(title=title, theme=gr.themes.Monochrome()) as ui:\n",
" gr.Markdown(f'# {title}')\n",
" gr.Markdown('## I am your [**John Wick**](https://en.wikipedia.org/wiki/John_Wick), ready to accept any contract on your code. Consider it executed 🎯🔫!.')\n",
"\n",
" with gr.Row():\n",
" with gr.Column():\n",
" tasks = gr.Dropdown(\n",
" label=\"Tasks\",\n",
" choices=[task.value for task in Task],\n",
" value=Task.COMMENTS,\n",
" multiselect=True,\n",
" interactive=True,\n",
" )\n",
" code_input = gr.Code(\n",
" label='Code Input',\n",
" lines=40,\n",
" )\n",
" code_language = gr.Textbox(visible=False)\n",
"\n",
" with gr.Column():\n",
" gr.Markdown('## Kill Zone 🧟🧠💀')\n",
" code_output = gr.Markdown('💣')\n",
"\n",
"\n",
" run_btn = gr.Button('📜 Issue Contract')\n",
"\n",
" def set_language(tasks, code, language):\n",
" syntax_highlights = ['python', 'c', 'cpp', 'javascript', 'typescript']\n",
" logger.debug(f'Tasks: {tasks}, Languge: {language}')\n",
" highlight = language if language in syntax_highlights else None\n",
"\n",
" return tasks, gr.Code(value=code, language=highlight)\n",
"\n",
" examples, example_labels = get_examples()\n",
" examples = gr.Examples(\n",
" examples=examples,\n",
" example_labels=example_labels,\n",
" examples_per_page=20,\n",
" inputs=[tasks, code_input, code_language],\n",
" outputs=[tasks, code_input],\n",
" run_on_click=True,\n",
" fn=set_language\n",
" )\n",
"\n",
" run_btn.click(perform_tasks, inputs=[tasks, code_input], outputs=[code_output])\n",
"\n",
"ui.launch(debug=True)"
]
}
],
"metadata": {
"colab": {
"provenance": []
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 0
}

View File

@@ -0,0 +1 @@
OPENROUTER_API_KEY=your-api-key-here

View File

@@ -0,0 +1,35 @@
import gradio as gr
from test_generator import generate_tests
def create_interface():
with gr.Blocks(title="Unit Test Generator") as ui:
gr.Markdown("# Unit Test Generator")
gr.Markdown("Paste your Python code and get AI-generated unit tests")
with gr.Row():
with gr.Column(scale=1):
code_input = gr.Code(
label="Your Code",
language="python",
lines=15
)
generate_btn = gr.Button("Generate Tests", variant="primary")
with gr.Column(scale=1):
tests_output = gr.Textbox(
label="Generated Tests",
lines=15,
interactive=False
)
generate_btn.click(
fn=generate_tests,
inputs=[code_input],
outputs=[tests_output]
)
return ui
def launch():
ui = create_interface()
ui.launch(server_name="localhost", server_port=7860)

Some files were not shown because too many files have changed in this diff Show More