{ "cells": [ { "cell_type": "markdown", "id": "bcb31876-4d8c-41ef-aa24-b8c78dfd5808", "metadata": {}, "source": [ "# Project - Stock Information AI Assistant\n" ] }, { "cell_type": "code", "execution_count": 1, "id": "b7bd1bd7-19d9-4c4b-bc4b-9bc9cca8bd0f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Requirement already satisfied: finnhub-python in /opt/anaconda3/envs/llms/lib/python3.11/site-packages (2.4.24)\n", "Requirement already satisfied: requests>=2.22.0 in /opt/anaconda3/envs/llms/lib/python3.11/site-packages (from finnhub-python) (2.32.3)\n", "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/anaconda3/envs/llms/lib/python3.11/site-packages (from requests>=2.22.0->finnhub-python) (3.4.2)\n", "Requirement already satisfied: idna<4,>=2.5 in /opt/anaconda3/envs/llms/lib/python3.11/site-packages (from requests>=2.22.0->finnhub-python) (3.10)\n", "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/anaconda3/envs/llms/lib/python3.11/site-packages (from requests>=2.22.0->finnhub-python) (2.4.0)\n", "Requirement already satisfied: certifi>=2017.4.17 in /opt/anaconda3/envs/llms/lib/python3.11/site-packages (from requests>=2.22.0->finnhub-python) (2025.4.26)\n" ] } ], "source": [ "!pip install finnhub-python" ] }, { "cell_type": "code", "execution_count": 2, "id": "8b50bbe2-c0b1-49c3-9a5c-1ba7efa2bcb4", "metadata": {}, "outputs": [], "source": [ "# imports\n", "\n", "import os\n", "import json\n", "from dotenv import load_dotenv\n", "from openai import OpenAI\n", "import gradio as gr\n", "import finnhub\n", "from typing import Dict, List, Any, Optional\n", "from datetime import datetime" ] }, { "cell_type": "code", "execution_count": 3, "id": "ba0ddc1a-c775-4ed3-9531-ed0c5799e87f", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "2025-08-29 02:28:59,653 [INFO] Logger initialized!\n" ] } ], "source": [ "import logging\n", "\n", "# Configure root logger\n", "logging.basicConfig(\n", " level=logging.INFO, # Set level: DEBUG, INFO, WARNING, ERROR\n", " format=\"%(asctime)s [%(levelname)s] %(message)s\", \n", " force=True # Ensures reconfiguration if you rerun this cell\n", ")\n", "\n", "logger = logging.getLogger(__name__) # Use a global logger object\n", "logger.info(\"Logger initialized!\")" ] }, { "cell_type": "code", "execution_count": 4, "id": "747e8786-9da8-4342-b6c9-f5f69c2e22ae", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "2025-08-29 02:28:59,657 [INFO] OpenAI API Key exists and begins sk-proj-\n", "2025-08-29 02:28:59,657 [INFO] FINNHUB_API_KEY exists!\n" ] } ], "source": [ "# Initialization\n", "\n", "load_dotenv(override=True)\n", "\n", "openai_api_key = os.getenv('OPENAI_API_KEY')\n", "FINNHUB_API_KEY = os.getenv(\"FINNHUB_API_KEY\")\n", "\n", "if openai_api_key:\n", " logger.info(f\"OpenAI API Key exists and begins {openai_api_key[:8]}\")\n", "else:\n", " logger.error(\"OpenAI API Key not set\")\n", "\n", "if FINNHUB_API_KEY:\n", " logger.info(f\"FINNHUB_API_KEY exists!\")\n", "else:\n", " logger.error(\"OpenAI API Key not set\")\n", " \n", "MODEL = \"gpt-5-mini\"\n", "openai = OpenAI()\n", "finnhub_client = finnhub.Client(api_key=FINNHUB_API_KEY)" ] }, { "cell_type": "code", "execution_count": 5, "id": "ee3aaa9a-5495-42fd-a382-803fbfa92eaf", "metadata": {}, "outputs": [], "source": [ "system_message = f\"\"\"\n", "You are called \"TickerBot\", You are a helpful stock information assistant specializing in US stocks. You provide factual, educational information without investment advice. You have access to tools for:\n", "- Stock symbol lookup\n", "- Real-time quotes\n", "- Company Financials\n", "- Company News\n", "- Market overview\n", "\n", "### **Core Principles**\n", "**Educational Focus**: Explain financial metrics clearly and keep an educational tone.\n", "**Factual**: NEVER give buy/sell advice or predictions.\n", "**Accurate always**: If no data is available, inform the user in a friendly way. Always be accurate. If you don't know the answer, simply say so. Do not make up your own stock details information.\n", "\n", "### **How to Handle Different Requests**\n", "- For all temporal reasoning in this chat you can use `get_current_time()` tool to get time and then relative to current time you can proceed.\n", "- When users mention companies, search for symbols with the tool `search_symbol()` else proceed directly if obvious match\n", "- Try to search for news or data for only for a maximum of 1 month time range, else it becomes a lot of data to parse. If user asks for recent news just check the last 5 days from today; For example if today is 10-06-2025, use from=\"2025-06-05\", to=\"2025-06-10\"\n", "\n", "**Market Overview Requests**:\n", "- \"What's happening in the market?\" → Use `get_market_overview(\"general\")`\n", "- Summarize all news stories with very brief analysis\n", "\n", "### **Error Handling**\n", "- If symbol search fails: \"I couldn't find that company in US markets. Could you try a different name or provide the ticker symbol?\"\n", "- If some information gathered from the tool call says unavailable or error do not present it to the user unless they had specifically asked for it. Present rest of the gathered information if any.\n", "- If data is unavailable: \"Some data isn't available right now, but here's what I found...\"\n", "- Stay helpful and suggest alternatives\n", "\"\"\"" ] }, { "cell_type": "code", "execution_count": 6, "id": "fdf1a2b0-07be-47a0-9ce3-14d21b48c8f2", "metadata": {}, "outputs": [], "source": [ "def get_current_time() -> Dict[str, Any]:\n", " \"\"\"\n", " Retrieve the current UTC time in ISO format with timezone.\n", " Returns a dictionary for consistency with other tools.\n", " \"\"\"\n", " try:\n", " current_time = datetime.utcnow().isoformat() + 'Z'\n", " return {\n", " \"success\": True,\n", " \"current_time\": current_time\n", " }\n", " except Exception as e:\n", " return {\"success\": False, \"error\": f\"Failed to get time: {str(e)[:100]}\"}" ] }, { "cell_type": "code", "execution_count": 7, "id": "12d912fc-91fb-469e-9572-2876a099f5aa", "metadata": {}, "outputs": [], "source": [ "get_current_time_function = {\n", " \"name\": \"get_current_time\",\n", " \"description\": \"Get the current UTC time in ISO format (YYYY-MM-DDTHH:MM:SS.ssssssZ). Useful for temporal reasoning, date calculations, or setting time ranges for queries like news.\",\n", " \"parameters\": {\n", " \"type\": \"object\",\n", " \"properties\": {}, # No parameters needed\n", " \"required\": []\n", " }\n", "}\n", "get_current_time_tool = {\"type\": \"function\", \"function\": get_current_time_function}" ] }, { "cell_type": "code", "execution_count": 8, "id": "61a2a15d-b559-4844-b377-6bd5cb4949f6", "metadata": {}, "outputs": [], "source": [ "def validate_symbol(symbol: str) -> bool:\n", " \"\"\"Validate stock symbol format\"\"\"\n", " if not symbol or not isinstance(symbol, str):\n", " return False\n", " return symbol.isalnum() and 1 <= len(symbol) <= 5 and symbol.isupper()\n", "\n", "def search_symbol(query: str) -> Dict[str, Any]:\n", " \"\"\"Search for stock symbol using Finnhub client\"\"\"\n", " logger.info(f\"Tool search_symbol called for {query}\")\n", " try:\n", " if not query or len(query.strip()) < 1:\n", " return {\"success\": False, \"error\": \"Invalid search query\"}\n", " \n", " query = query.strip()[:50]\n", " result = finnhub_client.symbol_lookup(query)\n", " logger.info(f\"Tool search_symbol {result}\")\n", " \n", " if result.get(\"result\") and len(result[\"result\"]) > 0:\n", " first_result = result[\"result\"][0]\n", " symbol = first_result.get(\"symbol\", \"\").upper()\n", " \n", " if validate_symbol(symbol):\n", " return {\n", " \"success\": True,\n", " \"symbol\": symbol\n", " }\n", " else:\n", " return {\"success\": False, \"error\": \"Invalid symbol format found\"}\n", " else:\n", " return {\"success\": False, \"error\": \"No matching US stocks found\"}\n", " \n", " except Exception as e:\n", " return {\"success\": False, \"error\": f\"Symbol search failed: {str(e)[:100]}\"}" ] }, { "cell_type": "code", "execution_count": 9, "id": "173010e3-dfef-4611-8b68-d11256bd5fba", "metadata": {}, "outputs": [], "source": [ "search_symbol_function = {\n", " \"name\": \"search_symbol\",\n", " \"description\": \"Search for a stock symbol / ticker symbol based on company name or partial name\",\n", " \"parameters\": {\n", " \"type\": \"object\",\n", " \"properties\": {\n", " \"query\": {\n", " \"type\": \"string\",\n", " \"description\": \"Company name or partial name to search for, extract only relevant name part and pass it here, keep this to less than 50 characters\"\n", " }\n", " },\n", " \"required\": [\n", " \"query\"\n", " ]\n", " }\n", "}\n", "\n", "search_symbol_tool = {\"type\": \"function\", \"function\": search_symbol_function}" ] }, { "cell_type": "code", "execution_count": 10, "id": "448bb4ce-8e86-4ceb-ab52-96bddfd33337", "metadata": {}, "outputs": [], "source": [ "def _format_big_number_from_millions(value_millions: Any) -> str:\n", " \"\"\"\n", " Finnhub returns some large metrics (marketCapitalization, enterpriseValue, revenueTTM)\n", " in MILLIONS USD. Convert to full USD and format with M/B/T suffixes.\n", " \"\"\"\n", " if value_millions is None:\n", " return \"Unavailable\"\n", " try:\n", " value = float(value_millions) * 1_000_000 # convert millions -> full USD\n", " except (TypeError, ValueError):\n", " return \"Unavailable\"\n", "\n", " trillion = 1_000_000_000_000\n", " billion = 1_000_000_000\n", " million = 1_000_000\n", "\n", " if value >= trillion:\n", " return f\"{value / trillion:.2f}T USD\"\n", " if value >= billion:\n", " return f\"{value / billion:.2f}B USD\"\n", " if value >= million:\n", " return f\"{value / million:.2f}M USD\"\n", " return f\"{value:.2f} USD\"\n", "\n", "\n", "def _safe_metric(metrics: Dict[str, Any], key: str) -> Any:\n", " \"\"\"\n", " Return metric value if present; otherwise \"Unavailable\".\n", " We intentionally return the raw value for numeric metrics (no rounding/format)\n", " except for the specially formatted big-number fields handled elsewhere.\n", " \"\"\"\n", " if metrics is None:\n", " return \"Unavailable\"\n", " val = metrics.get(key)\n", " return val if val is not None else \"Unavailable\"\n", "\n", "\n", "def get_company_financials(symbol: str) -> Dict[str, Any]:\n", " \"\"\"\n", " Fetch and return a curated set of 'basic' financial metrics for `symbol`.\n", " - Calls finnhub_client.company_basic_financials(symbol, 'all')\n", " - Formats market cap, enterprise value, revenue (Finnhub returns these in millions)\n", " - Returns success flag and readable keys\n", " \"\"\"\n", " logger.info(f\"Tool get_company_financials called for {symbol}\")\n", " try:\n", " if not symbol or not symbol.strip():\n", " return {\"success\": False, \"error\": \"Invalid stock symbol\"}\n", "\n", " symbol = symbol.strip().upper()\n", "\n", " # --- API Call ---\n", " financials_resp = finnhub_client.company_basic_financials(symbol, \"all\")\n", " logger.info(f\"Tool company_basic_financials {financials_resp}\")\n", "\n", " # Finnhub places primary values under \"metric\"\n", " metrics = financials_resp.get(\"metric\", {})\n", " if not metrics:\n", " return {\"success\": False, \"error\": \"No financial metrics found\"}\n", "\n", " # --- Build result using helpers ---\n", " result = {\n", " \"success\": True,\n", " \"symbol\": symbol,\n", " \"financials\": {\n", " \"Market Cap\": _format_big_number_from_millions(metrics.get(\"marketCapitalization\")),\n", " \"Enterprise Value\": _format_big_number_from_millions(metrics.get(\"enterpriseValue\")),\n", " \"P/E Ratio (TTM)\": _safe_metric(metrics, \"peBasicExclExtraTTM\"),\n", " \"Forward P/E\": _safe_metric(metrics, \"forwardPE\"),\n", " \"Revenue (TTM)\": _format_big_number_from_millions(metrics.get(\"revenueTTM\")),\n", " \"Gross Margin (TTM)\": _safe_metric(metrics, \"grossMarginTTM\"),\n", " \"Net Profit Margin (TTM)\": _safe_metric(metrics, \"netProfitMarginTTM\"),\n", " \"EPS (TTM)\": _safe_metric(metrics, \"epsTTM\"),\n", " \"EPS Growth (5Y)\": _safe_metric(metrics, \"epsGrowth5Y\"),\n", " \"Dividend Yield (Indicated Annual)\": _safe_metric(metrics, \"dividendYieldIndicatedAnnual\"),\n", " \"Current Ratio (Quarterly)\": _safe_metric(metrics, \"currentRatioQuarterly\"),\n", " \"Debt/Equity (Long Term, Quarterly)\": _safe_metric(metrics, \"longTermDebt/equityQuarterly\"),\n", " \"Beta\": _safe_metric(metrics, \"beta\"),\n", " \"52-Week High\": _safe_metric(metrics, \"52WeekHigh\"),\n", " \"52-Week Low\": _safe_metric(metrics, \"52WeekLow\"),\n", " }\n", " }\n", "\n", " return result\n", "\n", " except Exception as e:\n", " # keep error message short but useful for debugging\n", " return {\"success\": False, \"error\": f\"Failed to fetch metrics: {str(e)[:200]}\"}" ] }, { "cell_type": "code", "execution_count": 11, "id": "9df7b74e-fec8-4e75-92a9-31acc75e6e97", "metadata": {}, "outputs": [], "source": [ "get_company_financials_function = {\n", " \"name\": \"get_company_financials\",\n", " \"description\": \"Fetch and return a curated set of basic financial metrics for a stock symbol. Calls Finnhub's company_basic_financials API, formats large numbers (market cap, enterprise value, revenue) in M/B/T USD, and shows metrics like P/E ratios, EPS, margins, dividend yield, debt/equity, beta, and 52-week range. Returns 'Unavailable' for missing values.\",\n", " \"parameters\": {\n", " \"type\": \"object\",\n", " \"properties\": {\n", " \"symbol\": {\n", " \"type\": \"string\",\n", " \"description\": \"Stock ticker symbol to fetch metrics for. Example: 'AAPL' for Apple Inc.\"\n", " }\n", " },\n", " \"required\": [\n", " \"symbol\"\n", " ]\n", " }\n", "}\n", "\n", "\n", "get_company_financials_tool = {\"type\": \"function\", \"function\": get_company_financials_function}" ] }, { "cell_type": "code", "execution_count": 12, "id": "cfeeb200-3f30-4855-82b9-cc8b2a950f80", "metadata": {}, "outputs": [], "source": [ "def get_stock_quote(symbol: str) -> dict:\n", " \"\"\"\n", " Fetch the latest stock quote for a given ticker symbol using Finnhub's /quote endpoint.\n", " Returns current price, daily high/low, open, previous close, percent change, and readable timestamp.\n", " \"\"\"\n", " logger.info(f\"Tool get_stock_quote called for {symbol}\")\n", " try:\n", " if not symbol or len(symbol.strip()) < 1:\n", " return {\"success\": False, \"error\": \"Invalid symbol provided\"}\n", " \n", " symbol = symbol.strip().upper()\n", " data = finnhub_client.quote(symbol)\n", " logger.info(f\"Tool get_stock_quote {data}\")\n", "\n", " if not data or \"c\" not in data:\n", " return {\"success\": False, \"error\": \"No quote data found\"}\n", " \n", " # Convert epoch timestamp to ISO UTC if present\n", " timestamp = data.get(\"t\")\n", " if timestamp and isinstance(timestamp, (int, float)):\n", " timestamp = datetime.utcfromtimestamp(timestamp).isoformat() + \"Z\"\n", " else:\n", " timestamp = \"Unavailable\"\n", " \n", " return {\n", " \"success\": True,\n", " \"symbol\": symbol,\n", " \"current_price\": round(data.get(\"c\", 0), 2) if data.get(\"c\") is not None else \"Unavailable\",\n", " \"change\": round(data.get(\"d\", 0), 2) if data.get(\"d\") is not None else \"Unavailable\",\n", " \"percent_change\": f\"{round(data.get('dp', 0), 2)}%\" if data.get(\"dp\") is not None else \"Unavailable\",\n", " \"high_price\": round(data.get(\"h\", 0), 2) if data.get(\"h\") is not None else \"Unavailable\",\n", " \"low_price\": round(data.get(\"l\", 0), 2) if data.get(\"l\") is not None else \"Unavailable\",\n", " \"open_price\": round(data.get(\"o\", 0), 2) if data.get(\"o\") is not None else \"Unavailable\",\n", " \"previous_close\": round(data.get(\"pc\", 0), 2) if data.get(\"pc\") is not None else \"Unavailable\",\n", " \"timestamp\": timestamp\n", " }\n", " except Exception as e:\n", " return {\"success\": False, \"error\": f\"Quote retrieval failed: {str(e)[:100]}\"}" ] }, { "cell_type": "code", "execution_count": 13, "id": "3724d92a-4515-4267-af6f-2c1ec2b6ed36", "metadata": {}, "outputs": [], "source": [ "get_stock_quote_function = {\n", " \"name\": \"get_stock_quote\",\n", " \"description\": \"Retrieve the latest stock quote for a given symbol, including current price, daily high/low, open, previous close, and percent change. Data is near real-time. Avoid constant polling; use websockets for streaming updates.\",\n", " \"parameters\": {\n", " \"type\": \"object\",\n", " \"properties\": {\n", " \"symbol\": {\n", " \"type\": \"string\",\n", " \"description\": \"Stock ticker symbol to fetch the latest quote for. Example: 'AAPL', 'MSFT'.\"\n", " }\n", " },\n", " \"required\": [\"symbol\"]\n", " }\n", "}\n", "\n", "get_stock_quote_tool = {\"type\": \"function\", \"function\": get_stock_quote_function}\n" ] }, { "cell_type": "code", "execution_count": 14, "id": "62f5d477-6626-428f-b8eb-d763e736ef5b", "metadata": {}, "outputs": [], "source": [ "def get_company_news(symbol: str, _from: str, to: str):\n", " \"\"\"\n", " Fetch the top 5 latest company news for a stock symbol within a date range.\n", " - Ensures the range does not exceed ~1 months (35 days).\n", " - Best practice: Keep searches to a month or less to avoid too much data.\n", "\n", " Args:\n", " symbol (str): Stock ticker (e.g., \"AAPL\").\n", " _from (str): Start date in YYYY-MM-DD format.\n", " to (str): End date in YYYY-MM-DD format.\n", "\n", " Returns:\n", " list or dict: Cleaned news data or error message.\n", " \"\"\"\n", " # Validate date format\n", " logger.info(f\"Tool get_company_news called for {symbol} from {_from} to {to}\")\n", " try:\n", " start_date = datetime.strptime(_from, \"%Y-%m-%d\")\n", " end_date = datetime.strptime(to, \"%Y-%m-%d\")\n", " except ValueError:\n", " return {\"success\": False, \"error\": \"Invalid date format. Use YYYY-MM-DD.\"}\n", "\n", " # Check date range\n", " delta_days = (end_date - start_date).days\n", " if delta_days > 35:\n", " return {\n", " \"success\": False, \n", " \"error\": f\"Date range too large ({delta_days} days). \"\n", " \"Please use a range of 1 months or less.\"\n", " }\n", "\n", " # Fetch data\n", " try:\n", " news = finnhub_client.company_news(symbol, _from=_from, to=to)\n", " logger.info(f\"Tool get_company_news {news}\")\n", " except Exception as e:\n", " return {\"success\": False, \"error\": str(e)}\n", "\n", " # Do not want to report just the latest news in the time period\n", " if len(news) <= 10:\n", " # If 10 or fewer articles, take all\n", " selected_news = news\n", " else:\n", " # Take first 5 (oldest) and last 5 (newest)\n", " selected_news = news[:5] + news[-5:]\n", "\n", " # Clean & transform objects\n", " cleaned_news = []\n", " for article in selected_news:\n", " cleaned_news.append({\n", " \"summary\": article.get(\"summary\"),\n", " \"source\": article.get(\"source\"),\n", " \"published_at\": datetime.utcfromtimestamp(article[\"datetime\"]).strftime(\"%Y-%m-%d %H:%M:%S UTC\"),\n", " \"related\": article.get(\"related\")\n", " })\n", "\n", " return {\"success\": True, \"news\": cleaned_news}" ] }, { "cell_type": "code", "execution_count": 15, "id": "5150ecb6-e3f1-46dc-94fa-2a9abe5165f6", "metadata": {}, "outputs": [], "source": [ "get_company_news_function = {\n", " \"name\": \"get_company_news\",\n", " \"description\": \"Fetch the top 5 most recent company news articles for a given stock symbol. ⚠️ Avoid querying more than a 1-month range at a time as it may return too much data. Only tells news about company within last 1 year. An error is returned if the requested time range exceeds 1 month.\",\n", " \"parameters\": {\n", " \"type\": \"object\",\n", " \"properties\": {\n", " \"symbol\": {\n", " \"type\": \"string\",\n", " \"description\": \"Stock ticker symbol, e.g., 'AAPL'.\"\n", " },\n", " \"_from\": {\n", " \"type\": \"string\",\n", " \"description\": \"Start date in YYYY-MM-DD format. Ensure it is not more than 1 year ago from today. Ensure it is before or equal to the date in to.\"\n", " },\n", " \"to\": {\n", " \"type\": \"string\",\n", " \"description\": \"End date in YYYY-MM-DD format. Ensure it is not more than 1 year ago. Ensure it is after or equal to the date in from.\"\n", " }\n", " },\n", " \"required\": [\n", " \"symbol\",\n", " \"_from\",\n", " \"to\"\n", " ]\n", " }\n", "}\n", "\n", "get_company_news_tool = {\"type\": \"function\", \"function\": get_company_news_function}" ] }, { "cell_type": "code", "execution_count": 16, "id": "26dd7375-626f-4235-b4a2-f1926f62cc5e", "metadata": {}, "outputs": [], "source": [ "def get_market_news(category: str = \"general\"):\n", " \"\"\"\n", " Fetch the latest market news for a given category.\n", " - Returns only the top 7 articles.\n", "\n", " Args:\n", " category (str): News category. One of [\"general\", \"forex\", \"crypto\", \"merger\"].\n", "\n", " Returns:\n", " list or dict: A cleaned list of news articles or error message.\n", " \"\"\"\n", " logger.info(f\"Tool get_market_news called for category '{category}'\")\n", "\n", " try:\n", " news = finnhub_client.general_news(category)\n", " logger.info(f\"Tool get_market_news {news}\")\n", " except Exception as e:\n", " return {\"success\": False, \"error\": str(e)}\n", "\n", " # Do not want to report just the latest news in the time period\n", " if len(news) <= 10:\n", " # If 10 or fewer articles, take all\n", " selected_news = news\n", " else:\n", " # Take first 5 (oldest) and last 5 (newest)\n", " selected_news = news[:5] + news[-5:]\n", "\n", " # Clean & transform objects\n", " cleaned_news = []\n", " for article in selected_news:\n", " cleaned_news.append({\n", " \"headline\": article.get(\"headline\"),\n", " \"summary\": article.get(\"summary\"),\n", " \"source\": article.get(\"source\"),\n", " \"category\": article.get(\"category\"),\n", " \"published_at\": datetime.utcfromtimestamp(article[\"datetime\"]).strftime(\"%Y-%m-%d %H:%M:%S UTC\"),\n", " \"related\": article.get(\"related\")\n", " })\n", "\n", " return {\"success\": True, \"news\": cleaned_news}" ] }, { "cell_type": "code", "execution_count": 17, "id": "5bd1aa28-119c-4c7a-bdc0-161a582ab1cc", "metadata": {}, "outputs": [], "source": [ "get_market_news_function = {\n", " \"name\": \"get_market_news\",\n", " \"description\": \"Fetch the latest market news by category. Returns the top 10 news articles with headline, summary, source, category, published time (UTC), and URLs. Categories: general, forex, crypto, merger. Use this to quickly get relevant financial news.\",\n", " \"parameters\": {\n", " \"type\": \"object\",\n", " \"properties\": {\n", " \"category\": {\n", " \"type\": \"string\",\n", " \"description\": \"News category to fetch. One of: general, forex, crypto, merger.\"\n", " }\n", " },\n", " \"required\": [\"category\"]\n", " }\n", "}\n", "\n", "get_market_news_tool = {\"type\": \"function\", \"function\": get_market_news_function}" ] }, { "cell_type": "code", "execution_count": 18, "id": "bdca8679-935f-4e7f-97e6-e71a4d4f228c", "metadata": {}, "outputs": [], "source": [ "# List of tools:\n", "tools = [search_symbol_tool, get_company_financials_tool, get_stock_quote_tool, get_company_news_tool, get_market_news_tool, get_current_time_tool]\n", "tool_functions = {\n", " \"search_symbol\": search_symbol,\n", " \"get_company_financials\": get_company_financials,\n", " \"get_stock_quote\": get_stock_quote,\n", " \"get_company_news\": get_company_news,\n", " \"get_market_news\": get_market_news,\n", " \"get_current_time\": get_current_time\n", "}" ] }, { "cell_type": "markdown", "id": "c3d3554f-b4e3-4ce7-af6f-68faa6dd2340", "metadata": {}, "source": [ "## Getting OpenAI to use our Tool\n", "\n", "There's some fiddly stuff to allow OpenAI \"to call our tool\"\n", "\n", "What we actually do is give the LLM the opportunity to inform us that it wants us to run the tool.\n", "\n", "Here's how the new chat function looks:" ] }, { "cell_type": "code", "execution_count": 19, "id": "86f76f57-76c4-4dc7-94a8-cfe7816a39f1", "metadata": {}, "outputs": [], "source": [ "def execute_tool_call(tool_call):\n", " func_name = tool_call.function.name\n", " args = json.loads(tool_call.function.arguments)\n", "\n", " logger.info(f\"Executing tool: {func_name} with args: {args}\")\n", "\n", " func = tool_functions.get(func_name)\n", " if not func:\n", " result = {\"error\": f\"Function '{func_name}' not found\"}\n", " else:\n", " try:\n", " result = func(**args)\n", " except Exception as e:\n", " logger.exception(f\"Error executing {func_name}\")\n", " result = {\"error\": str(e)}\n", "\n", " return {\n", " \"role\": \"tool\",\n", " \"tool_call_id\": tool_call.id,\n", " \"content\": json.dumps(result)\n", " }" ] }, { "cell_type": "code", "execution_count": 20, "id": "ce9b0744-9c78-408d-b9df-9f6fd9ed78cf", "metadata": {}, "outputs": [], "source": [ "def chat(message, history):\n", " messages = [{\"role\": \"system\", \"content\": system_message}] + history + [{\"role\": \"user\", \"content\": message}]\n", "\n", " while True:\n", " # Send to OpenAI\n", " response = openai.chat.completions.create(model=MODEL, messages=messages, tools=tools)\n", " ai_message = response.choices[0].message\n", " finish_reason = response.choices[0].finish_reason\n", "\n", " # If no tool calls, this is user-facing output\n", " if finish_reason != \"tool_calls\":\n", " return ai_message.content # ✅ Only return final assistant content\n", "\n", " # Otherwise, handle all tool calls in this message\n", " tool_responses = []\n", " for tool_call in ai_message.tool_calls:\n", " tool_responses.append(execute_tool_call(tool_call))\n", "\n", " # Add tool call request + tool responses to conversation\n", " messages.append(ai_message)\n", " messages.extend(tool_responses)" ] }, { "cell_type": "code", "execution_count": null, "id": "f4be8a71-b19e-4c2f-80df-f59ff2661f14", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "2025-08-29 02:29:05,739 [INFO] HTTP Request: GET http://127.0.0.1:7860/gradio_api/startup-events \"HTTP/1.1 200 OK\"\n", "2025-08-29 02:29:05,746 [INFO] HTTP Request: HEAD http://127.0.0.1:7860/ \"HTTP/1.1 200 OK\"\n" ] }, { "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": [ "
" ], "text/plain": [ "Senior Trump administration official:
This is in relation to Trump's removal of the 'de minimis' exemption on buying low value products from overseas. US consumers will now be taxed a minimum of $80, and up to $200, for ordering such items from offshore now.
More now, senior Trump administration official:
\n",
" \n",
" | \n",
" \n",
" I have a special request for you\n", " \n", " My editor tells me that it makes a HUGE difference when students rate this course on Udemy - it's one of the main ways that Udemy decides whether to show it to others. If you're able to take a minute to rate this, I'd be so very grateful! And regardless - always please reach out to me at ed@edwarddonner.com if I can help at any point.\n", " \n", " | \n",
"