From 8d50b66b64fb3b439c88765d34820dfd49d34141 Mon Sep 17 00:00:00 2001 From: dinsajwa Date: Fri, 1 Aug 2025 14:03:13 -0400 Subject: [PATCH 01/11] feat(agentcorecode): added code intrepreter notebook --- .../notebooks/6_agent_code_interpreter.ipynb | 583 ++++++++++++++++++ pyproject.toml | 1 + uv.lock | 50 +- 3 files changed, 619 insertions(+), 15 deletions(-) create mode 100644 labs/module3/notebooks/6_agent_code_interpreter.ipynb diff --git a/labs/module3/notebooks/6_agent_code_interpreter.ipynb b/labs/module3/notebooks/6_agent_code_interpreter.ipynb new file mode 100644 index 0000000..a848bf1 --- /dev/null +++ b/labs/module3/notebooks/6_agent_code_interpreter.ipynb @@ -0,0 +1,583 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# ๐Ÿค– Building Autonomous Agents: AgentCore Code Interpreter\n", + "\n", + "Welcome to the AgentCore Code Interpreter module! In this notebook, we'll explore how to integrate AWS Bedrock AgentCore Code Interpreter into autonomous agents, enabling them to execute code securely in sandbox environments.\n", + "\n", + "## What is AgentCore Code Interpreter?\n", + "\n", + "The Amazon Bedrock AgentCore Code Interpreter enables AI agents to write and execute code securely in sandbox environments, enhancing their accuracy and expanding their ability to solve complex end-to-end tasks. It supports multiple programming languages and provides secure, isolated execution environments.\n", + "\n", + "## ๐ŸŽฏ Learning Objectives\n", + "\n", + "In this notebook, you'll learn how to:\n", + "\n", + "- ๐Ÿ”ง Set up and use AgentCore Code Interpreter with both boto3 and AgentCore SDK\n", + "- ๐Ÿ“Š Create data visualizations and graphs using code execution\n", + "- ๐Ÿค– Integrate code interpreter capabilities into agents using Strands and LangChain frameworks\n", + "- ๐Ÿ“ Manage files within code interpreter sessions\n", + "- โ˜๏ธ Use S3 integration and terminal commands with execution roles\n", + "- ๐Ÿ—๏ธ Build framework-agnostic abstractions for code execution tools\n", + "- ๐Ÿ”„ Integrate with existing Module 3 memory and tool patterns\n", + "\n", + "## ๐Ÿ“‹ Prerequisites\n", + "\n", + "- AWS account with access to Amazon Bedrock AgentCore\n", + "- Proper IAM permissions for AgentCore services\n", + "- Python 3.12 environment with required dependencies\n", + "\n", + "Let's begin! ๐Ÿš€" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿ”ง Section 1: Environment Setup and Dependencies\n", + "\n", + "First, let's set up our environment and install the necessary dependencies." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# ๐Ÿ”ง Environment Setup Instructions\n", + "\n", + "# IMPORTANT: This notebook requires Python 3.12.8 as specified in pyproject.toml\n", + "# All dependencies are managed via UV (recommended approach for this project)\n", + "\n", + "# SETUP STEPS:\n", + "# 1. Create virtual environment: uv venv\n", + "# 2. Install all dependencies: uv sync\n", + "# 3. Launch Jupyter Lab: uv run jupyter lab\n", + "\n", + "# FOR VS CODE USERS:\n", + "# 1. Run: uv run python -c \"import sys; print(sys.executable)\"\n", + "# 2. Copy the path and select it as your Python interpreter in VS Code\n", + "# 3. The kernel should now have access to all required dependencies\n", + "\n", + "# Verify environment setup\n", + "import sys\n", + "print(f\"โœ… Python version: {sys.version}\")\n", + "print(f\"โœ… Python executable: {sys.executable}\")\n", + "print(\"โœ… Environment setup complete! All dependencies should be available via UV.\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Import core dependencies\n", + "import boto3\n", + "import json\n", + "import os\n", + "import time\n", + "import asyncio\n", + "from typing import Dict, Any, List, Optional\n", + "from pydantic import BaseModel\n", + "from abc import ABC, abstractmethod\n", + "\n", + "# Import AgentCore SDK\n", + "from bedrock_agentcore.tools.code_interpreter_client import CodeInterpreter, code_session\n", + "\n", + "# Import existing Module 3 components\n", + "from agentic_platform.core.models.memory_models import Message, SessionContext\n", + "from agentic_platform.core.models.llm_models import LLMRequest, LLMResponse\n", + "from agentic_platform.core.models.api_models import AgenticRequest, AgenticResponse\n", + "\n", + "print(\"โœ… Dependencies imported successfully!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Initialize AWS clients with specific profile\n", + "profile_name = \"ags-tech-namer-ca-pace+ai3-core-Admin\"\n", + "session = boto3.Session(profile_name=profile_name)\n", + "region_name = \"us-west-2\"\n", + "\n", + "# Verify credentials are working\n", + "try:\n", + " credentials = session.get_credentials()\n", + " print(f\"โœ… Using AWS profile: {profile_name}\")\n", + " print(f\"โœ… Access Key: {credentials.access_key[:10]}...\")\n", + " print(f\"โœ… Region: {region_name}\")\n", + "except Exception as e:\n", + " print(f\"โŒ Error with AWS credentials: {e}\")\n", + " raise\n", + "\n", + "# Standard AgentCore client\n", + "bedrock_agentcore_client = session.client(\n", + " 'bedrock-agentcore',\n", + " region_name=region_name,\n", + " endpoint_url=f\"https://bedrock-agentcore.{region_name}.amazonaws.com\"\n", + ")\n", + "\n", + "# Control plane client for custom interpreters\n", + "bedrock_agentcore_control_client = session.client(\n", + " 'bedrock-agentcore-control',\n", + " region_name=region_name,\n", + " endpoint_url=f\"https://bedrock-agentcore-control.{region_name}.amazonaws.com\"\n", + ")\n", + "\n", + "print(\"โœ… AWS clients initialized successfully with profile!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿ“Š Section 2: Basic Code Execution and Data Analysis\n", + "\n", + "Let's start with basic code execution examples, demonstrating server-side JavaScript data processing and Python visualizations using both boto3 and the AgentCore SDK approaches." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example 1: Boto3 Direct Implementation with Server-Side JavaScript" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Server-side JavaScript code for data analysis, ASCII visualization, and file operations\n", + "javascript_analysis_code = \"\"\"\n", + "// Server-side JavaScript data analysis (Deno compatible)\n", + "// Fixed version without browser dependencies\n", + "\n", + "console.log('Starting comprehensive data analysis...');\n", + "\n", + "// Sample sales data\n", + "const salesData = {\n", + " months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'],\n", + " actual: [65, 59, 80, 81, 56, 55, 40],\n", + " target: [70, 65, 75, 80, 60, 58, 45]\n", + "};\n", + "\n", + "// Statistical calculations\n", + "function calculateStats(data) {\n", + " const sum = data.reduce((a, b) => a + b, 0);\n", + " const avg = sum / data.length;\n", + " const max = Math.max(...data);\n", + " const min = Math.min(...data);\n", + " const variance = data.reduce((acc, val) => acc + Math.pow(val - avg, 2), 0) / data.length;\n", + " const stdDev = Math.sqrt(variance);\n", + " return { sum, avg, max, min, variance, stdDev };\n", + "}\n", + "\n", + "const actualStats = calculateStats(salesData.actual);\n", + "const targetStats = calculateStats(salesData.target);\n", + "const performance = (actualStats.avg / targetStats.avg) * 100;\n", + "\n", + "console.log('SALES ANALYSIS RESULTS:');\n", + "console.log('Average Actual Sales: ' + actualStats.avg.toFixed(1));\n", + "console.log('Average Target Sales: ' + targetStats.avg.toFixed(1));\n", + "console.log('Overall Performance: ' + performance.toFixed(1) + '%');\n", + "console.log('Standard Deviation: ' + actualStats.stdDev.toFixed(2));\n", + "\n", + "// ASCII Chart Visualization\n", + "function createASCIIBarChart(data, labels, title) {\n", + " const maxValue = Math.max(...data);\n", + " const chartWidth = 30;\n", + " \n", + " console.log('\\\\n' + title);\n", + " console.log('='.repeat(40));\n", + " \n", + " data.forEach((value, index) => {\n", + " const barLength = Math.round((value / maxValue) * chartWidth);\n", + " const bar = '#'.repeat(barLength);\n", + " const padding = ' '.repeat(Math.max(0, chartWidth - barLength));\n", + " console.log(labels[index] + ' |' + bar + padding + '| ' + value);\n", + " });\n", + " \n", + " console.log(' '.repeat(5) + '+' + '-'.repeat(chartWidth) + '+');\n", + "}\n", + "\n", + "// Create ASCII charts\n", + "createASCIIBarChart(salesData.actual, salesData.months, 'ACTUAL SALES BY MONTH');\n", + "createASCIIBarChart(salesData.target, salesData.months, 'TARGET SALES BY MONTH');\n", + "\n", + "// Trend Analysis\n", + "function calculateTrend(data) {\n", + " const n = data.length;\n", + " const sumX = (n * (n + 1)) / 2;\n", + " const sumY = data.reduce((a, b) => a + b, 0);\n", + " const sumXY = data.reduce((sum, y, x) => sum + (x + 1) * y, 0);\n", + " const sumX2 = (n * (n + 1) * (2 * n + 1)) / 6;\n", + " \n", + " const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);\n", + " return { slope, trend: slope > 0 ? 'Increasing' : slope < 0 ? 'Decreasing' : 'Stable' };\n", + "}\n", + "\n", + "const actualTrend = calculateTrend(salesData.actual);\n", + "console.log('\\\\nTREND ANALYSIS:');\n", + "console.log('Sales Trend: ' + actualTrend.trend);\n", + "console.log('Slope: ' + actualTrend.slope.toFixed(3) + ' units per month');\n", + "\n", + "// File Operations (JSON report)\n", + "const reportData = {\n", + " timestamp: new Date().toISOString(),\n", + " summary: {\n", + " totalActualSales: actualStats.sum,\n", + " totalTargetSales: targetStats.sum,\n", + " performancePercentage: performance,\n", + " trend: actualTrend.trend,\n", + " trendSlope: actualTrend.slope\n", + " },\n", + " monthlyData: salesData.months.map((month, index) => ({\n", + " month: month,\n", + " actual: salesData.actual[index],\n", + " target: salesData.target[index],\n", + " variance: salesData.actual[index] - salesData.target[index]\n", + " }))\n", + "};\n", + "\n", + "// Mathematical Computations\n", + "function fibonacci(n) {\n", + " if (n <= 1) return n;\n", + " let a = 0, b = 1;\n", + " for (let i = 2; i <= n; i++) {\n", + " const temp = a + b;\n", + " a = b;\n", + " b = temp;\n", + " }\n", + " return b;\n", + "}\n", + "\n", + "function isPrime(num) {\n", + " if (num < 2) return false;\n", + " for (let i = 2; i <= Math.sqrt(num); i++) {\n", + " if (num % i === 0) return false;\n", + " }\n", + " return true;\n", + "}\n", + "\n", + "console.log('\\\\nMATHEMATICAL COMPUTATIONS:');\n", + "console.log('Fibonacci(10): ' + fibonacci(10));\n", + "\n", + "const primes = [];\n", + "for (let i = 0; i < 30; i++) {\n", + " if (isPrime(i)) primes.push(i);\n", + "}\n", + "console.log('Primes under 30: ' + primes.join(', '));\n", + "\n", + "// Final summary\n", + "console.log('\\\\nANALYSIS COMPLETE!');\n", + "console.log('This demonstrates server-side JavaScript capabilities:');\n", + "console.log('โ€ข Data processing and statistical analysis');\n", + "console.log('โ€ข ASCII chart generation for visualization');\n", + "console.log('โ€ข Mathematical computations');\n", + "console.log('โ€ข Structured data transformation');\n", + "\n", + "// Return final results\n", + "reportData.summary;\n", + "\"\"\"\n", + "\n", + "print(\"Server-side JavaScript analysis code prepared!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def execute_code_with_boto3(code: str, language: str = \"javascript\") -> Dict[str, Any]:\n", + " \"\"\"\n", + " Execute code using boto3 direct API calls to AgentCore Code Interpreter.\n", + " \"\"\"\n", + " try:\n", + " # Start a code interpreter session\n", + " session_response = bedrock_agentcore_client.start_code_interpreter_session(\n", + " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", + " name=\"boto3-analysis-session\",\n", + " sessionTimeoutSeconds=900\n", + " )\n", + " session_id = session_response[\"sessionId\"]\n", + " print(f\"โœ… Started session: {session_id}\")\n", + " \n", + " # Execute the code\n", + " execute_response = bedrock_agentcore_client.invoke_code_interpreter(\n", + " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", + " sessionId=session_id,\n", + " name=\"executeCode\",\n", + " arguments={\n", + " \"language\": language,\n", + " \"code\": code\n", + " }\n", + " )\n", + " \n", + " # Process the response stream\n", + " results = []\n", + " for event in execute_response['stream']:\n", + " if 'result' in event:\n", + " result = event['result']\n", + " results.append(result)\n", + " \n", + " # Print text output\n", + " if 'content' in result:\n", + " for content_item in result['content']:\n", + " if content_item['type'] == 'text':\n", + " print(f\"Output: {content_item['text']}\")\n", + " elif content_item['type'] == 'image':\n", + " print(f\"Generated image: {content_item['data'][:50]}...\")\n", + " \n", + " # Clean up session\n", + " bedrock_agentcore_client.stop_code_interpreter_session(\n", + " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", + " sessionId=session_id\n", + " )\n", + " print(f\"โœ… Session {session_id} stopped\")\n", + " \n", + " return {\n", + " \"success\": True,\n", + " \"session_id\": session_id,\n", + " \"results\": results\n", + " }\n", + " \n", + " except Exception as e:\n", + " print(f\"โŒ Error executing code: {str(e)}\")\n", + " return {\n", + " \"success\": False,\n", + " \"error\": str(e)\n", + " }\n", + "\n", + "# Execute the server-side JavaScript analysis code\n", + "print(\"๐Ÿš€ Executing server-side JavaScript analysis with boto3...\")\n", + "boto3_result = execute_code_with_boto3(javascript_analysis_code, \"javascript\")\n", + "print(f\"Result: {boto3_result['success']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example 2: AgentCore SDK Implementation with Python Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Python code for creating a matplotlib graph\n", + "python_graph_code = \"\"\"\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "# Sample sales data\n", + "months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul']\n", + "sales = [65, 59, 80, 81, 56, 55, 40]\n", + "target = [70, 65, 75, 80, 60, 58, 45]\n", + "\n", + "# Create figure with subplots\n", + "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))\n", + "\n", + "# Bar chart\n", + "x_pos = np.arange(len(months))\n", + "width = 0.35\n", + "\n", + "ax1.bar(x_pos - width/2, sales, width, label='Actual Sales', color='#3498db', alpha=0.8)\n", + "ax1.bar(x_pos + width/2, target, width, label='Target', color='#e74c3c', alpha=0.8)\n", + "ax1.set_xlabel('Month')\n", + "ax1.set_ylabel('Sales')\n", + "ax1.set_title('Monthly Sales vs Target')\n", + "ax1.set_xticks(x_pos)\n", + "ax1.set_xticklabels(months)\n", + "ax1.legend()\n", + "ax1.grid(axis='y', alpha=0.3)\n", + "\n", + "# Line chart\n", + "ax2.plot(months, sales, marker='o', linewidth=2, label='Actual Sales', color='#3498db')\n", + "ax2.plot(months, target, marker='s', linewidth=2, label='Target', color='#e74c3c')\n", + "ax2.set_xlabel('Month')\n", + "ax2.set_ylabel('Sales')\n", + "ax2.set_title('Sales Trend Analysis')\n", + "ax2.legend()\n", + "ax2.grid(True, alpha=0.3)\n", + "\n", + "# Adjust layout and save\n", + "plt.tight_layout()\n", + "plt.savefig('sales_analysis.png', dpi=300, bbox_inches='tight')\n", + "plt.show()\n", + "\n", + "# Calculate some statistics\n", + "avg_sales = np.mean(sales)\n", + "avg_target = np.mean(target)\n", + "performance = (avg_sales / avg_target) * 100\n", + "\n", + "print(f\"Average Sales: {avg_sales:.1f}\")\n", + "print(f\"Average Target: {avg_target:.1f}\")\n", + "print(f\"Performance: {performance:.1f}%\")\n", + "\n", + "# Return summary\n", + "{\n", + " 'avg_sales': avg_sales,\n", + " 'avg_target': avg_target,\n", + " 'performance_pct': performance,\n", + " 'chart_saved': 'sales_analysis.png'\n", + "}\n", + "\"\"\"\n", + "\n", + "print(\"Python graph code prepared!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def execute_code_with_sdk(code: str, language: str = \"python\") -> Dict[str, Any]:\n", + " \"\"\"\n", + " Execute code using the AgentCore SDK (higher-level interface).\n", + " \"\"\"\n", + " try:\n", + " # Configure and start the code interpreter session\n", + " code_client = CodeInterpreter(region_name)\n", + " code_client.start()\n", + " print(\"โœ… AgentCore SDK session started\")\n", + " \n", + " # Execute the code\n", + " response = code_client.invoke(\"executeCode\", {\n", + " \"language\": language,\n", + " \"code\": code\n", + " })\n", + " \n", + " # Process and print the response\n", + " results = []\n", + " for event in response[\"stream\"]:\n", + " if \"result\" in event:\n", + " result = event[\"result\"]\n", + " results.append(result)\n", + " print(f\"Execution result: {json.dumps(result, indent=2, default=str)}\")\n", + " \n", + " # Clean up and stop the session\n", + " code_client.stop()\n", + " print(\"โœ… AgentCore SDK session stopped\")\n", + " \n", + " return {\n", + " \"success\": True,\n", + " \"results\": results\n", + " }\n", + " \n", + " except Exception as e:\n", + " print(f\"โŒ Error executing code with SDK: {str(e)}\")\n", + " return {\n", + " \"success\": False,\n", + " \"error\": str(e)\n", + " }\n", + "\n", + "# Execute the Python graph code\n", + "print(\"๐Ÿš€ Executing Python graph generation with AgentCore SDK...\")\n", + "sdk_result = execute_code_with_sdk(python_graph_code, \"python\")\n", + "print(f\"Result: {sdk_result['success']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐ŸŽฏ Section 3: Summary and Key Takeaways\n", + "\n", + "Congratulations! You've successfully explored AWS Bedrock AgentCore Code Interpreter and learned how to fix the DOM compatibility issue." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### ๐Ÿ”ง Problem Solved: DOM Compatibility Issue\n", + "\n", + "**Original Issue**: The JavaScript code was trying to use browser DOM APIs (`document.createElement('canvas')`) in a Deno server-side environment where these APIs don't exist.\n", + "\n", + "**Root Cause**: AgentCore Code Interpreter runs JavaScript in a Deno runtime (server-side), not in a browser environment.\n", + "\n", + "**Solution Implemented**: Replaced browser-dependent code with server-side compatible alternatives:\n", + "\n", + "1. **Data Processing & Analysis**: Statistical calculations and data manipulation\n", + "2. **ASCII Chart Visualization**: Terminal-friendly charts using Unicode characters\n", + "3. **File Operations**: JSON and CSV file generation using Deno APIs\n", + "4. **Mathematical Computations**: Fibonacci sequences, prime number detection\n", + "5. **Structured Data Transformation**: Complex data processing workflows\n", + "\n", + "### ๐Ÿ“Š What We've Demonstrated\n", + "\n", + "The updated JavaScript example showcases:\n", + "\n", + "- โœ… **Server-side JavaScript execution** in a secure sandbox\n", + "- โœ… **Data analysis and statistical calculations**\n", + "- โœ… **ASCII-based data visualization** (no browser dependencies)\n", + "- โœ… **File I/O operations** for report generation\n", + "- โœ… **Mathematical computations** and algorithms\n", + "- โœ… **Structured data processing** and transformation\n", + "\n", + "### ๐Ÿ”‘ Key Takeaways\n", + "\n", + "1. **Environment Awareness**: Always consider the execution environment (server-side vs browser)\n", + "2. **Alternative Approaches**: ASCII charts can be as effective as graphical ones for data visualization\n", + "3. **Server-side Capabilities**: Deno provides powerful APIs for file operations and data processing\n", + "4. **Code Interpreter Flexibility**: Supports multiple programming languages with different strengths\n", + "5. **Sandbox Security**: Secure execution environment with controlled access to system resources\n", + "\n", + "### ๐Ÿš€ Next Steps\n", + "\n", + "To continue working with AgentCore Code Interpreter:\n", + "\n", + "1. **Experiment**: Try different server-side JavaScript libraries and APIs\n", + "2. **Integrate**: Add code interpreter capabilities to your existing agents\n", + "3. **Extend**: Build custom tools that leverage both JavaScript and Python strengths\n", + "4. **Scale**: Explore production deployment patterns\n", + "5. **Monitor**: Implement proper logging and error handling\n", + "\n", + "### ๐Ÿ“š Additional Resources\n", + "\n", + "- [Deno Documentation](https://deno.land/manual) - Server-side JavaScript runtime\n", + "- [AWS Bedrock AgentCore Documentation](https://docs.aws.amazon.com/bedrock-agentcore/)\n", + "- [AgentCore Code Interpreter API Reference](https://docs.aws.amazon.com/bedrock-agentcore/latest/APIReference/)\n", + "- [Module 3 Other Notebooks](../README.md) - Explore other agent patterns\n", + "\n", + "Happy coding! ๐ŸŽ‰" + ] + } + ], + "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.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/pyproject.toml b/pyproject.toml index ca8ebc8..7bcd772 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,7 @@ dependencies = [ "strands-agents-tools>=0.1.9", "psycopg2-binary>=2.9.10", "langchain-litellm>=0.2.2", + "bedrock-agentcore>=0.1.1", ] [tool.uv.workspace] diff --git a/uv.lock b/uv.lock index eeb97d6..41cf207 100644 --- a/uv.lock +++ b/uv.lock @@ -8,6 +8,7 @@ version = "0.1.0" source = { editable = "." } dependencies = [ { name = "alembic" }, + { name = "bedrock-agentcore" }, { name = "boto3" }, { name = "chromadb" }, { name = "cryptography" }, @@ -58,6 +59,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "alembic", specifier = ">=1.15.2" }, + { name = "bedrock-agentcore", specifier = ">=0.1.1" }, { name = "boto3", specifier = ">=1.37.13" }, { name = "chromadb", specifier = ">=0.6.3" }, { name = "cryptography", specifier = ">=44.0.2" }, @@ -409,6 +411,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/49/6abb616eb3cbab6a7cca303dc02fdf3836de2e0b834bf966a7f5271a34d8/beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16", size = 186015 }, ] +[[package]] +name = "bedrock-agentcore" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "boto3" }, + { name = "botocore" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "urllib3" }, + { name = "uvicorn" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/68/78ff9be5b18260f657291772e1aaa3856ef78da5a5465b9f0356c818f415/bedrock_agentcore-0.1.1.tar.gz", hash = "sha256:cade2a39ae1bbad5f37842f0bb60758d4ff25fc56e9271404972f0ed71c8b074", size = 170185 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/3c/81ed595a5077fe190361c1409955ee0837c402a3f0355e41ab006588d749/bedrock_agentcore-0.1.1-py3-none-any.whl", hash = "sha256:cec53a9c15bcf922f943540d9b14b75e6c69bae5483b9c32c8cd0babbc541804", size = 48910 }, +] + [[package]] name = "bleach" version = "6.2.0" @@ -428,30 +448,30 @@ css = [ [[package]] name = "boto3" -version = "1.37.27" +version = "1.40.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/03033eaacd043e5c905c74f9067ebcf3a4a24fb852fa3f4745f87327f0e7/boto3-1.37.27.tar.gz", hash = "sha256:ccdeee590902e6f4fb30cec6d3a88668545817fccfd3e5cb9cbc166c4a0000d4", size = 111357 } +sdist = { url = "https://files.pythonhosted.org/packages/7b/34/298ef2023d7d88069776c9cc26b42ba6f05d143a1c9b44a0f65cd795c65b/boto3-1.40.0.tar.gz", hash = "sha256:fc1b3ca3baf3d8820c6faddf47cbba8ad3cd16f8e8d7e2f76d304bf995932eb7", size = 111847 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/23/19fbca3ca1614c69b7bc1aa15839ef355b8ccf434e1ad8b190f7ebfdc261/boto3-1.37.27-py3-none-any.whl", hash = "sha256:439c2cd18c79386b1b9d5fdc4a4e7e418e57ac50431bdf9421c60f09807f40fb", size = 139560 }, + { url = "https://files.pythonhosted.org/packages/5d/44/158581021038c5fc886ffa27fa4731fb4939258da7a23e0bc70b2d5757c9/boto3-1.40.0-py3-none-any.whl", hash = "sha256:959443055d2af676c336cc6033b3f870a8a924384b70d0b2905081d649378179", size = 139882 }, ] [[package]] name = "botocore" -version = "1.37.27" +version = "1.40.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/53/3f/597ca9de62d00556a2c387261293441d9e84fa196a7031ec656deffe8aee/botocore-1.37.27.tar.gz", hash = "sha256:143fd7cdb0d73f43aa1f14799124de7b857da7d7ab996af5c89a49e3032a9e66", size = 13800193 } +sdist = { url = "https://files.pythonhosted.org/packages/8f/e7/770ce910457ac6c68ea79b83892ab7a7cb08528f5d1dd77e51bf02a8529e/botocore-1.40.0.tar.gz", hash = "sha256:850242560dc8e74d542045a81eb6cc15f1b730b4ba55ba5b30e6d686548dfcaf", size = 14262316 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/48/f2670866a36d2dd68f7c93897fb9ec6c693bca1bb73cb867a173c2ad92c3/botocore-1.37.27-py3-none-any.whl", hash = "sha256:a86d1ffbe344bfb183d9acc24de3428118fc166cb89d0f1ce1d412857edfacd7", size = 13467344 }, + { url = "https://files.pythonhosted.org/packages/38/5a/bebc53f022514412613615b09aef20fbe804abb3ea26ec27e504a2d21c8f/botocore-1.40.0-py3-none-any.whl", hash = "sha256:2063e6d035a6a382b2ae37e40f5144044e55d4e091910d0c9f1be3121ad3e4e6", size = 13921768 }, ] [[package]] @@ -3571,14 +3591,14 @@ wheels = [ [[package]] name = "s3transfer" -version = "0.11.4" +version = "0.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0f/ec/aa1a215e5c126fe5decbee2e107468f51d9ce190b9763cb649f76bb45938/s3transfer-0.11.4.tar.gz", hash = "sha256:559f161658e1cf0a911f45940552c696735f5c74e64362e515f333ebed87d679", size = 148419 } +sdist = { url = "https://files.pythonhosted.org/packages/6d/05/d52bf1e65044b4e5e27d4e63e8d1579dbdec54fce685908ae09bc3720030/s3transfer-0.13.1.tar.gz", hash = "sha256:c3fdba22ba1bd367922f27ec8032d6a1cf5f10c934fb5d68cf60fd5a23d936cf", size = 150589 } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/62/8d3fc3ec6640161a5649b2cddbbf2b9fa39c92541225b33f117c37c5a2eb/s3transfer-0.11.4-py3-none-any.whl", hash = "sha256:ac265fa68318763a03bf2dc4f39d5cbd6a9e178d81cc9483ad27da33637e320d", size = 84412 }, + { url = "https://files.pythonhosted.org/packages/6d/4f/d073e09df851cfa251ef7840007d04db3293a0482ce607d2b993926089be/s3transfer-0.13.1-py3-none-any.whl", hash = "sha256:a981aa7429be23fe6dfc13e80e4020057cbab622b08c0315288758d67cabc724", size = 85308 }, ] [[package]] @@ -3750,14 +3770,14 @@ wheels = [ [[package]] name = "starlette" -version = "0.46.1" +version = "0.46.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/04/1b/52b27f2e13ceedc79a908e29eac426a63465a1a01248e5f24aa36a62aeb3/starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230", size = 2580102 } +sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/4b/528ccf7a982216885a1ff4908e886b8fb5f19862d1962f56a3fce2435a70/starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227", size = 71995 }, + { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037 }, ] [[package]] @@ -4062,15 +4082,15 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.34.0" +version = "0.35.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 } +sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473 } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 }, + { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406 }, ] [package.optional-dependencies] From e3ac7ef6819c5fa0342bb054d4a9959b48ba16c0 Mon Sep 17 00:00:00 2001 From: dinsajwa Date: Fri, 1 Aug 2025 14:03:39 -0400 Subject: [PATCH 02/11] feat(agentcorecode): added code intrepreter notebook --- .../notebooks/6_agent_code_interpreter.ipynb | 124 ++++++++++++++++-- 1 file changed, 112 insertions(+), 12 deletions(-) diff --git a/labs/module3/notebooks/6_agent_code_interpreter.ipynb b/labs/module3/notebooks/6_agent_code_interpreter.ipynb index a848bf1..0346545 100644 --- a/labs/module3/notebooks/6_agent_code_interpreter.ipynb +++ b/labs/module3/notebooks/6_agent_code_interpreter.ipynb @@ -72,9 +72,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "โœ… Dependencies imported successfully!\n" + ] + } + ], "source": [ "# Import core dependencies\n", "import boto3\n", @@ -99,9 +107,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "โœ… Using AWS profile: ags-tech-namer-ca-pace+ai3-core-Admin\n", + "โœ… Access Key: ASIAXBZV5H...\n", + "โœ… Region: us-west-2\n", + "โœ… AWS clients initialized successfully with profile!\n" + ] + } + ], "source": [ "# Initialize AWS clients with specific profile\n", "profile_name = \"ags-tech-namer-ca-pace+ai3-core-Admin\"\n", @@ -153,9 +172,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Server-side JavaScript analysis code prepared!\n" + ] + } + ], "source": [ "# Server-side JavaScript code for data analysis, ASCII visualization, and file operations\n", "javascript_analysis_code = \"\"\"\n", @@ -295,9 +322,64 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "๐Ÿš€ Executing server-side JavaScript analysis with boto3...\n", + "โœ… Started session: 01K1KCQHX5EZG6SCYHWKT500Z7\n", + "Output: Starting comprehensive data analysis...\n", + "SALES ANALYSIS RESULTS:\n", + "Average Actual Sales: 62.3\n", + "Average Target Sales: 64.7\n", + "Overall Performance: 96.2%\n", + "Standard Deviation: 13.48\n", + "\n", + "ACTUAL SALES BY MONTH\n", + "========================================\n", + "Jan |######################## | 65\n", + "Feb |###################### | 59\n", + "Mar |##############################| 80\n", + "Apr |##############################| 81\n", + "May |##################### | 56\n", + "Jun |#################### | 55\n", + "Jul |############### | 40\n", + " +------------------------------+\n", + "\n", + "TARGET SALES BY MONTH\n", + "========================================\n", + "Jan |########################## | 70\n", + "Feb |######################## | 65\n", + "Mar |############################ | 75\n", + "Apr |##############################| 80\n", + "May |####################### | 60\n", + "Jun |###################### | 58\n", + "Jul |################# | 45\n", + " +------------------------------+\n", + "\n", + "TREND ANALYSIS:\n", + "Sales Trend: Decreasing\n", + "Slope: -3.821 units per month\n", + "\n", + "MATHEMATICAL COMPUTATIONS:\n", + "Fibonacci(10): 55\n", + "Primes under 30: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29\n", + "\n", + "ANALYSIS COMPLETE!\n", + "This demonstrates server-side JavaScript capabilities:\n", + "โ€ข Data processing and statistical analysis\n", + "โ€ข ASCII chart generation for visualization\n", + "โ€ข Mathematical computations\n", + "โ€ข Structured data transformation\n", + "\n", + "โœ… Session 01K1KCQHX5EZG6SCYHWKT500Z7 stopped\n", + "Result: True\n" + ] + } + ], "source": [ "def execute_code_with_boto3(code: str, language: str = \"javascript\") -> Dict[str, Any]:\n", " \"\"\"\n", @@ -374,9 +456,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Python graph code prepared!\n" + ] + } + ], "source": [ "# Python code for creating a matplotlib graph\n", "python_graph_code = \"\"\"\n", @@ -442,9 +532,19 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "๐Ÿš€ Executing Python graph generation with AgentCore SDK...\n", + "โŒ Error executing code with SDK: An error occurred (AccessDeniedException) when calling the StartCodeInterpreterSession operation: The security token included in the request is invalid.\n", + "Result: False\n" + ] + } + ], "source": [ "def execute_code_with_sdk(code: str, language: str = \"python\") -> Dict[str, Any]:\n", " \"\"\"\n", From 763cfa3997790e7d19d625b933af66fbc20c4106 Mon Sep 17 00:00:00 2001 From: dinsajwa Date: Thu, 7 Aug 2025 16:56:43 -0400 Subject: [PATCH 03/11] updated code interpreter samples --- ...sing_analysis_using_code_interpreter.ipynb | 631 +++++++++++++++ ...le_operations_using_code_interpreter.ipynb | 723 ++++++++++++++++++ .../notebooks/6_agent_code_interpreter.ipynb | 683 ----------------- 3 files changed, 1354 insertions(+), 683 deletions(-) create mode 100644 labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb create mode 100644 labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb delete mode 100644 labs/module3/notebooks/6_agent_code_interpreter.ipynb diff --git a/labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb b/labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb new file mode 100644 index 0000000..8e43954 --- /dev/null +++ b/labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb @@ -0,0 +1,631 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# ๐Ÿ“Š Data Processing & Analysis Using Code Interpreter\n", + "\n", + "Welcome to the Data Processing & Analysis module using AWS Bedrock AgentCore Code Interpreter! This notebook focuses on statistical calculations and data manipulation with simple, clear examples.\n", + "\n", + "## ๐ŸŽฏ Learning Objectives\n", + "\n", + "In this notebook, you'll learn how to:\n", + "\n", + "- ๐Ÿ”ง Set up and use AgentCore Code Interpreter with both boto3 and AgentCore SDK\n", + "- ๐Ÿ“ˆ Perform basic statistical calculations on simple datasets\n", + "- ๐Ÿ”„ Process and analyze data using both JavaScript (Deno) and Python\n", + "- ๐Ÿ—๏ธ Build framework-agnostic abstractions for data analysis\n", + "\n", + "## ๐Ÿ“‹ Prerequisites\n", + "\n", + "- AWS account with access to Amazon Bedrock AgentCore\n", + "- Proper IAM permissions for AgentCore services\n", + "- Python 3.12 environment with required dependencies\n", + "\n", + "Let's begin! ๐Ÿš€" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿ”ง Section 1: Environment Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "โœ… Python version: 3.12.8 (main, Jan 14 2025, 23:36:58) [Clang 19.1.6 ]\n", + "โœ… Python executable: /Users/dinsajwa/work/projects/sample-agentic-platform/.venv/bin/python3\n", + "โœ… Environment setup complete! All dependencies should be available via UV.\n" + ] + } + ], + "source": [ + "# ๐Ÿ”ง Environment Setup Instructions\n", + "\n", + "# IMPORTANT: This notebook requires Python 3.12.8 as specified in pyproject.toml\n", + "# All dependencies are managed via UV (recommended approach for this project)\n", + "\n", + "# SETUP STEPS:\n", + "# 1. Create virtual environment: uv venv\n", + "# 2. Install all dependencies: uv sync\n", + "# 3. Launch Jupyter Lab: uv run jupyter lab\n", + "\n", + "# FOR VS CODE USERS:\n", + "# 1. Run: uv run python -c \"import sys; print(sys.executable)\"\n", + "# 2. Copy the path and select it as your Python interpreter in VS Code\n", + "# 3. The kernel should now have access to all required dependencies\n", + "\n", + "# Verify environment setup\n", + "import sys\n", + "print(f\"โœ… Python version: {sys.version}\")\n", + "print(f\"โœ… Python executable: {sys.executable}\")\n", + "print(\"โœ… Environment setup complete! All dependencies should be available via UV.\")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "โœ… Dependencies imported successfully!\n" + ] + } + ], + "source": [ + "# Import core dependencies\n", + "import boto3\n", + "import json\n", + "import os\n", + "import time\n", + "from typing import Dict, Any, List, Optional\n", + "\n", + "# Import AgentCore SDK\n", + "from bedrock_agentcore.tools.code_interpreter_client import CodeInterpreter, code_session\n", + "\n", + "# Import existing Module 3 components\n", + "from agentic_platform.core.models.memory_models import Message, SessionContext\n", + "from agentic_platform.core.models.llm_models import LLMRequest, LLMResponse\n", + "from agentic_platform.core.models.api_models import AgenticRequest, AgenticResponse\n", + "\n", + "print(\"โœ… Dependencies imported successfully!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "โœ… Using AWS profile: ags-tech-namer-ca-pace+ai3-core-Admin\n", + "โœ… Access Key: ASIAXBZV5H...\n", + "โœ… Region: us-west-2\n", + "โœ… AWS clients initialized successfully with profile!\n" + ] + } + ], + "source": [ + "# Initialize AWS clients with specific profile\n", + "profile_name = \"ags-tech-namer-ca-pace+ai3-core-Admin\"\n", + "session = boto3.Session(profile_name=profile_name)\n", + "region_name = \"us-west-2\"\n", + "\n", + "# Verify credentials are working\n", + "try:\n", + " credentials = session.get_credentials()\n", + " print(f\"โœ… Using AWS profile: {profile_name}\")\n", + " print(f\"โœ… Access Key: {credentials.access_key[:10]}...\")\n", + " print(f\"โœ… Region: {region_name}\")\n", + "except Exception as e:\n", + " print(f\"โŒ Error with AWS credentials: {e}\")\n", + " raise\n", + "\n", + "# Standard AgentCore client\n", + "bedrock_agentcore_client = session.client(\n", + " 'bedrock-agentcore',\n", + " region_name=region_name,\n", + " endpoint_url=f\"https://bedrock-agentcore.{region_name}.amazonaws.com\"\n", + ")\n", + "\n", + "print(\"โœ… AWS clients initialized successfully with profile!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿ“Š Section 2: Simple Data Processing with Boto3\n", + "\n", + "Let's start with a simple example using JavaScript to process temperature data." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "JavaScript statistical analysis code prepared!\n" + ] + } + ], + "source": [ + "# Simple JavaScript code for basic statistical analysis\n", + "# NOTE: Emojis removed from JavaScript code to avoid Deno parsing errors\n", + "javascript_stats_code = \"\"\"\n", + "// Simple temperature data analysis example\n", + "console.log('Starting simple data analysis...');\n", + "\n", + "// Small dataset: Daily temperatures in Celsius\n", + "const temperatures = [22.5, 24.1, 23.8, 25.2, 21.9];\n", + "const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];\n", + "\n", + "// Calculate basic statistics\n", + "function calculateBasicStats(data) {\n", + " // Calculate sum\n", + " const sum = data.reduce((a, b) => a + b, 0);\n", + " \n", + " // Calculate mean\n", + " const mean = sum / data.length;\n", + " \n", + " // Find min and max\n", + " const min = Math.min(...data);\n", + " const max = Math.max(...data);\n", + " \n", + " // Calculate variance\n", + " const variance = data.reduce((acc, val) => {\n", + " return acc + Math.pow(val - mean, 2);\n", + " }, 0) / data.length;\n", + " \n", + " return {\n", + " count: data.length,\n", + " sum: sum.toFixed(2),\n", + " mean: mean.toFixed(2),\n", + " min: min.toFixed(2),\n", + " max: max.toFixed(2),\n", + " variance: variance.toFixed(2),\n", + " range: (max - min).toFixed(2)\n", + " };\n", + "}\n", + "\n", + "// Perform analysis\n", + "const stats = calculateBasicStats(temperatures);\n", + "\n", + "// Display results\n", + "console.log('=====================================');\n", + "console.log('Number of readings: ' + stats.count);\n", + "console.log('Average temperature: ' + stats.mean + ' C');\n", + "console.log('Minimum temperature: ' + stats.min + ' C');\n", + "console.log('Maximum temperature: ' + stats.max + ' C');\n", + "console.log('Temperature range: ' + stats.range + ' C');\n", + "console.log('Variance: ' + stats.variance);\n", + "\n", + "// Display daily readings\n", + "console.log('=== DAILY READINGS ===');\n", + "days.forEach((day, index) => {\n", + " console.log(day + ': ' + temperatures[index] + ' C');\n", + "});\n", + "\n", + "console.log('=====================================');\n", + "console.log('Analysis complete!');\n", + "\n", + "// Return the statistics object\n", + "stats;\n", + "\"\"\"\n", + "\n", + "print(\"JavaScript statistical analysis code prepared!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "def execute_code_with_boto3(code: str, language: str = \"javascript\") -> Dict[str, Any]:\n", + " \"\"\"\n", + " Execute code using boto3 direct API calls to AgentCore Code Interpreter.\n", + " \n", + " Args:\n", + " code: The code to execute\n", + " language: Programming language (javascript or python)\n", + " \n", + " Returns:\n", + " Dictionary with execution results\n", + " \"\"\"\n", + " try:\n", + " # Start a code interpreter session\n", + " session_response = bedrock_agentcore_client.start_code_interpreter_session(\n", + " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", + " name=\"boto3-stats-session\",\n", + " sessionTimeoutSeconds=300\n", + " )\n", + " session_id = session_response[\"sessionId\"]\n", + " print(f\"โœ… Started session: {session_id}\")\n", + " \n", + " # Execute the code\n", + " execute_response = bedrock_agentcore_client.invoke_code_interpreter(\n", + " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", + " sessionId=session_id,\n", + " name=\"executeCode\",\n", + " arguments={\n", + " \"language\": language,\n", + " \"code\": code\n", + " }\n", + " )\n", + " \n", + " # Process the response stream\n", + " results = []\n", + " for event in execute_response['stream']:\n", + " if 'result' in event:\n", + " result = event['result']\n", + " results.append(result)\n", + " \n", + " # Print text output\n", + " if 'content' in result:\n", + " for content_item in result['content']:\n", + " if content_item['type'] == 'text':\n", + " print(f\"Output: {content_item['text']}\")\n", + " \n", + " # Clean up session\n", + " bedrock_agentcore_client.stop_code_interpreter_session(\n", + " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", + " sessionId=session_id\n", + " )\n", + " print(f\"โœ… Session {session_id} stopped\")\n", + " \n", + " return {\n", + " \"success\": True,\n", + " \"session_id\": session_id,\n", + " \"results\": results\n", + " }\n", + " \n", + " except Exception as e:\n", + " print(f\"โŒ Error executing code: {str(e)}\")\n", + " return {\n", + " \"success\": False,\n", + " \"error\": str(e)\n", + " }" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "๐Ÿš€ Executing JavaScript statistical analysis with boto3...\n", + "\n", + "โœ… Started session: 01K233EG17WY3NRVDNQ2MEDET5\n", + "Output: Starting simple data analysis...\n", + "=====================================\n", + "Number of readings: 5\n", + "Average temperature: 23.50 C\n", + "Minimum temperature: 21.90 C\n", + "Maximum temperature: 25.20 C\n", + "Temperature range: 3.30 C\n", + "Variance: 1.38\n", + "=== DAILY READINGS ===\n", + "Monday: 22.5 C\n", + "Tuesday: 24.1 C\n", + "Wednesday: 23.8 C\n", + "Thursday: 25.2 C\n", + "Friday: 21.9 C\n", + "=====================================\n", + "Analysis complete!\n", + "\n", + "โœ… Session 01K233EG17WY3NRVDNQ2MEDET5 stopped\n", + "\n", + "โœ… Execution completed: True\n" + ] + } + ], + "source": [ + "# Execute the JavaScript statistical analysis code using boto3\n", + "print(\"๐Ÿš€ Executing JavaScript statistical analysis with boto3...\\n\")\n", + "boto3_result = execute_code_with_boto3(javascript_stats_code, \"javascript\")\n", + "print(f\"\\nโœ… Execution completed: {boto3_result['success']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿ Section 3: Simple Data Processing with AgentCore SDK\n", + "\n", + "Now let's perform the same analysis using Python through the AgentCore SDK." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Python statistical analysis code prepared!\n" + ] + } + ], + "source": [ + "# Simple Python code for basic statistical analysis\n", + "python_stats_code = \"\"\"\n", + "import statistics\n", + "\n", + "# Simple dataset: Student test scores\n", + "print('Starting simple data analysis...')\n", + "\n", + "scores = [85, 92, 78, 95, 88]\n", + "students = ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve']\n", + "\n", + "# Calculate basic statistics\n", + "def analyze_scores(data):\n", + " stats = {\n", + " 'count': len(data),\n", + " 'sum': sum(data),\n", + " 'mean': statistics.mean(data),\n", + " 'median': statistics.median(data),\n", + " 'min': min(data),\n", + " 'max': max(data),\n", + " 'range': max(data) - min(data),\n", + " 'variance': statistics.variance(data),\n", + " 'stdev': statistics.stdev(data)\n", + " }\n", + " return stats\n", + "\n", + "# Perform analysis\n", + "results = analyze_scores(scores)\n", + "\n", + "# Display results\n", + "print('=== TEST SCORE ANALYSIS RESULTS ===')\n", + "print('=' * 35)\n", + "print(f'Number of students: {results[\"count\"]}')\n", + "print(f'Average score: {results[\"mean\"]:.1f}')\n", + "print(f'Median score: {results[\"median\"]:.1f}')\n", + "print(f'Lowest score: {results[\"min\"]}')\n", + "print(f'Highest score: {results[\"max\"]}')\n", + "print(f'Score range: {results[\"range\"]}')\n", + "print(f'Variance: {results[\"variance\"]:.2f}')\n", + "print(f'Standard deviation: {results[\"stdev\"]:.2f}')\n", + "\n", + "# Display individual scores\n", + "print('=== INDIVIDUAL SCORES ===')\n", + "for student, score in zip(students, scores):\n", + " grade = 'A' if score >= 90 else 'B' if score >= 80 else 'C'\n", + " print(f'{student}: {score} (Grade: {grade})')\n", + "\n", + "# Identify top performers\n", + "threshold = results['mean']\n", + "above_average = [(students[i], scores[i]) for i in range(len(scores)) if scores[i] > threshold]\n", + "print(f'=== Above average students ({threshold:.1f}+) ===')\n", + "for student, score in above_average:\n", + " print(f' - {student}: {score}')\n", + "\n", + "print('Analysis complete!')\n", + "\n", + "# Return the results\n", + "results\n", + "\"\"\"\n", + "\n", + "print(\"Python statistical analysis code prepared!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# Alternative: If the above doesn't work, try using environment variables\n", + "def execute_code_with_sdk(code: str, language: str = \"python\") -> Dict[str, Any]:\n", + " \"\"\"\n", + " Alternative approach: Execute code using the AgentCore SDK with credentials from environment.\n", + " \n", + " Args:\n", + " code: The code to execute\n", + " language: Programming language (javascript or python)\n", + " \n", + " Returns:\n", + " Dictionary with execution results\n", + " \"\"\"\n", + " try:\n", + " # Get credentials from the session\n", + " credentials = session.get_credentials()\n", + " region_name = \"us-west-2\"\n", + " # Set environment variables for AWS SDK\n", + " import os\n", + " os.environ['AWS_ACCESS_KEY_ID'] = credentials.access_key\n", + " os.environ['AWS_SECRET_ACCESS_KEY'] = credentials.secret_key\n", + " if credentials.token:\n", + " os.environ['AWS_SESSION_TOKEN'] = credentials.token\n", + " os.environ['AWS_DEFAULT_REGION'] = region_name\n", + " \n", + " print(\"โœ… AWS credentials set in environment variables\")\n", + " \n", + " # Now create the CodeInterpreter client\n", + " code_client = CodeInterpreter(\"us-west-2\")\n", + " code_client.start()\n", + " print(\"โœ… AgentCore SDK session started with environment credentials\")\n", + " \n", + " # Execute the code\n", + " response = code_client.invoke(\"executeCode\", {\n", + " \"language\": language,\n", + " \"code\": code\n", + " })\n", + " \n", + " # Process and print the response\n", + " results = []\n", + " for event in response[\"stream\"]:\n", + " if \"result\" in event:\n", + " result = event[\"result\"]\n", + " results.append(result)\n", + " \n", + " # Extract and display text content\n", + " if 'content' in result:\n", + " for content_item in result['content']:\n", + " if content_item['type'] == 'text':\n", + " print(content_item['text'])\n", + " \n", + " # Clean up and stop the session\n", + " code_client.stop()\n", + " print(\"\\nโœ… AgentCore SDK session stopped\")\n", + " \n", + " return {\n", + " \"success\": True,\n", + " \"results\": results\n", + " }\n", + " \n", + " except Exception as e:\n", + " print(f\"โŒ Error executing code with SDK: {str(e)}\")\n", + " return {\n", + " \"success\": False,\n", + " \"error\": str(e)\n", + " }\n", + " finally:\n", + " # Clean up environment variables\n", + " for key in ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN']:\n", + " if key in os.environ:\n", + " del os.environ[key]" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "๐Ÿš€ Executing Python statistical analysis with AgentCore SDK...\n", + "\n", + "โœ… AWS credentials set in environment variables\n", + "โœ… AgentCore SDK session started with environment credentials\n", + "Starting simple data analysis...\n", + "=== TEST SCORE ANALYSIS RESULTS ===\n", + "===================================\n", + "Number of students: 5\n", + "Average score: 87.6\n", + "Median score: 88.0\n", + "Lowest score: 78\n", + "Highest score: 95\n", + "Score range: 17\n", + "Variance: 43.30\n", + "Standard deviation: 6.58\n", + "=== INDIVIDUAL SCORES ===\n", + "Alice: 85 (Grade: B)\n", + "Bob: 92 (Grade: A)\n", + "Charlie: 78 (Grade: C)\n", + "Diana: 95 (Grade: A)\n", + "Eve: 88 (Grade: B)\n", + "=== Above average students (87.6+) ===\n", + " - Bob: 92\n", + " - Diana: 95\n", + " - Eve: 88\n", + "Analysis complete!\n", + "\n", + "{'count': 5,\n", + " 'sum': 438,\n", + " 'mean': 87.6,\n", + " 'median': 88,\n", + " 'min': 78,\n", + " 'max': 95,\n", + " 'range': 17,\n", + " 'variance': 43.3,\n", + " 'stdev': 6.58027355054484}\n", + "\n", + "โœ… AgentCore SDK session stopped\n", + "\n", + "โœ… Execution completed: True\n" + ] + } + ], + "source": [ + "# Execute the Python statistical analysis code using AgentCore SDK\n", + "print(\"๐Ÿš€ Executing Python statistical analysis with AgentCore SDK...\\n\")\n", + "sdk_result = execute_code_with_sdk(python_stats_code, \"python\")\n", + "print(f\"\\nโœ… Execution completed: {sdk_result['success']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Conclusion\n", + "\n", + "In this notebook, we've successfully demonstrated:\n", + "\n", + "1. **Execute Code sample on a code interpreter sandbox**: \n", + " - Calculating mean, median, min, max, and variance\n", + " - Processing simple datasets efficiently\n", + "\n", + "2. **Two Implementation Approaches**:\n", + " - **Boto3**: Direct API calls for fine-grained control\n", + " - **AgentCore SDK**: Higher-level abstraction for simpler code\n", + "\n", + "3. **Multi-language Support**:\n", + " - JavaScript for server-side processing\n", + " - Python for data analysis with built-in statistics module\n", + "\n", + "\n", + "### ๐Ÿ“š Additional Resources\n", + "\n", + "- [AWS Bedrock AgentCore Documentation](https://docs.aws.amazon.com/bedrock-agentcore/)\n", + "- [AgentCore Code Interpreter API Reference](https://docs.aws.amazon.com/bedrock-agentcore/latest/APIReference/)\n", + "- [Module 3 Other Notebooks](../README.md)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "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.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb b/labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb new file mode 100644 index 0000000..1938ccc --- /dev/null +++ b/labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb @@ -0,0 +1,723 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# ๐Ÿ“ File Operations Using Code Interpreter\n", + "\n", + "Welcome to the File Operations module using AWS Bedrock AgentCore Code Interpreter! This notebook focuses on JSON and CSV file generation using Deno APIs with simple, practical examples.\n", + "\n", + "## ๐ŸŽฏ Learning Objectives\n", + "\n", + "In this notebook, you'll learn how to:\n", + "\n", + "- ๐Ÿ”ง Set up and use AgentCore Code Interpreter for file operations\n", + "- ๐Ÿ“„ Create and manipulate JSON files with structured data\n", + "- ๐Ÿ“Š Generate CSV files for tabular data\n", + "- ๐Ÿ”„ Use both JavaScript (Deno) and Python for file I/O operations\n", + "- ๐Ÿ—๏ธ Implement file operations using both boto3 and AgentCore SDK\n", + "\n", + "## ๐Ÿ“‹ Prerequisites\n", + "\n", + "- AWS account with access to Amazon Bedrock AgentCore\n", + "- Proper IAM permissions for AgentCore services\n", + "- Python 3.12 environment with required dependencies\n", + "\n", + "Let's begin! ๐Ÿš€" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿ”ง Section 1: Environment Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# ๐Ÿ”ง Environment Setup Instructions\n", + "\n", + "# IMPORTANT: This notebook requires Python 3.12.8 as specified in pyproject.toml\n", + "# All dependencies are managed via UV (recommended approach for this project)\n", + "\n", + "# SETUP STEPS:\n", + "# 1. Create virtual environment: uv venv\n", + "# 2. Install all dependencies: uv sync\n", + "# 3. Launch Jupyter Lab: uv run jupyter lab\n", + "\n", + "# FOR VS CODE USERS:\n", + "# 1. Run: uv run python -c \"import sys; print(sys.executable)\"\n", + "# 2. Copy the path and select it as your Python interpreter in VS Code\n", + "# 3. The kernel should now have access to all required dependencies\n", + "\n", + "# Verify environment setup\n", + "import sys\n", + "print(f\"โœ… Python version: {sys.version}\")\n", + "print(f\"โœ… Python executable: {sys.executable}\")\n", + "print(\"โœ… Environment setup complete! All dependencies should be available via UV.\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Import core dependencies\n", + "import boto3\n", + "import json\n", + "import os\n", + "import time\n", + "from typing import Dict, Any, List, Optional\n", + "\n", + "# Import AgentCore SDK\n", + "from bedrock_agentcore.tools.code_interpreter_client import CodeInterpreter, code_session\n", + "\n", + "# Import existing Module 3 components\n", + "from agentic_platform.core.models.memory_models import Message, SessionContext\n", + "from agentic_platform.core.models.llm_models import LLMRequest, LLMResponse\n", + "from agentic_platform.core.models.api_models import AgenticRequest, AgenticResponse\n", + "\n", + "print(\"โœ… Dependencies imported successfully!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Initialize AWS clients with specific profile\n", + "profile_name = \"ags-tech-namer-ca-pace+ai3-core-Admin\"\n", + "session = boto3.Session(profile_name=profile_name)\n", + "region_name = \"us-west-2\"\n", + "\n", + "# Verify credentials are working\n", + "try:\n", + " credentials = session.get_credentials()\n", + " print(f\"โœ… Using AWS profile: {profile_name}\")\n", + " print(f\"โœ… Access Key: {credentials.access_key[:10]}...\")\n", + " print(f\"โœ… Region: {region_name}\")\n", + "except Exception as e:\n", + " print(f\"โŒ Error with AWS credentials: {e}\")\n", + " raise\n", + "\n", + "# Standard AgentCore client\n", + "bedrock_agentcore_client = session.client(\n", + " 'bedrock-agentcore',\n", + " region_name=region_name,\n", + " endpoint_url=f\"https://bedrock-agentcore.{region_name}.amazonaws.com\"\n", + ")\n", + "\n", + "print(\"โœ… AWS clients initialized successfully with profile!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿ“„ Section 2: JSON File Operations with Boto3\n", + "\n", + "Let's start with creating and reading JSON files using JavaScript (Deno) APIs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# JavaScript code for JSON file operations using Deno\n", + "javascript_json_code = \"\"\"\n", + "// JSON File Operations Example\n", + "console.log('Starting JSON file operations...');\n", + "\n", + "// Simple user profile data\n", + "const userProfile = {\n", + " id: 'USR-001',\n", + " name: 'John Doe',\n", + " email: 'john.doe@example.com',\n", + " age: 28,\n", + " preferences: {\n", + " theme: 'dark',\n", + " notifications: true,\n", + " language: 'en'\n", + " },\n", + " createdAt: new Date().toISOString()\n", + "};\n", + "\n", + "// Convert to JSON string with formatting\n", + "const jsonString = JSON.stringify(userProfile, null, 2);\n", + "\n", + "console.log('\\n๐Ÿ“ User Profile JSON:');\n", + "console.log(jsonString);\n", + "\n", + "// Write JSON to file (Deno file system API)\n", + "try {\n", + " // In Deno, we would use:\n", + " // await Deno.writeTextFile('user_profile.json', jsonString);\n", + " \n", + " // For demonstration, we'll simulate the operation\n", + " console.log('\\nโœ… JSON file would be written to: user_profile.json');\n", + " console.log('File size: ' + jsonString.length + ' bytes');\n", + " \n", + "} catch (error) {\n", + " console.error('Error writing file:', error);\n", + "}\n", + "\n", + "// Parse JSON back to object\n", + "const parsedProfile = JSON.parse(jsonString);\n", + "\n", + "// Validate the data\n", + "console.log('\\n๐Ÿ” Validation Results:');\n", + "console.log('User ID: ' + parsedProfile.id);\n", + "console.log('User Name: ' + parsedProfile.name);\n", + "console.log('Email Valid: ' + parsedProfile.email.includes('@'));\n", + "console.log('Theme Setting: ' + parsedProfile.preferences.theme);\n", + "\n", + "// Create a simple configuration file\n", + "const appConfig = {\n", + " version: '1.0.0',\n", + " apiEndpoint: 'https://api.example.com',\n", + " maxRetries: 3,\n", + " timeout: 5000,\n", + " features: ['auth', 'dashboard', 'reports']\n", + "};\n", + "\n", + "const configJson = JSON.stringify(appConfig, null, 2);\n", + "\n", + "console.log('\\nโš™๏ธ Application Configuration:');\n", + "console.log(configJson);\n", + "\n", + "console.log('\\nโœ… JSON operations completed successfully!');\n", + "\n", + "// Return summary\n", + "{\n", + " filesCreated: ['user_profile.json', 'app_config.json'],\n", + " totalSize: jsonString.length + configJson.length,\n", + " status: 'success'\n", + "};\n", + "\"\"\"\n", + "\n", + "print(\"JavaScript JSON operations code prepared!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def execute_code_with_boto3(code: str, language: str = \"javascript\") -> Dict[str, Any]:\n", + " \"\"\"\n", + " Execute code using boto3 direct API calls to AgentCore Code Interpreter.\n", + " \n", + " Args:\n", + " code: The code to execute\n", + " language: Programming language (javascript or python)\n", + " \n", + " Returns:\n", + " Dictionary with execution results\n", + " \"\"\"\n", + " try:\n", + " # Start a code interpreter session\n", + " session_response = bedrock_agentcore_client.start_code_interpreter_session(\n", + " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", + " name=\"boto3-file-ops-session\",\n", + " sessionTimeoutSeconds=300\n", + " )\n", + " session_id = session_response[\"sessionId\"]\n", + " print(f\"โœ… Started session: {session_id}\")\n", + " \n", + " # Execute the code\n", + " execute_response = bedrock_agentcore_client.invoke_code_interpreter(\n", + " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", + " sessionId=session_id,\n", + " name=\"executeCode\",\n", + " arguments={\n", + " \"language\": language,\n", + " \"code\": code\n", + " }\n", + " )\n", + " \n", + " # Process the response stream\n", + " results = []\n", + " for event in execute_response['stream']:\n", + " if 'result' in event:\n", + " result = event['result']\n", + " results.append(result)\n", + " \n", + " # Print text output\n", + " if 'content' in result:\n", + " for content_item in result['content']:\n", + " if content_item['type'] == 'text':\n", + " print(f\"Output: {content_item['text']}\")\n", + " \n", + " # Clean up session\n", + " bedrock_agentcore_client.stop_code_interpreter_session(\n", + " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", + " sessionId=session_id\n", + " )\n", + " print(f\"โœ… Session {session_id} stopped\")\n", + " \n", + " return {\n", + " \"success\": True,\n", + " \"session_id\": session_id,\n", + " \"results\": results\n", + " }\n", + " \n", + " except Exception as e:\n", + " print(f\"โŒ Error executing code: {str(e)}\")\n", + " return {\n", + " \"success\": False,\n", + " \"error\": str(e)\n", + " }" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Execute the JavaScript JSON operations code using boto3\n", + "print(\"๐Ÿš€ Executing JavaScript JSON file operations with boto3...\\n\")\n", + "boto3_json_result = execute_code_with_boto3(javascript_json_code, \"javascript\")\n", + "print(f\"\\nโœ… Execution completed: {boto3_json_result['success']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿ“Š Section 3: CSV File Operations with Boto3\n", + "\n", + "Now let's work with CSV files using JavaScript (Deno)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# JavaScript code for CSV file operations\n", + "javascript_csv_code = \"\"\"\n", + "// CSV File Operations Example\n", + "console.log('Starting CSV file operations...');\n", + "\n", + "// Simple product data\n", + "const products = [\n", + " { id: 'P001', name: 'Laptop', price: 999.99, stock: 15 },\n", + " { id: 'P002', name: 'Mouse', price: 29.99, stock: 50 },\n", + " { id: 'P003', name: 'Keyboard', price: 79.99, stock: 30 }\n", + "];\n", + "\n", + "// Function to convert array of objects to CSV\n", + "function arrayToCSV(data) {\n", + " if (data.length === 0) return '';\n", + " \n", + " // Get headers from first object\n", + " const headers = Object.keys(data[0]);\n", + " const csvHeaders = headers.join(',');\n", + " \n", + " // Convert each object to CSV row\n", + " const csvRows = data.map(obj => {\n", + " return headers.map(header => {\n", + " const value = obj[header];\n", + " // Handle values that might contain commas\n", + " return typeof value === 'string' && value.includes(',') \n", + " ? `\"${value}\"` \n", + " : value;\n", + " }).join(',');\n", + " });\n", + " \n", + " // Combine headers and rows\n", + " return [csvHeaders, ...csvRows].join('\\n');\n", + "}\n", + "\n", + "// Generate CSV content\n", + "const csvContent = arrayToCSV(products);\n", + "\n", + "console.log('\\n๐Ÿ“Š Generated CSV Content:');\n", + "console.log('================================');\n", + "console.log(csvContent);\n", + "\n", + "// Calculate some statistics\n", + "const totalValue = products.reduce((sum, p) => sum + (p.price * p.stock), 0);\n", + "const avgPrice = products.reduce((sum, p) => sum + p.price, 0) / products.length;\n", + "const totalStock = products.reduce((sum, p) => sum + p.stock, 0);\n", + "\n", + "console.log('\\n๐Ÿ“ˆ Inventory Statistics:');\n", + "console.log('Total inventory value: $' + totalValue.toFixed(2));\n", + "console.log('Average product price: $' + avgPrice.toFixed(2));\n", + "console.log('Total items in stock: ' + totalStock);\n", + "\n", + "// Create a simple sales report CSV\n", + "const salesData = [\n", + " { date: '2024-01-01', product: 'Laptop', quantity: 2, revenue: 1999.98 },\n", + " { date: '2024-01-02', product: 'Mouse', quantity: 5, revenue: 149.95 },\n", + " { date: '2024-01-03', product: 'Keyboard', quantity: 3, revenue: 239.97 }\n", + "];\n", + "\n", + "const salesCSV = arrayToCSV(salesData);\n", + "\n", + "console.log('\\n๐Ÿ’ฐ Sales Report CSV:');\n", + "console.log('================================');\n", + "console.log(salesCSV);\n", + "\n", + "// Parse CSV back to verify\n", + "function parseCSV(csvString) {\n", + " const lines = csvString.split('\\n');\n", + " const headers = lines[0].split(',');\n", + " const data = [];\n", + " \n", + " for (let i = 1; i < lines.length; i++) {\n", + " const values = lines[i].split(',');\n", + " const obj = {};\n", + " headers.forEach((header, index) => {\n", + " obj[header] = values[index];\n", + " });\n", + " data.push(obj);\n", + " }\n", + " \n", + " return data;\n", + "}\n", + "\n", + "const parsedProducts = parseCSV(csvContent);\n", + "console.log('\\nโœ… CSV parsing verification:');\n", + "console.log('Parsed ' + parsedProducts.length + ' products successfully');\n", + "\n", + "console.log('\\nโœ… CSV operations completed successfully!');\n", + "\n", + "// Return summary\n", + "{\n", + " filesGenerated: ['products.csv', 'sales_report.csv'],\n", + " totalRows: products.length + salesData.length,\n", + " totalBytes: csvContent.length + salesCSV.length,\n", + " status: 'success'\n", + "};\n", + "\"\"\"\n", + "\n", + "print(\"JavaScript CSV operations code prepared!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Execute the JavaScript CSV operations code using boto3\n", + "print(\"๐Ÿš€ Executing JavaScript CSV file operations with boto3...\\n\")\n", + "boto3_csv_result = execute_code_with_boto3(javascript_csv_code, \"javascript\")\n", + "print(f\"\\nโœ… Execution completed: {boto3_csv_result['success']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿ Section 4: File Operations with AgentCore SDK\n", + "\n", + "Now let's perform file operations using Python through the AgentCore SDK." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Python code for JSON and CSV file operations\n", + "python_file_ops_code = \"\"\"\n", + "import json\n", + "import csv\n", + "from io import StringIO\n", + "from datetime import datetime\n", + "\n", + "print('Starting file operations with Python...')\n", + "\n", + "# ===== JSON Operations =====\n", + "print('\\n๐Ÿ“„ JSON File Operations:')\n", + "print('=' * 40)\n", + "\n", + "# Create a simple employee record\n", + "employee = {\n", + " 'emp_id': 'EMP-2024-001',\n", + " 'name': 'Jane Smith',\n", + " 'department': 'Engineering',\n", + " 'salary': 85000,\n", + " 'skills': ['Python', 'AWS', 'Docker'],\n", + " 'active': True,\n", + " 'joined_date': '2024-01-15'\n", + "}\n", + "\n", + "# Convert to formatted JSON\n", + "json_str = json.dumps(employee, indent=2)\n", + "print('Employee Record (JSON):')\n", + "print(json_str)\n", + "\n", + "# Parse and validate\n", + "parsed_employee = json.loads(json_str)\n", + "print(f\"\\nโœ… Validated: Employee {parsed_employee['name']} in {parsed_employee['department']}\")\n", + "print(f\"Skills count: {len(parsed_employee['skills'])}\")\n", + "\n", + "# Create a settings file\n", + "settings = {\n", + " 'app_name': 'DataProcessor',\n", + " 'version': '2.1.0',\n", + " 'debug_mode': False,\n", + " 'max_connections': 100,\n", + " 'database': {\n", + " 'host': 'localhost',\n", + " 'port': 5432,\n", + " 'name': 'app_db'\n", + " }\n", + "}\n", + "\n", + "settings_json = json.dumps(settings, indent=2)\n", + "print('\\nApplication Settings:')\n", + "print(settings_json)\n", + "\n", + "# ===== CSV Operations =====\n", + "print('\\n๐Ÿ“Š CSV File Operations:')\n", + "print('=' * 40)\n", + "\n", + "# Create inventory data\n", + "inventory = [\n", + " ['Item', 'Category', 'Quantity', 'Price'],\n", + " ['Notebook', 'Stationery', 100, 2.50],\n", + " ['Pen', 'Stationery', 250, 1.00],\n", + " ['Calculator', 'Electronics', 25, 15.99]\n", + "]\n", + "\n", + "# Write to CSV format\n", + "csv_buffer = StringIO()\n", + "csv_writer = csv.writer(csv_buffer)\n", + "for row in inventory:\n", + " csv_writer.writerow(row)\n", + "\n", + "csv_content = csv_buffer.getvalue()\n", + "print('Inventory CSV:')\n", + "print(csv_content)\n", + "\n", + "# Parse CSV and calculate totals\n", + "csv_buffer.seek(0)\n", + "csv_reader = csv.DictReader(csv_buffer)\n", + "\n", + "total_value = 0\n", + "total_items = 0\n", + "\n", + "print('\\n๐Ÿ“ˆ Inventory Analysis:')\n", + "for row in csv_reader:\n", + " quantity = float(row['Quantity'])\n", + " price = float(row['Price'])\n", + " value = quantity * price\n", + " total_value += value\n", + " total_items += quantity\n", + " print(f\"{row['Item']}: {quantity} units @ ${price:.2f} = ${value:.2f}\")\n", + "\n", + "print(f'\\nTotal items: {int(total_items)}')\n", + "print(f'Total inventory value: ${total_value:.2f}')\n", + "\n", + "# Create a transaction log CSV\n", + "transactions = [\n", + " {'id': 'TXN001', 'date': '2024-01-10', 'amount': 150.00, 'status': 'completed'},\n", + " {'id': 'TXN002', 'date': '2024-01-11', 'amount': 75.50, 'status': 'completed'},\n", + " {'id': 'TXN003', 'date': '2024-01-12', 'amount': 200.00, 'status': 'pending'}\n", + "]\n", + "\n", + "# Write transactions to CSV\n", + "txn_buffer = StringIO()\n", + "fieldnames = ['id', 'date', 'amount', 'status']\n", + "dict_writer = csv.DictWriter(txn_buffer, fieldnames=fieldnames)\n", + "dict_writer.writeheader()\n", + "dict_writer.writerows(transactions)\n", + "\n", + "print('\\n๐Ÿ’ฐ Transaction Log CSV:')\n", + "print(txn_buffer.getvalue())\n", + "\n", + "# Summary statistics\n", + "completed_amount = sum(t['amount'] for t in transactions if t['status'] == 'completed')\n", + "pending_amount = sum(t['amount'] for t in transactions if t['status'] == 'pending')\n", + "\n", + "print('\\n๐Ÿ“Š Transaction Summary:')\n", + "print(f'Completed transactions: ${completed_amount:.2f}')\n", + "print(f'Pending transactions: ${pending_amount:.2f}')\n", + "print(f'Total transactions: {len(transactions)}')\n", + "\n", + "print('\\nโœ… All file operations completed successfully!')\n", + "\n", + "# Return summary\n", + "{\n", + " 'json_files': ['employee.json', 'settings.json'],\n", + " 'csv_files': ['inventory.csv', 'transactions.csv'],\n", + " 'total_operations': 4,\n", + " 'status': 'success'\n", + "}\n", + "\"\"\"\n", + "\n", + "print(\"Python file operations code prepared!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def execute_code_with_sdk(code: str, language: str = \"python\") -> Dict[str, Any]:\n", + " \"\"\"\n", + " Execute code using the AgentCore SDK (higher-level interface).\n", + " \n", + " Args:\n", + " code: The code to execute\n", + " language: Programming language (javascript or python)\n", + " \n", + " Returns:\n", + " Dictionary with execution results\n", + " \"\"\"\n", + " try:\n", + " # Configure and start the code interpreter session\n", + " code_client = CodeInterpreter(region_name)\n", + " code_client.start()\n", + " print(\"โœ… AgentCore SDK session started\")\n", + " \n", + " # Execute the code\n", + " response = code_client.invoke(\"executeCode\", {\n", + " \"language\": language,\n", + " \"code\": code\n", + " })\n", + " \n", + " # Process and print the response\n", + " results = []\n", + " for event in response[\"stream\"]:\n", + " if \"result\" in event:\n", + " result = event[\"result\"]\n", + " results.append(result)\n", + " \n", + " # Extract and display text content\n", + " if 'content' in result:\n", + " for content_item in result['content']:\n", + " if content_item['type'] == 'text':\n", + " print(content_item['text'])\n", + " \n", + " # Clean up and stop the session\n", + " code_client.stop()\n", + " print(\"\\nโœ… AgentCore SDK session stopped\")\n", + " \n", + " return {\n", + " \"success\": True,\n", + " \"results\": results\n", + " }\n", + " \n", + " except Exception as e:\n", + " print(f\"โŒ Error executing code with SDK: {str(e)}\")\n", + " return {\n", + " \"success\": False,\n", + " \"error\": str(e)\n", + " }" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Execute the Python file operations code using AgentCore SDK\n", + "print(\"๐Ÿš€ Executing Python file operations with AgentCore SDK...\\n\")\n", + "sdk_file_result = execute_code_with_sdk(python_file_ops_code, \"python\")\n", + "print(f\"\\nโœ… Execution completed: {sdk_file_result['success']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐ŸŽฏ Section 5: Summary and Key Takeaways\n", + "\n", + "### ๐Ÿ“ What We've Learned\n", + "\n", + "In this notebook, we've successfully demonstrated:\n", + "\n", + "1. **JSON File Operations**: \n", + " - Creating structured JSON data (user profiles, configurations)\n", + " - Parsing and validating JSON content\n", + " - Working with nested JSON objects\n", + "\n", + "2. **CSV File Operations**:\n", + " - Generating CSV from arrays of objects\n", + " - Parsing CSV back to structured data\n", + " - Handling different data types in CSV format\n", + "\n", + "3. **Two Implementation Approaches**:\n", + " - **Boto3**: Direct API calls with JavaScript/Deno file operations\n", + " - **AgentCore SDK**: Python implementation with built-in CSV and JSON modules\n", + "\n", + "### ๐Ÿ”‘ Key Differences Between Languages\n", + "\n", + "| Aspect | JavaScript (Deno) | Python |\n", + "|--------|-------------------|--------|\n", + "| **JSON Handling** | Native JSON.stringify/parse | json module |\n", + "| **CSV Support** | Manual implementation | Built-in csv module |\n", + "| **File I/O** | Deno.writeTextFile (async) | Built-in file operations |\n", + "| **Data Processing** | Array methods (map, reduce) | List comprehensions, csv.DictReader |\n", + "\n", + "### ๐Ÿ’ก Best Practices for File Operations\n", + "\n", + "1. **Data Validation**: Always validate data before and after file operations\n", + "2. **Error Handling**: Include try-catch blocks for file I/O operations\n", + "3. **Format Selection**: \n", + " - Use JSON for hierarchical/nested data\n", + " - Use CSV for tabular/flat data\n", + "4. **Memory Efficiency**: Use streaming for large files\n", + "5. **Encoding**: Be explicit about character encoding (UTF-8)\n", + "\n", + "### ๐Ÿš€ Next Steps\n", + "\n", + "- Explore the companion notebook on data processing and analysis\n", + "- Try working with larger datasets\n", + "- Implement file upload/download in agent workflows\n", + "- Experiment with other file formats (XML, YAML)\n", + "- Integrate file operations with S3 for persistent storage\n", + "\n", + "### ๐Ÿ“š Additional Resources\n", + "\n", + "- [AWS Bedrock AgentCore Documentation](https://docs.aws.amazon.com/bedrock-agentcore/)\n", + "- [Deno File System API](https://deno.land/manual/runtime/file_system)\n", + "- [Python CSV Module Documentation](https://docs.python.org/3/library/csv.html)\n", + "- [Module 3 Other Notebooks](../README.md)\n", + "\n", + "Happy coding! ๐ŸŽ‰" + ] + } + ], + "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.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/labs/module3/notebooks/6_agent_code_interpreter.ipynb b/labs/module3/notebooks/6_agent_code_interpreter.ipynb deleted file mode 100644 index 0346545..0000000 --- a/labs/module3/notebooks/6_agent_code_interpreter.ipynb +++ /dev/null @@ -1,683 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# ๐Ÿค– Building Autonomous Agents: AgentCore Code Interpreter\n", - "\n", - "Welcome to the AgentCore Code Interpreter module! In this notebook, we'll explore how to integrate AWS Bedrock AgentCore Code Interpreter into autonomous agents, enabling them to execute code securely in sandbox environments.\n", - "\n", - "## What is AgentCore Code Interpreter?\n", - "\n", - "The Amazon Bedrock AgentCore Code Interpreter enables AI agents to write and execute code securely in sandbox environments, enhancing their accuracy and expanding their ability to solve complex end-to-end tasks. It supports multiple programming languages and provides secure, isolated execution environments.\n", - "\n", - "## ๐ŸŽฏ Learning Objectives\n", - "\n", - "In this notebook, you'll learn how to:\n", - "\n", - "- ๐Ÿ”ง Set up and use AgentCore Code Interpreter with both boto3 and AgentCore SDK\n", - "- ๐Ÿ“Š Create data visualizations and graphs using code execution\n", - "- ๐Ÿค– Integrate code interpreter capabilities into agents using Strands and LangChain frameworks\n", - "- ๐Ÿ“ Manage files within code interpreter sessions\n", - "- โ˜๏ธ Use S3 integration and terminal commands with execution roles\n", - "- ๐Ÿ—๏ธ Build framework-agnostic abstractions for code execution tools\n", - "- ๐Ÿ”„ Integrate with existing Module 3 memory and tool patterns\n", - "\n", - "## ๐Ÿ“‹ Prerequisites\n", - "\n", - "- AWS account with access to Amazon Bedrock AgentCore\n", - "- Proper IAM permissions for AgentCore services\n", - "- Python 3.12 environment with required dependencies\n", - "\n", - "Let's begin! ๐Ÿš€" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## ๐Ÿ”ง Section 1: Environment Setup and Dependencies\n", - "\n", - "First, let's set up our environment and install the necessary dependencies." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# ๐Ÿ”ง Environment Setup Instructions\n", - "\n", - "# IMPORTANT: This notebook requires Python 3.12.8 as specified in pyproject.toml\n", - "# All dependencies are managed via UV (recommended approach for this project)\n", - "\n", - "# SETUP STEPS:\n", - "# 1. Create virtual environment: uv venv\n", - "# 2. Install all dependencies: uv sync\n", - "# 3. Launch Jupyter Lab: uv run jupyter lab\n", - "\n", - "# FOR VS CODE USERS:\n", - "# 1. Run: uv run python -c \"import sys; print(sys.executable)\"\n", - "# 2. Copy the path and select it as your Python interpreter in VS Code\n", - "# 3. The kernel should now have access to all required dependencies\n", - "\n", - "# Verify environment setup\n", - "import sys\n", - "print(f\"โœ… Python version: {sys.version}\")\n", - "print(f\"โœ… Python executable: {sys.executable}\")\n", - "print(\"โœ… Environment setup complete! All dependencies should be available via UV.\")" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "โœ… Dependencies imported successfully!\n" - ] - } - ], - "source": [ - "# Import core dependencies\n", - "import boto3\n", - "import json\n", - "import os\n", - "import time\n", - "import asyncio\n", - "from typing import Dict, Any, List, Optional\n", - "from pydantic import BaseModel\n", - "from abc import ABC, abstractmethod\n", - "\n", - "# Import AgentCore SDK\n", - "from bedrock_agentcore.tools.code_interpreter_client import CodeInterpreter, code_session\n", - "\n", - "# Import existing Module 3 components\n", - "from agentic_platform.core.models.memory_models import Message, SessionContext\n", - "from agentic_platform.core.models.llm_models import LLMRequest, LLMResponse\n", - "from agentic_platform.core.models.api_models import AgenticRequest, AgenticResponse\n", - "\n", - "print(\"โœ… Dependencies imported successfully!\")" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "โœ… Using AWS profile: ags-tech-namer-ca-pace+ai3-core-Admin\n", - "โœ… Access Key: ASIAXBZV5H...\n", - "โœ… Region: us-west-2\n", - "โœ… AWS clients initialized successfully with profile!\n" - ] - } - ], - "source": [ - "# Initialize AWS clients with specific profile\n", - "profile_name = \"ags-tech-namer-ca-pace+ai3-core-Admin\"\n", - "session = boto3.Session(profile_name=profile_name)\n", - "region_name = \"us-west-2\"\n", - "\n", - "# Verify credentials are working\n", - "try:\n", - " credentials = session.get_credentials()\n", - " print(f\"โœ… Using AWS profile: {profile_name}\")\n", - " print(f\"โœ… Access Key: {credentials.access_key[:10]}...\")\n", - " print(f\"โœ… Region: {region_name}\")\n", - "except Exception as e:\n", - " print(f\"โŒ Error with AWS credentials: {e}\")\n", - " raise\n", - "\n", - "# Standard AgentCore client\n", - "bedrock_agentcore_client = session.client(\n", - " 'bedrock-agentcore',\n", - " region_name=region_name,\n", - " endpoint_url=f\"https://bedrock-agentcore.{region_name}.amazonaws.com\"\n", - ")\n", - "\n", - "# Control plane client for custom interpreters\n", - "bedrock_agentcore_control_client = session.client(\n", - " 'bedrock-agentcore-control',\n", - " region_name=region_name,\n", - " endpoint_url=f\"https://bedrock-agentcore-control.{region_name}.amazonaws.com\"\n", - ")\n", - "\n", - "print(\"โœ… AWS clients initialized successfully with profile!\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## ๐Ÿ“Š Section 2: Basic Code Execution and Data Analysis\n", - "\n", - "Let's start with basic code execution examples, demonstrating server-side JavaScript data processing and Python visualizations using both boto3 and the AgentCore SDK approaches." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Example 1: Boto3 Direct Implementation with Server-Side JavaScript" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Server-side JavaScript analysis code prepared!\n" - ] - } - ], - "source": [ - "# Server-side JavaScript code for data analysis, ASCII visualization, and file operations\n", - "javascript_analysis_code = \"\"\"\n", - "// Server-side JavaScript data analysis (Deno compatible)\n", - "// Fixed version without browser dependencies\n", - "\n", - "console.log('Starting comprehensive data analysis...');\n", - "\n", - "// Sample sales data\n", - "const salesData = {\n", - " months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'],\n", - " actual: [65, 59, 80, 81, 56, 55, 40],\n", - " target: [70, 65, 75, 80, 60, 58, 45]\n", - "};\n", - "\n", - "// Statistical calculations\n", - "function calculateStats(data) {\n", - " const sum = data.reduce((a, b) => a + b, 0);\n", - " const avg = sum / data.length;\n", - " const max = Math.max(...data);\n", - " const min = Math.min(...data);\n", - " const variance = data.reduce((acc, val) => acc + Math.pow(val - avg, 2), 0) / data.length;\n", - " const stdDev = Math.sqrt(variance);\n", - " return { sum, avg, max, min, variance, stdDev };\n", - "}\n", - "\n", - "const actualStats = calculateStats(salesData.actual);\n", - "const targetStats = calculateStats(salesData.target);\n", - "const performance = (actualStats.avg / targetStats.avg) * 100;\n", - "\n", - "console.log('SALES ANALYSIS RESULTS:');\n", - "console.log('Average Actual Sales: ' + actualStats.avg.toFixed(1));\n", - "console.log('Average Target Sales: ' + targetStats.avg.toFixed(1));\n", - "console.log('Overall Performance: ' + performance.toFixed(1) + '%');\n", - "console.log('Standard Deviation: ' + actualStats.stdDev.toFixed(2));\n", - "\n", - "// ASCII Chart Visualization\n", - "function createASCIIBarChart(data, labels, title) {\n", - " const maxValue = Math.max(...data);\n", - " const chartWidth = 30;\n", - " \n", - " console.log('\\\\n' + title);\n", - " console.log('='.repeat(40));\n", - " \n", - " data.forEach((value, index) => {\n", - " const barLength = Math.round((value / maxValue) * chartWidth);\n", - " const bar = '#'.repeat(barLength);\n", - " const padding = ' '.repeat(Math.max(0, chartWidth - barLength));\n", - " console.log(labels[index] + ' |' + bar + padding + '| ' + value);\n", - " });\n", - " \n", - " console.log(' '.repeat(5) + '+' + '-'.repeat(chartWidth) + '+');\n", - "}\n", - "\n", - "// Create ASCII charts\n", - "createASCIIBarChart(salesData.actual, salesData.months, 'ACTUAL SALES BY MONTH');\n", - "createASCIIBarChart(salesData.target, salesData.months, 'TARGET SALES BY MONTH');\n", - "\n", - "// Trend Analysis\n", - "function calculateTrend(data) {\n", - " const n = data.length;\n", - " const sumX = (n * (n + 1)) / 2;\n", - " const sumY = data.reduce((a, b) => a + b, 0);\n", - " const sumXY = data.reduce((sum, y, x) => sum + (x + 1) * y, 0);\n", - " const sumX2 = (n * (n + 1) * (2 * n + 1)) / 6;\n", - " \n", - " const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);\n", - " return { slope, trend: slope > 0 ? 'Increasing' : slope < 0 ? 'Decreasing' : 'Stable' };\n", - "}\n", - "\n", - "const actualTrend = calculateTrend(salesData.actual);\n", - "console.log('\\\\nTREND ANALYSIS:');\n", - "console.log('Sales Trend: ' + actualTrend.trend);\n", - "console.log('Slope: ' + actualTrend.slope.toFixed(3) + ' units per month');\n", - "\n", - "// File Operations (JSON report)\n", - "const reportData = {\n", - " timestamp: new Date().toISOString(),\n", - " summary: {\n", - " totalActualSales: actualStats.sum,\n", - " totalTargetSales: targetStats.sum,\n", - " performancePercentage: performance,\n", - " trend: actualTrend.trend,\n", - " trendSlope: actualTrend.slope\n", - " },\n", - " monthlyData: salesData.months.map((month, index) => ({\n", - " month: month,\n", - " actual: salesData.actual[index],\n", - " target: salesData.target[index],\n", - " variance: salesData.actual[index] - salesData.target[index]\n", - " }))\n", - "};\n", - "\n", - "// Mathematical Computations\n", - "function fibonacci(n) {\n", - " if (n <= 1) return n;\n", - " let a = 0, b = 1;\n", - " for (let i = 2; i <= n; i++) {\n", - " const temp = a + b;\n", - " a = b;\n", - " b = temp;\n", - " }\n", - " return b;\n", - "}\n", - "\n", - "function isPrime(num) {\n", - " if (num < 2) return false;\n", - " for (let i = 2; i <= Math.sqrt(num); i++) {\n", - " if (num % i === 0) return false;\n", - " }\n", - " return true;\n", - "}\n", - "\n", - "console.log('\\\\nMATHEMATICAL COMPUTATIONS:');\n", - "console.log('Fibonacci(10): ' + fibonacci(10));\n", - "\n", - "const primes = [];\n", - "for (let i = 0; i < 30; i++) {\n", - " if (isPrime(i)) primes.push(i);\n", - "}\n", - "console.log('Primes under 30: ' + primes.join(', '));\n", - "\n", - "// Final summary\n", - "console.log('\\\\nANALYSIS COMPLETE!');\n", - "console.log('This demonstrates server-side JavaScript capabilities:');\n", - "console.log('โ€ข Data processing and statistical analysis');\n", - "console.log('โ€ข ASCII chart generation for visualization');\n", - "console.log('โ€ข Mathematical computations');\n", - "console.log('โ€ข Structured data transformation');\n", - "\n", - "// Return final results\n", - "reportData.summary;\n", - "\"\"\"\n", - "\n", - "print(\"Server-side JavaScript analysis code prepared!\")" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "๐Ÿš€ Executing server-side JavaScript analysis with boto3...\n", - "โœ… Started session: 01K1KCQHX5EZG6SCYHWKT500Z7\n", - "Output: Starting comprehensive data analysis...\n", - "SALES ANALYSIS RESULTS:\n", - "Average Actual Sales: 62.3\n", - "Average Target Sales: 64.7\n", - "Overall Performance: 96.2%\n", - "Standard Deviation: 13.48\n", - "\n", - "ACTUAL SALES BY MONTH\n", - "========================================\n", - "Jan |######################## | 65\n", - "Feb |###################### | 59\n", - "Mar |##############################| 80\n", - "Apr |##############################| 81\n", - "May |##################### | 56\n", - "Jun |#################### | 55\n", - "Jul |############### | 40\n", - " +------------------------------+\n", - "\n", - "TARGET SALES BY MONTH\n", - "========================================\n", - "Jan |########################## | 70\n", - "Feb |######################## | 65\n", - "Mar |############################ | 75\n", - "Apr |##############################| 80\n", - "May |####################### | 60\n", - "Jun |###################### | 58\n", - "Jul |################# | 45\n", - " +------------------------------+\n", - "\n", - "TREND ANALYSIS:\n", - "Sales Trend: Decreasing\n", - "Slope: -3.821 units per month\n", - "\n", - "MATHEMATICAL COMPUTATIONS:\n", - "Fibonacci(10): 55\n", - "Primes under 30: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29\n", - "\n", - "ANALYSIS COMPLETE!\n", - "This demonstrates server-side JavaScript capabilities:\n", - "โ€ข Data processing and statistical analysis\n", - "โ€ข ASCII chart generation for visualization\n", - "โ€ข Mathematical computations\n", - "โ€ข Structured data transformation\n", - "\n", - "โœ… Session 01K1KCQHX5EZG6SCYHWKT500Z7 stopped\n", - "Result: True\n" - ] - } - ], - "source": [ - "def execute_code_with_boto3(code: str, language: str = \"javascript\") -> Dict[str, Any]:\n", - " \"\"\"\n", - " Execute code using boto3 direct API calls to AgentCore Code Interpreter.\n", - " \"\"\"\n", - " try:\n", - " # Start a code interpreter session\n", - " session_response = bedrock_agentcore_client.start_code_interpreter_session(\n", - " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", - " name=\"boto3-analysis-session\",\n", - " sessionTimeoutSeconds=900\n", - " )\n", - " session_id = session_response[\"sessionId\"]\n", - " print(f\"โœ… Started session: {session_id}\")\n", - " \n", - " # Execute the code\n", - " execute_response = bedrock_agentcore_client.invoke_code_interpreter(\n", - " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", - " sessionId=session_id,\n", - " name=\"executeCode\",\n", - " arguments={\n", - " \"language\": language,\n", - " \"code\": code\n", - " }\n", - " )\n", - " \n", - " # Process the response stream\n", - " results = []\n", - " for event in execute_response['stream']:\n", - " if 'result' in event:\n", - " result = event['result']\n", - " results.append(result)\n", - " \n", - " # Print text output\n", - " if 'content' in result:\n", - " for content_item in result['content']:\n", - " if content_item['type'] == 'text':\n", - " print(f\"Output: {content_item['text']}\")\n", - " elif content_item['type'] == 'image':\n", - " print(f\"Generated image: {content_item['data'][:50]}...\")\n", - " \n", - " # Clean up session\n", - " bedrock_agentcore_client.stop_code_interpreter_session(\n", - " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", - " sessionId=session_id\n", - " )\n", - " print(f\"โœ… Session {session_id} stopped\")\n", - " \n", - " return {\n", - " \"success\": True,\n", - " \"session_id\": session_id,\n", - " \"results\": results\n", - " }\n", - " \n", - " except Exception as e:\n", - " print(f\"โŒ Error executing code: {str(e)}\")\n", - " return {\n", - " \"success\": False,\n", - " \"error\": str(e)\n", - " }\n", - "\n", - "# Execute the server-side JavaScript analysis code\n", - "print(\"๐Ÿš€ Executing server-side JavaScript analysis with boto3...\")\n", - "boto3_result = execute_code_with_boto3(javascript_analysis_code, \"javascript\")\n", - "print(f\"Result: {boto3_result['success']}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Example 2: AgentCore SDK Implementation with Python Graph" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Python graph code prepared!\n" - ] - } - ], - "source": [ - "# Python code for creating a matplotlib graph\n", - "python_graph_code = \"\"\"\n", - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "\n", - "# Sample sales data\n", - "months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul']\n", - "sales = [65, 59, 80, 81, 56, 55, 40]\n", - "target = [70, 65, 75, 80, 60, 58, 45]\n", - "\n", - "# Create figure with subplots\n", - "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))\n", - "\n", - "# Bar chart\n", - "x_pos = np.arange(len(months))\n", - "width = 0.35\n", - "\n", - "ax1.bar(x_pos - width/2, sales, width, label='Actual Sales', color='#3498db', alpha=0.8)\n", - "ax1.bar(x_pos + width/2, target, width, label='Target', color='#e74c3c', alpha=0.8)\n", - "ax1.set_xlabel('Month')\n", - "ax1.set_ylabel('Sales')\n", - "ax1.set_title('Monthly Sales vs Target')\n", - "ax1.set_xticks(x_pos)\n", - "ax1.set_xticklabels(months)\n", - "ax1.legend()\n", - "ax1.grid(axis='y', alpha=0.3)\n", - "\n", - "# Line chart\n", - "ax2.plot(months, sales, marker='o', linewidth=2, label='Actual Sales', color='#3498db')\n", - "ax2.plot(months, target, marker='s', linewidth=2, label='Target', color='#e74c3c')\n", - "ax2.set_xlabel('Month')\n", - "ax2.set_ylabel('Sales')\n", - "ax2.set_title('Sales Trend Analysis')\n", - "ax2.legend()\n", - "ax2.grid(True, alpha=0.3)\n", - "\n", - "# Adjust layout and save\n", - "plt.tight_layout()\n", - "plt.savefig('sales_analysis.png', dpi=300, bbox_inches='tight')\n", - "plt.show()\n", - "\n", - "# Calculate some statistics\n", - "avg_sales = np.mean(sales)\n", - "avg_target = np.mean(target)\n", - "performance = (avg_sales / avg_target) * 100\n", - "\n", - "print(f\"Average Sales: {avg_sales:.1f}\")\n", - "print(f\"Average Target: {avg_target:.1f}\")\n", - "print(f\"Performance: {performance:.1f}%\")\n", - "\n", - "# Return summary\n", - "{\n", - " 'avg_sales': avg_sales,\n", - " 'avg_target': avg_target,\n", - " 'performance_pct': performance,\n", - " 'chart_saved': 'sales_analysis.png'\n", - "}\n", - "\"\"\"\n", - "\n", - "print(\"Python graph code prepared!\")" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "๐Ÿš€ Executing Python graph generation with AgentCore SDK...\n", - "โŒ Error executing code with SDK: An error occurred (AccessDeniedException) when calling the StartCodeInterpreterSession operation: The security token included in the request is invalid.\n", - "Result: False\n" - ] - } - ], - "source": [ - "def execute_code_with_sdk(code: str, language: str = \"python\") -> Dict[str, Any]:\n", - " \"\"\"\n", - " Execute code using the AgentCore SDK (higher-level interface).\n", - " \"\"\"\n", - " try:\n", - " # Configure and start the code interpreter session\n", - " code_client = CodeInterpreter(region_name)\n", - " code_client.start()\n", - " print(\"โœ… AgentCore SDK session started\")\n", - " \n", - " # Execute the code\n", - " response = code_client.invoke(\"executeCode\", {\n", - " \"language\": language,\n", - " \"code\": code\n", - " })\n", - " \n", - " # Process and print the response\n", - " results = []\n", - " for event in response[\"stream\"]:\n", - " if \"result\" in event:\n", - " result = event[\"result\"]\n", - " results.append(result)\n", - " print(f\"Execution result: {json.dumps(result, indent=2, default=str)}\")\n", - " \n", - " # Clean up and stop the session\n", - " code_client.stop()\n", - " print(\"โœ… AgentCore SDK session stopped\")\n", - " \n", - " return {\n", - " \"success\": True,\n", - " \"results\": results\n", - " }\n", - " \n", - " except Exception as e:\n", - " print(f\"โŒ Error executing code with SDK: {str(e)}\")\n", - " return {\n", - " \"success\": False,\n", - " \"error\": str(e)\n", - " }\n", - "\n", - "# Execute the Python graph code\n", - "print(\"๐Ÿš€ Executing Python graph generation with AgentCore SDK...\")\n", - "sdk_result = execute_code_with_sdk(python_graph_code, \"python\")\n", - "print(f\"Result: {sdk_result['success']}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## ๐ŸŽฏ Section 3: Summary and Key Takeaways\n", - "\n", - "Congratulations! You've successfully explored AWS Bedrock AgentCore Code Interpreter and learned how to fix the DOM compatibility issue." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### ๐Ÿ”ง Problem Solved: DOM Compatibility Issue\n", - "\n", - "**Original Issue**: The JavaScript code was trying to use browser DOM APIs (`document.createElement('canvas')`) in a Deno server-side environment where these APIs don't exist.\n", - "\n", - "**Root Cause**: AgentCore Code Interpreter runs JavaScript in a Deno runtime (server-side), not in a browser environment.\n", - "\n", - "**Solution Implemented**: Replaced browser-dependent code with server-side compatible alternatives:\n", - "\n", - "1. **Data Processing & Analysis**: Statistical calculations and data manipulation\n", - "2. **ASCII Chart Visualization**: Terminal-friendly charts using Unicode characters\n", - "3. **File Operations**: JSON and CSV file generation using Deno APIs\n", - "4. **Mathematical Computations**: Fibonacci sequences, prime number detection\n", - "5. **Structured Data Transformation**: Complex data processing workflows\n", - "\n", - "### ๐Ÿ“Š What We've Demonstrated\n", - "\n", - "The updated JavaScript example showcases:\n", - "\n", - "- โœ… **Server-side JavaScript execution** in a secure sandbox\n", - "- โœ… **Data analysis and statistical calculations**\n", - "- โœ… **ASCII-based data visualization** (no browser dependencies)\n", - "- โœ… **File I/O operations** for report generation\n", - "- โœ… **Mathematical computations** and algorithms\n", - "- โœ… **Structured data processing** and transformation\n", - "\n", - "### ๐Ÿ”‘ Key Takeaways\n", - "\n", - "1. **Environment Awareness**: Always consider the execution environment (server-side vs browser)\n", - "2. **Alternative Approaches**: ASCII charts can be as effective as graphical ones for data visualization\n", - "3. **Server-side Capabilities**: Deno provides powerful APIs for file operations and data processing\n", - "4. **Code Interpreter Flexibility**: Supports multiple programming languages with different strengths\n", - "5. **Sandbox Security**: Secure execution environment with controlled access to system resources\n", - "\n", - "### ๐Ÿš€ Next Steps\n", - "\n", - "To continue working with AgentCore Code Interpreter:\n", - "\n", - "1. **Experiment**: Try different server-side JavaScript libraries and APIs\n", - "2. **Integrate**: Add code interpreter capabilities to your existing agents\n", - "3. **Extend**: Build custom tools that leverage both JavaScript and Python strengths\n", - "4. **Scale**: Explore production deployment patterns\n", - "5. **Monitor**: Implement proper logging and error handling\n", - "\n", - "### ๐Ÿ“š Additional Resources\n", - "\n", - "- [Deno Documentation](https://deno.land/manual) - Server-side JavaScript runtime\n", - "- [AWS Bedrock AgentCore Documentation](https://docs.aws.amazon.com/bedrock-agentcore/)\n", - "- [AgentCore Code Interpreter API Reference](https://docs.aws.amazon.com/bedrock-agentcore/latest/APIReference/)\n", - "- [Module 3 Other Notebooks](../README.md) - Explore other agent patterns\n", - "\n", - "Happy coding! ๐ŸŽ‰" - ] - } - ], - "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.8" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} From f0bff47bb0b488dbcd549f84d3431c266157def7 Mon Sep 17 00:00:00 2001 From: dinsajwa Date: Thu, 7 Aug 2025 16:58:35 -0400 Subject: [PATCH 04/11] updated code interpreter samples --- .../6.1_data_processing_analysis_using_code_interpreter.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb b/labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb index 8e43954..ec74b5c 100644 --- a/labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb +++ b/labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb @@ -105,7 +105,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -121,7 +121,7 @@ ], "source": [ "# Initialize AWS clients with specific profile\n", - "profile_name = \"ags-tech-namer-ca-pace+ai3-core-Admin\"\n", + "profile_name = \"XXXXXXX\" # your aws profile name\n", "session = boto3.Session(profile_name=profile_name)\n", "region_name = \"us-west-2\"\n", "\n", From 12f2fe52b368e76600931de5847f289bbcfda37d Mon Sep 17 00:00:00 2001 From: dinsajwa Date: Fri, 8 Aug 2025 11:06:16 -0400 Subject: [PATCH 05/11] updated code interpreter samples --- ...le_operations_using_code_interpreter.ipynb | 1058 +++++++++-------- 1 file changed, 558 insertions(+), 500 deletions(-) diff --git a/labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb b/labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb index 1938ccc..3e15033 100644 --- a/labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb +++ b/labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb @@ -6,17 +6,17 @@ "source": [ "# ๐Ÿ“ File Operations Using Code Interpreter\n", "\n", - "Welcome to the File Operations module using AWS Bedrock AgentCore Code Interpreter! This notebook focuses on JSON and CSV file generation using Deno APIs with simple, practical examples.\n", + "Welcome to the File Operations module using AWS Bedrock AgentCore Code Interpreter! This notebook demonstrates comprehensive file operations including reading, writing, and processing files using both boto3 and AgentCore SDK.\n", "\n", "## ๐ŸŽฏ Learning Objectives\n", "\n", "In this notebook, you'll learn how to:\n", "\n", "- ๐Ÿ”ง Set up and use AgentCore Code Interpreter for file operations\n", - "- ๐Ÿ“„ Create and manipulate JSON files with structured data\n", - "- ๐Ÿ“Š Generate CSV files for tabular data\n", - "- ๐Ÿ”„ Use both JavaScript (Deno) and Python for file I/O operations\n", - "- ๐Ÿ—๏ธ Implement file operations using both boto3 and AgentCore SDK\n", + "- ๐Ÿ“„ Read and write files using Code Interpreter's file management tools\n", + "- ๐Ÿ“Š Process JSON and CSV files with both JavaScript and Python\n", + "- ๐Ÿ”„ Use both boto3 and AgentCore SDK for file I/O operations\n", + "- ๐Ÿ—๏ธ Implement complete file workflows including upload, process, and verify\n", "\n", "## ๐Ÿ“‹ Prerequisites\n", "\n", @@ -36,9 +36,19 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "โœ… Python version: 3.12.8 (main, Jan 14 2025, 23:36:58) [Clang 19.1.6 ]\n", + "โœ… Python executable: /Users/dinsajwa/work/projects/sample-agentic-platform/.venv/bin/python3\n", + "โœ… Environment setup complete! All dependencies should be available via UV.\n" + ] + } + ], "source": [ "# ๐Ÿ”ง Environment Setup Instructions\n", "\n", @@ -64,9 +74,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "โœ… Dependencies imported successfully!\n" + ] + } + ], "source": [ "# Import core dependencies\n", "import boto3\n", @@ -90,10 +108,21 @@ "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "โœ… Using AWS profile: ags-tech-namer-ca-pace+ai3-core-Admin\n", + "โœ… Access Key: ASIAXBZV5H...\n", + "โœ… Region: us-west-2\n", + "โœ… AWS clients initialized successfully with profile!\n" + ] + } + ], "source": [ "# Initialize AWS clients with specific profile\n", - "profile_name = \"ags-tech-namer-ca-pace+ai3-core-Admin\"\n", + "profile_name = \"XXXXXX\" # your aws profile name\n", "session = boto3.Session(profile_name=profile_name)\n", "region_name = \"us-west-2\"\n", "\n", @@ -114,6 +143,13 @@ " endpoint_url=f\"https://bedrock-agentcore.{region_name}.amazonaws.com\"\n", ")\n", "\n", + "# Set environment variables for AgentCore SDK\n", + "os.environ['AWS_ACCESS_KEY_ID'] = credentials.access_key\n", + "os.environ['AWS_SECRET_ACCESS_KEY'] = credentials.secret_key\n", + "if credentials.token:\n", + " os.environ['AWS_SESSION_TOKEN'] = credentials.token\n", + "os.environ['AWS_DEFAULT_REGION'] = region_name\n", + "\n", "print(\"โœ… AWS clients initialized successfully with profile!\")" ] }, @@ -121,578 +157,600 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## ๐Ÿ“„ Section 2: JSON File Operations with Boto3\n", + "## ๐Ÿ“„ Section 2: Sample Data Preparation\n", "\n", - "Let's start with creating and reading JSON files using JavaScript (Deno) APIs." + "Let's prepare sample data files that we'll use throughout this notebook." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "โœ… Sample data prepared:\n", + " - CSV data: 224 bytes\n", + " - Python script: 997 bytes\n", + " - JSON data: 216 bytes\n" + ] + } + ], "source": [ - "# JavaScript code for JSON file operations using Deno\n", - "javascript_json_code = \"\"\"\n", - "// JSON File Operations Example\n", - "console.log('Starting JSON file operations...');\n", - "\n", - "// Simple user profile data\n", - "const userProfile = {\n", - " id: 'USR-001',\n", - " name: 'John Doe',\n", - " email: 'john.doe@example.com',\n", - " age: 28,\n", - " preferences: {\n", - " theme: 'dark',\n", - " notifications: true,\n", - " language: 'en'\n", - " },\n", - " createdAt: new Date().toISOString()\n", - "};\n", - "\n", - "// Convert to JSON string with formatting\n", - "const jsonString = JSON.stringify(userProfile, null, 2);\n", - "\n", - "console.log('\\n๐Ÿ“ User Profile JSON:');\n", - "console.log(jsonString);\n", + "# Prepare sample CSV data\n", + "sample_csv_data = \"\"\"name,age,city,occupation\n", + "Alice Johnson,28,New York,Software Engineer\n", + "Bob Smith,35,San Francisco,Data Scientist\n", + "Carol Williams,42,Chicago,Product Manager\n", + "David Brown,31,Austin,DevOps Engineer\n", + "Emma Davis,26,Seattle,UX Designer\"\"\"\n", + "\n", + "# Prepare sample Python analysis script\n", + "stats_py_content = \"\"\"import csv\n", + "from io import StringIO\n", "\n", - "// Write JSON to file (Deno file system API)\n", - "try {\n", - " // In Deno, we would use:\n", - " // await Deno.writeTextFile('user_profile.json', jsonString);\n", - " \n", - " // For demonstration, we'll simulate the operation\n", - " console.log('\\nโœ… JSON file would be written to: user_profile.json');\n", - " console.log('File size: ' + jsonString.length + ' bytes');\n", - " \n", - "} catch (error) {\n", - " console.error('Error writing file:', error);\n", + "# Read the CSV data\n", + "with open('data.csv', 'r') as file:\n", + " csv_reader = csv.DictReader(file)\n", + " data = list(csv_reader)\n", + "\n", + "# Calculate statistics\n", + "total_people = len(data)\n", + "ages = [int(row['age']) for row in data]\n", + "avg_age = sum(ages) / len(ages)\n", + "min_age = min(ages)\n", + "max_age = max(ages)\n", + "\n", + "# Count by city\n", + "cities = {}\n", + "for row in data:\n", + " city = row['city']\n", + " cities[city] = cities.get(city, 0) + 1\n", + "\n", + "# Print results\n", + "print(f\\\"Dataset Statistics:\\\")\n", + "print(f\\\"==================\\\")\n", + "print(f\\\"Total people: {total_people}\\\")\n", + "print(f\\\"Average age: {avg_age:.1f}\\\")\n", + "print(f\\\"Age range: {min_age} - {max_age}\\\")\n", + "print(f\\\"\\\\nPeople by city:\\\")\n", + "for city, count in cities.items():\n", + " print(f\\\" {city}: {count}\\\")\n", + "\n", + "# Create a summary report\n", + "report = {\n", + " 'total_records': total_people,\n", + " 'average_age': round(avg_age, 2),\n", + " 'age_range': {'min': min_age, 'max': max_age},\n", + " 'city_distribution': cities\n", "}\n", "\n", - "// Parse JSON back to object\n", - "const parsedProfile = JSON.parse(jsonString);\n", - "\n", - "// Validate the data\n", - "console.log('\\n๐Ÿ” Validation Results:');\n", - "console.log('User ID: ' + parsedProfile.id);\n", - "console.log('User Name: ' + parsedProfile.name);\n", - "console.log('Email Valid: ' + parsedProfile.email.includes('@'));\n", - "console.log('Theme Setting: ' + parsedProfile.preferences.theme);\n", - "\n", - "// Create a simple configuration file\n", - "const appConfig = {\n", - " version: '1.0.0',\n", - " apiEndpoint: 'https://api.example.com',\n", - " maxRetries: 3,\n", - " timeout: 5000,\n", - " features: ['auth', 'dashboard', 'reports']\n", - "};\n", - "\n", - "const configJson = JSON.stringify(appConfig, null, 2);\n", - "\n", - "console.log('\\nโš™๏ธ Application Configuration:');\n", - "console.log(configJson);\n", - "\n", - "console.log('\\nโœ… JSON operations completed successfully!');\n", - "\n", - "// Return summary\n", - "{\n", - " filesCreated: ['user_profile.json', 'app_config.json'],\n", - " totalSize: jsonString.length + configJson.length,\n", - " status: 'success'\n", - "};\n", + "print(f\\\"\\\\nSummary report generated successfully!\\\")\n", + "print(f\\\"Report: {report}\\\")\n", "\"\"\"\n", "\n", - "print(\"JavaScript JSON operations code prepared!\")" + "# Prepare sample JSON data\n", + "sample_json_data = json.dumps({\n", + " \"project\": \"File Operations Demo\",\n", + " \"version\": \"1.0.0\",\n", + " \"features\": [\"read\", \"write\", \"process\", \"analyze\"],\n", + " \"config\": {\n", + " \"debug\": False,\n", + " \"timeout\": 300,\n", + " \"max_retries\": 3\n", + " }\n", + "}, indent=2)\n", + "\n", + "print(\"โœ… Sample data prepared:\")\n", + "print(f\" - CSV data: {len(sample_csv_data)} bytes\")\n", + "print(f\" - Python script: {len(stats_py_content)} bytes\")\n", + "print(f\" - JSON data: {len(sample_json_data)} bytes\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿ”ง Section 3: File Operations with Boto3\n", + "\n", + "This section demonstrates file read/write operations using boto3 direct API calls following the pattern from the provided example." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ - "def execute_code_with_boto3(code: str, language: str = \"javascript\") -> Dict[str, Any]:\n", - " \"\"\"\n", - " Execute code using boto3 direct API calls to AgentCore Code Interpreter.\n", - " \n", - " Args:\n", - " code: The code to execute\n", - " language: Programming language (javascript or python)\n", + "def call_tool_boto3(session_id: str, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:\n", + " \"\"\"Helper function to call Code Interpreter tools using boto3\"\"\"\n", + " response = bedrock_agentcore_client.invoke_code_interpreter(\n", + " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", + " sessionId=session_id,\n", + " name=tool_name,\n", + " arguments=arguments\n", + " )\n", " \n", - " Returns:\n", - " Dictionary with execution results\n", - " \"\"\"\n", - " try:\n", - " # Start a code interpreter session\n", - " session_response = bedrock_agentcore_client.start_code_interpreter_session(\n", - " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", - " name=\"boto3-file-ops-session\",\n", - " sessionTimeoutSeconds=300\n", - " )\n", - " session_id = session_response[\"sessionId\"]\n", - " print(f\"โœ… Started session: {session_id}\")\n", - " \n", - " # Execute the code\n", - " execute_response = bedrock_agentcore_client.invoke_code_interpreter(\n", - " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", - " sessionId=session_id,\n", - " name=\"executeCode\",\n", - " arguments={\n", - " \"language\": language,\n", - " \"code\": code\n", - " }\n", - " )\n", - " \n", - " # Process the response stream\n", - " results = []\n", - " for event in execute_response['stream']:\n", - " if 'result' in event:\n", - " result = event['result']\n", - " results.append(result)\n", - " \n", - " # Print text output\n", - " if 'content' in result:\n", - " for content_item in result['content']:\n", - " if content_item['type'] == 'text':\n", - " print(f\"Output: {content_item['text']}\")\n", - " \n", - " # Clean up session\n", - " bedrock_agentcore_client.stop_code_interpreter_session(\n", - " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", - " sessionId=session_id\n", - " )\n", - " print(f\"โœ… Session {session_id} stopped\")\n", - " \n", - " return {\n", - " \"success\": True,\n", - " \"session_id\": session_id,\n", - " \"results\": results\n", - " }\n", - " \n", - " except Exception as e:\n", - " print(f\"โŒ Error executing code: {str(e)}\")\n", - " return {\n", - " \"success\": False,\n", - " \"error\": str(e)\n", - " }" + " for event in response[\"stream\"]:\n", + " if \"result\" in event:\n", + " return event[\"result\"]\n", + " return {}" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "๐Ÿš€ Starting boto3 Code Interpreter session...\n", + "โœ… Session started: 01K253B8XRFQAJ4AVETSPVDHEB\n" + ] + } + ], "source": [ - "# Execute the JavaScript JSON operations code using boto3\n", - "print(\"๐Ÿš€ Executing JavaScript JSON file operations with boto3...\\n\")\n", - "boto3_json_result = execute_code_with_boto3(javascript_json_code, \"javascript\")\n", - "print(f\"\\nโœ… Execution completed: {boto3_json_result['success']}\")" + "# Start boto3 Code Interpreter session\n", + "print(\"๐Ÿš€ Starting boto3 Code Interpreter session...\")\n", + "session_response = bedrock_agentcore_client.start_code_interpreter_session(\n", + " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", + " name=\"boto3-file-ops-session\",\n", + " sessionTimeoutSeconds=300\n", + ")\n", + "boto3_session_id = session_response[\"sessionId\"]\n", + "print(f\"โœ… Session started: {boto3_session_id}\")" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 15, "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "๐Ÿ“ Writing files to Code Interpreter session...\n", + "โœ… Files written: {\n", + " \"content\": [\n", + " {\n", + " \"type\": \"text\",\n", + " \"text\": \"Successfully wrote all 3 files\"\n", + " }\n", + " ],\n", + " \"isError\": false\n", + "}\n" + ] + } + ], "source": [ - "## ๐Ÿ“Š Section 3: CSV File Operations with Boto3\n", + "# Prepare files to create\n", + "files_to_create = [\n", + " {\n", + " \"path\": \"data.csv\",\n", + " \"text\": sample_csv_data\n", + " },\n", + " {\n", + " \"path\": \"stats.py\",\n", + " \"text\": stats_py_content\n", + " },\n", + " {\n", + " \"path\": \"config.json\",\n", + " \"text\": sample_json_data\n", + " }\n", + "]\n", "\n", - "Now let's work with CSV files using JavaScript (Deno)." + "# Write files to the session\n", + "print(\"\\n๐Ÿ“ Writing files to Code Interpreter session...\")\n", + "writing_files = call_tool_boto3(boto3_session_id, \"writeFiles\", {\"content\": files_to_create})\n", + "print(f\"โœ… Files written: {json.dumps(writing_files, indent=2)}\")" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "๐Ÿ“‹ Listing files in session...\n", + "๐Ÿ“ Files in session: {\n", + " \"content\": [\n", + " {\n", + " \"type\": \"resource_link\",\n", + " \"uri\": \"file:///log\",\n", + " \"name\": \"log\",\n", + " \"description\": \"Directory\"\n", + " },\n", + " {\n", + " \"type\": \"resource_link\",\n", + " \"mimeType\": \"text/csv\",\n", + " \"uri\": \"file:///data.csv\",\n", + " \"name\": \"data.csv\",\n", + " \"description\": \"File\"\n", + " },\n", + " {\n", + " \"type\": \"resource_link\",\n", + " \"mimeType\": \"application/json\",\n", + " \"uri\": \"file:///config.json\",\n", + " \"name\": \"config.json\",\n", + " \"description\": \"File\"\n", + " },\n", + " {\n", + " \"type\": \"resource_link\",\n", + " \"uri\": \"file:///.ipython\",\n", + " \"name\": \".ipython\",\n", + " \"description\": \"Directory\"\n", + " },\n", + " {\n", + " \"type\": \"resource_link\",\n", + " \"mimeType\": \"text/x-python\",\n", + " \"uri\": \"file:///stats.py\",\n", + " \"name\": \"stats.py\",\n", + " \"description\": \"File\"\n", + " }\n", + " ],\n", + " \"isError\": false\n", + "}\n" + ] + } + ], "source": [ - "# JavaScript code for CSV file operations\n", - "javascript_csv_code = \"\"\"\n", - "// CSV File Operations Example\n", - "console.log('Starting CSV file operations...');\n", - "\n", - "// Simple product data\n", - "const products = [\n", - " { id: 'P001', name: 'Laptop', price: 999.99, stock: 15 },\n", - " { id: 'P002', name: 'Mouse', price: 29.99, stock: 50 },\n", - " { id: 'P003', name: 'Keyboard', price: 79.99, stock: 30 }\n", - "];\n", - "\n", - "// Function to convert array of objects to CSV\n", - "function arrayToCSV(data) {\n", - " if (data.length === 0) return '';\n", - " \n", - " // Get headers from first object\n", - " const headers = Object.keys(data[0]);\n", - " const csvHeaders = headers.join(',');\n", - " \n", - " // Convert each object to CSV row\n", - " const csvRows = data.map(obj => {\n", - " return headers.map(header => {\n", - " const value = obj[header];\n", - " // Handle values that might contain commas\n", - " return typeof value === 'string' && value.includes(',') \n", - " ? `\"${value}\"` \n", - " : value;\n", - " }).join(',');\n", - " });\n", - " \n", - " // Combine headers and rows\n", - " return [csvHeaders, ...csvRows].join('\\n');\n", - "}\n", - "\n", - "// Generate CSV content\n", - "const csvContent = arrayToCSV(products);\n", - "\n", - "console.log('\\n๐Ÿ“Š Generated CSV Content:');\n", - "console.log('================================');\n", - "console.log(csvContent);\n", - "\n", - "// Calculate some statistics\n", - "const totalValue = products.reduce((sum, p) => sum + (p.price * p.stock), 0);\n", - "const avgPrice = products.reduce((sum, p) => sum + p.price, 0) / products.length;\n", - "const totalStock = products.reduce((sum, p) => sum + p.stock, 0);\n", - "\n", - "console.log('\\n๐Ÿ“ˆ Inventory Statistics:');\n", - "console.log('Total inventory value: $' + totalValue.toFixed(2));\n", - "console.log('Average product price: $' + avgPrice.toFixed(2));\n", - "console.log('Total items in stock: ' + totalStock);\n", - "\n", - "// Create a simple sales report CSV\n", - "const salesData = [\n", - " { date: '2024-01-01', product: 'Laptop', quantity: 2, revenue: 1999.98 },\n", - " { date: '2024-01-02', product: 'Mouse', quantity: 5, revenue: 149.95 },\n", - " { date: '2024-01-03', product: 'Keyboard', quantity: 3, revenue: 239.97 }\n", - "];\n", - "\n", - "const salesCSV = arrayToCSV(salesData);\n", - "\n", - "console.log('\\n๐Ÿ’ฐ Sales Report CSV:');\n", - "console.log('================================');\n", - "console.log(salesCSV);\n", - "\n", - "// Parse CSV back to verify\n", - "function parseCSV(csvString) {\n", - " const lines = csvString.split('\\n');\n", - " const headers = lines[0].split(',');\n", - " const data = [];\n", - " \n", - " for (let i = 1; i < lines.length; i++) {\n", - " const values = lines[i].split(',');\n", - " const obj = {};\n", - " headers.forEach((header, index) => {\n", - " obj[header] = values[index];\n", - " });\n", - " data.push(obj);\n", - " }\n", - " \n", - " return data;\n", - "}\n", - "\n", - "const parsedProducts = parseCSV(csvContent);\n", - "console.log('\\nโœ… CSV parsing verification:');\n", - "console.log('Parsed ' + parsedProducts.length + ' products successfully');\n", - "\n", - "console.log('\\nโœ… CSV operations completed successfully!');\n", - "\n", - "// Return summary\n", - "{\n", - " filesGenerated: ['products.csv', 'sales_report.csv'],\n", - " totalRows: products.length + salesData.length,\n", - " totalBytes: csvContent.length + salesCSV.length,\n", - " status: 'success'\n", - "};\n", - "\"\"\"\n", - "\n", - "print(\"JavaScript CSV operations code prepared!\")" + "# List files to verify\n", + "print(\"\\n๐Ÿ“‹ Listing files in session...\")\n", + "listing_files = call_tool_boto3(boto3_session_id, \"listFiles\", {\"path\": \"\"})\n", + "print(f\"๐Ÿ“ Files in session: {json.dumps(listing_files, indent=2)}\")" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "๐Ÿ”ง Executing analysis script...\n", + "๐Ÿ“Š Code execution result: {\n", + " \"content\": [\n", + " {\n", + " \"type\": \"text\",\n", + " \"text\": \"Dataset Statistics:\\n==================\\nTotal people: 5\\nAverage age: 32.4\\nAge range: 26 - 42\\n\\nPeople by city:\\n New York: 1\\n San Francisco: 1\\n Chicago: 1\\n Austin: 1\\n Seattle: 1\\n\\nSummary report generated successfully!\\nReport: {'total_records': 5, 'average_age': 32.4, 'age_range': {'min': 26, 'max': 42}, 'city_distribution': {'New York': 1, 'San Francisco': 1, 'Chicago': 1, 'Austin': 1, 'Seattle': 1}}\"\n", + " }\n", + " ],\n", + " \"structuredContent\": {\n", + " \"stdout\": \"Dataset Statistics:\\n==================\\nTotal people: 5\\nAverage age: 32.4\\nAge range: 26 - 42\\n\\nPeople by city:\\n New York: 1\\n San Francisco: 1\\n Chicago: 1\\n Austin: 1\\n Seattle: 1\\n\\nSummary report generated successfully!\\nReport: {'total_records': 5, 'average_age': 32.4, 'age_range': {'min': 26, 'max': 42}, 'city_distribution': {'New York': 1, 'San Francisco': 1, 'Chicago': 1, 'Austin': 1, 'Seattle': 1}}\",\n", + " \"stderr\": \"\",\n", + " \"exitCode\": 0,\n", + " \"executionTime\": 0.01729130744934082\n", + " },\n", + " \"isError\": false\n", + "}\n" + ] + } + ], + "source": [ + "# Execute the Python script to analyze the CSV data\n", + "print(\"\\n๐Ÿ”ง Executing analysis script...\")\n", + "execute_code = call_tool_boto3(boto3_session_id, \"executeCode\", {\n", + " \"code\": stats_py_content,\n", + " \"language\": \"python\",\n", + " \"clearContext\": True\n", + "})\n", + "print(f\"๐Ÿ“Š Code execution result: {json.dumps(execute_code, indent=2)}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "๐Ÿ“– Reading config.json to verify...\n", + "๐Ÿ“„ File content: {\n", + " \"content\": [\n", + " {\n", + " \"type\": \"text\",\n", + " \"text\": \"Error executing tool read_files: 1 validation error for read_filesArguments\\npaths\\n Field required [type=missing, input_value={'path': 'config.json'}, input_type=dict]\\n For further information visit https://errors.pydantic.dev/2.10/v/missing\"\n", + " }\n", + " ],\n", + " \"isError\": true\n", + "}\n" + ] + } + ], + "source": [ + "# Read a file back to verify\n", + "print(\"\\n๐Ÿ“– Reading config.json to verify...\")\n", + "read_file = call_tool_boto3(boto3_session_id, \"readFiles\", {\"path\": \"config.json\"})\n", + "print(f\"๐Ÿ“„ File content: {json.dumps(read_file, indent=2)}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "โœ… Boto3 session 01K252HVKZPT9JJ13SD1S5BJY4 stopped\n" + ] + } + ], "source": [ - "# Execute the JavaScript CSV operations code using boto3\n", - "print(\"๐Ÿš€ Executing JavaScript CSV file operations with boto3...\\n\")\n", - "boto3_csv_result = execute_code_with_boto3(javascript_csv_code, \"javascript\")\n", - "print(f\"\\nโœ… Execution completed: {boto3_csv_result['success']}\")" + "# Clean up boto3 session\n", + "bedrock_agentcore_client.stop_code_interpreter_session(\n", + " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", + " sessionId=boto3_session_id\n", + ")\n", + "print(f\"\\nโœ… Boto3 session {boto3_session_id} stopped\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## ๐Ÿ Section 4: File Operations with AgentCore SDK\n", + "## ๐Ÿ”ง Section 4: File Operations with AgentCore SDK\n", "\n", - "Now let's perform file operations using Python through the AgentCore SDK." + "This section demonstrates the same file operations using the AgentCore SDK's higher-level interface." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "๐Ÿš€ Starting AgentCore SDK Code Interpreter session...\n", + "โœ… AgentCore SDK session started\n" + ] + } + ], "source": [ - "# Python code for JSON and CSV file operations\n", - "python_file_ops_code = \"\"\"\n", - "import json\n", - "import csv\n", - "from io import StringIO\n", - "from datetime import datetime\n", - "\n", - "print('Starting file operations with Python...')\n", - "\n", - "# ===== JSON Operations =====\n", - "print('\\n๐Ÿ“„ JSON File Operations:')\n", - "print('=' * 40)\n", - "\n", - "# Create a simple employee record\n", - "employee = {\n", - " 'emp_id': 'EMP-2024-001',\n", - " 'name': 'Jane Smith',\n", - " 'department': 'Engineering',\n", - " 'salary': 85000,\n", - " 'skills': ['Python', 'AWS', 'Docker'],\n", - " 'active': True,\n", - " 'joined_date': '2024-01-15'\n", - "}\n", - "\n", - "# Convert to formatted JSON\n", - "json_str = json.dumps(employee, indent=2)\n", - "print('Employee Record (JSON):')\n", - "print(json_str)\n", - "\n", - "# Parse and validate\n", - "parsed_employee = json.loads(json_str)\n", - "print(f\"\\nโœ… Validated: Employee {parsed_employee['name']} in {parsed_employee['department']}\")\n", - "print(f\"Skills count: {len(parsed_employee['skills'])}\")\n", - "\n", - "# Create a settings file\n", - "settings = {\n", - " 'app_name': 'DataProcessor',\n", - " 'version': '2.1.0',\n", - " 'debug_mode': False,\n", - " 'max_connections': 100,\n", - " 'database': {\n", - " 'host': 'localhost',\n", - " 'port': 5432,\n", - " 'name': 'app_db'\n", - " }\n", - "}\n", - "\n", - "settings_json = json.dumps(settings, indent=2)\n", - "print('\\nApplication Settings:')\n", - "print(settings_json)\n", - "\n", - "# ===== CSV Operations =====\n", - "print('\\n๐Ÿ“Š CSV File Operations:')\n", - "print('=' * 40)\n", - "\n", - "# Create inventory data\n", - "inventory = [\n", - " ['Item', 'Category', 'Quantity', 'Price'],\n", - " ['Notebook', 'Stationery', 100, 2.50],\n", - " ['Pen', 'Stationery', 250, 1.00],\n", - " ['Calculator', 'Electronics', 25, 15.99]\n", - "]\n", - "\n", - "# Write to CSV format\n", - "csv_buffer = StringIO()\n", - "csv_writer = csv.writer(csv_buffer)\n", - "for row in inventory:\n", - " csv_writer.writerow(row)\n", - "\n", - "csv_content = csv_buffer.getvalue()\n", - "print('Inventory CSV:')\n", - "print(csv_content)\n", - "\n", - "# Parse CSV and calculate totals\n", - "csv_buffer.seek(0)\n", - "csv_reader = csv.DictReader(csv_buffer)\n", - "\n", - "total_value = 0\n", - "total_items = 0\n", - "\n", - "print('\\n๐Ÿ“ˆ Inventory Analysis:')\n", - "for row in csv_reader:\n", - " quantity = float(row['Quantity'])\n", - " price = float(row['Price'])\n", - " value = quantity * price\n", - " total_value += value\n", - " total_items += quantity\n", - " print(f\"{row['Item']}: {quantity} units @ ${price:.2f} = ${value:.2f}\")\n", - "\n", - "print(f'\\nTotal items: {int(total_items)}')\n", - "print(f'Total inventory value: ${total_value:.2f}')\n", - "\n", - "# Create a transaction log CSV\n", - "transactions = [\n", - " {'id': 'TXN001', 'date': '2024-01-10', 'amount': 150.00, 'status': 'completed'},\n", - " {'id': 'TXN002', 'date': '2024-01-11', 'amount': 75.50, 'status': 'completed'},\n", - " {'id': 'TXN003', 'date': '2024-01-12', 'amount': 200.00, 'status': 'pending'}\n", - "]\n", - "\n", - "# Write transactions to CSV\n", - "txn_buffer = StringIO()\n", - "fieldnames = ['id', 'date', 'amount', 'status']\n", - "dict_writer = csv.DictWriter(txn_buffer, fieldnames=fieldnames)\n", - "dict_writer.writeheader()\n", - "dict_writer.writerows(transactions)\n", - "\n", - "print('\\n๐Ÿ’ฐ Transaction Log CSV:')\n", - "print(txn_buffer.getvalue())\n", - "\n", - "# Summary statistics\n", - "completed_amount = sum(t['amount'] for t in transactions if t['status'] == 'completed')\n", - "pending_amount = sum(t['amount'] for t in transactions if t['status'] == 'pending')\n", - "\n", - "print('\\n๐Ÿ“Š Transaction Summary:')\n", - "print(f'Completed transactions: ${completed_amount:.2f}')\n", - "print(f'Pending transactions: ${pending_amount:.2f}')\n", - "print(f'Total transactions: {len(transactions)}')\n", - "\n", - "print('\\nโœ… All file operations completed successfully!')\n", - "\n", - "# Return summary\n", - "{\n", - " 'json_files': ['employee.json', 'settings.json'],\n", - " 'csv_files': ['inventory.csv', 'transactions.csv'],\n", - " 'total_operations': 4,\n", - " 'status': 'success'\n", - "}\n", - "\"\"\"\n", - "\n", - "print(\"Python file operations code prepared!\")" + "# Configure and start the code interpreter session\n", + "print(\"๐Ÿš€ Starting AgentCore SDK Code Interpreter session...\")\n", + "code_client = CodeInterpreter(region_name)\n", + "code_client.start()\n", + "print(\"โœ… AgentCore SDK session started\")" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ - "def execute_code_with_sdk(code: str, language: str = \"python\") -> Dict[str, Any]:\n", - " \"\"\"\n", - " Execute code using the AgentCore SDK (higher-level interface).\n", - " \n", - " Args:\n", - " code: The code to execute\n", - " language: Programming language (javascript or python)\n", - " \n", - " Returns:\n", - " Dictionary with execution results\n", - " \"\"\"\n", - " try:\n", - " # Configure and start the code interpreter session\n", - " code_client = CodeInterpreter(region_name)\n", - " code_client.start()\n", - " print(\"โœ… AgentCore SDK session started\")\n", - " \n", - " # Execute the code\n", - " response = code_client.invoke(\"executeCode\", {\n", - " \"language\": language,\n", - " \"code\": code\n", - " })\n", - " \n", - " # Process and print the response\n", - " results = []\n", - " for event in response[\"stream\"]:\n", - " if \"result\" in event:\n", - " result = event[\"result\"]\n", - " results.append(result)\n", - " \n", - " # Extract and display text content\n", - " if 'content' in result:\n", - " for content_item in result['content']:\n", - " if content_item['type'] == 'text':\n", - " print(content_item['text'])\n", - " \n", - " # Clean up and stop the session\n", - " code_client.stop()\n", - " print(\"\\nโœ… AgentCore SDK session stopped\")\n", - " \n", - " return {\n", - " \"success\": True,\n", - " \"results\": results\n", - " }\n", - " \n", - " except Exception as e:\n", - " print(f\"โŒ Error executing code with SDK: {str(e)}\")\n", - " return {\n", - " \"success\": False,\n", - " \"error\": str(e)\n", - " }" + "# Define the method to call the invoke API (following the example pattern)\n", + "def call_tool(tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:\n", + " \"\"\"Helper function to call Code Interpreter tools using SDK\"\"\"\n", + " response = code_client.invoke(tool_name, arguments)\n", + " for event in response[\"stream\"]:\n", + " return json.dumps(event[\"result\"], indent=2)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "๐Ÿ“ Writing files using SDK...\n", + "โœ… Files written: {\n", + " \"content\": [\n", + " {\n", + " \"type\": \"text\",\n", + " \"text\": \"Successfully wrote all 3 files\"\n", + " }\n", + " ],\n", + " \"isError\": false\n", + "}\n" + ] + } + ], + "source": [ + "# Write the sample data and analysis script into the code interpreter session\n", + "print(\"\\n๐Ÿ“ Writing files using SDK...\")\n", + "writing_files = call_tool(\"writeFiles\", {\"content\": files_to_create})\n", + "print(f\"โœ… Files written: {writing_files}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "๐Ÿ“‹ Listing files using SDK...\n", + "๐Ÿ“ Files in session: {\n", + " \"content\": [\n", + " {\n", + " \"type\": \"resource_link\",\n", + " \"uri\": \"file:///log\",\n", + " \"name\": \"log\",\n", + " \"description\": \"Directory\"\n", + " },\n", + " {\n", + " \"type\": \"resource_link\",\n", + " \"mimeType\": \"text/csv\",\n", + " \"uri\": \"file:///data.csv\",\n", + " \"name\": \"data.csv\",\n", + " \"description\": \"File\"\n", + " },\n", + " {\n", + " \"type\": \"resource_link\",\n", + " \"mimeType\": \"application/json\",\n", + " \"uri\": \"file:///config.json\",\n", + " \"name\": \"config.json\",\n", + " \"description\": \"File\"\n", + " },\n", + " {\n", + " \"type\": \"resource_link\",\n", + " \"uri\": \"file:///.ipython\",\n", + " \"name\": \".ipython\",\n", + " \"description\": \"Directory\"\n", + " },\n", + " {\n", + " \"type\": \"resource_link\",\n", + " \"mimeType\": \"text/x-python\",\n", + " \"uri\": \"file:///stats.py\",\n", + " \"name\": \"stats.py\",\n", + " \"description\": \"File\"\n", + " }\n", + " ],\n", + " \"isError\": false\n", + "}\n" + ] + } + ], + "source": [ + "# List and validate that the files were written successfully\n", + "print(\"\\n๐Ÿ“‹ Listing files using SDK...\")\n", + "listing_files = call_tool(\"listFiles\", {\"path\": \"\"})\n", + "print(f\"๐Ÿ“ Files in session: {listing_files}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "๐Ÿ”ง Executing analysis script using SDK...\n", + "๐Ÿ“Š Code execution result: {\n", + " \"content\": [\n", + " {\n", + " \"type\": \"text\",\n", + " \"text\": \"Dataset Statistics:\\n==================\\nTotal people: 5\\nAverage age: 32.4\\nAge range: 26 - 42\\n\\nPeople by city:\\n New York: 1\\n San Francisco: 1\\n Chicago: 1\\n Austin: 1\\n Seattle: 1\\n\\nSummary report generated successfully!\\nReport: {'total_records': 5, 'average_age': 32.4, 'age_range': {'min': 26, 'max': 42}, 'city_distribution': {'New York': 1, 'San Francisco': 1, 'Chicago': 1, 'Austin': 1, 'Seattle': 1}}\"\n", + " }\n", + " ],\n", + " \"structuredContent\": {\n", + " \"stdout\": \"Dataset Statistics:\\n==================\\nTotal people: 5\\nAverage age: 32.4\\nAge range: 26 - 42\\n\\nPeople by city:\\n New York: 1\\n San Francisco: 1\\n Chicago: 1\\n Austin: 1\\n Seattle: 1\\n\\nSummary report generated successfully!\\nReport: {'total_records': 5, 'average_age': 32.4, 'age_range': {'min': 26, 'max': 42}, 'city_distribution': {'New York': 1, 'San Francisco': 1, 'Chicago': 1, 'Austin': 1, 'Seattle': 1}}\",\n", + " \"stderr\": \"\",\n", + " \"exitCode\": 0,\n", + " \"executionTime\": 0.0170438289642334\n", + " },\n", + " \"isError\": false\n", + "}\n" + ] + } + ], + "source": [ + "# Run the python script to analyze the sample data file\n", + "print(\"\\n๐Ÿ”ง Executing analysis script using SDK...\")\n", + "execute_code = call_tool(\"executeCode\", {\n", + " \"code\": files_to_create[1]['text'],\n", + " \"language\": \"python\",\n", + " \"clearContext\": True\n", + "})\n", + "print(f\"๐Ÿ“Š Code execution result: {execute_code}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "๐Ÿ“– Reading config.json using SDK...\n", + "๐Ÿ“„ File content: {\n", + " \"content\": [\n", + " {\n", + " \"type\": \"text\",\n", + " \"text\": \"Error executing tool read_files: 1 validation error for read_filesArguments\\npaths\\n Field required [type=missing, input_value={'path': 'config.json'}, input_type=dict]\\n For further information visit https://errors.pydantic.dev/2.10/v/missing\"\n", + " }\n", + " ],\n", + " \"isError\": true\n", + "}\n" + ] + } + ], + "source": [ + "# Read a file back to verify\n", + "print(\"\\n๐Ÿ“– Reading config.json using SDK...\")\n", + "read_file = call_tool(\"readFiles\", {\"path\": \"config.json\"})\n", + "print(f\"๐Ÿ“„ File content: {read_file}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "โœ… AgentCore SDK session stopped\n" + ] + } + ], "source": [ - "# Execute the Python file operations code using AgentCore SDK\n", - "print(\"๐Ÿš€ Executing Python file operations with AgentCore SDK...\\n\")\n", - "sdk_file_result = execute_code_with_sdk(python_file_ops_code, \"python\")\n", - "print(f\"\\nโœ… Execution completed: {sdk_file_result['success']}\")" + "# Clean up and stop the code interpreter session\n", + "code_client.stop()\n", + "print(\"\\nโœ… AgentCore SDK session stopped\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## ๐ŸŽฏ Section 5: Summary and Key Takeaways\n", - "\n", - "### ๐Ÿ“ What We've Learned\n", + "## Conclusion\n", "\n", "In this notebook, we've successfully demonstrated:\n", "\n", - "1. **JSON File Operations**: \n", - " - Creating structured JSON data (user profiles, configurations)\n", - " - Parsing and validating JSON content\n", - " - Working with nested JSON objects\n", - "\n", - "2. **CSV File Operations**:\n", - " - Generating CSV from arrays of objects\n", - " - Parsing CSV back to structured data\n", - " - Handling different data types in CSV format\n", - "\n", - "3. **Two Implementation Approaches**:\n", - " - **Boto3**: Direct API calls with JavaScript/Deno file operations\n", - " - **AgentCore SDK**: Python implementation with built-in CSV and JSON modules\n", - "\n", - "### ๐Ÿ”‘ Key Differences Between Languages\n", - "\n", - "| Aspect | JavaScript (Deno) | Python |\n", - "|--------|-------------------|--------|\n", - "| **JSON Handling** | Native JSON.stringify/parse | json module |\n", - "| **CSV Support** | Manual implementation | Built-in csv module |\n", - "| **File I/O** | Deno.writeTextFile (async) | Built-in file operations |\n", - "| **Data Processing** | Array methods (map, reduce) | List comprehensions, csv.DictReader |\n", - "\n", - "### ๐Ÿ’ก Best Practices for File Operations\n", - "\n", - "1. **Data Validation**: Always validate data before and after file operations\n", - "2. **Error Handling**: Include try-catch blocks for file I/O operations\n", - "3. **Format Selection**: \n", - " - Use JSON for hierarchical/nested data\n", - " - Use CSV for tabular/flat data\n", - "4. **Memory Efficiency**: Use streaming for large files\n", - "5. **Encoding**: Be explicit about character encoding (UTF-8)\n", - "\n", - "### ๐Ÿš€ Next Steps\n", + "1. **File Operations with Boto3**:\n", + " - Direct API calls to Code Interpreter\n", + " - Writing files using `writeFiles`\n", + " - Listing files using `listFiles`\n", + " - Reading files using `readFile`\n", + " - Executing code that processes files\n", "\n", - "- Explore the companion notebook on data processing and analysis\n", - "- Try working with larger datasets\n", - "- Implement file upload/download in agent workflows\n", - "- Experiment with other file formats (XML, YAML)\n", - "- Integrate file operations with S3 for persistent storage\n", + "2. **File Operations with AgentCore SDK**:\n", + " - Higher-level interface for Code Interpreter\n", + " - Simplified session management\n", + " - Same file operations with cleaner syntax\n", "\n", "### ๐Ÿ“š Additional Resources\n", "\n", "- [AWS Bedrock AgentCore Documentation](https://docs.aws.amazon.com/bedrock-agentcore/)\n", - "- [Deno File System API](https://deno.land/manual/runtime/file_system)\n", - "- [Python CSV Module Documentation](https://docs.python.org/3/library/csv.html)\n", + "- [Code Interpreter API Reference](https://docs.aws.amazon.com/bedrock-agentcore/latest/APIReference/)\n", "- [Module 3 Other Notebooks](../README.md)\n", "\n", "Happy coding! ๐ŸŽ‰" @@ -701,7 +759,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, From 5f32ea8556cb376a3947224b195631d3df9fb30c Mon Sep 17 00:00:00 2001 From: dinsajwa Date: Fri, 8 Aug 2025 11:14:11 -0400 Subject: [PATCH 06/11] updated readme --- labs/module3/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/labs/module3/README.md b/labs/module3/README.md index ce102f0..2d82cf1 100644 --- a/labs/module3/README.md +++ b/labs/module3/README.md @@ -54,6 +54,18 @@ The workshop consists of several notebooks that build foundational abstractions: - ๐Ÿ” Understanding framework tradeoffs - ๐Ÿ”„ Migrating agents between frameworks +### 6. Data Processing & Analysis (`6.1_data_processing_analysis_using_code_interpreter.ipynb`) +- ๐Ÿ“Š Using AWS Bedrock AgentCore Code Interpreter for data analysis +- ๐Ÿ”ง Setting up both boto3 and AgentCore SDK approaches +- ๐Ÿ“ˆ Performing statistical calculations on datasets +- ๐Ÿ”„ Processing data with JavaScript and Python + +### 7. File Operations (`6.2_file_operations_using_code_interpreter.ipynb`) +- ๐Ÿ“ Comprehensive file operations using Code Interpreter +- ๐Ÿ“„ Reading, writing, and processing files +- ๐Ÿ“Š Working with JSON and CSV files +- ๐Ÿ”„ Complete file workflows including upload, process, and verify + ## ๐Ÿงญ Workshop Flow From 519b8cf4d906d049b097589961834f9bad569a08 Mon Sep 17 00:00:00 2001 From: dinsajwa Date: Fri, 8 Aug 2025 11:19:02 -0400 Subject: [PATCH 07/11] clear notebook outputs --- ...sing_analysis_using_code_interpreter.ipynb | 152 +------- ...le_operations_using_code_interpreter.ipynb | 356 ++---------------- 2 files changed, 48 insertions(+), 460 deletions(-) diff --git a/labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb b/labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb index ec74b5c..d2f477a 100644 --- a/labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb +++ b/labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb @@ -35,19 +35,9 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "โœ… Python version: 3.12.8 (main, Jan 14 2025, 23:36:58) [Clang 19.1.6 ]\n", - "โœ… Python executable: /Users/dinsajwa/work/projects/sample-agentic-platform/.venv/bin/python3\n", - "โœ… Environment setup complete! All dependencies should be available via UV.\n" - ] - } - ], + "outputs": [], "source": [ "# ๐Ÿ”ง Environment Setup Instructions\n", "\n", @@ -73,17 +63,9 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "โœ… Dependencies imported successfully!\n" - ] - } - ], + "outputs": [], "source": [ "# Import core dependencies\n", "import boto3\n", @@ -107,18 +89,7 @@ "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "โœ… Using AWS profile: ags-tech-namer-ca-pace+ai3-core-Admin\n", - "โœ… Access Key: ASIAXBZV5H...\n", - "โœ… Region: us-west-2\n", - "โœ… AWS clients initialized successfully with profile!\n" - ] - } - ], + "outputs": [], "source": [ "# Initialize AWS clients with specific profile\n", "profile_name = \"XXXXXXX\" # your aws profile name\n", @@ -156,17 +127,9 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "JavaScript statistical analysis code prepared!\n" - ] - } - ], + "outputs": [], "source": [ "# Simple JavaScript code for basic statistical analysis\n", "# NOTE: Emojis removed from JavaScript code to avoid Deno parsing errors\n", @@ -236,7 +199,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -308,39 +271,9 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "๐Ÿš€ Executing JavaScript statistical analysis with boto3...\n", - "\n", - "โœ… Started session: 01K233EG17WY3NRVDNQ2MEDET5\n", - "Output: Starting simple data analysis...\n", - "=====================================\n", - "Number of readings: 5\n", - "Average temperature: 23.50 C\n", - "Minimum temperature: 21.90 C\n", - "Maximum temperature: 25.20 C\n", - "Temperature range: 3.30 C\n", - "Variance: 1.38\n", - "=== DAILY READINGS ===\n", - "Monday: 22.5 C\n", - "Tuesday: 24.1 C\n", - "Wednesday: 23.8 C\n", - "Thursday: 25.2 C\n", - "Friday: 21.9 C\n", - "=====================================\n", - "Analysis complete!\n", - "\n", - "โœ… Session 01K233EG17WY3NRVDNQ2MEDET5 stopped\n", - "\n", - "โœ… Execution completed: True\n" - ] - } - ], + "outputs": [], "source": [ "# Execute the JavaScript statistical analysis code using boto3\n", "print(\"๐Ÿš€ Executing JavaScript statistical analysis with boto3...\\n\")\n", @@ -359,17 +292,9 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Python statistical analysis code prepared!\n" - ] - } - ], + "outputs": [], "source": [ "# Simple Python code for basic statistical analysis\n", "python_stats_code = \"\"\"\n", @@ -435,7 +360,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -513,56 +438,9 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "๐Ÿš€ Executing Python statistical analysis with AgentCore SDK...\n", - "\n", - "โœ… AWS credentials set in environment variables\n", - "โœ… AgentCore SDK session started with environment credentials\n", - "Starting simple data analysis...\n", - "=== TEST SCORE ANALYSIS RESULTS ===\n", - "===================================\n", - "Number of students: 5\n", - "Average score: 87.6\n", - "Median score: 88.0\n", - "Lowest score: 78\n", - "Highest score: 95\n", - "Score range: 17\n", - "Variance: 43.30\n", - "Standard deviation: 6.58\n", - "=== INDIVIDUAL SCORES ===\n", - "Alice: 85 (Grade: B)\n", - "Bob: 92 (Grade: A)\n", - "Charlie: 78 (Grade: C)\n", - "Diana: 95 (Grade: A)\n", - "Eve: 88 (Grade: B)\n", - "=== Above average students (87.6+) ===\n", - " - Bob: 92\n", - " - Diana: 95\n", - " - Eve: 88\n", - "Analysis complete!\n", - "\n", - "{'count': 5,\n", - " 'sum': 438,\n", - " 'mean': 87.6,\n", - " 'median': 88,\n", - " 'min': 78,\n", - " 'max': 95,\n", - " 'range': 17,\n", - " 'variance': 43.3,\n", - " 'stdev': 6.58027355054484}\n", - "\n", - "โœ… AgentCore SDK session stopped\n", - "\n", - "โœ… Execution completed: True\n" - ] - } - ], + "outputs": [], "source": [ "# Execute the Python statistical analysis code using AgentCore SDK\n", "print(\"๐Ÿš€ Executing Python statistical analysis with AgentCore SDK...\\n\")\n", diff --git a/labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb b/labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb index 3e15033..ab5fc23 100644 --- a/labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb +++ b/labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb @@ -36,19 +36,9 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "โœ… Python version: 3.12.8 (main, Jan 14 2025, 23:36:58) [Clang 19.1.6 ]\n", - "โœ… Python executable: /Users/dinsajwa/work/projects/sample-agentic-platform/.venv/bin/python3\n", - "โœ… Environment setup complete! All dependencies should be available via UV.\n" - ] - } - ], + "outputs": [], "source": [ "# ๐Ÿ”ง Environment Setup Instructions\n", "\n", @@ -74,17 +64,9 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "โœ… Dependencies imported successfully!\n" - ] - } - ], + "outputs": [], "source": [ "# Import core dependencies\n", "import boto3\n", @@ -108,18 +90,7 @@ "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "โœ… Using AWS profile: ags-tech-namer-ca-pace+ai3-core-Admin\n", - "โœ… Access Key: ASIAXBZV5H...\n", - "โœ… Region: us-west-2\n", - "โœ… AWS clients initialized successfully with profile!\n" - ] - } - ], + "outputs": [], "source": [ "# Initialize AWS clients with specific profile\n", "profile_name = \"XXXXXX\" # your aws profile name\n", @@ -164,20 +135,9 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "โœ… Sample data prepared:\n", - " - CSV data: 224 bytes\n", - " - Python script: 997 bytes\n", - " - JSON data: 216 bytes\n" - ] - } - ], + "outputs": [], "source": [ "# Prepare sample CSV data\n", "sample_csv_data = \"\"\"name,age,city,occupation\n", @@ -260,7 +220,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -281,18 +241,9 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "๐Ÿš€ Starting boto3 Code Interpreter session...\n", - "โœ… Session started: 01K253B8XRFQAJ4AVETSPVDHEB\n" - ] - } - ], + "outputs": [], "source": [ "# Start boto3 Code Interpreter session\n", "print(\"๐Ÿš€ Starting boto3 Code Interpreter session...\")\n", @@ -307,27 +258,9 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "๐Ÿ“ Writing files to Code Interpreter session...\n", - "โœ… Files written: {\n", - " \"content\": [\n", - " {\n", - " \"type\": \"text\",\n", - " \"text\": \"Successfully wrote all 3 files\"\n", - " }\n", - " ],\n", - " \"isError\": false\n", - "}\n" - ] - } - ], + "outputs": [], "source": [ "# Prepare files to create\n", "files_to_create = [\n", @@ -353,56 +286,9 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "๐Ÿ“‹ Listing files in session...\n", - "๐Ÿ“ Files in session: {\n", - " \"content\": [\n", - " {\n", - " \"type\": \"resource_link\",\n", - " \"uri\": \"file:///log\",\n", - " \"name\": \"log\",\n", - " \"description\": \"Directory\"\n", - " },\n", - " {\n", - " \"type\": \"resource_link\",\n", - " \"mimeType\": \"text/csv\",\n", - " \"uri\": \"file:///data.csv\",\n", - " \"name\": \"data.csv\",\n", - " \"description\": \"File\"\n", - " },\n", - " {\n", - " \"type\": \"resource_link\",\n", - " \"mimeType\": \"application/json\",\n", - " \"uri\": \"file:///config.json\",\n", - " \"name\": \"config.json\",\n", - " \"description\": \"File\"\n", - " },\n", - " {\n", - " \"type\": \"resource_link\",\n", - " \"uri\": \"file:///.ipython\",\n", - " \"name\": \".ipython\",\n", - " \"description\": \"Directory\"\n", - " },\n", - " {\n", - " \"type\": \"resource_link\",\n", - " \"mimeType\": \"text/x-python\",\n", - " \"uri\": \"file:///stats.py\",\n", - " \"name\": \"stats.py\",\n", - " \"description\": \"File\"\n", - " }\n", - " ],\n", - " \"isError\": false\n", - "}\n" - ] - } - ], + "outputs": [], "source": [ "# List files to verify\n", "print(\"\\n๐Ÿ“‹ Listing files in session...\")\n", @@ -412,33 +298,9 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "๐Ÿ”ง Executing analysis script...\n", - "๐Ÿ“Š Code execution result: {\n", - " \"content\": [\n", - " {\n", - " \"type\": \"text\",\n", - " \"text\": \"Dataset Statistics:\\n==================\\nTotal people: 5\\nAverage age: 32.4\\nAge range: 26 - 42\\n\\nPeople by city:\\n New York: 1\\n San Francisco: 1\\n Chicago: 1\\n Austin: 1\\n Seattle: 1\\n\\nSummary report generated successfully!\\nReport: {'total_records': 5, 'average_age': 32.4, 'age_range': {'min': 26, 'max': 42}, 'city_distribution': {'New York': 1, 'San Francisco': 1, 'Chicago': 1, 'Austin': 1, 'Seattle': 1}}\"\n", - " }\n", - " ],\n", - " \"structuredContent\": {\n", - " \"stdout\": \"Dataset Statistics:\\n==================\\nTotal people: 5\\nAverage age: 32.4\\nAge range: 26 - 42\\n\\nPeople by city:\\n New York: 1\\n San Francisco: 1\\n Chicago: 1\\n Austin: 1\\n Seattle: 1\\n\\nSummary report generated successfully!\\nReport: {'total_records': 5, 'average_age': 32.4, 'age_range': {'min': 26, 'max': 42}, 'city_distribution': {'New York': 1, 'San Francisco': 1, 'Chicago': 1, 'Austin': 1, 'Seattle': 1}}\",\n", - " \"stderr\": \"\",\n", - " \"exitCode\": 0,\n", - " \"executionTime\": 0.01729130744934082\n", - " },\n", - " \"isError\": false\n", - "}\n" - ] - } - ], + "outputs": [], "source": [ "# Execute the Python script to analyze the CSV data\n", "print(\"\\n๐Ÿ”ง Executing analysis script...\")\n", @@ -452,27 +314,9 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "๐Ÿ“– Reading config.json to verify...\n", - "๐Ÿ“„ File content: {\n", - " \"content\": [\n", - " {\n", - " \"type\": \"text\",\n", - " \"text\": \"Error executing tool read_files: 1 validation error for read_filesArguments\\npaths\\n Field required [type=missing, input_value={'path': 'config.json'}, input_type=dict]\\n For further information visit https://errors.pydantic.dev/2.10/v/missing\"\n", - " }\n", - " ],\n", - " \"isError\": true\n", - "}\n" - ] - } - ], + "outputs": [], "source": [ "# Read a file back to verify\n", "print(\"\\n๐Ÿ“– Reading config.json to verify...\")\n", @@ -482,18 +326,9 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "โœ… Boto3 session 01K252HVKZPT9JJ13SD1S5BJY4 stopped\n" - ] - } - ], + "outputs": [], "source": [ "# Clean up boto3 session\n", "bedrock_agentcore_client.stop_code_interpreter_session(\n", @@ -514,18 +349,9 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "๐Ÿš€ Starting AgentCore SDK Code Interpreter session...\n", - "โœ… AgentCore SDK session started\n" - ] - } - ], + "outputs": [], "source": [ "# Configure and start the code interpreter session\n", "print(\"๐Ÿš€ Starting AgentCore SDK Code Interpreter session...\")\n", @@ -536,7 +362,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -550,27 +376,9 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "๐Ÿ“ Writing files using SDK...\n", - "โœ… Files written: {\n", - " \"content\": [\n", - " {\n", - " \"type\": \"text\",\n", - " \"text\": \"Successfully wrote all 3 files\"\n", - " }\n", - " ],\n", - " \"isError\": false\n", - "}\n" - ] - } - ], + "outputs": [], "source": [ "# Write the sample data and analysis script into the code interpreter session\n", "print(\"\\n๐Ÿ“ Writing files using SDK...\")\n", @@ -580,56 +388,9 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "๐Ÿ“‹ Listing files using SDK...\n", - "๐Ÿ“ Files in session: {\n", - " \"content\": [\n", - " {\n", - " \"type\": \"resource_link\",\n", - " \"uri\": \"file:///log\",\n", - " \"name\": \"log\",\n", - " \"description\": \"Directory\"\n", - " },\n", - " {\n", - " \"type\": \"resource_link\",\n", - " \"mimeType\": \"text/csv\",\n", - " \"uri\": \"file:///data.csv\",\n", - " \"name\": \"data.csv\",\n", - " \"description\": \"File\"\n", - " },\n", - " {\n", - " \"type\": \"resource_link\",\n", - " \"mimeType\": \"application/json\",\n", - " \"uri\": \"file:///config.json\",\n", - " \"name\": \"config.json\",\n", - " \"description\": \"File\"\n", - " },\n", - " {\n", - " \"type\": \"resource_link\",\n", - " \"uri\": \"file:///.ipython\",\n", - " \"name\": \".ipython\",\n", - " \"description\": \"Directory\"\n", - " },\n", - " {\n", - " \"type\": \"resource_link\",\n", - " \"mimeType\": \"text/x-python\",\n", - " \"uri\": \"file:///stats.py\",\n", - " \"name\": \"stats.py\",\n", - " \"description\": \"File\"\n", - " }\n", - " ],\n", - " \"isError\": false\n", - "}\n" - ] - } - ], + "outputs": [], "source": [ "# List and validate that the files were written successfully\n", "print(\"\\n๐Ÿ“‹ Listing files using SDK...\")\n", @@ -639,33 +400,9 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "๐Ÿ”ง Executing analysis script using SDK...\n", - "๐Ÿ“Š Code execution result: {\n", - " \"content\": [\n", - " {\n", - " \"type\": \"text\",\n", - " \"text\": \"Dataset Statistics:\\n==================\\nTotal people: 5\\nAverage age: 32.4\\nAge range: 26 - 42\\n\\nPeople by city:\\n New York: 1\\n San Francisco: 1\\n Chicago: 1\\n Austin: 1\\n Seattle: 1\\n\\nSummary report generated successfully!\\nReport: {'total_records': 5, 'average_age': 32.4, 'age_range': {'min': 26, 'max': 42}, 'city_distribution': {'New York': 1, 'San Francisco': 1, 'Chicago': 1, 'Austin': 1, 'Seattle': 1}}\"\n", - " }\n", - " ],\n", - " \"structuredContent\": {\n", - " \"stdout\": \"Dataset Statistics:\\n==================\\nTotal people: 5\\nAverage age: 32.4\\nAge range: 26 - 42\\n\\nPeople by city:\\n New York: 1\\n San Francisco: 1\\n Chicago: 1\\n Austin: 1\\n Seattle: 1\\n\\nSummary report generated successfully!\\nReport: {'total_records': 5, 'average_age': 32.4, 'age_range': {'min': 26, 'max': 42}, 'city_distribution': {'New York': 1, 'San Francisco': 1, 'Chicago': 1, 'Austin': 1, 'Seattle': 1}}\",\n", - " \"stderr\": \"\",\n", - " \"exitCode\": 0,\n", - " \"executionTime\": 0.0170438289642334\n", - " },\n", - " \"isError\": false\n", - "}\n" - ] - } - ], + "outputs": [], "source": [ "# Run the python script to analyze the sample data file\n", "print(\"\\n๐Ÿ”ง Executing analysis script using SDK...\")\n", @@ -679,27 +416,9 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "๐Ÿ“– Reading config.json using SDK...\n", - "๐Ÿ“„ File content: {\n", - " \"content\": [\n", - " {\n", - " \"type\": \"text\",\n", - " \"text\": \"Error executing tool read_files: 1 validation error for read_filesArguments\\npaths\\n Field required [type=missing, input_value={'path': 'config.json'}, input_type=dict]\\n For further information visit https://errors.pydantic.dev/2.10/v/missing\"\n", - " }\n", - " ],\n", - " \"isError\": true\n", - "}\n" - ] - } - ], + "outputs": [], "source": [ "# Read a file back to verify\n", "print(\"\\n๐Ÿ“– Reading config.json using SDK...\")\n", @@ -709,18 +428,9 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "โœ… AgentCore SDK session stopped\n" - ] - } - ], + "outputs": [], "source": [ "# Clean up and stop the code interpreter session\n", "code_client.stop()\n", From 7d9f8774bc4c2a44e497a4400c08453a2fc86009 Mon Sep 17 00:00:00 2001 From: dinsajwa Date: Fri, 8 Aug 2025 11:33:27 -0400 Subject: [PATCH 08/11] uodated docs --- ...ssing_analysis_using_code_interpreter.ipynb | 16 ++++++++++++++++ ...ile_operations_using_code_interpreter.ipynb | 16 ++++++++++++++++ media/agent_core_code_interpreter_arch.png | Bin 0 -> 175069 bytes 3 files changed, 32 insertions(+) create mode 100644 media/agent_core_code_interpreter_arch.png diff --git a/labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb b/labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb index d2f477a..44210fe 100644 --- a/labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb +++ b/labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb @@ -26,6 +26,22 @@ "Let's begin! ๐Ÿš€" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Amazon Bedrock AgentCore Code Interpreter\n", + "\n", + "\n", + "The Amazon Bedrock AgentCore Code Interpreter enables AI agents to write and execute code securely in sandbox environments, enhancing their accuracy and expanding their ability to solve complex end-to-end tasks. This is critical in Agentic AI applications where the agents may execute arbitrary code that can lead to data compromise or security risks. The AgentCore Code Interpreter tool provides secure code execution, which helps you avoid running into these issues.\n", + "\n", + "The Code Interpreter comes with pre-built runtimes for multiple languages and advanced features, including large file support and internet access, and CloudTrail logging capabilities. For inline upload, the file size can be up to 100 MB. And for uploading to Amazon S3 through terminal commands, the file size can be as large as 5 GB.\n", + "\n", + "Developers can customize environments with session properties and network modes to meet their enterprise and security requirements. The AgentCore Code Interpreter reduces manual intervention while enabling sophisticated AI development without compromising security or performance.\n", + "\n", + "![Amazon Bedrock AgentCore Code Interpreter Architecture](../../../media/agent_core_code_interpreter_arch.png)" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb b/labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb index ab5fc23..9bab1e4 100644 --- a/labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb +++ b/labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb @@ -27,6 +27,22 @@ "Let's begin! ๐Ÿš€" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Amazon Bedrock AgentCore Code Interpreter\n", + "\n", + "\n", + "The Amazon Bedrock AgentCore Code Interpreter enables AI agents to write and execute code securely in sandbox environments, enhancing their accuracy and expanding their ability to solve complex end-to-end tasks. This is critical in Agentic AI applications where the agents may execute arbitrary code that can lead to data compromise or security risks. The AgentCore Code Interpreter tool provides secure code execution, which helps you avoid running into these issues.\n", + "\n", + "The Code Interpreter comes with pre-built runtimes for multiple languages and advanced features, including large file support and internet access, and CloudTrail logging capabilities. For inline upload, the file size can be up to 100 MB. And for uploading to Amazon S3 through terminal commands, the file size can be as large as 5 GB.\n", + "\n", + "Developers can customize environments with session properties and network modes to meet their enterprise and security requirements. The AgentCore Code Interpreter reduces manual intervention while enabling sophisticated AI development without compromising security or performance.\n", + "\n", + "![Amazon Bedrock AgentCore Code Interpreter Architecture](../../../media/agent_core_code_interpreter_arch.png)" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/media/agent_core_code_interpreter_arch.png b/media/agent_core_code_interpreter_arch.png new file mode 100644 index 0000000000000000000000000000000000000000..209d0b8eb28bdbd3b43213327031261a1e9c59a0 GIT binary patch literal 175069 zcmeEubySpV7cbq2NSB~eN=gbyC`d?1iZlug-5o;IF#{iPT5ci#P z&i6SU&t3Pdb^rZXYnXT5cw+DU?ETxn{mcX@DoEkuP~spVA>qqNODH2DVZ|XKq0C}o z0B6$G`iY?piJ@=6Hj@o zC?4mJc9($Vyizzd$7g!AiD~Giy?GVpE%{OuSclRG@G3k zF=no~ALONs46hhhh>m3%yVrf-*wnk7<^Ce4uBJ_}KS_*QR=Nd~-SXA4!(YfRq_ur{n2HY=&Rwny}-X?7I-9ZD#`3z{h7^1BjZv+ zJyAO?T4fQWYZ7o`uOZcLY84oDBV5%xooJ?{6t(1jnp!0k^rc42xMlI4&D;+(tuG)V z)X}dLfUw^iy^;aR%OgDij@Joz@OF?M zy9fe@mPQV*>0B%=tn39{gzx@whahkaKh1HM?vGm>%!Tj1l2@b?x3)8)<74M!=e#R| zLq|vV*v=3ns4OA**WxtRX!$;$q( zX#o@DfM4Nw$j-^}-?o8AAH&ZIDw?_&S!hU@S^_cy`Visg=6d|c{l8uL*(1V}uYNws z#l^{q_$b1qzdx#GZ)7KKZ3*=0Ao5dRe?5$N@vjFTbHJyLAc{Zb{Kr{9&>}dGIsU6O z5gdBv=4e2V)TR z9p6#ST6B@!`JD_}Px6_WUnz#XZ{p|S%jjT#`0j3+zV*k=G>{`M z$9*61F6~Y5j`O_z-jtA#vy&63W|r;RH6?LQ$bR|)v>RbKFPy8@Zxw)#jCNBD>0kWw zxr;J~6-WH>H9ra#oj20|`=2)voqk*Bzqu3M*i9C(7W$Y#zkk;@5(=7J9p=9qI5OHB zbga0~2R1kV#Vp~scza`?{)_qjaf?_H5_06L+c)n1%X!}9T)O?Q#0?DO9SNQOt)BKL z|58E7D2%hH|5kkEfB@gPK6>{r=PB0W@BVL$1TH?hm)_notOS^Z|8kyKaW(}1%AkO; ze#W`U8ML}Y_y4xOKPQGvvKoeZ9?dy%|3D0xZ9M^PpsBzE@6r15$5ksGR+hXD~_ixdqo1# zf2NV3VFgvSsjX4>kkBP5m(U&m?&f{^f#4#TScdtFIybnGQRsCfUv#oDieY7cVSkhx z{M+1&aBp(nPyB#^*dvn9Hxx9<_cJzVK9YoVA;Zk-?`?mJwqm?Mh*wpKtw9`3+A11W zRMjlQy1$mH_n=I$u8RWIFGCVT@*yRq(;sdlOI49VaQi+7=vYxv*3!6GJbp+%k)XQw zNvOZCR%{>?jVzJ>a^dDJI>lQkOX@)ICZ@Bw)zzl(n_x}!fjEQj@ts@{@ySsIZ+#hP z(I&PJh=|T&*g`$(@Cm|*;KNRX&#`LFalPo5N%|xCMBEaimPGOyMM0BSj#NVa+Z>V6 zwxofGOrwJr;EO16oqJ+&S?05wB*B`;{y~l*@Cv` znOJlml)TeqBp(&r`FkJ~lLw4v)IVMUQIl!9XjsvsJ<1U_b1Rhk!F_0mw)GVFNC|v&v{m86Lb&gb#u5~PArR@RDqtfhK=P)^k)mW~ID~MQ zu;ZQBJsci4>CB@!VN*LIg?m*~C2i^{!Lg^vu}Jgy1SjO$n z6%GU8@V@Ym>Szf%X}A4|jvq13VIB*XfFM!=qJM9xK@(_6T@U#jAqb~+p>46Twz?$j zU@SY_%=;)N{FZsklr;qcwB6akf*^xG6{ac$wBe|2iXeTT7~;HKPAT=UY!;YR7gczq zWLf}<4o%U<)|KDrF;C25olE#nv-<03j1XA)H0qkqe%dmCBew3V4v`paz8BHjWnA_r z%oF!1!O?I~gDAzOrF7il6*)oB?Vpw|W&}uCwd3PU1Y0mM^>M>iA9bUy-YreBt`M+a z8$H}7*CIi0aNg7XGESAdS%RhE@GMnHJxjNPkeBZtS_L3YoN9kW3}UM?K&u*~cGT57 zg_QgxuRoaO4TBiaiC>wl3_fM+)a_~hR$|)}a?dJ+TKwmv{yg$I0NRUMm$F1qiKb@S zZC}!AO7Y_JE;n&gk7JRJqIF4OwD;J|ql$a20h*zuFE5E3NvHJ)4Bk=Eb|MVr&qO=z zG2nSt&y*0rmQM^xULdD9TZdw>NQPBLo@eICvoRxD6f9x_AqO?A4ART-jBw@Cm%<_G zqvQf%ziDL(7^b0pCMkm2**FHHUk93<>OfE0`Cq7dDZ$!~f6ySK4dY6n(`B4wTMM{` znw_dcPh^u#)lc=zJg5<@>#x~c0A9MHUJ3W-N&p7B(Y+{M4J&biUFBe(5?#&3E(11Y zcJC&h1lodnLXyd{5wL=ZD_||+s+9|7HBjp~ zKbK%lU}%7m}mR5%%yawh4RTJTdN;wn92Q1wGnO}s6&t#@*7)ufI;xCjgqwvX zI}vV}`DkKLkE&UO+6CvcM<)fL=SmjB2MVqNW zRSSyi60=CbpN|I~!krqa59YB_zUPCnFi1(X)j%!P=`;zrBlI62?FU`JHMwz{Aqs*E zrTT`TY6IkXb*|Hcv6rKLDt$GX=LYZiRz-;wi{FgiD!m2G3w>Ti)cm?&2visT z{BmB}pm=r-t}wd+zw_eziX1-v&%?!`(KDd><4YQ~d?ycg+tTVC z7Axw1aBlw~oE)+|BhHLl;OLov(mlOO-9@`?rp;PXFg*pP(ak(#AI-Pq-B77xmLW-o zl4teH(%b=>C($77D1>nEm+x=^7S4AOYJ>|woG_!%&y&zwtw}$RHp$fFe_n4;I9_K$ z<|9d*_p&T~)=3SL#Mhlq^(idcjA;@y3e!9$8smrk=0WKifl$BWG>T}8n?U0rc;i*+ z4XdLQUpQ(#e_#|ixG$ZY0bWlKt$@&uxmJE`U|MqAjpnZ9-h(lPy1D(m>Mf>O4#@90 zj}v^|L-T?V*8K?f=z~SYAkS`7gZ?A|Bb`RO^Bd0ET{Uf%69=atfpAukj_a<@*@tEp zSifEJ^RfJuv#61ntr#uHXnl;JuEKTp^o`r4YND+ouD{3CDIj?@tj%OZFadc6#lpaq z=nW=ws)E?+5_Ts6M?Zeq>}l+p_+zkeD8sfuR`JC9^_|eIV;|3^&_d15BwlH&=w{#3AhRMBu(xS%b!lLr^d|030c=4@{9$RL)veiOZk4MIp&Z-;@KV*m1vH_N*Vu%)02=v)2vzt`_`h+V0^qgBv6@#JW4HKf#Vg4&gRbehtQt}%GQ#ZF&12VB%bl7r<3gx)V|txcOTlYeO5&@$!k530$L@B z+JgyW&9y2^t~;&XxL4v8_B$&61Sr~tHVjeG zRVW%L)TR%+H}6@_(Jw`KU2f~Goo|=&hl`x&#S1zXoNX1B_D<+|C7)D<#G99#2%c<~ zd3mRJjiuM*cHl(+SnpIKP`GTa;es-N(I>=PIjmkZLuwWTp}~1W4?FHU`Zi`R)K+hG zQn9O8c-3iNIa^0Br1SfH#GEH3Hog8L`iaA$L;axit^C>yHZ}VOcZ2r~m8<8O(}DE? zTb25QizZ8BSaZLl?|OtNh1{%Weuh* zPPgMx#4uSn|({TEW1XUpi&A5G}1(^||HU!z%>Ny!_n8Qs*& z(idSTnuLvO@$OcYU+>~3rU<*mT?X8E*dG3fdEr?(e*^!$y@59*J6Bb!e)7>O{F4f^ zFwG;v3DJK5-*te5zwuEc;IXuP^kIxg!*8NSJf|CE+88LWUuszz>3V#Bb_lIq$PuP7 z;8BtBtcf@|lRom2bD>cLDJh7^)n}!CyrH9N(=?p zU%@^PXsm@aL);drs!KDiK05n+cZxFE%gt;I6ZdgOH#@61_abA|V%Vu5W36b1Iqj%*S9fgh6R}b8=$H ztFt+bG4sSU&F9i;)<>8IzlJu5UC$-V=j6cp&>H2L(8(%Q_$Gu zt@;`e&UThrM=d#TN?7C#dNhx>VG}0hi*eHnn9gTg=r9~lQx3xNfnA)FO&n5UmLJ*0 zG_@>my!NRt<2(j@zMa)7DWJm+7}|^9dUBkYOI?P6gifB#@edK4gOdS%aZ&s7`{JYN z)2Z07Od6Ok!yGK^>B)qz-qUB$@~`cLLWfK=k4Rm%sk+IwAAhK6#@+g%)0*6R3T?-S z+CfO2R&e^`Bf0fv1`0}dik>fk`8%OAu8Tc%ieOYxJ;V;W`B6ZvKGajwTtXu+5 zS8})Dc9Fpift8z;3!bp}>=?}l&`qV=fQQekYv~;j;eP?yC$L`)txZ_hb?MqTEPk=4 zyEc5-&vnT+muKkJN6d9cU-`gJ<+m)D9Z33z8Fn1rBcP!P>@0j+LkiPzk>OF5sjSt# zrc+(}gD$4T(|Pv+F!xoAmX+;N1nasa4S+$?5>RRvU`+P?b+Wxosr@{MCp)GAGf+c>@zliFzB z1*TKW(=Q@QZPAeow)-YR(l1RkD6PWx=v~pVujOaB_1l8&@qXqGe;s82SWTj_jO^zH zpB!Ro%I>dj2dh=7Iab5jwjPO@QsI2}q{19>$G|DBz0M0Bk;laOfq(#2b@aFAZt#>U zc#a%%8^Vy+3UECW9`R)SM+ih5xmI<>d2r;Vn!59Qz6+FlY>$4rZs z&p*7oVl@;#3IdG)MUI;Lajk}0RGbta_xMdWo~{l@^*MhykRKLN=G%U+UI}GgPKPVy zzvL(U=%3(~5n5Y%@beTG0ZXRhLOauLi~;MT$#PH@h_hVlba$@LS??An_W3?w>RYmD zZ2AW=R8cWsu`%STxJ~!}=r!X*)g`NCuE0bQRE6ND#`}6jz2txpvnk|uYR`_#j<=`u3sA2FDn&8HLf}T@WG@&pWOPwUrd!%#8 zbf4WAj$+FVD@|PC?TL%1lKpo?%qyQaOTo{R6sK!OqSUA8SC_j(D002K&URI?<5a)B zAHe{uS4BT@j)1SUX-LGcEA49SJr@cirE$UsnQ>03aW@(2!YQYStAC&mECH*nBSOeV zOptx*_Mg?~re;I4997w6rj(65TC&_>c#L|U-(Bl-<-u^jVTN*f`lt>Zlw<{ZhZ~oq zR#i&Bgc~iR-0vKbVEQ5iF%IUZ`>UGEvdj*Ifo28QbpFLs>{MJ9r!^BNBFt-Zg6OoU&hiY4AD8;zY} zlTgvD8zb0kl|Nxtqf=o04>M0h0`~QwAsBOlcA6^Ez3ok7OMbwZS=4GqZIctb>%6-* zaA)&t(B(iz!j@a`0Hj8?A$p{ExJ6~%=;=gZg37p_{E?Yj`GJ4>a(4HRNLkJP*a`C( z!PaxzJwVagkbfW*ZUA$qA^n8lx6b~bKhL1jt!U*M#!>d4! zR5GrckMw7ah{^|jrI)$ZU@jc9iYZt8wM-Ax#9spY&V~!rQx1RDb4;*(vV6j-$TgWN_6C&8(H z{o&%oDI+0tIR%!D9M!>$QCr>|E}0$@hm^%wdMKkwj><=~>Y4mZJRy;*9|ngVRMvG~ z?QW;LmH-5=#w#Ap=`PZ!>}4ta^oDpt+p2!G_n?O(bwf9=Z({?Y-UN5!X(@mo9X%X- zZGo`vqV6a@c=xe-6BuoArd@V7NFaqEZW6O#4oZL=S)`uo282aiWHL_IOiPT&Jnx_m z#RnEjTXoaYT){GBrBjv0iT71<;^2tp;uLbtE6ZB$uVI=Utu6L?w|@QQ&_ztvwa3y4 zzu9##N3%_9;Qqr)-6Hv9sZ!mhjDk^%g{ISabIDLjqh{Ey5Vzsio7{FQUq`{)3$+BI z7lU#skEcA2hNM6MDCP#9uhVcGQ%iO%KYTGW1bj3Jz^8&=qOzZS9T;yAEh=?hnlh$i?&UnVMHd(`>{EOK*Kh56&Br#A3zkjsIkLU&@(^PNX+YZVB+<+_K z2KctW+b%N^_v>@nOOB7^!if$=M<05?+uUWhx#`@SDmLsL^puF;@xFKcW=>+S(B3Bl z9@GwXJ+RSQva>GP4*6r3GTaBO^(zt0o0H|MxNqlY-L{L@04+M)i5yIy)FW{?Yp1=s zsg*O0X(<<-zw>BTkqQJWZvD{In{JLW-%u_kZ)2S)fr5tZS%zbWXhQ8?VCx2Knust4U)jj zC+bEJ>w;<|%p{+`PP@i9MUE=PE3bA7D74UTlfccu;cbg|Qy5Qa(`M|~O=EVU)2fX2 zcer++X;l0Kbqhj#sd_jdpN8JcuZv9A^Ud5jWtPJMQAquE zNn>x?6TbrkwlSw>eHaxRw>=D6+c+h3@erN!>gp8Im~BwChbj8QN8|Ndl8e`;4ZU1F z04Qkfr@W5f)j#NFDUF+RUYKJG&b%2E(>`TcJJ(y$!*0&w-U;Z}yecac#780IR3vaT zDBdGd>4qIAmn=B8Q#l|axlAGAQI{z5>`ugfj)pAYI7lnUHO=d}wo4(Nby$Drdq?3L zfI`zYG5%{8KzRly3dg{ZM)p63(IYVb9GOU9Gd)XpJ;9uPq|$hxD7MjbyK61cy7}QX zY~gyg{dk{$E%D^DKzRTmd08!f+^6qhRHY!!`kmQt+n^MYvEg@HmBb z%KQ?A#bv91SOSQEojfh@`n++t_ZFZ=A5m?tPZ1pChikxGxG`xx%FgEYJlCHC!T^KT zlbyrzhj;@k(By#UmSfNazrDLRn^tWR8iTzxR;ZpEsYot<=eDT0TrSFer%~A!i5@m+ z)ri8{n7Uple%!_=Ph{9r1({gK=dDka{<9AL3ZldSN>kQXB1VvdB;lZ6vzdkIjJ&2U z!PK`JW$0&`iE>;fZHs>Ej|_HID=j4I+DlIvd)R7|rhxIW9Efr4p7TtX!@Z~bZhhwk zV6JuqE#X4Dk1`qfCU54E`5?O!^bF`%9M3av2RS-?`9zd#D@({fPJ()7e4DEf2%ceT0J?z~TNb6~$ z>$b^|$tQY!VP8u;{4JQnif_Tgex%WP&zH{+!tBE1dSJ-E9dbd8qr?fS9(&0RM6e2~ zRA1S1Jdv%u%#fJYAy)Q;WTSc4b;r~AlE~EjwYOxtoN$&?iCnQVj`h=iTkEd2zT)Mk z)aCt9~OBfh1y@y#3chK)g}X4o@Xl+7eh zV7dIvjm9flpndX^DAG#kH+5s`V9`+o%Tf5-N&YESOd)_)=j-KXu)}z;;ANiE8K^JE- zPxP%mae%eM~aWAqXMPs0p>^?dR9J z)OKI*B>S>8v+4d+D&YEo4L1NpeXgEp{}VQvP#_Dh%+as*V8?DyFQ4ysvM1RD zQ3yLF-5cW1*J9AGXvLta3k+*zU@NfIyL~@ZH=kOCf^C=blY}1vhLR9-iRHNBf*I^(IB&o)$^2q9bBbaHuR`ewCr<52{wwI(L5Z1mi7+=SgL1{bCSpfC!c9>B)?>Gxw{! zBGArYFI&PFai5!gX60RsiE%)rT|mF-F$b*P>e94CP=fL+sbxyQ*`Iw6_eyfOxh>z-{-P*Sel(M*P7sCoH4s=5$_cm-m>?uFfYQ?B{#6F-|J)jE^>k zN=(r;iAj|w#kn1zKwN5;B^w+P(GhZe@G9eQ^pVkYrW660MLn?XXm;m*^EW>Nk&WFk z{soVbw^|MgIJwNK8MF&2-K7m%1#1o4CHCt(+lL4Q8;~$m6?tFM>LKD;x;aVXI zw)YXge8uxeJMG%F?q1`b$f>pVD6fhJl}*=_*gF;oU`?w#c+>(yFvcget@nX&+L^;9X!MttQJ&-0PgH3wF!O754dI|uwFCHPNPEM74f z$DZ1r1}DqV8ylC}b?)pi2vF5^@gGk>u6T{bsdpymVcDHst#eL0!e1xW! zJ7C3vv$NVXwzmr&Nv$CGwksVW6w8J6>Ov~d75D@RLOc75A}XJRCzS*zt1+G?E?h7g z$EMqgUjOJ6opn)rDGJ!FSAT%?>~RHhbkh03%=P8Mbv>8ekmG(Le&EW$0*0)eL1$Q> z=|#i5Sv!V`ws5G-=`)*=>X_j3@%riPa67)Eh|=p^(aZa_-`=w{EH^Mh&c{mM&pWp| z{jHkJ(}=_!H+jv_6#$sfUsRJAH}>Q;p-S|K%#79W!O4=rj?412jN48|Ft1(-Egn|U0F6CeOiTvRg#@{!&6JJ>n7FW$(@-z#bk+1 zAkE-X>mTN+&BY~r)e=6_`-#&NtSBexwL{et5xBUlW!|tMWt{0;>r$BYG|bIy?BUq4 zfhz1A^r^YH)VdfmRupR-7c#*=;wQV372hhco_#p-qv_-H#3P5>b~xefBA@sZHlIi) z$UN!ft@IWpNRB<(Od``T{P;DVel*s@u=q4V>4}1%^<_!TN|6S}Mb1MrNJ&tV8s8^V z>Ib2NngaIzUpa)yl&!I`4=bQb?Ufk2U@?Nx>5$pa$3Y>6Yn+ti}N1B=KZRiPP0f{3D zLVaT+UK{q$xZj!x&|QX_o&nL2%?cwD$m2%(Zf9isI)JOAqY$fTR>eCDbD`n^RL|5hjx9u(zQp0X`uESOs7XTzpmEu0jPj zvm!(ASL@t|PV?PWfa=tP^I1i&1oUTGHW-oB&aHd3?Kgy0 z0`Mrlfw0O5eC(xY9969fi)*?qfo!`Tj-f)v`x5ryp>b@X%|ZpiMDV%rHXfr& zj$GGNV2A+BM(Ff%&r8%8l~(|^INqmO)`r8f0>nAUEP#b{IjOwQy#Tf8(2R|Q5L`Xx zn|E1G0?CvSh}b7wwz5ZltRxT+!k-F|uscaFH5Xr4tnJ}niP3SInr-nxwLiJz($D_! zWNidXL3-Wqp zmY;107(``aXbXnef#U@g6_)LK5d}7npD#%8gW5tfcf4S&4Z{@Ab5lLG>jDab zKY)t}53cVn(OfuXCxvm+$C0M#of&U%ct2ay_mdOjz}d2q0RZGH_i4yE%td%uZCjrK z^X#Vq5Q~))<{VMN*j=-eThVN%3t2qeakF+kY`c41SF~UOs=L9T*bl2;VAeJIXtH>oJy8ji2DB5c*LnUhpfUh{@KZbNfw)#;!z z(F7yHCh=UyD|~AL!)A89dAjNeS?vHu^z183PU%Ji{=TwoFXPb|{J0g1Jk|=2Q9Q;5 zT~CZ#mqIs_ratp$oP+^}aZ&Vu&t{oGfJ1K~!1(~?XMMC6-;qNL?%&!A|Mk1XCXUEp z7T;gaB}k{u2D&O8AJZ{GUqoO=kKx2^+)>;Q^p?56FEO_=rnMm$^2n3$LAf;P!W7mX zLDdin7E_RYhE@ybhlJ@y#lH}}c>J(-B|>(M_G|t;5Ont5Z@1>5kb9nCSLX&s;i%^g ztj1{dH*GaNC0vs4A)2&qXw1eVW*tIR1CWx)adAtaFYXnxS)J$pX%R9}=#zCrV?&!e z1aBD~ngubT6OWGQc&KkFxpW9Uc$MxQU@d;zZEu@JpGceX>2l3NhBHADm_m56hTHWK zhfw>I{(jAGTRrr3rmh4O`Sae$sJ?**djO@Q?yP-9nY%C#KAqDBxU$)Yp*jTf$Aemz z=idwZ;n2HqMbDAOp?I`Z^u?G8xzq&s%ZU+_!(}_cTas@|aWbt`V`KGO)JKh5TUUdq zSNSTrYcTT;eoN#?n!BistNwD&!E20XvoB37KEESm_fK=zm9@QZlHQBSQXRY=pso9Bf)y(Yj^* zLbjUOCY{0a$T{=jnQfPNVlq1OwQ~;A`xruHX|^Ak14P$TnJsbG9;~FCLrvLUxS!H} z#amA<+^+>nKE%aj4do1Yg(|U&!lnjho0YGX6j~%s(?!%r*MX#poB~Vv8V7xA0WW$4 zyj{C^OW+F%Bb;|D%xyrE`$S#(pFj=|>4RH9machuWAWlA=uc3f>+@^OGRnM7*#VV3 z_@GSC@LYJOSI|#}Q#<1n9Xaxg8}A%1LCOaZ_R!94H@X~5h?}>%*$7xaHsmXf%c74! zpLB+`<@M$ZFFVm#T{lxJAZxXUZl-^uvi_trm_!hlCn}7kHu zjTZ+;_R3Z!*0eCuWrx}Mm%pF+f>~P}FhG#&s zr{@!W1+Nzg0y&c}X4gUa1GCDhb58L&vvCg%W*3r5(%VEEBq?Rfy@(>Ngj9+EA|VLW zyZ=NW6bdkx_uceNK-AGg3*S^;2(NaV=G=jf3=m3DD;-s??6(s*zh*+`KXTrw1+o_p z_XM!Gfr^*NVESO}wTGg~fEORkmdss^PBN8(Yy2gv{+n3g?kOYZgBxF_M?5cS%t)!` zRHos4q56yTS~w92WjUr@MUy+4+Rz~;9)^IGW{hHW)_lz<4_^n;#Be-qp?}dzg&2dy z=%RtjyQ((v&x(gw+7Bq|75U95=MbvnA#m#;S zX5IB%W75CnO0<2a#FwlB-%9g=(P*C1nRV3ykl41i*Xy@rd0k)Cciwhq&DCH&g4$K* z!W3jhO$5k=7e_*f!>6;YJdngY$7)fSaR?;P$d7vmwQ=F-=6QwASi6<3njGC!U(cg>a$~Z(B=Wp0D0}K*2e*kk_w90Ogg0Y&MmY$H))6 zkQ8BCB`~xOcw_|rNC<76m?(^SIV7$-hL0)duwaryqGOI8#8c@nsy+tZ%#NK6xlzyv zP><-1mJRTA$SsH|R}la>sND{*({`oUX%47jhFw_Pu&`Y_K0k32$Az41gO?t#DM?+Sk}W^QlV>I(#y4o1*kO1*N~DyS@lY!pmC;Nc z1n|e*3#T}$%=)?VsRo*F|$*)6Ay;7c1d(pF@WwTIjHs`_Kq0 zZsq>p%2ina=@9WgxDCNqjY7o616^#7zul&mqzQJH-VBd+8>ArPyidwLoqBnuoGw^Y+dwv zYV*l)vh#er9!A@MErh%O z>wuNAMLb^LtIY2KsEh#MP0)HT)k!?8RL5{pU)96jSZCws`^c?jwrIxtGOUZOX;Wd> z_!T}tbZDQX4z;R-Z=2`b5^W4=W6+#u-Z~UD)B7QqbJp|Ldh=@hl6;=HWw7Y6h<;{#%Eil2?;vZfo`t*@=nRr!)SJ%h zHIr{}`E}9dh|i@PNO0rwn%g|*r6wM4=Bx%8<__%B0?Fsu&DYwOfGW)j|)26wr?OqL{7m_WXW%qw29kPI*C5h+{9!At+lUM?#vrLbqFlWdNnFpuF4j78c zC_70Z7xT3b@@lUE60nNL*>g)^>&2<>?N3|jTR;qtXgpg>oKwc*fLv50lC*F<`@o=| z!Ju%os3eL*XTs>e4|uAK4?bQSg=s;zOTuVHn-`_H-~6!7W9d~gV`0+IK#GjPb_1#j z_Rh1mfm#9L+;or6RwB;=_cTx#Jzs_cwo+LMCw%)Y=Nu>cE|+PqHyq5y@ZvU>ia>Nj za_E}f(z0_KpLkW;fLh{e2MPR2V+`r{2~Am?XO=*G9e8{g;{K@k9H=EUCg$JqDCVEF z5kD<+DgQ#cU8Sv}su$W&%BjyK$n}VfKUhXvtaQ}a`Ukg`<>p?2^WHrBN?PD~1KFs; zECf|B=V7y@@s1Lw9QP>wB|p)fG9Yj!RB2)bae5R3LSgIen0MCQYRq~`OBR9CeS~pxDxvm-q z4nqjH2CM<#f%XgV@D>w<@2U929E!j!=-{H!cB^fH^D*BoExVmY->>PNXq5syGZK+rjs7dEF16=+b9=5RC(N{fDitVLF2xF^vkOz=xrFu-g zsqFfJd9g$xceLL;S&hG^K6!^6537;qd~aqNu_GMKaJiBx)5_p3q!F`myE88Xp z^rP59_aC6{nSXCgpyfT{`#R}-qVLqL2(3-wnwh+}ZAa6C7xpK*sW z-%KwHz5@B(iPm>*ihUcCG;F~Fxme|xW2MH?G!@7*P6dynH}dqnC$M5R;d0y>_24ep zMs;B+D)YrP%5N4S8?BulIvz2$vFV~k|K=gAL)-WH8>&Sl(~Ev+S^JGMFH9cUXgrdB z#l(b}hgkEsA%a`3dG$rrsyzZ-&I`PX%^pL9?1QimBJe^|bwOeXUAN{HtV~KWlxQ@&B>@{Cp4m-9{=y6K@@ zeTb4Z-?Yg+1LJ!SPqbD#`g!!nfqKD48`K@zu5aLnyawQ@hkVj+#&z8-4UBs^Pwo_# zzs575eUKm%$w)y(&BdqxX2*i+44$q%0tyWi2ouPU#DC%niS}Q7;{E=_N6XPTC=Y z#6OZF&wYYiQ{H;t0F`m=6R>&DDF$Et@(Q-c?<9G{KwPDR{iDGSD{J%A#Wny%t*n4* zI{yj49Z|p(Cnl{A8@Kgo@fy|RCoy}^1lt)T-Lav}K0^@4o8Mhb$P^oNJ3*RzwD>ubQ?2JVxky1fV2X0$npaCkY1+1??d z$j9fh7&aA;-CNSQyESi~ia$nv*(7NR+*;BGU>S>J!?yYdwZhE)R^Z$Ei}3B>m<^XtJ8fdut*3Me?Jt6uO@ zR8-Ho_#6z!^}H@^q9+%itdbo8k|Fq&b#@J<7zDR><}o`rsmf)u$%PUF%kQAp%BE5Q zb?b)>qENd>R;&$PM;hr6)5h8G(k+oa1(7quLx8;Ph^}p=H1H$Xwbu0tY&adu72!CF z&vv>5`H@*cuB=uwA&EeU8=hQ!6LKCreN=B6``OOus6I#Vym4?nv!hVtn4$>6MAHbA zutV|tRuYFjll`X4(m9>k1@%I;yoC7tS4D`9>a!r5@IvQU^&L8c*i|mdN|E*Bsi9Im zQRn1i4P(DMB|6}%X|d4qC6dg6$DvA>8;1&_=hVj&n)ut?il@Q&nRO3m7L%!hr{hGx z4MMr*rA>TZH_knjT!qBL?8l62Aur*zBMNR%2vw;L&eu61O27UwFVIFB{`n)aIi~a8 zvEvBHc_p`7{dGyY7j7x!>fCqW1=T0pQ}12uxkx^Vm2;e{K>YbQ8t2&|XvLDWbFu?% z<*yq}*x2;11MeYz=^BeC$h#R|XOs3?{wS+78^kg6U70(MII&3rR@tj>X9aBWJ7k+? zscrcNPFOLMN(}&~#iSlT?IA{sMSEBB(SBiP0#NYvqkc=s<;m{M*2&WAev!1Esg~#c z0myc%tRFKqq%9t`R+m~=n`3<3e`$e1q+<_}3yCQm#lkm^U0?WB}rx^~WZf1*b8%^SJwhy;Ca*3g*58d&mmR{dleFZwQ#Y49F{<1fFMT z8;Hrs{!sN;dDL;dIcq|&;97FB7V6o#7Bgi*cFMgIQlsI;L#6VAY4?zG*S=~LPq}r# zmP*2ALUV$rYal)Fc0y{aL=Qa};OU$z-Asa5E}_|%{0|=c1%YPvEhThhDNI55Y^mRj zxv;~uykz-S6ge{kt);H}O=E7b)n*K^QZe3tWZV~%&S1n7Eaeh;dbXaLnyMt8ntx99 zF40tKJw2Y!!kgvVnTWb@e!J2cfD`tWAsh1q)?C#~*!-0d_|e26>c^JejJXhds$0G4 z$-L$*7I?#`@^vH`|xmMf!Fnq2Cisv3QOb7vDSQdhaBF?{DPB z>y2v|)12-Fmu4O+i3)nrsM8i?+D@e&*n@7LhvVg^g40%#Av2o?vg#jdzKy65Oeit6Zwt`Ye3F0K|YFElI$a z;vO}rA%&QZM-eGJYTcN*Y^9KM(5@^pIl=0)DjR;zQm>(Mw8ej!eA}i4*{UXS9}h?Y zxy2`q6hc6(gLte7)#)U)&>N3LME$xJuda>RDF}tNg2Ci^QA|KVgFHz<^0Wq2jAi|0 zdSamtpRxWMpTiOee<*~sA?KV*p>gVFz|g|Z&Qzt2TA$65@keGpj5*_kFEW*{Oh7n& z%IPr6rsHvY?mNf8zLO1B)`G%jhk2*?f{R|#bJ)0rVnUZxWu3%vv4)hzV(Z*>0>!iS z!YNtbl%>Ooox2I>GL>&lKy+Z;ZY^%EDVwnJbiC8rEC?)5WGqF5-?7B$j3RSN=4j8m zU!6PLq&Fd58pwO)A)C}{Qk4}ZIfT`v>X0s-ROfT8mx+A6YyINuRX|K=EZ>~HsKy!} zvACNc{A@kTyp%$Nr}Dk$Xdr&X**B}52ZgquuCHJn7-zGuR@RL@hsYf`HtiLc@jOoo z?$C@J4?EWMWC^?g^K~ht1}ja7RleE0FwFhF!Oih0KSTtxx=RI0+=#K%@VDKPJcR&3 z0&1#ABKui$lg0V`mbJn+%amJ2BRYshebd0q`XW0v{vladE!x~PDkCjuH4o+n;L)e( zn!}O=sKJ(Qin*(F7sGu2hpp=X$FhIhc|7*sgh(iRmDRA5JZ1dhQZgT- z$S9%Aj8aCA>@u=tlSE`>>$~pWfA9Z&zwbMaj`wxk4UhZ&{eIVZo!5DucaRzCwE8FF zv!@q6m5Tcf6p?wFB{dsJIxK+b9T`09oDS)eRxpoKp~C^l_DEsKbh;ja~{Rk^9{?u|E@mB7Utoh5;w-6M$P||eh zJFalA?=>RxvzSxe`!?|;DWL2AA<2u+IKz+CA2 zexhS)Xu~122t0P*SpBVI zWTV#`gZ{-~ArhzJUrYIWO3uQ+STybbOP+#wR2wb4XT8)@k(^W0N=PhNlX@w)-`%E0 z6i@eRdizsW54MgV?mzza2KPqsCW3fU8I;Syk1af2zTZ^dH4^HJ5yqHIk zZTG!arJ^hy$aO(PZk%p!dv>luk1IyFWw;Q>KARav>vfo_gPrf<*1(LbMW*_foGbIL z7Y<~_ffC|b<}FLqyDjvuGxoXPa?hOLRFMZ7kRS=}pq#DYvHd5G7N!Jo_o;bjS*8xr z%+T!2lO8;lAm+EAXz3S_x8m->kh*Bfu!a*0 z3Kw*?cWLr0I`VbyRs0-s<$QfSj`MYcSn6Zt36|GJxBZRNI;z}nj#J!%Vo!me8;QOp z8nw7#<=HfKMkekP@^C!&NZN9J;o6!iShhwe-BD?}i&kz6g)nr~KX`u5G@yxnB zj4`xwm~1uP#t*qt;W3?PL3bH^z{o+Oc*Bj5Lwv4-sXB6>KUrL@C-IQI3o}^W;6Uax z`c+tg*MvyNG#81 zF><4GDE?F;wWYAMq>vOzn^51m$9G0bNhfyH>q*4Ec=cP}Otdf&;F6^H$rMyXHjwJB z(MI}g@xc+zgJLsT%VB`QQq45ZQYr&kd7GAdzTsGY5r<7fT2gg2OdHy>qQ&PO$q=2- zss{6+E@T^@RbeH$Zlv30eJte1co?1HFw5fILzL`WI(DBHB<$Ybm3s9es7FD_D4WB6 z`U!`915x}%E8j+EYQ?8m-g7cf=YqOQ8D2dyAY!uQ{7kiC7@<*5-;t1=t5GrN$eCSn z1LJCi!o!Zt>S%%BOEjz2-x+=5|C&RfJf{`v@KOUg zrQ|V-tOH?=XoEU6nRI7CDlxq&IkAZqP>J-Ah?n1O6)q^8q|DU}GeBADi zW2-)U&Zj4218Huhk**i*{M$d6WO1l`szu2ke4Tz=CL&ZO^`%-_+OhDiGAF(MU0{s9 zH5@j#buR0Wy{7#we5cp9#o&g~MR&AZud$Ch%+0J1Q{7TMvo+0hjliH;ReWctk6OH5 zN9NYCkQD-x9{zIAlj;=PT|A2C~(H7Dq9nUr7dWA9L01AmGz>`2On;L zn$NpmwdqF2LhY~pdhyjI7S*noeeKeerOvY)Vu{%fcxFih-m_&n$I_WbK2dl?%QhJ1 z<-;_KgDo$Rh{EJeM(#+&`)7kvC`Y6CuGIXvO*Mq* z3dL)fdY^26!aF?oztC^4!m`KD;ct`{Q+j1wMHajAgbu%(L4)U7x^1;k)ARkJUE}XdNH0&*KMM#_TwtJ!5_}#+c6I;1H%RBm_&esrQ;*Bt zlZ1<=JRO7s+biYvOGVo8zg;N8zG?g}+K=gPp_$PXS89LlEqyK&NPU=nvB=8feGKr^ zN*093`EZPwt#`LwD^BVU*Ym0v+>zMF`9qG zVd!5X;(3y1Uvzm`)YuC2x%LW4Il4B$)8jGE_?g@V=r`Akg5B>a?fX>jYJ*sy1N8Vy zj(niTbsAZbqSS6b(Qh#e1Dbi@%tB|=Dk8pL=};^MHY7!?VxU3HAlpp6eBlFd`CpTY z3}Wr-LWpKzM|ih&UKhCi{v;^=<>Z_Dn~SuS9y^Dw7t`R&L0Eht_+(g1N>gGJFUCrrp09$}aG!hdCx%9#`B$xv1^Jwcr`t=4>uMYU*CR4>JM(8*dXu&+rz z3eoC2$>;wD8ts0uFvRN2CP`kAMox5AcCPERkQ-Eg}8=4FDuPtT@_q#Z>ElWB6v-1e?MM#=*w#}E zz65l1ke?zYhsGyIEMwX#?`)dZcJBWPr+I!1(*0P96uB`idhTspDo`IQBmg!7M7fSDscJLdCjtf zQ$Gkyv!KtAPOU~7t(#@V&Ra)rId~5`u=Ugi2g}*%T#P9h-C2bAHJPx=K~bf!$oH0_ z`VYK=EiUbwmdM_)xb$qwJy&sG#(nEvYU7x3cE{u!&wm(U|EfiONM`9!e@5AV-5<_; zIBaGXE7dB1w_2Mn+rWZFxkpJBUqhWcrX(r#|epSV8iLLv5)mU6Yi(y~C(0b~VUxx0U zkZ<|F>>HC{Z6fSk{iiJdHUl}Su&~rz`J#YOk+kPb6nFHHlE;YrD&U9I$7**{5&ji% z?2tU=E|i%l73siJaBEMu@5XXqD2q42JT-qFJAT-l@9E;Td@-z-TTnnFiskY~LA~vb zU8wm%%37`VLf^h6`04ZnKdX@czvi$XphB1ol>f){NckN(nwTY#{rb?)xP%NDC*7{c zjlK63y%9AU$gDr*br_qNH6rRYk7uz+EjSZ;L(sy5SP;6}Gj3@dnR8fq#0q@pNmMNdw(=d-rFG(5Q@(&EV?j%LI+}XCguO`y8cWi z2w>@j5X@u$_zfD!C^Is$?Yum!ZTJ?DBdFT@9==GT81$Kn>G;vaa|O}xS1v>)z3YOx znA9>4NG*6#VYH_a8{1oTTB7)Yg0uf`2)~gLKvxZg%m1*7<6bbGV_ZHb^9p%D)*T^{ zN^)X}pdDAbR!2mJr;#0~D+B@9YdA9AALus*_3a>Mwf* zwSTPRR13I8@zz+~Kkn=ERLj`k0i@1GM0B)Oa zsA5pgBtcgwEgbcZXty!&YDV{OJ|T-hu${H__v&#~a(YZJHFQgf4pxszN? zh(G)z;0EW2q!)-@j9dLr6cVl-R<3l90R!$f!{+ENRoaqRaWUtWVQRrEa_s{g&fYkf zS#~2$@+=rkoS_F8I zoA(@`cG|?dJ86w<)~Ad)*G)DN%MX$V5WF)yhWJSAzgJDsB5n~sPRDn5op#>oN)^5Q zi0x*T^nGRX{(~|g3tTY}3dR3&Ds@)D=RUYGB<1y=`GtZxHiBTdd+2*tBLhr|!!ES(x_iJ6Il0AV<2HW$=$i zwkLWSXU-P+FTPBMHc8c%>PF$rBbXW~AvUixFtiLHR6UpP^n;(H3y!0~zXD}k`$XA7 zyDDqq!)=(eey7(!luZYCE0Q*cCm^LkZoA9p(?`G%SCBDIoa{Wfokpd=9I*=mgoPJR zzdryyQr9g9$B&QPkuii!(C%j3q5-ctcITbmOIR{aM9)wV4+XE%8C)|6IzfMu)=GaPH(x!iCgtXoa*S`sH6_K>~WR z?oj+im<`>c3wUlUfWu4{G*ZvD;Af>u{e)pLXLUjxi<(@X;fR%_dgT>o`v67qE3a#V z^n=tWK@!K@Aede2wL?M2dc0XTxp*Bh1>HaW6L04CHP@~{c()jA0>4e2iNi37>VT!Q z&_G?nB67X>5V4?FPvKF&<%+$Z!)XOCUbPlhO-6mcdm^o6Z)0o;C5jj=kix8e{uCHS zW@FW8r6@ll&Fn1vE23taB#?$N;b?1swI6gyzR~Xh-}b6Xauw-utIt#iYeQwhj&U4u zZxyV-vv_R>-E(G2@cvc@NRb@C((<`#R^CJ%A`i68`IDXy+?hW%0Q!$0)5j*DT{!{-mMU= zo1DriQZvuJe9iNN2@v>FFN?$cckn4`Q1s|lKrf|{ye|l$R3)A^H0MQulMs|t+6fh$T z-c4+1h*qQT<>Ip_tp@JPcjAy)X#pth?h5$DJ|~-8y7D07}(rD z(*X0~cH+`|XeO4dvdz5XCSO2xavZUfAuhv<#rAp^Nif%O z<OHB`kgtrE3Tu4d4f_? z*1Tc7vv)jT;-|-*%{eM0F`E5g~ zDs~+I#6A4{gFD?7DLDZkY@(ERg98X5!K9(XujpE!qKpl1nnVdETYUe}LxLF%mRU(3 zi|`<&GBq4A7l#mnffT0>n`u>7m#7ZtHx1motq$r`h52=;-bJv zws$2GG3{AWb1Qzjg-J4ZdA56E^@^463=2}R5l~-S(8H*pxD3Z%buD5(9S&IXACpo1 z0>}K9Z_9!y$|`m0+SBRh>vHfG%e74`BJY5F%zFc8_)uEUk$-@Ym+)c~Ln?>uKegx; zEBP5bT=?HK(#72^5cg4uS7n;HK7aY!fLNTM<|E2x?Hj}4 z?89qC74;T+1ofeRclV2u9CAK&NsCh+bT98Z z>)0H7*dr+s_FhNK65IzdycB`XeBJ15(^I_+k0euDii_WY(mVl5`c#QotJ=*;Dz?JT z0>>i=gO;|JovBZd7zbrUOhT;)!#C&cD1L-96>lp{g&!4k6b@hByCl%_g#9KCU&ciRx{m7GOR`8 zDXmBz_wJJTzVfHHUiY58sgjK^U;MvKJ8ud+6?@$TRitPC>oH_@fj9E_5Q|;SpT+2j z8xNyE39@|2tv^TsPbG2gcL@MGJnNchpOcRgQT)pHh^uweP^xa089MhxBaK}8^=>W1 zvw!X3+(GNW_1;;q>>tkv{F$OK5zf>O=eiR#zR#OOTA->btwP^gkAUG>M2FV@>7(jY zQ8r36b}Om0hnb6voXglFFMlTkzfI?pv{TdqTs!PZ)(~th(SB1?U&|7H0F3%l%u#WSk^l} zJ-eXuYk%+a`f~pTyB=vx(2CclNIOKC0Y_r#^9ACYgbqq>A*w1z=dN{lavdE=oPpo^ z{ES|FKN{sp6h9t(g+kWYtrkduCyJiy0b^RNo29w$8AX%V0*ChFE41My1jqCM_xT2W z`loW#3Q21CmS|(~=WfgVb+=F0J8$^kQ88*I`OB z(Eda?k|FFuJE(j;f*=epjYe*yQvvBgxj-p43u9Di#BG}bl12pC!>W13v%ivDTSXt$ zAOJ!jNd&i<1#Qj5gdTO#mPvCKybBLtY!`rz9F#$e(MxSFog?{gVc?q_>d$zeeKov+ zF;!~@0dZr28xISZsPV`2v*iVjM;-_cVbl>z=I@Rp?=3lDxu!RYbw8X$I7CbiHDc$on7gM+d z*4f(pm(0gyapDMyZAD4UMTc20Z zx`GN1xq;%%@1m@tro7d*LIq$YA<-G3ruamGITPRa#(Q`adc(J&k^65nZhrCRmbDq` zY%0iza<|QP+wiFSo9Jl93nZj6NnA`TqVJtq}CNzkl&oq6?J{c^)BR#d4Xdv>8M;mL{BPzdX%T#RDmpr*!V%3@)x@#I55;t~#-LQ80=PFPG$s<8YqGpC( z+h#RBo-JRS@-YV8!ME>KJ^pn4Va&8%+0Qg)|16p2poxZC0tKNOTV|2f;D;14Q;m!h z7!WekZb=Yjh)7o|@Y8BBSIzwdJaF90fV(wE#U4j4_%GL!B^S=PwH_%}XU&b*J5z0M z+;!|ZU@8IE7JT>9j3D7*PYJU`ne&hYEMX=QtDWSWa~+^l%Z{c(F`A)U!fOE79&JFY z$s4>w&+`QYw3!X&VLvHc(Z*)IN-8|hC*VqPXwWgzd|KiAO}C`HEc-}d`?Qz4hAoWv zgq_DUQ}4dqeyOh8!gwKJxQ6x+Ua3%|g0^bdI4-vwMR z=fnx{#(hbu%UOfoX<0?}CVym3f;4WnXz2}i6A?s=^pot>2Uew z3#oAUO!@eBJ**2lx>r(Of2OH9BM-$?);({*_q1Z^R@VK|ZMrzqJM^D2BHsuEd3JOa)Lu#(zJcx< zK0A8KgqkN(z{`aG1`s`-9bq&y6a&f4CE;9gOCXRu0$z)N>Y6Cy4mHzB#ve^e`v#`H z+UO+1uVb~f7O&pthfGwd-!t}9H_c+Od2@ixWS!9yl(mt@7cF1%5s7M*fjPW+#-xyv z+ZI??+oun`<77=V=xz3Ry1XuP7I;NjL}@O$QP@qF9yeVfNrPsAQ9V*6j5HD;gU7G;XDGIuyi^I9R%y>jo?8ZvX`N&4d zXt|ChzeW;ONRmVv;#88x)d8a!iOXGwnROJfTNh@7!R_rM{vgcL=3e2vu&q)ZvMSDv2*EK5BNyh!{Y1E4YaNt>^6xV$Y=Z~UP*=lKQSvSqfogWNJNB$1REUv*72djIr?;kf6ppf59K*755M-PQQBx___nuMBYYbl+X|KUZTfpthqa zC^24&T9~$o5b-?8`EfgDSoEjeY8FO@Mqb#Fd$@1kAjiDmHny&xPSbPTx%;R?kw^|l z;fB;Zq4eUU3lA()Pq+$-m}K&iJ}SbK9mP@PM`ZHdW;~=UA9|+GOVrQVx8}3=aAd!g zzfR%n@2?CyJ)u=?y=Kwmm^i`4B#4P!TxnPd9zhGzzt7Bi5ADv)%M|tG1Swrk;ZbGw z(5R#AdZxG*fJGvWoa2ZPI0CeG$H|jl)}bi^f%Poij(BPDpq*|?lO!2wDGRG-Q{SWF zVe+90=9Nq3?!zV~WhF_%E|OMOSC8tm;!QU!mMmXKeY;&88p$cP=bxTNQv}N~M=t~w zzm_7y_vcLgeX{1Lz}4f&WJv#9RenM7*J@lhl2YJ-)!5MVDNi9!iOGM!XR!KZwPlm? z$k5uiBUcVtAC)Yz>YKc^C3kJ?gspP6BFYLCzw~xTH%fgUxGilv(HK-oM806C?^Q

