From eab85d7cb4c699af1c23f1d64e2d4d6b60b16704 Mon Sep 17 00:00:00 2001 From: vwinland Date: Mon, 24 Nov 2025 11:25:39 -0500 Subject: [PATCH 1/2] Add Vrunda's react prompting tutorial Signed-off-by: vwinland --- generative-ai/ReAct.ipynb | 662 +++++++++++++++++++++++++++++++++++++ images/ReAct-prompting.jpg | Bin 0 -> 16789 bytes images/react-prompting.png | Bin 0 -> 31388 bytes 3 files changed, 662 insertions(+) create mode 100644 generative-ai/ReAct.ipynb create mode 100644 images/ReAct-prompting.jpg create mode 100644 images/react-prompting.png diff --git a/generative-ai/ReAct.ipynb b/generative-ai/ReAct.ipynb new file mode 100644 index 000000000..7a18d2540 --- /dev/null +++ b/generative-ai/ReAct.ipynb @@ -0,0 +1,662 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# ReAct Prompting for Financial Insight: Classification and Summarization with Granite 4.0 Nano" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Author: Vrunda Gadesha" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This tutorial shows how to build an AI agent using the ReAct (Reasoning and Acting) framework and the IBM Granite 4.0 Nano language model. The agent uses prompt engineering to combine reasoning steps with an action plan. It processes news URLs, retrieves data from external sources, and generates a structured financial analysis in JSON format. This approach ensures clear, transparent, and grounded results." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Before we jumpt into the code, Let's understand some core concepts.\n", + "\n", + "**What is ReAct Prompting?**\n", + "\n", + "The ReAct framework enhances large language models (LLMs) by combining internal reasoning with external actions. It integrates Reasoning (Chain-of-Thought, or CoT) and Acting (Tool Use) to create an effective ReAct agent capable of dynamic decision-making.\n", + "\n", + "- **Thought (Reasoning):** The model generates reasoning traces that break down the task, track progress, and plan the next logical step. These traces can operate in few-shot or zero-shot settings, depending on prompt design.\n", + "- **Action (Acting):** The model follows an action plan to execute tool commands, such as web_browser(url), often managed through frameworks like LangChain.\n", + "- **Observation:** The agent retrieves external information or data from a knowledge base after executing an action.\n", + "\n", + "ReAct’s Benefits: It grounds the model in verified external data rather than internal training memory, improving transparency and supporting reliable decision-making in complex tasks." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![ReAct Prompting Diagram](../images/react-prompting.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Introduction to Granite 4.0 Nano\n", + "\n", + "We use the IBM Granite 4.0 Nano model family, specifically the 1B-instruct variant. This AI model is finetuned for reasoning and instruction-following tasks, making it suitable for building intelligent agents.\n", + "\n", + "- **Efficiency:** Optimized for fast, low-latency inference through its lightweight architecture and efficient API integration, comparable to how OpenAI models like ChatGPT are deployed.\n", + "- **Agentic Capability:** Instruction-tuned to follow complex, multi-step prompts with self-consistency in the model’s reasoning. It handles both structured and verbal reasoning, recognizes tool schemas, and produces reliable, JSON-formatted outputs.\n", + "\n", + "The Granite 4.0 Nano model demonstrates how compact, finetuned models can achieve strong performance and interpretability similar to larger commercial AI models." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Usecase" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Financial news is rarely straightforward. Articles often mix conflicting signals — for example, a company may report strong sales (positive signal) but its stock may fall due to weak guidance or higher expenses (negative reaction).\n", + "\n", + "A single-step summary cannot capture this nuance. The ReAct agent improves question answering and reasoning by working step by step, combining reasoning and action in interleaved trajectories. It uses in-context examples and fact verification to ensure each action step aligns with reliable real-world information.\n", + "\n", + "The agent converts raw news text into structured financial intelligence. It delivers results as one JSON object using Python-based workflows built on machine learning and retrieval techniques like RAG (Retrieval-Augmented Generation).\n", + "\n", + "Classification:\n", + "\n", + "- **Sentiment:** Categorizes the market impact (Positive, Negative, Mixed, Neutral).\n", + "- **Topic:** Identifies the main business driver (Earnings, Regulatory, HR/Layoffs, Supply Chain, etc.).\n", + "- **Summary:** Produces a concise executive summary (3–5 key points) with fact verification, avoiding verbose output.\n", + "\n", + "The strict JSON schema ensures outputs are instantly consumable by downstream systems and comparable to benchmarks such as HotpotQA or other baseline datasets. This approach supports accurate, real-time financial insight and a grounded final answer." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Prerequisites\n", + "\n", + "You can execute this tutorial on your local machine using your own Python and Jupyter environment. The local setup gives you full control and flexibility. To run this tutorial, uou must have following setup in your machine. \n", + "\n", + "To execute the notebook on your local Windows or macOS machine, ensure the following setup:\n", + "- Operating system: Windows 10 or later, or macOS 12 or later\n", + "- Python: Version 3.10 or newer\n", + "- RAM: Minimum 8 GB (16 GB recommended for larger workloads)\n", + "- Tools: Jupyter Notebook or JupyterLab installed" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Steps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 1. Install Dependencies\n", + "\n", + "To run the Granite 4.0 Nano model and ReAct workflow, you need the core libraries that handle model loading and computation. Installing them ensures your environment can execute reasoning and action steps smoothly." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "!pip install torch torchvision torchaudio --quiet\n", + "!pip install accelerate transformers --quiet" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 2. Load the Model and Tokenizer\n", + "\n", + "This step loads the Granite 4.0 Nano instruction‑tuned model and its tokenizer from Hugging Face. The tokenizer converts text to tokens, and the model generates responses. The code automatically selects GPU if available, otherwise runs on CPU." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loading model 'ibm-granite/granite-4.0-1b' on cpu...\n", + "Model and tokenizer loaded successfully.\n" + ] + } + ], + "source": [ + "import torch\n", + "from transformers import AutoModelForCausalLM, AutoTokenizer\n", + "\n", + "device = \"cpu\" # Use \"cuda\" if GPU is available\n", + "MODEL_NAME = \"ibm-granite/granite-4.0-1b\"\n", + "\n", + "print(f\"Loading model '{MODEL_NAME}' on {device}...\")\n", + "\n", + "tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)\n", + "model = AutoModelForCausalLM.from_pretrained(MODEL_NAME).to(device)\n", + "model.eval()\n", + "\n", + "print(\"Model and tokenizer loaded successfully.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "***Note: Model loading takes about 2–4 minutes on 8 GB RAM and 1–2 minutes on 16 GB RAM; if you see an “out of memory” error on 8 GB systems, restart the kernel and close other applications before retrying.***" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 3. Define the Data\n", + "\n", + "This step creates a small dataset of five financial news summaries used to test the ReAct agent. The articles are drawn from different sectors — biotechnology (Lenz Therapeutics), payments (Shift4 Payments), lidar technology (Luminar), electric vehicles (Lucid), and consumer goods (Celsius).\n", + "\n", + "Each article includes mixed market signals such as strong earnings but weak guidance, higher costs, or regulatory challenges. This mix helps evaluate how the agent handles conflicting information and classifies sentiment accurately." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Simulated financial data defined.\n" + ] + } + ], + "source": [ + "SIMULATED_ARTICLE_DATA = {\n", + " \"https://ir.lenz-tx.com/news-events/press-releases/detail/43/lenz-therapeutics-reports-third-quarter-2025-financial-results-and-recent-corporate-highlights\": \n", + " \"\"\"\n", + " LENZ Therapeutics reported Q3 2025 results. The company launched VIZZ, saw 5,000 prescriptions, and received $10M in milestone payments. Operating expenses rose 44%, driven by SG&A. Net loss: $16.7M. Stock fell 9.4% premarket.\n", + " \"\"\",\n", + " \"https://www.investing.com/news/transcripts/earnings-call-transcript-shift4-payments-q3-2025-shows-strong-growth-93CH-4339020\":\n", + " \"\"\"\n", + " Shift4 Payments reported EPS of $1.47 (beat) and revenue of $589.2M (miss). Stock up 5.7% premarket after recent weakness.\n", + " \"\"\",\n", + " \"https://www.photonics.com/Articles/Amid-SEC-Investigation-Luminar-Cuts-Workforce/p5/a71615\":\n", + " \"\"\"\n", + " Luminar faces SEC investigation, missed payments, CFO exit, and workforce cuts. Bankruptcy risk rising amid liquidity issues.\n", + " \"\"\",\n", + " \"https://www.cbtnews.com/lucid-trims-production-forecast-misses-q3-expectations/\":\n", + " \"\"\"\n", + " Lucid missed Q3 targets with $336.6M revenue. Loss widened to $2.65 per share. Production forecast trimmed due to supply chain issues.\n", + " \"\"\",\n", + " \"https://www.investing.com/news/transcripts/earnings-call-transcript-celsius-q3-2025-earnings-beat-expectations-stock-dips-93CH-4338388\":\n", + " \"\"\"\n", + " Celsius beat estimates with EPS $0.42 and revenue $725.1M. Stock fell 17.5% on cautious guidance.\n", + " \"\"\"\n", + "}\n", + "\n", + "print(\"Simulated financial data defined.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 4. Define the Tool Function\n", + "\n", + "This step defines a simple function, web_browser(), that simulates how the ReAct agent retrieves text from an external source. It looks up the article text for a given URL from the dataset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✅ web_browser tool defined successfully.\n" + ] + } + ], + "source": [ + "def web_browser(url: str) -> str:\n", + " \"\"\"Simulates a web lookup by returning article text for a given URL.\"\"\"\n", + " return SIMULATED_ARTICLE_DATA.get(url, f\"Error: Could not retrieve content for {url}. URL not found.\")\n", + "\n", + "print(\"✅ web_browser tool defined successfully.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 5. Define the ReAct Prompt Template\n", + "\n", + "This step defines the prompt that guides the agent’s reasoning. The template enforces the ReAct pattern — Thought → Action → Observation — and specifies how the model should use the web_browser() tool and format its final output as structured JSON." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "REACT_PROMPT_TEMPLATE = \"\"\"\n", + "You are an expert financial analyst agent. Your task is to analyze the provided financial news article URL. Follow the ReAct framework (Thought -> Action -> Observation) until you produce the final structured JSON analysis.\n", + "\n", + "**AVAILABLE TOOLS:**\n", + "web_browser(url: str) -> str: Fetches the full textual content of the article.\n", + "\n", + "**FINAL OUTPUT INSTRUCTIONS:**\n", + "Return a single JSON object with:\n", + "1. \"Classification\": {{ \"Sentiment\": Positive/Negative/Mixed/Neutral, \"Topic\": Earnings/Regulatory/Product/Operations/M&A/HR/Layoffs/Supply Chain }}\n", + "2. \"Summary\": A concise bulleted list of 3-5 key takeaways.\n", + "\n", + "---\n", + "**INPUT ARTICLE URL:** {url}\n", + "---\n", + "**START ANALYSIS:**\n", + "\"\"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 6. Tool Execution Function\n", + "\n", + "This function prepares the initial ReAct prompt, performs the tool call, and returns all intermediate components for the next step." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "def prepare_react_context(url: str, verbose: bool = False):\n", + " current_prompt = REACT_PROMPT_TEMPLATE.format(url=url)\n", + " action_call = f\"Action: web_browser(url='{url}')\"\n", + " observation = web_browser(url)\n", + " \n", + " if verbose:\n", + " print(f\"Prompt snippet: {current_prompt[:120]}...\")\n", + " print(f\"Observation snippet: {observation[:120]}...\")\n", + " \n", + " return current_prompt, action_call, observation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 7. Define the Main ReAct Agent Function\n", + "\n", + "This function calls the setup function above, performs model generation, and safely parses the JSON output." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "def run_granite_react_agent(url: str, max_tokens: int = 512, verbose: bool = False) -> str:\n", + " \"\"\"Executes the ReAct loop using Granite model and returns structured text analysis.\"\"\"\n", + " \n", + " current_prompt, action_call, observation = prepare_react_context(url, verbose)\n", + " \n", + " # Strong instruction for structured output\n", + " system_instruction = (\n", + " \"You are an expert financial analyst agent following the ReAct framework. \"\n", + " \"Analyze the observation and produce the output in this exact format:\\n\\n\"\n", + " \"Classification:\\nSentiment: Positive/Negative/Mixed/Neutral\\nTopic: Earnings/Regulatory/Product/Operations/M&A/HR/Layoffs/Supply Chain\\n\\n\"\n", + " \"Summary:\\n- Key takeaway 1\\n- Key takeaway 2\\n- Key takeaway 3\\n\\n\"\n", + " \"Start with Classification, then Summary. Do not include any extra text or role markers. \"\n", + " \"Base sentiment and topic ONLY on the observation text. Use actual values from the article.\"\n", + " )\n", + " \n", + " chat = [\n", + " {\"role\": \"system\", \"content\": system_instruction},\n", + " {\"role\": \"user\", \"content\": f\"{current_prompt}\\nThought: Retrieve article content using web_browser tool.\\n\"\n", + " f\"{action_call}\\nObservation: {observation}\\n\\n\"\n", + " f\"Thought: Proceed with classification and summarization.\\nAction:\"}\n", + " ]\n", + " \n", + " # Apply Granite chat template\n", + " chat_text = tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=True)\n", + " input_tokens = tokenizer(chat_text, return_tensors=\"pt\").to(device)\n", + " \n", + " if verbose:\n", + " print(f\"Prompt length: {len(chat_text)} chars | Token count: {input_tokens['input_ids'].shape[-1]}\")\n", + " \n", + " # Generate output\n", + " with torch.no_grad():\n", + " output_tokens = model.generate(**input_tokens, max_new_tokens=max_tokens)\n", + " \n", + " # Decode response\n", + " full_response = tokenizer.batch_decode(output_tokens, skip_special_tokens=True)[0]\n", + " \n", + " return full_response.strip()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 8. Test Single URL\n", + "\n", + "This step runs the ReAct agent on one example URL to verify the workflow and ensure the model generates a valid JSON response before processing multiple cases." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Testing single ReAct run...\n", + "Prompt snippet: \n", + "You are an expert financial analyst agent. Your task is to analyze the provided financial news article URL. Follow the ...\n", + "Observation snippet: \n", + " Luminar faces SEC investigation, missed payments, CFO exit, and workforce cuts. Bankruptcy risk rising amid liquidi...\n", + "Prompt length: 1795 chars | Token count: 400\n", + "\n", + "--- Model Output ---\n", + "\n", + "systemYou are an expert financial analyst agent following the ReAct framework. Analyze the observation and produce the output in this exact format:\n", + "\n", + "Classification:\n", + "Sentiment: Positive/Negative/Mixed/Neutral\n", + "Topic: Earnings/Regulatory/Product/Operations/M&A/HR/Layoffs/Supply Chain\n", + "\n", + "Summary:\n", + "- Key takeaway 1\n", + "- Key takeaway 2\n", + "- Key takeaway 3\n", + "\n", + "Start with Classification, then Summary. Do not include any extra text or role markers. Base sentiment and topic ONLY on the observation text. Use actual values from the article.\n", + "user\n", + "You are an expert financial analyst agent. Your task is to analyze the provided financial news article URL. Follow the ReAct framework (Thought -> Action -> Observation) until you produce the final structured JSON analysis.\n", + "\n", + "**AVAILABLE TOOLS:**\n", + "web_browser(url: str) -> str: Fetches the full textual content of the article.\n", + "\n", + "**FINAL OUTPUT INSTRUCTIONS:**\n", + "Return a single JSON object with:\n", + "1. \"Classification\": { \"Sentiment\": Positive/Negative/Mixed/Neutral, \"Topic\": Earnings/Regulatory/Product/Operations/M&A/HR/Layoffs/Supply Chain }\n", + "2. \"Summary\": A concise bulleted list of 3-5 key takeaways.\n", + "\n", + "---\n", + "**INPUT ARTICLE URL:** https://www.photonics.com/Articles/Amid-SEC-Investigation-Luminar-Cuts-Workforce/p5/a71615\n", + "---\n", + "**START ANALYSIS:**\n", + "\n", + "Thought: Retrieve article content using web_browser tool.\n", + "Action: web_browser(url='https://www.photonics.com/Articles/Amid-SEC-Investigation-Luminar-Cuts-Workforce/p5/a71615')\n", + "Observation: \n", + " Luminar faces SEC investigation, missed payments, CFO exit, and workforce cuts. Bankruptcy risk rising amid liquidity issues.\n", + " \n", + "\n", + "Thought: Proceed with classification and summarization.\n", + "Action:\n", + "assistantClassification:\n", + "Sentiment: Negative\n", + "Topic: Operations\n", + "\n", + "Summary:\n", + "- Luminar faces a significant SEC investigation due to missed payments.\n", + "- The company has experienced a CFO exit, indicating internal financial challenges.\n", + "- Luminar has implemented workforce cuts to address liquidity issues.\n", + "- The company is facing bankruptcy risk due to its financial struggles.\n" + ] + } + ], + "source": [ + "# Test single URL before batch\n", + "test_url = \"https://www.photonics.com/Articles/Amid-SEC-Investigation-Luminar-Cuts-Workforce/p5/a71615\"\n", + "print(\"Testing single ReAct run...\")\n", + "result = run_granite_react_agent(test_url, verbose=True)\n", + "print(\"\\n--- Model Output ---\\n\")\n", + "print(result)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 9. Batch Analysis\n", + "\n", + "This cell applies the ReAct agent to all case study URLs, aggregates the results into a structured DataFrame, and displays the sentiment, topic, and summary for each company." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--- Starting Batch ReAct Analysis ---\n", + "\n", + "Analyzing: Article 1: Luminar...\n", + "\n", + "Analyzing: Article 2: Celsius...\n", + "\n", + "Analyzing: Article 3: Lucid...\n", + "\n", + "Analyzing: Article 4: Shift4...\n", + "\n", + "Analyzing: Article 5: Lenz...\n", + "\n", + "--- Batch Analysis Results ---\n", + "\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Company/CaseSentimentTopicSummary
0Article 1: LuminarNegativeOperations- Key takeaway 1\\n- Key takeaway 2\\n- Key takeaway 3\\n- Luminar faces a significant SEC investigation due to missed payments.\\n- The company has experienced a CFO exit, indicating internal financial challenges.\\n- Luminar has implemented workforce cuts to address liquidity issues.\\n- The company is facing bankruptcy risk due to its financial struggles.
1Article 2: CelsiusNegativeEarnings- Key takeaway 1\\n- Key takeaway 2\\n- Key takeaway 3\\n- Celsius reported EPS of $0.42, beating estimates.\\n- Revenue of $725.1M, also exceeding expectations.\\n- Stock price fell 17.5% following the earnings call.
2Article 3: LucidNegativeOperations- Key takeaway 1\\n- Key takeaway 2\\n- Key takeaway 3\\n- Lucid missed Q3 revenue targets, resulting in a $336.6 million shortfall.\\n- The company experienced a widening loss of $2.65 per share.\\n- Production forecasts were reduced due to supply chain challenges.
3Article 4: Shift4PositiveEarnings- Key takeaway 1\\n- Key takeaway 2\\n- Key takeaway 3\\n- EPS beat expectations with $1.47\\n- Revenue missed the target at $589.2M
4Article 5: LenzNegativeEarnings- Key takeaway 1\\n- Key takeaway 2\\n- Key takeaway 3\\n- Q3 2025 financial results show a net loss of $16.7 million.\\n- VIZZ launch and increased prescriptions indicate product performance.\\n- $10 million in milestone payments reflect successful clinical progress.\\n- Operating expenses rose 44% due to increased SG&A costs.
\n", + "
" + ], + "text/plain": [ + " Company/Case Sentiment Topic \\\n", + "0 Article 1: Luminar Negative Operations \n", + "1 Article 2: Celsius Negative Earnings \n", + "2 Article 3: Lucid Negative Operations \n", + "3 Article 4: Shift4 Positive Earnings \n", + "4 Article 5: Lenz Negative Earnings \n", + "\n", + " Summary \n", + "0 - Key takeaway 1\\n- Key takeaway 2\\n- Key takeaway 3\\n- Luminar faces a significant SEC investigation due to missed payments.\\n- The company has experienced a CFO exit, indicating internal financial challenges.\\n- Luminar has implemented workforce cuts to address liquidity issues.\\n- The company is facing bankruptcy risk due to its financial struggles. \n", + "1 - Key takeaway 1\\n- Key takeaway 2\\n- Key takeaway 3\\n- Celsius reported EPS of $0.42, beating estimates.\\n- Revenue of $725.1M, also exceeding expectations.\\n- Stock price fell 17.5% following the earnings call. \n", + "2 - Key takeaway 1\\n- Key takeaway 2\\n- Key takeaway 3\\n- Lucid missed Q3 revenue targets, resulting in a $336.6 million shortfall.\\n- The company experienced a widening loss of $2.65 per share.\\n- Production forecasts were reduced due to supply chain challenges. \n", + "3 - Key takeaway 1\\n- Key takeaway 2\\n- Key takeaway 3\\n- EPS beat expectations with $1.47\\n- Revenue missed the target at $589.2M \n", + "4 - Key takeaway 1\\n- Key takeaway 2\\n- Key takeaway 3\\n- Q3 2025 financial results show a net loss of $16.7 million.\\n- VIZZ launch and increased prescriptions indicate product performance.\\n- $10 million in milestone payments reflect successful clinical progress.\\n- Operating expenses rose 44% due to increased SG&A costs. " + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pandas as pd\n", + "\n", + "CASE_STUDY_URLS = {\n", + " \"Article 1: Luminar\": \"https://www.photonics.com/Articles/Amid-SEC-Investigation-Luminar-Cuts-Workforce/p5/a71615\",\n", + " \"Article 2: Celsius\": \"https://www.investing.com/news/transcripts/earnings-call-transcript-celsius-q3-2025-earnings-beat-expectations-stock-dips-93CH-4338388\",\n", + " \"Article 3: Lucid\": \"https://www.cbtnews.com/lucid-trims-production-forecast-misses-q3-expectations/\",\n", + " \"Article 4: Shift4\": \"https://www.investing.com/news/transcripts/earnings-call-transcript-shift4-payments-q3-2025-shows-strong-growth-93CH-4339020\",\n", + " \"Article 5: Lenz\": \"https://ir.lenz-tx.com/news-events/press-releases/detail/43/lenz-therapeutics-reports-third-quarter-2025-financial-results-and-recent-corporate-highlights\"\n", + "}\n", + "\n", + "results = []\n", + "print(\"--- Starting Batch ReAct Analysis ---\")\n", + "\n", + "for name, url in CASE_STUDY_URLS.items():\n", + " print(f\"\\nAnalyzing: {name}...\")\n", + " analysis_text = run_granite_react_agent(url)\n", + " \n", + " # Split into sections for display\n", + " sentiment = \"\"\n", + " topic = \"\"\n", + " summary = []\n", + " \n", + " # Extract Sentiment and Topic from text\n", + " for line in analysis_text.splitlines():\n", + " if line.startswith(\"Sentiment:\"):\n", + " sentiment = line.replace(\"Sentiment:\", \"\").strip()\n", + " elif line.startswith(\"Topic:\"):\n", + " topic = line.replace(\"Topic:\", \"\").strip()\n", + " elif line.startswith(\"- \"):\n", + " summary.append(line.strip())\n", + " \n", + " results.append({\n", + " \"Company/Case\": name,\n", + " \"Sentiment\": sentiment,\n", + " \"Topic\": topic,\n", + " \"Summary\": \"\\n\".join(summary)\n", + " })\n", + "\n", + "df = pd.DataFrame(results)\n", + "print(\"\\n--- Batch Analysis Results ---\\n\")\n", + "\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Conclusion\n", + "\n", + "This tutorial demonstrated how to build a financial insight agent using the ReAct framework and the IBM Granite 4.0 Nano language model.\n", + "You learned how the agent performs reasoning and action steps, retrieves external data through a simulated lookup tool, and produces structured financial summaries.\n", + "\n", + "The ReAct workflow helps reduce hallucination by grounding each decision in retrieved information rather than relying only on internal memory.\n", + "By combining transparent reasoning traces with tool use, the agent provides reliable, auditable financial insights.\n", + "\n", + "**Next Steps**\n", + "\n", + "You can extend this tutorial by connecting the agent to live financial APIs or a retrieval‑augmented generation (RAG) pipeline for real‑time market analysis.\n", + "\n", + "Running the model on IBM watsonx.ai improves speed and scalability, while custom prompts or domain‑specific fine‑tuning can further enhance performance for specialized financial use cases.\n" + ] + } + ], + "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.12.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/images/ReAct-prompting.jpg b/images/ReAct-prompting.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b447cb15ed3eb18aec195b1e8f3715e344ec5df4 GIT binary patch literal 16789 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 AD*ylh literal 0 HcmV?d00001 diff --git a/images/react-prompting.png b/images/react-prompting.png new file mode 100644 index 0000000000000000000000000000000000000000..83d28614beedd3b76c42c676c447db8b9d43ed4e GIT binary patch literal 31388 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 l8vIs+-)iu`&Q@G@( Date: Mon, 24 Nov 2025 11:34:25 -0500 Subject: [PATCH 2/2] Add react prompting tutorial to mkdocs site Signed-off-by: vwinland --- docs/assets/react-prompting.png | Bin 0 -> 31388 bytes docs/tutorials-list.md | 1 + docs/tutorials/prompt-engineering/ReAct.ipynb | 662 ++++++++++++++++++ 3 files changed, 663 insertions(+) create mode 100644 docs/assets/react-prompting.png create mode 100644 docs/tutorials/prompt-engineering/ReAct.ipynb diff --git a/docs/assets/react-prompting.png b/docs/assets/react-prompting.png new file mode 100644 index 0000000000000000000000000000000000000000..83d28614beedd3b76c42c676c447db8b9d43ed4e GIT binary patch literal 31388 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 l8vIs+-)iu`&Q@G@( str:\n", + " \"\"\"Simulates a web lookup by returning article text for a given URL.\"\"\"\n", + " return SIMULATED_ARTICLE_DATA.get(url, f\"Error: Could not retrieve content for {url}. URL not found.\")\n", + "\n", + "print(\"✅ web_browser tool defined successfully.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 5. Define the ReAct Prompt Template\n", + "\n", + "This step defines the prompt that guides the agent’s reasoning. The template enforces the ReAct pattern — Thought → Action → Observation — and specifies how the model should use the web_browser() tool and format its final output as structured JSON." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "REACT_PROMPT_TEMPLATE = \"\"\"\n", + "You are an expert financial analyst agent. Your task is to analyze the provided financial news article URL. Follow the ReAct framework (Thought -> Action -> Observation) until you produce the final structured JSON analysis.\n", + "\n", + "**AVAILABLE TOOLS:**\n", + "web_browser(url: str) -> str: Fetches the full textual content of the article.\n", + "\n", + "**FINAL OUTPUT INSTRUCTIONS:**\n", + "Return a single JSON object with:\n", + "1. \"Classification\": {{ \"Sentiment\": Positive/Negative/Mixed/Neutral, \"Topic\": Earnings/Regulatory/Product/Operations/M&A/HR/Layoffs/Supply Chain }}\n", + "2. \"Summary\": A concise bulleted list of 3-5 key takeaways.\n", + "\n", + "---\n", + "**INPUT ARTICLE URL:** {url}\n", + "---\n", + "**START ANALYSIS:**\n", + "\"\"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 6. Tool Execution Function\n", + "\n", + "This function prepares the initial ReAct prompt, performs the tool call, and returns all intermediate components for the next step." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "def prepare_react_context(url: str, verbose: bool = False):\n", + " current_prompt = REACT_PROMPT_TEMPLATE.format(url=url)\n", + " action_call = f\"Action: web_browser(url='{url}')\"\n", + " observation = web_browser(url)\n", + " \n", + " if verbose:\n", + " print(f\"Prompt snippet: {current_prompt[:120]}...\")\n", + " print(f\"Observation snippet: {observation[:120]}...\")\n", + " \n", + " return current_prompt, action_call, observation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 7. Define the Main ReAct Agent Function\n", + "\n", + "This function calls the setup function above, performs model generation, and safely parses the JSON output." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "def run_granite_react_agent(url: str, max_tokens: int = 512, verbose: bool = False) -> str:\n", + " \"\"\"Executes the ReAct loop using Granite model and returns structured text analysis.\"\"\"\n", + " \n", + " current_prompt, action_call, observation = prepare_react_context(url, verbose)\n", + " \n", + " # Strong instruction for structured output\n", + " system_instruction = (\n", + " \"You are an expert financial analyst agent following the ReAct framework. \"\n", + " \"Analyze the observation and produce the output in this exact format:\\n\\n\"\n", + " \"Classification:\\nSentiment: Positive/Negative/Mixed/Neutral\\nTopic: Earnings/Regulatory/Product/Operations/M&A/HR/Layoffs/Supply Chain\\n\\n\"\n", + " \"Summary:\\n- Key takeaway 1\\n- Key takeaway 2\\n- Key takeaway 3\\n\\n\"\n", + " \"Start with Classification, then Summary. Do not include any extra text or role markers. \"\n", + " \"Base sentiment and topic ONLY on the observation text. Use actual values from the article.\"\n", + " )\n", + " \n", + " chat = [\n", + " {\"role\": \"system\", \"content\": system_instruction},\n", + " {\"role\": \"user\", \"content\": f\"{current_prompt}\\nThought: Retrieve article content using web_browser tool.\\n\"\n", + " f\"{action_call}\\nObservation: {observation}\\n\\n\"\n", + " f\"Thought: Proceed with classification and summarization.\\nAction:\"}\n", + " ]\n", + " \n", + " # Apply Granite chat template\n", + " chat_text = tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=True)\n", + " input_tokens = tokenizer(chat_text, return_tensors=\"pt\").to(device)\n", + " \n", + " if verbose:\n", + " print(f\"Prompt length: {len(chat_text)} chars | Token count: {input_tokens['input_ids'].shape[-1]}\")\n", + " \n", + " # Generate output\n", + " with torch.no_grad():\n", + " output_tokens = model.generate(**input_tokens, max_new_tokens=max_tokens)\n", + " \n", + " # Decode response\n", + " full_response = tokenizer.batch_decode(output_tokens, skip_special_tokens=True)[0]\n", + " \n", + " return full_response.strip()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 8. Test Single URL\n", + "\n", + "This step runs the ReAct agent on one example URL to verify the workflow and ensure the model generates a valid JSON response before processing multiple cases." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Testing single ReAct run...\n", + "Prompt snippet: \n", + "You are an expert financial analyst agent. Your task is to analyze the provided financial news article URL. Follow the ...\n", + "Observation snippet: \n", + " Luminar faces SEC investigation, missed payments, CFO exit, and workforce cuts. Bankruptcy risk rising amid liquidi...\n", + "Prompt length: 1795 chars | Token count: 400\n", + "\n", + "--- Model Output ---\n", + "\n", + "systemYou are an expert financial analyst agent following the ReAct framework. Analyze the observation and produce the output in this exact format:\n", + "\n", + "Classification:\n", + "Sentiment: Positive/Negative/Mixed/Neutral\n", + "Topic: Earnings/Regulatory/Product/Operations/M&A/HR/Layoffs/Supply Chain\n", + "\n", + "Summary:\n", + "- Key takeaway 1\n", + "- Key takeaway 2\n", + "- Key takeaway 3\n", + "\n", + "Start with Classification, then Summary. Do not include any extra text or role markers. Base sentiment and topic ONLY on the observation text. Use actual values from the article.\n", + "user\n", + "You are an expert financial analyst agent. Your task is to analyze the provided financial news article URL. Follow the ReAct framework (Thought -> Action -> Observation) until you produce the final structured JSON analysis.\n", + "\n", + "**AVAILABLE TOOLS:**\n", + "web_browser(url: str) -> str: Fetches the full textual content of the article.\n", + "\n", + "**FINAL OUTPUT INSTRUCTIONS:**\n", + "Return a single JSON object with:\n", + "1. \"Classification\": { \"Sentiment\": Positive/Negative/Mixed/Neutral, \"Topic\": Earnings/Regulatory/Product/Operations/M&A/HR/Layoffs/Supply Chain }\n", + "2. \"Summary\": A concise bulleted list of 3-5 key takeaways.\n", + "\n", + "---\n", + "**INPUT ARTICLE URL:** https://www.photonics.com/Articles/Amid-SEC-Investigation-Luminar-Cuts-Workforce/p5/a71615\n", + "---\n", + "**START ANALYSIS:**\n", + "\n", + "Thought: Retrieve article content using web_browser tool.\n", + "Action: web_browser(url='https://www.photonics.com/Articles/Amid-SEC-Investigation-Luminar-Cuts-Workforce/p5/a71615')\n", + "Observation: \n", + " Luminar faces SEC investigation, missed payments, CFO exit, and workforce cuts. Bankruptcy risk rising amid liquidity issues.\n", + " \n", + "\n", + "Thought: Proceed with classification and summarization.\n", + "Action:\n", + "assistantClassification:\n", + "Sentiment: Negative\n", + "Topic: Operations\n", + "\n", + "Summary:\n", + "- Luminar faces a significant SEC investigation due to missed payments.\n", + "- The company has experienced a CFO exit, indicating internal financial challenges.\n", + "- Luminar has implemented workforce cuts to address liquidity issues.\n", + "- The company is facing bankruptcy risk due to its financial struggles.\n" + ] + } + ], + "source": [ + "# Test single URL before batch\n", + "test_url = \"https://www.photonics.com/Articles/Amid-SEC-Investigation-Luminar-Cuts-Workforce/p5/a71615\"\n", + "print(\"Testing single ReAct run...\")\n", + "result = run_granite_react_agent(test_url, verbose=True)\n", + "print(\"\\n--- Model Output ---\\n\")\n", + "print(result)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 9. Batch Analysis\n", + "\n", + "This cell applies the ReAct agent to all case study URLs, aggregates the results into a structured DataFrame, and displays the sentiment, topic, and summary for each company." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--- Starting Batch ReAct Analysis ---\n", + "\n", + "Analyzing: Article 1: Luminar...\n", + "\n", + "Analyzing: Article 2: Celsius...\n", + "\n", + "Analyzing: Article 3: Lucid...\n", + "\n", + "Analyzing: Article 4: Shift4...\n", + "\n", + "Analyzing: Article 5: Lenz...\n", + "\n", + "--- Batch Analysis Results ---\n", + "\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Company/CaseSentimentTopicSummary
0Article 1: LuminarNegativeOperations- Key takeaway 1\\n- Key takeaway 2\\n- Key takeaway 3\\n- Luminar faces a significant SEC investigation due to missed payments.\\n- The company has experienced a CFO exit, indicating internal financial challenges.\\n- Luminar has implemented workforce cuts to address liquidity issues.\\n- The company is facing bankruptcy risk due to its financial struggles.
1Article 2: CelsiusNegativeEarnings- Key takeaway 1\\n- Key takeaway 2\\n- Key takeaway 3\\n- Celsius reported EPS of $0.42, beating estimates.\\n- Revenue of $725.1M, also exceeding expectations.\\n- Stock price fell 17.5% following the earnings call.
2Article 3: LucidNegativeOperations- Key takeaway 1\\n- Key takeaway 2\\n- Key takeaway 3\\n- Lucid missed Q3 revenue targets, resulting in a $336.6 million shortfall.\\n- The company experienced a widening loss of $2.65 per share.\\n- Production forecasts were reduced due to supply chain challenges.
3Article 4: Shift4PositiveEarnings- Key takeaway 1\\n- Key takeaway 2\\n- Key takeaway 3\\n- EPS beat expectations with $1.47\\n- Revenue missed the target at $589.2M
4Article 5: LenzNegativeEarnings- Key takeaway 1\\n- Key takeaway 2\\n- Key takeaway 3\\n- Q3 2025 financial results show a net loss of $16.7 million.\\n- VIZZ launch and increased prescriptions indicate product performance.\\n- $10 million in milestone payments reflect successful clinical progress.\\n- Operating expenses rose 44% due to increased SG&A costs.
\n", + "
" + ], + "text/plain": [ + " Company/Case Sentiment Topic \\\n", + "0 Article 1: Luminar Negative Operations \n", + "1 Article 2: Celsius Negative Earnings \n", + "2 Article 3: Lucid Negative Operations \n", + "3 Article 4: Shift4 Positive Earnings \n", + "4 Article 5: Lenz Negative Earnings \n", + "\n", + " Summary \n", + "0 - Key takeaway 1\\n- Key takeaway 2\\n- Key takeaway 3\\n- Luminar faces a significant SEC investigation due to missed payments.\\n- The company has experienced a CFO exit, indicating internal financial challenges.\\n- Luminar has implemented workforce cuts to address liquidity issues.\\n- The company is facing bankruptcy risk due to its financial struggles. \n", + "1 - Key takeaway 1\\n- Key takeaway 2\\n- Key takeaway 3\\n- Celsius reported EPS of $0.42, beating estimates.\\n- Revenue of $725.1M, also exceeding expectations.\\n- Stock price fell 17.5% following the earnings call. \n", + "2 - Key takeaway 1\\n- Key takeaway 2\\n- Key takeaway 3\\n- Lucid missed Q3 revenue targets, resulting in a $336.6 million shortfall.\\n- The company experienced a widening loss of $2.65 per share.\\n- Production forecasts were reduced due to supply chain challenges. \n", + "3 - Key takeaway 1\\n- Key takeaway 2\\n- Key takeaway 3\\n- EPS beat expectations with $1.47\\n- Revenue missed the target at $589.2M \n", + "4 - Key takeaway 1\\n- Key takeaway 2\\n- Key takeaway 3\\n- Q3 2025 financial results show a net loss of $16.7 million.\\n- VIZZ launch and increased prescriptions indicate product performance.\\n- $10 million in milestone payments reflect successful clinical progress.\\n- Operating expenses rose 44% due to increased SG&A costs. " + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pandas as pd\n", + "\n", + "CASE_STUDY_URLS = {\n", + " \"Article 1: Luminar\": \"https://www.photonics.com/Articles/Amid-SEC-Investigation-Luminar-Cuts-Workforce/p5/a71615\",\n", + " \"Article 2: Celsius\": \"https://www.investing.com/news/transcripts/earnings-call-transcript-celsius-q3-2025-earnings-beat-expectations-stock-dips-93CH-4338388\",\n", + " \"Article 3: Lucid\": \"https://www.cbtnews.com/lucid-trims-production-forecast-misses-q3-expectations/\",\n", + " \"Article 4: Shift4\": \"https://www.investing.com/news/transcripts/earnings-call-transcript-shift4-payments-q3-2025-shows-strong-growth-93CH-4339020\",\n", + " \"Article 5: Lenz\": \"https://ir.lenz-tx.com/news-events/press-releases/detail/43/lenz-therapeutics-reports-third-quarter-2025-financial-results-and-recent-corporate-highlights\"\n", + "}\n", + "\n", + "results = []\n", + "print(\"--- Starting Batch ReAct Analysis ---\")\n", + "\n", + "for name, url in CASE_STUDY_URLS.items():\n", + " print(f\"\\nAnalyzing: {name}...\")\n", + " analysis_text = run_granite_react_agent(url)\n", + " \n", + " # Split into sections for display\n", + " sentiment = \"\"\n", + " topic = \"\"\n", + " summary = []\n", + " \n", + " # Extract Sentiment and Topic from text\n", + " for line in analysis_text.splitlines():\n", + " if line.startswith(\"Sentiment:\"):\n", + " sentiment = line.replace(\"Sentiment:\", \"\").strip()\n", + " elif line.startswith(\"Topic:\"):\n", + " topic = line.replace(\"Topic:\", \"\").strip()\n", + " elif line.startswith(\"- \"):\n", + " summary.append(line.strip())\n", + " \n", + " results.append({\n", + " \"Company/Case\": name,\n", + " \"Sentiment\": sentiment,\n", + " \"Topic\": topic,\n", + " \"Summary\": \"\\n\".join(summary)\n", + " })\n", + "\n", + "df = pd.DataFrame(results)\n", + "print(\"\\n--- Batch Analysis Results ---\\n\")\n", + "\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Conclusion\n", + "\n", + "This tutorial demonstrated how to build a financial insight agent using the ReAct framework and the IBM Granite 4.0 Nano language model.\n", + "You learned how the agent performs reasoning and action steps, retrieves external data through a simulated lookup tool, and produces structured financial summaries.\n", + "\n", + "The ReAct workflow helps reduce hallucination by grounding each decision in retrieved information rather than relying only on internal memory.\n", + "By combining transparent reasoning traces with tool use, the agent provides reliable, auditable financial insights.\n", + "\n", + "**Next Steps**\n", + "\n", + "You can extend this tutorial by connecting the agent to live financial APIs or a retrieval‑augmented generation (RAG) pipeline for real‑time market analysis.\n", + "\n", + "Running the model on IBM watsonx.ai improves speed and scalability, while custom prompts or domain‑specific fine‑tuning can further enhance performance for specialized financial use cases.\n" + ] + } + ], + "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.12.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}