diff --git a/03-integrations/vector-stores/elasticsearch/01-gateway-target-lambda-elasticsearch.ipynb b/03-integrations/vector-stores/elasticsearch/01-gateway-target-lambda-elasticsearch.ipynb new file mode 100644 index 000000000..5c77ef63e --- /dev/null +++ b/03-integrations/vector-stores/elasticsearch/01-gateway-target-lambda-elasticsearch.ipynb @@ -0,0 +1,1866 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4ba51a29-a566-4b5a-97f0-10e634567e40", + "metadata": {}, + "source": [ + "# Connecting AgentCore Runtime Agents to Elasticsearch via AgentCore Gateway\n", + "\n", + "## Overview\n", + "\n", + "Building AI agents that can intelligently search and retrieve information from your organization's data stores is essential for creating powerful customer support and knowledge management solutions. However, connecting AI agents to enterprise search systems like Elasticsearch while maintaining security, scalability, and standardization can be complex.\n", + "\n", + "Amazon Bedrock AgentCore Gateway can help integrate any third-party vector store like Elasticsearch and others, giving you flexibility to work with your existing data infrastructure. Elasticsearch is particularly well-suited for enterprise AI applications because it combines powerful vector search capabilities with traditional full-text search, advanced filtering, and proven scalability—making it ideal for organizations that need to search across diverse, large-scale datasets.\n", + "\n", + "This tutorial demonstrates how to build an AI agent that performs semantic search on Elasticsearch data through Amazon Bedrock AgentCore Gateway. You'll learn how to transform AWS Lambda functions into Model Context Protocol (MCP) compliant tools and connect them to AI agents running in AgentCore Runtime—all while maintaining enterprise-grade security through dual authentication.\n", + "\n", + "### Tutorial Details\n", + "\n", + "| Information | Details |\n", + "|:---------------------|:----------------------------------------------------------|\n", + "| Tutorial type | Intermediate / Integration |\n", + "| Agent type | Customer Support RAG Agent |\n", + "| AgentCore components | Gateway, Identity, Runtime |\n", + "| Agentic Framework | Strands Agents |\n", + "| Gateway Target type | AWS Lambda |\n", + "| Data Store | Elasticsearch |\n", + "| Inbound Auth | Amazon Cognito (OAuth) |\n", + "| Outbound Auth | AWS IAM |\n", + "| LLM models | Anthropic Claude Haiku 4.5 |\n", + "| Example complexity | Intermediate |\n", + "| SDK used | boto3, bedrock-agentcore, bedrock-agentcore-starter-toolkit |\n", + "\n", + "### What You'll Learn\n", + "\n", + "In this tutorial, you'll learn:\n", + "1. How to package AWS Lambda functions for use with AgentCore Gateway\n", + "2. How to configure dual authentication (inbound OAuth and outbound IAM) for secure access\n", + "3. How to build a Strands agent that interacts with Gateway tools\n", + "4. How to deploy the complete solution to AgentCore Runtime\n", + "5 . How to test end-to-end RAG functionality with Elasticsearch\n", + "\n", + "### Architecture\n", + "\n", + "This tutorial demonstrates a customer support agent that retrieves policy information from Elasticsearch:\n", + "\n", + "
\n", + " \n", + "
\n", + "\n", + "**Architecture Flow:**\n", + "1. **User Request**: Client sends authenticated request to AgentCore Runtime\n", + "2. **Inbound Auth**: Cognito validates OAuth token for gateway access\n", + "3. **Agent Processing**: Strands agent determines which tools to use\n", + "4. **Gateway Invocation**: Agent calls MCP tools via Gateway\n", + "5. **Outbound Auth**: Gateway uses IAM to invoke Lambda\n", + "6. **Data Retrieval**: Lambda queries Elasticsearch and returns results\n", + "7. **Response Generation**: Agent synthesizes final response for user" + ] + }, + { + "cell_type": "markdown", + "id": "5362e1ad-f027-4452-a8d9-0b861c0115c2", + "metadata": {}, + "source": [ + "## 0. Prerequisites\n", + "\n", + "To execute this tutorial you will need:\n", + "\n", + "**Software Requirements:**\n", + "* Python 3.10 or newer\n", + "* Jupyter notebook environment\n", + "\n", + "**AWS Requirements:**\n", + "* AWS credentials configured with appropriate permissions for:\n", + " - Amazon Bedrock (model access)\n", + " - AWS Lambda (create and invoke functions)\n", + " - Amazon Cognito (user pool management)\n", + " - Amazon ECR (container registry)\n", + " - IAM (role and policy management)\n", + " - AWS Systems Manager Parameter Store\n", + "* Amazon Bedrock model access (Claude Haiku 4.5)\n", + "\n", + "**Elasticsearch Requirements:**\n", + "* Active Elasticsearch index\n", + "* Elasticsearch credentials:\n", + " - `ELASTIC_ENDPOINT_URL`: Your Elasticsearch endpoint\n", + " - `ELASTIC_API_KEY`: API key for authentication\n", + " - `ELASTIC_INDEX_NAME`: Index name\n", + "\n", + "### Setting Up Your Elasticsearch Configuration\n", + "\n", + "Before proceeding, configure your Elasticsearch connection details:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15bc0ab5", + "metadata": {}, + "outputs": [], + "source": [ + "# Replace these values with your actual Elasticsearch details\n", + "ELASTIC_ENDPOINT_URL = \"Your Elasticsearch endpoint\"\n", + "ELASTIC_API_KEY = \"API key for authentication\"\n", + "ELASTIC_INDEX_NAME = \"Index name\"" + ] + }, + { + "cell_type": "markdown", + "id": "install-dependencies", + "metadata": {}, + "source": [ + "### Installing Required Dependencies\n", + "\n", + "Let's install all required Python packages:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e326677b-11b5-4488-b01b-573ad275a764", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install -qUr requirements.txt" + ] + }, + { + "cell_type": "markdown", + "id": "setup-imports", + "metadata": {}, + "source": [ + "### Setting Up Environment\n", + "\n", + "Let's import required libraries and configure our environment:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cd5f3851-d0ca-484f-bb41-68f66ddbea44", + "metadata": {}, + "outputs": [], + "source": [ + "# Standard library imports\n", + "import os\n", + "import sys\n", + "import uuid\n", + "import time\n", + "import shutil\n", + "import zipfile\n", + "import subprocess\n", + "from pathlib import Path\n", + "\n", + "# AWS SDK imports\n", + "import boto3\n", + "from botocore.exceptions import ClientError\n", + "\n", + "# Set AWS region\n", + "REGION = os.getenv('AWS_REGION', 'us-east-1')\n", + "\n", + "# Configure utility imports\n", + "current_dir = os.getcwd() if '__file__' not in globals() else os.path.dirname(os.path.abspath(__file__))\n", + "sys.path.insert(0, current_dir)\n", + "\n", + "# Import tutorial utilities\n", + "import utils\n", + "from utils_execution import put_ssm_parameter\n", + "\n", + "print(f\"✅ Environment configured for region: {REGION}\")" + ] + }, + { + "cell_type": "markdown", + "id": "bulk-upload-policies", + "metadata": {}, + "source": [ + "### Uploading Policy Documents to Elasticsearch\n", + "\n", + "Before we can build our RAG agent, we need to populate our Elasticsearch index with product policy documents. This step will bulk upload all text files from the `policies/` directory to your Elasticsearch instance.\n", + "\n", + "> ⏱️ **Performance**: Bulk indexing is efficient for large document sets. This process typically takes a few seconds for dozens of documents." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bulk-upload-code", + "metadata": {}, + "outputs": [], + "source": [ + "from elasticsearch import Elasticsearch\n", + "from elasticsearch.helpers import bulk\n", + "\n", + "print(\"📤 Bulk uploading policy documents to Elasticsearch...\\n\")\n", + "\n", + "# Initialize Elasticsearch client\n", + "try:\n", + " es_client = Elasticsearch(\n", + " ELASTIC_ENDPOINT_URL,\n", + " api_key=ELASTIC_API_KEY\n", + " )\n", + " \n", + " # Test connection\n", + " if es_client.ping():\n", + " print(\"✅ Connected to Elasticsearch\")\n", + " else:\n", + " print(\"❌ Failed to connect to Elasticsearch\")\n", + " raise Exception(\"Elasticsearch connection failed\")\n", + " \n", + "except Exception as e:\n", + " print(f\"❌ Error connecting to Elasticsearch: {e}\")\n", + " raise\n", + "\n", + "# Define the policies directory\n", + "policies_dir = Path(\"policies\")\n", + "\n", + "if not policies_dir.exists():\n", + " print(f\"❌ Directory not found: {policies_dir}\")\n", + " print(\"💡 Please create a 'policies/' directory and add your .txt policy files.\")\n", + " raise FileNotFoundError(f\"Directory {policies_dir} does not exist\")\n", + "\n", + "# Get all .txt files\n", + "policy_files = list(policies_dir.glob(\"*.txt\"))\n", + "\n", + "if not policy_files:\n", + " print(f\"⚠️ No .txt files found in {policies_dir}\")\n", + " print(\"💡 Add your policy documents as .txt files to the policies/ directory.\")\n", + "else:\n", + " print(f\"📁 Found {len(policy_files)} policy document(s)\\n\")\n", + "\n", + "# Prepare documents for bulk indexing\n", + "def generate_docs():\n", + " \"\"\"Generator function to yield documents for bulk indexing.\"\"\"\n", + " for idx, file_path in enumerate(policy_files, 1):\n", + " try:\n", + " # Read file content\n", + " with open(file_path, 'r', encoding='utf-8') as f:\n", + " content = f.read()\n", + " \n", + " # Create document structure\n", + " doc = {\n", + " \"_index\": ELASTIC_INDEX_NAME,\n", + " \"_id\": file_path.stem, # Use filename (without extension) as document ID\n", + " \"_source\": {\n", + " \"filename\": file_path.name,\n", + " \"attachment\": {\n", + " \"content\": content\n", + " },\n", + " \"document_type\": \"policy\",\n", + " \"indexed_at\": \"2024-01-01T00:00:00Z\" # You can use datetime.now().isoformat()\n", + " }\n", + " }\n", + " \n", + " print(f\" [{idx}/{len(policy_files)}] Preparing: {file_path.name}\")\n", + " yield doc\n", + " \n", + " except Exception as e:\n", + " print(f\" ⚠️ Error reading {file_path.name}: {e}\")\n", + " continue\n", + "\n", + "# Perform bulk indexing\n", + "try:\n", + " print(\"\\n⏳ Indexing documents...\\n\")\n", + " \n", + " # Execute bulk operation\n", + " success, failed = bulk(\n", + " es_client,\n", + " generate_docs(),\n", + " raise_on_error=False,\n", + " refresh=True # Make documents immediately searchable\n", + " )\n", + " \n", + " print(\"\\n\" + \"=\" * 70)\n", + " print(f\"✅ Successfully indexed: {success} document(s)\")\n", + " \n", + " if failed:\n", + " print(f\"⚠️ Failed to index: {failed} document(s)\")\n", + " \n", + " # Verify indexing\n", + " count_response = es_client.count(index=ELASTIC_INDEX_NAME)\n", + " total_docs = count_response['count']\n", + " print(f\"📊 Total documents in index '{ELASTIC_INDEX_NAME}': {total_docs}\")\n", + " print(\"=\" * 70)\n", + " \n", + " # Show sample document structure\n", + " if total_docs > 0:\n", + " print(\"\\n📄 Sample document structure:\")\n", + " sample_doc = es_client.search(\n", + " index=ELASTIC_INDEX_NAME,\n", + " size=1\n", + " )['hits']['hits'][0]\n", + " \n", + " print(f\" Document ID: {sample_doc['_id']}\")\n", + " print(f\" Filename: {sample_doc['_source'].get('filename')}\")\n", + " print(f\" Content preview: {sample_doc['_source']['attachment']['content'][:100]}...\")\n", + " \n", + " print(\"\\n🎉 Bulk upload completed successfully!\\n\")\n", + " \n", + "except Exception as e:\n", + " print(f\"\\n❌ Error during bulk indexing: {e}\")\n", + " import traceback\n", + " traceback.print_exc()\n", + " raise\n", + "\n", + "finally:\n", + " # Close Elasticsearch connection\n", + " es_client.close()\n", + " print(\"✅ Elasticsearch connection closed\")" + ] + }, + { + "cell_type": "markdown", + "id": "0b9e6e7c", + "metadata": {}, + "source": [ + "## 1. Creating the Lambda Function for Elasticsearch Integration\n", + "\n", + "The first step in our journey is to create an AWS Lambda function that will query Elasticsearch. This Lambda function will later be transformed into an MCP tool by AgentCore Gateway, making it accessible to our AI agent.\n", + "\n", + "### The Lambda Function Design\n", + "\n", + "Our Lambda function will:\n", + "1. Accept a search query as input\n", + "2. Connect to Elasticsearch using stored credentials\n", + "3. Perform a multi-match query across document content\n", + "4. Return relevant search results to the agent" + ] + }, + { + "cell_type": "markdown", + "id": "create-lambda-structure", + "metadata": {}, + "source": [ + "### Step 1: Create Project Structure\n", + "\n", + "First, we'll create a directory to organize our Lambda function code:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "create-directories", + "metadata": {}, + "outputs": [], + "source": [ + "# Create directory structure for Lambda package\n", + "os.makedirs(\"elastic_lambda_code/package\", exist_ok=True)\n", + "print(\"✅ Created Lambda project directories\")" + ] + }, + { + "cell_type": "markdown", + "id": "lambda-requirements", + "metadata": {}, + "source": [ + "### Step 2: Define Lambda Dependencies\n", + "\n", + "Our Lambda function needs the Elasticsearch Python client to query our data store:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "910895ed", + "metadata": {}, + "outputs": [], + "source": [ + "%%writefile elastic_lambda_code/requirements.txt\n", + "elasticsearch" + ] + }, + { + "cell_type": "markdown", + "id": "lambda-code-explanation", + "metadata": {}, + "source": [ + "### Step 3: Create the Lambda Function Code\n", + "\n", + "Now let's create the actual Lambda function. This function will be invoked by AgentCore Gateway and will query Elasticsearch based on the tool name and parameters passed by the agent.\n", + "\n", + "#### Key Components:\n", + "\n", + "1. **Environment Variables**: The function reads Elasticsearch credentials from environment variables (set during Lambda creation)\n", + "2. **Event Schema**: The `event` parameter contains the tool input (e.g., search query)\n", + "3. **Context Metadata**: The `context.client_context.custom` contains Gateway metadata including tool name\n", + "4. **Tool Name Parsing**: We extract the actual tool name by removing the Gateway target prefix\n", + "5. **Elasticsearch Query**: We perform a multi-match search across document content\n", + "\n", + "> 💡 **Gateway Context**: AgentCore Gateway automatically injects metadata into the Lambda context, including the gateway ID, target ID, and tool name. This allows a single Lambda function to implement multiple tools." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e467d3d6", + "metadata": {}, + "outputs": [], + "source": [ + "%%writefile elastic_lambda_code/lambda_function_code.py\n", + "import json\n", + "import boto3\n", + "from elasticsearch import Elasticsearch\n", + "import os\n", + "\n", + "# Load Elasticsearch configuration from environment variables\n", + "endpoint_url = os.environ['ELASTIC_ENDPOINT_URL_ENV']\n", + "api_key = os.environ['ELASTIC_API_KEY_ENV']\n", + "index_name = os.environ['ELASTIC_INDEX_NAME_ENV']\n", + "\n", + "# Initialize Elasticsearch client\n", + "client = Elasticsearch(\n", + " endpoint_url,\n", + " api_key=api_key\n", + ")\n", + "\n", + "def lambda_handler(event, context):\n", + " \"\"\"\n", + " Lambda handler for Elasticsearch search via AgentCore Gateway.\n", + " \n", + " Args:\n", + " event: Contains the tool input matching the inputSchema defined in Gateway\n", + " context: Contains Gateway metadata in context.client_context.custom\n", + " \n", + " Context structure:\n", + " {\n", + " 'bedrockAgentCoreGatewayId': 'Y02ERAYBHB',\n", + " 'bedrockAgentCoreTargetId': 'RQHDN3J002',\n", + " 'bedrockAgentCoreMessageVersion': '1.0',\n", + " 'bedrockAgentCoreToolName': 'target_name___elastic_rag_tool',\n", + " 'bedrockAgentCoreSessionId': ''\n", + " }\n", + " \"\"\"\n", + " \n", + " # Extract tool name from Gateway context\n", + " # Gateway prefixes tool names with target name and delimiter\n", + " delimiter = \"___\"\n", + " original_tool_name = context.client_context.custom['bedrockAgentCoreToolName']\n", + " tool_name = original_tool_name[original_tool_name.index(delimiter) + len(delimiter):]\n", + " \n", + " # Route to appropriate tool handler\n", + " if tool_name == 'elastic_rag_tool':\n", + " # Extract search query from event\n", + " query = event[\"query\"]\n", + " \n", + " # Build Elasticsearch query\n", + " # Using multi_match to search across document content fields\n", + " retriever_object = {\n", + " \"standard\": {\n", + " \"query\": {\n", + " \"multi_match\": {\n", + " \"query\": query,\n", + " \"fields\": [\n", + " \"attachment.content\" \n", + " ]\n", + " }\n", + " }\n", + " }\n", + " }\n", + " \n", + " # Execute search\n", + " search_response = client.search(\n", + " index=index_name,\n", + " retriever=retriever_object,\n", + " )\n", + " \n", + " # Return results\n", + " return {'statusCode': 200, 'body': search_response.body}\n", + " else:\n", + " # Handle unsupported tools\n", + " return {'statusCode': 400, 'body': \"Unsupported tool.\"}" + ] + }, + { + "cell_type": "markdown", + "id": "package-lambda", + "metadata": {}, + "source": [ + "### Step 4: Package Lambda Deployment Bundle\n", + "\n", + "Now we'll create the deployment package by:\n", + "1. Installing dependencies to the `package/` directory\n", + "2. Creating a zip file with all dependencies\n", + "3. Adding our Lambda function code to the zip\n", + "\n", + "This process follows AWS Lambda's [deployment package specifications](https://docs.aws.amazon.com/lambda/latest/dg/python-package.html)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1213f364", + "metadata": {}, + "outputs": [], + "source": [ + "# Store original directory\n", + "original_dir = os.getcwd()\n", + "project_dir = \"elastic_lambda_code\"\n", + "\n", + "try:\n", + " print(\"📦 Building Lambda deployment package...\")\n", + " \n", + " # Navigate to project directory\n", + " os.chdir(project_dir)\n", + " print(f\"✅ Changed to: {os.getcwd()}\")\n", + " \n", + " # Ensure package directory exists\n", + " os.makedirs(\"package\", exist_ok=True)\n", + " print(\"✅ Created package directory\")\n", + " \n", + " # Install dependencies\n", + " print(\"📥 Installing dependencies...\")\n", + " subprocess.check_call([\n", + " sys.executable, \"-m\", \"pip\", \"install\", \n", + " \"-r\", \"requirements.txt\",\n", + " \"--target\", \"./package\",\n", + " \"--quiet\"\n", + " ])\n", + " print(\"✅ Installed dependencies to ./package\")\n", + " \n", + " # Create zip with dependencies\n", + " os.chdir(\"package\")\n", + " print(\"🗜️ Creating deployment package...\")\n", + " subprocess.check_call([\n", + " \"zip\", \"-r\", \"../my_deployment_package.zip\", \".\", \"-q\"\n", + " ])\n", + " print(\"✅ Created zip with dependencies\")\n", + " \n", + " # Add Lambda function code\n", + " os.chdir(\"..\")\n", + " subprocess.check_call([\n", + " \"zip\", \"my_deployment_package.zip\", \"lambda_function_code.py\", \"-q\"\n", + " ])\n", + " print(\"✅ Added lambda_function_code.py to zip\")\n", + " \n", + " # Move to parent directory\n", + " shutil.move(\"my_deployment_package.zip\", \"../my_deployment_package.zip\")\n", + " print(\"✅ Moved deployment package to project root\")\n", + " \n", + "finally:\n", + " # Always return to original directory\n", + " os.chdir(original_dir)\n", + " print(f\"✅ Returned to: {os.getcwd()}\")\n", + "\n", + "print(\"\\n🎉 Lambda deployment package created successfully!\")" + ] + }, + { + "cell_type": "markdown", + "id": "7eb0e75a", + "metadata": {}, + "source": [ + "### Step 5: Deploy the Lambda Function\n", + "\n", + "Now let's create the actual Lambda function in AWS using our deployment package. The utility function will:\n", + "- Create an IAM execution role for the Lambda\n", + "- Upload the deployment package\n", + "- Configure environment variables for Elasticsearch access\n", + "- Set appropriate timeout and memory settings\n", + "\n", + "> ⚠️ **Security Note**: The Lambda function will have access to your Elasticsearch instance via the API key stored in environment variables. In production, consider using AWS Secrets Manager for enhanced security." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ddf8bfe9-8435-44cc-9e8a-ed8ece48f6a6", + "metadata": {}, + "outputs": [], + "source": [ + "print(\"🚀 Creating Lambda function...\")\n", + "\n", + "# Configure Lambda environment variables\n", + "environment_variables = {\n", + " 'ELASTIC_ENDPOINT_URL_ENV': ELASTIC_ENDPOINT_URL,\n", + " 'ELASTIC_API_KEY_ENV': ELASTIC_API_KEY,\n", + " 'ELASTIC_INDEX_NAME_ENV': ELASTIC_INDEX_NAME,\n", + "}\n", + "\n", + "# Create Lambda function (Can take up to 2 min)\n", + "lambda_resp = utils.create_gateway_lambda(\"my_deployment_package.zip\", environment_variables)\n", + "\n", + "# Check creation status\n", + "if lambda_resp is not None:\n", + " if lambda_resp['exit_code'] == 0:\n", + " print(f\"✅ Lambda function created successfully\")\n", + " print(f\" ARN: {lambda_resp['lambda_function_arn']}\")\n", + " lambda_function_arn = lambda_resp['lambda_function_arn']\n", + " else:\n", + " print(f\"❌ Lambda function creation failed: {lambda_resp['lambda_function_arn']}\")\n", + " raise Exception(\"Lambda creation failed\")\n", + "else:\n", + " print(\"❌ Lambda function creation returned None\")\n", + " raise Exception(\"Lambda creation failed\")" + ] + }, + { + "cell_type": "markdown", + "id": "e93da982-0d11-427c-9958-fff3c80b32f1", + "metadata": {}, + "source": [ + "## 2. Configuring Authentication for AgentCore Gateway\n", + "\n", + "Security is paramount when building production AI systems. AgentCore Gateway implements a **dual authentication model** that provides defense-in-depth:\n", + "\n", + "### Understanding Dual Authentication\n", + "\n", + "**1. Inbound Authentication (Who can call the Gateway?)**\n", + "- Validates users/agents attempting to access Gateway tools\n", + "- Uses OAuth 2.0 tokens from Amazon Cognito\n", + "- Ensures only authorized clients can invoke MCP tools\n", + "\n", + "**2. Outbound Authentication (How does Gateway access resources?)**\n", + "- Authorizes Gateway to call backend services (Lambda, APIs)\n", + "- Uses AWS IAM roles for Lambda invocation\n", + "- Enables secure, auditable access to downstream resources\n", + "\n", + "### The Authentication Flow\n", + "\n", + "```\n", + "Client (Agent)\n", + " │\n", + " │ [1] Request + OAuth Token\n", + " ↓\n", + "AgentCore Gateway\n", + " │\n", + " │ [2] Validate OAuth Token (Inbound Auth)\n", + " │\n", + " │ [3] Assume IAM Role (Outbound Auth)\n", + " ↓\n", + "AWS Lambda → Elasticsearch\n", + "```\n", + "\n", + "Let's set up both authentication layers." + ] + }, + { + "cell_type": "markdown", + "id": "65120594-c3ec-4d51-810b-8d478851d8d2", + "metadata": {}, + "source": [ + "## 3. Setting Up Amazon Cognito for Inbound Authorization\n", + "\n", + "Amazon Cognito serves as our identity provider (IdP), managing user authentication and issuing OAuth tokens that grant access to our Gateway.\n", + "\n", + "### What We'll Create\n", + "\n", + "1. **Cognito User Pool**: Container for user identities and authentication settings\n", + "2. **Resource Server**: Defines custom OAuth scopes for fine-grained access control\n", + "3. **App Client**: Machine-to-machine (M2M) client for programmatic access\n", + "4. **OAuth Scopes**: `gateway:read` and `gateway:write` permissions\n", + "\n", + "> 💡 **Production Tip**: In production environments, use principle of least privilege—grant only the minimum scopes required for each client's functionality." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0bbbc736-6f5e-4a60-a598-ead43a0c1f90", + "metadata": {}, + "outputs": [], + "source": [ + "print(\"🔐 Setting up Amazon Cognito authentication...\\n\")\n", + "\n", + "# Configuration\n", + "USER_POOL_NAME = \"agentcore-gateway-elasticsearch-pool\"\n", + "RESOURCE_SERVER_ID = \"elasticsearch-gateway\"\n", + "RESOURCE_SERVER_NAME = \"Elasticsearch Gateway Resource Server\"\n", + "CLIENT_NAME = \"elasticsearch-gateway-client\"\n", + "\n", + "# Define OAuth scopes for access control\n", + "SCOPES = [\n", + " {\"ScopeName\": \"gateway:read\", \"ScopeDescription\": \"Read access to Gateway tools\"},\n", + " {\"ScopeName\": \"gateway:write\", \"ScopeDescription\": \"Write access to Gateway tools\"}\n", + "]\n", + "scope_string = f\"{RESOURCE_SERVER_ID}/gateway:read {RESOURCE_SERVER_ID}/gateway:write\"\n", + "\n", + "# Initialize Cognito client\n", + "cognito = boto3.client(\"cognito-idp\", region_name=REGION)\n", + "\n", + "# Step 1: Create or retrieve User Pool\n", + "print(\"📋 Creating/retrieving User Pool...\")\n", + "user_pool_id = utils.get_or_create_user_pool(cognito, USER_POOL_NAME)\n", + "print(f\"✅ User Pool ID: {user_pool_id}\")\n", + "\n", + "# Step 2: Create or retrieve Resource Server\n", + "print(\"\\n🔧 Configuring Resource Server...\")\n", + "utils.get_or_create_resource_server(cognito, user_pool_id, RESOURCE_SERVER_ID, RESOURCE_SERVER_NAME, SCOPES)\n", + "print(\"✅ Resource Server configured with OAuth scopes\")\n", + "\n", + "# Step 3: Create or retrieve M2M App Client\n", + "print(\"\\n🔑 Creating Machine-to-Machine client...\")\n", + "client_id, client_secret = utils.get_or_create_m2m_client(cognito, user_pool_id, CLIENT_NAME, RESOURCE_SERVER_ID)\n", + "print(f\"✅ Client ID: {client_id}\")\n", + "print(\"✅ Client Secret generated (stored securely)\")\n", + "\n", + "# Step 4: Generate discovery URL for Gateway configuration\n", + "cognito_discovery_url = f'https://cognito-idp.{REGION}.amazonaws.com/{user_pool_id}/.well-known/openid-configuration'\n", + "print(f\"\\n🌐 Discovery URL: {cognito_discovery_url}\")\n", + "\n", + "print(\"\\n🎉 Cognito authentication setup complete!\")" + ] + }, + { + "cell_type": "markdown", + "id": "f1a63450-7fb9-42fc-ab4f-3d86c27bb2f8", + "metadata": {}, + "source": [ + "## 4. Creating the AgentCore Gateway\n", + "\n", + "Now we're ready to create our AgentCore Gateway—the central hub that transforms our Lambda function into an MCP-compliant tool accessible by AI agents.\n", + "\n", + "### What is AgentCore Gateway?\n", + "\n", + "AgentCore Gateway is a fully-managed service that:\n", + "- **Standardizes Integration**: Provides uniform MCP interfaces across different backend services\n", + "- **Manages Security**: Handles both inbound and outbound authentication\n", + "- **Eliminates Infrastructure**: No servers to manage or maintain\n", + "- **Scales Automatically**: Handles varying loads without configuration\n", + "\n", + "### Gateway Components\n", + "\n", + "Creating a Gateway involves:\n", + "1. **IAM Execution Role**: Grants Gateway permission to invoke Lambda functions\n", + "2. **Gateway Resource**: The main Gateway instance with authentication configuration\n", + "3. **Gateway Target**: Connects the Gateway to your Lambda function\n", + "\n", + "Let's create each component." + ] + }, + { + "cell_type": "markdown", + "id": "create-gateway-role", + "metadata": {}, + "source": [ + "### Step 1: Create IAM Execution Role\n", + "\n", + "The Gateway needs an IAM role to invoke Lambda functions on your behalf. This role implements the **outbound authentication** we discussed earlier." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9ef17018-d2be-40e3-a892-55dd420297f9", + "metadata": {}, + "outputs": [], + "source": [ + "print(\"🔐 Creating IAM execution role for Gateway...\")\n", + "\n", + "# Create role with appropriate trust policy and permissions\n", + "agentcore_gateway_iam_role = utils.create_agentcore_gateway_role(\"elasticsearch-gateway\")\n", + "gateway_role_arn = agentcore_gateway_iam_role['Role']['Arn']\n", + "\n", + "print(f\"✅ Gateway IAM Role created\")\n", + "print(f\" ARN: {gateway_role_arn}\")" + ] + }, + { + "cell_type": "markdown", + "id": "create-gateway-resource", + "metadata": {}, + "source": [ + "### Step 2: Create Gateway with Cognito Authorization\n", + "\n", + "Now we'll create the Gateway itself, configuring it to use our Cognito User Pool for inbound authentication.\n", + "\n", + "#### Configuration Details:\n", + "\n", + "- **Protocol Type**: `MCP` - Model Context Protocol for standardized tool interfaces\n", + "- **Authorizer Type**: `CUSTOM_JWT` - Uses Cognito JWT tokens for authentication\n", + "- **Allowed Clients**: List of Cognito client IDs permitted to access the Gateway\n", + "- **Discovery URL**: Cognito's OIDC discovery endpoint for token validation\n", + "\n", + "> 💡 **Security Note**: Only clients listed in `allowedClients` can successfully invoke Gateway tools, even with a valid JWT token. This provides an additional layer of access control." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "72f2cd57-7777-42d3-b6f3-c45ed0a935c4", + "metadata": {}, + "outputs": [], + "source": [ + "print(\"🚪 Creating AgentCore Gateway...\\n\")\n", + "\n", + "# Initialize Gateway client\n", + "gateway_client = boto3.client('bedrock-agentcore-control', region_name=REGION)\n", + "\n", + "# Configure Cognito JWT authorization\n", + "auth_config = {\n", + " \"customJWTAuthorizer\": { \n", + " \"allowedClients\": [client_id], # Must match Cognito Client ID\n", + " \"discoveryUrl\": cognito_discovery_url\n", + " }\n", + "}\n", + "\n", + "# Generate unique Gateway name\n", + "gateway_name = f'ElasticsearchGateway-{str(uuid.uuid4())[:8]}'\n", + "\n", + "# Create Gateway\n", + "try:\n", + " create_response = gateway_client.create_gateway(\n", + " name=gateway_name,\n", + " roleArn=gateway_role_arn,\n", + " protocolType='MCP',\n", + " authorizerType='CUSTOM_JWT',\n", + " authorizerConfiguration=auth_config,\n", + " description='AgentCore Gateway for Elasticsearch integration via Lambda'\n", + " )\n", + " \n", + " # Extract Gateway details\n", + " gateway_id = create_response[\"gatewayId\"]\n", + " gateway_url = create_response[\"gatewayUrl\"]\n", + " \n", + " print(f\"✅ Gateway created successfully!\")\n", + " print(f\" Gateway ID: {gateway_id}\")\n", + " print(f\" Gateway URL: {gateway_url}\")\n", + " \n", + " # Store Gateway ID in Parameter Store for later use\n", + " put_ssm_parameter(\"/app/customer_support_elastic/agentcore/gateway_id\", gateway_id)\n", + " print(f\"\\n✅ Gateway ID stored in SSM Parameter Store\")\n", + " \n", + "except ClientError as e:\n", + " print(f\"❌ Error creating Gateway: {e}\")\n", + " raise" + ] + }, + { + "cell_type": "markdown", + "id": "94dc57be-e50e-4997-a440-3bcf582d09bc", + "metadata": {}, + "source": [ + "## 5. Creating the Gateway Target and MCP Tools\n", + "\n", + "The final step in setting up our Gateway is creating a **Gateway Target**—this connects our Lambda function to the Gateway and defines how it should be exposed as MCP tools.\n", + "\n", + "### Understanding Gateway Targets\n", + "\n", + "A Gateway Target:\n", + "- **Maps Lambda to MCP**: Transforms your Lambda function into one or more MCP tools\n", + "- **Defines Tool Schema**: Specifies tool names, descriptions, and input parameters\n", + "- **Configures Credentials**: Sets up how Gateway authenticates to Lambda (IAM, API key, OAuth)\n", + "\n", + "### Our Tool Definition\n", + "\n", + "We're creating a single tool called `elastic_rag_tool` that:\n", + "- **Name**: `elastic_rag_tool`\n", + "- **Purpose**: Performs semantic search on Elasticsearch\n", + "- **Input**: A `query` string parameter\n", + "- **Output**: Relevant search results from Elasticsearch\n", + "\n", + "### Tool Schema Structure\n", + "\n", + "The tool schema follows JSON Schema format:\n", + "```json\n", + "{\n", + " \"name\": \"elastic_rag_tool\",\n", + " \"description\": \"tool to perform search on Elastic\",\n", + " \"inputSchema\": {\n", + " \"type\": \"object\",\n", + " \"properties\": {\n", + " \"query\": {\"type\": \"string\"}\n", + " },\n", + " \"required\": [\"query\"]\n", + " }\n", + "}\n", + "```\n", + "\n", + "This schema tells the AI agent what parameters the tool expects and which are required.\n", + "\n", + "> 💡 **Multi-Tool Lambda**: A single Lambda function can implement multiple tools by checking the tool name in the context (as we did in our Lambda code)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8c1474da-cdd6-4dbd-84e7-3fdad018647d", + "metadata": {}, + "outputs": [], + "source": [ + "print(\"🔧 Creating Gateway Target for Lambda...\\n\")\n", + "\n", + "# Define Lambda target configuration with MCP tool schema\n", + "lambda_target_config = {\n", + " \"mcp\": {\n", + " \"lambda\": {\n", + " \"lambdaArn\": lambda_function_arn,\n", + " \"toolSchema\": {\n", + " \"inlinePayload\": [\n", + " {\n", + " \"name\": \"elastic_rag_tool\",\n", + " \"description\": \"Searches Elasticsearch for relevant product policy information. Use this tool when customers ask about product documentation, troubleshooting guides, warranty information, or any product-related questions.\",\n", + " \"inputSchema\": {\n", + " \"type\": \"object\",\n", + " \"properties\": {\n", + " \"query\": {\n", + " \"type\": \"string\",\n", + " \"description\": \"The search query to find relevant documents in Elasticsearch\"\n", + " }\n", + " },\n", + " \"required\": [\"query\"]\n", + " }\n", + " }\n", + " ]\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "# Configure credential provider (outbound authentication)\n", + "credential_config = [\n", + " {\n", + " \"credentialProviderType\": \"GATEWAY_IAM_ROLE\" # Use Gateway's IAM role for Lambda invocation\n", + " }\n", + "]\n", + "\n", + "# Generate unique target name\n", + "target_name = f'ElasticSearchTarget-{str(uuid.uuid4())[:8]}'\n", + "\n", + "# Create Gateway Target\n", + "try:\n", + " response = gateway_client.create_gateway_target(\n", + " gatewayIdentifier=gateway_id,\n", + " name=target_name,\n", + " description='Lambda target for Elasticsearch RAG functionality',\n", + " targetConfiguration=lambda_target_config,\n", + " credentialProviderConfigurations=credential_config\n", + " )\n", + " \n", + " target_id = response['targetId']\n", + " \n", + " print(f\"✅ Gateway Target created successfully!\")\n", + " print(f\" Target ID: {target_id}\")\n", + " print(f\" Target Name: {target_name}\")\n", + " print(f\"\\n🎉 Lambda function is now available as MCP tool: {target_name}___elastic_rag_tool\")\n", + " \n", + "except ClientError as e:\n", + " print(f\"❌ Error creating Gateway Target: {e}\")\n", + " raise" + ] + }, + { + "cell_type": "markdown", + "id": "d3ac6532-5299-4024-917d-bcd60caea6ed", + "metadata": {}, + "source": [ + "## 6. Testing the Gateway with a Strands Agent\n", + "\n", + "Now that we've set up our complete infrastructure, let's test it by creating a Strands agent that can access our Elasticsearch data through the Gateway.\n", + "\n", + "### How MCP Integration Works\n", + "\n", + "The Strands agent integrates with AgentCore Gateway using the Model Context Protocol (MCP) specification. Here's the flow:\n", + "\n", + "```\n", + "┌─────────────────┐\n", + "│ Strands Agent │\n", + "└────────┬────────┘\n", + " │\n", + " │ 1. ListTools (discover available tools)\n", + " ↓\n", + "┌─────────────────────┐\n", + "│ AgentCore Gateway │ ← OAuth Token\n", + "└─────────┬───────────┘\n", + " │\n", + " │ 2. InvokeTool (call specific tool)\n", + " ↓\n", + "┌──────────────────┐\n", + "│ AWS Lambda │\n", + "└────────┬─────────┘\n", + " │\n", + " │ 3. Query\n", + " ↓\n", + "┌──────────────────┐\n", + "│ Elasticsearch │\n", + "└──────────────────┘\n", + "```\n", + "\n", + "### MCP Client Configuration\n", + "\n", + "The Strands agent uses an `MCPClient` to communicate with the Gateway:\n", + "- **Transport**: HTTP(S) with bearer token authentication\n", + "- **Headers**: OAuth token for inbound authentication\n", + "- **Protocol**: Standard MCP operations (ListTools, InvokeTools)\n", + "\n", + "Let's start by requesting an access token from Cognito." + ] + }, + { + "cell_type": "markdown", + "id": "873031fe-62b5-4196-91be-500c1f87dfd4", + "metadata": {}, + "source": [ + "### Step 1: Request OAuth Access Token\n", + "\n", + "Before we can call the Gateway, we need to obtain an OAuth access token from Cognito. This token proves our identity and grants us access to the Gateway tools.\n", + "\n", + "> ⏰ **Token Expiration**: Cognito access tokens are typically valid for 1 hour. If your token expires, you'll need to request a new one using the same process." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ed1d1d6-e84c-4286-bf25-3ad6a49723b9", + "metadata": {}, + "outputs": [], + "source": [ + "print(\"🔑 Requesting OAuth access token from Cognito...\\n\")\n", + "\n", + "# Wait for Cognito domain propagation\n", + "print(\"⏳ Waiting for Cognito domain propagation...\")\n", + "time.sleep(10)\n", + "\n", + "try:\n", + " # Request access token\n", + " token_response = utils.get_token(user_pool_id, client_id, client_secret, scope_string, REGION)\n", + " access_token = token_response[\"access_token\"]\n", + " \n", + " print(\"✅ Access token obtained successfully\")\n", + " print(f\" Token type: Bearer\")\n", + " print(f\" Scopes: {scope_string}\")\n", + " print(f\" Token (truncated): {access_token[:50]}...\")\n", + " \n", + "except Exception as e:\n", + " print(f\"❌ Error obtaining token: {e}\")\n", + " print(\"\\n💡 Tip: If this fails, wait a few minutes for Cognito domain propagation to complete, then try again.\")\n", + " raise" + ] + }, + { + "cell_type": "markdown", + "id": "6fd0379d-9576-43cb-aa9b-72b86c43b472", + "metadata": {}, + "source": [ + "### Step 2: Create MCP Client and Agent\n", + "\n", + "Now let's create our Strands agent with MCP client integration. The agent will:\n", + "1. Connect to the Gateway using the OAuth token\n", + "2. Discover available tools via the `ListTools` MCP operation\n", + "3. Load those tools into its configuration\n", + "4. Be ready to invoke them based on user queries\n", + "\n", + "> 💡 **Model Choice**: We're using Claude Haiku 4.5, but you can substitute any Bedrock-supported model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "be4b39c4-7387-4ce8-b728-f2347fbdaa36", + "metadata": {}, + "outputs": [], + "source": [ + "from strands.models import BedrockModel\n", + "from mcp.client.streamable_http import streamablehttp_client\n", + "from strands.tools.mcp.mcp_client import MCPClient\n", + "from strands import Agent\n", + "import logging\n", + "\n", + "# Configure logging to see agent reasoning\n", + "logging.basicConfig(\n", + " format=\"%(levelname)s | %(name)s | %(message)s\",\n", + " handlers=[logging.StreamHandler()]\n", + ")\n", + "logging.getLogger(\"strands\").setLevel(logging.INFO)\n", + "\n", + "print(\"🤖 Creating Strands agent with MCP tools...\\n\")\n", + "\n", + "# Create HTTP transport factory for MCP client\n", + "def create_streamable_http_transport():\n", + " \"\"\"Factory function to create authenticated HTTP transport for MCP.\"\"\"\n", + " return streamablehttp_client(\n", + " gateway_url,\n", + " headers={\"Authorization\": f\"Bearer {access_token}\"}\n", + " )\n", + "\n", + "# Initialize MCP client\n", + "mcp_client = MCPClient(create_streamable_http_transport)\n", + "\n", + "# Create Bedrock model\n", + "model = BedrockModel(\n", + " model_id=\"us.anthropic.claude-haiku-4-5-20251001-v1:0\",\n", + " temperature=0.3,\n", + " region_name=REGION,\n", + ")\n", + "\n", + "print(\"✅ MCP client configured\")\n", + "print(\"✅ Bedrock model initialized\")" + ] + }, + { + "cell_type": "markdown", + "id": "test-agent", + "metadata": {}, + "source": [ + "### Step 3: Test the Agent\n", + "\n", + "Let's test our agent with various queries to verify the complete integration:\n", + "\n", + "1. **List available tools** - Verify MCP tool discovery\n", + "2. **Search Elasticsearch** - Test the RAG functionality\n", + "3. **Answer customer queries** - Validate end-to-end workflow" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e64794e7-4e5f-4fc5-824a-61c901e356c4", + "metadata": {}, + "outputs": [], + "source": [ + "print(\"🧪 Testing agent with MCP tools...\\n\")\n", + "print(\"=\" * 70)\n", + "\n", + "with mcp_client:\n", + " # Step 1: Discover available tools\n", + " print(\"\\n📋 Step 1: Discovering MCP tools...\")\n", + " tools = mcp_client.list_tools_sync()\n", + " print(f\"✅ Found {len(tools)} tool(s)\")\n", + " \n", + " # Step 2: Create agent with tools\n", + " print(\"\\n🤖 Step 2: Creating agent...\")\n", + " agent = Agent(model=model, tools=tools)\n", + " print(f\"✅ Agent created with tools: {agent.tool_names}\")\n", + " \n", + " # Step 3: Test tool listing query\n", + " print(\"\\n\" + \"=\" * 70)\n", + " print(\"Test 1: List available tools\")\n", + " print(\"=\" * 70)\n", + " response = agent(\"Hi, can you list all tools available to you?\")\n", + " print(f\"\\n🤖 Agent: {response.message['content'][0]['text']}\")\n", + " \n", + " # Step 4: Test Elasticsearch search\n", + " print(\"\\n\" + \"=\" * 70)\n", + " print(\"Test 2: Search Elasticsearch for product information\")\n", + " print(\"=\" * 70)\n", + " response = agent(\"Use the elastic_rag_tool to find information about how to troubleshoot the LAPTOP ULTRA 15.6\")\n", + " print(f\"\\n🤖 Agent: {response.message['content'][0]['text']}\")\n", + "\n", + "print(\"\\n\" + \"=\" * 70)\n", + "print(\"✅ All tests completed successfully!\")\n", + "print(\"=\" * 70)" + ] + }, + { + "cell_type": "markdown", + "id": "475cdb90", + "metadata": {}, + "source": [ + "## 7. Deploying to AgentCore Runtime\n", + "\n", + "So far, we've tested our agent locally in this notebook. Now let's deploy it to **Amazon Bedrock AgentCore Runtime** for production use—a fully-managed environment that handles infrastructure, scaling, and deployment for you.\n", + "\n", + "### Deployment Architecture\n", + "\n", + "When deployed to Runtime, your agent becomes a fully-managed service. The deployment process will:\n", + "1. Create a Docker container from your agent code\n", + "2. Push the container to Amazon ECR (Elastic Container Registry)\n", + "3. Deploy the container to AgentCore Runtime\n", + "4. Configure authentication and networking\n", + "5. Provide an HTTPS endpoint for invocation\n", + "\n", + "Let's start by creating the runtime-ready agent code." + ] + }, + { + "cell_type": "markdown", + "id": "0c0841b7", + "metadata": {}, + "source": [ + "### Step 1: Create the Runtime-Ready Agent\n", + "\n", + "To deploy our agent to AgentCore Runtime, we need to create a Python file with a special entrypoint function. This entrypoint handles incoming requests from the Runtime environment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cb225a50", + "metadata": {}, + "outputs": [], + "source": [ + "%%writefile strands_agent.py\n", + "from bedrock_agentcore.runtime import BedrockAgentCoreApp\n", + "from strands import Agent\n", + "from strands.models import BedrockModel\n", + "from strands.tools.mcp.mcp_client import MCPClient\n", + "import boto3\n", + "import os\n", + "import logging\n", + "from mcp.client.streamable_http import streamablehttp_client\n", + "\n", + "# Configure detailed logging\n", + "logging.basicConfig(\n", + " level=logging.INFO,\n", + " format='%(asctime)s [%(levelname)s] %(name)s: %(message)s'\n", + ")\n", + "logger = logging.getLogger(\"strands-agent\")\n", + "\n", + "# Get AWS region from environment\n", + "REGION = boto3.session.Session().region_name\n", + "\n", + "# Initialize the AgentCore Runtime application\n", + "app = BedrockAgentCoreApp()\n", + "\n", + "# Define the system prompt that governs agent behavior\n", + "system_prompt = \"\"\"\n", + "You are a helpful customer support agent specializing in product assistance and troubleshooting.\n", + "You have access to a tool that searches our product knowledge base stored in Elasticsearch.\n", + "\n", + "When customers ask questions about products, policies, troubleshooting, or documentation:\n", + "1. Use the elastic_rag_tool to search for relevant information\n", + "2. Synthesize the search results into clear, helpful responses\n", + "3. If you cannot find relevant information, politely inform the customer\n", + "\n", + "\n", + " - Never assume parameter values when using tools\n", + " - If you need more information, ask the customer for clarification\n", + " - NEVER disclose information about internal tools or systems\n", + " - Always maintain a professional and helpful tone\n", + " - Focus on resolving customer inquiries efficiently\n", + " - Present technical information in user-friendly terms\n", + " - Prioritize customer privacy and data security\n", + "\n", + "\"\"\"\n", + "\n", + "# Global agent instance - will be initialized with first request\n", + "agent = None\n", + "mcp_client = None\n", + "\n", + "def get_ssm_parameter(name: str, with_decryption: bool = True) -> str:\n", + " \"\"\"Retrieve parameter from AWS Systems Manager Parameter Store.\"\"\"\n", + " ssm = boto3.client(\"ssm\", region_name=REGION)\n", + " response = ssm.get_parameter(Name=name, WithDecryption=with_decryption)\n", + " return response[\"Parameter\"][\"Value\"]\n", + "\n", + "def initialize_agent(auth_header: str):\n", + " \"\"\"Initialize the agent for first use\"\"\"\n", + " global agent, mcp_client\n", + " \n", + " logger.info(\"Initializing agent for first request\")\n", + " \n", + " # Retrieve Gateway configuration from Parameter Store\n", + " logger.info(\"Fetching Gateway configuration...\")\n", + " gateway_id = get_ssm_parameter(\"/app/customer_support_elastic/agentcore/gateway_id\")\n", + " logger.info(f\"✅ Retrieved Gateway ID: {gateway_id}\")\n", + " \n", + " # Get Gateway URL from AWS API\n", + " gateway_client = boto3.client(\"bedrock-agentcore-control\", region_name=REGION)\n", + " gateway_response = gateway_client.get_gateway(gatewayIdentifier=gateway_id)\n", + " gateway_url = gateway_response['gatewayUrl']\n", + " logger.info(f\"✅ Gateway URL: {gateway_url}\")\n", + " \n", + " # Create Bedrock model\n", + " logger.info(\"Creating Bedrock model...\")\n", + " model = BedrockModel(\n", + " model_id=\"us.anthropic.claude-haiku-4-5-20251001-v1:0\",\n", + " temperature=0.1,\n", + " region_name=REGION\n", + " )\n", + " \n", + " # Create MCP client with authentication\n", + " logger.info(\"Creating MCP client...\")\n", + " mcp_client = MCPClient(lambda: streamablehttp_client(\n", + " url=gateway_url,\n", + " headers={\"Authorization\": auth_header}\n", + " ))\n", + " \n", + " # Initialize MCP client and get tools\n", + " mcp_client.__enter__()\n", + " tools = mcp_client.list_tools_sync()\n", + " logger.info(f\"✅ Loaded {len(tools)} tool(s) from Gateway\")\n", + " \n", + " # Create agent with tools\n", + " logger.info(\"Creating agent with tools...\")\n", + " agent = Agent(\n", + " model=model,\n", + " tools=tools,\n", + " system_prompt=system_prompt,\n", + " )\n", + " logger.info(\"✅ Agent initialized successfully\")\n", + "\n", + "@app.entrypoint\n", + "async def invoke(payload, context=None):\n", + " \"\"\"\n", + " AgentCore Runtime entrypoint function.\n", + " \n", + " Args:\n", + " payload: Dictionary containing 'prompt' key with user input\n", + " context: Runtime context with request_headers containing Authorization token\n", + " \n", + " Returns:\n", + " str: Agent response text\n", + " \"\"\"\n", + " global agent\n", + " \n", + " # Log payload and context\n", + " logger.info(f\"Received payload: {payload}\")\n", + " logger.info(f\"Context: {context}\")\n", + " \n", + " # Extract user input\n", + " user_input = payload.get(\"prompt\")\n", + " \n", + " # Validate required fields\n", + " if user_input is None:\n", + " error_msg = \"❌ ERROR: Missing 'prompt' field in payload\"\n", + " logger.error(error_msg)\n", + " return error_msg\n", + " \n", + " # Get request headers (handle None case)\n", + " request_headers = context.request_headers or {}\n", + " \n", + " # Extract JWT token for Gateway authentication\n", + " auth_header = request_headers.get('Authorization', '')\n", + " \n", + " if not auth_header:\n", + " return \"Error: Missing Authorization header. Please provide a valid OAuth token.\"\n", + " \n", + " logger.info(f\"✅ Authorization header present: {auth_header[:20]}...\")\n", + " \n", + " try:\n", + " # Initialize agent on first request\n", + " if agent is None:\n", + " logger.info(\"First request - initializing agent\")\n", + " initialize_agent(auth_header)\n", + " else:\n", + " logger.info(\"Using existing agent instance\")\n", + " \n", + " # Invoke agent with user input\n", + " logger.info(f\"🤖 Processing query: {user_input[:50]}...\")\n", + " response = agent(user_input)\n", + " \n", + " # Extract and return response text\n", + " response_text = response.message[\"content\"][0][\"text\"]\n", + " logger.info(f\"✅ Agent response: {response_text[:50]}...\")\n", + " \n", + " return response_text\n", + " \n", + " except Exception as e:\n", + " error_msg = f\"Error processing request: {str(e)}\"\n", + " logger.error(f\"❌ {error_msg}\", exc_info=True)\n", + " return error_msg\n", + "\n", + "if __name__ == \"__main__\":\n", + " # Start the HTTP server\n", + " logger.info(\"Starting AgentCore application\")\n", + " app.run()" + ] + }, + { + "cell_type": "markdown", + "id": "153f3182", + "metadata": {}, + "source": [ + "### Step 2: Configure Runtime Deployment\n", + "\n", + "Now we'll configure the deployment using the **AgentCore Starter Toolkit**.\n", + "\n", + "> 💡 **Header Allowlist**: We configure the Runtime to forward the `Authorization` header to our agent code, enabling OAuth token propagation through the entire stack." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "112e9c17", + "metadata": {}, + "outputs": [], + "source": [ + "from bedrock_agentcore_starter_toolkit import Runtime\n", + "from utils_execution import create_agentcore_runtime_execution_role\n", + "\n", + "print(\"⚙️ Configuring AgentCore Runtime deployment...\\n\")\n", + "\n", + "# Create execution role for Runtime\n", + "print(\"🔐 Creating execution role...\")\n", + "execution_role_arn = create_agentcore_runtime_execution_role()\n", + "print(f\"✅ Execution role created: {execution_role_arn}\")\n", + "\n", + "# Initialize Runtime toolkit\n", + "agentcore_runtime = Runtime()\n", + "\n", + "# Configure deployment\n", + "print(\"\\n📋 Configuring deployment parameters...\")\n", + "response = agentcore_runtime.configure(\n", + " entrypoint=\"strands_agent.py\",\n", + " execution_role=execution_role_arn,\n", + " auto_create_ecr=True, # Automatically create ECR repository\n", + " requirements_file=\"requirements.txt\",\n", + " region=REGION,\n", + " agent_name=\"customer_support_elasticsearch_agent\",\n", + " non_interactive=True,\n", + " memory_mode=\"NO_MEMORY\", # We're not using AgentCore Memory in this example\n", + " # Configure Cognito JWT authorization\n", + " authorizer_configuration={\n", + " \"customJWTAuthorizer\": {\n", + " \"allowedClients\": [client_id],\n", + " \"discoveryUrl\": cognito_discovery_url\n", + " }\n", + " },\n", + " # Allow Authorization header to be forwarded to agent\n", + " request_header_configuration={\n", + " \"requestHeaderAllowlist\": [\n", + " \"Authorization\" # Required for OAuth token propagation\n", + " ]\n", + " },\n", + ")\n", + "\n", + "print(\"\\n✅ Configuration completed successfully\")\n", + "print(\"\\n📄 Generated files:\")\n", + "print(\" - Dockerfile\")\n", + "print(\" - .bedrock_agentcore.yaml\")" + ] + }, + { + "cell_type": "markdown", + "id": "d5601176", + "metadata": {}, + "source": [ + "### Step 3: Launch the Agent to Runtime\n", + "\n", + "Now comes the exciting part—launching your agent to production! This step will:\n", + "\n", + "1. **Build Docker Image**: Creates a container with your agent code and dependencies\n", + "2. **Push to ECR**: Uploads the image to Amazon Elastic Container Registry\n", + "3. **Create Runtime**: Provisions the AgentCore Runtime infrastructure\n", + "4. **Configure Networking**: Sets up secure HTTPS endpoints\n", + "5. **Deploy Container**: Runs your agent in the managed environment\n", + "\n", + "> ⏱️ **Deployment Time**: Initial deployment typically can take up to 10 minutes. The toolkit will create an AWS CodeBuild project to build your container.\n", + "\n", + "> 💡 **Update Existing Runtime**: If an agent with the same name already exists, use `auto_update_on_conflict=True` to update it instead of creating a new one." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c157d032", + "metadata": {}, + "outputs": [], + "source": [ + "print(\"🚀 Launching agent to AgentCore Runtime...\\n\")\n", + "print(\"This process will:\")\n", + "print(\" 1. Build Docker container\")\n", + "print(\" 2. Push to Amazon ECR\")\n", + "print(\" 3. Deploy to AgentCore Runtime\")\n", + "print(\" 4. Configure authentication and networking\\n\")\n", + "print(\"⏳ This may take several minutes...\\n\")\n", + "\n", + "try:\n", + " # Launch the agent\n", + " launch_result = agentcore_runtime.launch()\n", + " \n", + " print(\"\\n✅ Launch initiated successfully!\")\n", + " print(f\" Agent ARN: {launch_result.agent_arn}\")\n", + " print(f\" Agent ID: {launch_result.agent_id}\")\n", + " print(f\" ECR URI: {launch_result.ecr_uri}\")\n", + " \n", + "except Exception as e:\n", + " print(f\"\\n❌ Launch failed: {e}\")\n", + " print(\"\\n💡 Tip: If the agent name already exists, you can update it:\")\n", + " print(\" launch_result = agentcore_runtime.launch(auto_update_on_conflict=True)\")\n", + " raise" + ] + }, + { + "cell_type": "markdown", + "id": "c690b525", + "metadata": {}, + "source": [ + "## 8. Testing the deployment\n", + "\n", + "Congratulations! Your agent is now deployed and running. Let's test it with various customer support scenarios.\n", + "\n", + "### Testing Strategy\n", + "\n", + "We'll test three key scenarios:\n", + "1. **Product Troubleshooting**: Testing Elasticsearch retrieval with specific product queries\n", + "2. **General Information**: Testing how the agent handles queries requiring knowledge base search\n", + "3. **Session Continuity**: Verifying the agent session handling\n", + "\n", + "### Authentication for Testing\n", + "\n", + "First, we need a fresh OAuth token (tokens expire after a period of time):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ca573a0", + "metadata": {}, + "outputs": [], + "source": [ + "print(\"🔑 Obtaining fresh OAuth token for testing...\\n\")\n", + "\n", + "try:\n", + " token_response = utils.get_token(user_pool_id, client_id, client_secret, scope_string, REGION)\n", + " test_token = token_response[\"access_token\"]\n", + " \n", + " print(\"✅ Token obtained successfully\")\n", + " print(f\" Token (truncated): {test_token[:50]}...\")\n", + "except Exception as e:\n", + " print(f\"❌ Error obtaining token: {e}\")\n", + " raise" + ] + }, + { + "cell_type": "markdown", + "id": "test-scenarios", + "metadata": {}, + "source": [ + "### Test Scenario 1: Product Troubleshooting\n", + "\n", + "Let's test our agent with a customer asking about troubleshooting a specific product:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9efc4ab8", + "metadata": {}, + "outputs": [], + "source": [ + "print(\"🧪 Test 1: Product Troubleshooting\\n\")\n", + "print(\"=\" * 70)\n", + "\n", + "user_query = \"How can I troubleshoot my LAPTOP ULTRA 15.6?\"\n", + "print(f\"👤 Customer: {user_query}\\n\")\n", + "\n", + "try:\n", + " response = agentcore_runtime.invoke(\n", + " {\"prompt\": user_query},\n", + " bearer_token=test_token,\n", + " )\n", + " \n", + " print(f\"🤖 Agent Response:\")\n", + " print(\"-\" * 70)\n", + " print(response[\"response\"])\n", + " print(\"=\" * 70)\n", + " print(\"\\n✅ Test 1 completed successfully\\n\")\n", + " \n", + "except Exception as e:\n", + " print(f\"❌ Test failed: {e}\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "test-scenario-2", + "metadata": {}, + "source": [ + "### Test Scenario 2: General Product Information\n", + "\n", + "Let's test with a different type of query about product support:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "208df52d", + "metadata": {}, + "outputs": [], + "source": [ + "print(\"🧪 Test 2: General Product Information\\n\")\n", + "print(\"=\" * 70)\n", + "\n", + "user_query = \"What number can I call for health related questions on my Smart Watch Series X?\"\n", + "print(f\"👤 Customer: {user_query}\\n\")\n", + "\n", + "try:\n", + " response = agentcore_runtime.invoke(\n", + " {\"prompt\": user_query},\n", + " bearer_token=test_token,\n", + " )\n", + " \n", + " print(f\"🤖 Agent Response:\")\n", + " print(\"-\" * 70)\n", + " print(response[\"response\"])\n", + " print(\"=\" * 70)\n", + " print(\"\\n✅ Test 2 completed successfully\\n\")\n", + " \n", + "except Exception as e:\n", + " print(f\"❌ Test failed: {e}\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "test-scenario-3", + "metadata": {}, + "source": [ + "### Test Scenario 3: Session Management\n", + "\n", + "Let's test session continuity by asking a follow-up question:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "test-session", + "metadata": {}, + "outputs": [], + "source": [ + "print(\"🧪 Test 3: Session Management\\n\")\n", + "print(\"=\" * 70)\n", + "\n", + "# Create a session ID for continuity\n", + "session_id = f\"test-session-{uuid.uuid4()}\"\n", + "\n", + "# First query\n", + "user_query_1 = \"What information do you have for Smart Watch Series X?\"\n", + "print(f\"👤 Customer (Message 1): {user_query_1}\\n\")\n", + "\n", + "response_1 = agentcore_runtime.invoke(\n", + " {\"prompt\": user_query_1},\n", + " bearer_token=test_token,\n", + " session_id=session_id\n", + ")\n", + "\n", + "print(f\"🤖 Agent: {response_1['response'][:200]}...\\n\")\n", + "\n", + "# Follow-up query\n", + "user_query_2 = \"Can you tell me more about the specific product I asked about?\"\n", + "print(f\"👤 Customer (Message 2): {user_query_2}\\n\")\n", + "\n", + "response_2 = agentcore_runtime.invoke(\n", + " {\"prompt\": user_query_2},\n", + " bearer_token=test_token,\n", + " session_id=session_id\n", + ")\n", + "\n", + "print(f\"🤖 Agent:\")\n", + "print(\"-\" * 70)\n", + "print(response_2['response'])\n", + "print(\"=\" * 70)\n", + "print(\"\\n✅ Test 3 completed successfully\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "key-concepts", + "metadata": {}, + "source": [ + "## Key Concepts Summary\n", + "\n", + "Congratulations on completing this tutorial! Let's review the key concepts you've learned:\n", + "\n", + "### 1. AgentCore Gateway Architecture\n", + "- **MCP Transformation**: How to transform Lambda functions into Model Context Protocol (MCP) tools\n", + "- **Dual Authentication**: Implementing both inbound (OAuth) and outbound (IAM) authentication\n", + "- **Managed Infrastructure**: Leveraging fully-managed Gateway services to eliminate operational overhead\n", + "\n", + "### 2. Authentication & Security\n", + "- **Cognito Integration**: Setting up OAuth 2.0 authentication with Amazon Cognito\n", + "- **Token Propagation**: Forwarding JWT tokens through Runtime to Gateway\n", + "- **IAM Roles**: Configuring least-privilege access for Gateway and Runtime\n", + "- **Defense in Depth**: Multiple layers of security from client to data store\n", + "\n", + "### 3. Elasticsearch Integration\n", + "- **RAG Pattern**: Implementing Retrieval-Augmented Generation with Elasticsearch\n", + "- **Lambda as Data Bridge**: Using Lambda to securely connect Gateway to Elasticsearch\n", + "- **Semantic Search**: Performing multi-match queries for relevant document retrieval\n", + "\n", + "### 4. AgentCore Runtime Deployment\n", + "- **Containerization**: Packaging agents as Docker containers for portability\n", + "- **Starter Toolkit**: Using automation tools to simplify deployment processes\n", + "- **Production Readiness**: Deploying scalable, managed agent services\n", + "- **Header Configuration**: Forwarding authentication and custom headers to agents\n", + "\n", + "### 5. MCP Protocol\n", + "- **Standard Interface**: Using MCP for consistent tool integration\n", + "- **Tool Discovery**: Dynamic tool loading via `ListTools` operation\n", + "- **Tool Invocation**: Executing tools via `InvokeTools` operation\n", + "- **Client Libraries**: Leveraging MCP client SDKs for seamless integration\n", + "\n", + "### Architecture Benefits\n", + "\n", + "This architecture provides several production advantages:\n", + "\n", + "✅ **Scalability**: All components (Gateway, Runtime, Lambda) scale automatically\n", + "\n", + "✅ **Security**: Multiple authentication layers protect every access point\n", + "\n", + "✅ **Maintainability**: Separation of concerns makes updates and debugging easier\n", + "\n", + "✅ **Flexibility**: Easy to add new tools, data sources, or agents\n", + "\n", + "✅ **Cost Efficiency**: Pay only for actual usage with serverless components" + ] + }, + { + "cell_type": "markdown", + "id": "cleanup", + "metadata": {}, + "source": [ + "## Cleanup (Optional)\n", + "\n", + "To avoid incurring unnecessary AWS charges, you can delete the resources created in this tutorial. This section will guide you through cleaning up:\n", + "\n", + "1. **AgentCore Runtime Agent** - The deployed agent container\n", + "2. **AgentCore Gateway** - The MCP Gateway and targets\n", + "3. **Lambda Function** - The Elasticsearch query function\n", + "4. **Cognito Resources** - User pool and app clients\n", + "5. **IAM Roles** - Execution roles created for services\n", + "6. **ECR Repository** - Container image repository\n", + "7. **SSM Parameters** - Stored configuration values\n", + "\n", + "> ⚠️ **Warning**: This action is irreversible. Make sure you want to delete these resources before proceeding.\n", + "\n", + "### Cleanup Steps" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cleanup-code", + "metadata": {}, + "outputs": [], + "source": [ + "# ⚠️ WARNING: Only run this cell if you want to delete all created resources\n", + "\n", + "print(\"🧹 Starting cleanup process...\\n\")\n", + "print(\"=\" * 70)\n", + "\n", + "cleanup_errors = []\n", + "\n", + "# 1. Delete AgentCore Runtime\n", + "print(\"\\n1️⃣ Deleting AgentCore Runtime...\")\n", + "try:\n", + " if 'launch_result' in locals() and hasattr(launch_result, 'agent_id'):\n", + " runtime_client = boto3.client('bedrock-agentcore-control', region_name=REGION)\n", + " runtime_client.delete_agent_runtime(agentRuntimeId=launch_result.agent_id)\n", + " print(f\" ✅ Deleted Runtime: {launch_result.agent_id}\")\n", + " else:\n", + " print(\" ⏭️ No Runtime to delete\")\n", + "except Exception as e:\n", + " print(f\" ❌ Error: {e}\")\n", + " cleanup_errors.append(f\"Runtime deletion: {e}\")\n", + "\n", + "# 2. Delete Gateway Target\n", + "print(\"\\n2️⃣ Deleting Gateway Target...\")\n", + "try:\n", + " if 'target_id' in locals() and 'gateway_id' in locals():\n", + " gateway_client.delete_gateway_target(\n", + " gatewayIdentifier=gateway_id,\n", + " targetId=target_id\n", + " )\n", + " print(f\" ✅ Deleted Target: {target_id}\")\n", + " else:\n", + " print(\" ⏭️ No Gateway Target to delete\")\n", + "except Exception as e:\n", + " print(f\" ❌ Error: {e}\")\n", + " cleanup_errors.append(f\"Gateway Target deletion: {e}\")\n", + "\n", + "# 3. Delete Gateway\n", + "time.sleep(10)\n", + "print(\"\\n3️⃣ Deleting AgentCore Gateway...\")\n", + "try:\n", + " if 'gateway_id' in locals():\n", + " gateway_client.delete_gateway(gatewayIdentifier=gateway_id)\n", + " print(f\" ✅ Deleted Gateway: {gateway_id}\")\n", + " else:\n", + " print(\" ⏭️ No Gateway to delete\")\n", + "except Exception as e:\n", + " print(f\" ❌ Error: {e}\")\n", + " cleanup_errors.append(f\"Gateway deletion: {e}\")\n", + "\n", + "# 4. Delete Lambda Function\n", + "print(\"\\n4️⃣ Deleting Lambda Function...\")\n", + "try:\n", + " if 'lambda_function_arn' in locals():\n", + " lambda_client = boto3.client('lambda', region_name=REGION)\n", + " function_name = lambda_function_arn.split(':')[-1]\n", + " lambda_client.delete_function(FunctionName=function_name)\n", + " print(f\" ✅ Deleted Lambda: {function_name}\")\n", + " else:\n", + " print(\" ⏭️ No Lambda function to delete\")\n", + "except Exception as e:\n", + " print(f\" ❌ Error: {e}\")\n", + " cleanup_errors.append(f\"Lambda deletion: {e}\")\n", + "\n", + "# 5. Delete Cognito User Pool\n", + "print(\"\\n5️⃣ Deleting Cognito User Pool…\")\n", + "try:\n", + " if 'user_pool_id' in locals():\n", + " # First, delete the domain if it exists\n", + " try:\n", + " response = cognito.describe_user_pool(UserPoolId=user_pool_id)\n", + " domain = response.get('UserPool', {}).get('Domain')\n", + " if domain:\n", + " cognito.delete_user_pool_domain(\n", + " Domain=domain,\n", + " UserPoolId=user_pool_id\n", + " )\n", + " print(f\" ✅ Deleted User Pool Domain: {domain}\")\n", + " except cognito.exceptions.ResourceNotFoundException:\n", + " print(\" ⏭️ No domain found\")\n", + " except Exception as domain_error:\n", + " print(f\" ⚠️ Domain deletion warning: {domain_error}\")\n", + " \n", + " # Now delete the user pool\n", + " cognito.delete_user_pool(UserPoolId=user_pool_id)\n", + " print(f\" ✅ Deleted User Pool: {user_pool_id}\")\n", + " else:\n", + " print(\" ⏭️ No User Pool to delete\")\n", + "except Exception as e:\n", + " print(f\" ❌ Error: {e}\")\n", + " cleanup_errors.append(f\"Cognito deletion: {e}\")\n", + "\n", + "# 6. Delete ECR Repository\n", + "print(\"\\n6️⃣ Deleting ECR Repository...\")\n", + "try:\n", + " if 'launch_result' in locals() and hasattr(launch_result, 'ecr_uri'):\n", + " ecr_client = boto3.client('ecr', region_name=REGION)\n", + " repo_name = launch_result.ecr_uri.split('/')[1].split(':')[0]\n", + " ecr_client.delete_repository(repositoryName=repo_name, force=True)\n", + " print(f\" ✅ Deleted ECR Repository: {repo_name}\")\n", + " else:\n", + " print(\" ⏭️ No ECR Repository to delete\")\n", + "except Exception as e:\n", + " print(f\" ❌ Error: {e}\")\n", + " cleanup_errors.append(f\"ECR deletion: {e}\")\n", + "\n", + "# 7. Delete SSM Parameters\n", + "print(\"\\n7️⃣ Deleting SSM Parameters...\")\n", + "try:\n", + " ssm_client = boto3.client('ssm', region_name=REGION)\n", + " ssm_client.delete_parameter(Name=\"/app/customer_support_elastic/agentcore/gateway_id\")\n", + " print(\" ✅ Deleted SSM Parameters\")\n", + "except Exception as e:\n", + " print(f\" ⏭️ No SSM Parameters to delete (or already deleted)\")\n", + "\n", + "# Summary\n", + "print(\"\\n\" + \"=\" * 70)\n", + "if cleanup_errors:\n", + " print(\"⚠️ Cleanup completed with errors:\")\n", + " for error in cleanup_errors:\n", + " print(f\" - {error}\")\n", + "else:\n", + " print(\"✅ Cleanup completed successfully!\")\n", + "print(\"=\" * 70)\n", + "\n", + "print(\"\\n💡 Note: IAM roles may have a deletion delay due to AWS IAM eventual consistency.\")\n", + "print(\" You can manually delete them from the IAM console if needed.\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/03-integrations/vector-stores/elasticsearch/images/runtime-gateway-elastic.png b/03-integrations/vector-stores/elasticsearch/images/runtime-gateway-elastic.png new file mode 100644 index 000000000..7b218fd7f Binary files /dev/null and b/03-integrations/vector-stores/elasticsearch/images/runtime-gateway-elastic.png differ diff --git a/03-integrations/vector-stores/elasticsearch/policies/customer_support_policies.txt b/03-integrations/vector-stores/elasticsearch/policies/customer_support_policies.txt new file mode 100644 index 000000000..1861aa303 --- /dev/null +++ b/03-integrations/vector-stores/elasticsearch/policies/customer_support_policies.txt @@ -0,0 +1,162 @@ +CUSTOMER SUPPORT POLICIES AND PROCEDURES +======================================= + +1. GENERAL CUSTOMER SERVICE PRINCIPLES +===================================== + +1.1 Customer First Approach +- Always prioritize customer satisfaction and experience +- Treat every customer with respect, empathy, and professionalism +- Listen actively to understand customer needs and concerns +- Take ownership of issues and follow through to resolution + +1.2 Response Time Standards +- Phone calls: Answer within 3 rings or 20 seconds +- Email inquiries: Respond within 2 hours during business hours +- Live chat: Initial response within 30 seconds +- Social media: Respond within 1 hour during business hours +- Escalated issues: Acknowledge within 15 minutes + +1.3 Communication Guidelines +- Use clear, simple language avoiding technical jargon +- Maintain a friendly, helpful, and professional tone +- Provide accurate information or clearly state when you need to research +- Always confirm customer understanding before ending interactions +- Document all interactions in the customer management system + +2. WARRANTY AND RETURNS POLICY +============================= + +2.1 Warranty Coverage +- Standard warranty: 1 year from purchase date +- Extended warranty: Available for purchase, extends coverage up to 3 years +- Premium warranty: Includes accidental damage protection +- Warranty covers manufacturing defects and hardware failures +- Software issues covered for first 90 days + +2.2 Warranty Claim Process +- Verify purchase date and warranty status in system +- Collect detailed description of the issue +- Attempt troubleshooting steps before approving replacement +- For valid claims, provide prepaid shipping label +- Process replacement within 3-5 business days of receiving defective item + +2.3 Return Policy +- 30-day return window from purchase date +- Items must be in original condition with all accessories +- Restocking fee: 15% for opened electronics, waived for defective items +- Refund processed within 5-7 business days after inspection +- Customer pays return shipping unless item is defective + +3. ESCALATION PROCEDURES +======================= + +3.1 When to Escalate +- Customer requests to speak with supervisor +- Warranty claim exceeds $500 in value +- Customer threatens legal action +- Complaint involves safety concerns +- Unable to resolve issue within 48 hours +- Customer expresses extreme dissatisfaction + +3.2 Escalation Levels +- Level 1: Senior Support Agent +- Level 2: Team Lead/Supervisor +- Level 3: Customer Service Manager +- Level 4: Director of Customer Experience + +3.3 Escalation Process +- Provide complete case summary to escalation contact +- Include all previous communication history +- Document reason for escalation +- Follow up within 24 hours to ensure progress +- Keep customer informed of escalation status + +4. CUSTOMER DATA AND PRIVACY +=========================== + +4.1 Data Protection +- Never share customer information with unauthorized parties +- Verify customer identity before discussing account details +- Use secure channels for sharing sensitive information +- Follow GDPR and other applicable privacy regulations +- Report any suspected data breaches immediately + +4.2 Account Verification +- Verify identity using email address, phone number, or account PIN +- For high-value accounts, use two-factor authentication +- Document verification method used in case notes +- If unable to verify, escalate to supervisor + +5. COMPLAINT HANDLING +==================== + +5.1 Complaint Resolution Process +- Acknowledge the complaint and apologize for the inconvenience +- Gather all relevant details and document thoroughly +- Investigate the issue using all available resources +- Provide solution or explanation within committed timeframe +- Follow up to ensure customer satisfaction + +5.2 Compensation Guidelines +- Service failures: Offer sincere apology and service credit +- Product defects: Full refund or replacement at customer's choice +- Shipping delays: Offer expedited shipping or partial refund +- Multiple issues: Consider goodwill gesture or account credit +- All compensation over $100 requires supervisor approval + +6. PRODUCT KNOWLEDGE REQUIREMENTS +================================ + +6.1 Technical Competency +- Maintain current knowledge of all product lines +- Complete monthly product training sessions +- Stay updated on common issues and solutions +- Understand warranty terms for each product category +- Know troubleshooting procedures for major products + +6.2 System Knowledge +- Proficient in customer management system +- Understand warranty lookup and claim processes +- Know how to process returns and exchanges +- Familiar with inventory and shipping systems +- Able to generate customer reports and summaries + +7. QUALITY ASSURANCE +=================== + +7.1 Performance Metrics +- Customer satisfaction score: Target 90% or higher +- First call resolution rate: Target 80% or higher +- Average handling time: Product-specific targets +- Escalation rate: Less than 5% of total interactions +- Policy compliance: 100% adherence required + +7.2 Monitoring and Feedback +- Regular call monitoring and coaching sessions +- Monthly one-on-one performance reviews +- Customer feedback surveys for service quality +- Peer review and knowledge sharing sessions +- Continuous improvement initiatives + +8. EMERGENCY PROCEDURES +====================== + +8.1 Safety Issues +- Immediately escalate any safety-related concerns +- Document all details of safety incidents +- Provide clear safety instructions to customers +- Follow up to ensure customer safety +- Report to product safety team within 1 hour + +8.2 System Outages +- Follow backup procedures for system failures +- Keep customers informed of service disruptions +- Use alternative communication methods when necessary +- Prioritize urgent issues during limited service +- Document all outage-related customer interactions + +POLICY COMPLIANCE +================ +All customer support staff must familiarize themselves with these policies and follow them consistently. Violations may result in corrective action up to and including termination. This policy is effective immediately and supersedes all previous versions. + diff --git a/03-integrations/vector-stores/elasticsearch/policies/gaming_console_pro_manual.txt b/03-integrations/vector-stores/elasticsearch/policies/gaming_console_pro_manual.txt new file mode 100644 index 000000000..5a37215ef --- /dev/null +++ b/03-integrations/vector-stores/elasticsearch/policies/gaming_console_pro_manual.txt @@ -0,0 +1,261 @@ +GAMING CONSOLE PRO - USER MANUAL & TROUBLESHOOTING GUIDE +======================================================== + +PRODUCT SPECIFICATIONS +===================== +Model: Gaming Console Pro +Serial Number Format: MNO######## +SKU: GCP-1TB-BLK/WHT +Processor: AMD Zen 3 8-core CPU @ 3.5GHz +Graphics: Custom AMD RDNA 2 GPU (10.3 TFLOPs) +Memory: 16GB GDDR6 RAM +Storage: 1TB Custom NVMe SSD (expandable) +4K Gaming: Up to 120fps supported +Ray Tracing: Hardware-accelerated +Audio: 3D Spatial Audio, Dolby Atmos +Ports: HDMI 2.1, 3x USB-A 3.2, 1x USB-C, Ethernet, Power +Wireless: Wi-Fi 6, Bluetooth 5.1 +Optical Drive: 4K UHD Blu-ray (read-only) +Dimensions: 15.1 x 10.4 x 4.1 inches +Weight: 9.9 lbs (4.5 kg) + +INITIAL SETUP +============ +1. Place console in well-ventilated area (6 inches clearance) +2. Connect HDMI cable to TV/monitor +3. Connect power cable to outlet +4. Press power button on front of console +5. Follow on-screen setup wizard +6. Connect to internet (Wi-Fi or Ethernet) +7. Create or sign in to gaming account +8. Download system updates +9. Set up parental controls if needed + +CONTROLLER SETUP +=============== +1. Press Xbox/PlayStation button to turn on +2. Connect USB cable for initial pairing +3. Press pairing button on console and controller +4. Wait for controller LED to become solid +5. Test all buttons and triggers +6. Adjust controller settings in system menu +7. Set up additional controllers as needed + +COMMON TROUBLESHOOTING +===================== + +CONSOLE WON'T TURN ON +- Check power cable connection +- Try different power outlet +- Ensure power strip is turned on +- Check for overheating (cool down for 30 minutes) +- Listen for fan noise when pressing power button +- May need power supply replacement + +OVERHEATING ISSUES +- Ensure proper ventilation around console +- Clean air vents with compressed air +- Check room temperature (keep below 80°F/27°C) +- Avoid placing in enclosed spaces +- Turn off console when not in use +- Consider external cooling fan for entertainment center + +NO VIDEO OUTPUT +- Check HDMI cable connection +- Try different HDMI port on TV +- Test with different HDMI cable +- Ensure TV is on correct input +- Try different TV/monitor +- Check HDMI cable supports 4K if using 4K mode + +AUDIO PROBLEMS +No Sound: +- Check TV/monitor volume settings +- Verify HDMI connection carries audio +- Check audio output settings in console menu +- Try different audio format +- Test with headphones connected to controller + +Audio Cutting Out: +- Check HDMI cable quality +- Update audio drivers +- Change audio format to more compatible option +- Check for electromagnetic interference + +CONTROLLER ISSUES +Won't Connect: +- Press pairing button on console and controller +- Reset controller with small button on back +- Try connecting with USB cable +- Check battery level +- Try different controller to isolate issue + +Stick Drift: +- Calibrate controller in settings menu +- Clean around analog sticks with compressed air +- Adjust deadzone settings in games +- May require controller replacement under warranty + +GAME INSTALLATION PROBLEMS +- Check available storage space +- Pause and resume download +- Clear system cache +- Check internet connection stability +- Try installing from disc instead of download +- Restart console and try again + +NETWORK CONNECTIVITY +Wi-Fi Issues: +- Check Wi-Fi password +- Move closer to router +- Restart console and router +- Try 5GHz network instead of 2.4GHz +- Check for network congestion +- Use wired connection for best performance + +Online Gaming Problems: +- Check NAT type (Open preferred) +- Forward ports on router if needed +- Check internet speed (minimum 25Mbps for 4K gaming) +- Contact ISP if consistent issues +- Try different DNS servers + +STORAGE MANAGEMENT +=================== +- Delete unused games and apps +- Move games to external storage +- Clear cache files regularly +- Check for corrupted save files +- Use cloud save backup +- Consider upgrading internal SSD + +PERFORMANCE OPTIMIZATION +======================= +- Keep system software updated +- Restart console weekly +- Clear cache monthly +- Ensure adequate cooling +- Close unused applications +- Use Game Mode on TV/monitor +- Adjust graphics settings for performance vs. quality + +DISC DRIVE ISSUES +================ +Won't Read Discs: +- Clean disc with soft cloth (center to edge) +- Check for scratches or damage +- Try different disc to test drive +- Ensure disc is inserted correctly +- Check region compatibility +- May need optical drive replacement + +Disc Stuck: +- Turn off console completely +- Unplug power for 30 seconds +- Try manual eject (check manual for location) +- Contact support if disc remains stuck +- Do not force or shake console + +SYSTEM CRASHES +============== +- Note error codes when they appear +- Check for overheating +- Ensure adequate power supply +- Check for corrupted game files +- Boot in safe mode +- Factory reset as last resort (backup saves first) + +STREAMING AND MEDIA +================== +- Update streaming apps regularly +- Check internet speed (4K requires 25Mbps+) +- Clear app cache if streaming issues occur +- Ensure TV supports HDR if using HDR content +- Use wired connection for 4K streaming +- Close games before streaming for better performance + +PARENTAL CONTROLS +================= +- Set up family accounts for children +- Control spending limits +- Restrict access to mature content +- Set time limits for gaming +- Monitor online interactions +- Block purchase of certain content types + +MAINTENANCE SCHEDULE +=================== +Weekly: +- Restart console at least once +- Check for system updates +- Clean controller surfaces + +Monthly: +- Clean air vents with compressed air +- Check cable connections +- Clear system cache +- Update installed games + +Annually: +- Professional cleaning of internal components +- Check warranty status +- Backup save files to cloud/external storage +- Update any external accessories + +WARRANTY COVERAGE +================ +Standard Warranty: 12 months +- Manufacturing defects +- Hardware component failures +- Power supply issues +- Optical drive problems +- Controller defects (90 days) + +Gaming Warranty: Up to 24 months +- All standard coverage +- Advanced replacement program +- Controller replacement included +- Priority support for online gaming issues + +NOT COVERED: +- Physical damage from drops/impacts +- Liquid damage +- Damage from power surges +- Unauthorized modifications +- Normal wear on controller thumbsticks +- Scratched game discs + +SAFETY INFORMATION +================= +- Ensure proper ventilation at all times +- Use only official power cables +- Keep away from liquids +- Don't move console while disc is spinning +- Turn off during electrical storms +- Keep console level and stable +- Don't block air vents + +TECHNICAL SUPPORT +================ +Before Calling Support: +- Note exact error messages +- Try basic troubleshooting steps +- Have serial number ready +- Know software version +- List recently installed games/apps + +CONTACT INFORMATION +================== +Gaming Support: 1-800-555-GAMING +Technical Support: 1-800-555-TECH +Warranty Service: 1-800-555-WARRANTY +Online Support: support.company.com/gaming +Community Forums: community.company.com +Live Chat: Available 24/7 on support website + +SERIAL NUMBER LOCATION +===================== +Back panel of console (white sticker) +System Settings > Console Information +Original packaging barcode label +Power-on screen (briefly displayed) diff --git a/03-integrations/vector-stores/elasticsearch/policies/laptop_ultra_15_6_manual.txt b/03-integrations/vector-stores/elasticsearch/policies/laptop_ultra_15_6_manual.txt new file mode 100644 index 000000000..c54b72cda --- /dev/null +++ b/03-integrations/vector-stores/elasticsearch/policies/laptop_ultra_15_6_manual.txt @@ -0,0 +1,183 @@ + +LAPTOP ULTRA 15.6" - USER MANUAL & TROUBLESHOOTING GUIDE +======================================================== + +PRODUCT SPECIFICATIONS +===================== +Model: Laptop Ultra 15.6" +Serial Number Format: DEF######## +SKU: LU-156-SLV/BLK +Dimensions: 14.1 x 9.7 x 0.7 inches +Weight: 4.2 lbs (1.9 kg) +Display: 15.6" Full HD IPS (1920x1080) +Processor: Intel Core i7-12700H +RAM: 16GB DDR4 (expandable to 32GB) +Storage: 512GB NVMe SSD +Graphics: Intel Iris Xe integrated +Operating System: Windows 11 Home +Battery: 70Wh Li-Polymer +Ports: 2x USB-A 3.2, 2x USB-C/Thunderbolt 4, HDMI 2.1, 3.5mm audio +Wireless: Wi-Fi 6E, Bluetooth 5.2 + +INITIAL SETUP +============ +1. Connect power adapter and charge for 30 minutes +2. Press power button to turn on +3. Follow Windows 11 setup wizard +4. Connect to Wi-Fi network +5. Sign in with Microsoft account +6. Install Windows updates +7. Install manufacturer drivers from included USB +8. Register product for warranty + +BASIC OPERATIONS +=============== +Power On: Press power button once +Sleep Mode: Close lid or press power button briefly +Shutdown: Start menu > Power > Shut down +Force Shutdown: Hold power button for 10 seconds +Function Keys: Fn + F1-F12 for special functions +Touchpad: Two-finger scroll, pinch to zoom +Backlit Keyboard: Fn + F10 to toggle + +COMMON TROUBLESHOOTING +===================== + +WON'T TURN ON +- Check power adapter connection +- Try different power outlet +- Remove battery (if removable) for 30 seconds +- Hold power button for 15 seconds to discharge +- Check for damaged power adapter + +OVERHEATING +- Check air vents for dust blockage +- Use on hard, flat surfaces only +- Close unnecessary programs +- Update BIOS and drivers +- Consider professional cleaning if issue persists + +SLOW PERFORMANCE +- Check available storage space (need 15% free minimum) +- Run Disk Cleanup utility +- Disable startup programs not needed +- Scan for malware +- Check RAM usage in Task Manager +- Consider SSD upgrade if using traditional HDD + +BATTERY ISSUES +- Calibrate battery (full discharge then full charge) +- Check power settings for battery optimization +- Replace battery if capacity drops below 80% +- Update battery drivers +- Avoid extreme temperatures + +DISPLAY PROBLEMS +Black Screen: +- Check brightness settings (Fn + F6/F7) +- Connect external monitor to test +- Update graphics drivers +- Check display cable connection internally + +Flickering Screen: +- Update graphics drivers +- Check refresh rate settings +- Test with external monitor +- May indicate loose cable connection + +KEYBOARD/TOUCHPAD ISSUES +- Update input device drivers +- Check for Windows updates +- Clean keyboard with compressed air +- Enable/disable touchpad: Fn + F9 +- External mouse can be used as alternative + +AUDIO PROBLEMS +- Check volume settings and mute status +- Update audio drivers +- Check default audio device settings +- Test with headphones to isolate speaker issues +- Run Windows audio troubleshooter + +NETWORK CONNECTIVITY +Wi-Fi Issues: +- Check Wi-Fi is enabled (Fn + F2) +- Forget and reconnect to network +- Update Wi-Fi drivers +- Reset network adapters +- Check router compatibility + +Ethernet Issues: +- Check cable connection +- Update network drivers +- Test with different ethernet cable +- Check network adapter settings + +STORAGE ISSUES +- Run Check Disk utility (chkdsk) +- Defragment hard drive (HDD only) +- Check for failing sectors +- Monitor temperature with HWiNFO +- Backup data regularly + +SOFTWARE ISSUES +Blue Screen of Death (BSOD): +- Note error code and message +- Boot in Safe Mode +- Update or rollback recent drivers +- Run Windows Memory Diagnostic +- System restore to previous working state + +Slow Boot: +- Disable unnecessary startup programs +- Run SFC /scannow command +- Update BIOS +- Check hard drive health +- Consider clean Windows installation + +WARRANTY COVERAGE +================ +Standard Warranty: 12 months +- Manufacturing defects +- Hardware component failures +- Screen defects (dead pixels if >5) +- Keyboard and touchpad issues +- Battery issues (if capacity <80%) + +Extended Warranty: Up to 36 months +- All standard coverage +- Advanced replacement program +- Battery replacement included +- Priority technical support + +NOT COVERED: +- Physical damage from drops/impacts +- Liquid damage +- Software issues after 90 days +- Wear items (keyboard keys, touchpad surface) +- Damage from unauthorized repairs + +MAINTENANCE TIPS +=============== +- Clean vents monthly with compressed air +- Wipe screen with microfiber cloth +- Avoid eating/drinking near laptop +- Use laptop bag for transport +- Keep software updated +- Run antivirus scans regularly +- Backup important data weekly + +CONTACT INFORMATION +================== +Technical Support: 1-800-555-LAPTOP +Warranty Service: 1-800-555-WARRANTY +Online Support: support.company.com/laptop +Driver Downloads: drivers.company.com +Email Support: laptop-support@company.com + +SERIAL NUMBER LOCATION +===================== +Bottom of laptop (white sticker) +Settings > System > About +Original packaging barcode label +BIOS/UEFI system information screen diff --git a/03-integrations/vector-stores/elasticsearch/policies/smart_watch_series_x_manual.txt b/03-integrations/vector-stores/elasticsearch/policies/smart_watch_series_x_manual.txt new file mode 100644 index 000000000..67d4602a0 --- /dev/null +++ b/03-integrations/vector-stores/elasticsearch/policies/smart_watch_series_x_manual.txt @@ -0,0 +1,232 @@ +SMART WATCH SERIES X - USER MANUAL & TROUBLESHOOTING GUIDE +========================================================== + +PRODUCT SPECIFICATIONS +===================== +Model: Smart Watch Series X +Serial Number Format: JKL######## +SKU: SW-SX-BLK/WHT/ROS/BLU +Display: 1.78" AMOLED Always-On Display (448 x 368) +Processor: Dual-core 1.18GHz +Storage: 32GB internal +RAM: 1GB +Operating System: WatchOS 10 +Battery: 308mAh Li-Ion +Battery Life: 18 hours typical use, 36 hours low power mode +Water Resistance: 50 meters (swimming approved) +Sensors: Heart rate, ECG, Blood oxygen, GPS, accelerometer, gyroscope +Connectivity: Wi-Fi 802.11b/g/n, Bluetooth 5.0, NFC, Optional LTE +Dimensions: 45mm x 38mm x 10.7mm +Weight: 38.8g (without band) + +INITIAL SETUP +============ +1. Download companion app on iPhone/Android +2. Turn on watch by holding side button +3. Select language and region +4. Open app and tap "Start Pairing" +5. Hold watch near phone until paired +6. Sign in with Apple ID/Google account +7. Choose apps to install +8. Set up Apple Pay/Google Pay +9. Configure health and fitness settings + +BASIC NAVIGATION +=============== +Digital Crown: Scroll through apps and menus +Side Button: Access recent apps, Apple Pay, Emergency SOS +Touch Screen: Tap, swipe, and long press +Control Center: Swipe up from bottom +Notification Center: Swipe down from top +App Dock: Press side button once +Home Screen: Press Digital Crown + +COMMON TROUBLESHOOTING +===================== + +WATCH WON'T TURN ON +- Charge for at least 30 minutes +- Check charging cable connection +- Clean charging contacts on back of watch +- Try different power adapter +- Force restart: Hold side button + Digital Crown for 10 seconds + +BATTERY DRAINS QUICKLY +- Check background app refresh settings +- Disable always-on display if not needed +- Reduce screen brightness +- Turn off unnecessary notifications +- Disable location services for unused apps +- Check for rogue apps consuming power + +CHARGING ISSUES +- Ensure charging contacts are clean +- Check for debris in charging port +- Try different charging cable +- Ensure charger is properly aligned +- Check power source (try different adapter) +- Charging should begin immediately when properly connected + +CONNECTIVITY PROBLEMS +Bluetooth Issues: +- Ensure phone is within range (30 feet) +- Restart both watch and phone +- Forget and re-pair devices +- Check Bluetooth is enabled on phone +- Reset network settings on watch + +Wi-Fi Issues: +- Check Wi-Fi password is correct +- Restart router if necessary +- Ensure network is 2.4GHz or 5GHz compatible +- Try different Wi-Fi network +- Reset network settings + +GPS NOT WORKING +- Enable location services in settings +- Ensure clear view of sky when outdoors +- Allow GPS to calibrate (may take 2-3 minutes) +- Check GPS accuracy in settings +- Restart watch if GPS seems stuck + +HEART RATE SENSOR ISSUES +- Ensure watch is snug but not too tight +- Clean sensor on back of watch +- Check for tattoos or scars affecting reading +- Allow sensor to calibrate during setup +- Try different wrist position + +APPS CRASHING OR SLOW +- Force close problematic apps +- Restart watch +- Check for app updates +- Free up storage space +- Reset all settings if problem persists + +WATER DAMAGE PREVENTION +====================== +Despite 50m water resistance: +- Rinse with fresh water after ocean/pool use +- Dry thoroughly before charging +- Avoid soap, shampoo, or chemicals +- Don't press buttons underwater +- Check seals regularly for wear + +AFTER WATER EXPOSURE: +1. Remove from water immediately if issues occur +2. Power off watch +3. Dry with lint-free cloth +4. Leave in dry environment for 24 hours +5. Do not attempt to charge if wet inside + +FITNESS AND HEALTH FEATURES +=========================== +Heart Rate Monitoring: +- Continuous monitoring throughout day +- Workout heart rate zones +- Irregular rhythm notifications +- Resting and walking heart rate trends + +Activity Tracking: +- Stand reminders every hour +- Move goal based on calories burned +- Exercise goal for minutes of brisk activity +- Workout detection for common exercises + +Sleep Tracking: +- Automatic sleep detection +- Sleep stages monitoring +- Sleep schedule and wind-down +- Sleep goal setting and tracking + +WATCH FACE CUSTOMIZATION +======================== +- Press and hold current watch face +- Swipe to browse available faces +- Tap "Customize" to edit complications +- Add complications by tapping empty spaces +- Download additional faces from app store + +NOTIFICATION MANAGEMENT +====================== +- Mirror iPhone notification settings by default +- Customize which apps send notifications +- Set Do Not Disturb schedules +- Configure haptic strength for notifications +- Use Theatre Mode for silent operation + +WARRANTY COVERAGE +================ +Standard Warranty: 12 months +- Manufacturing defects +- Battery issues (if capacity drops below 80%) +- Sensor malfunctions +- Display defects +- Button/crown functionality + +Extended Warranty: Up to 24 months +- All standard coverage +- Battery replacement included +- Water damage coverage +- Accidental damage (screen cracks) + +NOT COVERED: +- Normal wear and tear of bands +- Scratches from normal use +- Damage from unauthorized repairs +- Issues from non-approved accessories +- Battery degradation from normal use + +BAND CARE AND REPLACEMENT +========================= +Sport Band: +- Rinse with warm water after workouts +- Use mild soap if needed +- Air dry completely before wearing +- Replace every 12-18 months for hygiene + +Leather Band: +- Keep dry when possible +- Clean with leather cleaner only +- Avoid water exposure +- Replace when worn or cracked + +Metal Band: +- Clean with soft cloth +- Use toothbrush for tight spaces +- Avoid harsh chemicals +- Professional cleaning recommended annually + +ACCESSIBILITY FEATURES +===================== +- VoiceOver for screen reading +- Zoom for enlarged text and controls +- Bold text and larger font sizes +- Reduce motion for sensitive users +- AssistiveTouch for physical limitations +- Sound recognition for hearing impaired + +PERFORMANCE OPTIMIZATION +======================= +- Restart watch weekly +- Keep watchOS updated +- Manage storage by deleting unused apps +- Clear cache by restarting +- Adjust animation speed in accessibility settings +- Use power reserve mode for extended battery + +CONTACT INFORMATION +================== +Technical Support: 1-800-555-WATCH +Warranty Service: 1-800-555-WARRANTY +Health Questions: 1-800-555-HEALTH +Fitness Support: fitness@company.com +Online Support: support.company.com/watch +Companion App: "Watch Series X" in app stores + +SERIAL NUMBER LOCATION +===================== +Watch app > General > About > Serial Number +Engraved on back of watch case (very small) +Original packaging barcode sticker +Settings > General > About on watch \ No newline at end of file diff --git a/03-integrations/vector-stores/elasticsearch/policies/smartphone_pro_max_128gb_manual.txt b/03-integrations/vector-stores/elasticsearch/policies/smartphone_pro_max_128gb_manual.txt new file mode 100644 index 000000000..2fecab4b6 --- /dev/null +++ b/03-integrations/vector-stores/elasticsearch/policies/smartphone_pro_max_128gb_manual.txt @@ -0,0 +1,147 @@ +SMARTPHONE PRO MAX 128GB - USER MANUAL & TROUBLESHOOTING GUIDE +============================================================= + +PRODUCT SPECIFICATIONS +===================== +Model: SmartPhone Pro Max 128GB +Serial Number Format: ABC######## +SKU: SPM-128-BLK/WHT/GLD +Dimensions: 6.33 x 3.07 x 0.31 inches +Weight: 8.03 oz (228g) +Display: 6.7" Super Retina XDR OLED +Storage: 128GB internal (non-expandable) +RAM: 8GB +Battery: 4352mAh Li-Ion +Camera: Triple 48MP + 12MP + 12MP rear, 12MP front +Operating System: iOS 17 +Water Resistance: IP68 (6m for 30 minutes) + +INITIAL SETUP +============ +1. Insert SIM card using provided SIM tool +2. Press and hold power button to turn on +3. Follow on-screen setup wizard +4. Connect to Wi-Fi network +5. Sign in with Apple ID or create new account +6. Enable Face ID for security +7. Restore from backup or set up as new device + +BASIC OPERATIONS +=============== +Power On/Off: Hold side button for 3 seconds +Force Restart: Press volume up, then volume down, then hold side button +Screenshot: Press side button + volume up simultaneously +Siri Activation: Hold side button or say "Hey Siri" +Control Center: Swipe down from top-right corner +Notification Center: Swipe down from top-left corner + +COMMON TROUBLESHOOTING +===================== + +DEVICE WON'T TURN ON +- Check battery charge level +- Try force restart sequence +- Use original charger and cable +- Check for physical damage to charging port +- If unresponsive, may need replacement + +CHARGING ISSUES +- Clean charging port with dry brush +- Try different Lightning cable +- Test with different power adapter +- Check for bent pins in charging port +- Wireless charging may work if port is damaged + +POOR BATTERY LIFE +- Check battery health in Settings > Battery > Battery Health +- Reduce screen brightness +- Disable background app refresh for unused apps +- Turn off location services for unnecessary apps +- Update to latest iOS version + +CONNECTIVITY PROBLEMS +Wi-Fi Issues: +- Forget and reconnect to network +- Reset network settings +- Check router compatibility +- Move closer to router + +Cellular Issues: +- Check signal strength +- Restart device +- Remove and reinsert SIM card +- Contact carrier for network issues + +CAMERA NOT WORKING +- Force close camera app +- Restart device +- Check for software updates +- Clean camera lenses +- If hardware issue, warranty replacement may be needed + +APPS CRASHING +- Force close problematic apps +- Restart device +- Update apps through App Store +- Free up storage space +- Reset all settings if problem persists + +WATER DAMAGE RESPONSE +===================== +If device gets wet: +1. Power off immediately +2. Do not charge device +3. Wipe dry with soft cloth +4. Remove SIM card and dry separately +5. Place in rice or silica gel packets for 24-48 hours +6. Do not use heat sources like hair dryers +7. Contact support if device doesn't function normally + +WARRANTY COVERAGE +================ +Standard Warranty: 12 months from purchase +- Manufacturing defects +- Hardware failures +- Battery issues (if capacity drops below 80%) +- Power button/volume button failures + +Extended Warranty: Up to 36 months +- All standard coverage +- Battery replacement included +- Expedited replacement service + +Premium Warranty: Up to 36 months +- All extended coverage +- Accidental damage (2 incidents per year) +- Liquid damage coverage +- Screen replacement coverage + +NOT COVERED: +- Intentional damage +- Cosmetic wear and tear +- Software issues after 90 days +- Unauthorized modifications +- Third-party accessory damage + +SAFETY INFORMATION +================= +- Use only Apple-certified accessories +- Keep device dry and clean +- Avoid extreme temperatures (-20°C to 45°C) +- Do not disassemble device +- Replace damaged screens immediately +- Keep away from magnetic fields + +CONTACT INFORMATION +================== +Technical Support: 1-800-555-TECH +Warranty Claims: 1-800-555-WARRANTY +Online Support: support.company.com/smartphone +Live Chat: Available 24/7 on website +Email Support: smartphone-support@company.com + +SERIAL NUMBER LOCATION +===================== +Settings > General > About > Serial Number +Physical location: SIM card tray area (small print) +Original box: Barcode label on side panel \ No newline at end of file diff --git a/03-integrations/vector-stores/elasticsearch/policies/warranty_support_guidelines.txt b/03-integrations/vector-stores/elasticsearch/policies/warranty_support_guidelines.txt new file mode 100644 index 000000000..17bddbd0f --- /dev/null +++ b/03-integrations/vector-stores/elasticsearch/policies/warranty_support_guidelines.txt @@ -0,0 +1,218 @@ +WARRANTY SUPPORT GUIDELINES AND PROCEDURES +========================================== + +1. WARRANTY TYPES AND COVERAGE +============================= + +1.1 Standard Warranty (1 Year) +- Covers manufacturing defects and hardware failures +- Includes parts and labor for repairs +- Free shipping both ways for warranty claims +- Does not cover accidental damage or wear and tear +- Software support included for first 90 days + +1.2 Extended Warranty (2-3 Years) +- All standard warranty coverage extended +- Includes battery replacement for portable devices +- Advanced replacement option available +- 24/7 technical support access +- Covers power adapter and cable replacements + +1.3 Premium Warranty (2-3 Years) +- Comprehensive coverage including accidental damage +- Covers liquid damage and drops (up to 2 incidents per year) +- Expedited service with next-day replacement +- Includes annual maintenance and cleaning +- Covers cosmetic damage repairs + +1.4 Specialty Warranties +- Gaming Warranty: Covers intensive use scenarios +- Professional Warranty: For business and commercial use +- Audio Warranty: Specialized for audio equipment +- Network Warranty: For networking and connectivity devices + +2. WARRANTY VERIFICATION PROCESS +=============================== + +2.1 Information Required +- Product serial number (mandatory) +- Customer name and contact information +- Purchase date and location +- Description of the issue +- Previous troubleshooting attempts + +2.2 Verification Steps +- Look up serial number in warranty database +- Confirm customer identity matches purchase record +- Verify warranty is still active and not voided +- Check for any previous warranty claims +- Document verification in customer record + +2.3 Common Verification Issues +- Serial number not found: Check for data entry errors +- Warranty expired: Explain options for paid service +- Voided warranty: Explain reason and alternatives +- Multiple owners: Verify authorized transfer +- Refurbished products: Different warranty terms apply + +3. TROUBLESHOOTING REQUIREMENTS +============================== + +3.1 Mandatory Troubleshooting +- Always attempt basic troubleshooting before replacement +- Document all troubleshooting steps attempted +- Educate customer on proper product care +- Verify issue is not user error or software related +- Confirm issue is covered under warranty terms + +3.2 Basic Troubleshooting Steps +- Power cycle the device +- Check all connections and cables +- Verify software/firmware is up to date +- Test with different power sources or locations +- Try factory reset if applicable and appropriate + +3.3 Advanced Troubleshooting +- Remote diagnostic tools when available +- Guide customer through component testing +- Coordinate with technical support team +- Use manufacturer diagnostic procedures +- Document all findings and test results + +4. WARRANTY CLAIM APPROVAL +========================= + +4.1 Approval Criteria +- Issue is confirmed hardware-related +- Product is within warranty period +- Customer has attempted required troubleshooting +- Issue is covered under warranty terms +- No evidence of misuse or abuse + +4.2 Approval Levels +- Standard claims under $200: Agent approval +- Claims $200-$500: Supervisor approval required +- Claims over $500: Manager approval required +- Accidental damage claims: Special approval process +- Bulk/commercial claims: Director approval required + +4.3 Documentation Requirements +- Complete issue description and troubleshooting log +- Customer contact information and preferences +- Warranty type and coverage details +- Replacement or repair authorization +- Shipping instructions and tracking information + +5. REPLACEMENT AND REPAIR OPTIONS +================================ + +5.1 Replacement Process +- Offer equivalent or newer model if available +- Explain any differences in replacement product +- Provide prepaid shipping label for return +- Set expectation for processing time (3-5 business days) +- Offer expedited shipping for Premium warranty customers + +5.2 Repair Process +- Explain repair process and timeline (7-10 business days) +- Provide repair ticket number for tracking +- Offer loaner device for Premium warranty customers +- Keep customer updated on repair status +- Test all functionality before returning to customer + +5.3 Refund Process +- Available when replacement/repair not possible +- Refund amount based on depreciation schedule +- Requires manager approval for refunds over $300 +- Process refund within 5-7 business days +- Customer keeps accessories with refund option + +6. SPECIAL CIRCUMSTANCES +======================= + +6.1 Accidental Damage Claims +- Verify Premium warranty coverage +- Limit of 2 incidents per year +- Requires detailed incident description +- May require photos of damage +- Deductible may apply based on warranty terms + +6.2 Liquid Damage +- Immediate troubleshooting: power off device +- Covered only under Premium warranty +- Requires rice/silica gel treatment attempt +- Not covered if corrosion is present +- May require professional cleaning service + +6.3 Repeated Failures +- Track recurring issues in customer record +- After 3 repair attempts, offer replacement +- Consider product upgrade for loyal customers +- Escalate to quality assurance team +- Document patterns for product improvement + +7. CUSTOMER COMMUNICATION +======================== + +7.1 Setting Expectations +- Clearly explain warranty coverage and limitations +- Provide realistic timelines for resolution +- Explain any costs customer may incur +- Offer alternatives when warranty doesn't apply +- Confirm customer understanding before proceeding + +7.2 Status Updates +- Send confirmation email with claim details +- Provide tracking information for shipments +- Update customer on any delays or issues +- Confirm receipt of returned items +- Follow up after resolution to ensure satisfaction + +7.3 Difficult Situations +- Remain calm and professional +- Acknowledge customer frustration +- Explain warranty limitations clearly +- Offer alternatives or paid service options +- Escalate when appropriate + +8. FRAUD PREVENTION +================== + +8.1 Red Flags +- Multiple warranty claims from same customer +- Serial numbers that don't match product description +- Vague or changing problem descriptions +- Pressure for immediate resolution +- Reluctance to provide purchase information + +8.2 Verification Procedures +- Cross-reference purchase records +- Verify serial numbers haven't been reported stolen +- Check for consistent customer information +- Require photos of product and damage when suspicious +- Escalate suspicious claims to fraud prevention team + +9. CONTINUOUS IMPROVEMENT +======================== + +9.1 Feedback Collection +- Document common issues and failure patterns +- Track customer satisfaction with warranty service +- Identify opportunities for process improvement +- Share insights with product development teams +- Monitor warranty claim trends and costs + +9.2 Training and Development +- Regular updates on new warranty policies +- Product-specific warranty training +- Customer service skills development +- Technical troubleshooting certification +- Cross-training on different product lines + +IMPORTANT REMINDERS +================== +- Always prioritize customer satisfaction within policy guidelines +- When in doubt, escalate to supervisor for guidance +- Document everything for quality assurance and legal protection +- Stay updated on product recalls and safety issues +- Remember that good warranty service builds customer loyalty diff --git a/03-integrations/vector-stores/elasticsearch/policies/wireless_headphones_elite_manual.txt b/03-integrations/vector-stores/elasticsearch/policies/wireless_headphones_elite_manual.txt new file mode 100644 index 000000000..75c73d4a5 --- /dev/null +++ b/03-integrations/vector-stores/elasticsearch/policies/wireless_headphones_elite_manual.txt @@ -0,0 +1,190 @@ +WIRELESS HEADPHONES ELITE - USER MANUAL & TROUBLESHOOTING GUIDE +============================================================== + +PRODUCT SPECIFICATIONS +===================== +Model: Wireless Headphones Elite +Serial Number Format: GHI######## +SKU: WH-ELT-BLK/WHT/BLU +Driver Size: 40mm dynamic drivers +Frequency Response: 20Hz - 20kHz +Impedance: 32 ohms +Sensitivity: 110dB SPL/mW +Battery Life: Up to 35 hours (ANC off), 25 hours (ANC on) +Charging Time: 3 hours full charge, 15 min quick charge = 3 hours playback +Bluetooth Version: 5.2 with multipoint connection +Active Noise Cancellation: Hybrid ANC technology +Weight: 250g +Foldable Design: Yes, with carrying case included + +INITIAL SETUP +============ +1. Remove from charging case +2. Hold power button for 3 seconds until LED flashes blue +3. Enable Bluetooth on your device +4. Select "Headphones Elite" from Bluetooth list +5. Wait for "Connected" confirmation +6. Download companion app for full features +7. Register product in app for warranty + +CONTROLS AND INDICATORS +====================== +Power Button: 3-second hold to turn on/off +Play/Pause: Single tap on right earcup +Next Track: Double tap on right earcup +Previous Track: Triple tap on right earcup +Volume Up: Touch and hold top of right earcup +Volume Down: Touch and hold bottom of right earcup +ANC Toggle: Press ANC button on left earcup +Voice Assistant: Long press ANC button +Call Answer/End: Single tap during incoming call + +LED INDICATORS +============= +Blue Solid: Powered on and connected +Blue Flashing: Pairing mode +Red Solid: Low battery (less than 10%) +Red/Blue Alternating: Charging +Green Solid: Fully charged +Orange Solid: ANC enabled + +COMMON TROUBLESHOOTING +===================== + +WON'T TURN ON +- Check battery level (charge for 30 minutes) +- Try different charging cable +- Reset headphones (hold power + ANC for 10 seconds) +- Check charging port for debris +- May need battery replacement if old + +BLUETOOTH CONNECTION ISSUES +- Clear Bluetooth cache on device +- Delete headphones from paired devices list +- Reset headphones and re-pair +- Check if device is in range (30 feet max) +- Ensure only one device is actively connected + +POOR AUDIO QUALITY +- Check Bluetooth codec (prefer aptX or LDAC) +- Ensure full battery charge +- Clean headphone drivers with soft cloth +- Check audio source quality +- Update firmware through companion app +- Try different audio app or device + +NO SOUND IN ONE EAR +- Check stereo balance settings on device +- Clean headphone jack/port +- Test with different audio source +- Check for loose internal connections +- May require warranty replacement + +ACTIVE NOISE CANCELLATION NOT WORKING +- Ensure ANC is enabled (orange LED) +- Check for proper seal around ears +- Clean ANC microphones with dry cloth +- Update firmware to latest version +- Reset ANC calibration in companion app + +MICROPHONE ISSUES +- Check microphone isn't muted +- Position microphone close to mouth +- Reduce background noise +- Test with different calling app +- Clean microphone with soft brush + +BATTERY DRAIN FAST +- Disable ANC when not needed +- Lower volume levels +- Turn off when not in use +- Check for firmware updates +- Calibrate battery (full discharge then full charge) +- Battery may need replacement after 2-3 years + +COMFORT ISSUES +- Adjust headband for proper fit +- Take breaks every hour during extended use +- Clean ear cushions regularly +- Replace ear cushions if worn (available separately) +- Try different wearing positions + +CHARGING PROBLEMS +- Use original charging cable +- Check charging port for debris +- Try different USB power source +- Charging case may have separate power switch +- Replace charging cable if damaged + +APP CONNECTION ISSUES +- Ensure headphones are connected to phone via Bluetooth first +- Close and restart companion app +- Check for app updates +- Clear app cache and data +- Reinstall app if necessary + +FIRMWARE UPDATES +================ +1. Connect headphones to phone via Bluetooth +2. Open companion app +3. Check for firmware updates in settings +4. Keep headphones connected during update +5. Do not power off during update process +6. Update may take 5-10 minutes + +WARRANTY COVERAGE +================ +Standard Warranty: 12 months +- Manufacturing defects +- Battery issues (if capacity drops below 70%) +- Driver failures +- Button/control malfunctions +- Bluetooth connectivity issues + +Premium Warranty: Up to 36 months +- All standard coverage +- Battery replacement included +- Accidental damage protection +- Wear and tear coverage (ear cushions, headband) + +NOT COVERED: +- Physical damage from drops +- Water damage (not water resistant) +- Normal wear of cushions and padding +- Damage from unauthorized repairs +- Issues caused by third-party accessories + +CARE AND MAINTENANCE +=================== +- Store in provided case when not in use +- Clean with slightly damp cloth only +- Avoid extreme temperatures +- Don't bend or twist headband excessively +- Replace ear cushions annually for hygiene +- Keep charging port clean and dry +- Avoid exposure to liquids + +TECHNICAL SPECIFICATIONS +======================== +Supported Codecs: SBC, AAC, aptX, aptX HD, LDAC +Range: Up to 30 feet (10 meters) +Standby Time: Up to 200 hours +Charging Port: USB-C +Quick Charge: 15 minutes = 3 hours playback +Multipoint: Connect to 2 devices simultaneously +Voice Assistants: Siri, Google Assistant, Alexa + +CONTACT INFORMATION +================== +Technical Support: 1-800-555-AUDIO +Warranty Claims: 1-800-555-WARRANTY +Online Support: support.company.com/headphones +Companion App: "Headphones Elite" on App Store/Google Play +Email Support: audio-support@company.com + +SERIAL NUMBER LOCATION +===================== +Inside left ear cup (small white sticker) +Companion app > Settings > Device Information +Original packaging barcode label +Charging case inner compartment diff --git a/03-integrations/vector-stores/elasticsearch/requirements.txt b/03-integrations/vector-stores/elasticsearch/requirements.txt new file mode 100644 index 000000000..7a0bcbdc9 --- /dev/null +++ b/03-integrations/vector-stores/elasticsearch/requirements.txt @@ -0,0 +1,9 @@ +strands-agents +strands-agents-tools +uv +boto3 +bedrock-agentcore +bedrock-agentcore-starter-toolkit +mcp +requests +elasticsearch \ No newline at end of file diff --git a/03-integrations/vector-stores/elasticsearch/utils.py b/03-integrations/vector-stores/elasticsearch/utils.py new file mode 100644 index 000000000..0ab6fa896 --- /dev/null +++ b/03-integrations/vector-stores/elasticsearch/utils.py @@ -0,0 +1,792 @@ +import boto3 +import json +from boto3.session import Session +import botocore +from botocore.exceptions import ClientError +import requests +import time +import uuid + +def setup_cognito_user_pool(): + boto_session = Session() + region = boto_session.region_name + + # Initialize Cognito client + cognito_client = boto3.client('cognito-idp', region_name=region) + + try: + # Create User Pool + user_pool_response = cognito_client.create_user_pool( + PoolName='MCPServerPool', + Policies={ + 'PasswordPolicy': { + 'MinimumLength': 8 + } + } + ) + pool_id = user_pool_response['UserPool']['Id'] + + # Create App Client + app_client_response = cognito_client.create_user_pool_client( + UserPoolId=pool_id, + ClientName='MCPServerPoolClient', + GenerateSecret=False, + ExplicitAuthFlows=[ + 'ALLOW_USER_PASSWORD_AUTH', + 'ALLOW_REFRESH_TOKEN_AUTH' + ] + ) + client_id = app_client_response['UserPoolClient']['ClientId'] + + # Create User + cognito_client.admin_create_user( + UserPoolId=pool_id, + Username='testuser', + TemporaryPassword='Temp123!', + MessageAction='SUPPRESS' + ) + + # Set Permanent Password + cognito_client.admin_set_user_password( + UserPoolId=pool_id, + Username='testuser', + Password='MyPassword123!', + Permanent=True + ) + + # Authenticate User and get Access Token + auth_response = cognito_client.initiate_auth( + ClientId=client_id, + AuthFlow='USER_PASSWORD_AUTH', + AuthParameters={ + 'USERNAME': 'testuser', + 'PASSWORD': 'MyPassword123!' + } + ) + bearer_token = auth_response['AuthenticationResult']['AccessToken'] + + # Output the required values + print(f"Pool id: {pool_id}") + print(f"Discovery URL: https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration") + print(f"Client ID: {client_id}") + print(f"Bearer Token: {bearer_token}") + + # Return values if needed for further processing + return { + 'pool_id': pool_id, + 'client_id': client_id, + 'bearer_token': bearer_token, + 'discovery_url':f"https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration" + } + + except Exception as e: + print(f"Error: {e}") + return None + +def get_or_create_user_pool(cognito, USER_POOL_NAME): + boto_session = Session() + region = boto_session.region_name + response = cognito.list_user_pools(MaxResults=60) + for pool in response["UserPools"]: + if pool["Name"] == USER_POOL_NAME: + user_pool_id = pool["Id"] + response = cognito.describe_user_pool( + UserPoolId=user_pool_id + ) + + # Get the domain from user pool description + user_pool = response.get('UserPool', {}) + domain = user_pool.get('Domain') + + if domain: + region = user_pool_id.split('_')[0] if '_' in user_pool_id else region + domain_url = f"https://{domain}.auth.{region}.amazoncognito.com" + print(f"Found domain for user pool {user_pool_id}: {domain} ({domain_url})") + else: + print(f"No domains found for user pool {user_pool_id}") + return pool["Id"] + print('Creating new user pool') + created = cognito.create_user_pool(PoolName=USER_POOL_NAME) + user_pool_id = created["UserPool"]["Id"] + user_pool_id_without_underscore_lc = user_pool_id.replace("_", "").lower() + cognito.create_user_pool_domain( + Domain=user_pool_id_without_underscore_lc, + UserPoolId=user_pool_id + ) + print("Domain created as well") + return created["UserPool"]["Id"] + +def get_or_create_resource_server(cognito, user_pool_id, RESOURCE_SERVER_ID, RESOURCE_SERVER_NAME, SCOPES): + try: + cognito.describe_resource_server( + UserPoolId=user_pool_id, + Identifier=RESOURCE_SERVER_ID + ) + return RESOURCE_SERVER_ID + except cognito.exceptions.ResourceNotFoundException: + print('creating new resource server') + cognito.create_resource_server( + UserPoolId=user_pool_id, + Identifier=RESOURCE_SERVER_ID, + Name=RESOURCE_SERVER_NAME, + Scopes=SCOPES + ) + return RESOURCE_SERVER_ID + +def get_or_create_m2m_client(cognito, user_pool_id, CLIENT_NAME, RESOURCE_SERVER_ID, SCOPES=None): + response = cognito.list_user_pool_clients(UserPoolId=user_pool_id, MaxResults=60) + for client in response["UserPoolClients"]: + if client["ClientName"] == CLIENT_NAME: + describe = cognito.describe_user_pool_client(UserPoolId=user_pool_id, ClientId=client["ClientId"]) + return client["ClientId"], describe["UserPoolClient"]["ClientSecret"] + print('creating new m2m client') + + # Default scopes if not provided (for backward compatibility) + if SCOPES is None: + SCOPES = [f"{RESOURCE_SERVER_ID}/gateway:read", f"{RESOURCE_SERVER_ID}/gateway:write"] + + created = cognito.create_user_pool_client( + UserPoolId=user_pool_id, + ClientName=CLIENT_NAME, + GenerateSecret=True, + AllowedOAuthFlows=["client_credentials"], + AllowedOAuthScopes=SCOPES, + AllowedOAuthFlowsUserPoolClient=True, + SupportedIdentityProviders=["COGNITO"], + ExplicitAuthFlows=["ALLOW_REFRESH_TOKEN_AUTH"] + ) + return created["UserPoolClient"]["ClientId"], created["UserPoolClient"]["ClientSecret"] + +def get_token(user_pool_id: str, client_id: str, client_secret: str, scope_string: str, REGION: str) -> dict: + try: + user_pool_id_without_underscore = user_pool_id.replace("_", "") + url = f"https://{user_pool_id_without_underscore}.auth.{REGION}.amazoncognito.com/oauth2/token" + headers = {"Content-Type": "application/x-www-form-urlencoded"} + data = { + "grant_type": "client_credentials", + "client_id": client_id, + "client_secret": client_secret, + "scope": scope_string, + + } + print(client_id) + response = requests.post(url, headers=headers, data=data, timeout=30) + response.raise_for_status() + return response.json() + + except requests.exceptions.RequestException as err: + return {"error": str(err)} + +def create_agentcore_role(agent_name): + iam_client = boto3.client('iam') + agentcore_role_name = f'agentcore-{agent_name}-role' + boto_session = Session() + region = boto_session.region_name + account_id = boto3.client("sts").get_caller_identity()["Account"] + role_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "BedrockPermissions", + "Effect": "Allow", + "Action": [ + "bedrock:InvokeModel", + "bedrock:InvokeModelWithResponseStream" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "logs:DescribeLogStreams", + "logs:CreateLogGroup" + ], + "Resource": [ + f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*" + ] + }, + { + "Effect": "Allow", + "Action": [ + "logs:DescribeLogGroups" + ], + "Resource": [ + f"arn:aws:logs:{region}:{account_id}:log-group:*" + ] + }, + { + "Effect": "Allow", + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Resource": [ + f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*" + ] + }, + { + "Effect": "Allow", + "Action": [ + "xray:PutTraceSegments", + "xray:PutTelemetryRecords", + "xray:GetSamplingRules", + "xray:GetSamplingTargets" + ], + "Resource": [ "*" ] + }, + { + "Effect": "Allow", + "Resource": "*", + "Action": "cloudwatch:PutMetricData", + "Condition": { + "StringEquals": { + "cloudwatch:namespace": "bedrock-agentcore" + } + } + }, + { + "Effect": "Allow", + "Resource": "*", + "Action": "s3:GetObject", + }, + { + "Effect": "Allow", + "Resource": "*", + "Action": "lambda:InvokeFunction" + }, + { + "Effect": "Allow", + "Action": [ + "bedrock-agentcore:*", + "iam:PassRole" + ], + "Resource": "*" + }, + { + "Sid": "GetAgentAccessToken", + "Effect": "Allow", + "Action": [ + "bedrock-agentcore:GetWorkloadAccessToken", + "bedrock-agentcore:GetWorkloadAccessTokenForJWT", + "bedrock-agentcore:GetWorkloadAccessTokenForUserId" + ], + "Resource": [ + f"arn:aws:bedrock-agentcore:{region}:{account_id}:workload-identity-directory/default", + f"arn:aws:bedrock-agentcore:{region}:{account_id}:workload-identity-directory/default/workload-identity/{agent_name}-*" + ] + } + ] + } + assume_role_policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AssumeRolePolicy", + "Effect": "Allow", + "Principal": { + "Service": "bedrock-agentcore.amazonaws.com" + }, + "Action": "sts:AssumeRole", + "Condition": { + "StringEquals": { + "aws:SourceAccount": f"{account_id}" + }, + "ArnLike": { + "aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*" + } + } + } + ] + } + + assume_role_policy_document_json = json.dumps( + assume_role_policy_document + ) + role_policy_document = json.dumps(role_policy) + # Create IAM Role for the Lambda function + try: + agentcore_iam_role = iam_client.create_role( + RoleName=agentcore_role_name, + AssumeRolePolicyDocument=assume_role_policy_document_json + ) + + # Pause to make sure role is created + time.sleep(10) + except iam_client.exceptions.EntityAlreadyExistsException: + print("Role already exists -- deleting and creating it again") + policies = iam_client.list_role_policies( + RoleName=agentcore_role_name, + MaxItems=100 + ) + print("policies:", policies) + for policy_name in policies['PolicyNames']: + iam_client.delete_role_policy( + RoleName=agentcore_role_name, + PolicyName=policy_name + ) + print(f"deleting {agentcore_role_name}") + iam_client.delete_role( + RoleName=agentcore_role_name + ) + print(f"recreating {agentcore_role_name}") + agentcore_iam_role = iam_client.create_role( + RoleName=agentcore_role_name, + AssumeRolePolicyDocument=assume_role_policy_document_json + ) + + # Attach the AWSLambdaBasicExecutionRole policy + print(f"attaching role policy {agentcore_role_name}") + try: + iam_client.put_role_policy( + PolicyDocument=role_policy_document, + PolicyName="AgentCorePolicy", + RoleName=agentcore_role_name + ) + except Exception as e: + print(e) + + return agentcore_iam_role + +def create_agentcore_gateway_role(gateway_name): + iam_client = boto3.client('iam') + agentcore_gateway_role_name = f'agentcore-{gateway_name}-role' + boto_session = Session() + region = boto_session.region_name + account_id = boto3.client("sts").get_caller_identity()["Account"] + role_policy = { + "Version": "2012-10-17", + "Statement": [{ + "Sid": "VisualEditor0", + "Effect": "Allow", + "Action": [ + "bedrock-agentcore:*", + "bedrock:*", + "agent-credential-provider:*", + "iam:PassRole", + "secretsmanager:GetSecretValue", + "lambda:InvokeFunction" + ], + "Resource": "*" + } + ] + } + + assume_role_policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AssumeRolePolicy", + "Effect": "Allow", + "Principal": { + "Service": "bedrock-agentcore.amazonaws.com" + }, + "Action": "sts:AssumeRole", + "Condition": { + "StringEquals": { + "aws:SourceAccount": f"{account_id}" + }, + "ArnLike": { + "aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*" + } + } + } + ] + } + + assume_role_policy_document_json = json.dumps( + assume_role_policy_document + ) + + role_policy_document = json.dumps(role_policy) + # Create IAM Role for the Lambda function + try: + agentcore_iam_role = iam_client.create_role( + RoleName=agentcore_gateway_role_name, + AssumeRolePolicyDocument=assume_role_policy_document_json + ) + + # Pause to make sure role is created + time.sleep(10) + except iam_client.exceptions.EntityAlreadyExistsException: + print("Role already exists -- deleting and creating it again") + policies = iam_client.list_role_policies( + RoleName=agentcore_gateway_role_name, + MaxItems=100 + ) + print("policies:", policies) + for policy_name in policies['PolicyNames']: + iam_client.delete_role_policy( + RoleName=agentcore_gateway_role_name, + PolicyName=policy_name + ) + print(f"deleting {agentcore_gateway_role_name}") + iam_client.delete_role( + RoleName=agentcore_gateway_role_name + ) + print(f"recreating {agentcore_gateway_role_name}") + agentcore_iam_role = iam_client.create_role( + RoleName=agentcore_gateway_role_name, + AssumeRolePolicyDocument=assume_role_policy_document_json + ) + + # Attach the AWSLambdaBasicExecutionRole policy + print(f"attaching role policy {agentcore_gateway_role_name}") + try: + iam_client.put_role_policy( + PolicyDocument=role_policy_document, + PolicyName="AgentCorePolicy", + RoleName=agentcore_gateway_role_name + ) + except Exception as e: + print(e) + + return agentcore_iam_role + + +def create_agentcore_gateway_role_s3_smithy(gateway_name): + iam_client = boto3.client('iam') + agentcore_gateway_role_name = f'agentcore-{gateway_name}-role' + boto_session = Session() + region = boto_session.region_name + account_id = boto3.client("sts").get_caller_identity()["Account"] + role_policy = { + "Version": "2012-10-17", + "Statement": [{ + "Sid": "VisualEditor0", + "Effect": "Allow", + "Action": [ + "bedrock-agentcore:*", + "bedrock:*", + "agent-credential-provider:*", + "iam:PassRole", + "secretsmanager:GetSecretValue", + "lambda:InvokeFunction", + "s3:*", + ], + "Resource": "*" + } + ] + } + + assume_role_policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AssumeRolePolicy", + "Effect": "Allow", + "Principal": { + "Service": "bedrock-agentcore.amazonaws.com" + }, + "Action": "sts:AssumeRole", + "Condition": { + "StringEquals": { + "aws:SourceAccount": f"{account_id}" + }, + "ArnLike": { + "aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*" + } + } + } + ] + } + + assume_role_policy_document_json = json.dumps( + assume_role_policy_document + ) + + role_policy_document = json.dumps(role_policy) + # Create IAM Role for the Lambda function + try: + agentcore_iam_role = iam_client.create_role( + RoleName=agentcore_gateway_role_name, + AssumeRolePolicyDocument=assume_role_policy_document_json + ) + + # Pause to make sure role is created + time.sleep(10) + except iam_client.exceptions.EntityAlreadyExistsException: + print("Role already exists -- deleting and creating it again") + policies = iam_client.list_role_policies( + RoleName=agentcore_gateway_role_name, + MaxItems=100 + ) + print("policies:", policies) + for policy_name in policies['PolicyNames']: + iam_client.delete_role_policy( + RoleName=agentcore_gateway_role_name, + PolicyName=policy_name + ) + print(f"deleting {agentcore_gateway_role_name}") + iam_client.delete_role( + RoleName=agentcore_gateway_role_name + ) + print(f"recreating {agentcore_gateway_role_name}") + agentcore_iam_role = iam_client.create_role( + RoleName=agentcore_gateway_role_name, + AssumeRolePolicyDocument=assume_role_policy_document_json + ) + + # Attach the AWSLambdaBasicExecutionRole policy + print(f"attaching role policy {agentcore_gateway_role_name}") + try: + iam_client.put_role_policy( + PolicyDocument=role_policy_document, + PolicyName="AgentCorePolicy", + RoleName=agentcore_gateway_role_name + ) + except Exception as e: + print(e) + + return agentcore_iam_role + +def create_gateway_lambda(lambda_function_code_path, environment_variables) -> dict[str, int]: + boto_session = Session() + region = boto_session.region_name + + return_resp = {"lambda_function_arn": "Pending", "exit_code": 1} + + # Initialize Cognito client + lambda_client = boto3.client('lambda', region_name=region) + iam_client = boto3.client('iam', region_name=region) + + role_name = f'gateway_lambda_iamrole_{str(uuid.uuid4())[:8]}' + role_arn = '' + lambda_function_name = f'gateway_lambda_{str(uuid.uuid4())[:8]}' + + print("Reading code from zip file") + with open(lambda_function_code_path, 'rb') as f: + lambda_function_code = f.read() + + try: + print("Creating IAM role for lambda function") + + response = iam_client.create_role( + RoleName=role_name, + AssumeRolePolicyDocument=json.dumps({ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] + }), + Description="IAM role to be assumed by lambda function" + ) + + role_arn = response['Role']['Arn'] + + print("Attaching policy to the IAM role") + + response = iam_client.attach_role_policy( + RoleName=role_name, + PolicyArn='arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' + ) + + print(f"Role '{role_name}' created successfully: {role_arn}") + time.sleep(100) + except botocore.exceptions.ClientError as error: + if error.response['Error']['Code'] == "EntityAlreadyExists": + response = iam_client.get_role(RoleName=role_name) + role_arn = response['Role']['Arn'] + print(f"IAM role {role_name} already exists. Using the same ARN {role_arn}") + else: + error_message = error.response['Error']['Code'] + "-" + error.response['Error']['Message'] + print(f"Error creating role: {error_message}") + return_resp['lambda_function_arn'] = error_message + + if role_arn != "": + print("Creating lambda function") + # Create lambda function + try: + lambda_response = lambda_client.create_function( + FunctionName=lambda_function_name, + Role=role_arn, + Runtime='python3.12', + Handler='lambda_function_code.lambda_handler', + Code = {'ZipFile': lambda_function_code}, + Description='Lambda function example for Bedrock AgentCore Gateway', + PackageType='Zip', + Environment={ + 'Variables': { + 'ELASTIC_ENDPOINT_URL_ENV': environment_variables["ELASTIC_ENDPOINT_URL_ENV"], + 'ELASTIC_API_KEY_ENV': environment_variables["ELASTIC_API_KEY_ENV"], + 'ELASTIC_INDEX_NAME_ENV': environment_variables["ELASTIC_INDEX_NAME_ENV"]} + }, + ) + + return_resp['lambda_function_arn'] = lambda_response['FunctionArn'] + return_resp['exit_code'] = 0 + except botocore.exceptions.ClientError as error: + if error.response['Error']['Code'] == "ResourceConflictException": + response = lambda_client.get_function(FunctionName=lambda_function_name) + lambda_arn = response['Configuration']['FunctionArn'] + print(f"AWS Lambda function {lambda_function_name} already exists. Using the same ARN {lambda_arn}") + return_resp['lambda_function_arn'] = lambda_arn + else: + error_message = error.response['Error']['Code'] + "-" + error.response['Error']['Message'] + print(f"Error creating lambda function: {error_message}") + return_resp['lambda_function_arn'] = error_message + + return return_resp + +def delete_gateway(gateway_client,gatewayId): + print("Deleting all targets for gateway", gatewayId) + list_response = gateway_client.list_gateway_targets( + gatewayIdentifier = gatewayId, + maxResults=100 + ) + for item in list_response['items']: + targetId = item["targetId"] + print("Deleting target ", targetId) + gateway_client.delete_gateway_target( + gatewayIdentifier = gatewayId, + targetId = targetId + ) + time.sleep(5) + print("Deleting gateway ", gatewayId) + gateway_client.delete_gateway(gatewayIdentifier = gatewayId) + +def delete_all_gateways(gateway_client): + try: + list_response = gateway_client.list_gateways( + maxResults=100 + ) + for item in list_response['items']: + gatewayId= item["gatewayId"] + delete_gateway(gatewayId) + except Exception as e: + print(e) + +def get_current_role_arn(): + sts_client = boto3.client("sts") + role_arn = sts_client.get_caller_identity()["Arn"] + return {role_arn} + +def create_gateway_invoke_tool_role(role_name, gateway_id, current_arn): + # Normalize current_arn + if isinstance(current_arn, (list, set, tuple)): + current_arn = list(current_arn)[0] + current_arn = str(current_arn) + + # AWS clients + boto_session = Session() + region = boto_session.region_name + iam_client = boto3.client('iam', region_name=region) + sts_client = boto3.client("sts") + account_id = sts_client.get_caller_identity()["Account"] + + # --- Trust policy (AssumeRolePolicyDocument) --- + assume_role_policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AssumeRoleByAgentCore", + "Effect": "Allow", + "Principal": {"Service": "bedrock-agentcore.amazonaws.com"}, + "Action": ["sts:AssumeRole"] + }, + { + "Sid": "AllowCallerToAssume", + "Effect": "Allow", + "Principal": {"AWS": [current_arn]}, + "Action": ["sts:AssumeRole"] + } + ] + } + assume_role_policy_json = json.dumps(assume_role_policy_document) + + # --- Inline role policy (Bedrock gateway invoke) --- + role_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["bedrock-agentcore:InvokeGateway"], + "Resource": f"arn:aws:bedrock-agentcore:{region}:{account_id}:gateway/{gateway_id}" + } + ] + } + role_policy_json = json.dumps(role_policy) + + # --- Create or update IAM role --- + try: + agentcoregw_iam_role = iam_client.create_role( + RoleName=role_name, + AssumeRolePolicyDocument=assume_role_policy_json + ) + print(f"Created new role: {role_name}") + time.sleep(3) + except iam_client.exceptions.EntityAlreadyExistsException: + print(f"Role '{role_name}' already exists — updating trust and inline policy.") + iam_client.update_assume_role_policy( + RoleName=role_name, + PolicyDocument=assume_role_policy_json + ) + for policy_name in iam_client.list_role_policies(RoleName=role_name).get('PolicyNames', []): + iam_client.delete_role_policy(RoleName=role_name, PolicyName=policy_name) + agentcoregw_iam_role = iam_client.get_role(RoleName=role_name) + + # Attach inline role policy (gateway invoke) + iam_client.put_role_policy( + RoleName=role_name, + PolicyName="AgentCorePolicy", + PolicyDocument=role_policy_json + ) + + role_arn = agentcoregw_iam_role['Role']['Arn'] + + # --- Ensure current_arn can assume role (with retry) --- + arn_parts = current_arn.split(":") + resource_type, resource_name = arn_parts[5].split("/", 1) + + assume_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "sts:AssumeRole", + "Resource": role_arn + } + ] + } + + # Attach assume-role policy if user/role + try: + if resource_type == "user": + iam_client.put_user_policy( + UserName=resource_name, + PolicyName=f"AllowAssume_{role_name}", + PolicyDocument=json.dumps(assume_policy) + ) + elif resource_type == "role": + iam_client.put_role_policy( + RoleName=resource_name, + PolicyName=f"AllowAssume_{role_name}", + PolicyDocument=json.dumps(assume_policy) + ) + except ClientError as e: + print(f"Unable to attach assume-role policy: {e}") + print("Make sure the caller has iam:PutUserPolicy or iam:PutRolePolicy permission.") + + # Retry loop for eventual consistency + max_retries=5 + for i in range(max_retries): + try: + sts_client.assume_role(RoleArn=role_arn, RoleSessionName="testSession") + print(f"Caller {current_arn} can now assume role {role_name}") + break + except ClientError as e: + if "AccessDenied" in str(e): + print(f"Attempt {i+1}/{max_retries}: AccessDenied, retrying in 3s...") + time.sleep(3) + else: + raise + else: + raise RuntimeError(f"Failed to assume role {role_name} after {max_retries} retries") + + print(f" Role '{role_name}' is ready and {current_arn} can invoke the Bedrock Agent Gateway.") + return agentcoregw_iam_role diff --git a/03-integrations/vector-stores/elasticsearch/utils_execution.py b/03-integrations/vector-stores/elasticsearch/utils_execution.py new file mode 100644 index 000000000..be2f4da74 --- /dev/null +++ b/03-integrations/vector-stores/elasticsearch/utils_execution.py @@ -0,0 +1,778 @@ +import base64 +import hashlib +import hmac +import json +import os +from typing import Any, Dict + +import boto3 +import yaml +from boto3.session import Session + +sts_client = boto3.client("sts") + +# Get AWS account details +REGION = boto3.session.Session().region_name + +username = "testuser" +cognito_config_name = "customer_support_agent" + +role_name = f"CustomerSupportAssistantBedrockAgentCoreRole-{REGION}" +policy_name = f"CustomerSupportAssistantBedrockAgentCorePolicy-{REGION}" + + +def get_ssm_parameter(name: str, with_decryption: bool = True) -> str: + ssm = boto3.client("ssm") + + response = ssm.get_parameter(Name=name, WithDecryption=with_decryption) + + return response["Parameter"]["Value"] + + +def put_ssm_parameter( + name: str, value: str, parameter_type: str = "String", with_encryption: bool = False +) -> None: + ssm = boto3.client("ssm") + + put_params = { + "Name": name, + "Value": value, + "Type": parameter_type, + "Overwrite": True, + } + + if with_encryption: + put_params["Type"] = "SecureString" + + ssm.put_parameter(**put_params) + + +def delete_ssm_parameter(name: str) -> None: + ssm = boto3.client("ssm") + try: + ssm.delete_parameter(Name=name) + except ssm.exceptions.ParameterNotFound: + pass + + +def load_api_spec(file_path: str) -> list: + with open(file_path, "r") as f: + data = json.load(f) + if not isinstance(data, list): + raise ValueError("Expected a list in the JSON file") + return data + + +def get_aws_region() -> str: + session = Session() + return session.region_name + + +def get_aws_account_id() -> str: + sts = boto3.client("sts") + return sts.get_caller_identity()["Account"] + + +def get_cognito_client_secret() -> str: + client = boto3.client("cognito-idp") + response = client.describe_user_pool_client( + UserPoolId=get_ssm_parameter("/app/customersupport/agentcore/pool_id"), + ClientId=get_ssm_parameter("/app/customersupport/agentcore/client_id"), + ) + return response["UserPoolClient"]["ClientSecret"] + + +def read_config(file_path: str) -> Dict[str, Any]: + """ + Read configuration from a file path. Supports JSON, YAML, and YML formats. + + Args: + file_path (str): Path to the configuration file + + Returns: + Dict[str, Any]: Configuration data as a dictionary + + Raises: + FileNotFoundError: If the file doesn't exist + ValueError: If the file format is not supported or invalid + yaml.YAMLError: If YAML parsing fails + json.JSONDecodeError: If JSON parsing fails + """ + if not os.path.exists(file_path): + raise FileNotFoundError(f"Configuration file not found: {file_path}") + + # Get file extension to determine format + _, ext = os.path.splitext(file_path.lower()) + + try: + with open(file_path, "r", encoding="utf-8") as file: + if ext == ".json": + return json.load(file) + elif ext in [".yaml", ".yml"]: + return yaml.safe_load(file) + else: + # Try to auto-detect format by attempting JSON first, then YAML + content = file.read() + file.seek(0) + + # Try JSON first + try: + return json.loads(content) + except json.JSONDecodeError: + # Try YAML + try: + return yaml.safe_load(content) + except yaml.YAMLError: + raise ValueError( + f"Unsupported configuration file format: {ext}. " + f"Supported formats: .json, .yaml, .yml" + ) + + except json.JSONDecodeError as e: + raise ValueError(f"Invalid JSON in configuration file {file_path}: {e}") + except yaml.YAMLError as e: + raise ValueError(f"Invalid YAML in configuration file {file_path}: {e}") + except Exception as e: + raise ValueError(f"Error reading configuration file {file_path}: {e}") + + +def save_customer_support_secret(secret_value): + """Save a secret in AWS Secrets Manager.""" + boto_session = Session() + region = boto_session.region_name + secrets_client = boto3.client("secretsmanager", region_name=region) + + try: + secrets_client.create_secret( + Name=cognito_config_name, + SecretString=secret_value, + Description="Contains the Cognito Configuration for the Customer Support Agent", + ) + print("✅ Created secret") + except secrets_client.exceptions.ResourceExistsException: + secrets_client.update_secret(SecretId=cognito_config_name, SecretString=secret_value) + print("✅ Updated existing secret") + except Exception as e: + print(f"❌ Error saving secret: {str(e)}") + return False + return True + + +def get_customer_support_secret(): + """Get a secret value from AWS Secrets Manager.""" + boto_session = Session() + region = boto_session.region_name + secrets_client = boto3.client("secretsmanager", region_name=region) + try: + response = secrets_client.get_secret_value(SecretId=cognito_config_name) + return response["SecretString"] + except Exception as e: + print(f"❌ Error getting secret: {str(e)}") + return None + + +def delete_customer_support_secret(): + """Delete a secret from AWS Secrets Manager.""" + boto_session = Session() + region = boto_session.region_name + secrets_client = boto3.client("secretsmanager", region_name=region) + try: + secrets_client.delete_secret( + SecretId=cognito_config_name, ForceDeleteWithoutRecovery=True + ) + print("✅ Deleted secret") + return True + except Exception as e: + print(f"❌ Error deleting secret: {str(e)}") + return False + + +def get_or_create_cognito_pool(refresh_token=False): + boto_session = Session() + region = boto_session.region_name + # Initialize Cognito client + cognito_client = boto3.client("cognito-idp", region_name=region) + try: + # check for existing cognito pool + cognito_config_str = get_customer_support_secret() + cognito_config = json.loads(cognito_config_str) + if refresh_token: + cognito_config["bearer_token"] = reauthenticate_user( + cognito_config["client_id"], cognito_config["client_secret"] + ) + return cognito_config + except Exception: + print("No existing cognito config found. Creating a new one..") + + try: + # Create User Pool + user_pool_response = cognito_client.create_user_pool( + PoolName="MCPServerPool", Policies={"PasswordPolicy": {"MinimumLength": 8}} + ) + pool_id = user_pool_response["UserPool"]["Id"] + # Create App Client + app_client_response = cognito_client.create_user_pool_client( + UserPoolId=pool_id, + ClientName="MCPServerPoolClient", + GenerateSecret=True, + ExplicitAuthFlows=[ + "ALLOW_USER_PASSWORD_AUTH", + "ALLOW_REFRESH_TOKEN_AUTH", + "ALLOW_USER_SRP_AUTH", + ], + ) + print(app_client_response["UserPoolClient"]) + client_id = app_client_response["UserPoolClient"]["ClientId"] + client_secret = app_client_response["UserPoolClient"]["ClientSecret"] + + # Create User + cognito_client.admin_create_user( + UserPoolId=pool_id, + Username=username, + TemporaryPassword="Temp123!", + MessageAction="SUPPRESS", + ) + + # Set Permanent Password + cognito_client.admin_set_user_password( + UserPoolId=pool_id, + Username=username, + Password="MyPassword123!", + Permanent=True, + ) + + message = bytes(username + client_id, "utf-8") + key = bytes(client_secret, "utf-8") + secret_hash = base64.b64encode( + hmac.new(key, message, digestmod=hashlib.sha256).digest() + ).decode() + + # Authenticate User and get Access Token + auth_response = cognito_client.initiate_auth( + ClientId=client_id, + AuthFlow="USER_PASSWORD_AUTH", + AuthParameters={ + "USERNAME": username, + "PASSWORD": "MyPassword123!", + "SECRET_HASH": secret_hash, + }, + ) + bearer_token = auth_response["AuthenticationResult"]["AccessToken"] + discovery_url = f"https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration" + # Output the required values + print(f"Pool id: {pool_id}") + print(f"Discovery URL: {discovery_url}") + print(f"Client ID: {client_id}") + print(f"Bearer Token: {bearer_token}") + # Return values if needed for further processing + cognito_config = { + "pool_id": pool_id, + "client_id": client_id, + "client_secret": client_secret, + "secret_hash": secret_hash, + "bearer_token": bearer_token, + "discovery_url": discovery_url, + } + put_ssm_parameter("/app/customersupport/agentcore/client_id", client_id) + put_ssm_parameter("/app/customersupport/agentcore/pool_id", pool_id) + put_ssm_parameter( + "/app/customersupport/agentcore/cognito_discovery_url", discovery_url + ) + put_ssm_parameter("/app/customersupport/agentcore/client_secret", client_secret) + + save_customer_support_secret(json.dumps(cognito_config)) + + return cognito_config + except Exception as e: + print(f"Error: {e}") + return None + + +def cleanup_cognito_resources(pool_id): + """ + Delete Cognito resources including users, app clients, and user pool + """ + try: + # Initialize Cognito client using the same session configuration + boto_session = Session() + region = boto_session.region_name + cognito_client = boto3.client("cognito-idp", region_name=region) + + if pool_id: + try: + # List and delete all app clients + clients_response = cognito_client.list_user_pool_clients( + UserPoolId=pool_id, MaxResults=60 + ) + + for client in clients_response["UserPoolClients"]: + print(f"Deleting app client: {client['ClientName']}") + cognito_client.delete_user_pool_client( + UserPoolId=pool_id, ClientId=client["ClientId"] + ) + + # List and delete all users + users_response = cognito_client.list_users( + UserPoolId=pool_id, AttributesToGet=["email"] + ) + + for user in users_response.get("Users", []): + print(f"Deleting user: {user['Username']}") + cognito_client.admin_delete_user( + UserPoolId=pool_id, Username=user["Username"] + ) + + # Delete the user pool + print(f"Deleting user pool: {pool_id}") + cognito_client.delete_user_pool(UserPoolId=pool_id) + + print("Successfully cleaned up all Cognito resources") + return True + + except cognito_client.exceptions.ResourceNotFoundException: + print( + f"User pool {pool_id} not found. It may have already been deleted." + ) + return True + + except Exception as e: + print(f"Error during cleanup: {str(e)}") + return False + else: + print("No matching user pool found") + return True + + except Exception as e: + print(f"Error initializing cleanup: {str(e)}") + return False + + +def reauthenticate_user(client_id, client_secret): + boto_session = Session() + region = boto_session.region_name + # Initialize Cognito client + cognito_client = boto3.client("cognito-idp", region_name=region) + # Authenticate User and get Access Token + + message = bytes(username + client_id, "utf-8") + key = bytes(client_secret, "utf-8") + secret_hash = base64.b64encode( + hmac.new(key, message, digestmod=hashlib.sha256).digest() + ).decode() + + auth_response = cognito_client.initiate_auth( + ClientId=client_id, + AuthFlow="USER_PASSWORD_AUTH", + AuthParameters={ + "USERNAME": username, + "PASSWORD": "MyPassword123!", + "SECRET_HASH": secret_hash, + }, + ) + bearer_token = auth_response["AuthenticationResult"]["AccessToken"] + return bearer_token + + +def create_agentcore_runtime_execution_role(): + iam = boto3.client("iam") + boto_session = Session() + region = boto_session.region_name + account_id = get_aws_account_id() + + # Trust relationship policy + trust_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AssumeRolePolicy", + "Effect": "Allow", + "Principal": {"Service": "bedrock-agentcore.amazonaws.com"}, + "Action": "sts:AssumeRole", + "Condition": { + "StringEquals": {"aws:SourceAccount": account_id}, + "ArnLike": { + "aws:SourceArn": f"arn:aws:bedrock-agentcore:{region}:{account_id}:*" + }, + }, + } + ], + } + + # IAM policy document + policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "ECRImageAccess", + "Effect": "Allow", + "Action": ["ecr:BatchGetImage", "ecr:GetDownloadUrlForLayer"], + "Resource": [f"arn:aws:ecr:{region}:{account_id}:repository/*"], + }, + { + "Effect": "Allow", + "Action": ["logs:DescribeLogStreams", "logs:CreateLogGroup"], + "Resource": [ + f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*" + ], + }, + { + "Effect": "Allow", + "Action": ["logs:DescribeLogGroups"], + "Resource": [f"arn:aws:logs:{region}:{account_id}:log-group:*"], + }, + { + "Effect": "Allow", + "Action": ["logs:CreateLogStream", "logs:PutLogEvents"], + "Resource": [ + f"arn:aws:logs:{region}:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*" + ], + }, + { + "Sid": "ECRTokenAccess", + "Effect": "Allow", + "Action": ["ecr:GetAuthorizationToken"], + "Resource": "*", + }, + { + "Effect": "Allow", + "Action": [ + "xray:PutTraceSegments", + "xray:PutTelemetryRecords", + "xray:GetSamplingRules", + "xray:GetSamplingTargets", + ], + "Resource": ["*"], + }, + { + "Effect": "Allow", + "Resource": "*", + "Action": "cloudwatch:PutMetricData", + "Condition": { + "StringEquals": {"cloudwatch:namespace": "bedrock-agentcore"} + }, + }, + { + "Sid": "GetAgentAccessToken", + "Effect": "Allow", + "Action": [ + "bedrock-agentcore:GetWorkloadAccessToken", + "bedrock-agentcore:GetWorkloadAccessTokenForJWT", + "bedrock-agentcore:GetWorkloadAccessTokenForUserId", + ], + "Resource": [ + f"arn:aws:bedrock-agentcore:{region}:{account_id}:workload-identity-directory/default", + f"arn:aws:bedrock-agentcore:{region}:{account_id}:workload-identity-directory/default/workload-identity/customer_support_agent-*", + ], + }, + { + "Sid": "BedrockModelInvocation", + "Effect": "Allow", + "Action": [ + "bedrock:InvokeModel", + "bedrock:InvokeModelWithResponseStream", + "bedrock:ApplyGuardrail", + "bedrock:Retrieve", + ], + "Resource": [ + "arn:aws:bedrock:*::foundation-model/*", + f"arn:aws:bedrock:{region}:{account_id}:*", + ], + }, + { + "Sid": "AllowAgentToUseMemory", + "Effect": "Allow", + "Action": [ + "bedrock-agentcore:CreateEvent", + "bedrock-agentcore:GetMemoryRecord", + "bedrock-agentcore:GetMemory", + "bedrock-agentcore:RetrieveMemoryRecords", + "bedrock-agentcore:ListMemoryRecords", + ], + "Resource": [f"arn:aws:bedrock-agentcore:{region}:{account_id}:*"], + }, + { + "Sid": "GetMemoryId", + "Effect": "Allow", + "Action": ["ssm:GetParameter"], + "Resource": [f"arn:aws:ssm:{region}:{account_id}:parameter/*"], + }, + { + "Sid": "GatewayAccess", + "Effect": "Allow", + "Action": [ + "bedrock-agentcore:GetGateway", + "bedrock-agentcore:InvokeGateway", + ], + "Resource": [ + f"arn:aws:bedrock-agentcore:{region}:{account_id}:gateway/*" + ], + }, + ], + } + + try: + # Check if role already exists + try: + existing_role = iam.get_role(RoleName=role_name) + print(f"ℹ️ Role {role_name} already exists") + print(f"Role ARN: {existing_role['Role']['Arn']}") + return existing_role["Role"]["Arn"] + except iam.exceptions.NoSuchEntityException: + pass + + # Create IAM role + role_response = iam.create_role( + RoleName=role_name, + AssumeRolePolicyDocument=json.dumps(trust_policy), + Description="IAM role for Amazon Bedrock AgentCore with required permissions", + ) + + print(f"✅ Created IAM role: {role_name}") + print(f"Role ARN: {role_response['Role']['Arn']}") + + # Check if policy already exists + policy_arn = f"arn:aws:iam::{account_id}:policy/{policy_name}" + + try: + iam.get_policy(PolicyArn=policy_arn) + print(f"ℹ️ Policy {policy_name} already exists") + except iam.exceptions.NoSuchEntityException: + # Create policy + policy_response = iam.create_policy( + PolicyName=policy_name, + PolicyDocument=json.dumps(policy_document), + Description="Policy for Amazon Bedrock AgentCore permissions", + ) + print(f"✅ Created policy: {policy_name}") + policy_arn = policy_response["Policy"]["Arn"] + + # Attach policy to role + try: + iam.attach_role_policy(RoleName=role_name, PolicyArn=policy_arn) + print("✅ Attached policy to role") + except Exception as e: + if "already attached" in str(e).lower(): + print("ℹ️ Policy already attached to role") + else: + raise + + print(f"Policy ARN: {policy_arn}") + + put_ssm_parameter( + "/app/customersupport/agentcore/runtime_execution_role_arn", + role_response["Role"]["Arn"], + ) + return role_response["Role"]["Arn"] + + except Exception as e: + print(f"❌ Error creating IAM role: {str(e)}") + return None + + +def delete_agentcore_runtime_execution_role(): + iam = boto3.client("iam") + + try: + account_id = boto3.client("sts").get_caller_identity()["Account"] + policy_arn = f"arn:aws:iam::{account_id}:policy/{policy_name}" + + # Detach policy from role + try: + iam.detach_role_policy(RoleName=role_name, PolicyArn=policy_arn) + print("✅ Detached policy from role") + except Exception: + pass + + # Delete role + try: + iam.delete_role(RoleName=role_name) + print(f"✅ Deleted role: {role_name}") + except Exception: + pass + + # Delete policy + try: + iam.delete_policy(PolicyArn=policy_arn) + print(f"✅ Deleted policy: {policy_name}") + except Exception: + pass + + delete_ssm_parameter( + "/app/customersupport/agentcore/runtime_execution_role_arn" + ) + + except Exception as e: + print(f"❌ Error during cleanup: {str(e)}") + + +def agentcore_memory_cleanup(memory_id: str = None): + """List all memories and their associated strategies""" + control_client = boto3.client("bedrock-agentcore-control", region_name=REGION) + if memory_id: + response = control_client.delete_memory(memoryId=memory_id) + print(f"✅ Successfully deleted memory: {memory_id}") + else: + next_token = None + while True: + # Build request parameters + params = {} + if next_token: + params["nextToken"] = next_token + + # List memories + try: + response = control_client.list_memories(**params) + + # Process each memory + for memory in response.get("memories", []): + memory_id = memory.get("id") + print(f"\nMemory ID: {memory_id}") + print(f"Status: {memory.get('status')}") + response = control_client.delete_memory(memoryId=memory_id) + response = control_client.list_memories(**params) + print(f"✅ Successfully deleted memory: {memory_id}") + + response = control_client.list_memories(**params) + # Process each memory status + for memory in response.get("memories", []): + memory_id = memory.get("id") + print(f"\nMemory ID: {memory_id}") + print(f"Status: {memory.get('status')}") + + except Exception as e: + print(f"⚠️ Error getting memory details: {e}") + # Check for more results + next_token = response.get("nextToken") + if not next_token: + break + + +def gateway_target_cleanup(gateway_id: str = None): + if not gateway_id: + gateway_client = boto3.client( + "bedrock-agentcore-control", + region_name=REGION, + ) + response = gateway_client.list_gateways() + gateway_id = response["items"][0]["gatewayId"] + print(f"🗑️ Deleting all targets for gateway: {gateway_id}") + + # List and delete all targets + list_response = gateway_client.list_gateway_targets( + gatewayIdentifier=gateway_id, maxResults=100 + ) + + for item in list_response["items"]: + target_id = item["targetId"] + print(f" Deleting target: {target_id}") + gateway_client.delete_gateway_target( + gatewayIdentifier=gateway_id, targetId=target_id + ) + print(f" ✅ Target {target_id} deleted") + + # Delete the gateway + print(f"🗑️ Deleting gateway: {gateway_id}") + gateway_client.delete_gateway(gatewayIdentifier=gateway_id) + print(f"✅ Gateway {gateway_id} deleted successfully") + + +def runtime_resource_cleanup(runtime_arn: str = None): + try: + # Initialize AWS clients + agentcore_control_client = boto3.client( + "bedrock-agentcore-control", region_name=REGION + ) + if runtime_arn: + runtime_id = runtime_arn.split(":")[-1].split("/")[-1] + response = agentcore_control_client.delete_agent_runtime( + agentRuntimeId=runtime_id + ) + print(f" ✅ Agent runtime deleted: {response['status']}") + else: + ecr_client = boto3.client("ecr", region_name=REGION) + + # Delete the AgentCore Runtime + # print(" 🗑️ Deleting AgentCore Runtime...") + runtimes = agentcore_control_client.list_agent_runtimes() + for runtime in runtimes["agentRuntimes"]: + response = agentcore_control_client.delete_agent_runtime( + agentRuntimeId=runtime["agentRuntimeId"] + ) + print(f" ✅ Agent runtime deleted: {response['status']}") + + # Delete the ECR repository + print(" 🗑️ Deleting ECR repository...") + repositories = ecr_client.describe_repositories() + for repo in repositories["repositories"]: + if "bedrock-agentcore-customer_support_agent" in repo["repositoryName"]: + ecr_client.delete_repository( + repositoryName=repo["repositoryName"], force=True + ) + print(f" ✅ ECR repository deleted: {repo['repositoryName']}") + + except Exception as e: + print(f" ⚠️ Error during runtime cleanup: {e}") + + +def delete_observability_resources(): + # Configuration + log_group_name = "agents/customer-support-assistant-logs" + log_stream_name = "default" + + logs_client = boto3.client("logs", region_name=REGION) + + # Delete log stream first (must be done before deleting log group) + try: + print(f" 🗑️ Deleting log stream '{log_stream_name}'...") + logs_client.delete_log_stream( + logGroupName=log_group_name, logStreamName=log_stream_name + ) + print(f" ✅ Log stream '{log_stream_name}' deleted successfully") + except Exception as e: + if e.response["Error"]["Code"] == "ResourceNotFoundException": + print(f" ℹ️ Log stream '{log_stream_name}' doesn't exist") + else: + print(f" ⚠️ Error deleting log stream: {e}") + + # Delete log group + try: + print(f" 🗑️ Deleting log group '{log_group_name}'...") + logs_client.delete_log_group(logGroupName=log_group_name) + print(f" ✅ Log group '{log_group_name}' deleted successfully") + except Exception as e: + if e.response["Error"]["Code"] == "ResourceNotFoundException": + print(f" ℹ️ Log group '{log_group_name}' doesn't exist") + else: + print(f" ⚠️ Error deleting log group: {e}") + + +def local_file_cleanup(): + # List of files to clean up + files_to_delete = [ + "Dockerfile", + ".dockerignore", + ".bedrock_agentcore.yaml", + "customer_support_agent.py", + "agent_runtime.py", + ] + + deleted_files = [] + missing_files = [] + + for file in files_to_delete: + if os.path.exists(file): + try: + os.unlink(file) + deleted_files.append(file) + print(f" ✅ Deleted {file}") + except Exception as e: + print(f" ⚠️ Error deleting {file}: {e}") + else: + missing_files.append(file) + + if deleted_files: + print(f"\n📁 Successfully deleted {len(deleted_files)} files") + if missing_files: + print( + f"ℹ️ {len(missing_files)} files were already missing: {', '.join(missing_files)}" + ) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 94fd23f36..9a27f8b9f 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -44,3 +44,4 @@ - Chintan Patel - Shreyas Subramanian - David Kaleko +- joseanavarrom