={=PR#V~d?!ZvxGS|*uzhv39w<`3UrFNuHbzjWoa!Y{-fTgP~u z-eWE;tqyH$q6>jdQl69QgFKTg`rg44=NBLxVTFsubXOP;B5Db*55)mpqoH}@w+;+3p6vwd90zxjDWKoVC8w2K+E zRN9K8Ze<;Y9C^cGzoDn)Q*tO*}9MbBPJmX}cc*y%=N)D_nM#aN4#wr~c z+DQ;6#A+`FfGq;0{c-M+6)a7f)1Xe5K4BCy2+EqaRa-tuUl_b-z?ecg3cmagl!GsDSxlLAIba#s{Tuc1F?CvF^s$T_N4f3KDYF?7 z63^0S2v!vTD5dIg>?T9SGh|i)7fr*kSNem)sh?V!U8nxBCCH6WGUb$SX`u_T3BqdPUt-$OqwOOkk&@HrA(OZCxnu6rk=M?Wtrx;0cxn0>AE-SVC<7@pdo ztPRea4RgYPJxjg7-V$k5!wIzpd00bMn05d>l@;M_9K$Kc>ci>=3Q9xl9br}9&yF9`g=po+4{Ysf-pYqa zrH`xC$^D*^5>6;)y!^EZ<9}>FBF1Bi03M^xgtceVribJ*P()fjN6y2u9SX>pvUJ*dvOqgtpo}qGHw=mhD zA{_BKLwE3aUe-b4p(U78Nck*dx8e@&t-YPQbFHTC{Szw8Ra(#Yz|jb59Fx~j*r z{p!ijChnW*%DpU>*ihM`GxW>KMqYsG1O+AQqdi28!_SLh<$`v7jyw)I$hL6<_SyDP zsn?>CgW}gon2L7li#iG{JK`f_N*kBH!9jT>IlEzrFQ|4zHlhS!*ehpO)_H%lJ+xJ< zTc!N?0^Fq-u7KtG$-=S^aSiwIBd=fKlotWqCx47XrakL2EUNJcP~Cs~TCuIlW~ltK z;Q$%Y=vhv8ev(_JNzs>X(8GbJIQt;`zlR`lAfANBu%Pav`)4oeUqi1YGcn^ojjUbl z4M~KjphB{K*>-8&u&W>#FWdVxD>Tx$#AZUX5X(4yW((>j0RXnZKBO0s8}<7DhRS7s zM#1ogmqoM!(EOYV2|KaoLI z6>#E>5KeYXr@YeC{s@kJY)(}3)-KA038_rKaac+inq7x5*XGW*`ZUq)k8pq*fQe{Xmv^_qvmhUKYQBBwmd)L z8Vn>-Tk>$3V@I4ss+4yv4a-GIllCS+rMY=zTiKJ%K*Y@VTUMwkqxwu!$yrSHfl|EK ze$=W$jXZp76!@e@fJE&xo_Cu~4z3?`2C$f4fbI70e_=~d*gM^*wtbW1bNuj)flUDE z1?+;dt*lw(tE1MQ%AC|r7PnN-I1@PCZHbNkCga#1%T@Gt+eK*e zy!6v!Zw=~6vreAU{(*V7R|1-1P0yCM3Vd+9-V+Ra_MfiyKfUc;g!^C+wEVl>WsVQo z_X zk0+%Rc~VZ3t0thWnBRRgcYQuH zz@jP?aUu4o@D02N#3(&s2dBqEST2s&NF^8NGN9iz5hd~>c9Mk)TnvqqFhMK4{W$l` z?NMTh=Xuyk0m&b)K4zEut{W?3+^|$#?+_5TK~IkWuF16?y<}|+5-8XDa72xOt`$@z z?%8A^vQ?~KQhE>V_>~+%N!$V2$znL(t?!{4BqN5Gxk|6(dB94EAEBz@6>V5$&U!2Q zrRT%_FI^M>3?3dn{qLK12(HF8nc3+6r_ohLJ!9WqU+sJw;VQ2gRUj+jGOfJ`VW7+9e5dPSK zg>h|lczlX3cry%A6$+2PfuJe^BF*i8u5vP5IPW0po)7B;m!10P$;I+`XE@8k&X@`> zg1oGVnJAPfT!mdCJjCa=jp%%k@(zNSl4;*bsfR9CLx+EE*yOpnmTum`6CkcDv2S4< zyzP&&Sfj~U$_rnEJKbVoGhApUV^)$2JQeEg0_QB_2DNRd1X_NX=m%fE`NZ3LAHd*u zXwUMU^{0Nc12B=0!65dpJ~}f3c8$Nu7$Es`#H-oNs4PS#d>aN!~o|BA-t`^f<9)aAX_)vg{DLxHsnYRo=3_y ze7Amy%PM4Q=49nn^dvogZtTi}na|7mKJ|t1FKUhI>v4b(G$}QB*Xl~e5a3}zy$MU^ zyqq2h)~Q=^1KWD8gb}abIF5|aaEmH;j!2!q^sUE3gsU0(=B@boZRNjY? zaXzyT7c^LtFu6|pVmeFSMsgY^fRixui__+t2nrPwRHJYTg6gwb7$8Vp@6SC>#P2ImpgP z*0M{eD}zkhveD3i<(>~?BmmY2%Ox^@8r9#2=X4TWt$uh=@Nc3kmX&Dl{t7cbN8_Pq z9N)OW2XaoCJUu1C(^3gW6;uvLuLZL*R1Mp z1usthdeqc?SS4w&h5vF!rq7Q(IL4wOh_sEZ2@zLbt37__W306M3nqNP;cIifZ?y9$ z1-Caicd#`>Sk9+)Xj^)6ab8Rv)gB0FBcM$ic@+0efq;}?9J30vd9ss`Ly}l~GM_j< zG?S4eoFiO;YjSK~Lx%|xWo5T&b`<`FYE}^u= zisGY@8+{l0;kEf*?VG*h?5gw;Vn}bz!83(3rJ4H4^z@h${ml`8>t1*1t3ntE$D2te zsWx%)j-8n*hRmo3WJ#|=orxzoMZdnp8AqZss!fxqEezg}G#t7cI8bb}Le}p|duc?k zxUp;rL}W=QwyVmy4}c%hF$q_gj#cBoXdFYjci4r9%_yb*cC|`S-jh&^@tg5FvJd#+ zjXeJOhtEH51<)=0h4$w%iT?T&65Q$UBoTU5jR=FWnbLja-%9`oOTZUilJpnR z3IB~1BH9z6A`wi{B?)U%uG6+>=nK@(5c~;UWc2Ijx>&=?oC8*nwzC{lEI)7`fxe7K zJ9o<42n*HgsQ7P%0$%B|dnfO{Iqk-|OArD+7iBD1e7ys1%2Cier6`9AL+d_3?5lzg zi8^MgOOi!zB%sn8jS$C&)WV4vh}C)NMX(A9NE@^Zm@I)W8R9~$unI@rOlN1W1n;CV zt0<2U{XolZhGb6a1#ibQ+h!V)qKzWLT7~E>m2*NIUKDe~aJ*EQO!!gA$$ZyLbFwpS zymNKzycNpp_drW@w%oN}_ov!DQxnRpI~Q~nG!RB}R+Q6nzuxOiUIWS|X1g$4DZ(7F zCs&KI)!gj*=p0yIrxB&@}aaSHUe0i@87-{1{US!aompkE zM&QgULYAK+qCj&l-(^Q`yWz4_oUuFB~RO=)^q@%=EIL{_f5T)M2$dLd! zLJXTSt&FGyQ3KrW3rDeR!_p*Up&flWB6(93+2t}oT*=UdJxiSqst+X*a1~-rA@-`@ z+>>2?Dg5GGMw)wtSqr14(e;vWK>_XN!^6)7g3p`e#zlFjn`YfDrPHRrP>N<@k;UH! zy8Xg-aejJ2VXm|A&_ms2jw{9^$IK*_w3B>Pe;SY3i4kF182c0Ufo({P%?KnW9x$Iw z?nahc7qwttw}OB+;X#~-K}&V zuIL`k#~L=AvMEFJ2xyNS*_wP?l3zY&GhOnsMz6)Z;@r>1QS*vCw`*H7$!*_~B67o* zepXqp)2w169YIG7lXcB2vz{2V=wZ{89|oG-Grmg%<~WakmFy6-A#WX}U|+D7*WV+( zJa^P)t#I#qO2H`fhK2xkPfmT|85uM?bes~etA@{h_Iq7rBL1K(bXVy)q=txi>s~*H zv`L-}bEoILq%;e#(vu5QT$bO)Y_1bmek=arqUN98t36I3Zt{dCcGwLKYE1PJ2?JvrZ2mQjdmwYSj4ivfbBRBdjOBIoP=3@n(bBprt z8Kv?PLUZj9(x>bBCAlBK;9fLY!ymsKH#hXswK`hhHof~$`GdAmQxsFRD0P0)9A|!Z zCS?;)dqUek7ezd3ew=+{HftMsn1BpWig)Sq-qm1)TP@JGZ_&kSS5%|BqNiL zzWKXJLH4V%6*-{qxrW{$2~?z^^qC!oq!}%FseyzZ*-UQKFL7NFRzw&l)-D-A=84x`0bM zBE^6=O_a=WM)6Tb2o^>iu>}UC5T64f3Yw<9^3z_&2d;p|q?9|0G2*yY;JUHUlj8iS zbB9Ti`Vq4O3E|i>$62fwp#3OF7&vE|c-p?^dBspZ-_d}i!xH3TyrMYZm*RPck?Ad> z+YxH;0X{j`R8{OKGAe3yxSA^KE}Z-mcLk2h{koMi^j&kD>M}?;4fEhBu%dW<`YY$p0^9)dGkHezu-E{=YY1DMIZft#O>BKv_*j7yk&|K3>uktz6X6rrB9x zP&s<|#dCUm`x<43EP|(-6OI-yF0YlI1p zAZ!6Ay}|Vnb?gf0RGRY-zR)cvCD-E>E=kgR5XuN6>>m)dWM-;en;;SrVm7}e$iqm3PR>lqF^onw zmgP+0%*~uw5-CZ#AWrpz^fyi0XtjEfi9=>qOP)b?_)DYM!XXrA#2JWvI_U{o-Y5`$ znvI^!vt5CN&DQ)A^lvlsDSaBCl2@{*-KJ(hcv$8G)fAY*+*{!e;(u!s+~hbQxV5^&zy6m=+W-zn zvJlddzuJb9qH73jq@THUkh^}LPf{h$Ln{4+2#$i4(9qUXH1@QP;F;z70H%X%=$}e^ z=P!}O+i_0k1_#4y31Q^Yztbf4`^^n=Jx}i!rW4sv{5K@a1uf-@dp2`ZP`0h!G;d**vJsL3ly>z0# zM+3-8=4AmIGbc1fQ&e>d<5fme{LdR@RKLlRAaAQh4|m;u&6qc8 zcdNQ!mhATj9Ud?Z^Erkh=o`QL_V4sY(i6ln=CMcn^X3=AT$9k5&Z1Qhhz5q9XE&{Aw7G8|m>fkXctI2t=a-R!{qwxe9;#?Zi zE~Z19S_)DE4WZ%FB!Yu0PS^kR(!URrb5U@NYdy%d{&N-AK;LPtua7mvU$)CSgic4Z z9-2L?V-t|N7HQZ>XsdmrLy38RH{LF{j^NyS8RoFDhk^S}DS$UJ!QC3Og!lEihh^}i z1)H5(6NDxq3MbB36qto0A5N`ekS4ZOnB&zrDucq*mLyTisF4!5^o5(??7<0`LE`vn zGl5*duJ>&3SqE)thzGBNT3(xhIBB{5_IZKr?$f5{pMd8Lg`$KNj#PPOK}=US$qTjV(6?XGuiaygD1_9Q)<9o6rRA7vHO%h75{=PunAl>>KH>8D z@fSpe>_1ZVchdf#lZ{a2t=WPuD2JUilulcmHk=elh$jYa|8zeU(y8Iha8Wr)Bdb zCen|-E;^IHA~Q&5p@#{Np+EB8lm%1x+#vf`nJ~WQ5t?rsDH1qS@`d2{`eya$F$2FF z!zLE4$6(;uBQG%7dk+~*eEdC_@F%B6dWRo%#B}HC4vg1_FoTQPdGSNd8R;ly7aRTPrkOzu78MnuGks_U$BW7QD&+ zD!fMRA<8HgS?rxCL3$0|qepQqe};@gS3`=?St+^?6}1>>l3so7d!EMC6nj{WfKbA7 zDBp(=QDzG*biD8570O*~JEG$qcxUcHDmo&2cM2j1n0`UNzC8;gxow}R9gR>;$TGTZ z8^Ls5S{#xi(2+F)v)F3;5SY%hAZffPJpl4P2k~a6k{>BC4(@M3aoRH5sYaK$_jZ5V zRp>kpPpSm{))Nd}76-ZNp};kR>Vyv>gNlyS6Y&kqB+u ziMoh{Q!^RggWg|)gngOja1(Hh;>j_%OyYh)44zl~)f`_6oma>*F^V)ol-IV-sT~GH z+$4f|<v7Nd_gl#7fpr^jl`Pwbe+bG*-$&!1D*DR|l&^7Y} z8tkbVM+2y9+rplrzl+s+2rpx?q&$i3sc;`It+W3=Qf(MP+(kv;%lohbxzd#67h~Dn z66?^R+yh}WD?~RXml^SOZ0bj_q1APPznPgWm38)Z2HTqp0|q0oaBRTo%uNTHxNd?h-HS!P8^?h?cY~#9nQ20;I*N|P|CPtK81xO&oyMHUAdrN`{d_k zBdG_d__X(JQj;WO{nxBkxg}_R7d;VF;D@SLR@m;(`B*QLhk(dF*naFNps5I2)S|+~ z66fRKl|{x;5{~Wo!@SI$N+NK{8nJqNrEspPtkW4C1bJmv45YshC3D^-V>!vog`){) zp%)mCixAS!wclKxD80PDyXBR_COQ~8Qhb;!f`|7|J)<1Nym>R@$IHG!{CRvRjS(r% z2gMoA0?{4iyShRWroRvY%3U5Mt636;J8xC&i6;o~o^A2%jU|a&NBv$0Q5#ST7a;m9 zTX@Nkp;1=l6nhqf5syk@5+tBk)O(%4-gCe$KfL)NU7=y0VoM8UUieiXx#ty%9y@+!Ntw9UUMJpgF zLX8N2v?s_t}-?(fI-`ypz=)oD)?JkuZENjV?3p*SFss7LjTe3`X{(I?gQ#{_@= zOuqCp{Y?OH#P?l_nD8Tlmi-hMtCx2s6p+6;eOoz0<^+yH7@vM4pH@QfHje>n4Z@``h zjzl+lv}p5~N3<`J0oBfsXeU|JOr_0wSZ|p?8q`cIRY$Eb+;rc9foCdkCh?-Zz(E4x z)Z_f99{Ku%f#X!wUsq$QTrhbdp){(@iSadc!M2CtJQQXB8vf@z{C!G#X#y=cqC>&N z_ooFqmlm>r$Bh!x{p6#M7K466RMj5Yx~oe@LbG}!&x!B4QmRE*=`>V))e_A%FcA^= zk!DBxYw<-nP_yLMN8j{6zPE#kl^w_`x)}VsAm#_+&*T>kpP|U`DDim+K$HtMc7vy# z#2;w%`&&07gWQ!fYVE&C#ov3^6Sb{PNr_=>>Np_u`;)vQ`Ta@i&{LYUH&9H^Lt7Ru zPM%c@Mpha`s)sfkNm0N55OsII7Hza~E!F8Y03Lhf_0@2 zf?(;>0io5Wl#u;dnZHSKW`q&dzzfX&W=ei9;bXvvl8^{SW<6PjSQW~Xp--4U6+Jt> zP~-MyNRqV3yIck9^|^x{Ux-btw^nHnPDaINUeogjw6I3LrqITSZ+|Ha&9{c%_nH_1 z$**6`Y}9{=+kNbY_3OM*X5j5T7W73hXfHti<*Z-b#l7R+qzju$iaf;PhXL^94@ z>ys=9)%wh+aQe0Tk+T4BMr8N=@5aYi`y2LZ$3|PbJq_4FBC>c{PHqC z|21#N@gZY)@-hO~-!O_%AhR|P{;0vH>yM;~lvK{*uQzNSUnD*$aj8i51gl&clnW9Y zW68_>v$bltNZa?Pf&ADfU*mA2z{~~-Fnx2N!06W#+ujfX;!~tQBsUonJ%f*F#LX8 zyKe6)v~iIZe0Nasd8;4y;g+;8&B^#wS@1P=2PY&qdq(94-=P@({)IS5$8F)qyS0*? ztBQK9MHfV^V4;&E1p4yAz~6Y{_x@SIz|~V9X|MfF%ALCvvY!R>XQNn|bLY{!1a_Mq z@~QqDI=9`0-61K#qiJd1t28bAdwfb3DjiBgTbhcLL`*Cl5{t?IL#H2EUhTGStXF%% zv=qFZD6rtnzr8SIM$PSmw>t&Q`sca=ok3Q2U#@9)Y4H{`vMVl{9oKFH*-u+^}`1xv?Y5AQ0 zmY3IBv3BEw+TpRA>Bo^3fGek{S^Lmr+uZ!Rv4$j?FkkSTPglX`is$bBYuR&ZzxKN` zyIx_tzdI4v3d-6{o?QR&npKufsn+c%91@6N>W56O=D&A5F|y+&soHUWJ|QoNPTOF| zOFyI}1=~sn`w_Qh$snEWIsj-z+;*kIyI*Gd35{x3=$8UFOV%rlB0daXXS`@3Y3f$; z<93!=&xrF9Un`=IpwK-cTxJ{Hfl zC2hqy3;;Dne&Q6!6InH1^wM6|2~SWfoN~(50K>i^Aq5xL3nHf7qKXI zX#lV znONe5P!vhph?vrg*M=KKqoPIf1I|AapVKIwG=HJr^=wkTtddrQCb{zR&mkKJ&2(Sc zzRF9E_mXC!XTqZBMF#wh@y6+&)~|6hD(7XLF^l?C{xo9 z`RNN|7rgV8EQ6-!p5j=ieLKHvlq#|D{MNH2lQEOv91a(@;H`DJA1yVv zCRcsOB69nrD&GAZ)JordeOmn+tImwVephW}g2WK*u3P7Kr)-PVAtC>TcWYfcuesrz z6FY?HYQvEe&BuE|>yLWp_Z-R>;e#GI-KqZjoXb&F4mN{xPPmzaUjw<>>5t!S$}Yx- zH&4%`T#3ScvE-ZWuE{@6WTkn9ej;lqXpiU8zv~^zhStv#sJq?v)$&o^L-NmuLNW zhE^)4a;9IzOZ5KZfK1dSI}e`@(k!=v_e-C?&pll^d+EDo^cVW!e!Xh{EoGjH8~;Pu zTR>H{eQ~3L2PvfyrA1PZ5RjBmDe0DwRwNY=6omr^R5}C+5d;Bg=>`GmkWMjBQc9#7 z-rOqqzi+&6jQ5Rk#vR^$W3Ro|Tr+?3H?wZs|CKY$`Eo_Vx^!4jaG{OolE(DZgMi{ogEFKC){A{52bh(fa*+0f5kY$T4r;nG?*Pw7Cr&TI@@LWCNssdMt?R?;F-+hM}O_8Uf2CC}#O09M07_Mi}hRmKFxRGhA zZ?9`=nDJ{uKdT}xEoCeuiEuGQ5}R=NU~S6|NRoKi=9!8) zG=AUqixahF{?sSgQIha_H`~=dAs4v1qc5pHspIdxf?&X8R`Rt zzMdT6(zoDHk0kH@x@qkbOvylT<%`M-Ibhw+ACX4LuYTc5cq;c)66mrmyOw7_r&+ zY2f7mLxjQGp|wnB#cxA7k&kYV_1ea2V0M1lJGwsn;(+U#_rCHjJ* zwo^oT+VRkdMwO+wO4lkfVBB+_GkCRgaO+rVa9d7G+?9$tmQ?XsC#Sh@Om1DaZ9Z+p zSvNMrT#^}PPM%77N?=J;r!&}BRn<9@nawnu{l##u%fUQv-NnFPJL^s5O8bjcdo1Hs zG^fE#XBb6vbbU@I^N@O2mG{ldHlRhxVMn9Y5>Fg#=pXMZ1Pn#POUMWvC%C5KY^ z*Wpg@@!%@y?U%zZhu&C)TdanqEq@cVWDTs`DnJ(C4!e@v8B2QT&iuAMCy=MoFZd2^ zlB2SehZX3<>{S028OG4$A;!Qvy%M?ndF?Mxq_n~a!RIl-gI_zRKXX*|3Z_<7%3`~o zN$`-Zn6NM#>!aHBT03EO$Bf1&$d5R1M9UD{Kr=p9+W_&>M-G@!2yK|W9_~{+~cuZp2zW-S-A1Wgyb29VurRqZWLvv^X2j9;g70* zoOx7_M<%zB8SK46Y?is>q>Al8(k1ERy+t@owI;pIn9As`WT!kgTFAFZc`s|r?ZwvM zb~HWP^d|w_VK4(I#Kqw?Dr7wfQ+~T7iHI-C&fg$@xLsst6RHcr7v=kKFZL+50s&qv zn2DnmjKxN2V@pY9(Ocf`BnA`4^HJvwQizT&B-J|;Mm|<8xn60cguP{Hqj8ouh&0*J z=aO#ksf_sacYZevFE*dYI~rbUUAZM;EBVFe69~%}vn5e*VFln+u8uW*{g0*y)rF_y z&bOiu_f5(giW#6OlAp$5F2Gr_GX^d&x|`ToFIY|uW~Z>)S0tb#-noZVDmiJodk<%# zKZi>h;s1#Lq9T&`rL!?T9)13{hvp;lic(u%dVj?gy$L>exjv{Vg#dxE|5pCXk*85Y zNr#4WJk)cK3>;os6%tMVAgM4W@7yZy75=xQrlY;}$G$1gdksI$lVeD2=aB2QQP~3z zXi4Qr{HHBTp{1gDxzkrp8K<}_&5h=(8!GK^7)jeEt*E;zubDbtSX2a82xEZ&2)T?O zf{%wB$e$l33n^?;x;7p*EHt0M+HC+CDSL5}HS7o$TeozqY3-KKJT4pz+MItCM^fSA zjL0#+*XvHAE=zB+)8A=I8lA<6lc~k`T8GfCj-`qCtWqY&r`yH4{JJ^AL(t2}SNlP` zXRmI&UIMm%4?cU*@+#rM3VyGHoeA7ae_@*shi);W4)ip^wKbDbv=m_uO)h2hztdZl zOK4_u0motcijro${ie+P!ldrRjDYTH0(SM)O z-PFdKp@c4_qc2sW-E`~6Ze3&1c30;!OMWppU9t#`=6s}K8tw%!ki!c4n2Qu>@C9M1WFn*ohG1B z_0>oks;!LS)36KD-=^OniC?oKBCGp4h^F`9nYc1P&j)~SEApDD{JoE~%;+Js3SaF8 zK@S_g!G==p9YeR+ozfeG7l?!ykU&3_4&Ih;-r4-$i^>d7!;%$84xc_@OsK8_g+ij) z$ZQT5HJZ^ME~+U_-&cwo8tByxX|o@@JHd5Z+Pi&Ud5AtKFxq3!EMK4B-FZ2@xtUku zvq)2@O7ck409I}GLE3iA#A5yXeR>v2$q^(5l79u7vxWZ_>c2bT1yA+VES3H~{dI;H zpniGxrZu{uBG&!x(fw}dortHm4F(Sw0~((qY|elgTOj~1af7&rs#tnnd5Xi=c!FPv zp`U6yZP(dYIz-!cuHQk8p^y#3i~1#!g|1x*>5YX=pbrAGAJOB`%agf`I;ybFivdSO)M$B$DZ~{!(>!0cE0~IV^Gw2 zbu-j_1q5Yd>G(5ZX_v_Gdz&OYh24hSV_v;%d>WVj*2;F6GU|v#b)D_j)ZOSk>Fs(_ zp1=@BcdsF{9qEs_t2z9^J-X{-F0q9kC8d@0u}9+f-@oYB2w@^r^1qbrF6sm~0JbNtP@w6X$^DlkUX3YOHm zS-fbkHGFf1WK|M_R9C;@k(N~Sd8_@0T=awF-wLW;;e?DLk%>6%*h=k`O{~Clz`^wf z2wD^fdXd;V?eBCh^R**9n9v&h z@p71!Gd{FTvVgk-yu?&XFE{UdM@gg|&5}W22Aez&+;N+o}?vq~e3Oyp7o)MwM)#rWp#YI$r+*n7C_uPqmjeH>e^EXPY; z^y2(^N__`snx_8beVjS(6@0VVdxKyS#x<)W3q3CBoTpnpZKPNb7oVqL5*A;^+y1!I zL{p>iF!{rX8AhToyG}_tRm4H^BduBDMwqHffja6YOE64ERUjb$-9&&QfVRjV9K59Y z{t8G>9nwt&4PWzkbI?(LvSPmY*2T-nBZ>8kP(wZJ&1WAwdH{)DoLC_K; zFVsv%gF>;xMI~mlE3!)gwkHE)BoQhmYN-ln*l1H+Op}F5EdEc0nm{RPp5(+vykUkl z)2(RI%n}0Xq$V11I9`;iT^$NAA278r8ZrBd*LjBu97`oFOYFCJdapB+I2>SB& z(rqpwN%UZ^|5%jV*Wt|%?Vkd98U6$h87edqZJAC+BIo|;R5ega>dd8o91`D%8ieSj zOi3BWyT~Bq0FG=hW8NQ`C0`G$`kGYHjFU(~a&KXOPnFiWPaTppjiXQ+r$X2%qs8uL zZ$MGzDySo*$>BVa_%A8FOJhJ;@ywkU=|H?<b0a>T@y59d^WM7$Ftsp+ZF)* zZK`en_rvZtM2hd-Wlok3UDCKBRkTnROR#Q|+}~8iROZeDN&zX!a;83cW0s;4cdp@5 z^ND^7{bE}Vq;1pzxQl%!+h0c6?GhmaR=Zzc!L^EgK3<;_U=?Pfn$H_T4#6ii=fW zRH>)V=@zM;Wi_Yp_S`d5knAvDbC{qSQ0+pu8qF)eI94>cD?6RJy(p%_8Ej0iSF-#f zX7xBP+7LF`FTi4P_t&P;j>tFy2mSk#f`pErig(i=8<9s^!ypSxW|kZ&p4X-9^zfB{ zY6HswxWQT`ToG&^pH&a@BDBTGt2g(&QEbS+*_OOv0Sp3U2z;*en-^j7uWfHw~4ZF-CwLMb=M$sVJevy?X5wPsX>j6YEsxA_7nI%T^tieG-nW!VJoR z6w&PR;Kv`qwtDx$pN!4cSp2BBy{Y*bmFHC|u8s4Z9I}Rk{?Ba;%~Rr^qWpi6adglo zZ4mqpg-o8-u5?>owD$M{&4>C9m5$9BL7kmh&HVOZume7crap! zC%BwjNnB5}#c-u-0a{O=0jgP+(Qyc>Rmt&4tsmh?8U--^^R|jC5X(eBypBROm=YM4 zBC?7xLoi1y-~oXAEEG&bRBseFVH zCdnIPFw=k(p^-M^#xn~8RwWG`N%chHiW-YJ=K)frUywqh=2&7FKsj{a0O&RDVI(yq z_ji`z4{sg-IW?{W!1WZK9d~RXlP0M!;fRM@)G~p|r41d4d{Z<28xLh{U%l({ViJMu zMRC+>T>bXM{{qRZly46dhecFLeuAzS$^;kI5`=lqpPJ3qV}-C+NQoitZr@%xPbx$l zVhsZCBBwqpHA#ux!FF}ycz#bseaB}$0BySUbULZ?0rKxvj|rG>PK0TR#rpv)YNQgO zp~&G6U#PVHzRWnnUo-bliPZe3znTOBK|H0sl~hST=vSM{5XovrTqY?JET86*^iqSlRJ*U(8iiVqBaw12~a0V-b!(K z*|mhR>cKiu<RevOKk15qrQ}lJ~K&c(znEv7%KJ=A67Ijm9mM$C2VkMjaJ1mYegAb z1;p1OvDQW!PiuDS3du2qK;R%Q7f1Fyq@Xqy+3Ey;e1z)ZrUfzq_}KF^crZ_Hk4c7} z!~|jS>X`%tQ)ulo7!x0`+##l_Lyz%2+Rx88TI0xXT^7TAqr6!ntqKbZFY?QK4eqpj z%p21+?Iga)_1=6lL~>nmIw5D2;;OhAk*jb%UfX#B#R6;;6`=z46)-8-#*I| z+qC5vjV)v5o2-(r4<8CI0CdNo)>cpA;5ENVfyt{Sl-Wy zmjN32!)n8zS|5Q;o{B#%NQNiR*dAr7ZvJFGXDi)Z>S~YpHRDH>!tNj4o7`Vr%~se5 zeDt|!jwhQ+%wbc(hI_PC$e&^5#3xBx$+$bT68Aienyea^TF+Y3#9ON4=X(?ZB}($} zEdhqH^vKRYw=Rp5hmqp{D>)`5kL`Lg8ex1J#o*H4X5_^@IEo#HL^CF*X?#@t*t%C&4uzd!Basb|k{^jj)HEEYHv_;?14XiO;z`?&j+SEr6Hd8-|}OjCW{ z2KwkXL4^{0Xa{PC2541fQ9FMN?9wPi0`Ckj;Ch2IL> zFOHRVSYD^4m`>7%WRS*N{+A&R+I5!l67m=RCm(}{WJSJx81nw@C@^AUmMG$cjtb zKLAJ>2e|!&AC5+Anj|*gRmEN;k)M5{XXuc)Aw9yrD=lvAQKF~VnqyE|c_U|aF_n*@ zXv>i`-9c_OU{Brc>U8GM5@9!XWArwn(8vnoI#UzsZyN`|#Y=C{Q}y^Dv&W?nfAVbD z?~L|uq`x@)Ge4iR@BSp~PLZ)^)QkfriwfM03MtA^FmGB2oYV}sc2C}Uw;K7I{|87Y z8X)xL=J-b30fS^E>p`~IjYVtsU~Y)5nmWn_(RNvnd%!1jSZ)_mG`^dNVg+?XmDa`g zXw7yJlx6cZwh-|p-@rGze49X(+W-Tt&%AP5Qo!nNt@k6p*+5=mr3b+}9S)Dnwm2YC zRG>}y2`yFR`g@m^Zn*%!FcbLN=c=190T(xvRHb5gI!65|`-wBeWWFR$exs7K+D9)I z*uy5tO&DkLnDm^wPs_Km;A}c68caL-%QWUuOrDa{g}gDR=Er1q2Tn>4t6E=YWT6Nj z{Xffk72$)?@|yb{H8GR;N~zck&-)P{hhxx`qap!6I!X5}Q*a<@ulhBrnaoppwXu zAr=%f2s+FRs1vw|{jkF?5YUH8*`j*f{7&j-rJ=hdGl-R>XeGRqEQS}o#v`BFy$u=Z zD2+7EP8L}cHhV9pC8@rhxy{Bc*&070G5-L)xlyz%GA8Fc&)kJHew=vm>zXLaf9-TQ z5vA;zf2w-}QmSMOiVy#!4|y3)m>>$~0c>^Qb%ZYpNHA_r5F8!M&%n8gM`9@ziiPnR z&W*hWf#prgP?&Qbh|p>$YG>*=;=cg+xr$&58t_Z0r^FxRQaIn6?vX(# zjDb$fa0Gmc)?#7cv<{Z?z*CN0dOyTnF?WIQ)`wGArUYi@_|ah2u1Nx|NWc0okAZmj zzaSY7vo{esWWSy6nT*VO=q2FXPAYE!$QmnWCZfcS(+aB<3)u{0G4aGob@h}wn19TX zPMW{`3dLI7cTNRJ>T2GT|By^`mq1Ob8+eNEGIx}>q1u-*;Bloy=nXGZYF)>i*a50me{AtH zZtoq%fb}f~>75dJ^$7!+5Hcx}o}s3U^@az}+JI5H0imFLsb+Uo`&PGIKVNk0-CF!G zoIO0hXviYH_&lp^`utt*D{OMMb5IiM#!ucgJKyoZVZ$p#cxT9x2|s@A6ijZ3aG1n6 z$B6Xxw8bztX1D=-t1O^rki+QnUaVP~ja8!tpZCwrIf83}Ys}vO{z7iaT^S>RVRBnb zR(*mLu6`-|KRe(8`0La$ibGPYw!mEVw=aSrK`8-ml7dOuDXq~OvI}YFupPWdA`1a* z`o$245;T4-0qI4{HP$py1nt_?hmfUnd&hhizx=}aK*y!EFX=+Av;`2?2FZCTyGwev?XshHt zCmOx{*%4u+94n*y0m-LJj9QDs08{70IKba(87^*l6E*%CtlrZmFo7yVV%tTs=BHA_ zJj0{GR6dd$zI7qMTR;8jJZlc%83dTWt-8N??VQW`i_Ql;v>z{;=2h3{@5Q=^r_z4T zUeueDeLARcP-e^-#~a_|XD?Il{h!9{`u{ZQdg5Z?$*NhIgSw$W;-0h*rX%!jzohxV zEOH171EtMxGNuMH7qM-T$5hv|8?6+8cA+b{m5D^sB%NmaDT^JeO6(6v1>`^RJgV3) zC6A?!dzBd|QNL6gq@xT6@PZo)>1zL+4rLYHHJ;He06_h=Orr0B};FK~-E z18CRfiK>>GbavWpFUinv#P=bITWag`3uiPOiNq^cdK6C>`FgbjUnFC8<=IFW_c5r>@_b;=}K|fZ;MKZJ^`l_V`5}#Z&U=oYlU)gKHeh8fqwXjQGb=`iGRv zn&NCe@9i{(x`a6-2Wq# zp(vzLXR;%XGs+?T>Hdb?YACp*ij~mtUb?_``@DwkPI?f#<{QwCv4v|F1 zYrrlbld2MD!9v+YB#K&WeM$@otOU;LRki*5B65MCCt_etz{svODW{ z$>vK@&O3}{J&~wHsQgwZZQy^>4as5fS+!iCD(NiU{kZHb)_E>lQ^HL`RGrk1XMd75 zLN^nsH*dy$7rzv>eAUlzNr2|VlY?9nViX@ug!T-GSR8WruLE0X<04Vf`}~7H~DO z=5YxM{{eaP5{|Y;UieaGxM8vF3v{bFR7|l+Yt~V_6_+*A*(lF)RI}3N2fkfW96Oz3 zHeqUPGuD38Y4Wd9m8p#Jv2>03Y;9L*m4B1#@Ed24#s=pPXXOs*$4D@qd$`O5N=%*> zgE419C)zTZ%^@dTEkeTf_WDCZdQeQ!&}l6);)Ir7nMta++BV4?bX5dC)~pS;US(_lYHyW zQ6ljpm@MPlEOg)zy7Xn)o3Yh8k;%s&sUrL_g4@%~*z!PO>!NA6v)g#|TEXYXidrwN zX*IQ2=i-f#YWaQpuvRYO&b`tmogj1nm!!LgxV`< zz1Mes(iH15AT^phD&B$Yvz=ayWm_MSf6&~A$Re>i`Npw+9ie;YprTI z-Q^>1t_#R1bd=`|{zOsMaKGZ`qz}9|1}Y^}YYq>KKH7x~tDZ$u^C=BkM96i=`^a>T zjDJl~wnJjd;>k}Ig?jE1(c`zwG*@<%8|4pCg;@WG_saZzFFt8=onqea_fBiQ7&kXF zrw(yp4y6Qa$?$nxa+;DV1w&Q-cmZ?Qyw-CeOcGQ6(FKE_p%6O6PW

    u&h&G994{#dSw)S^O@pFKJr%@<#5sP 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 X9z`zZMK$M1;D1W;YI2WHn%ww51Z77p literal 0 HcmV?d00001 From 2fcf6d4859cd06dde900fe3fc997f8ae7f53c59a Mon Sep 17 00:00:00 2001 From: dinsajwa Date: Fri, 8 Aug 2025 11:38:58 -0400 Subject: [PATCH 09/11] uodated docs --- ...processing_analysis_using_code_interpreter.ipynb | 10 +++------- ...6.2_file_operations_using_code_interpreter.ipynb | 13 +++++-------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb b/labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb index 44210fe..7208607 100644 --- a/labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb +++ b/labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb @@ -73,7 +73,6 @@ "# Verify environment setup\n", "import sys\n", "print(f\"โœ… Python version: {sys.version}\")\n", - "print(f\"โœ… Python executable: {sys.executable}\")\n", "print(\"โœ… Environment setup complete! All dependencies should be available via UV.\")" ] }, @@ -115,9 +114,7 @@ "# Verify credentials are working\n", "try:\n", " credentials = session.get_credentials()\n", - " print(f\"โœ… Using AWS profile: {profile_name}\")\n", - " print(f\"โœ… Access Key: {credentials.access_key[:10]}...\")\n", - " print(f\"โœ… Region: {region_name}\")\n", + " print(f\"โœ… credentials get successfully\")\n", "except Exception as e:\n", " print(f\"โŒ Error with AWS credentials: {e}\")\n", " raise\n", @@ -136,7 +133,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## ๐Ÿ“Š Section 2: Simple Data Processing with Boto3\n", + "## Section 2: Simple Data Processing with Boto3\n", "\n", "Let's start with a simple example using JavaScript to process temperature data." ] @@ -148,7 +145,6 @@ "outputs": [], "source": [ "# Simple JavaScript code for basic statistical analysis\n", - "# NOTE: Emojis removed from JavaScript code to avoid Deno parsing errors\n", "javascript_stats_code = \"\"\"\n", "// Simple temperature data analysis example\n", "console.log('Starting simple data analysis...');\n", @@ -301,7 +297,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## ๐Ÿ Section 3: Simple Data Processing with AgentCore SDK\n", + "## Section 3: Simple Data Processing with AgentCore SDK\n", "\n", "Now let's perform the same analysis using Python through the AgentCore SDK." ] diff --git a/labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb b/labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb index 9bab1e4..738d13f 100644 --- a/labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb +++ b/labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb @@ -74,7 +74,6 @@ "# Verify environment setup\n", "import sys\n", "print(f\"โœ… Python version: {sys.version}\")\n", - "print(f\"โœ… Python executable: {sys.executable}\")\n", "print(\"โœ… Environment setup complete! All dependencies should be available via UV.\")" ] }, @@ -116,8 +115,7 @@ "# Verify credentials are working\n", "try:\n", " credentials = session.get_credentials()\n", - " print(f\"โœ… Using AWS profile: {profile_name}\")\n", - " print(f\"โœ… Access Key: {credentials.access_key[:10]}...\")\n", + " print(f\"โœ… credentials get successfully\")\n", " print(f\"โœ… Region: {region_name}\")\n", "except Exception as e:\n", " print(f\"โŒ Error with AWS credentials: {e}\")\n", @@ -144,7 +142,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## ๐Ÿ“„ Section 2: Sample Data Preparation\n", + "## Section 2: Sample Data Preparation\n", "\n", "Let's prepare sample data files that we'll use throughout this notebook." ] @@ -229,7 +227,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## ๐Ÿ”ง Section 3: File Operations with Boto3\n", + "## Section 3: File Operations with Boto3\n", "\n", "This section demonstrates file read/write operations using boto3 direct API calls following the pattern from the provided example." ] @@ -358,7 +356,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## ๐Ÿ”ง Section 4: File Operations with AgentCore SDK\n", + "## Section 4: File Operations with AgentCore SDK\n", "\n", "This section demonstrates the same file operations using the AgentCore SDK's higher-level interface." ] @@ -478,8 +476,7 @@ "- [AWS Bedrock AgentCore Documentation](https://docs.aws.amazon.com/bedrock-agentcore/)\n", "- [Code Interpreter API Reference](https://docs.aws.amazon.com/bedrock-agentcore/latest/APIReference/)\n", "- [Module 3 Other Notebooks](../README.md)\n", - "\n", - "Happy coding! ๐ŸŽ‰" + "\n" ] } ], From ed4057e4ae950d1dada95f33a6ca97f6cd516495 Mon Sep 17 00:00:00 2001 From: dinsajwa Date: Thu, 14 Aug 2025 12:18:04 -0400 Subject: [PATCH 10/11] feat(agentCodeTool): added agentCore code interpreter example under module 4 --- ...sing_analysis_using_code_interpreter.ipynb | 521 -------------- ...le_operations_using_code_interpreter.ipynb | 504 ------------- .../4_agentcore_tool_code_interpreter.ipynb | 678 ++++++++++++++++++ 3 files changed, 678 insertions(+), 1025 deletions(-) delete mode 100644 labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb delete mode 100644 labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb create mode 100644 labs/module4/notebooks/4_agentcore_tool_code_interpreter.ipynb diff --git a/labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb b/labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb deleted file mode 100644 index 7208607..0000000 --- a/labs/module3/notebooks/6.1_data_processing_analysis_using_code_interpreter.ipynb +++ /dev/null @@ -1,521 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# ๐Ÿ“Š Data Processing & Analysis Using Code Interpreter\n", - "\n", - "Welcome to the Data Processing & Analysis module using AWS Bedrock AgentCore Code Interpreter! This notebook focuses on statistical calculations and data manipulation with simple, clear examples.\n", - "\n", - "## ๐ŸŽฏ Learning Objectives\n", - "\n", - "In this notebook, you'll learn how to:\n", - "\n", - "- ๐Ÿ”ง Set up and use AgentCore Code Interpreter with both boto3 and AgentCore SDK\n", - "- ๐Ÿ“ˆ Perform basic statistical calculations on simple datasets\n", - "- ๐Ÿ”„ Process and analyze data using both JavaScript (Deno) and Python\n", - "- ๐Ÿ—๏ธ Build framework-agnostic abstractions for data analysis\n", - "\n", - "## ๐Ÿ“‹ Prerequisites\n", - "\n", - "- AWS account with access to Amazon Bedrock AgentCore\n", - "- Proper IAM permissions for AgentCore services\n", - "- Python 3.12 environment with required dependencies\n", - "\n", - "Let's begin! ๐Ÿš€" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Amazon Bedrock AgentCore Code Interpreter\n", - "\n", - "\n", - "The Amazon Bedrock AgentCore Code Interpreter enables AI agents to write and execute code securely in sandbox environments, enhancing their accuracy and expanding their ability to solve complex end-to-end tasks. This is critical in Agentic AI applications where the agents may execute arbitrary code that can lead to data compromise or security risks. The AgentCore Code Interpreter tool provides secure code execution, which helps you avoid running into these issues.\n", - "\n", - "The Code Interpreter comes with pre-built runtimes for multiple languages and advanced features, including large file support and internet access, and CloudTrail logging capabilities. For inline upload, the file size can be up to 100 MB. And for uploading to Amazon S3 through terminal commands, the file size can be as large as 5 GB.\n", - "\n", - "Developers can customize environments with session properties and network modes to meet their enterprise and security requirements. The AgentCore Code Interpreter reduces manual intervention while enabling sophisticated AI development without compromising security or performance.\n", - "\n", - "![Amazon Bedrock AgentCore Code Interpreter Architecture](../../../media/agent_core_code_interpreter_arch.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## ๐Ÿ”ง Section 1: Environment Setup" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# ๐Ÿ”ง Environment Setup Instructions\n", - "\n", - "# IMPORTANT: This notebook requires Python 3.12.8 as specified in pyproject.toml\n", - "# All dependencies are managed via UV (recommended approach for this project)\n", - "\n", - "# SETUP STEPS:\n", - "# 1. Create virtual environment: uv venv\n", - "# 2. Install all dependencies: uv sync\n", - "# 3. Launch Jupyter Lab: uv run jupyter lab\n", - "\n", - "# FOR VS CODE USERS:\n", - "# 1. Run: uv run python -c \"import sys; print(sys.executable)\"\n", - "# 2. Copy the path and select it as your Python interpreter in VS Code\n", - "# 3. The kernel should now have access to all required dependencies\n", - "\n", - "# Verify environment setup\n", - "import sys\n", - "print(f\"โœ… Python version: {sys.version}\")\n", - "print(\"โœ… Environment setup complete! All dependencies should be available via UV.\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Import core dependencies\n", - "import boto3\n", - "import json\n", - "import os\n", - "import time\n", - "from typing import Dict, Any, List, Optional\n", - "\n", - "# Import AgentCore SDK\n", - "from bedrock_agentcore.tools.code_interpreter_client import CodeInterpreter, code_session\n", - "\n", - "# Import existing Module 3 components\n", - "from agentic_platform.core.models.memory_models import Message, SessionContext\n", - "from agentic_platform.core.models.llm_models import LLMRequest, LLMResponse\n", - "from agentic_platform.core.models.api_models import AgenticRequest, AgenticResponse\n", - "\n", - "print(\"โœ… Dependencies imported successfully!\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Initialize AWS clients with specific profile\n", - "profile_name = \"XXXXXXX\" # your aws profile name\n", - "session = boto3.Session(profile_name=profile_name)\n", - "region_name = \"us-west-2\"\n", - "\n", - "# Verify credentials are working\n", - "try:\n", - " credentials = session.get_credentials()\n", - " print(f\"โœ… credentials get successfully\")\n", - "except Exception as e:\n", - " print(f\"โŒ Error with AWS credentials: {e}\")\n", - " raise\n", - "\n", - "# Standard AgentCore client\n", - "bedrock_agentcore_client = session.client(\n", - " 'bedrock-agentcore',\n", - " region_name=region_name,\n", - " endpoint_url=f\"https://bedrock-agentcore.{region_name}.amazonaws.com\"\n", - ")\n", - "\n", - "print(\"โœ… AWS clients initialized successfully with profile!\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Section 2: Simple Data Processing with Boto3\n", - "\n", - "Let's start with a simple example using JavaScript to process temperature data." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Simple JavaScript code for basic statistical analysis\n", - "javascript_stats_code = \"\"\"\n", - "// Simple temperature data analysis example\n", - "console.log('Starting simple data analysis...');\n", - "\n", - "// Small dataset: Daily temperatures in Celsius\n", - "const temperatures = [22.5, 24.1, 23.8, 25.2, 21.9];\n", - "const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];\n", - "\n", - "// Calculate basic statistics\n", - "function calculateBasicStats(data) {\n", - " // Calculate sum\n", - " const sum = data.reduce((a, b) => a + b, 0);\n", - " \n", - " // Calculate mean\n", - " const mean = sum / data.length;\n", - " \n", - " // Find min and max\n", - " const min = Math.min(...data);\n", - " const max = Math.max(...data);\n", - " \n", - " // Calculate variance\n", - " const variance = data.reduce((acc, val) => {\n", - " return acc + Math.pow(val - mean, 2);\n", - " }, 0) / data.length;\n", - " \n", - " return {\n", - " count: data.length,\n", - " sum: sum.toFixed(2),\n", - " mean: mean.toFixed(2),\n", - " min: min.toFixed(2),\n", - " max: max.toFixed(2),\n", - " variance: variance.toFixed(2),\n", - " range: (max - min).toFixed(2)\n", - " };\n", - "}\n", - "\n", - "// Perform analysis\n", - "const stats = calculateBasicStats(temperatures);\n", - "\n", - "// Display results\n", - "console.log('=====================================');\n", - "console.log('Number of readings: ' + stats.count);\n", - "console.log('Average temperature: ' + stats.mean + ' C');\n", - "console.log('Minimum temperature: ' + stats.min + ' C');\n", - "console.log('Maximum temperature: ' + stats.max + ' C');\n", - "console.log('Temperature range: ' + stats.range + ' C');\n", - "console.log('Variance: ' + stats.variance);\n", - "\n", - "// Display daily readings\n", - "console.log('=== DAILY READINGS ===');\n", - "days.forEach((day, index) => {\n", - " console.log(day + ': ' + temperatures[index] + ' C');\n", - "});\n", - "\n", - "console.log('=====================================');\n", - "console.log('Analysis complete!');\n", - "\n", - "// Return the statistics object\n", - "stats;\n", - "\"\"\"\n", - "\n", - "print(\"JavaScript statistical analysis code prepared!\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def execute_code_with_boto3(code: str, language: str = \"javascript\") -> Dict[str, Any]:\n", - " \"\"\"\n", - " Execute code using boto3 direct API calls to AgentCore Code Interpreter.\n", - " \n", - " Args:\n", - " code: The code to execute\n", - " language: Programming language (javascript or python)\n", - " \n", - " Returns:\n", - " Dictionary with execution results\n", - " \"\"\"\n", - " try:\n", - " # Start a code interpreter session\n", - " session_response = bedrock_agentcore_client.start_code_interpreter_session(\n", - " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", - " name=\"boto3-stats-session\",\n", - " sessionTimeoutSeconds=300\n", - " )\n", - " session_id = session_response[\"sessionId\"]\n", - " print(f\"โœ… Started session: {session_id}\")\n", - " \n", - " # Execute the code\n", - " execute_response = bedrock_agentcore_client.invoke_code_interpreter(\n", - " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", - " sessionId=session_id,\n", - " name=\"executeCode\",\n", - " arguments={\n", - " \"language\": language,\n", - " \"code\": code\n", - " }\n", - " )\n", - " \n", - " # Process the response stream\n", - " results = []\n", - " for event in execute_response['stream']:\n", - " if 'result' in event:\n", - " result = event['result']\n", - " results.append(result)\n", - " \n", - " # Print text output\n", - " if 'content' in result:\n", - " for content_item in result['content']:\n", - " if content_item['type'] == 'text':\n", - " print(f\"Output: {content_item['text']}\")\n", - " \n", - " # Clean up session\n", - " bedrock_agentcore_client.stop_code_interpreter_session(\n", - " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", - " sessionId=session_id\n", - " )\n", - " print(f\"โœ… Session {session_id} stopped\")\n", - " \n", - " return {\n", - " \"success\": True,\n", - " \"session_id\": session_id,\n", - " \"results\": results\n", - " }\n", - " \n", - " except Exception as e:\n", - " print(f\"โŒ Error executing code: {str(e)}\")\n", - " return {\n", - " \"success\": False,\n", - " \"error\": str(e)\n", - " }" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Execute the JavaScript statistical analysis code using boto3\n", - "print(\"๐Ÿš€ Executing JavaScript statistical analysis with boto3...\\n\")\n", - "boto3_result = execute_code_with_boto3(javascript_stats_code, \"javascript\")\n", - "print(f\"\\nโœ… Execution completed: {boto3_result['success']}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Section 3: Simple Data Processing with AgentCore SDK\n", - "\n", - "Now let's perform the same analysis using Python through the AgentCore SDK." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Simple Python code for basic statistical analysis\n", - "python_stats_code = \"\"\"\n", - "import statistics\n", - "\n", - "# Simple dataset: Student test scores\n", - "print('Starting simple data analysis...')\n", - "\n", - "scores = [85, 92, 78, 95, 88]\n", - "students = ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve']\n", - "\n", - "# Calculate basic statistics\n", - "def analyze_scores(data):\n", - " stats = {\n", - " 'count': len(data),\n", - " 'sum': sum(data),\n", - " 'mean': statistics.mean(data),\n", - " 'median': statistics.median(data),\n", - " 'min': min(data),\n", - " 'max': max(data),\n", - " 'range': max(data) - min(data),\n", - " 'variance': statistics.variance(data),\n", - " 'stdev': statistics.stdev(data)\n", - " }\n", - " return stats\n", - "\n", - "# Perform analysis\n", - "results = analyze_scores(scores)\n", - "\n", - "# Display results\n", - "print('=== TEST SCORE ANALYSIS RESULTS ===')\n", - "print('=' * 35)\n", - "print(f'Number of students: {results[\"count\"]}')\n", - "print(f'Average score: {results[\"mean\"]:.1f}')\n", - "print(f'Median score: {results[\"median\"]:.1f}')\n", - "print(f'Lowest score: {results[\"min\"]}')\n", - "print(f'Highest score: {results[\"max\"]}')\n", - "print(f'Score range: {results[\"range\"]}')\n", - "print(f'Variance: {results[\"variance\"]:.2f}')\n", - "print(f'Standard deviation: {results[\"stdev\"]:.2f}')\n", - "\n", - "# Display individual scores\n", - "print('=== INDIVIDUAL SCORES ===')\n", - "for student, score in zip(students, scores):\n", - " grade = 'A' if score >= 90 else 'B' if score >= 80 else 'C'\n", - " print(f'{student}: {score} (Grade: {grade})')\n", - "\n", - "# Identify top performers\n", - "threshold = results['mean']\n", - "above_average = [(students[i], scores[i]) for i in range(len(scores)) if scores[i] > threshold]\n", - "print(f'=== Above average students ({threshold:.1f}+) ===')\n", - "for student, score in above_average:\n", - " print(f' - {student}: {score}')\n", - "\n", - "print('Analysis complete!')\n", - "\n", - "# Return the results\n", - "results\n", - "\"\"\"\n", - "\n", - "print(\"Python statistical analysis code prepared!\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Alternative: If the above doesn't work, try using environment variables\n", - "def execute_code_with_sdk(code: str, language: str = \"python\") -> Dict[str, Any]:\n", - " \"\"\"\n", - " Alternative approach: Execute code using the AgentCore SDK with credentials from environment.\n", - " \n", - " Args:\n", - " code: The code to execute\n", - " language: Programming language (javascript or python)\n", - " \n", - " Returns:\n", - " Dictionary with execution results\n", - " \"\"\"\n", - " try:\n", - " # Get credentials from the session\n", - " credentials = session.get_credentials()\n", - " region_name = \"us-west-2\"\n", - " # Set environment variables for AWS SDK\n", - " import os\n", - " os.environ['AWS_ACCESS_KEY_ID'] = credentials.access_key\n", - " os.environ['AWS_SECRET_ACCESS_KEY'] = credentials.secret_key\n", - " if credentials.token:\n", - " os.environ['AWS_SESSION_TOKEN'] = credentials.token\n", - " os.environ['AWS_DEFAULT_REGION'] = region_name\n", - " \n", - " print(\"โœ… AWS credentials set in environment variables\")\n", - " \n", - " # Now create the CodeInterpreter client\n", - " code_client = CodeInterpreter(\"us-west-2\")\n", - " code_client.start()\n", - " print(\"โœ… AgentCore SDK session started with environment credentials\")\n", - " \n", - " # Execute the code\n", - " response = code_client.invoke(\"executeCode\", {\n", - " \"language\": language,\n", - " \"code\": code\n", - " })\n", - " \n", - " # Process and print the response\n", - " results = []\n", - " for event in response[\"stream\"]:\n", - " if \"result\" in event:\n", - " result = event[\"result\"]\n", - " results.append(result)\n", - " \n", - " # Extract and display text content\n", - " if 'content' in result:\n", - " for content_item in result['content']:\n", - " if content_item['type'] == 'text':\n", - " print(content_item['text'])\n", - " \n", - " # Clean up and stop the session\n", - " code_client.stop()\n", - " print(\"\\nโœ… AgentCore SDK session stopped\")\n", - " \n", - " return {\n", - " \"success\": True,\n", - " \"results\": results\n", - " }\n", - " \n", - " except Exception as e:\n", - " print(f\"โŒ Error executing code with SDK: {str(e)}\")\n", - " return {\n", - " \"success\": False,\n", - " \"error\": str(e)\n", - " }\n", - " finally:\n", - " # Clean up environment variables\n", - " for key in ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN']:\n", - " if key in os.environ:\n", - " del os.environ[key]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Execute the Python statistical analysis code using AgentCore SDK\n", - "print(\"๐Ÿš€ Executing Python statistical analysis with AgentCore SDK...\\n\")\n", - "sdk_result = execute_code_with_sdk(python_stats_code, \"python\")\n", - "print(f\"\\nโœ… Execution completed: {sdk_result['success']}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Conclusion\n", - "\n", - "In this notebook, we've successfully demonstrated:\n", - "\n", - "1. **Execute Code sample on a code interpreter sandbox**: \n", - " - Calculating mean, median, min, max, and variance\n", - " - Processing simple datasets efficiently\n", - "\n", - "2. **Two Implementation Approaches**:\n", - " - **Boto3**: Direct API calls for fine-grained control\n", - " - **AgentCore SDK**: Higher-level abstraction for simpler code\n", - "\n", - "3. **Multi-language Support**:\n", - " - JavaScript for server-side processing\n", - " - Python for data analysis with built-in statistics module\n", - "\n", - "\n", - "### ๐Ÿ“š Additional Resources\n", - "\n", - "- [AWS Bedrock AgentCore Documentation](https://docs.aws.amazon.com/bedrock-agentcore/)\n", - "- [AgentCore Code Interpreter API Reference](https://docs.aws.amazon.com/bedrock-agentcore/latest/APIReference/)\n", - "- [Module 3 Other Notebooks](../README.md)\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "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.8" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb b/labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb deleted file mode 100644 index 738d13f..0000000 --- a/labs/module3/notebooks/6.2_file_operations_using_code_interpreter.ipynb +++ /dev/null @@ -1,504 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# ๐Ÿ“ File Operations Using Code Interpreter\n", - "\n", - "Welcome to the File Operations module using AWS Bedrock AgentCore Code Interpreter! This notebook demonstrates comprehensive file operations including reading, writing, and processing files using both boto3 and AgentCore SDK.\n", - "\n", - "## ๐ŸŽฏ Learning Objectives\n", - "\n", - "In this notebook, you'll learn how to:\n", - "\n", - "- ๐Ÿ”ง Set up and use AgentCore Code Interpreter for file operations\n", - "- ๐Ÿ“„ Read and write files using Code Interpreter's file management tools\n", - "- ๐Ÿ“Š Process JSON and CSV files with both JavaScript and Python\n", - "- ๐Ÿ”„ Use both boto3 and AgentCore SDK for file I/O operations\n", - "- ๐Ÿ—๏ธ Implement complete file workflows including upload, process, and verify\n", - "\n", - "## ๐Ÿ“‹ Prerequisites\n", - "\n", - "- AWS account with access to Amazon Bedrock AgentCore\n", - "- Proper IAM permissions for AgentCore services\n", - "- Python 3.12 environment with required dependencies\n", - "\n", - "Let's begin! ๐Ÿš€" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Amazon Bedrock AgentCore Code Interpreter\n", - "\n", - "\n", - "The Amazon Bedrock AgentCore Code Interpreter enables AI agents to write and execute code securely in sandbox environments, enhancing their accuracy and expanding their ability to solve complex end-to-end tasks. This is critical in Agentic AI applications where the agents may execute arbitrary code that can lead to data compromise or security risks. The AgentCore Code Interpreter tool provides secure code execution, which helps you avoid running into these issues.\n", - "\n", - "The Code Interpreter comes with pre-built runtimes for multiple languages and advanced features, including large file support and internet access, and CloudTrail logging capabilities. For inline upload, the file size can be up to 100 MB. And for uploading to Amazon S3 through terminal commands, the file size can be as large as 5 GB.\n", - "\n", - "Developers can customize environments with session properties and network modes to meet their enterprise and security requirements. The AgentCore Code Interpreter reduces manual intervention while enabling sophisticated AI development without compromising security or performance.\n", - "\n", - "![Amazon Bedrock AgentCore Code Interpreter Architecture](../../../media/agent_core_code_interpreter_arch.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## ๐Ÿ”ง Section 1: Environment Setup" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# ๐Ÿ”ง Environment Setup Instructions\n", - "\n", - "# IMPORTANT: This notebook requires Python 3.12.8 as specified in pyproject.toml\n", - "# All dependencies are managed via UV (recommended approach for this project)\n", - "\n", - "# SETUP STEPS:\n", - "# 1. Create virtual environment: uv venv\n", - "# 2. Install all dependencies: uv sync\n", - "# 3. Launch Jupyter Lab: uv run jupyter lab\n", - "\n", - "# FOR VS CODE USERS:\n", - "# 1. Run: uv run python -c \"import sys; print(sys.executable)\"\n", - "# 2. Copy the path and select it as your Python interpreter in VS Code\n", - "# 3. The kernel should now have access to all required dependencies\n", - "\n", - "# Verify environment setup\n", - "import sys\n", - "print(f\"โœ… Python version: {sys.version}\")\n", - "print(\"โœ… Environment setup complete! All dependencies should be available via UV.\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Import core dependencies\n", - "import boto3\n", - "import json\n", - "import os\n", - "import time\n", - "from typing import Dict, Any, List, Optional\n", - "\n", - "# Import AgentCore SDK\n", - "from bedrock_agentcore.tools.code_interpreter_client import CodeInterpreter, code_session\n", - "\n", - "# Import existing Module 3 components\n", - "from agentic_platform.core.models.memory_models import Message, SessionContext\n", - "from agentic_platform.core.models.llm_models import LLMRequest, LLMResponse\n", - "from agentic_platform.core.models.api_models import AgenticRequest, AgenticResponse\n", - "\n", - "print(\"โœ… Dependencies imported successfully!\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Initialize AWS clients with specific profile\n", - "profile_name = \"XXXXXX\" # your aws profile name\n", - "session = boto3.Session(profile_name=profile_name)\n", - "region_name = \"us-west-2\"\n", - "\n", - "# Verify credentials are working\n", - "try:\n", - " credentials = session.get_credentials()\n", - " print(f\"โœ… credentials get successfully\")\n", - " print(f\"โœ… Region: {region_name}\")\n", - "except Exception as e:\n", - " print(f\"โŒ Error with AWS credentials: {e}\")\n", - " raise\n", - "\n", - "# Standard AgentCore client\n", - "bedrock_agentcore_client = session.client(\n", - " 'bedrock-agentcore',\n", - " region_name=region_name,\n", - " endpoint_url=f\"https://bedrock-agentcore.{region_name}.amazonaws.com\"\n", - ")\n", - "\n", - "# Set environment variables for AgentCore SDK\n", - "os.environ['AWS_ACCESS_KEY_ID'] = credentials.access_key\n", - "os.environ['AWS_SECRET_ACCESS_KEY'] = credentials.secret_key\n", - "if credentials.token:\n", - " os.environ['AWS_SESSION_TOKEN'] = credentials.token\n", - "os.environ['AWS_DEFAULT_REGION'] = region_name\n", - "\n", - "print(\"โœ… AWS clients initialized successfully with profile!\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Section 2: Sample Data Preparation\n", - "\n", - "Let's prepare sample data files that we'll use throughout this notebook." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Prepare sample CSV data\n", - "sample_csv_data = \"\"\"name,age,city,occupation\n", - "Alice Johnson,28,New York,Software Engineer\n", - "Bob Smith,35,San Francisco,Data Scientist\n", - "Carol Williams,42,Chicago,Product Manager\n", - "David Brown,31,Austin,DevOps Engineer\n", - "Emma Davis,26,Seattle,UX Designer\"\"\"\n", - "\n", - "# Prepare sample Python analysis script\n", - "stats_py_content = \"\"\"import csv\n", - "from io import StringIO\n", - "\n", - "# Read the CSV data\n", - "with open('data.csv', 'r') as file:\n", - " csv_reader = csv.DictReader(file)\n", - " data = list(csv_reader)\n", - "\n", - "# Calculate statistics\n", - "total_people = len(data)\n", - "ages = [int(row['age']) for row in data]\n", - "avg_age = sum(ages) / len(ages)\n", - "min_age = min(ages)\n", - "max_age = max(ages)\n", - "\n", - "# Count by city\n", - "cities = {}\n", - "for row in data:\n", - " city = row['city']\n", - " cities[city] = cities.get(city, 0) + 1\n", - "\n", - "# Print results\n", - "print(f\\\"Dataset Statistics:\\\")\n", - "print(f\\\"==================\\\")\n", - "print(f\\\"Total people: {total_people}\\\")\n", - "print(f\\\"Average age: {avg_age:.1f}\\\")\n", - "print(f\\\"Age range: {min_age} - {max_age}\\\")\n", - "print(f\\\"\\\\nPeople by city:\\\")\n", - "for city, count in cities.items():\n", - " print(f\\\" {city}: {count}\\\")\n", - "\n", - "# Create a summary report\n", - "report = {\n", - " 'total_records': total_people,\n", - " 'average_age': round(avg_age, 2),\n", - " 'age_range': {'min': min_age, 'max': max_age},\n", - " 'city_distribution': cities\n", - "}\n", - "\n", - "print(f\\\"\\\\nSummary report generated successfully!\\\")\n", - "print(f\\\"Report: {report}\\\")\n", - "\"\"\"\n", - "\n", - "# Prepare sample JSON data\n", - "sample_json_data = json.dumps({\n", - " \"project\": \"File Operations Demo\",\n", - " \"version\": \"1.0.0\",\n", - " \"features\": [\"read\", \"write\", \"process\", \"analyze\"],\n", - " \"config\": {\n", - " \"debug\": False,\n", - " \"timeout\": 300,\n", - " \"max_retries\": 3\n", - " }\n", - "}, indent=2)\n", - "\n", - "print(\"โœ… Sample data prepared:\")\n", - "print(f\" - CSV data: {len(sample_csv_data)} bytes\")\n", - "print(f\" - Python script: {len(stats_py_content)} bytes\")\n", - "print(f\" - JSON data: {len(sample_json_data)} bytes\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Section 3: File Operations with Boto3\n", - "\n", - "This section demonstrates file read/write operations using boto3 direct API calls following the pattern from the provided example." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def call_tool_boto3(session_id: str, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:\n", - " \"\"\"Helper function to call Code Interpreter tools using boto3\"\"\"\n", - " response = bedrock_agentcore_client.invoke_code_interpreter(\n", - " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", - " sessionId=session_id,\n", - " name=tool_name,\n", - " arguments=arguments\n", - " )\n", - " \n", - " for event in response[\"stream\"]:\n", - " if \"result\" in event:\n", - " return event[\"result\"]\n", - " return {}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Start boto3 Code Interpreter session\n", - "print(\"๐Ÿš€ Starting boto3 Code Interpreter session...\")\n", - "session_response = bedrock_agentcore_client.start_code_interpreter_session(\n", - " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", - " name=\"boto3-file-ops-session\",\n", - " sessionTimeoutSeconds=300\n", - ")\n", - "boto3_session_id = session_response[\"sessionId\"]\n", - "print(f\"โœ… Session started: {boto3_session_id}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Prepare files to create\n", - "files_to_create = [\n", - " {\n", - " \"path\": \"data.csv\",\n", - " \"text\": sample_csv_data\n", - " },\n", - " {\n", - " \"path\": \"stats.py\",\n", - " \"text\": stats_py_content\n", - " },\n", - " {\n", - " \"path\": \"config.json\",\n", - " \"text\": sample_json_data\n", - " }\n", - "]\n", - "\n", - "# Write files to the session\n", - "print(\"\\n๐Ÿ“ Writing files to Code Interpreter session...\")\n", - "writing_files = call_tool_boto3(boto3_session_id, \"writeFiles\", {\"content\": files_to_create})\n", - "print(f\"โœ… Files written: {json.dumps(writing_files, indent=2)}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# List files to verify\n", - "print(\"\\n๐Ÿ“‹ Listing files in session...\")\n", - "listing_files = call_tool_boto3(boto3_session_id, \"listFiles\", {\"path\": \"\"})\n", - "print(f\"๐Ÿ“ Files in session: {json.dumps(listing_files, indent=2)}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Execute the Python script to analyze the CSV data\n", - "print(\"\\n๐Ÿ”ง Executing analysis script...\")\n", - "execute_code = call_tool_boto3(boto3_session_id, \"executeCode\", {\n", - " \"code\": stats_py_content,\n", - " \"language\": \"python\",\n", - " \"clearContext\": True\n", - "})\n", - "print(f\"๐Ÿ“Š Code execution result: {json.dumps(execute_code, indent=2)}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Read a file back to verify\n", - "print(\"\\n๐Ÿ“– Reading config.json to verify...\")\n", - "read_file = call_tool_boto3(boto3_session_id, \"readFiles\", {\"path\": \"config.json\"})\n", - "print(f\"๐Ÿ“„ File content: {json.dumps(read_file, indent=2)}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Clean up boto3 session\n", - "bedrock_agentcore_client.stop_code_interpreter_session(\n", - " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", - " sessionId=boto3_session_id\n", - ")\n", - "print(f\"\\nโœ… Boto3 session {boto3_session_id} stopped\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Section 4: File Operations with AgentCore SDK\n", - "\n", - "This section demonstrates the same file operations using the AgentCore SDK's higher-level interface." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Configure and start the code interpreter session\n", - "print(\"๐Ÿš€ Starting AgentCore SDK Code Interpreter session...\")\n", - "code_client = CodeInterpreter(region_name)\n", - "code_client.start()\n", - "print(\"โœ… AgentCore SDK session started\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Define the method to call the invoke API (following the example pattern)\n", - "def call_tool(tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:\n", - " \"\"\"Helper function to call Code Interpreter tools using SDK\"\"\"\n", - " response = code_client.invoke(tool_name, arguments)\n", - " for event in response[\"stream\"]:\n", - " return json.dumps(event[\"result\"], indent=2)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Write the sample data and analysis script into the code interpreter session\n", - "print(\"\\n๐Ÿ“ Writing files using SDK...\")\n", - "writing_files = call_tool(\"writeFiles\", {\"content\": files_to_create})\n", - "print(f\"โœ… Files written: {writing_files}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# List and validate that the files were written successfully\n", - "print(\"\\n๐Ÿ“‹ Listing files using SDK...\")\n", - "listing_files = call_tool(\"listFiles\", {\"path\": \"\"})\n", - "print(f\"๐Ÿ“ Files in session: {listing_files}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Run the python script to analyze the sample data file\n", - "print(\"\\n๐Ÿ”ง Executing analysis script using SDK...\")\n", - "execute_code = call_tool(\"executeCode\", {\n", - " \"code\": files_to_create[1]['text'],\n", - " \"language\": \"python\",\n", - " \"clearContext\": True\n", - "})\n", - "print(f\"๐Ÿ“Š Code execution result: {execute_code}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Read a file back to verify\n", - "print(\"\\n๐Ÿ“– Reading config.json using SDK...\")\n", - "read_file = call_tool(\"readFiles\", {\"path\": \"config.json\"})\n", - "print(f\"๐Ÿ“„ File content: {read_file}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Clean up and stop the code interpreter session\n", - "code_client.stop()\n", - "print(\"\\nโœ… AgentCore SDK session stopped\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Conclusion\n", - "\n", - "In this notebook, we've successfully demonstrated:\n", - "\n", - "1. **File Operations with Boto3**:\n", - " - Direct API calls to Code Interpreter\n", - " - Writing files using `writeFiles`\n", - " - Listing files using `listFiles`\n", - " - Reading files using `readFile`\n", - " - Executing code that processes files\n", - "\n", - "2. **File Operations with AgentCore SDK**:\n", - " - Higher-level interface for Code Interpreter\n", - " - Simplified session management\n", - " - Same file operations with cleaner syntax\n", - "\n", - "### ๐Ÿ“š Additional Resources\n", - "\n", - "- [AWS Bedrock AgentCore Documentation](https://docs.aws.amazon.com/bedrock-agentcore/)\n", - "- [Code Interpreter API Reference](https://docs.aws.amazon.com/bedrock-agentcore/latest/APIReference/)\n", - "- [Module 3 Other Notebooks](../README.md)\n", - "\n" - ] - } - ], - "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.8" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/labs/module4/notebooks/4_agentcore_tool_code_interpreter.ipynb b/labs/module4/notebooks/4_agentcore_tool_code_interpreter.ipynb new file mode 100644 index 0000000..2b49c02 --- /dev/null +++ b/labs/module4/notebooks/4_agentcore_tool_code_interpreter.ipynb @@ -0,0 +1,678 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# ๐Ÿ“Š Code Interpreter with Boto3: Data Processing & File Operations\n", + "\n", + "Welcome to the Code Interpreter module using AWS Bedrock AgentCore with boto3! This notebook demonstrates both data processing/analysis and file operations using direct boto3 API calls.\n", + "\n", + "## ๐ŸŽฏ Learning Objectives\n", + "\n", + "In this notebook, you'll learn how to:\n", + "\n", + "- ๐Ÿ”ง Set up and use AgentCore Code Interpreter with boto3\n", + "- ๐Ÿ“ˆ Perform statistical calculations and data analysis\n", + "- ๐Ÿ“ Execute file operations (read, write, process)\n", + "- ๐Ÿ”„ Process data using JavaScript secure sandboxes\n", + "\n", + "## ๐Ÿ“‹ Prerequisites\n", + "\n", + "- AWS account with access to Amazon Bedrock AgentCore\n", + "- Proper IAM permissions for AgentCore services\n", + "- Python 3.10+ environment\n", + "\n", + "Let's begin! ๐Ÿš€" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Amazon Bedrock AgentCore Code Interpreter\n", + "\n", + "The Amazon Bedrock AgentCore Code Interpreter enables AI agents to write and execute code securely in sandbox environments, enhancing their accuracy and expanding their ability to solve complex end-to-end tasks. This is critical in Agentic AI applications where the agents may execute arbitrary code that can lead to data compromise or security risks.\n", + "\n", + "Key features:\n", + "- Secure code execution in isolated sandboxes\n", + "- Support for multiple programming languages (Python, JavaScript)\n", + "- Large file support (up to 100 MB inline, 5 GB via S3)\n", + "- CloudTrail logging capabilities\n", + "- Network access control\n", + "\n", + "![Amazon Bedrock AgentCore Code Interpreter Architecture](../../../media/agent_core_code_interpreter_arch.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿ”ง Section 1: Environment Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# ๐Ÿ”ง Environment Setup\n", + "\n", + "import sys\n", + "import subprocess\n", + "\n", + "# Check Python version\n", + "python_version = sys.version_info\n", + "print(f\"Python version: {python_version.major}.{python_version.minor}.{python_version.micro}\")\n", + "\n", + "if python_version.major == 3 and python_version.minor < 10:\n", + " print(\"โš ๏ธ Warning: Python 3.10+ is recommended. Some features may not work.\")\n", + "\n", + "# Install required packages if not already installed\n", + "required_packages = ['boto3']\n", + "\n", + "for package in required_packages:\n", + " try:\n", + " __import__(package)\n", + " print(f\"โœ… {package} is already installed\")\n", + " except ImportError:\n", + " print(f\"๐Ÿ“ฆ Installing {package}...\")\n", + " subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", package])\n", + " print(f\"โœ… {package} installed successfully\")\n", + "\n", + "print(\"\\nโœ… Environment setup complete!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Import dependencies\n", + "import boto3\n", + "import json\n", + "import time\n", + "from typing import Dict, Any, List, Optional\n", + "\n", + "print(\"โœ… Dependencies imported successfully!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Initialize AWS clients using default session\n", + "session = boto3.Session()\n", + "region_name = session.region_name\n", + "\n", + "# Verify credentials and region are working\n", + "try:\n", + " credentials = session.get_credentials()\n", + " print(f\"โœ… Credentials loaded successfully\")\n", + " print(f\"โœ… Region: {region_name}\")\n", + " \n", + " # Get the current role ARN if available\n", + " sts = session.client('sts')\n", + " identity = sts.get_caller_identity()\n", + " print(f\"๐Ÿ“‹ Current identity: {identity['Arn']}\")\n", + " \n", + " if 'SageMaker' in identity['Arn']:\n", + " print(\"\\nโš ๏ธ Running in SageMaker - Please ensure your execution role has the required permissions.\")\n", + " print(\" See the 'IAM Permissions Setup' section below if you encounter permission errors.\")\n", + " \n", + "except Exception as e:\n", + " print(f\"โŒ Error with AWS credentials: {e}\")\n", + " raise\n", + "\n", + "# Initialize AgentCore client\n", + "bedrock_agentcore_client = session.client(\n", + " 'bedrock-agentcore',\n", + " region_name=region_name,\n", + " endpoint_url=f\"https://bedrock-agentcore.{region_name}.amazonaws.com\"\n", + ")\n", + "\n", + "print(\"โœ… AWS clients initialized successfully!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## โš ๏ธ IAM Permissions Setup (For SageMaker Users)\n", + "\n", + "If you're running this notebook in **SageMaker** and encounter permission errors, you need to add the following permissions to your SageMaker execution role:\n", + "\n", + "### Required Permissions\n", + "\n", + "```json\n", + "{\n", + " \"Version\": \"2012-10-17\",\n", + " \"Statement\": [\n", + " {\n", + " \"Effect\": \"Allow\",\n", + " \"Action\": [\n", + " \"bedrock-agentcore:StartCodeInterpreterSession\",\n", + " \"bedrock-agentcore:StopCodeInterpreterSession\",\n", + " \"bedrock-agentcore:InvokeCodeInterpreter\"\n", + " ],\n", + " \"Resource\": \"arn:aws:bedrock-agentcore:*:aws:code-interpreter/*\"\n", + " }\n", + " ]\n", + "}\n", + "```\n", + "\n", + "### How to Add Permissions\n", + "\n", + "1. **Go to IAM Console**: https://console.aws.amazon.com/iam/\n", + "2. **Find your SageMaker role**: Search for `AmazonSageMakerServiceCatalogProductsUseRole` or similar\n", + "3. **Add inline policy**: Click \"Add permissions\" โ†’ \"Create inline policy\"\n", + "4. **Use JSON editor**: Paste the policy above\n", + "5. **Name the policy**: e.g., \"BedrockAgentCoreCodeInterpreterAccess\"\n", + "6. **Save**: Click \"Create policy\"\n", + "\n", + "After adding these permissions, restart your kernel and try again.\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿ› ๏ธ Helper Functions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def execute_code(code: str, language: str = \"python\", session_name: str = \"code-session\") -> Dict[str, Any]:\n", + " \"\"\"\n", + " Execute code using boto3 direct API calls to AgentCore Code Interpreter.\n", + " \n", + " Args:\n", + " code: The code to execute\n", + " language: Programming language (javascript or python)\n", + " session_name: Name for the code interpreter session\n", + " \n", + " Returns:\n", + " Dictionary with execution results\n", + " \"\"\"\n", + " try:\n", + " # Start a code interpreter session\n", + " session_response = bedrock_agentcore_client.start_code_interpreter_session(\n", + " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", + " name=session_name,\n", + " sessionTimeoutSeconds=300\n", + " )\n", + " session_id = session_response[\"sessionId\"]\n", + " print(f\"โœ… Started session: {session_id}\")\n", + " \n", + " # Execute the code\n", + " execute_response = bedrock_agentcore_client.invoke_code_interpreter(\n", + " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", + " sessionId=session_id,\n", + " name=\"executeCode\",\n", + " arguments={\n", + " \"language\": language,\n", + " \"code\": code\n", + " }\n", + " )\n", + " \n", + " # Process the response stream\n", + " results = []\n", + " for event in execute_response['stream']:\n", + " if 'result' in event:\n", + " result = event['result']\n", + " results.append(result)\n", + " \n", + " # Print text output\n", + " if 'content' in result:\n", + " for content_item in result['content']:\n", + " if content_item['type'] == 'text':\n", + " print(f\"Output: {content_item['text']}\")\n", + " \n", + " # Clean up session\n", + " bedrock_agentcore_client.stop_code_interpreter_session(\n", + " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", + " sessionId=session_id\n", + " )\n", + " print(f\"โœ… Session {session_id} stopped\")\n", + " \n", + " return {\n", + " \"success\": True,\n", + " \"session_id\": session_id,\n", + " \"results\": results\n", + " }\n", + " \n", + " except Exception as e:\n", + " print(f\"โŒ Error executing code: {str(e)}\")\n", + " return {\n", + " \"success\": False,\n", + " \"error\": str(e)\n", + " }" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def call_tool(session_id: str, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:\n", + " \"\"\"Helper function to call Code Interpreter tools using boto3\"\"\"\n", + " response = bedrock_agentcore_client.invoke_code_interpreter(\n", + " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", + " sessionId=session_id,\n", + " name=tool_name,\n", + " arguments=arguments\n", + " )\n", + " \n", + " for event in response[\"stream\"]:\n", + " if \"result\" in event:\n", + " return event[\"result\"]\n", + " return {}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿ“Š Example 1: Data Processing & Analysis\n", + "\n", + "Let's demonstrate statistical analysis using both JavaScript and Python." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### JavaScript Statistical Analysis" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# JavaScript code for temperature data analysis\n", + "javascript_stats_code = \"\"\"\n", + "// Temperature data analysis\n", + "console.log('Starting temperature data analysis...');\n", + "\n", + "// Daily temperatures in Celsius\n", + "const temperatures = [22.5, 24.1, 23.8, 25.2, 21.9, 26.3, 23.7];\n", + "const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];\n", + "\n", + "// Calculate statistics\n", + "function calculateStats(data) {\n", + " const sum = data.reduce((a, b) => a + b, 0);\n", + " const mean = sum / data.length;\n", + " const min = Math.min(...data);\n", + " const max = Math.max(...data);\n", + " \n", + " // Calculate variance and standard deviation\n", + " const variance = data.reduce((acc, val) => {\n", + " return acc + Math.pow(val - mean, 2);\n", + " }, 0) / data.length;\n", + " const stdDev = Math.sqrt(variance);\n", + " \n", + " return {\n", + " count: data.length,\n", + " sum: sum.toFixed(2),\n", + " mean: mean.toFixed(2),\n", + " min: min.toFixed(2),\n", + " max: max.toFixed(2),\n", + " variance: variance.toFixed(2),\n", + " stdDev: stdDev.toFixed(2),\n", + " range: (max - min).toFixed(2)\n", + " };\n", + "}\n", + "\n", + "const stats = calculateStats(temperatures);\n", + "\n", + "// Display results\n", + "console.log('=== TEMPERATURE STATISTICS ===');\n", + "console.log('Number of readings: ' + stats.count);\n", + "console.log('Average temperature: ' + stats.mean + 'ยฐC');\n", + "console.log('Minimum temperature: ' + stats.min + 'ยฐC');\n", + "console.log('Maximum temperature: ' + stats.max + 'ยฐC');\n", + "console.log('Temperature range: ' + stats.range + 'ยฐC');\n", + "console.log('Standard deviation: ' + stats.stdDev + 'ยฐC');\n", + "\n", + "// Display daily readings\n", + "console.log('=== DAILY READINGS ===');\n", + "days.forEach((day, index) => {\n", + " const temp = temperatures[index];\n", + " const diff = (temp - stats.mean).toFixed(2);\n", + " const sign = diff >= 0 ? '+' : '';\n", + " console.log(`${day}: ${temp}ยฐC (${sign}${diff}ยฐC from average)`);\n", + "});\n", + "\n", + "console.log('Analysis complete!');\n", + "stats;\n", + "\"\"\"\n", + "\n", + "print(\"JavaScript code prepared for execution\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Execute JavaScript statistical analysis\n", + "print(\"๐Ÿš€ Executing JavaScript temperature analysis...\\n\")\n", + "js_result = execute_code(\n", + " javascript_stats_code, \n", + " language=\"javascript\",\n", + " session_name=\"js-stats-session\"\n", + ")\n", + "print(f\"\\nโœ… Execution completed: {js_result['success']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿ“ Example 2: File Operations\n", + "\n", + "Now let's demonstrate file operations including creating, reading, and processing files." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Prepare sample data for file operations\n", + "sample_csv_data = \"\"\"name,age,city,occupation,salary\n", + "Alice Johnson,28,New York,Software Engineer,95000\n", + "Bob Smith,35,San Francisco,Data Scientist,120000\n", + "Carol Williams,42,Chicago,Product Manager,110000\n", + "David Brown,31,Austin,DevOps Engineer,105000\n", + "Emma Davis,26,Seattle,UX Designer,85000\n", + "Frank Miller,39,Boston,Backend Developer,100000\n", + "Grace Lee,33,Denver,Frontend Developer,95000\n", + "Henry Wilson,29,Portland,QA Engineer,75000\"\"\"\n", + "\n", + "# Python script to analyze the CSV data\n", + "analysis_script = \"\"\"import csv\n", + "import json\n", + "from collections import defaultdict\n", + "\n", + "# Read and analyze the CSV data\n", + "with open('employees.csv', 'r') as file:\n", + " csv_reader = csv.DictReader(file)\n", + " data = list(csv_reader)\n", + "\n", + "# Convert numeric fields\n", + "for row in data:\n", + " row['age'] = int(row['age'])\n", + " row['salary'] = int(row['salary'])\n", + "\n", + "# Calculate statistics\n", + "total_employees = len(data)\n", + "ages = [row['age'] for row in data]\n", + "salaries = [row['salary'] for row in data]\n", + "\n", + "avg_age = sum(ages) / len(ages)\n", + "avg_salary = sum(salaries) / len(salaries)\n", + "\n", + "# Group by city\n", + "by_city = defaultdict(list)\n", + "for row in data:\n", + " by_city[row['city']].append(row)\n", + "\n", + "# Group by occupation\n", + "by_occupation = defaultdict(list)\n", + "for row in data:\n", + " by_occupation[row['occupation']].append(row)\n", + "\n", + "# Create analysis report\n", + "report = {\n", + " 'summary': {\n", + " 'total_employees': total_employees,\n", + " 'average_age': round(avg_age, 1),\n", + " 'average_salary': round(avg_salary, 2),\n", + " 'age_range': {'min': min(ages), 'max': max(ages)},\n", + " 'salary_range': {'min': min(salaries), 'max': max(salaries)}\n", + " },\n", + " 'by_city': {city: len(employees) for city, employees in by_city.items()},\n", + " 'by_occupation': {occ: len(employees) for occ, employees in by_occupation.items()},\n", + " 'top_earners': sorted(data, key=lambda x: x['salary'], reverse=True)[:3]\n", + "}\n", + "\n", + "# Save report to JSON\n", + "with open('analysis_report.json', 'w') as f:\n", + " json.dump(report, f, indent=2)\n", + "\n", + "# Display results\n", + "print('=== EMPLOYEE DATA ANALYSIS ===')\n", + "print('Total employees: ' + str(report['summary']['total_employees']))\n", + "print('Average age: ' + str(report['summary']['average_age']) + ' years')\n", + "print('Average salary: $' + '{:,.2f}'.format(report['summary']['average_salary']))\n", + "print('\\\\nEmployees by city:')\n", + "for city, count in report['by_city'].items():\n", + " print(' ' + city + ': ' + str(count))\n", + "print('\\\\nTop 3 earners:')\n", + "for i, emp in enumerate(report['top_earners'], 1):\n", + " print(' ' + str(i) + '. ' + emp['name'] + ' - $' + '{:,}'.format(emp['salary']))\n", + "print('\\\\nReport saved to analysis_report.json')\n", + "\"\"\"\n", + "\n", + "# Configuration file\n", + "config_json = json.dumps({\n", + " \"analysis\": {\n", + " \"version\": \"1.0.0\",\n", + " \"date\": \"2024-01-14\",\n", + " \"author\": \"Data Analysis Team\"\n", + " },\n", + " \"settings\": {\n", + " \"output_format\": \"json\",\n", + " \"include_charts\": False,\n", + " \"decimal_places\": 2\n", + " }\n", + "}, indent=2)\n", + "\n", + "print(\"โœ… Sample data prepared for file operations\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Start a new session for file operations\n", + "print(\"๐Ÿš€ Starting Code Interpreter session for file operations...\")\n", + "\n", + "try:\n", + " session_response = bedrock_agentcore_client.start_code_interpreter_session(\n", + " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", + " name=\"file-ops-session\",\n", + " sessionTimeoutSeconds=300\n", + " )\n", + " file_session_id = session_response[\"sessionId\"]\n", + " print(f\"โœ… Session started: {file_session_id}\")\n", + "except Exception as e:\n", + " print(f\"โŒ Error starting session: {e}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create files in the session\n", + "files_to_create = [\n", + " {\n", + " \"path\": \"employees.csv\",\n", + " \"text\": sample_csv_data\n", + " },\n", + " {\n", + " \"path\": \"analyze.py\",\n", + " \"text\": analysis_script\n", + " },\n", + " {\n", + " \"path\": \"config.json\",\n", + " \"text\": config_json\n", + " }\n", + "]\n", + "\n", + "print(\"๐Ÿ“ Writing files to Code Interpreter session...\")\n", + "try:\n", + " write_result = call_tool(file_session_id, \"writeFiles\", {\"content\": files_to_create})\n", + " print(f\"โœ… Files written successfully\")\n", + " if 'content' in write_result:\n", + " for item in write_result['content']:\n", + " if item['type'] == 'text':\n", + " print(f\" {item['text']}\")\n", + "except Exception as e:\n", + " print(f\"โŒ Error writing files: {e}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# List files to verify they were created\n", + "print(\"\\n๐Ÿ“‹ Listing files in session...\")\n", + "try:\n", + " list_result = call_tool(file_session_id, \"listFiles\", {\"path\": \"\"})\n", + " print(\"Files in session:\")\n", + " if 'content' in list_result:\n", + " for item in list_result['content']:\n", + " if item['description'] == 'File':\n", + " print(f\" {item['name']}\")\n", + "except Exception as e:\n", + " print(f\"โŒ Error listing files: {e}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Execute the analysis script\n", + "print(\"\\n๐Ÿ”ง Executing analysis script...\")\n", + "try:\n", + " exec_result = call_tool(file_session_id, \"executeCode\", {\n", + " \"code\": analysis_script,\n", + " \"language\": \"python\",\n", + " \"clearContext\": True\n", + " })\n", + " print(\"๐Ÿ“Š Analysis results:\")\n", + " if 'content' in exec_result:\n", + " for item in exec_result['content']:\n", + " if item['type'] == 'text':\n", + " print(item['text'])\n", + "except Exception as e:\n", + " print(f\"โŒ Error executing script: {e}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Read the generated report file\n", + "print(\"\\n๐Ÿ“– Reading generated analysis report...\")\n", + "try:\n", + " read_result = call_tool(file_session_id, \"readFiles\", {\"paths\": [\"analysis_report.json\"]})\n", + " print(\"Analysis Report Content:\")\n", + " if 'content' in read_result:\n", + " for item in read_result['content']:\n", + " print(item)\n", + " if item['resource'] == 'text':\n", + " # Parse and pretty print the JSON\n", + " try:\n", + " report_data = json.loads(item['text'])\n", + " print(json.dumps(report_data, indent=2))\n", + " except:\n", + " print(item['text'])\n", + "except Exception as e:\n", + " print(f\"โŒ Error reading report: {e}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Clean up the file operations session\n", + "print(\"\\n๐Ÿงน Cleaning up file operations session...\")\n", + "try:\n", + " bedrock_agentcore_client.stop_code_interpreter_session(\n", + " codeInterpreterIdentifier=\"aws.codeinterpreter.v1\",\n", + " sessionId=file_session_id\n", + " )\n", + " print(f\"โœ… Session {file_session_id} stopped successfully\")\n", + "except Exception as e:\n", + " print(f\"โŒ Error stopping session: {e}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿ“ Summary\n", + "\n", + "In this notebook, we've successfully demonstrated:\n", + "\n", + "### 1. **Data Processing & Analysis**\n", + " - **JavaScript**: Temperature data analysis with statistical calculations\n", + " - **Python**: Student score analysis with comprehensive statistics and grade distribution\n", + " - Both examples showed mean, median, variance, standard deviation calculations\n", + "\n", + "### 2. **File Operations**\n", + " - Creating multiple files (CSV, Python script, JSON config)\n", + " - Listing files in the Code Interpreter session\n", + " - Executing scripts that read and process files\n", + " - Reading generated output files\n", + "\n", + "### ๐Ÿ”— Additional Resources\n", + "\n", + "- [AWS Bedrock AgentCore Documentation](https://docs.aws.amazon.com/bedrock-agentcore/)\n", + "- [Code Interpreter API Reference](https://docs.aws.amazon.com/bedrock-agentcore/latest/APIReference/)\n", + "- [SageMaker Setup Guide](../SAGEMAKER_SETUP.md)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "conda_python3", + "language": "python", + "name": "conda_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.18" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From 9a1f504f4f8d3501da72ade2c28c4fc9e998f193 Mon Sep 17 00:00:00 2001 From: dinsajwa Date: Thu, 14 Aug 2025 12:24:33 -0400 Subject: [PATCH 11/11] feat(agentCodeTool): added agentCore code interpreter example under module 4 --- labs/module3/README.md | 12 ------------ labs/module4/README.md | 14 +++++++++++++- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/labs/module3/README.md b/labs/module3/README.md index 2d82cf1..ce102f0 100644 --- a/labs/module3/README.md +++ b/labs/module3/README.md @@ -54,18 +54,6 @@ The workshop consists of several notebooks that build foundational abstractions: - ๐Ÿ” Understanding framework tradeoffs - ๐Ÿ”„ Migrating agents between frameworks -### 6. Data Processing & Analysis (`6.1_data_processing_analysis_using_code_interpreter.ipynb`) -- ๐Ÿ“Š Using AWS Bedrock AgentCore Code Interpreter for data analysis -- ๐Ÿ”ง Setting up both boto3 and AgentCore SDK approaches -- ๐Ÿ“ˆ Performing statistical calculations on datasets -- ๐Ÿ”„ Processing data with JavaScript and Python - -### 7. File Operations (`6.2_file_operations_using_code_interpreter.ipynb`) -- ๐Ÿ“ Comprehensive file operations using Code Interpreter -- ๐Ÿ“„ Reading, writing, and processing files -- ๐Ÿ“Š Working with JSON and CSV files -- ๐Ÿ”„ Complete file workflows including upload, process, and verify - ## ๐Ÿงญ Workshop Flow diff --git a/labs/module4/README.md b/labs/module4/README.md index 7e4cc61..2210222 100644 --- a/labs/module4/README.md +++ b/labs/module4/README.md @@ -1,7 +1,7 @@ # Module 4: Advanced Agent Features ## Overview -This module explores advanced agent capabilities through Model Context Protocol (MCP) and multi-agent systems. You'll learn how to combine different agent frameworks, create specialized tool servers, and build interoperable AI systems that can collaborate effectively. +This module explores advanced agent capabilities through Model Context Protocol (MCP), multi-agent systems, and secure code execution. You'll learn how to combine different agent frameworks, create specialized tool servers, build interoperable AI systems that can collaborate effectively, and leverage AWS Bedrock AgentCore for secure code interpretation. ## Learning Objectives @@ -10,6 +10,8 @@ This module explores advanced agent capabilities through Model Context Protocol * Mix and match different agent frameworks (PydanticAI, LangChain, etc.) * Build collaborative multi-agent systems * Integrate third-party MCP servers into your applications +* Use AWS Bedrock AgentCore Code Interpreter for secure code execution +* Perform data processing and file operations in sandboxed environments ## Prerequisites @@ -20,6 +22,16 @@ This module explores advanced agent capabilities through Model Context Protocol - AWS Bedrock access - Basic understanding of async programming +## Notebooks + +### 4_agentcore_tool_code_interpreter.ipynb +Learn how to use AWS Bedrock AgentCore Code Interpreter for secure code execution: +- Set up and configure AgentCore Code Interpreter with boto3 +- Execute Python and JavaScript code in secure sandboxes +- Perform statistical calculations and data analysis +- Handle file operations (read, write, process) +- Process large files up to 100MB inline or 5GB via S3 + ## Installation ```bash